diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba4f841..88c8f6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,7 @@ repos: hooks: - id: mypy args: [--config-file, pyproject.toml] + exclude: ^src/protocol/ additional_dependencies: - "pydantic>=2.13.4" - "pydantic-settings>=2.15.0" diff --git a/pyproject.toml b/pyproject.toml index 81986eb..30d0343 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,11 @@ strict = true # Pydantic plugin for correct model field inference plugins = ["pydantic.mypy"] + +[[tool.mypy.overrides]] +module = "protocol.*" +ignore_errors = true + [tool.pydantic-mypy] init_forbid_extra = true warn_required_dynamic_aliases = true diff --git a/schema/operation.schema.json b/schema/operation.schema.json index 4342936..6673bcd 100644 --- a/schema/operation.schema.json +++ b/schema/operation.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sadtech.ir/schemas/operation.schema.json", "title": "Operation", - "description": "An operation is a single action that can be performed on the database by sync protocol.", + "description": "An operation is a single action performed on the database.", "type": "object", "properties": { "id": { @@ -40,4 +40,4 @@ "type": "string" } } -} \ No newline at end of file +} diff --git a/schema/snapshot.schema.json b/schema/snapshot.schema.json index 3646e8d..49586a6 100644 --- a/schema/snapshot.schema.json +++ b/schema/snapshot.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sadtech.ir/schemas/snapshot.schema.json", "title": "Snapshot", - "description": "A snapshot is a complete copy of the database on the server. It is sent to the device when the device's local database is out of sync with the server's database.", + "description": "A snapshot is a complete copy of the database on the server.", "type": "object", "properties": { "id": { @@ -23,4 +23,4 @@ "format": "int64" } } -} \ No newline at end of file +} diff --git a/schema/sync-request.schema.json b/schema/sync-request.schema.json index c1dee88..4f688f1 100644 --- a/schema/sync-request.schema.json +++ b/schema/sync-request.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sadtech.ir/schemas/sync-request.schema.json", "title": "SyncRequest", - "description": "A sync request is a request sent by a device to the server to synchronize its local database with the server's database.", + "description": "A sync request is a request sent by a device to the server.", "type": "object", "properties": { "device_id": { @@ -26,4 +26,4 @@ "cursor", "operations" ] -} \ No newline at end of file +} diff --git a/schema/sync-response.schema.json b/schema/sync-response.schema.json index bf93861..7c13e3c 100644 --- a/schema/sync-response.schema.json +++ b/schema/sync-response.schema.json @@ -2,11 +2,11 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sadtech.ir/schemas/sync-response.schema.json", "title": "SyncResponse", - "description": "A sync response is a response sent by the server to a device to synchronize its local database with the server's database.", + "description": "A sync response is a response sent by the server.", "type": "object", "properties": { "cursor": { - "description": "The cursor is a string that represents the last known state of the database on the device. It is used to determine which operations need to be sent to the device.", + "description": "The cursor is an integer that represents the last known state of the database.", "type": "integer" }, "operations": { @@ -25,4 +25,4 @@ "cursor", "operations" ] -} \ No newline at end of file +} diff --git a/src/client/main.py b/src/client/main.py index 59c8139..848f357 100644 --- a/src/client/main.py +++ b/src/client/main.py @@ -1,7 +1,7 @@ import asyncio -from server.types.sync_request import SyncRequest -from server.types.sync_response import SyncResponse +from protocol.sync_request import SyncRequest +from protocol.sync_response import SyncResponse async def run_client() -> None: diff --git a/src/engine/__init__.py b/src/engine/__init__.py index 24b5af6..4bb2053 100644 --- a/src/engine/__init__.py +++ b/src/engine/__init__.py @@ -1,5 +1,5 @@ from engine.adapter import InMemoryAdapter, StorageAdapter -from engine.types.operation import Operation +from protocol.operation import Operation class SyncEngine: diff --git a/src/engine/adapter.py b/src/engine/adapter.py index 0fd6375..67a8942 100644 --- a/src/engine/adapter.py +++ b/src/engine/adapter.py @@ -3,7 +3,7 @@ from __future__ import annotations import threading from typing import Protocol -from engine.types.operation import Operation +from protocol.operation import Operation class StorageAdapter(Protocol): diff --git a/src/generator/languages/templates/python.jinja2 b/src/generator/languages/templates/python.jinja2 index 875c37a..33f8cab 100644 --- a/src/generator/languages/templates/python.jinja2 +++ b/src/generator/languages/templates/python.jinja2 @@ -6,7 +6,9 @@ from pydantic import BaseModel class {{ model.name }}(BaseModel): - """{{ model.description }}""" + """ + {{ model.description }} + """ {% for field in model.fields %} {{ field.name }}: {{ field.type }} @@ -16,5 +18,5 @@ class {{ model.name }}(BaseModel): return self.model_dump_json().encode("utf-8") @classmethod - def decode(cls, data: bytes): - return cls.model_validate_json(data) \ No newline at end of file + def decode(cls, data: bytes) -> "{{ model.name }}": + return cls.model_validate_json(data) diff --git a/src/protocol/__init__.py b/src/protocol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/engine/types/operation.py b/src/protocol/operation.py similarity index 77% rename from src/engine/types/operation.py rename to src/protocol/operation.py index 98fbcfa..910ebc3 100644 --- a/src/engine/types/operation.py +++ b/src/protocol/operation.py @@ -2,13 +2,15 @@ from pydantic import BaseModel class Operation(BaseModel): - """A single action that can be performed on the database by sync protocol.""" + """ + An operation is a single action performed on the database. + """ id: str | None = None type: str | None = None device_id: str | None = None timestamp: str | None = None - payload: dict[str, object] | None = None + payload: dict | None = None entity_type: str | None = None entity_id: str | None = None diff --git a/src/engine/types/snapshot.py b/src/protocol/snapshot.py similarity index 72% rename from src/engine/types/snapshot.py rename to src/protocol/snapshot.py index 9e2ac91..93acdae 100644 --- a/src/engine/types/snapshot.py +++ b/src/protocol/snapshot.py @@ -2,11 +2,13 @@ from pydantic import BaseModel class Snapshot(BaseModel): - """A complete copy of the server's database, sent when a device is out of sync.""" + """ + A snapshot is a complete copy of the database on the server. + """ id: str | None = None cursor: int | None = None - data: dict[str, object] | None = None + data: dict | None = None timestamp: int | None = None def encode(self) -> bytes: diff --git a/src/server/types/sync_request.py b/src/protocol/sync_request.py similarity index 71% rename from src/server/types/sync_request.py rename to src/protocol/sync_request.py index dcf9721..965e48d 100644 --- a/src/server/types/sync_request.py +++ b/src/protocol/sync_request.py @@ -1,10 +1,12 @@ from pydantic import BaseModel -from engine.types.operation import Operation +from .operation import Operation class SyncRequest(BaseModel): - """A sync request sent by a device to synchronize with the server's database.""" + """ + A sync request is a request sent by a device to the server. + """ device_id: str cursor: int diff --git a/src/server/types/sync_response.py b/src/protocol/sync_response.py similarity index 66% rename from src/server/types/sync_response.py rename to src/protocol/sync_response.py index 8d6e53a..03ff773 100644 --- a/src/server/types/sync_response.py +++ b/src/protocol/sync_response.py @@ -1,11 +1,13 @@ from pydantic import BaseModel -from engine.types.operation import Operation -from engine.types.snapshot import Snapshot +from .operation import Operation +from .snapshot import Snapshot class SyncResponse(BaseModel): - """A sync response sent by the server to synchronize a device's local database.""" + """ + A sync response is a response sent by the server. + """ cursor: int operations: list[Operation] diff --git a/src/server/main.py b/src/server/main.py index ef9185b..b2bbe10 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -2,9 +2,8 @@ import asyncio from engine import SyncEngine from engine.adapter import InMemoryAdapter - -from .types.sync_request import SyncRequest -from .types.sync_response import SyncResponse +from protocol.sync_request import SyncRequest +from protocol.sync_response import SyncResponse # One shared engine (and its in-memory log) for the lifetime of the server. # Swap InMemoryAdapter for any StorageAdapter implementation to persist ops. diff --git a/src/transport/tcp.py b/src/transport/tcp.py new file mode 100644 index 0000000..e69de29