diff --git a/requirements.txt b/requirements.txt index d588476..14c74ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,15 @@ -y-py==0.6.2 +aiosqlite==0.20.0 +anyio==4.3.0 +exceptiongroup==1.2.0 +idna==3.6 +iniconfig==2.0.0 +packaging==24.0 +pluggy==1.4.0 +pyperclip==1.8.2 +ruff==0.3.3 +sniffio==1.3.1 +tomli==2.0.1 +typing_extensions==4.10.0 websockets==12.0 +y-py==0.6.2 ypy-websocket==0.12.4 diff --git a/src/internal/objects/builder.py b/src/internal/objects/builder.py index d6e59a1..b754076 100644 --- a/src/internal/objects/builder.py +++ b/src/internal/objects/builder.py @@ -22,17 +22,27 @@ def build_from_serialized( # because it will be possible that repository and object will have different brokers. # myb we want to restrict that def build_by_type( - type: BoardObjectType, - pub_sub_broker: internal.pub_sub.interfaces.IPubSubBroker, - **kwargs + type: BoardObjectType, pub_sub_broker: internal.pub_sub.interfaces.IPubSubBroker, **kwargs ) -> interfaces.IBoardObjectWithPosition: id = generate_object_id() if 'position' in kwargs and isinstance(kwargs['position'], internal.models.Position): - return TYPE_IMPLS[type](id, datetime.now().replace(microsecond=0), kwargs['position'], pub_sub_broker) + return TYPE_IMPLS[type]( + id, datetime.now().replace(microsecond=0), kwargs['position'], pub_sub_broker + ) if type == BoardObjectType.GROUP and 'children_ids' in kwargs: - return TYPE_IMPLS[type](id, datetime.now().replace(microsecond=0), pub_sub_broker, kwargs['children_ids']) + return TYPE_IMPLS[type]( + id, datetime.now().replace(microsecond=0), pub_sub_broker, kwargs['children_ids'] + ) if type == BoardObjectType.CONNECTOR and 'start_id' in kwargs and 'end_id' in kwargs: - return TYPE_IMPLS[type](id, datetime.now().replace(microsecond=0), pub_sub_broker, kwargs['start_id'], kwargs['end_id']) + return TYPE_IMPLS[type]( + id, + datetime.now().replace(microsecond=0), + pub_sub_broker, + kwargs['start_id'], + kwargs['end_id'], + ) if type == BoardObjectType.PEN and 'points' in kwargs: - return TYPE_IMPLS[type](id, datetime.now().replace(microsecond=0), pub_sub_broker, kwargs['points']) + return TYPE_IMPLS[type]( + id, datetime.now().replace(microsecond=0), pub_sub_broker, kwargs['points'] + ) raise ValueError('No object to build') diff --git a/src/internal/objects/impl/table.py b/src/internal/objects/impl/table.py index 5ea1d9f..c745f2e 100644 --- a/src/internal/objects/impl/table.py +++ b/src/internal/objects/impl/table.py @@ -30,7 +30,7 @@ def __init__( height: float = 30, col_widths: list[float] = None, row_heights: list[float] = None, - linked_objects: dict[str, list] = dict() # noqa + linked_objects: dict[str, list] = None, ): super().__init__(id, types.BoardObjectType.TABLE, create_dttm, position, pub_sub_broker) self.default_width = width @@ -43,7 +43,10 @@ def __init__( self.rows_height = [self.default_height] * rows else: self.rows_height = row_heights - self.linked_objects = linked_objects + if linked_objects: + self.linked_objects = linked_objects + else: + self.linked_objects = {} def serialize(self) -> dict: serialized = super().serialize() @@ -58,7 +61,8 @@ def serialize(self) -> dict: @staticmethod def from_serialized( - data: dict, pub_sub_broker: internal.pub_sub.interfaces.IPubSubBroker, + data: dict, + pub_sub_broker: internal.pub_sub.interfaces.IPubSubBroker, ) -> BoardObjectTable: # TODO: child class should not know how to build parent from serialized data return BoardObjectTable( diff --git a/src/internal/objects/impl/test_table.py b/src/internal/objects/impl/test_table.py index a039d7d..4e9d589 100644 --- a/src/internal/objects/impl/test_table.py +++ b/src/internal/objects/impl/test_table.py @@ -18,13 +18,21 @@ def test_board_object_table_serialization(): height = 30 col_widths = [width] * columns row_heights = [height] * rows - linked_objects = dict() + linked_objects = {} broker = internal.pub_sub.mocks.MockPubSubBroker() table_object = BoardObjectTable( - id, create_dttm, position, broker, - columns, rows, width, height, col_widths, - row_heights, linked_objects + id, + create_dttm, + position, + broker, + columns, + rows, + width, + height, + col_widths, + row_heights, + linked_objects, ) assert table_object.serialize() == { 'id': id, @@ -52,7 +60,7 @@ def test_board_object_table_deserialization(): height = 30 col_widths = [width] * columns row_heights = [height] * rows - linked_objects = dict() + linked_objects = {} serialized = { 'id': id, diff --git a/src/internal/objects/test_builder.py b/src/internal/objects/test_builder.py index 69215bf..2f75092 100644 --- a/src/internal/objects/test_builder.py +++ b/src/internal/objects/test_builder.py @@ -62,7 +62,7 @@ def test_group_building(): 'type': 'group', 'create_dttm': datetime.now().strftime('%Y-%m-%dT%H-%M-%SZ'), 'id': generate_object_id(), - 'children_ids': [generate_object_id, generate_object_id] + 'children_ids': [generate_object_id, generate_object_id], } broker = internal.pub_sub.mocks.MockPubSubBroker() @@ -81,7 +81,7 @@ def test_connector_building(): 'color': 'black', 'width': 2, 'connector_type': 'curved', - 'stroke_style': 'left' + 'stroke_style': 'left', } broker = internal.pub_sub.mocks.MockPubSubBroker() @@ -89,12 +89,13 @@ def test_connector_building(): assert isinstance(connector, BoardObjectConnector) assert connector.serialize() == serialized_connector + def test_table_building(): serialized_table = { 'id': generate_object_id(), 'create_dttm': datetime.now().strftime('%Y-%m-%dT%H-%M-%SZ'), 'type': 'table', - 'position': {'x': 1, 'y': 2, 'z': 3}, + 'position': {'x': 1, 'y': 2, 'z': 3}, 'table-columns': 2, 'table-rows': 2, 'columns-width': [50, 50], @@ -108,4 +109,3 @@ def test_table_building(): table = internal.objects.build_from_serialized(serialized_table, broker) assert isinstance(table, BoardObjectTable) assert table.serialize() == serialized_table - diff --git a/src/internal/repositories/mocks/repository.py b/src/internal/repositories/mocks/repository.py index 1c21377..9fa07e9 100644 --- a/src/internal/repositories/mocks/repository.py +++ b/src/internal/repositories/mocks/repository.py @@ -24,4 +24,4 @@ def delete(self, object_id: internal.objects.interfaces.ObjectId) -> None: raise NotImplementedError() def get_updated(self) -> dict[internal.objects.interfaces.ObjectId, Optional[dict]]: - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/src/internal/storages/impl/shared_ydoc_storage.py b/src/internal/storages/impl/shared_ydoc_storage.py index 2133b2e..55de681 100644 --- a/src/internal/storages/impl/shared_ydoc_storage.py +++ b/src/internal/storages/impl/shared_ydoc_storage.py @@ -36,7 +36,7 @@ def get_websocket_provider(self, websocket: Websocket): def _transaction_callback_obj(self, event: YMapEvent): for obj_id, change in event.keys.items(): - obj = dict() + obj = {} obj['obj_repr'] = change['newValue'] if change['action'] != 'delete' else None obj['obj_action'] = change['action'] obj['obj_id'] = obj_id diff --git a/src/internal/storages/impl/test_shared_ydoc_storage.py b/src/internal/storages/impl/test_shared_ydoc_storage.py index 51d11d0..ff42a7e 100644 --- a/src/internal/storages/impl/test_shared_ydoc_storage.py +++ b/src/internal/storages/impl/test_shared_ydoc_storage.py @@ -39,7 +39,7 @@ async def test_shared_ydoc_storage_get_updates(): assert obj['obj_repr'] == updates[obj['obj_id']] # client 2 - storage_2 = SharedYDocStorage(board_name) + storage_2 = SharedYDocStorage(board_name, board_key) assert storage_2.is_empty_updates() async with connect(storage_2.get_uri_connection()) as websocket: async with storage_2.get_websocket_provider(websocket): # type: ignore diff --git a/src/internal/view/choose_board/__init__.py b/src/internal/view/choose_board/__init__.py index e69de29..b4b6359 100644 --- a/src/internal/view/choose_board/__init__.py +++ b/src/internal/view/choose_board/__init__.py @@ -0,0 +1 @@ +from .choose_board import get_board_info as get_board_info diff --git a/src/internal/view/choose_board/choose_board.py b/src/internal/view/choose_board/choose_board.py index c778cbe..d204f27 100644 --- a/src/internal/view/choose_board/choose_board.py +++ b/src/internal/view/choose_board/choose_board.py @@ -1,17 +1,20 @@ import pathlib import tkinter import uuid +import dataclasses +import os from tkinter import ttk import pyperclip -def _load_from_file(path_to_file: pathlib.Path): - with open(path_to_file, 'r') as file: - res = [list(line.split('#')) for line in file] - board_names_keys = {str(line[0]): str(line[1]) for line in res} - - return board_names_keys +def _load_available_boards_from_file(path_to_file: pathlib.Path): + boards = {} + if os.path.exists(path_to_file): + with open(path_to_file, 'r') as file: + res = [list(line.split('#')) for line in file] + boards = {str(line[0]): str(line[1]) for line in res} + return boards def _is_valid_uuid(uuid_to_test, version=4): @@ -22,8 +25,13 @@ def _is_valid_uuid(uuid_to_test, version=4): return str(uuid_obj) == uuid_to_test -def _create_new_board(board_name_entry: ttk.Entry, board_key_entry: ttk.Entry, listbox: tkinter.Listbox, - path_to_file: str, available_boards: dict): +def _create_new_board( + board_name_entry: ttk.Entry, + board_key_entry: ttk.Entry, + listbox: tkinter.Listbox, + path_to_file: str, + available_boards: dict, +): board_name = board_name_entry.get() board_name_entry.delete(0, tkinter.END) @@ -40,7 +48,7 @@ def _create_new_board(board_name_entry: ttk.Entry, board_key_entry: ttk.Entry, l with open(path_to_file, 'a') as file: if available_boards: file.write('\n') - file.write(board_name + "#" + board_key) + file.write(board_name + '#' + board_key) available_boards[board_name] = board_key listbox.insert(tkinter.END, board_name) @@ -59,13 +67,19 @@ def _copy_key(available_boards: dict, boards_listbox: tkinter.Listbox): pyperclip.paste() -def get_board_name_key(): +@dataclasses.dataclass +class BoardInfo: + name: str + access_key: str + + +def get_board_info() -> BoardInfo: window = tkinter.Tk(className='Choose board') window.geometry('600x300') - path_to_file = "boards.txt" + path_to_file = 'boards.txt' - available_boards = _load_from_file(path_to_file) + available_boards = _load_available_boards_from_file(path_to_file) boards_listbox = tkinter.Listbox() boards_listbox.grid(row=2, column=0, columnspan=3, sticky=tkinter.EW, padx=5, pady=5) for board_name in available_boards.keys(): @@ -73,49 +87,52 @@ def get_board_name_key(): new_board_name_entry = ttk.Entry() new_board_name_entry.grid(column=1, row=0, padx=6, pady=6, sticky=tkinter.EW) - board_name_label = ttk.Label(text="Название доски") + board_name_label = ttk.Label(text='Название доски') board_name_label.grid(column=0, row=0, padx=6, pady=6, sticky=tkinter.EW) new_board_key_entry = ttk.Entry() new_board_key_entry.grid(column=1, row=1, padx=6, pady=6, sticky=tkinter.EW) - board_key_label = ttk.Label(text="Ключ доступа, если есть") + board_key_label = ttk.Label(text='Ключ доступа, если есть') board_key_label.grid(column=0, row=1, padx=6, pady=6, sticky=tkinter.EW) create_button = ttk.Button( text='Создать новую доску', - command=lambda board_name_entry_arg=new_board_name_entry, - board_key_entry_arg=new_board_key_entry, - boards_listbox_arg=boards_listbox, - path_to_file_arg=path_to_file, - available_boards_arg=available_boards: _create_new_board( + command=lambda board_name_entry_arg=new_board_name_entry, board_key_entry_arg=new_board_key_entry, boards_listbox_arg=boards_listbox, path_to_file_arg=path_to_file, available_boards_arg=available_boards: _create_new_board( board_name_entry_arg, board_key_entry_arg, boards_listbox_arg, path_to_file_arg, - available_boards_arg - ) + available_boards_arg, + ), ) create_button.grid(column=2, row=1, padx=6, pady=6) open_button = ttk.Button( - text='Открыть выбранную доску', command=lambda window_arg=window: window_arg.quit()) + text='Открыть выбранную доску', command=lambda window_arg=window: window_arg.quit() + ) open_button.grid(row=3, column=2, padx=5, pady=5) open_button['state'] = 'disabled' copy_key_button = ttk.Button( text='Скопировать ключ доски', - command=lambda available_boards_arg=available_boards, boards_listbox_arg=boards_listbox: - _copy_key(available_boards_arg, boards_listbox_arg)) + command=lambda available_boards_arg=available_boards, boards_listbox_arg=boards_listbox: _copy_key( + available_boards_arg, boards_listbox_arg + ), + ) copy_key_button.grid(row=3, column=0, padx=5, pady=5) copy_key_button['state'] = 'disabled' - boards_listbox.bind('<>', - lambda _, open_button_arg=open_button, copy_key_button_arg=copy_key_button: - _set_buttons_state_to_normal(open_button_arg, copy_key_button_arg), ) + boards_listbox.bind( + '<>', + lambda _, open_button_arg=open_button, copy_key_button_arg=copy_key_button: _set_buttons_state_to_normal( + open_button_arg, copy_key_button_arg + ), + ) window.mainloop() + # TODO: отлавливать исключение selected_board = boards_listbox.curselection() selected_board_name = boards_listbox.get(selected_board) selected_board_key = available_boards[selected_board_name] window.destroy() - return selected_board_name, selected_board_key + return BoardInfo(selected_board_name, selected_board_key) diff --git a/src/internal/view/objects/impl/object.py b/src/internal/view/objects/impl/object.py index 15c5dd1..038edd2 100644 --- a/src/internal/view/objects/impl/object.py +++ b/src/internal/view/objects/impl/object.py @@ -89,10 +89,7 @@ def aligning(self, dependencies: internal.view.dependencies.Dependencies): # flag = False top = dependencies.canvas.find_overlapping( - obj_frame[0] - _PADDING, - obj_frame[1] - _PADDING, - obj_frame[2] + _PADDING, - obj_frame[1] + obj_frame[0] - _PADDING, obj_frame[1] - _PADDING, obj_frame[2] + _PADDING, obj_frame[1] ) for item in top: tags = dependencies.canvas.gettags(item) @@ -128,7 +125,7 @@ def aligning(self, dependencies: internal.view.dependencies.Dependencies): obj_frame[0] - _PADDING, obj_frame[3], obj_frame[2] + _PADDING, - obj_frame[3] + _PADDING + obj_frame[3] + _PADDING, ) for item in bottom: tags = dependencies.canvas.gettags(item) @@ -159,10 +156,7 @@ def aligning(self, dependencies: internal.view.dependencies.Dependencies): return self.remove_aligning(dependencies) left = dependencies.canvas.find_overlapping( - obj_frame[0] - _PADDING, - obj_frame[1] - _PADDING, - obj_frame[0], - obj_frame[3] + _PADDING + obj_frame[0] - _PADDING, obj_frame[1] - _PADDING, obj_frame[0], obj_frame[3] + _PADDING ) for item in left: tags = dependencies.canvas.gettags(item) @@ -197,7 +191,7 @@ def aligning(self, dependencies: internal.view.dependencies.Dependencies): obj_frame[2], obj_frame[1] - _PADDING, obj_frame[2] + _PADDING, - obj_frame[3] + _PADDING + obj_frame[3] + _PADDING, ) for item in right: tags = dependencies.canvas.gettags(item) @@ -206,8 +200,7 @@ def aligning(self, dependencies: internal.view.dependencies.Dependencies): continue obj_x1, obj_y1, obj_x2, obj_y2 = dependencies.canvas.bbox(tags[0]) if not ( - obj_y1 in [obj_frame[1], obj_frame[3]] or - obj_y2 in [obj_frame[1], obj_frame[3]] + obj_y1 in [obj_frame[1], obj_frame[3]] or obj_y2 in [obj_frame[1], obj_frame[3]] ): continue diff --git a/src/main.py b/src/main.py index 5767d70..a525577 100644 --- a/src/main.py +++ b/src/main.py @@ -55,16 +55,8 @@ async def create_tasks(tasks): async def main(): parser = argparse.ArgumentParser() - # board_name, board_key = get_board_name_key() - board_name, board_key = 'Whiteboard', 'board_sardor' - - parser.add_argument( - 'board-name', - type=str, - help='server board name', - nargs='?', - default='sample_board', - ) + # TODO: утащить куда-то во view + board_info = internal.view.choose_board.get_board_info() parser.add_argument( 'logging-level', @@ -84,7 +76,7 @@ async def main(): logging.debug('initializing pubsub broker') broker = internal.pub_sub.impl.PubSubBroker() logging.debug('initializing storage') - storage = internal.storages.impl.SharedYDocStorage(board_name, board_key) + storage = internal.storages.impl.SharedYDocStorage(board_info.name, board_info.access_key) serialized_objects = storage.get_serialized_objects() objects = [] for serialized_obj in serialized_objects.values(): @@ -109,7 +101,7 @@ async def main(): logging.debug('initializing tkinter') view = internal.view.view.create_view( - controller=controller, repo=repo, pub_sub=broker, board_name=board_name + controller=controller, repo=repo, pub_sub=broker, board_name=board_info.name ) logging.debug('run client connection, get updates from server and tkinter UI update')