From f7529a75c362a3f789f424705c816fc4240704f7 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Wed, 17 Dec 2025 12:45:23 +0300 Subject: [PATCH] rename name to topic --- Dockerfile | 16 +++++-------- Justfile | 2 +- docker-compose.yml | 3 ++- redis_timers/router.py | 4 ++-- tests/test_router.py | 21 ++++------------ tests/test_timers.py | 54 +++++++++++++++++++++--------------------- 6 files changed, 42 insertions(+), 58 deletions(-) diff --git a/Dockerfile b/Dockerfile index 87fbb82..5400070 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,12 @@ FROM python:3.13-slim -# required for psycopg2 -RUN apt update \ - && apt install -y --no-install-recommends \ - build-essential \ - libpq-dev \ - && apt clean \ - && rm -rf /var/lib/apt/lists/* - COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv RUN useradd --no-create-home --gid root runner -ENV UV_PYTHON_PREFERENCE=only-system -ENV UV_NO_CACHE=true +ENV UV_PROJECT_ENVIRONMENT=/code/.venv \ + UV_NO_MANAGED_PYTHON=1 \ + UV_NO_CACHE=true \ + UV_LINK_MODE=copy WORKDIR /code @@ -26,3 +20,5 @@ COPY . . RUN chown -R runner:root /code && chmod -R g=u /code USER runner + +RUN uv sync --all-extras --frozen diff --git a/Justfile b/Justfile index dea9123..9a63c5b 100644 --- a/Justfile +++ b/Justfile @@ -7,7 +7,7 @@ sh: docker compose run --service-ports application bash test *args: down && down - docker compose run application uv run --no-sync pytest {{ args }} + docker compose run application uv run pytest {{ args }} build: docker compose build application diff --git a/docker-compose.yml b/docker-compose.yml index 4efb629..8f71f61 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,8 @@ services: dockerfile: ./Dockerfile restart: always volumes: - - .:/srv/www/ + - .:/code + - /code/.venv depends_on: redis: condition: service_healthy diff --git a/redis_timers/router.py b/redis_timers/router.py index 4bbd61a..c558ef2 100644 --- a/redis_timers/router.py +++ b/redis_timers/router.py @@ -13,7 +13,7 @@ class Router: def handler[T: pydantic.BaseModel]( self, *, - name: str | None = None, + topic: str, schema: type[T], ) -> typing.Callable[ [typing.Callable[[T], typing.Coroutine[None, None, None]]], @@ -24,7 +24,7 @@ def _decorator( ) -> typing.Callable[[T], typing.Coroutine[None, None, None]]: self.handlers.append( Handler( - topic=name or func.__name__, + topic=topic, schema=schema, handler=func, ) diff --git a/tests/test_router.py b/tests/test_router.py index a0ca14f..d84a199 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -13,10 +13,10 @@ class AnotherSchema(pydantic.BaseModel): count: int -def test_register_handler_with_name() -> None: +def test_register_handler() -> None: router: typing.Final = Router() - @router.handler(name="test_timer", schema=SomeSchema) + @router.handler(topic="test_timer", schema=SomeSchema) async def test_handler(data: SomeSchema) -> None: ... assert len(router.handlers) == 1 @@ -26,27 +26,14 @@ async def test_handler(data: SomeSchema) -> None: ... assert handler.handler == test_handler -def test_register_handler_without_name() -> None: - router: typing.Final = Router() - - @router.handler(schema=SomeSchema) - async def my_timer_handler(data: SomeSchema) -> None: ... - - assert len(router.handlers) == 1 - handler: typing.Final = router.handlers[0] - assert handler.topic == "my_timer_handler" - assert handler.schema == SomeSchema - assert handler.handler == my_timer_handler - - def test_register_handler_multiple_handlers() -> None: router: typing.Final = Router() expected_handlers_count: typing.Final = 2 - @router.handler(name="handler1", schema=SomeSchema) + @router.handler(topic="handler1", schema=SomeSchema) async def handler1(data: SomeSchema) -> None: ... - @router.handler(name="handler2", schema=AnotherSchema) + @router.handler(topic="handler2", schema=AnotherSchema) async def handler2(data: AnotherSchema) -> None: ... assert len(router.handlers) == expected_handlers_count diff --git a/tests/test_timers.py b/tests/test_timers.py index b0e3dc1..3de361e 100644 --- a/tests/test_timers.py +++ b/tests/test_timers.py @@ -16,7 +16,7 @@ DUPLICATE_TIMER_COUNT = 2 -class TestPayloadModel(pydantic.BaseModel): +class SomePayloadModel(pydantic.BaseModel): message: str count: int @@ -27,9 +27,9 @@ class AnotherPayloadModel(pydantic.BaseModel): class HandlerResults: def __init__(self) -> None: - self.results: list[TestPayloadModel | AnotherPayloadModel] = [] + self.results: list[SomePayloadModel | AnotherPayloadModel] = [] - def add_result(self, result: TestPayloadModel | AnotherPayloadModel) -> None: + def add_result(self, result: SomePayloadModel | AnotherPayloadModel) -> None: self.results.append(result) def clear(self) -> None: @@ -43,7 +43,7 @@ async def redis_client() -> AsyncGenerator["aioredis.Redis[str]"]: await client.delete(settings.TIMERS_TIMELINE_KEY, settings.TIMERS_PAYLOADS_KEY) yield client finally: - await client.close() + await client.aclose() # type: ignore[attr-defined] @pytest.fixture @@ -57,13 +57,13 @@ async def handler_results() -> AsyncGenerator[HandlerResults]: def timers_instance(redis_client: "aioredis.Redis[str]", handler_results: HandlerResults) -> Timers: router1 = Router() - @router1.handler(schema=TestPayloadModel) - async def test_handler(data: TestPayloadModel) -> None: + @router1.handler(topic="some_topic", schema=SomePayloadModel) + async def test_handler(data: SomePayloadModel) -> None: handler_results.add_result(data) router2 = Router() - @router2.handler(name="another_topic", schema=AnotherPayloadModel) + @router2.handler(topic="another_topic", schema=AnotherPayloadModel) async def another_handler(data: AnotherPayloadModel) -> None: handler_results.add_result(data) @@ -75,25 +75,25 @@ async def another_handler(data: AnotherPayloadModel) -> None: async def test_set_and_remove_timer(timers_instance: Timers) -> None: - payload = TestPayloadModel(message="test", count=1) + payload = SomePayloadModel(message="test", count=1) await timers_instance.set_timer( - topic="test_handler", timer_id="test_timer_1", payload=payload, activation_period=datetime.timedelta(seconds=1) + topic="some_topic", timer_id="test_timer_1", payload=payload, activation_period=datetime.timedelta(seconds=1) ) # Check that timer exists in Redis timeline_keys, payloads_dict = await timers_instance.fetch_all_timers() assert len(timeline_keys) == 1 timer_key = timeline_keys[0] - assert timer_key == "test_handler--test_timer_1" + assert timer_key == "some_topic--test_timer_1" # Check payloads has the timer data assert timer_key in payloads_dict payload_data = payloads_dict[timer_key] - parsed_payload = TestPayloadModel.model_validate_json(payload_data) + parsed_payload = SomePayloadModel.model_validate_json(payload_data) assert parsed_payload == payload # Remove the timer - await timers_instance.remove_timer(topic="test_handler", timer_id="test_timer_1") + await timers_instance.remove_timer(topic="some_topic", timer_id="test_timer_1") # Check that timer is removed from Redis timeline_keys, payloads_dict = await timers_instance.fetch_all_timers() @@ -102,9 +102,9 @@ async def test_set_and_remove_timer(timers_instance: Timers) -> None: async def test_handle_ready_timers(timers_instance: Timers, handler_results: HandlerResults) -> None: - payload = TestPayloadModel(message="ready_timer", count=42) + payload = SomePayloadModel(message="ready_timer", count=42) await timers_instance.set_timer( - topic="test_handler", + topic="some_topic", timer_id="ready_timer_1", payload=payload, activation_period=datetime.timedelta(seconds=0), # Ready immediately @@ -117,7 +117,7 @@ async def test_handle_ready_timers(timers_instance: Timers, handler_results: Han assert handler_results.results assert len(handler_results.results) == 1 result = handler_results.results[0] - assert isinstance(result, TestPayloadModel) + assert isinstance(result, SomePayloadModel) assert result == payload # Check that timer was removed from Redis @@ -127,11 +127,11 @@ async def test_handle_ready_timers(timers_instance: Timers, handler_results: Han async def test_handle_multiple_ready_timers(timers_instance: Timers, handler_results: HandlerResults) -> None: - payload1 = TestPayloadModel(message="timer_1", count=1) + payload1 = SomePayloadModel(message="timer_1", count=1) payload2 = AnotherPayloadModel(value="timer_2") await timers_instance.set_timer( - topic="test_handler", + topic="some_topic", timer_id="multi_timer_1", payload=payload1, activation_period=datetime.timedelta(seconds=0), @@ -150,9 +150,9 @@ async def test_handle_multiple_ready_timers(timers_instance: Timers, handler_res async def test_timer_not_ready_yet(timers_instance: Timers, handler_results: HandlerResults) -> None: - payload = TestPayloadModel(message="future_timer", count=99) + payload = SomePayloadModel(message="future_timer", count=99) await timers_instance.set_timer( - topic="test_handler", + topic="some_topic", timer_id="future_timer_1", payload=payload, activation_period=datetime.timedelta(seconds=10), @@ -163,16 +163,16 @@ async def test_timer_not_ready_yet(timers_instance: Timers, handler_results: Han assert len(handler_results.results) == 0 timeline_keys, payloads_dict = await timers_instance.fetch_all_timers() assert len(timeline_keys) == 1 - timer_key = "test_handler--future_timer_1" + timer_key = "some_topic--future_timer_1" assert timer_key in payloads_dict async def test_remove_nonexistent_timer(timers_instance: Timers) -> None: - await timers_instance.remove_timer(topic="test_handler", timer_id="nonexistent_timer") + await timers_instance.remove_timer(topic="some_topic", timer_id="nonexistent_timer") async def test_set_timer_with_invalid_topic(timers_instance: Timers) -> None: - payload = TestPayloadModel(message="test", count=1) + payload = SomePayloadModel(message="test", count=1) with pytest.raises(RuntimeError, match="Handler is not found"): await timers_instance.set_timer( @@ -194,17 +194,17 @@ async def test_empty_timeline_handling(timers_instance: Timers, handler_results: async def test_duplicate_timer_replacement(timers_instance: Timers, handler_results: HandlerResults) -> None: - payload1 = TestPayloadModel(message="first", count=1) + payload1 = SomePayloadModel(message="first", count=1) await timers_instance.set_timer( - topic="test_handler", + topic="some_topic", timer_id="duplicate_timer", payload=payload1, activation_period=datetime.timedelta(seconds=10), # Far in future ) - payload2 = TestPayloadModel(message="second", count=2) + payload2 = SomePayloadModel(message="second", count=2) await timers_instance.set_timer( - topic="test_handler", + topic="some_topic", timer_id="duplicate_timer", payload=payload2, activation_period=datetime.timedelta(seconds=0), # Ready immediately @@ -214,5 +214,5 @@ async def test_duplicate_timer_replacement(timers_instance: Timers, handler_resu assert len(handler_results.results) == 1 result = handler_results.results[0] - assert isinstance(result, TestPayloadModel) + assert isinstance(result, SomePayloadModel) assert result == payload2