diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1502f9b..b84f744 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,38 +16,9 @@ repos: - id: check-builtin-literals - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 23.12.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.9 hooks: - - id: black - args: ["--line-length", "100"] - - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - files: \.py$ - args: [--profile=black] - - - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 - hooks: - - id: pyupgrade - args: [--py37-plus] - - - repo: https://github.com/PyCQA/doc8 - rev: v1.1.1 - hooks: - - id: doc8 - args: [--max-line-length=200] - exclude: docs/source/other/full-config.rst - stages: [manual] - - - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.2.3 - hooks: - - id: Flake8-pyproject - alias: flake8 - additional_dependencies: - ["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"] - stages: [manual] + - id: ruff + args: [--fix, --show-fixes] + - id: ruff-format diff --git a/pycrdt_websocket/__init__.py b/pycrdt_websocket/__init__.py index 9a561b4..e13d154 100644 --- a/pycrdt_websocket/__init__.py +++ b/pycrdt_websocket/__init__.py @@ -1,6 +1,7 @@ -from .asgi_server import ASGIServer # noqa -from .websocket_provider import WebsocketProvider # noqa -from .websocket_server import WebsocketServer, YRoom # noqa -from .yutils import YMessageType # noqa +from .asgi_server import ASGIServer as ASGIServer +from .websocket_provider import WebsocketProvider as WebsocketProvider +from .websocket_server import WebsocketServer as WebsocketServer +from .websocket_server import YRoom as YRoom +from .yutils import YMessageType as YMessageType __version__ = "0.12.6" diff --git a/pycrdt_websocket/asgi_server.py b/pycrdt_websocket/asgi_server.py index 5815a67..cff64d2 100644 --- a/pycrdt_websocket/asgi_server.py +++ b/pycrdt_websocket/asgi_server.py @@ -64,7 +64,8 @@ def __init__( Arguments: websocket_server: An instance of WebsocketServer. - on_connect: An optional callback to call when connecting the WebSocket. If the callback returns True, the WebSocket is not accepted. + on_connect: An optional callback to call when connecting the WebSocket. + If the callback returns True, the WebSocket is not accepted. on_disconnect: An optional callback called when disconnecting the WebSocket. """ self._websocket_server = websocket_server diff --git a/pycrdt_websocket/django_channels_consumer.py b/pycrdt_websocket/django_channels_consumer.py index c2e6e21..9f917b4 100644 --- a/pycrdt_websocket/django_channels_consumer.py +++ b/pycrdt_websocket/django_channels_consumer.py @@ -3,7 +3,7 @@ from logging import getLogger from typing import TypedDict -from channels.generic.websocket import AsyncWebsocketConsumer # type: ignore +from channels.generic.websocket import AsyncWebsocketConsumer # type: ignore[import-not-found] from pycrdt import Doc from .websocket import Websocket diff --git a/pycrdt_websocket/websocket.py b/pycrdt_websocket/websocket.py index 7714cc3..1fecaba 100644 --- a/pycrdt_websocket/websocket.py +++ b/pycrdt_websocket/websocket.py @@ -1,15 +1,11 @@ -import sys - -if sys.version_info >= (3, 8): - from typing import Protocol -else: - from typing_extensions import Protocol +from typing import Protocol class Websocket(Protocol): """WebSocket. - The Websocket instance can receive messages using an async iterator, until the connection is closed: + The Websocket instance can receive messages using an async iterator, + until the connection is closed: ```py async for message in websocket: ... diff --git a/pycrdt_websocket/websocket_server.py b/pycrdt_websocket/websocket_server.py index 0f0e666..7515b1d 100644 --- a/pycrdt_websocket/websocket_server.py +++ b/pycrdt_websocket/websocket_server.py @@ -80,7 +80,8 @@ async def start_room(self, room: YRoom) -> None: """ if self._task_group is None: raise RuntimeError( - "The WebsocketServer is not running: use `async with websocket_server:` or `await websocket_server.start()`" + "The WebsocketServer is not running: use `async with websocket_server:` or " + "`await websocket_server.start()`" ) if not room.started.is_set(): @@ -137,7 +138,8 @@ async def serve(self, websocket: Websocket) -> None: """ if self._task_group is None: raise RuntimeError( - "The WebsocketServer is not running: use `async with websocket_server:` or `await websocket_server.start()`" + "The WebsocketServer is not running: use `async with websocket_server:` or " + "`await websocket_server.start()`" ) async with create_task_group() as tg: diff --git a/pycrdt_websocket/yroom.py b/pycrdt_websocket/yroom.py index 4818a23..23c3582 100644 --- a/pycrdt_websocket/yroom.py +++ b/pycrdt_websocket/yroom.py @@ -114,7 +114,8 @@ def on_message(self) -> Callable[[bytes], Awaitable[bool] | bool] | None: def on_message(self, value: Callable[[bytes], Awaitable[bool] | bool] | None): """ Arguments: - value: An optional callback to call when a message is received. If the callback returns True, the message is skipped. + value: An optional callback to call when a message is received. + If the callback returns True, the message is skipped. """ self._on_message = value @@ -221,7 +222,8 @@ async def serve(self, websocket: Websocket): ) for client in self.clients: self.log.debug( - "Sending Y awareness from client with endpoint %s to client with endpoint: %s", + "Sending Y awareness from client with endpoint " + "%s to client with endpoint: %s", websocket.path, client.path, ) diff --git a/pycrdt_websocket/ystore.py b/pycrdt_websocket/ystore.py index aa91c38..ab74d23 100644 --- a/pycrdt_websocket/ystore.py +++ b/pycrdt_websocket/ystore.py @@ -41,8 +41,9 @@ async def write(self, data: bytes) -> None: ... @abstractmethod - async def read(self) -> AsyncIterator[tuple[bytes, bytes]]: - ... + async def read(self) -> AsyncIterator[tuple[bytes, bytes, float]]: + if False: + yield @property def started(self) -> Event: @@ -125,7 +126,7 @@ async def apply_updates(self, ydoc: Doc) -> None: Arguments: ydoc: The YDoc on which to apply the updates. """ - async for update, *rest in self.read(): # type: ignore + async for update, *rest in self.read(): ydoc.apply_update(update) @@ -179,16 +180,16 @@ async def check_version(self) -> int: move_file = True if move_file: new_path = await get_new_path(self.path) - self.log.warning(f"YStore version mismatch, moving {self.path} to {new_path}") + self.log.warning("YStore version mismatch, moving %s to %s", self.path, new_path) await anyio.Path(self.path).rename(new_path) if version_mismatch: async with await anyio.open_file(self.path, "wb") as f: - version_bytes = f"VERSION:{self.version}\n".encode() # noqa + version_bytes = f"VERSION:{self.version}\n".encode() await f.write(version_bytes) offset = len(version_bytes) return offset - async def read(self) -> AsyncIterator[tuple[bytes, bytes, float]]: # type: ignore + async def read(self) -> AsyncIterator[tuple[bytes, bytes, float]]: """Async iterator for reading the store content. Returns: @@ -350,7 +351,8 @@ async def _init_db(self): async with self.lock: async with aiosqlite.connect(self.db_path) as db: cursor = await db.execute( - "SELECT count(name) FROM sqlite_master WHERE type='table' and name='yupdates'" + "SELECT count(name) FROM sqlite_master " + "WHERE type='table' and name='yupdates'" ) table_exists = (await cursor.fetchone())[0] if table_exists: @@ -363,13 +365,14 @@ async def _init_db(self): create_db = True if move_db: new_path = await get_new_path(self.db_path) - self.log.warning(f"YStore version mismatch, moving {self.db_path} to {new_path}") + self.log.warning("YStore version mismatch, moving %s to %s", self.db_path, new_path) await anyio.Path(self.db_path).rename(new_path) if create_db: async with self.lock: async with aiosqlite.connect(self.db_path) as db: await db.execute( - "CREATE TABLE yupdates (path TEXT NOT NULL, yupdate BLOB, metadata BLOB, timestamp REAL NOT NULL)" + "CREATE TABLE yupdates (path TEXT NOT NULL, yupdate BLOB, " + "metadata BLOB, timestamp REAL NOT NULL)" ) await db.execute( "CREATE INDEX idx_yupdates_path_timestamp ON yupdates (path, timestamp)" @@ -378,7 +381,7 @@ async def _init_db(self): await db.commit() self.db_initialized.set() - async def read(self) -> AsyncIterator[tuple[bytes, bytes, float]]: # type: ignore + async def read(self) -> AsyncIterator[tuple[bytes, bytes, float]]: """Async iterator for reading the store content. Returns: @@ -412,7 +415,8 @@ async def write(self, data: bytes) -> None: async with aiosqlite.connect(self.db_path) as db: # first, determine time elapsed since last update cursor = await db.execute( - "SELECT timestamp FROM yupdates WHERE path = ? ORDER BY timestamp DESC LIMIT 1", + "SELECT timestamp FROM yupdates WHERE path = ? " + "ORDER BY timestamp DESC LIMIT 1", (self.path,), ) row = await cursor.fetchone() @@ -424,7 +428,7 @@ async def write(self, data: bytes) -> None: async with db.execute( "SELECT yupdate FROM yupdates WHERE path = ?", (self.path,) ) as cursor: - async for update, in cursor: + async for (update,) in cursor: ydoc.apply_update(update) # delete history await db.execute("DELETE FROM yupdates WHERE path = ?", (self.path,)) diff --git a/pycrdt_websocket/yutils.py b/pycrdt_websocket/yutils.py index 56611c3..476ccd9 100644 --- a/pycrdt_websocket/yutils.py +++ b/pycrdt_websocket/yutils.py @@ -78,7 +78,7 @@ def read_message(self) -> bytes | None: if length == 0: return b"" i1 = self.i0 + length - message = self.stream[self.i0 : i1] # noqa + message = self.stream[self.i0 : i1] self.i0 = i1 self.length -= length return message @@ -99,7 +99,7 @@ def read_var_string(self): def put_updates(update_send_stream: MemoryObjectSendStream, event: TransactionEvent) -> None: try: - update = event.update # type: ignore + update = event.update update_send_stream.send_nowait(update) except Exception: pass diff --git a/pyproject.toml b/pyproject.toml index 127451c..9f5db78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ dependencies = [ "anyio >=3.6.2,<5", "aiosqlite >=0.18.0,<1", - "pycrdt >=0.8.1,<0.9.0", + "pycrdt >=0.8.7,<0.9.0", ] [project.optional-dependencies] @@ -66,14 +66,15 @@ include = [ "/tests", ] -[tool.flake8] -ignore = "E501, W503, E402" -exclude = [ - ".github", -] -enable-extensions = "G" -extend-ignore = [ - "G001", "G002", "G004", "G200", "G201", "G202", - # black adds spaces around ':' - "E203", +[tool.ruff] +line-length = 99 +select = [ + "ASYNC", # flake8-async + "E", "F", "W", # default Flake8 + "G", # flake8-logging-format + "I", # isort + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "RUF100", # unused noqa (yesqa) + "UP", # pyupgrade ] diff --git a/tests/conftest.py b/tests/conftest.py index 0aedcc9..d7cddd9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest from pycrdt import Array, Doc -from websockets import serve # type: ignore +from websockets import serve from pycrdt_websocket import WebsocketServer diff --git a/tests/test_asgi.py b/tests/test_asgi.py index 000649c..0936364 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -2,7 +2,7 @@ import uvicorn from anyio import create_task_group, sleep from pycrdt import Doc, Map -from websockets import connect # type: ignore +from websockets import connect from pycrdt_websocket import ASGIServer, WebsocketProvider, WebsocketServer @@ -26,14 +26,14 @@ async def test_asgi(unused_tcp_port): ydoc1["map"] = ymap1 = Map() ymap1["key"] = "value" async with connect( - f"ws://localhost:{unused_tcp_port}/my-roomname" # noqa + f"ws://localhost:{unused_tcp_port}/my-roomname" ) as websocket1, WebsocketProvider(ydoc1, websocket1): await sleep(0.1) # client 2 ydoc2 = Doc() async with connect( - f"ws://localhost:{unused_tcp_port}/my-roomname" # noqa + f"ws://localhost:{unused_tcp_port}/my-roomname" ) as websocket2, WebsocketProvider(ydoc2, websocket2): await sleep(0.1) diff --git a/tests/test_pycrdt_yjs.py b/tests/test_pycrdt_yjs.py index 13924fd..9e438a5 100644 --- a/tests/test_pycrdt_yjs.py +++ b/tests/test_pycrdt_yjs.py @@ -1,7 +1,7 @@ import pytest from anyio import Event, create_task_group, move_on_after, sleep from pycrdt import Array, Doc, Map -from websockets import connect # type: ignore +from websockets import connect from pycrdt_websocket import WebsocketProvider