From 3bf582e5583f2b76ea25d2752c9a446b9c69a211 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Sun, 20 Apr 2025 10:41:07 +0200 Subject: [PATCH 1/9] Fix httpx>=0.24 --- rpcpy/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcpy/client.py b/rpcpy/client.py index d8b0aa9..e477d4c 100644 --- a/rpcpy/client.py +++ b/rpcpy/client.py @@ -213,7 +213,7 @@ def __init__(self) -> None: self.message: ServerSentEvent = {} def feed(self, line: str) -> ServerSentEvent | None: - if line == "\n": # event split line + if not line or line == "\n": # event split line event = self.message self.message = {} return event From 4acdc5273005c8be63586129527110db374a1a5b Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Sun, 20 Apr 2025 10:41:49 +0200 Subject: [PATCH 2/9] Add OpenAPI Pydantic v2 suppurt --- rpcpy/application.py | 7 ++++--- rpcpy/openapi.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/rpcpy/application.py b/rpcpy/application.py index 1867ef8..e93ff92 100644 --- a/rpcpy/application.py +++ b/rpcpy/application.py @@ -35,6 +35,7 @@ from rpcpy.openapi import ( ValidationError, create_model, + create_root_model, is_typed_dict_type, parse_typed_dict, set_type_model, @@ -162,9 +163,9 @@ def get_openapi_docs(self) -> dict: elif return_annotation is None: resp_model = create_model(callback.__name__ + "-return") else: - resp_model = create_model( - callback.__name__ + "-return", - __root__=(return_annotation, ...), + resp_model = create_root_model( + model_name=callback.__name__ + "-return", + return_annotation=return_annotation, ) _schema = copy.deepcopy(resp_model.schema()) definitions.update(_schema.pop("definitions", {})) diff --git a/rpcpy/openapi.py b/rpcpy/openapi.py index da41b6b..ac98251 100644 --- a/rpcpy/openapi.py +++ b/rpcpy/openapi.py @@ -19,6 +19,9 @@ try: from pydantic import BaseModel, ValidationError, create_model from pydantic import validate_arguments as pydantic_validate_arguments + from pydantic import VERSION as PYDANTIC_VERSION + + IS_PYDANTIC_V2 = int(PYDANTIC_VERSION.split(".")[0]) >= 2 # visit this issue # https://github.com/samuelcolvin/pydantic/issues/1205 @@ -41,6 +44,8 @@ def change_exception(*args, **kwargs): except ImportError: + IS_PYDANTIC_V2 = False + def create_model(*args, **kwargs): # type: ignore raise NotImplementedError("Need install `pydantic` from pypi.") @@ -54,6 +59,10 @@ class ValidationError(Exception): # type: ignore if typing.TYPE_CHECKING: from pydantic import BaseModel + from pydantic import VERSION as PYDANTIC_VERSION + +if IS_PYDANTIC_V2: + from pydantic import RootModel def set_type_model(func: Callable) -> Callable: @@ -111,6 +120,24 @@ def parse_typed_dict(typed_dict) -> typing.Type[BaseModel]: return create_model(typed_dict.__name__, **annotations) # type: ignore +def create_root_model(model_name: str, return_annotation: type) -> typing.Type[BaseModel]: + """ + Create a Pydantic model with a single root field for the return type. + + This function handles both Pydantic v1 and v2 styles of model creation. + """ + if IS_PYDANTIC_V2: + # Dynamically create a subclass of RootModel + return type( + model_name, + (RootModel,), + {"__annotations__": {"root": return_annotation}}, + ) + else: + # Pydantic v1 style using create_model with __root__ + return create_model(model_name, __root__=(return_annotation, ...)) + + TEMPLATE = """ From 2f2cda41f7487f351f23df073ae3553e2eac0cb9 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Sun, 20 Apr 2025 10:42:31 +0200 Subject: [PATCH 3/9] Speed up tests by removing unnecessary sleep --- tests/test_client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 7ebf4a8..5825b2e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -25,7 +25,6 @@ def sayhi(name: str) -> str: @app.register def yield_data(max_num: int) -> Generator[int, None, None]: for i in range(max_num): - time.sleep(1) yield i @app.register @@ -55,7 +54,6 @@ async def sayhi(name: str) -> str: @app.register async def yield_data(max_num: int) -> AsyncGenerator[int, None]: for i in range(max_num): - await asyncio.sleep(1) yield i @app.register From 5d04c8f3c3388f235e60aef98104ecb9d04f0988 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Sun, 20 Apr 2025 10:50:51 +0200 Subject: [PATCH 4/9] iSort fix --- rpcpy/openapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcpy/openapi.py b/rpcpy/openapi.py index ac98251..2fe601a 100644 --- a/rpcpy/openapi.py +++ b/rpcpy/openapi.py @@ -17,9 +17,9 @@ Callable = typing.TypeVar("Callable", bound=typing.Callable) try: + from pydantic import VERSION as PYDANTIC_VERSION from pydantic import BaseModel, ValidationError, create_model from pydantic import validate_arguments as pydantic_validate_arguments - from pydantic import VERSION as PYDANTIC_VERSION IS_PYDANTIC_V2 = int(PYDANTIC_VERSION.split(".")[0]) >= 2 @@ -58,8 +58,8 @@ class ValidationError(Exception): # type: ignore """ if typing.TYPE_CHECKING: - from pydantic import BaseModel from pydantic import VERSION as PYDANTIC_VERSION + from pydantic import BaseModel if IS_PYDANTIC_V2: from pydantic import RootModel From c2530017b8a47a52ba86e8156fe8a2accdd5663b Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Mon, 5 May 2025 15:52:45 +0200 Subject: [PATCH 5/9] Update ci.yml --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5813fe4..fb3b750 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: "${{ matrix.os }}" strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10"] + python-version: [3.9, 3.10, 3.11, 3.12] os: [windows-latest, ubuntu-latest, macos-latest] env: OS: ${{ matrix.os }} @@ -102,3 +102,4 @@ jobs: - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + From 1fdab0a85727b80e4748a85e1256eab0f76ca3fe Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Wed, 7 May 2025 13:55:16 +0200 Subject: [PATCH 6/9] Update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb3b750..1d072fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: "${{ matrix.os }}" strategy: matrix: - python-version: [3.9, 3.10, 3.11, 3.12] + python-version: ["3.9", "3.10", "3.11", "3.12"] os: [windows-latest, ubuntu-latest, macos-latest] env: OS: ${{ matrix.os }} @@ -81,7 +81,7 @@ jobs: id-token: write strategy: matrix: - python-version: [3.7] + python-version: ["3.12"] os: [ubuntu-latest] steps: From 55b93f2edf38d75b7f1a9ae070db06f4ed436d64 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Wed, 7 May 2025 17:00:52 +0200 Subject: [PATCH 7/9] Remove unused time import --- tests/test_client.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 5825b2e..0569ce3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,5 +1,4 @@ import asyncio -import time from typing import AsyncGenerator, Generator import httpx @@ -88,8 +87,7 @@ def async_client(asgi_app): def test_sync_client(sync_client): @sync_client.remote_call - def sayhi(name: str) -> str: - ... + def sayhi(name: str) -> str: ... assert sayhi("rpc.py") == "hi rpc.py" @@ -99,8 +97,7 @@ def sayhi(name: str) -> str: ): @sync_client.remote_call - async def sayhi(name: str) -> str: - ... + async def sayhi(name: str) -> str: ... @sync_client.remote_call def yield_data(max_num: int): @@ -112,8 +109,7 @@ def yield_data(max_num: int): index += 1 @sync_client.remote_call - def exception() -> str: - ... + def exception() -> str: ... with pytest.raises(RemoteCallError, match="ValueError: Message"): exception() @@ -130,8 +126,7 @@ def exception_in_g() -> Generator[str, None, None]: @pytest.mark.asyncio async def test_async_client(async_client): @async_client.remote_call - async def sayhi(name: str) -> str: - ... + async def sayhi(name: str) -> str: ... assert await sayhi("rpc.py") == "hi rpc.py" @@ -141,8 +136,7 @@ async def sayhi(name: str) -> str: ): @async_client.remote_call - def sayhi(name: str) -> str: - ... + def sayhi(name: str) -> str: ... @async_client.remote_call async def yield_data(max_num: int): @@ -154,8 +148,7 @@ async def yield_data(max_num: int): index += 1 @async_client.remote_call - async def exception() -> str: - ... + async def exception() -> str: ... with pytest.raises(RemoteCallError, match="ValueError: Message"): await exception() @@ -171,8 +164,7 @@ async def exception_in_g() -> AsyncGenerator[str, None]: def test_none(sync_client): @sync_client.remote_call - def none() -> None: - ... + def none() -> None: ... assert none() is None @@ -183,8 +175,7 @@ def none() -> None: @pytest.mark.asyncio async def test_async_none(async_client): @async_client.remote_call - async def none() -> None: - ... + async def none() -> None: ... assert await none() is None From 6fb0f81660ec3774dc6d312735cc9288b86eb0b8 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Sat, 10 May 2025 17:13:29 +0200 Subject: [PATCH 8/9] Revert "Remove unused time import" This reverts commit 55b93f2edf38d75b7f1a9ae070db06f4ed436d64. --- tests/test_client.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 0569ce3..5825b2e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,5 @@ import asyncio +import time from typing import AsyncGenerator, Generator import httpx @@ -87,7 +88,8 @@ def async_client(asgi_app): def test_sync_client(sync_client): @sync_client.remote_call - def sayhi(name: str) -> str: ... + def sayhi(name: str) -> str: + ... assert sayhi("rpc.py") == "hi rpc.py" @@ -97,7 +99,8 @@ def sayhi(name: str) -> str: ... ): @sync_client.remote_call - async def sayhi(name: str) -> str: ... + async def sayhi(name: str) -> str: + ... @sync_client.remote_call def yield_data(max_num: int): @@ -109,7 +112,8 @@ def yield_data(max_num: int): index += 1 @sync_client.remote_call - def exception() -> str: ... + def exception() -> str: + ... with pytest.raises(RemoteCallError, match="ValueError: Message"): exception() @@ -126,7 +130,8 @@ def exception_in_g() -> Generator[str, None, None]: @pytest.mark.asyncio async def test_async_client(async_client): @async_client.remote_call - async def sayhi(name: str) -> str: ... + async def sayhi(name: str) -> str: + ... assert await sayhi("rpc.py") == "hi rpc.py" @@ -136,7 +141,8 @@ async def sayhi(name: str) -> str: ... ): @async_client.remote_call - def sayhi(name: str) -> str: ... + def sayhi(name: str) -> str: + ... @async_client.remote_call async def yield_data(max_num: int): @@ -148,7 +154,8 @@ async def yield_data(max_num: int): index += 1 @async_client.remote_call - async def exception() -> str: ... + async def exception() -> str: + ... with pytest.raises(RemoteCallError, match="ValueError: Message"): await exception() @@ -164,7 +171,8 @@ async def exception_in_g() -> AsyncGenerator[str, None]: def test_none(sync_client): @sync_client.remote_call - def none() -> None: ... + def none() -> None: + ... assert none() is None @@ -175,7 +183,8 @@ def none() -> None: ... @pytest.mark.asyncio async def test_async_none(async_client): @async_client.remote_call - async def none() -> None: ... + async def none() -> None: + ... assert await none() is None From ffcb2f28ee5206216d9e92ca0d5963b4f8a152c4 Mon Sep 17 00:00:00 2001 From: Martin Trapp Date: Sat, 10 May 2025 17:14:30 +0200 Subject: [PATCH 9/9] Update test_client.py --- tests/test_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index 5825b2e..ccdffdd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,5 +1,4 @@ import asyncio -import time from typing import AsyncGenerator, Generator import httpx