From db2688d5b6d6791e2bb52ba1414cb21ff08002b4 Mon Sep 17 00:00:00 2001 From: sevaho Date: Wed, 4 Feb 2026 17:40:31 +0100 Subject: [PATCH 1/6] Create test --- shell.nix | 12 --------- tests/asyncapi/test_generation.py | 41 ++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/shell.nix b/shell.nix index 0703d57..309bf2e 100644 --- a/shell.nix +++ b/shell.nix @@ -9,21 +9,9 @@ in pkgs.mkShell { packages = with pkgs; [ python311 - ruff rustc cargo - - (poetry.override { python3 = python311; }) - - (python311.withPackages (p: with p; [ - pip - python-lsp-server - pynvim - pyls-isort - python-lsp-black - ])) - ]; env.LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ diff --git a/tests/asyncapi/test_generation.py b/tests/asyncapi/test_generation.py index 9d68cba..c0ba43b 100644 --- a/tests/asyncapi/test_generation.py +++ b/tests/asyncapi/test_generation.py @@ -1,4 +1,4 @@ -from typing import Any, Union +from typing import Any, Union, Optional from uuid import uuid4 import pytest @@ -125,6 +125,45 @@ def test_generate_schema_w_external_docs_should_generate(): schema = client.asyncapi_schema assert schema["externalDocs"] == external_docs.dict() +async def test_optional_types_are_generated_correctly(app: NatsAPI): + class User(BaseModel): + mandatory_property_1: str + optional_property_1: str | None + optional_property_2: Optional[str] + optional_property_3: Optional[str] = None + optional_property_4: Optional[str] | None = None + optional_property_5: str | None = None + optional_property_6: str = None + + user_router = SubjectRouter(prefix="v1", tags=["users"], deprecated=True) + + @user_router.request( + "users.CREATE", + result=User, + description="Creates user that can be used throughout the app", + tags=["auth"], + suggested_timeout=0.5, + ) + def create_base_user(app, user: User): + return {"id": uuid4()} + + app.include_router(user_router) + app.generate_asyncapi() + schema = app.asyncapi_schema + + assert schema["components"]["schemas"]["User"]["properties"]["mandatory_property_1"]["type"] == "string" + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_1"]["type"] == "string" + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_2"]["type"] == "string" + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_3"]["type"] == "string" + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_4"]["type"] == "string" + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_5"]["type"] == "string" + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_6"]["type"] == "string" + + + schema_from_request = (await app.nc.request("natsapi.development.schema.RETRIEVE", {})).result + assert schema_from_request == schema + + async def test_generate_shema_w_requests_should_generate(app: NatsAPI): class BaseUser(BaseModel): From dd7305ad6e4b3d6a4c7ec41c9c59ef16534093e6 Mon Sep 17 00:00:00 2001 From: sevaho Date: Wed, 4 Feb 2026 18:02:40 +0100 Subject: [PATCH 2/6] Fix anyof generation for properties with pydantic v2 --- natsapi/_compat.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/natsapi/_compat.py b/natsapi/_compat.py index e8f1d82..f88fc7a 100644 --- a/natsapi/_compat.py +++ b/natsapi/_compat.py @@ -235,3 +235,15 @@ def sort(self, value: JsonSchemaValue, *args) -> JsonSchemaValue: https://docs.pydantic.dev/latest/concepts/json_schema/#json-schema-sorting """ return value + + def nullable_schema(self, schema: JsonSchemaValue) -> JsonSchemaValue: + """ + Override nullable schema generation to flatten optional types. + Instead of generating anyOf with [type, null], just return the type. + """ + if PYDANTIC_V2: + # Extract the inner schema and generate it without the null variant + inner_schema = schema.get('schema') + if inner_schema: + return self.generate_inner(inner_schema) + return super().nullable_schema(schema) From f2fb11046b6150cf9863eab6880a791e83a5cff6 Mon Sep 17 00:00:00 2001 From: sevaho Date: Fri, 6 Feb 2026 09:52:57 +0100 Subject: [PATCH 3/6] Add union test case --- tests/asyncapi/test_generation.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/asyncapi/test_generation.py b/tests/asyncapi/test_generation.py index c0ba43b..96e057f 100644 --- a/tests/asyncapi/test_generation.py +++ b/tests/asyncapi/test_generation.py @@ -125,6 +125,7 @@ def test_generate_schema_w_external_docs_should_generate(): schema = client.asyncapi_schema assert schema["externalDocs"] == external_docs.dict() + async def test_optional_types_are_generated_correctly(app: NatsAPI): class User(BaseModel): mandatory_property_1: str @@ -134,6 +135,7 @@ class User(BaseModel): optional_property_4: Optional[str] | None = None optional_property_5: str | None = None optional_property_6: str = None + optional_property_7: str | int user_router = SubjectRouter(prefix="v1", tags=["users"], deprecated=True) @@ -158,13 +160,15 @@ def create_base_user(app, user: User): assert schema["components"]["schemas"]["User"]["properties"]["optional_property_4"]["type"] == "string" assert schema["components"]["schemas"]["User"]["properties"]["optional_property_5"]["type"] == "string" assert schema["components"]["schemas"]["User"]["properties"]["optional_property_6"]["type"] == "string" - + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_7"]['anyOf'] == [ + {'type': 'string'}, + {'type': 'integer'}, + ] schema_from_request = (await app.nc.request("natsapi.development.schema.RETRIEVE", {})).result assert schema_from_request == schema - async def test_generate_shema_w_requests_should_generate(app: NatsAPI): class BaseUser(BaseModel): email: str = Field(..., description="Unique email of user", example="foo@bar.com") From 227c6ac1be6f5ef0cb5e91d1265550fa473ca355 Mon Sep 17 00:00:00 2001 From: sevaho Date: Fri, 6 Feb 2026 10:33:03 +0100 Subject: [PATCH 4/6] Fix ci checks --- natsapi/_compat.py | 2 +- tests/asyncapi/test_generation.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/natsapi/_compat.py b/natsapi/_compat.py index f88fc7a..0395b13 100644 --- a/natsapi/_compat.py +++ b/natsapi/_compat.py @@ -243,7 +243,7 @@ def nullable_schema(self, schema: JsonSchemaValue) -> JsonSchemaValue: """ if PYDANTIC_V2: # Extract the inner schema and generate it without the null variant - inner_schema = schema.get('schema') + inner_schema = schema.get("schema") if inner_schema: return self.generate_inner(inner_schema) return super().nullable_schema(schema) diff --git a/tests/asyncapi/test_generation.py b/tests/asyncapi/test_generation.py index 96e057f..40abb87 100644 --- a/tests/asyncapi/test_generation.py +++ b/tests/asyncapi/test_generation.py @@ -1,4 +1,4 @@ -from typing import Any, Union, Optional +from typing import Any, Optional, Union from uuid import uuid4 import pytest @@ -160,9 +160,9 @@ def create_base_user(app, user: User): assert schema["components"]["schemas"]["User"]["properties"]["optional_property_4"]["type"] == "string" assert schema["components"]["schemas"]["User"]["properties"]["optional_property_5"]["type"] == "string" assert schema["components"]["schemas"]["User"]["properties"]["optional_property_6"]["type"] == "string" - assert schema["components"]["schemas"]["User"]["properties"]["optional_property_7"]['anyOf'] == [ - {'type': 'string'}, - {'type': 'integer'}, + assert schema["components"]["schemas"]["User"]["properties"]["optional_property_7"]["anyOf"] == [ + {"type": "string"}, + {"type": "integer"}, ] schema_from_request = (await app.nc.request("natsapi.development.schema.RETRIEVE", {})).result From c068a3edf0128736d117c37d54e8624e19246c4f Mon Sep 17 00:00:00 2001 From: sevaho Date: Fri, 6 Feb 2026 10:34:33 +0100 Subject: [PATCH 5/6] Fix vulture --- tests/asyncapi/test_generation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/asyncapi/test_generation.py b/tests/asyncapi/test_generation.py index 40abb87..f58dbcf 100644 --- a/tests/asyncapi/test_generation.py +++ b/tests/asyncapi/test_generation.py @@ -128,6 +128,7 @@ def test_generate_schema_w_external_docs_should_generate(): async def test_optional_types_are_generated_correctly(app: NatsAPI): class User(BaseModel): + name: str mandatory_property_1: str optional_property_1: str | None optional_property_2: Optional[str] @@ -147,7 +148,7 @@ class User(BaseModel): suggested_timeout=0.5, ) def create_base_user(app, user: User): - return {"id": uuid4()} + return {"id": uuid4(), "name": user.name} app.include_router(user_router) app.generate_asyncapi() From bd6ee4134eb14de8d34e86cde420d4f5bbb6b29b Mon Sep 17 00:00:00 2001 From: sevaho Date: Fri, 6 Feb 2026 10:52:20 +0100 Subject: [PATCH 6/6] Only run test if python 3.10 or higher --- tests/asyncapi/test_generation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/asyncapi/test_generation.py b/tests/asyncapi/test_generation.py index f58dbcf..395ba74 100644 --- a/tests/asyncapi/test_generation.py +++ b/tests/asyncapi/test_generation.py @@ -1,3 +1,4 @@ +import sys from typing import Any, Optional, Union from uuid import uuid4 @@ -126,6 +127,7 @@ def test_generate_schema_w_external_docs_should_generate(): assert schema["externalDocs"] == external_docs.dict() +@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+ for union syntax") async def test_optional_types_are_generated_correctly(app: NatsAPI): class User(BaseModel): name: str