diff --git a/reflectapi-demo/src/tests/assert.rs b/reflectapi-demo/src/tests/assert.rs index b135702b..bb13227a 100644 --- a/reflectapi-demo/src/tests/assert.rs +++ b/reflectapi-demo/src/tests/assert.rs @@ -214,9 +214,15 @@ macro_rules! assert_builder_snapshot { .unwrap(); reflectapi::codegen::strip_boilerplate(&files["generated.ts"]) }; + let python = reflectapi::codegen::python::generate( + schema.clone(), + &reflectapi::codegen::python::Config::default(), + ) + .unwrap(); insta::assert_json_snapshot!(schema); insta::assert_snapshot!(typescript); insta::assert_snapshot!(rust); insta::assert_json_snapshot!(reflectapi::codegen::openapi::Spec::from(&schema)); + insta::assert_snapshot!(python); }}; } diff --git a/reflectapi-demo/src/tests/enums.rs b/reflectapi-demo/src/tests/enums.rs index 3bd09628..32012d39 100644 --- a/reflectapi-demo/src/tests/enums.rs +++ b/reflectapi-demo/src/tests/enums.rs @@ -154,6 +154,67 @@ fn test_internally_tagged_enum_with_tuple_variants() { assert_snapshot!(E); } +#[test] +fn test_internally_tagged_enum_with_optional_fields() { + // Repro for https://github.com/thepartly/reflectapi/issues/167: + // a nullable `Option` field on a struct used as an internally-tagged + // enum variant must be generated as an optional key (serde omits the key + // when the value is `None`), matching the standalone-struct behavior. + #[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)] + pub struct VariantBody { + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + pub always_null_on_wire: Option, + } + + #[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)] + #[serde(tag = "type", rename_all = "snake_case")] + pub enum Thing { + Variant(VariantBody), + Named { + #[serde(skip_serializing_if = "Option::is_none")] + amount: Option, + }, + } + + assert_snapshot!(Thing); +} + +#[test] +fn test_struct_with_flattened_internally_tagged_enum_with_optional_fields() { + // Repro for https://github.com/thepartly/reflectapi/issues/167: + // same as test_internally_tagged_enum_with_optional_fields, but with the + // enum flattened into a parent struct, which is rendered by a separate + // code path generating one model per variant. + #[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)] + pub struct VariantBody { + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + pub always_null_on_wire: Option, + } + + #[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)] + #[serde(tag = "type", rename_all = "snake_case")] + pub enum Thing { + Variant(VariantBody), + Named { + #[serde(skip_serializing_if = "Option::is_none")] + amount: Option, + }, + } + + #[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)] + pub struct Offer { + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub note: Option, + #[serde(flatten)] + pub details: Thing, + } + + assert_snapshot!(Offer); +} + #[test] fn test_internally_tagged_enum_with_unit_variants() { #[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)] diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__basic__reflectapi_struct_with_hidden_header_field-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__basic__reflectapi_struct_with_hidden_header_field-5.snap new file mode 100644 index 00000000..ecfcc74c --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__basic__reflectapi_struct_with_hidden_header_field-5.snap @@ -0,0 +1,153 @@ +--- +source: reflectapi-demo/src/tests/basic.rs +expression: python +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: hidden_header_test +""" + +from __future__ import annotations + + +# Standard library imports +from typing import Annotated, Any, Generic, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +class ReflectapiDemoTestsBasicHeadersWithHidden(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + authorization: str = Field( + serialization_alias="_authorization", + validation_alias="_authorization", + description="Authorization header", + ) + + +# Namespace classes for dotted access to types +class reflectapi_demo: + """Namespace for reflectapi_demo types.""" + + class tests: + """Namespace for tests types.""" + + class basic: + """Namespace for basic types.""" + + HeadersWithHidden = ReflectapiDemoTestsBasicHeadersWithHidden + + +class AsyncTestClient: + """Async client for test operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def endpoint( + self, + headers: Optional[reflectapi_demo.tests.basic.HeadersWithHidden] = None, + ) -> ApiResponse[Any]: + """ + + Returns: + ApiResponse[Any]: Response containing Any data + """ + path = "/test.endpoint" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + headers_model=headers, + response_model=None, + ) + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.test = AsyncTestClient(self) + + +class TestClient: + """Synchronous client for test operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def endpoint( + self, + headers: Optional[reflectapi_demo.tests.basic.HeadersWithHidden] = None, + ) -> ApiResponse[Any]: + """ + + Returns: + ApiResponse[Any]: Response containing Any data + """ + path = "/test.endpoint" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + headers_model=headers, + response_model=None, + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.test = TestClient(self) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references +_rebuild_errors: list[str] = [] +for _model in [ + ReflectapiDemoTestsBasicHeadersWithHidden, +]: + if not hasattr(_model, "model_rebuild"): + continue + try: + _model.model_rebuild() + except Exception as _exc: + _rebuild_errors.append(f" - {_model.__name__}: {type(_exc).__name__}: {_exc}") +if _rebuild_errors: + raise RuntimeError( + "reflectapi: failed to rebuild " + + str(len(_rebuild_errors)) + + " generated model(s). This usually means the codegen emitted an annotation pointing at a symbol that was never defined (a dangling type reference). Fix the codegen rather than catching this error.\n" + + "\n".join(_rebuild_errors) + ) diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-2.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-2.snap new file mode 100644 index 00000000..deec637e --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-2.snap @@ -0,0 +1,88 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "super :: into_typescript_code :: < Thing > ()" +--- +// DO NOT MODIFY THIS FILE MANUALLY +// This file was generated by reflectapi-cli +// +// Schema name: + +export function client(base: string | Client): __definition.Interface { + return __implementation.__client(base); +} + +export namespace __definition { + export interface Interface { + inout_test: ( + input: reflectapi_demo.tests.enums.input.Thing, + headers: {}, + options?: RequestOptions, + ) => AsyncResult; + } +} +export namespace reflectapi { + /** + * Struct object with no fields + */ + export interface Empty {} + + /** + * Error object which is expected to be never returned + */ + export interface Infallible {} +} + +export namespace reflectapi_demo { + export namespace tests { + export namespace enums { + export namespace input { + export type Thing = + | ({ + type: "variant"; + } & reflectapi_demo.tests.enums.input.VariantBody) + | { + type: "named"; + amount: string | null; + }; + + export interface VariantBody { + amount: string | null; + always_null_on_wire: string | null; + } + } + + export namespace output { + export type Thing = + | ({ + type: "variant"; + } & reflectapi_demo.tests.enums.output.VariantBody) + | { + type: "named"; + amount?: string | null; + }; + + export interface VariantBody { + amount?: string | null; + always_null_on_wire: string | null; + } + } + } + } +} + +namespace __implementation { + + function inout_test(client: Client) { + return ( + input: reflectapi_demo.tests.enums.input.Thing, + headers: {}, + options?: RequestOptions, + ) => + __request< + reflectapi_demo.tests.enums.input.Thing, + {}, + reflectapi_demo.tests.enums.output.Thing, + {} + >(client, "/inout_test", input, headers, options); + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-3.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-3.snap new file mode 100644 index 00000000..3d8ceb17 --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-3.snap @@ -0,0 +1,109 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "super :: into_rust_code :: < Thing > ()" +--- +// DO NOT MODIFY THIS FILE MANUALLY +// This file was generated by reflectapi-cli +// +// Schema name: + +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +pub use interface::Interface; +pub use reflectapi::rt::*; + +pub mod interface { + + #[derive(Debug)] + pub struct Interface { + client: C, + } + + impl Interface { + pub fn new(client: C) -> Self { + Self { client } + } + pub async fn inout_test( + &self, + input: super::types::reflectapi_demo::tests::enums::input::Thing, + headers: reflectapi::Empty, + ) -> Result< + super::types::reflectapi_demo::tests::enums::output::Thing, + reflectapi::rt::Error, + > { + reflectapi::rt::__request_impl(&self.client, "/inout_test", input, headers).await + } + } + + #[cfg(feature = "reqwest")] + impl Interface> { + /// Convenience: build the client backed by a bare `reqwest::Client` + /// and the given base URL. Hides the + /// [`reflectapi::rt::ReqwestClient`] adapter so callers don't need + /// to name it. + pub fn try_new( + client: reqwest::Client, + base_url: reflectapi::rt::Url, + ) -> std::result::Result { + Ok(Self::new(reflectapi::rt::ReqwestClient::try_new( + client, base_url, + )?)) + } + } +} +pub mod types { + pub mod reflectapi_demo { + pub mod tests { + pub mod enums { + pub mod input { + + #[derive(Debug, serde::Serialize)] + #[serde(tag = "type")] + pub enum Thing { + #[serde(rename = "variant")] + Variant( + super::super::super::super::reflectapi_demo::tests::enums::input::VariantBody, + ), + #[serde(rename = "named")] + Named { + amount: std::option::Option, + }, +} + + #[derive(Debug, serde::Serialize)] + pub struct VariantBody { + pub amount: std::option::Option, + pub always_null_on_wire: std::option::Option, + } + } + pub mod output { + + #[derive(Debug, serde::Deserialize)] + #[serde(tag = "type")] + pub enum Thing { + #[serde(rename = "variant")] + Variant( + super::super::super::super::reflectapi_demo::tests::enums::output::VariantBody, + ), + #[serde(rename = "named")] + Named { + #[serde(default = "Default::default", skip_serializing_if = "std::option::Option::is_none")] + amount: std::option::Option, + }, +} + + #[derive(Debug, serde::Deserialize)] + pub struct VariantBody { + #[serde( + default = "Default::default", + skip_serializing_if = "std::option::Option::is_none" + )] + pub amount: std::option::Option, + pub always_null_on_wire: std::option::Option, + } + } + } + } + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-4.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-4.snap new file mode 100644 index 00000000..af113f6e --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-4.snap @@ -0,0 +1,239 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "reflectapi :: codegen :: openapi :: Spec :: from(& schema)" +--- +{ + "openapi": "3.1.0", + "info": { + "title": "", + "description": "", + "version": "1.0.0" + }, + "paths": { + "/inout_test": { + "description": "", + "post": { + "operationId": "inout_test", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reflectapi_demo.tests.enums.input.Thing" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "200 OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reflectapi_demo.tests.enums.output.Thing" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "reflectapi_demo.tests.enums.input.Thing": { + "oneOf": [ + { + "type": "object", + "title": "reflectapi_demo.tests.enums.input.VariantBody", + "required": [ + "always_null_on_wire", + "amount", + "type" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "variant" + } + } + }, + { + "type": "object", + "title": "named", + "required": [ + "amount", + "type" + ], + "properties": { + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "named" + } + } + } + ] + }, + "reflectapi_demo.tests.enums.input.VariantBody": { + "type": "object", + "title": "reflectapi_demo.tests.enums.input.VariantBody", + "required": [ + "always_null_on_wire", + "amount" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + } + } + }, + "reflectapi_demo.tests.enums.output.Thing": { + "oneOf": [ + { + "type": "object", + "title": "reflectapi_demo.tests.enums.output.VariantBody", + "required": [ + "always_null_on_wire", + "type" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "variant" + } + } + }, + { + "type": "object", + "title": "named", + "required": [ + "type" + ], + "properties": { + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "named" + } + } + } + ] + }, + "reflectapi_demo.tests.enums.output.VariantBody": { + "type": "object", + "title": "reflectapi_demo.tests.enums.output.VariantBody", + "required": [ + "always_null_on_wire" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + } + } + }, + "std.string.String": { + "description": "UTF-8 encoded string", + "type": "string" + } + } + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-5.snap new file mode 100644 index 00000000..6dc0c21a --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields-5.snap @@ -0,0 +1,245 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "super :: into_python_code :: < Thing > ()" +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: +""" + +from __future__ import annotations + + +# Standard library imports +from enum import Enum +from typing import Annotated, Any, Generic, Literal, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field, RootModel + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +class ReflectapiDemoTestsEnumsInputVariantBody(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsInputThingVariant(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["variant"] = Field( + default="variant", description="Discriminator field" + ) + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsInputThingNamed(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["named"] = Field(default="named", description="Discriminator field") + amount: str | None = None + + +class ReflectapiDemoTestsEnumsInputThing(RootModel): + root: Annotated[ + Union[ + ReflectapiDemoTestsEnumsInputThingVariant, + ReflectapiDemoTestsEnumsInputThingNamed, + ], + Field(discriminator="type"), + ] + + +class ReflectapiDemoTestsEnumsOutputVariantBody(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsOutputThingVariant(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["variant"] = Field( + default="variant", description="Discriminator field" + ) + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsOutputThingNamed(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["named"] = Field(default="named", description="Discriminator field") + amount: str | None = None + + +class ReflectapiDemoTestsEnumsOutputThing(RootModel): + root: Annotated[ + Union[ + ReflectapiDemoTestsEnumsOutputThingVariant, + ReflectapiDemoTestsEnumsOutputThingNamed, + ], + Field(discriminator="type"), + ] + + +# Namespace classes for dotted access to types +class reflectapi_demo: + """Namespace for reflectapi_demo types.""" + + class tests: + """Namespace for tests types.""" + + class enums: + """Namespace for enums types.""" + + class input: + """Namespace for input types.""" + + VariantBody = ReflectapiDemoTestsEnumsInputVariantBody + ThingVariant = ReflectapiDemoTestsEnumsInputThingVariant + ThingNamed = ReflectapiDemoTestsEnumsInputThingNamed + Thing = ReflectapiDemoTestsEnumsInputThing + + class output: + """Namespace for output types.""" + + VariantBody = ReflectapiDemoTestsEnumsOutputVariantBody + ThingVariant = ReflectapiDemoTestsEnumsOutputThingVariant + ThingNamed = ReflectapiDemoTestsEnumsOutputThingNamed + Thing = ReflectapiDemoTestsEnumsOutputThing + + +class AsyncInoutClient: + """Async client for inout operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def test( + self, + data: Optional[reflectapi_demo.tests.enums.input.Thing] = None, + ) -> ApiResponse[reflectapi_demo.tests.enums.output.Thing]: + """ + + Args: + data: Request data for the test operation. + + Returns: + ApiResponse[reflectapi_demo.tests.enums.output.Thing]: Response containing reflectapi_demo.tests.enums.output.Thing data + """ + path = "/inout_test" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + json_model=data, + response_model=reflectapi_demo.tests.enums.output.Thing, + ) + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.inout = AsyncInoutClient(self) + + +class InoutClient: + """Synchronous client for inout operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def test( + self, + data: Optional[reflectapi_demo.tests.enums.input.Thing] = None, + ) -> ApiResponse[reflectapi_demo.tests.enums.output.Thing]: + """ + + Args: + data: Request data for the test operation. + + Returns: + ApiResponse[reflectapi_demo.tests.enums.output.Thing]: Response containing reflectapi_demo.tests.enums.output.Thing data + """ + path = "/inout_test" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + json_model=data, + response_model=reflectapi_demo.tests.enums.output.Thing, + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.inout = InoutClient(self) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references +_rebuild_errors: list[str] = [] +for _model in [ + ReflectapiDemoTestsEnumsInputThing, + ReflectapiDemoTestsEnumsInputVariantBody, + ReflectapiDemoTestsEnumsOutputThing, + ReflectapiDemoTestsEnumsOutputVariantBody, +]: + if not hasattr(_model, "model_rebuild"): + continue + try: + _model.model_rebuild() + except Exception as _exc: + _rebuild_errors.append(f" - {_model.__name__}: {type(_exc).__name__}: {_exc}") +if _rebuild_errors: + raise RuntimeError( + "reflectapi: failed to rebuild " + + str(len(_rebuild_errors)) + + " generated model(s). This usually means the codegen emitted an annotation pointing at a symbol that was never defined (a dangling type reference). Fix the codegen rather than catching this error.\n" + + "\n".join(_rebuild_errors) + ) diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields.snap new file mode 100644 index 00000000..02cceed3 --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__internally_tagged_enum_with_optional_fields.snap @@ -0,0 +1,268 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: schema +--- +{ + "name": "", + "functions": [ + { + "name": "inout_test", + "path": "", + "input_type": { + "name": "reflectapi_demo::tests::enums::input::Thing" + }, + "output_kind": "complete", + "output_type": { + "name": "reflectapi_demo::tests::enums::output::Thing" + }, + "serialization": [ + "json", + "msgpack" + ] + } + ], + "input_types": { + "types": [ + { + "kind": "struct", + "name": "reflectapi::Empty", + "description": "Struct object with no fields", + "fields": "none" + }, + { + "kind": "enum", + "name": "reflectapi_demo::tests::enums::input::Thing", + "representation": { + "internal": { + "tag": "type" + } + }, + "variants": [ + { + "name": "variant", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "reflectapi_demo::tests::enums::input::VariantBody" + }, + "required": true + } + ] + } + }, + { + "name": "named", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + } + ] + } + } + ] + }, + { + "kind": "struct", + "name": "reflectapi_demo::tests::enums::input::VariantBody", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + }, + { + "name": "always_null_on_wire", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + } + ] + } + }, + { + "kind": "enum", + "name": "std::option::Option", + "description": "Optional nullable type", + "parameters": [ + { + "name": "T" + } + ], + "representation": "none", + "variants": [ + { + "name": "None", + "description": "The value is not provided, i.e. null", + "fields": "none" + }, + { + "name": "Some", + "description": "The value is provided and set to some value", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "T" + } + } + ] + } + } + ] + }, + { + "kind": "primitive", + "name": "std::string::String", + "description": "UTF-8 encoded string" + } + ] + }, + "output_types": { + "types": [ + { + "kind": "struct", + "name": "reflectapi::Infallible", + "description": "Error object which is expected to be never returned", + "fields": "none" + }, + { + "kind": "enum", + "name": "reflectapi_demo::tests::enums::output::Thing", + "representation": { + "internal": { + "tag": "type" + } + }, + "variants": [ + { + "name": "variant", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "reflectapi_demo::tests::enums::output::VariantBody" + }, + "required": true + } + ] + } + }, + { + "name": "named", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + } + } + ] + } + } + ] + }, + { + "kind": "struct", + "name": "reflectapi_demo::tests::enums::output::VariantBody", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + } + }, + { + "name": "always_null_on_wire", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + } + ] + } + }, + { + "kind": "enum", + "name": "std::option::Option", + "description": "Optional nullable type", + "parameters": [ + { + "name": "T" + } + ], + "representation": "none", + "variants": [ + { + "name": "None", + "description": "The value is not provided, i.e. null", + "fields": "none" + }, + { + "name": "Some", + "description": "The value is provided and set to some value", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "T" + } + } + ] + } + } + ] + }, + { + "kind": "primitive", + "name": "std::string::String", + "description": "UTF-8 encoded string" + } + ] + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-2.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-2.snap new file mode 100644 index 00000000..a89cff42 --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-2.snap @@ -0,0 +1,98 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "super :: into_typescript_code :: < Offer > ()" +--- +// DO NOT MODIFY THIS FILE MANUALLY +// This file was generated by reflectapi-cli +// +// Schema name: + +export function client(base: string | Client): __definition.Interface { + return __implementation.__client(base); +} + +export namespace __definition { + export interface Interface { + inout_test: ( + input: reflectapi_demo.tests.enums.input.Offer, + headers: {}, + options?: RequestOptions, + ) => AsyncResult; + } +} +export namespace reflectapi { + /** + * Struct object with no fields + */ + export interface Empty {} + + /** + * Error object which is expected to be never returned + */ + export interface Infallible {} +} + +export namespace reflectapi_demo { + export namespace tests { + export namespace enums { + export namespace input { + export type Offer = { + id: string; + note: string | null; + } & NullToEmptyObject; + + export type Thing = + | ({ + type: "variant"; + } & reflectapi_demo.tests.enums.input.VariantBody) + | { + type: "named"; + amount: string | null; + }; + + export interface VariantBody { + amount: string | null; + always_null_on_wire: string | null; + } + } + + export namespace output { + export type Offer = { + id: string; + note?: string | null; + } & NullToEmptyObject; + + export type Thing = + | ({ + type: "variant"; + } & reflectapi_demo.tests.enums.output.VariantBody) + | { + type: "named"; + amount?: string | null; + }; + + export interface VariantBody { + amount?: string | null; + always_null_on_wire: string | null; + } + } + } + } +} + +namespace __implementation { + + function inout_test(client: Client) { + return ( + input: reflectapi_demo.tests.enums.input.Offer, + headers: {}, + options?: RequestOptions, + ) => + __request< + reflectapi_demo.tests.enums.input.Offer, + {}, + reflectapi_demo.tests.enums.output.Offer, + {} + >(client, "/inout_test", input, headers, options); + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-3.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-3.snap new file mode 100644 index 00000000..41184dac --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-3.snap @@ -0,0 +1,127 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "super :: into_rust_code :: < Offer > ()" +--- +// DO NOT MODIFY THIS FILE MANUALLY +// This file was generated by reflectapi-cli +// +// Schema name: + +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +pub use interface::Interface; +pub use reflectapi::rt::*; + +pub mod interface { + + #[derive(Debug)] + pub struct Interface { + client: C, + } + + impl Interface { + pub fn new(client: C) -> Self { + Self { client } + } + pub async fn inout_test( + &self, + input: super::types::reflectapi_demo::tests::enums::input::Offer, + headers: reflectapi::Empty, + ) -> Result< + super::types::reflectapi_demo::tests::enums::output::Offer, + reflectapi::rt::Error, + > { + reflectapi::rt::__request_impl(&self.client, "/inout_test", input, headers).await + } + } + + #[cfg(feature = "reqwest")] + impl Interface> { + /// Convenience: build the client backed by a bare `reqwest::Client` + /// and the given base URL. Hides the + /// [`reflectapi::rt::ReqwestClient`] adapter so callers don't need + /// to name it. + pub fn try_new( + client: reqwest::Client, + base_url: reflectapi::rt::Url, + ) -> std::result::Result { + Ok(Self::new(reflectapi::rt::ReqwestClient::try_new( + client, base_url, + )?)) + } + } +} +pub mod types { + pub mod reflectapi_demo { + pub mod tests { + pub mod enums { + pub mod input { + + #[derive(Debug, serde::Serialize)] + pub struct Offer { + pub id: std::string::String, + pub note: std::option::Option, + #[serde(flatten)] + pub details: + super::super::super::super::reflectapi_demo::tests::enums::input::Thing, + } + + #[derive(Debug, serde::Serialize)] + #[serde(tag = "type")] + pub enum Thing { + #[serde(rename = "variant")] + Variant( + super::super::super::super::reflectapi_demo::tests::enums::input::VariantBody, + ), + #[serde(rename = "named")] + Named { + amount: std::option::Option, + }, +} + + #[derive(Debug, serde::Serialize)] + pub struct VariantBody { + pub amount: std::option::Option, + pub always_null_on_wire: std::option::Option, + } + } + pub mod output { + + #[derive(Debug, serde::Deserialize)] + pub struct Offer { + pub id: std::string::String, + #[serde(default = "Default::default", skip_serializing_if = "std::option::Option::is_none")] + pub note: std::option::Option, + #[serde(flatten)] + pub details: super::super::super::super::reflectapi_demo::tests::enums::output::Thing, +} + + #[derive(Debug, serde::Deserialize)] + #[serde(tag = "type")] + pub enum Thing { + #[serde(rename = "variant")] + Variant( + super::super::super::super::reflectapi_demo::tests::enums::output::VariantBody, + ), + #[serde(rename = "named")] + Named { + #[serde(default = "Default::default", skip_serializing_if = "std::option::Option::is_none")] + amount: std::option::Option, + }, +} + + #[derive(Debug, serde::Deserialize)] + pub struct VariantBody { + #[serde( + default = "Default::default", + skip_serializing_if = "std::option::Option::is_none" + )] + pub amount: std::option::Option, + pub always_null_on_wire: std::option::Option, + } + } + } + } + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-4.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-4.snap new file mode 100644 index 00000000..f16f709a --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-4.snap @@ -0,0 +1,300 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "reflectapi :: codegen :: openapi :: Spec :: from(& schema)" +--- +{ + "openapi": "3.1.0", + "info": { + "title": "", + "description": "", + "version": "1.0.0" + }, + "paths": { + "/inout_test": { + "description": "", + "post": { + "operationId": "inout_test", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reflectapi_demo.tests.enums.input.Offer" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "200 OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reflectapi_demo.tests.enums.output.Offer" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "reflectapi_demo.tests.enums.input.Offer": { + "allOf": [ + { + "$ref": "#/components/schemas/reflectapi_demo.tests.enums.input.Thing" + }, + { + "type": "object", + "title": "reflectapi_demo.tests.enums.input.Offer", + "required": [ + "id", + "note" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/std.string.String" + }, + "note": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + } + } + } + ] + }, + "reflectapi_demo.tests.enums.input.Thing": { + "oneOf": [ + { + "type": "object", + "title": "reflectapi_demo.tests.enums.input.VariantBody", + "required": [ + "always_null_on_wire", + "amount", + "type" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "variant" + } + } + }, + { + "type": "object", + "title": "named", + "required": [ + "amount", + "type" + ], + "properties": { + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "named" + } + } + } + ] + }, + "reflectapi_demo.tests.enums.input.VariantBody": { + "type": "object", + "title": "reflectapi_demo.tests.enums.input.VariantBody", + "required": [ + "always_null_on_wire", + "amount" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + } + } + }, + "reflectapi_demo.tests.enums.output.Offer": { + "allOf": [ + { + "$ref": "#/components/schemas/reflectapi_demo.tests.enums.output.Thing" + }, + { + "type": "object", + "title": "reflectapi_demo.tests.enums.output.Offer", + "required": [ + "id" + ], + "properties": { + "id": { + "$ref": "#/components/schemas/std.string.String" + }, + "note": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + } + } + } + ] + }, + "reflectapi_demo.tests.enums.output.Thing": { + "oneOf": [ + { + "type": "object", + "title": "reflectapi_demo.tests.enums.output.VariantBody", + "required": [ + "always_null_on_wire", + "type" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "variant" + } + } + }, + { + "type": "object", + "title": "named", + "required": [ + "type" + ], + "properties": { + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "type": { + "const": "named" + } + } + } + ] + }, + "reflectapi_demo.tests.enums.output.VariantBody": { + "type": "object", + "title": "reflectapi_demo.tests.enums.output.VariantBody", + "required": [ + "always_null_on_wire" + ], + "properties": { + "always_null_on_wire": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + }, + "amount": { + "oneOf": [ + { + "description": "Null", + "type": "null" + }, + { + "$ref": "#/components/schemas/std.string.String" + } + ] + } + } + }, + "std.string.String": { + "description": "UTF-8 encoded string", + "type": "string" + } + } + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-5.snap new file mode 100644 index 00000000..a3052c4a --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields-5.snap @@ -0,0 +1,347 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: "super :: into_python_code :: < Offer > ()" +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: +""" + +from __future__ import annotations + + +# Standard library imports +from enum import Enum +from typing import Annotated, Any, Generic, Literal, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field, RootModel + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +class ReflectapiDemoTestsEnumsInputOfferVariant(BaseModel): + """'variant' variant of ReflectapiDemoTestsEnumsInputOffer""" + + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + id: str + note: str | None = None + type_: Literal["variant"] = Field( + default="variant", + serialization_alias="type", + validation_alias="type", + description="Discriminator field", + ) + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsInputOfferNamed(BaseModel): + """'named' variant of ReflectapiDemoTestsEnumsInputOffer""" + + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + id: str + note: str | None = None + type_: Literal["named"] = Field( + default="named", + serialization_alias="type", + validation_alias="type", + description="Discriminator field", + ) + amount: str | None = None + + +class ReflectapiDemoTestsEnumsInputOffer(RootModel): + root: Annotated[ + Union[ + ReflectapiDemoTestsEnumsInputOfferVariant, + ReflectapiDemoTestsEnumsInputOfferNamed, + ], + Field(discriminator="type_"), + ] + + +class ReflectapiDemoTestsEnumsInputVariantBody(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsInputThingVariant(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["variant"] = Field( + default="variant", description="Discriminator field" + ) + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsInputThingNamed(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["named"] = Field(default="named", description="Discriminator field") + amount: str | None = None + + +class ReflectapiDemoTestsEnumsInputThing(RootModel): + root: Annotated[ + Union[ + ReflectapiDemoTestsEnumsInputThingVariant, + ReflectapiDemoTestsEnumsInputThingNamed, + ], + Field(discriminator="type"), + ] + + +class ReflectapiDemoTestsEnumsOutputOfferVariant(BaseModel): + """'variant' variant of ReflectapiDemoTestsEnumsOutputOffer""" + + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + id: str + note: str | None = None + type_: Literal["variant"] = Field( + default="variant", + serialization_alias="type", + validation_alias="type", + description="Discriminator field", + ) + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsOutputOfferNamed(BaseModel): + """'named' variant of ReflectapiDemoTestsEnumsOutputOffer""" + + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + id: str + note: str | None = None + type_: Literal["named"] = Field( + default="named", + serialization_alias="type", + validation_alias="type", + description="Discriminator field", + ) + amount: str | None = None + + +class ReflectapiDemoTestsEnumsOutputOffer(RootModel): + root: Annotated[ + Union[ + ReflectapiDemoTestsEnumsOutputOfferVariant, + ReflectapiDemoTestsEnumsOutputOfferNamed, + ], + Field(discriminator="type_"), + ] + + +class ReflectapiDemoTestsEnumsOutputVariantBody(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsOutputThingVariant(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["variant"] = Field( + default="variant", description="Discriminator field" + ) + amount: str | None = None + always_null_on_wire: str | None = None + + +class ReflectapiDemoTestsEnumsOutputThingNamed(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + type: Literal["named"] = Field(default="named", description="Discriminator field") + amount: str | None = None + + +class ReflectapiDemoTestsEnumsOutputThing(RootModel): + root: Annotated[ + Union[ + ReflectapiDemoTestsEnumsOutputThingVariant, + ReflectapiDemoTestsEnumsOutputThingNamed, + ], + Field(discriminator="type"), + ] + + +# Namespace classes for dotted access to types +class reflectapi_demo: + """Namespace for reflectapi_demo types.""" + + class tests: + """Namespace for tests types.""" + + class enums: + """Namespace for enums types.""" + + class input: + """Namespace for input types.""" + + OfferVariant = ReflectapiDemoTestsEnumsInputOfferVariant + OfferNamed = ReflectapiDemoTestsEnumsInputOfferNamed + Offer = ReflectapiDemoTestsEnumsInputOffer + VariantBody = ReflectapiDemoTestsEnumsInputVariantBody + ThingVariant = ReflectapiDemoTestsEnumsInputThingVariant + ThingNamed = ReflectapiDemoTestsEnumsInputThingNamed + Thing = ReflectapiDemoTestsEnumsInputThing + + class output: + """Namespace for output types.""" + + OfferVariant = ReflectapiDemoTestsEnumsOutputOfferVariant + OfferNamed = ReflectapiDemoTestsEnumsOutputOfferNamed + Offer = ReflectapiDemoTestsEnumsOutputOffer + VariantBody = ReflectapiDemoTestsEnumsOutputVariantBody + ThingVariant = ReflectapiDemoTestsEnumsOutputThingVariant + ThingNamed = ReflectapiDemoTestsEnumsOutputThingNamed + Thing = ReflectapiDemoTestsEnumsOutputThing + + +class AsyncInoutClient: + """Async client for inout operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def test( + self, + data: Optional[reflectapi_demo.tests.enums.input.Offer] = None, + ) -> ApiResponse[reflectapi_demo.tests.enums.output.Offer]: + """ + + Args: + data: Request data for the test operation. + + Returns: + ApiResponse[reflectapi_demo.tests.enums.output.Offer]: Response containing reflectapi_demo.tests.enums.output.Offer data + """ + path = "/inout_test" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + json_model=data, + response_model=reflectapi_demo.tests.enums.output.Offer, + ) + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.inout = AsyncInoutClient(self) + + +class InoutClient: + """Synchronous client for inout operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def test( + self, + data: Optional[reflectapi_demo.tests.enums.input.Offer] = None, + ) -> ApiResponse[reflectapi_demo.tests.enums.output.Offer]: + """ + + Args: + data: Request data for the test operation. + + Returns: + ApiResponse[reflectapi_demo.tests.enums.output.Offer]: Response containing reflectapi_demo.tests.enums.output.Offer data + """ + path = "/inout_test" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + json_model=data, + response_model=reflectapi_demo.tests.enums.output.Offer, + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.inout = InoutClient(self) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references +_rebuild_errors: list[str] = [] +for _model in [ + ReflectapiDemoTestsEnumsInputOffer, + ReflectapiDemoTestsEnumsInputThing, + ReflectapiDemoTestsEnumsInputVariantBody, + ReflectapiDemoTestsEnumsOutputOffer, + ReflectapiDemoTestsEnumsOutputThing, + ReflectapiDemoTestsEnumsOutputVariantBody, +]: + if not hasattr(_model, "model_rebuild"): + continue + try: + _model.model_rebuild() + except Exception as _exc: + _rebuild_errors.append(f" - {_model.__name__}: {type(_exc).__name__}: {_exc}") +if _rebuild_errors: + raise RuntimeError( + "reflectapi: failed to rebuild " + + str(len(_rebuild_errors)) + + " generated model(s). This usually means the codegen emitted an annotation pointing at a symbol that was never defined (a dangling type reference). Fix the codegen rather than catching this error.\n" + + "\n".join(_rebuild_errors) + ) diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields.snap new file mode 100644 index 00000000..25210615 --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__enums__struct_with_flattened_internally_tagged_enum_with_optional_fields.snap @@ -0,0 +1,337 @@ +--- +source: reflectapi-demo/src/tests/enums.rs +expression: schema +--- +{ + "name": "", + "functions": [ + { + "name": "inout_test", + "path": "", + "input_type": { + "name": "reflectapi_demo::tests::enums::input::Offer" + }, + "output_kind": "complete", + "output_type": { + "name": "reflectapi_demo::tests::enums::output::Offer" + }, + "serialization": [ + "json", + "msgpack" + ] + } + ], + "input_types": { + "types": [ + { + "kind": "struct", + "name": "reflectapi::Empty", + "description": "Struct object with no fields", + "fields": "none" + }, + { + "kind": "struct", + "name": "reflectapi_demo::tests::enums::input::Offer", + "fields": { + "named": [ + { + "name": "id", + "type": { + "name": "std::string::String" + }, + "required": true + }, + { + "name": "note", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + }, + { + "name": "details", + "type": { + "name": "reflectapi_demo::tests::enums::input::Thing" + }, + "required": true, + "flattened": true + } + ] + } + }, + { + "kind": "enum", + "name": "reflectapi_demo::tests::enums::input::Thing", + "representation": { + "internal": { + "tag": "type" + } + }, + "variants": [ + { + "name": "variant", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "reflectapi_demo::tests::enums::input::VariantBody" + }, + "required": true + } + ] + } + }, + { + "name": "named", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + } + ] + } + } + ] + }, + { + "kind": "struct", + "name": "reflectapi_demo::tests::enums::input::VariantBody", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + }, + { + "name": "always_null_on_wire", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + } + ] + } + }, + { + "kind": "enum", + "name": "std::option::Option", + "description": "Optional nullable type", + "parameters": [ + { + "name": "T" + } + ], + "representation": "none", + "variants": [ + { + "name": "None", + "description": "The value is not provided, i.e. null", + "fields": "none" + }, + { + "name": "Some", + "description": "The value is provided and set to some value", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "T" + } + } + ] + } + } + ] + }, + { + "kind": "primitive", + "name": "std::string::String", + "description": "UTF-8 encoded string" + } + ] + }, + "output_types": { + "types": [ + { + "kind": "struct", + "name": "reflectapi::Infallible", + "description": "Error object which is expected to be never returned", + "fields": "none" + }, + { + "kind": "struct", + "name": "reflectapi_demo::tests::enums::output::Offer", + "fields": { + "named": [ + { + "name": "id", + "type": { + "name": "std::string::String" + }, + "required": true + }, + { + "name": "note", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + } + }, + { + "name": "details", + "type": { + "name": "reflectapi_demo::tests::enums::output::Thing" + }, + "required": true, + "flattened": true + } + ] + } + }, + { + "kind": "enum", + "name": "reflectapi_demo::tests::enums::output::Thing", + "representation": { + "internal": { + "tag": "type" + } + }, + "variants": [ + { + "name": "variant", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "reflectapi_demo::tests::enums::output::VariantBody" + }, + "required": true + } + ] + } + }, + { + "name": "named", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + } + } + ] + } + } + ] + }, + { + "kind": "struct", + "name": "reflectapi_demo::tests::enums::output::VariantBody", + "fields": { + "named": [ + { + "name": "amount", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + } + }, + { + "name": "always_null_on_wire", + "type": { + "name": "std::option::Option", + "arguments": [ + { + "name": "std::string::String" + } + ] + }, + "required": true + } + ] + } + }, + { + "kind": "enum", + "name": "std::option::Option", + "description": "Optional nullable type", + "parameters": [ + { + "name": "T" + } + ], + "representation": "none", + "variants": [ + { + "name": "None", + "description": "The value is not provided, i.e. null", + "fields": "none" + }, + { + "name": "Some", + "description": "The value is provided and set to some value", + "fields": { + "unnamed": [ + { + "name": "0", + "type": { + "name": "T" + } + } + ] + } + } + ] + }, + { + "kind": "primitive", + "name": "std::string::String", + "description": "UTF-8 encoded string" + } + ] + } +} diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__conflicting_names-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__conflicting_names-5.snap new file mode 100644 index 00000000..7e04639a --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__conflicting_names-5.snap @@ -0,0 +1,147 @@ +--- +source: reflectapi-demo/src/tests/namespace.rs +expression: python +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: foos +""" + +from __future__ import annotations + + +# Standard library imports +from typing import Annotated, Any, Generic, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +class FoosFoo(BaseModel): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + +# Namespace classes for dotted access to types +class foos: + """Namespace for foos types.""" + + Foo = FoosFoo + + +class AsyncFoosClient: + """Async client for foos operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def get( + self, + data: Optional[int] = None, + ) -> ApiResponse[foos.Foo]: + """ + + Args: + data: Request data for the get operation. + + Returns: + ApiResponse[foos.Foo]: Response containing foos.Foo data + """ + path = "/foos.get" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + json_data=data, + response_model=foos.Foo, + ) + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.foos = AsyncFoosClient(self) + + +class FoosClient: + """Synchronous client for foos operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def get( + self, + data: Optional[int] = None, + ) -> ApiResponse[foos.Foo]: + """ + + Args: + data: Request data for the get operation. + + Returns: + ApiResponse[foos.Foo]: Response containing foos.Foo data + """ + path = "/foos.get" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + json_data=data, + response_model=foos.Foo, + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.foos = FoosClient(self) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references +_rebuild_errors: list[str] = [] +for _model in [ + FoosFoo, +]: + if not hasattr(_model, "model_rebuild"): + continue + try: + _model.model_rebuild() + except Exception as _exc: + _rebuild_errors.append(f" - {_model.__name__}: {type(_exc).__name__}: {_exc}") +if _rebuild_errors: + raise RuntimeError( + "reflectapi: failed to rebuild " + + str(len(_rebuild_errors)) + + " generated model(s). This usually means the codegen emitted an annotation pointing at a symbol that was never defined (a dangling type reference). Fix the codegen rather than catching this error.\n" + + "\n".join(_rebuild_errors) + ) diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__conflicting_namespace_names-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__conflicting_namespace_names-5.snap new file mode 100644 index 00000000..a072ad10 --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__conflicting_namespace_names-5.snap @@ -0,0 +1,159 @@ +--- +source: reflectapi-demo/src/tests/namespace.rs +expression: python +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: foo +""" + +from __future__ import annotations + + +# Standard library imports +from typing import Annotated, Any, Generic, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +class AsyncXClient: + """Async client for x operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def foo_get( + self, + ) -> ApiResponse[Any]: + """ + + Returns: + ApiResponse[Any]: Response containing Any data + """ + path = "/x.foo.get" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + response_model=None, + ) + + +class AsyncYClient: + """Async client for y operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def foo_get( + self, + ) -> ApiResponse[None]: + """ + + Returns: + ApiResponse[None]: Response containing None data + """ + path = "/y.foo.get" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + response_model=None, + ) + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.x = AsyncXClient(self) + + self.y = AsyncYClient(self) + + +class XClient: + """Synchronous client for x operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def foo_get( + self, + ) -> ApiResponse[Any]: + """ + + Returns: + ApiResponse[Any]: Response containing Any data + """ + path = "/x.foo.get" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + response_model=None, + ) + + +class YClient: + """Synchronous client for y operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def foo_get( + self, + ) -> ApiResponse[None]: + """ + + Returns: + ApiResponse[None]: Response containing None data + """ + path = "/y.foo.get" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + response_model=None, + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.x = XClient(self) + + self.y = YClient(self) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__generic_and_type_conflict-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__generic_and_type_conflict-5.snap new file mode 100644 index 00000000..ac70682e --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__generic_and_type_conflict-5.snap @@ -0,0 +1,157 @@ +--- +source: reflectapi-demo/src/tests/namespace.rs +expression: python +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: +""" + +from __future__ import annotations + + +# Standard library imports +from typing import Annotated, Any, Generic, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +# Type variables for generic types + + +I = TypeVar("I") + +_T_T = TypeVar("_T_T") + + +class ReflectapiDemoTestsNamespaceK(BaseModel, Generic[_T_T]): + model_config = ConfigDict( + extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True + ) + + t: _T_T + + +# Namespace classes for dotted access to types +class reflectapi_demo: + """Namespace for reflectapi_demo types.""" + + class tests: + """Namespace for tests types.""" + + class namespace: + """Namespace for namespace types.""" + + K = ReflectapiDemoTestsNamespaceK + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + async def get( + self, + data: Optional[ + reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]] + ] = None, + ) -> ApiResponse[ + reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]] + ]: + """ + + Args: + data: Request data for the get operation. + + Returns: + ApiResponse[reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]]]: Response containing reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]] data + """ + path = "/get" + + params: dict[str, Any] = {} + return await self._make_request( + path, + params=params if params else None, + json_model=data, + response_model=reflectapi_demo.tests.namespace.K[ + Annotated[Any, "External type: I"] + ], + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + def get( + self, + data: Optional[ + reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]] + ] = None, + ) -> ApiResponse[ + reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]] + ]: + """ + + Args: + data: Request data for the get operation. + + Returns: + ApiResponse[reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]]]: Response containing reflectapi_demo.tests.namespace.K[Annotated[Any, "External type: I"]] data + """ + path = "/get" + + params: dict[str, Any] = {} + return self._make_request( + path, + params=params if params else None, + json_model=data, + response_model=reflectapi_demo.tests.namespace.K[ + Annotated[Any, "External type: I"] + ], + ) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references +_rebuild_errors: list[str] = [] +for _model in [ + ReflectapiDemoTestsNamespaceK, +]: + if not hasattr(_model, "model_rebuild"): + continue + try: + _model.model_rebuild() + except Exception as _exc: + _rebuild_errors.append(f" - {_model.__name__}: {type(_exc).__name__}: {_exc}") +if _rebuild_errors: + raise RuntimeError( + "reflectapi: failed to rebuild " + + str(len(_rebuild_errors)) + + " generated model(s). This usually means the codegen emitted an annotation pointing at a symbol that was never defined (a dangling type reference). Fix the codegen rather than catching this error.\n" + + "\n".join(_rebuild_errors) + ) diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__namespace_with_dash-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__namespace_with_dash-5.snap new file mode 100644 index 00000000..1450ccaa --- /dev/null +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__namespace__namespace_with_dash-5.snap @@ -0,0 +1,107 @@ +--- +source: reflectapi-demo/src/tests/namespace.rs +expression: python +--- +""" +DO NOT MODIFY THIS FILE MANUALLY +This file was generated by reflectapi-cli + +Schema name: pet-orders +""" + +from __future__ import annotations + + +# Standard library imports +from typing import Annotated, Any, Generic, Optional, TypeVar, Union + +# Third-party imports +from pydantic import BaseModel, ConfigDict, Field + +# Runtime imports +from reflectapi_runtime import AsyncClientBase, ClientBase, ApiResponse +from reflectapi_runtime import ReflectapiEmpty +from reflectapi_runtime import ReflectapiInfallible + + +class AsyncJobsTwoClient: + """Async client for jobs_two operations.""" + + def __init__(self, client: AsyncClientBase) -> None: + self._client = client + + async def pet_orders_list_x( + self, + ) -> ApiResponse[Any]: + """desc + + Returns: + ApiResponse[Any]: Response containing Any data + """ + path = "/jobs-two.pet-orders.list-x" + + params: dict[str, Any] = {} + return await self._client._make_request( + path, + params=params if params else None, + response_model=None, + ) + + +class AsyncClient(AsyncClientBase): + """Async client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.jobs_two = AsyncJobsTwoClient(self) + + +class JobsTwoClient: + """Synchronous client for jobs_two operations.""" + + def __init__(self, client: ClientBase) -> None: + self._client = client + + def pet_orders_list_x( + self, + ) -> ApiResponse[Any]: + """desc + + Returns: + ApiResponse[Any]: Response containing Any data + """ + path = "/jobs-two.pet-orders.list-x" + + params: dict[str, Any] = {} + return self._client._make_request( + path, + params=params if params else None, + response_model=None, + ) + + +class Client(ClientBase): + """Synchronous client for the API.""" + + def __init__( + self, + base_url: str, + **kwargs: Any, + ) -> None: + super().__init__(base_url, **kwargs) + + self.jobs_two = JobsTwoClient(self) + + +# External type definitions +StdNumNonZeroU32 = Annotated[int, "Rust NonZero u32 type"] +StdNumNonZeroU64 = Annotated[int, "Rust NonZero u64 type"] +StdNumNonZeroI32 = Annotated[int, "Rust NonZero i32 type"] +StdNumNonZeroI64 = Annotated[int, "Rust NonZero i64 type"] + +# Rebuild models to resolve forward references diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_drops_inner_fields-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_drops_inner_fields-5.snap index 6dd302f4..6b77f7f1 100644 --- a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_drops_inner_fields-5.snap +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_drops_inner_fields-5.snap @@ -49,7 +49,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseReflectapiDemoTestsSerdeTestFlatC4 extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None inner_a: int inner_b: str diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_leaf_collision-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_leaf_collision-5.snap index 180b3615..cfe45b98 100644 --- a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_leaf_collision-5.snap +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_leaf_collision-5.snap @@ -49,7 +49,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseReflectapiDemoTestsSerdeModuleAS00 extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None a_field: int @@ -60,7 +60,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseReflectapiDemoTestsSerdeModuleBSD4 extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None b_field: bool diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_nested-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_nested-5.snap index f64cf279..9dfa9e56 100644 --- a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_nested-5.snap +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_nested-5.snap @@ -67,7 +67,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseReflectapiDemoTestsSerdeTestIden57 extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None job_id: int payload: str diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_two_instantiations-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_two_instantiations-5.snap index 02a6dd5a..a6014c33 100644 --- a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_two_instantiations-5.snap +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_two_instantiations-5.snap @@ -66,7 +66,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseReflectapiDemoTestsSerdeTestFlat7b extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None alt_x: bool @@ -77,7 +77,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseReflectapiDemoTestsSerdeTestFlatC4 extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None inner_a: int inner_b: str diff --git a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_typevar_nested_in_generic_arg-5.snap b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_typevar_nested_in_generic_arg-5.snap index 3cc2e5f1..d0d7ef4c 100644 --- a/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_typevar_nested_in_generic_arg-5.snap +++ b/reflectapi-demo/src/tests/snapshots/reflectapi_demo__tests__serde__generic_flatten_typevar_nested_in_generic_arg-5.snap @@ -49,7 +49,7 @@ class ReflectapiDemoTestsSerdeTestUpdateOrElseStdOptionReflectapiDemoTestsSerd2c extra="ignore", populate_by_name=True, protected_namespaces=(), defer_build=True ) - if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None + if_else: reflectapi_demo.tests.serde.TestFlattenIfElse | None = None inner_a: int inner_b: str diff --git a/reflectapi/src/codegen/python.rs b/reflectapi/src/codegen/python.rs index 61ee34ee..593f3b06 100644 --- a/reflectapi/src/codegen/python.rs +++ b/reflectapi/src/codegen/python.rs @@ -753,21 +753,15 @@ fn render_struct_with_flattened_internal_enum( active_generics, used_type_vars, )?; + let (optional, default_value, final_type) = + resolve_field_optionality(&field.type_ref.name, field_type, field.required); base_fields.push(templates::Field { name: python_name, - type_annotation: if field.required { - field_type - } else { - format!("{field_type} | None") - }, + type_annotation: final_type, description: Some(field.description().to_string()), deprecation_note: field.deprecation_note.clone(), - optional: !field.required, - default_value: if field.required { - None - } else { - Some("None".to_string()) - }, + optional, + default_value, alias, is_partial: field.type_ref.name == "reflectapi::Option", @@ -809,21 +803,15 @@ fn render_struct_with_flattened_internal_enum( )?; let (python_name, alias) = sanitize_field_name_with_alias(field.name(), field.serde_name()); + let (optional, default_value, final_type) = + resolve_field_optionality(&field.type_ref.name, field_type, field.required); base_fields.push(templates::Field { name: python_name, - type_annotation: if field.required { - field_type - } else { - format!("{field_type} | None") - }, + type_annotation: final_type, description: Some(field.description().to_string()), deprecation_note: field.deprecation_note.clone(), - optional: !field.required, - default_value: if field.required { - None - } else { - Some("None".to_string()) - }, + optional, + default_value, alias, is_partial: field.type_ref.name == "reflectapi::Option", @@ -928,21 +916,15 @@ fn render_struct_with_flattened_internal_enum( )?; let (sanitized, alias) = sanitize_field_name_with_alias(sf.name(), sf.serde_name()); + let (optional, default_value, final_type) = + resolve_field_optionality(&sf.type_ref.name, field_type, sf.required); fields.push(templates::Field { name: sanitized, - type_annotation: if sf.required { - field_type - } else { - format!("{field_type} | None") - }, + type_annotation: final_type, description: Some(sf.description().to_string()), deprecation_note: sf.deprecation_note.clone(), - optional: !sf.required, - default_value: if sf.required { - None - } else { - Some("None".to_string()) - }, + optional, + default_value, alias, is_partial: sf.type_ref.name == "reflectapi::Option", @@ -1018,21 +1000,15 @@ fn render_struct_with_flatten_standard( used_type_vars, )?; + let (optional, default_value, final_type) = + resolve_field_optionality(&field.type_ref.name, field_type, field.required); all_fields.push(templates::Field { name: python_name, - type_annotation: if field.required { - field_type - } else { - format!("{field_type} | None") - }, + type_annotation: final_type, description: Some(field.description().to_string()), deprecation_note: field.deprecation_note.clone(), - optional: !field.required, - default_value: if field.required { - None - } else { - Some("None".to_string()) - }, + optional, + default_value, alias, is_partial: field.type_ref.name == "reflectapi::Option", @@ -3740,15 +3716,8 @@ fn collect_flattened_enum_fields( }); let (sanitized, alias) = sanitize_field_name_with_alias(&field_name, &field_name); - let (optional, default_value, final_type) = if !parent_required { - ( - true, - Some("None".to_string()), - format!("{enum_python_type} | None"), - ) - } else { - (false, None, enum_python_type) - }; + let (optional, default_value, final_type) = + resolve_field_optionality(&type_ref.name, enum_python_type, parent_required); collected_fields.push(templates::Field { name: sanitized,