class ToDoListOptions(Schema): project = fields.CoalesceField( [fields.Integer(), fields.Text()], default=None, required=False) tag = custom_fields.StringIdentifiedField(models.Tag, default=None, required=False) query = fields.Text(default='') all_tasks = fields.Bool(default=False) flat = fields.Bool(default=False) limit = fields.Integer(default=25) ignore_priority = fields.Bool(default=False) minimum_priority = fields.CoalesceField( [fields.Integer(), fields.Text()], default=None, required=False) state = fields.Enum(models.State, soft_match=True, required=False, default=None) order_by = fields.List( fields.MultiChoiceField( { 'created_at', 'finished_at', 'state', 'priority', 'project', }, soft_match=True, ), required=False, default=None, )
class PrioritySchema(Schema[models.Priority]): id = fields.Integer(read_only=True) name = fields.Text(min=1, max=127, pattern=re.compile(r'\w+')) created_at = fields.Datetime(read_only=True) level = fields.Integer() project = fields.Lambda(lambda p: p.project.name) is_default = fields.Bool(default=False)
class CreateTodoSchema(Schema): tags = fields.List( fields.CoalesceField([ fields.Integer(), fields.Text(min=1, max=127, pattern=re.compile(r'\w+')), ]), default=(), ) project = fields.CoalesceField( [ fields.Integer(), fields.Text(min=1, max=127, pattern=re.compile(r'\w+')), ], required=False, ) parents = fields.List( fields.CoalesceField([ fields.Integer(), fields.Text(), ]), default=(), ) priority = fields.CoalesceField( [ fields.Integer(), fields.Text(min=1, max=127, pattern=re.compile(r'\w+')), ], required=False, )
class StatsOptionsSchema(Schema): project = fields.CoalesceField( [fields.Integer(), fields.Text()], default=None, required=False) tag = custom_fields.StringIdentifiedField(models.Tag, default=None, required=False) top_level_only = fields.Bool(default=True) ignore_priority = fields.Bool(default=False) minimum_priority = fields.CoalesceField( [fields.Integer(), fields.Text()], default=None, required=False) last_n_days = fields.Integer(default=128)
class PickTableSchema(Schema[PickPoint]): global_pick_number = fields.Integer() pack_number = fields.Lambda(lambda p: p.round.pack) pick_number = fields.Integer() pick = fields.Lambda( lambda p: describe_focusable(p.pick.cubeable if isinstance( p.pick, SinglePickPick) else p.pick.pick)) burn = fields.Lambda( lambda p: describe_focusable(p.pick.burn) if isinstance(p.pick, BurnPick) and p.pick.burn is not None else '') pack = fields.Lambda( lambda p: ', '.join(map( describe_focusable, p.booster.cubeables, )))
class AlarmSchema(Schema[models.Alarm]): id = fields.Integer(read_only=True) text = fields.Text() started_at = fields.Datetime(read_only=True) end_at = fields.Datetime() next_reminder_time_target = fields.Datetime(required=False, read_only=True) requires_acknowledgment = fields.Bool(required=False) retry_delay = fields.Integer(required=False, min=5) send_email = fields.Bool(required=False) silent = fields.Bool(required=False) level = fields.Enum(models.ImportanceLevel, required=False) times_notified = fields.Integer(read_only=True) acknowledged = fields.Bool(read_only=True) canceled = fields.Bool(read_only=True) success = fields.Bool(read_only=True)
class FirstToN(MatchOfn): name = 'FTN' options_schema = Schema({'n': fields.Integer(min = 1, max = 128, default = 2)}) def validate_result(self, result: CompletedMatch) -> Errors: errors = [] max_wins = max(result.results.values()) if max_wins != self._n: errors.append( 'match not completed, leader has {} wins, which does not match required {}.'.format( max_wins, self._n, ) ) if len(result.winners) > 1: errors.append('match cannot be a draw') return Errors(errors)
class ToDoSchema(Schema[models.ToDo]): id = fields.Integer(read_only=True) text = fields.Text() tags = fields.Lambda(lambda todo: [tag.name for tag in todo.tags]) comments = fields.Lambda( lambda todo: [comment.text for comment in todo.comments]) project = fields.Lambda(lambda todo: todo.project.name) priority = fields.Related(PrioritySchema(), read_only=True) created_at = fields.Datetime(read_only=True) finished_at = fields.Datetime(read_only=True) state = fields.Enum(models.State, read_only=True) children = fields.List(fields.SelfRelated(), read_only=True, source='active_children')
class MatchOfn(MatchType): options_schema = Schema({'n': fields.Integer(min = 1, max = 128, default = 3)}) def __init__(self, n: int, **kwargs): super().__init__(**kwargs) self._n = n @property def n(self) -> int: return self._n def __str__(self) -> str: return f'{self.name}{self._n}' def _serialize_args(self) -> t.Mapping[str, t.Any]: return {'n': self._n} @abstractmethod def validate_result(self, result: CompletedMatch) -> Errors: pass
class GridAligner(Aligner): name = 'Grid' schema = Schema( fields={ 'columns': fields.Integer(default=5, min=1, max=64), # 'margin': fields.Float(default = .05, min = 0., max = 1.), }, ) def __init__(self, scene: SelectionScene, columns: int = 5, margin: float = .05): super().__init__(scene) self._margin = int(IMAGE_WIDTH * margin) self._columns = columns self._cards: t.List[PhysicalCard] = [] @property def options(self) -> t.Mapping[str, t.Any]: return { 'columns': self._columns, } @property def cards(self) -> t.List[PhysicalCard]: return self._cards @property def supports_sort_orientation(self) -> bool: return False @property def supports_sub_sort(self) -> bool: return False def pick_up(self, items: t.Iterable[PhysicalCard]) -> GridPickUp: return GridPickUp( self, items, ) def get_position_at_index(self, idx: int) -> QPoint: return QPoint( max( (idx % self._columns) * (IMAGE_WIDTH + self._margin), 0, ), max( (idx // self._columns) * (IMAGE_HEIGHT + self._margin), 0, ), ) def map_position_to_index(self, position: QPoint) -> int: return min( int(position.x() // (IMAGE_WIDTH + self._margin) + (position.y() // (IMAGE_HEIGHT + self._margin) * self._columns)), len(self._cards), ) def realign(self, from_index: int = 0) -> None: from_index = max(from_index, 0) for card, idx in zip(self._cards[from_index:], range(from_index, len(self._cards))): card.setPos(self.get_position_at_index(idx)) def drop(self, items: t.Iterable[PhysicalCard], position: QPoint) -> GridDrop: return GridDrop( self, items, position, ) def multi_drop( self, drops: t.Sequence[t.Tuple[t.Sequence[PhysicalCard], QPoint]] ) -> GridMultiDrop: return GridMultiDrop( self, drops, ) def sort(self, sort_macro: SortMacro, cards: t.Sequence[PhysicalCard], in_place: bool = False) -> QUndoCommand: return GridSort( grid=self, specifications=list( itertools.chain(*(specifications for dimension, specifications in sort_macro.dimension_specifications_map))), cards=cards, original_order=copy.copy(self._cards), in_place=in_place, ) def context_menu(self, menu: QtWidgets.QMenu, position: QPoint, undo_stack: QUndoStack) -> None: resize_action = QtWidgets.QAction('Set Column Count', menu) resize_action.triggered.connect( lambda: self.update_column_count(menu, undo_stack)) menu.addAction(resize_action) def update_column_count(self, parent: QtWidgets.QWidget, undo_stack: QUndoStack) -> None: amount, ok = QInputDialog.getInt( parent, 'Choose new column count', '', self._columns, 1, 64, ) if ok: undo_stack.push(SetColumnCount( self, amount, ))
class StackingGrid(Aligner): _show_grid = False schema = Schema( fields = { 'rows': fields.Integer(default = 3, min = 1, max = 64), 'columns': fields.Integer(default = 15, min = 1, max = 64), # 'margin': fields.Float(default = STANDARD_IMAGE_MARGIN, min = 0., max = 1.), 'show_grid': fields.Bool(default = False), }, ) def __init__( self, scene: SelectionScene, *, rows: int = 5, columns: int = 5, margin: float = STANDARD_IMAGE_MARGIN, show_grid: bool = False, ): super().__init__(scene) self._stacked_cards: t.Dict[PhysicalCard, _CardInfo] = {} self._margin_pixel_size = margin * IMAGE_WIDTH self._stacker_map = self.create_stacker_map(rows, columns) self._show_grid = show_grid @property def options(self) -> t.Mapping[str, t.Any]: return { 'rows': self._stacker_map.column_height, 'columns': self._stacker_map.row_length, 'show_grid': self._show_grid, } @abstractmethod def create_stacker_map(self, rows: int, columns: int) -> StackerMap: pass @abstractmethod def request_space(self, card_stacker: CardStacker, x: int, y: int) -> None: pass @abstractmethod def create_stacker(self, x: int, y: int) -> CardStacker: pass @property def stacker_map(self) -> StackerMap: return self._stacker_map @property def stacked_cards(self) -> t.Dict[PhysicalCard, _CardInfo]: return self._stacked_cards @property def cards(self) -> t.Iterable[PhysicalCard]: for stacker in self._stacker_map.stackers: yield from stacker.cards def realign(self) -> None: for stacker in self._stacker_map: stacker.update() def get_card_info(self, card: PhysicalCard) -> _CardInfo: try: return self._stacked_cards[card] except KeyError: self._stacked_cards[card] = info = _CardInfo() return info def remove_card(self, card: PhysicalCard) -> None: try: del self._stacked_cards[card] except KeyError: pass def pick_up(self, items: t.Iterable[PhysicalCard]) -> StackingPickUp: return StackingPickUp( self, items, ) def drop(self, items: t.Iterable[PhysicalCard], position: QPoint) -> StackingDrop: x, y = position.x(), position.y() stacker = self.get_card_stacker(x, y) index = stacker.map_position_to_index(x - stacker.x, y - stacker.y) return StackingDrop( self, stacker, index, tuple(items), ) def multi_drop(self, drops: t.Iterable[t.Tuple[t.Sequence[PhysicalCard], QPoint]]) -> StackingMultiDrop: _drops = [] for cards, position in drops: x, y = position.x(), position.y() stacker = self.get_card_stacker(x, y) index = stacker.map_position_to_index(x - stacker.x, y - stacker.y) _drops.append( ( cards, stacker, index, ) ) return StackingMultiDrop(self, _drops) def get_card_stacker_at_index(self, x: int, y: int) -> CardStacker: return self.stacker_map.get_stacker( minmax(0, x, self.stacker_map.row_length - 1), minmax(0, y, self.stacker_map.column_height - 1), ) def get_card_stacker(self, x: int, y: int) -> CardStacker: return self.get_card_stacker_at_index( *self._stacker_map.map_position_to_index(x, y) ) def sort(self, sort_macro: SortMacro, cards: t.Sequence[PhysicalCard], in_place: bool = False) -> QUndoCommand: commands = [] for dimension, specifications in sort_macro.dimension_specifications_map: continuity = sort_macro.continuity_for_dimension(dimension) if continuity == DimensionContinuity.AUTO: if len(specifications) == 1: continuity = continuity.continuity_for(specifications[0].sort_property) else: continuity = DimensionContinuity.CONTINUOUS if dimension == SortDimension.SUB_DIVISIONS: commands.append( SortAllStackers( grid = self, specifications = specifications, ) ) else: commands.append( ( ContinuousSort if continuity == DimensionContinuity.CONTINUOUS else GroupedSort )( grid = self, cards = cards, specifications = specifications, orientation = QtCore.Qt.Horizontal if dimension == SortDimension.HORIZONTAL else QtCore.Qt.Vertical, in_place = in_place, ) ) if len(commands) == 1: return commands[0] return CommandPackage(commands) @classmethod def _get_sort_stack( cls, stacker: CardStacker, sort_property: t.Type[sorting.SortProperty], undo_stack: QUndoStack, ) -> t.Callable[[], None]: def _sort_stack() -> None: undo_stack.push( SortStacker( stacker = stacker, specifications = [sorting.SortSpecification(sort_property = sort_property)], ) ) return _sort_stack def _get_all_sort_stacker( self, sort_property: t.Type[sorting.SortProperty], undo_stack: QUndoStack, ) -> t.Callable[[], None]: def _sort_all_stackers() -> None: undo_stack.push( SortAllStackers( grid = self, specifications = [sorting.SortSpecification(sort_property = sort_property)], ) ) return _sort_all_stackers def insert_row(self, idx: int) -> QUndoCommand: pass def insert_column(self, idx: int) -> QUndoCommand: pass def _set_show_grid(self, show_grid: bool) -> None: self._show_grid = show_grid def context_menu(self, menu: QtWidgets.QMenu, position: QPoint, undo_stack: QUndoStack) -> None: stacker = self.get_card_stacker(position.x(), position.y()) stacker_sort_menu = menu.addMenu('Sort Stack') for sort_property in sorting.SortProperty.names_to_sort_property.values(): sort_action = QtWidgets.QAction(sort_property.name, stacker_sort_menu) sort_action.triggered.connect(self._get_sort_stack(stacker, sort_property, undo_stack)) stacker_sort_menu.addAction(sort_action) all_stacker_sort_menu = menu.addMenu('Sort All Stack') for sort_property in sorting.SortProperty.names_to_sort_property.values(): all_sort_action = QtWidgets.QAction(sort_property.name, all_stacker_sort_menu) all_sort_action.triggered.connect(self._get_all_sort_stacker(sort_property, undo_stack)) all_stacker_sort_menu.addAction(all_sort_action) resize_action = QtWidgets.QAction('Resize grid', menu) resize_action.triggered.connect(lambda: self.resize(undo_stack)) menu.addAction(resize_action) toggle_grid_action = QtWidgets.QAction('Hide Grid' if self._show_grid else 'Show Grid', menu) toggle_grid_action.triggered.connect(lambda: self._set_show_grid(not self._show_grid)) menu.addAction(toggle_grid_action) def resize(self, undo_stack: QUndoStack) -> None: dialog = GridResizeDialog(self) dialog.exec_() if dialog.accepted: undo_stack.push( GridResize( self, *reversed(dialog.get_values()), ) ) def draw_background(self, painter: QtGui.QPainter, rect: QtCore.QRectF) -> None: if not self._show_grid: return painter.setPen( QtGui.QPen( QtGui.QColor(0, 0, 0) ) ) lines = [] for x in list(self.stacker_map.row_termination_points())[:-1]: if rect.left() <= x <= rect.right(): lines.append( QLineF( x, rect.top(), x, rect.bottom(), ) ) for y in list(self.stacker_map.column_termination_points())[:-1]: if rect.top() <= y <= rect.bottom(): lines.append( QLineF( rect.left(), y, rect.right(), y, ) ) painter.drawLines(lines)
try: SC.session.commit() except IntegrityError: SC.session.rollback() return 'Project already exists', status.HTTP_400_BAD_REQUEST return schema.serialize(project) @todo_project_views.route('/project/', methods = ['PATCH']) @inject_schema( Schema( { 'project': StringIdentifiedField(models.Project), 'default_priority_filter': fields.CoalesceField( [fields.Integer(), fields.Text()], default = None, required = False, ), } ), use_args = False, ) def modify_project(project: models.Project, default_priority_filter: t.Union[int, str, None]): if default_priority_filter is not None: default_priority_filter = get_priority_level(SC.session, default_priority_filter, project) project.default_priority_filter = default_priority_filter SC.session.commit() return schemas.ProjectSchema().serialize(project)
class Swiss(Tournament[P]): name = 'swiss' options_schema = Schema( {'rounds': fields.Integer(min=1, max=128, default=3)}) allow_match_draws: bool = True def __init__(self, players: t.FrozenSet[P], rounds: int, seed_map: t.Mapping[P, float] = immutabledict(), **kwargs): super().__init__(players, seed_map, **kwargs) self._rounds = rounds @property def round_amount(self) -> int: return self._rounds def _get_ranked_players( self, previous_rounds: t.Sequence[CompletedRound[P]], ) -> t.Tuple[t.Sequence[t.Tuple[P, int]], t.Mapping[P, int]]: match_wins_map = defaultdict(int) game_wins_map = defaultdict(int) buys_map = defaultdict(int) for _round in previous_rounds: for result in _round.results: if len(result.results) == 1: buys_map[result.results.__iter__().__next__()] += 1 else: for player, wins in result.results.items(): game_wins_map[player] += wins winners = result.winners if len(winners) == 1: match_wins_map[winners.__iter__().__next__()] += 1 opponent_match_wins_map = defaultdict(int) opponent_game_win_percentage = defaultdict(int) for _round in previous_rounds: for result in _round.results: if len(result.results) == 1: continue for player, opponent in itertools.permutations( result.results.keys()): opponent_match_wins_map[player] += match_wins_map[opponent] opponent_game_win_percentage[player] += game_wins_map[ opponent] ranked_players = sorted( [(player, ( match_wins_map[player], opponent_match_wins_map[player], game_wins_map[player], opponent_game_win_percentage[player], -buys_map[player], -self._seed_map.get(player, 0), )) for player in self._players], key=lambda p: p[1], reverse=True, ) result = [] for idx, chunk in enumerate( more_itertools.split_when( ranked_players, lambda a, b: a[1] != b[1], )): for p, _ in chunk: result.append((p, idx)) return result, buys_map def get_round( self, previous_rounds: t.Sequence[CompletedRound[P]] = () ) -> t.Optional[Round[P]]: if len(previous_rounds) >= self._rounds: return None if not previous_rounds: buys_map = defaultdict(int) ranked_players = interleave( sorted(self._players, key=lambda p: (self._seed_map.get(p, 0), random.random())), ) else: _ranked_players, buys_map = self._get_ranked_players( previous_rounds) ranked_players = [] for players in more_itertools.split_when( reversed(_ranked_players), lambda a, b: a[1] != b[1]): random.shuffle(players) for p, _ in players: ranked_players.append(p) matches = [] if len(ranked_players) & 1: min_buys = min(buys_map[player] for player in self._players) for player in reversed(ranked_players): if buys_map[player] == min_buys: matches.append( ScheduledMatch( frozenset((ranked_players.pop( ranked_players.index(player)), )))) break for idx in range(0, len(ranked_players), 2): matches.append( ScheduledMatch(frozenset(ranked_players[idx:idx + 2]))) return Round(frozenset(matches)) def get_ranked_players( self, previous_rounds: t.Sequence[CompletedRound[P]] ) -> t.Sequence[t.Collection[P]]: ranked_players, _ = self._get_ranked_players(previous_rounds) return [[p for p, _ in tier] for tier in more_itertools.split_when( ranked_players, lambda a, b: a[1] != b[1])] def get_result( self, previous_rounds: t.Sequence[CompletedRound[P]] ) -> TournamentResult[P]: if len(previous_rounds) < self._rounds: raise ResultException('tournament not complete') return super().get_result(previous_rounds)
class TagSchema(Schema[models.Tag]): id = fields.Integer(read_only=True) name = fields.Text(min=1, max=127, pattern=re.compile(r'\w+')) created_at = fields.Datetime(read_only=True)
class AlarmListOptions(Schema): limit = fields.Integer(default=25) query = fields.CoalesceField( [fields.Integer(), fields.Text()], default=None, required=False)
class CommentSchema(Schema): target = fields.CoalesceField([fields.Integer(), fields.Text()]) project = custom_fields.StringIdentifiedField(models.Project, default=None) comment = fields.Text()
class TaggedSchema(Schema[models.Tagged]): todo_target = fields.CoalesceField([fields.Integer(), fields.Text()]) tag_target = custom_fields.StringIdentifiedField(models.Tag) project = custom_fields.StringIdentifiedField(models.Project, default=None) recursive = fields.Bool(default=False, write_only=True)
class ProjectSchema(Schema[models.Project]): id = fields.Integer(read_only=True) name = fields.Text(min=1, max=127, pattern=re.compile(r'\w+')) created_at = fields.Datetime(read_only=True) is_default = fields.Bool(default=False) default_priority_filter = fields.Integer(default=None, required=False)
class ModifyPrioritySchema(Schema[models.Tagged]): todo = fields.CoalesceField([fields.Integer(), fields.Text()]) project = custom_fields.StringIdentifiedField(models.Project, default=None) priority = fields.CoalesceField([fields.Integer(), fields.Text()]) recursive = fields.Bool(default=False, write_only=True)
class ModifyToDoSchema(Schema): target = fields.CoalesceField([fields.Integer(), fields.Text()]) project = custom_fields.StringIdentifiedField(models.Project, default=None) description = fields.Text()