@aws/nx-plugin 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-THIRD-PARTY +11686 -23398
- package/README.md +18 -0
- package/generators.json +7 -0
- package/migrations.json +39 -1
- package/package.json +6 -6
- package/src/agentcore-gateway/agent-connection/generator.js +6 -0
- package/src/agentcore-gateway/agent-connection/generator.js.map +1 -1
- package/src/agentcore-gateway/schema.json +1 -0
- package/src/agentcore-harness/generator.d.ts +1 -1
- package/src/connection/scaffold-catalog.d.ts +21 -0
- package/src/connection/scaffold-catalog.js +14 -6
- package/src/connection/scaffold-catalog.js.map +1 -1
- package/src/infra/app/generator.d.ts +1 -1
- package/src/init/generator.d.ts +1 -1
- package/src/mcp-server/guide-pipeline.d.ts +2 -1
- package/src/mcp-server/guide-pipeline.js +6 -0
- package/src/mcp-server/guide-pipeline.js.map +1 -1
- package/src/mcp-server/mdx-ast.d.ts +7 -0
- package/src/mcp-server/mdx-ast.js +15 -0
- package/src/mcp-server/mdx-ast.js.map +1 -1
- package/src/mcp-server/schema-registry.d.ts +2 -2
- package/src/mcp-server/schema-registry.js +2 -2
- package/src/mcp-server/schema-registry.js.map +1 -1
- package/src/migrations/latest/terraform-harness-environment-variables/metadata.json +3 -0
- package/src/migrations/latest/terraform-harness-environment-variables/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-harness-environment-variables/migration.js +54 -0
- package/src/migrations/latest/terraform-harness-environment-variables/migration.js.map +1 -0
- package/src/open-api/py-client/__snapshots__/generator.additional-properties.spec.ts.snap +34 -0
- package/src/open-api/py-client/__snapshots__/generator.arrays.spec.ts.snap +1750 -0
- package/src/open-api/py-client/__snapshots__/generator.complex-types.spec.ts.snap +82 -0
- package/src/open-api/py-client/__snapshots__/generator.composite-types.spec.ts.snap +447 -0
- package/src/open-api/py-client/__snapshots__/generator.duplicate-types.spec.ts.snap +404 -0
- package/src/open-api/py-client/__snapshots__/generator.errors.spec.ts.snap +430 -0
- package/src/open-api/py-client/__snapshots__/generator.fast-api.spec.ts.snap +447 -0
- package/src/open-api/py-client/__snapshots__/generator.petstore.spec.ts.snap +3488 -0
- package/src/open-api/py-client/__snapshots__/generator.primitive-types.spec.ts.snap +1444 -0
- package/src/open-api/py-client/__snapshots__/generator.request.spec.ts.snap +390 -0
- package/src/open-api/py-client/__snapshots__/generator.reserved-keywords.spec.ts.snap +364 -0
- package/src/open-api/py-client/__snapshots__/generator.response.spec.ts.snap +739 -0
- package/src/open-api/py-client/__snapshots__/generator.streaming.spec.ts.snap +384 -0
- package/src/open-api/py-client/__snapshots__/generator.tags.spec.ts.snap +414 -0
- package/src/open-api/py-client/files/client/__clientModuleName__.py.template +1076 -0
- package/src/open-api/py-client/files/shared/__init__.py.template +28 -0
- package/src/open-api/py-client/files/shared/errors.py.template +37 -0
- package/src/open-api/py-client/files/shared/types.py.template +319 -0
- package/src/open-api/py-client/generator.d.ts +53 -0
- package/src/open-api/py-client/generator.js +119 -0
- package/src/open-api/py-client/generator.js.map +1 -0
- package/src/open-api/py-client/schema.d.js +6 -0
- package/src/open-api/py-client/schema.d.js.map +1 -0
- package/src/open-api/py-client/schema.d.ts +12 -0
- package/src/open-api/py-client/schema.json +28 -0
- package/src/open-api/py-client/vessel-registry-spec.json +2716 -0
- package/src/open-api/ts-client/__snapshots__/generator.edge-cases.spec.ts.snap +274 -0
- package/src/open-api/ts-client/petstore-spec.d.ts +6 -0
- package/src/open-api/ts-client/petstore-spec.js +1275 -0
- package/src/open-api/ts-client/petstore-spec.js.map +1 -0
- package/src/open-api/ts-hooks/generator.spec.tsx +1 -1
- package/src/open-api/utils/codegen-data/languages.d.ts +68 -2
- package/src/open-api/utils/codegen-data/languages.js +433 -21
- package/src/open-api/utils/codegen-data/languages.js.map +1 -1
- package/src/open-api/utils/codegen-data/types.d.ts +187 -0
- package/src/open-api/utils/codegen-data/types.js +10 -1
- package/src/open-api/utils/codegen-data/types.js.map +1 -1
- package/src/open-api/utils/codegen-data.d.ts +40 -1
- package/src/open-api/utils/codegen-data.js +622 -17
- package/src/open-api/utils/codegen-data.js.map +1 -1
- package/src/open-api/utils/normalise.js +175 -41
- package/src/open-api/utils/normalise.js.map +1 -1
- package/src/open-api/utils/parser.js +1 -1
- package/src/open-api/utils/parser.js.map +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/preset/generator.d.ts +1 -1
- package/src/py/agent/gateway-connection/generator.js +0 -3
- package/src/py/agent/gateway-connection/generator.js.map +1 -1
- package/src/py/agent/react-connection/generator.d.ts +1 -1
- package/src/py/agent/schema.json +2 -0
- package/src/py/fast-api/react/generator.d.ts +1 -1
- package/src/py/mcp-server/schema.json +1 -0
- package/src/sdk/open-api.d.ts +2 -0
- package/src/sdk/open-api.js +1 -0
- package/src/sdk/open-api.js.map +1 -1
- package/src/smithy/project/generator.d.ts +1 -1
- package/src/smithy/react-connection/generator.d.ts +1 -1
- package/src/terraform/project/generator.d.ts +1 -1
- package/src/trpc/react/generator.d.ts +1 -1
- package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
- package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
- package/src/ts/agent/gateway-connection/generator.js +0 -3
- package/src/ts/agent/gateway-connection/generator.js.map +1 -1
- package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
- package/src/ts/agent/react-connection/generator.d.ts +1 -1
- package/src/ts/agent/schema.json +1 -0
- package/src/ts/api/schema.json +2 -0
- package/src/ts/astro-docs/generator.d.ts +1 -1
- package/src/ts/dcr-proxy/generator.d.ts +1 -1
- package/src/ts/dynamodb/generator.d.ts +1 -1
- package/src/ts/lambda-function/generator.d.ts +1 -1
- package/src/ts/lib/generator.d.ts +1 -1
- package/src/ts/mcp-server/schema.json +1 -0
- package/src/ts/nx-generator/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/ts/nx-generator/files/nx-plugin-for-aws/generator/generator.spec.ts.template +1 -1
- package/src/ts/nx-generator/generator.d.ts +1 -1
- package/src/ts/nx-migration/generator.d.ts +1 -1
- package/src/ts/nx-plugin/generator.d.ts +1 -1
- package/src/ts/rdb/generator.d.ts +1 -1
- package/src/ts/react-website/agui/generator.d.ts +1 -1
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +10 -10
- package/src/ts/react-website/app/schema.json +1 -0
- package/src/ts/website/app/schema.json +1 -0
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +1 -1
- package/src/utils/metrics-assertions.d.ts +18 -0
- package/src/utils/metrics-assertions.js +42 -0
- package/src/utils/metrics-assertions.js.map +1 -0
- package/src/utils/test/python-dependencies.d.ts +8 -0
- package/src/utils/test/python-dependencies.js +14 -0
- package/src/utils/test/python-dependencies.js.map +1 -0
- package/src/utils/test/python-worker/worker.py +703 -0
- package/src/utils/test/warm-python-cache.d.ts +18 -0
- package/src/utils/test/warm-python-cache.js +46 -0
- package/src/utils/test/warm-python-cache.js.map +1 -0
- package/src/utils/versions.d.ts +10 -9
- package/src/utils/versions.js +9 -8
- package/src/utils/versions.js.map +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`openApiPyClientGenerator - complex types > should handle nested objects > types.py 1`] = `
|
|
4
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Never, TypedDict
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Inner(BaseModel):
|
|
14
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
15
|
+
|
|
16
|
+
n: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Outer(BaseModel):
|
|
20
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
21
|
+
|
|
22
|
+
inner: Inner
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OuterRequest(TypedDict):
|
|
26
|
+
body: Outer
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
OuterError = Never
|
|
30
|
+
"
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
exports[`openApiPyClientGenerator - complex types > should handle nullable schemas in various contexts > types.py 1`] = `
|
|
34
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import Never, TypedDict
|
|
39
|
+
|
|
40
|
+
from pydantic import BaseModel, ConfigDict
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class N(BaseModel):
|
|
44
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
45
|
+
|
|
46
|
+
a: str | None = None
|
|
47
|
+
b: int | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class XRequest(TypedDict):
|
|
51
|
+
body: N
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
XError = Never
|
|
55
|
+
"
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
exports[`openApiPyClientGenerator - complex types > should handle operations with complex map types > types.py 1`] = `
|
|
59
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
60
|
+
|
|
61
|
+
from __future__ import annotations
|
|
62
|
+
|
|
63
|
+
from typing import Never
|
|
64
|
+
|
|
65
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class MapsResponse(BaseModel):
|
|
69
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
70
|
+
|
|
71
|
+
by_id: dict[str, MapsResponseByIdValue] = Field(alias="byId")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class MapsResponseByIdValue(BaseModel):
|
|
75
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
76
|
+
|
|
77
|
+
name: str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
MapsError = Never
|
|
81
|
+
"
|
|
82
|
+
`;
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`openApiPyClientGenerator - composite types > should emit Union types for anyOf > types.py 1`] = `
|
|
4
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Never, TypedDict
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Wrap(BaseModel):
|
|
14
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
15
|
+
|
|
16
|
+
value: WrapValue
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
WrapValue = str | int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class URequest(TypedDict):
|
|
23
|
+
body: Wrap
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
UError = Never
|
|
27
|
+
"
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
exports[`openApiPyClientGenerator - composite types > should emit Union types for oneOf > types.py 1`] = `
|
|
31
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from typing import Never
|
|
36
|
+
|
|
37
|
+
from pydantic import BaseModel, ConfigDict
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Wrap(BaseModel):
|
|
41
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
42
|
+
|
|
43
|
+
value: WrapValue
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
WrapValue = str | bool
|
|
47
|
+
|
|
48
|
+
OError = Never
|
|
49
|
+
"
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
exports[`openApiPyClientGenerator - composite types > should flatten allOf inheritance into a single pydantic class > client.py 1`] = `
|
|
53
|
+
""""TestApi — AUTO-GENERATED synchronous client."""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import datetime
|
|
58
|
+
from dataclasses import dataclass, field
|
|
59
|
+
from typing import Any, TypeVar
|
|
60
|
+
from urllib.parse import quote
|
|
61
|
+
|
|
62
|
+
import httpx
|
|
63
|
+
from pydantic import BaseModel, ValidationError
|
|
64
|
+
|
|
65
|
+
from . import types
|
|
66
|
+
from .errors import (
|
|
67
|
+
ApiError as ApiError,
|
|
68
|
+
)
|
|
69
|
+
from .errors import (
|
|
70
|
+
DogApiError as DogApiError,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
_ErrorT = TypeVar("_ErrorT", bound=BaseModel)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class TestApiConfig:
|
|
78
|
+
"""Client configuration for TestApi."""
|
|
79
|
+
|
|
80
|
+
#: Base URL for the API.
|
|
81
|
+
url: str
|
|
82
|
+
#: Client used for every request. Supply your own to add authentication or
|
|
83
|
+
#: any other middleware — an \`\`httpx.Auth\`\` (e.g. a SigV4 signer), an event
|
|
84
|
+
#: hook, a custom transport, timeouts, retries or a proxy all apply, and the
|
|
85
|
+
#: client's own headers and query params are sent alongside each operation's.
|
|
86
|
+
#: A supplied client is never closed by this one, so it can be shared.
|
|
87
|
+
httpx_client: httpx.Client | None = None
|
|
88
|
+
#: Headers added to every request, where the operation sets none itself.
|
|
89
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
90
|
+
#: By default the client sends a Content-Type header set to the media type
|
|
91
|
+
#: the OpenAPI specification declares for the request. Set this to omit it.
|
|
92
|
+
omit_content_type_header: bool = False
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestApi:
|
|
96
|
+
"""Synchronous API client for TestApi."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, config: TestApiConfig) -> None:
|
|
99
|
+
self._config = config
|
|
100
|
+
self._client = config.httpx_client or httpx.Client()
|
|
101
|
+
self._owns_client = config.httpx_client is None
|
|
102
|
+
self._base_url = config.url.rstrip("/")
|
|
103
|
+
|
|
104
|
+
def __enter__(self) -> TestApi:
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def __exit__(self, *exc: Any) -> None:
|
|
108
|
+
self.close()
|
|
109
|
+
|
|
110
|
+
def close(self) -> None:
|
|
111
|
+
if self._owns_client:
|
|
112
|
+
self._client.close()
|
|
113
|
+
|
|
114
|
+
def _url(
|
|
115
|
+
self,
|
|
116
|
+
path: str,
|
|
117
|
+
path_params: dict[str, Any],
|
|
118
|
+
path_styles: dict[str, tuple[str, bool]] | None = None,
|
|
119
|
+
) -> str:
|
|
120
|
+
for key, value in path_params.items():
|
|
121
|
+
style, explode = (path_styles or {}).get(key, ("simple", False))
|
|
122
|
+
# An object expands to its key,value members; a list to its items.
|
|
123
|
+
# Each is encoded on its own so the separators between them stay
|
|
124
|
+
# literal, as RFC 6570 requires.
|
|
125
|
+
dumped = self._dump(value)
|
|
126
|
+
if isinstance(dumped, dict):
|
|
127
|
+
values = [
|
|
128
|
+
part
|
|
129
|
+
for k, v in dumped.items()
|
|
130
|
+
if v is not None
|
|
131
|
+
for part in (k, self._scalar(v))
|
|
132
|
+
]
|
|
133
|
+
elif isinstance(dumped, list):
|
|
134
|
+
values = list(dumped)
|
|
135
|
+
else:
|
|
136
|
+
values = [value]
|
|
137
|
+
if style == "matrix":
|
|
138
|
+
# RFC 6570 path-style expansion: ;key=a,b (explode: ;key=a;key=b)
|
|
139
|
+
if explode:
|
|
140
|
+
rendered = "".join(
|
|
141
|
+
";" + key + "=" + quote(self._scalar(v), safe="")
|
|
142
|
+
for v in values
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
rendered = (
|
|
146
|
+
";"
|
|
147
|
+
+ key
|
|
148
|
+
+ "="
|
|
149
|
+
+ ",".join(quote(self._scalar(v), safe="") for v in values)
|
|
150
|
+
)
|
|
151
|
+
elif style == "label":
|
|
152
|
+
# RFC 6570 label expansion: .a,b (explode: .a.b)
|
|
153
|
+
rendered = "." + ("." if explode else ",").join(
|
|
154
|
+
quote(self._scalar(v), safe="") for v in values
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
# RFC 6570 simple expansion: a list renders comma-separated.
|
|
158
|
+
rendered = ",".join(quote(self._scalar(v), safe="") for v in values)
|
|
159
|
+
path = path.replace("{" + key + "}", rendered)
|
|
160
|
+
return self._base_url + path
|
|
161
|
+
|
|
162
|
+
def _deep_object(self, key: str, value: Any, out: dict[str, Any]) -> None:
|
|
163
|
+
if isinstance(value, BaseModel):
|
|
164
|
+
value = value.model_dump(mode="json", by_alias=True, exclude_unset=True)
|
|
165
|
+
if isinstance(value, dict):
|
|
166
|
+
for prop, v in value.items():
|
|
167
|
+
self._deep_object(f"{key}[{prop}]", v, out)
|
|
168
|
+
elif value is not None:
|
|
169
|
+
out[key] = value
|
|
170
|
+
|
|
171
|
+
_COLLECTION_DELIMITERS = {"csv": ",", "ssv": " ", "pipes": "|"}
|
|
172
|
+
|
|
173
|
+
#: Characters left literal in a cookie value: every RFC 6265 cookie-octet
|
|
174
|
+
#: except the \`,\` and \`;\` this client joins members on. A \`=\` stays literal
|
|
175
|
+
#: too — only the first one separates a cookie's name from its value.
|
|
176
|
+
_COOKIE_SAFE = "!#$%&'()*+-./:<>?@[]^_\`{|}~="
|
|
177
|
+
|
|
178
|
+
def _scalar(self, value: Any) -> str:
|
|
179
|
+
# The wire form of a single parameter value: JSON spellings for bools,
|
|
180
|
+
# ISO-8601 for dates, and the model's own wire shape for anything else.
|
|
181
|
+
if isinstance(value, bool):
|
|
182
|
+
return "true" if value else "false"
|
|
183
|
+
if isinstance(value, (datetime.date, datetime.datetime)):
|
|
184
|
+
return value.isoformat()
|
|
185
|
+
dumped = self._dump(value)
|
|
186
|
+
if isinstance(dumped, str):
|
|
187
|
+
return dumped
|
|
188
|
+
# RFC 6570 renders a list as its comma-joined members and an object as
|
|
189
|
+
# comma-joined key,value pairs. Python's own \`str\` would emit a repr —
|
|
190
|
+
# \`['a', 'b']\`, quotes and spaces included.
|
|
191
|
+
if isinstance(dumped, list):
|
|
192
|
+
return ",".join(self._scalar(v) for v in dumped)
|
|
193
|
+
if isinstance(dumped, dict):
|
|
194
|
+
return ",".join(
|
|
195
|
+
f"{k},{self._scalar(v)}" for k, v in dumped.items() if v is not None
|
|
196
|
+
)
|
|
197
|
+
if dumped is None:
|
|
198
|
+
return ""
|
|
199
|
+
return str(dumped)
|
|
200
|
+
|
|
201
|
+
def _query(
|
|
202
|
+
self,
|
|
203
|
+
query_params: dict[str, Any],
|
|
204
|
+
collection_formats: dict[str, str] | None = None,
|
|
205
|
+
) -> dict[str, Any]:
|
|
206
|
+
out: dict[str, Any] = {}
|
|
207
|
+
for key, value in query_params.items():
|
|
208
|
+
if value is None:
|
|
209
|
+
continue
|
|
210
|
+
fmt = (collection_formats or {}).get(key)
|
|
211
|
+
if fmt == "deepObject":
|
|
212
|
+
self._deep_object(key, value, out)
|
|
213
|
+
elif isinstance(value, list):
|
|
214
|
+
delimiter = self._COLLECTION_DELIMITERS.get(fmt or "multi")
|
|
215
|
+
if delimiter is None:
|
|
216
|
+
out[key] = [self._scalar(v) for v in value]
|
|
217
|
+
else:
|
|
218
|
+
out[key] = delimiter.join(self._scalar(v) for v in value)
|
|
219
|
+
elif self._is_object(value):
|
|
220
|
+
# OpenAPI's default query style is form/explode, under which an
|
|
221
|
+
# object expands to one parameter per property rather than
|
|
222
|
+
# being stringified.
|
|
223
|
+
for prop, prop_value in self._as_dict(value).items():
|
|
224
|
+
if prop_value is not None:
|
|
225
|
+
out[prop] = self._scalar(prop_value)
|
|
226
|
+
else:
|
|
227
|
+
out[key] = self._scalar(value)
|
|
228
|
+
return out
|
|
229
|
+
|
|
230
|
+
def _is_object(self, value: Any) -> bool:
|
|
231
|
+
return isinstance(value, (dict, BaseModel))
|
|
232
|
+
|
|
233
|
+
def _as_dict(self, value: Any) -> dict[str, Any]:
|
|
234
|
+
if isinstance(value, BaseModel):
|
|
235
|
+
return value.model_dump(mode="json", by_alias=True, exclude_unset=True)
|
|
236
|
+
return dict(value)
|
|
237
|
+
|
|
238
|
+
def _headers(
|
|
239
|
+
self,
|
|
240
|
+
header_params: dict[str, Any] | None = None,
|
|
241
|
+
collection_formats: dict[str, str] | None = None,
|
|
242
|
+
cookie_params: dict[str, Any] | None = None,
|
|
243
|
+
) -> list[tuple[str, str]]:
|
|
244
|
+
out: list[tuple[str, str]] = []
|
|
245
|
+
for key, value in (header_params or {}).items():
|
|
246
|
+
if value is None:
|
|
247
|
+
continue
|
|
248
|
+
if isinstance(value, list):
|
|
249
|
+
fmt = (collection_formats or {}).get(key, "csv")
|
|
250
|
+
delimiter = self._COLLECTION_DELIMITERS.get(fmt)
|
|
251
|
+
if delimiter is None:
|
|
252
|
+
for item in value:
|
|
253
|
+
out.append((key, self._scalar(item)))
|
|
254
|
+
else:
|
|
255
|
+
out.append((key, delimiter.join(self._scalar(v) for v in value)))
|
|
256
|
+
else:
|
|
257
|
+
out.append((key, self._scalar(value)))
|
|
258
|
+
# A config header only applies where the operation didn't set one:
|
|
259
|
+
# httpx comma-joins repeated names, which would corrupt the value.
|
|
260
|
+
operation_headers = {key.lower() for key, _ in out}
|
|
261
|
+
out.extend(
|
|
262
|
+
(key, value)
|
|
263
|
+
for key, value in self._config.headers.items()
|
|
264
|
+
if key.lower() not in operation_headers
|
|
265
|
+
)
|
|
266
|
+
# The operation's cookies are merged into one \`Cookie\` header alongside
|
|
267
|
+
# every other source. Passing them to httpx's \`cookies=\` instead would
|
|
268
|
+
# drop them outright whenever a \`Cookie\` header is present, since
|
|
269
|
+
# http.cookiejar skips a request that already carries one — and setting
|
|
270
|
+
# the header here would in turn shadow the caller's own jar and header,
|
|
271
|
+
# which httpx resolves in favour of the more specific value.
|
|
272
|
+
cookies = self._cookies(cookie_params or {})
|
|
273
|
+
if cookies:
|
|
274
|
+
pairs = [
|
|
275
|
+
*(
|
|
276
|
+
value
|
|
277
|
+
for source in (self._client.headers, dict(out))
|
|
278
|
+
for key, value in source.items()
|
|
279
|
+
if key.lower() == "cookie"
|
|
280
|
+
),
|
|
281
|
+
*(f"{key}={value}" for key, value in self._client.cookies.items()),
|
|
282
|
+
*(f"{key}={value}" for key, value in cookies.items()),
|
|
283
|
+
]
|
|
284
|
+
out = [(key, value) for key, value in out if key.lower() != "cookie"]
|
|
285
|
+
out.append(("Cookie", "; ".join(pairs)))
|
|
286
|
+
return out
|
|
287
|
+
|
|
288
|
+
def _default_content_type(
|
|
289
|
+
self, header_params: dict[str, Any], media_type: str
|
|
290
|
+
) -> None:
|
|
291
|
+
# The media type the specification declares, applied unless the operation
|
|
292
|
+
# takes a Content-Type of its own and the caller supplied one. Header
|
|
293
|
+
# names are case-insensitive, and a declared header the caller omitted is
|
|
294
|
+
# present holding \`None\` — neither counts as the caller setting it.
|
|
295
|
+
if any(
|
|
296
|
+
key.lower() == "content-type" and value is not None
|
|
297
|
+
for key, value in header_params.items()
|
|
298
|
+
):
|
|
299
|
+
return
|
|
300
|
+
for key in list(header_params):
|
|
301
|
+
if key.lower() == "content-type":
|
|
302
|
+
header_params[key] = media_type
|
|
303
|
+
return
|
|
304
|
+
header_params["Content-Type"] = media_type
|
|
305
|
+
|
|
306
|
+
def _cookies(self, cookie_params: dict[str, Any]) -> dict[str, str]:
|
|
307
|
+
out: dict[str, str] = {}
|
|
308
|
+
for key, value in cookie_params.items():
|
|
309
|
+
if value is None:
|
|
310
|
+
continue
|
|
311
|
+
dumped = self._dump(value)
|
|
312
|
+
# A cookie uses form-style expansion, so a list joins on \`,\` and an
|
|
313
|
+
# object on \`,\` between each key and value. Members are encoded so a
|
|
314
|
+
# \`;\` or \`,\` inside one can't be read as a delimiter, while the
|
|
315
|
+
# joining commas stay literal.
|
|
316
|
+
if isinstance(dumped, list):
|
|
317
|
+
rendered = ",".join(self._cookie_value(v) for v in dumped)
|
|
318
|
+
elif isinstance(dumped, dict):
|
|
319
|
+
rendered = ",".join(
|
|
320
|
+
self._cookie_value(k) + "," + self._cookie_value(v)
|
|
321
|
+
for k, v in dumped.items()
|
|
322
|
+
if v is not None
|
|
323
|
+
)
|
|
324
|
+
else:
|
|
325
|
+
rendered = self._cookie_value(value)
|
|
326
|
+
out[key] = rendered
|
|
327
|
+
return out
|
|
328
|
+
|
|
329
|
+
def _cookie_value(self, value: Any) -> str:
|
|
330
|
+
# Only the characters a cookie parser treats as structure are escaped, so
|
|
331
|
+
# a timestamp keeps its colons and a base64 token its \`=\` padding. Servers
|
|
332
|
+
# do not URL-decode cookies, so anything escaped here arrives escaped.
|
|
333
|
+
return quote(self._scalar(value), safe=self._COOKIE_SAFE)
|
|
334
|
+
|
|
335
|
+
def _dump(self, value: Any) -> Any:
|
|
336
|
+
if value is None:
|
|
337
|
+
return None
|
|
338
|
+
if isinstance(value, BaseModel):
|
|
339
|
+
return value.model_dump(mode="json", by_alias=True, exclude_unset=True)
|
|
340
|
+
if isinstance(value, (list, tuple)):
|
|
341
|
+
return [self._dump(v) for v in value]
|
|
342
|
+
if isinstance(value, dict):
|
|
343
|
+
return {k: self._dump(v) for k, v in value.items()}
|
|
344
|
+
if isinstance(value, (datetime.date, datetime.datetime)):
|
|
345
|
+
return value.isoformat()
|
|
346
|
+
return value
|
|
347
|
+
|
|
348
|
+
def _error_payload(self, response: httpx.Response) -> Any:
|
|
349
|
+
# An error body is whatever the server actually sent, which need not be
|
|
350
|
+
# the JSON the spec declares (a proxy's HTML page, say).
|
|
351
|
+
try:
|
|
352
|
+
return response.json()
|
|
353
|
+
except ValueError:
|
|
354
|
+
return response.text
|
|
355
|
+
|
|
356
|
+
def _error(self, wrapper: type[_ErrorT], status: int, payload: Any) -> _ErrorT:
|
|
357
|
+
# Coerce to the declared error type, falling back to the raw payload so
|
|
358
|
+
# a body that doesn't match the spec still raises the typed exception.
|
|
359
|
+
try:
|
|
360
|
+
return wrapper.model_validate({"status": status, "error": payload})
|
|
361
|
+
except ValidationError:
|
|
362
|
+
return wrapper.model_construct(status=status, error=payload)
|
|
363
|
+
|
|
364
|
+
def dog(
|
|
365
|
+
self,
|
|
366
|
+
*,
|
|
367
|
+
name: str,
|
|
368
|
+
breed: str,
|
|
369
|
+
) -> types.Dog:
|
|
370
|
+
header_params: dict[str, Any] = {}
|
|
371
|
+
_body_fields: dict[str, Any] = {}
|
|
372
|
+
_body_fields["name"] = name
|
|
373
|
+
_body_fields["breed"] = breed
|
|
374
|
+
body = types.Dog.model_validate(_body_fields).model_dump(
|
|
375
|
+
mode="json", by_alias=True, exclude_unset=True
|
|
376
|
+
)
|
|
377
|
+
request_kwargs: dict[str, Any] = {"json": body}
|
|
378
|
+
if not self._config.omit_content_type_header:
|
|
379
|
+
self._default_content_type(header_params, "application/json")
|
|
380
|
+
_request_url = self._url("/dog", {})
|
|
381
|
+
response = self._client.request(
|
|
382
|
+
"POST",
|
|
383
|
+
_request_url,
|
|
384
|
+
headers=self._headers(header_params),
|
|
385
|
+
**request_kwargs,
|
|
386
|
+
)
|
|
387
|
+
if response.status_code == 200:
|
|
388
|
+
return types.Dog.model_validate(response.json())
|
|
389
|
+
raise DogApiError(response.status_code, None)
|
|
390
|
+
"
|
|
391
|
+
`;
|
|
392
|
+
|
|
393
|
+
exports[`openApiPyClientGenerator - composite types > should flatten allOf inheritance into a single pydantic class > types.py 1`] = `
|
|
394
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
395
|
+
|
|
396
|
+
from __future__ import annotations
|
|
397
|
+
|
|
398
|
+
from typing import Never, TypedDict
|
|
399
|
+
|
|
400
|
+
from pydantic import BaseModel, ConfigDict
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class Animal(BaseModel):
|
|
404
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
405
|
+
|
|
406
|
+
name: str
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class Dog(BaseModel):
|
|
410
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
411
|
+
|
|
412
|
+
name: str
|
|
413
|
+
breed: str
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class DogRequest(TypedDict):
|
|
417
|
+
body: Dog
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
DogError = Never
|
|
421
|
+
"
|
|
422
|
+
`;
|
|
423
|
+
|
|
424
|
+
exports[`openApiPyClientGenerator - composite types > should handle recursive schema references > types.py 1`] = `
|
|
425
|
+
""""Types for TestApi — AUTO-GENERATED, do not edit."""
|
|
426
|
+
|
|
427
|
+
from __future__ import annotations
|
|
428
|
+
|
|
429
|
+
from typing import Never, TypedDict
|
|
430
|
+
|
|
431
|
+
from pydantic import BaseModel, ConfigDict
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class Tree(BaseModel):
|
|
435
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
436
|
+
|
|
437
|
+
value: int
|
|
438
|
+
children: list[Tree] | None = None
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class TreeRequest(TypedDict):
|
|
442
|
+
body: Tree
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
TreeError = Never
|
|
446
|
+
"
|
|
447
|
+
`;
|