Skip to content

Commit 6bb4a2a

Browse files
committed
Use pydantic in openapi spec
1 parent d122d98 commit 6bb4a2a

9 files changed

Lines changed: 414 additions & 192 deletions

File tree

lower_bounds_constraints.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ schema==0.7.5
77
tomli==2.0.0
88
tomli-w==1.0.0
99
pygments==2.17.2
10-
pydantic==2.11.7
10+
pydantic==2.8.1
1111
click-shell==2.1
1212
SecretStorage==3.3.3

pulp-glue/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
dependencies = [
2525
"multidict>=6.0.5,<6.8",
2626
"packaging>=22.0,<=26.0", # CalVer
27-
"pydantic>=2.11.7,<2.13",
27+
"pydantic>=2.8.1,<2.13",
2828
"requests>=2.24.0,<2.33",
2929
"tomli>=2.0.0,<2.1;python_version<'3.11'",
3030
]

pulp-glue/src/pulp_glue/common/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,9 @@ class ValidationError(OpenAPIError):
5959
"""Exception raised for failed client side validation of parameters or request bodies."""
6060

6161

62+
class SchemaError(OpenAPIError):
63+
"""Exception raised for unsurmountable inconsistencies of the openapi schema."""
64+
65+
6266
class UnsafeCallError(OpenAPIError):
6367
"""Exception raised for POST, PUT, PATCH or DELETE calls with `safe_calls_only=True`."""

pulp-glue/src/pulp_glue/common/openapi.py

Lines changed: 119 additions & 102 deletions
Large diffs are not rendered by default.

pulp-glue/src/pulp_glue/common/pydantic_oas.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class OASBase(pydantic.BaseModel, alias_generator=to_alias, extra="forbid"):
1515

1616
class ExtensibleOASBase(OASBase, extra="allow"):
1717
@pydantic.model_validator(mode="after")
18-
def _check_extensions(self) -> t.Self:
18+
def _check_extensions(self) -> "t.Self":
1919
if self.__pydantic_extra__ is not None:
2020
invalid_keys = [
2121
key for key in self.__pydantic_extra__.keys() if not key.startswith("x-")
@@ -30,8 +30,20 @@ def _check_extensions(self) -> t.Self:
3030
return self
3131

3232

33+
OperationName = t.Literal[
34+
"get",
35+
"put",
36+
"post",
37+
"patch",
38+
"delete",
39+
"options",
40+
"head",
41+
"trace",
42+
]
43+
44+
3345
class Reference(OASBase):
34-
_ref: t.Annotated[str, pydantic.Field(alias="$ref")]
46+
ref: t.Annotated[str, pydantic.Field(alias="$ref")]
3547
summary: str | None = None
3648
description: str | None = None
3749

@@ -192,7 +204,13 @@ class Encoding(ExtensibleOASBase):
192204
# TODO These only apply to RFC6570
193205
style: (
194206
t.Literal[
195-
"simple", "form", "matrix", "label", "spaceDelimited", "pipeDelimited", "deepObject"
207+
"simple",
208+
"form",
209+
"matrix",
210+
"label",
211+
"spaceDelimited",
212+
"pipeDelimited",
213+
"deepObject",
196214
]
197215
| None
198216
) = None
@@ -237,7 +255,7 @@ class ParameterBase(ExtensibleOASBase):
237255
allow_empty_value: bool = False
238256

239257
@pydantic.model_validator(mode="after")
240-
def _path_required(self) -> t.Self:
258+
def _path_required(self) -> "t.Self":
241259
if self.in_ == "path":
242260
assert self.required
243261
return self
@@ -251,10 +269,18 @@ class SchemaParameter(ParameterBase):
251269
schema_: Schema
252270
style: t.Annotated[
253271
t.Literal[
254-
"simple", "form", "matrix", "label", "spaceDelimited", "pipeDelimited", "deepObject"
272+
"simple",
273+
"form",
274+
"matrix",
275+
"label",
276+
"spaceDelimited",
277+
"pipeDelimited",
278+
"deepObject",
255279
],
256280
pydantic.Field(
257-
default_factory=lambda data: "simple" if data["in_"] in ["header", "path"] else "form"
281+
default_factory=lambda data: (
282+
"simple" if data.get("in_") in ["header", "path"] else "form"
283+
)
258284
),
259285
]
260286
explode: t.Annotated[bool, pydantic.Field(default_factory=lambda data: data["style"] == "form")]
@@ -275,7 +301,7 @@ class RequestBody(ExtensibleOASBase):
275301
class Response(ExtensibleOASBase):
276302
description: str
277303
headers: dict[str, Header | Reference] | None = None
278-
content: dict[str, MediaType] | None = None
304+
content: dict[str, MediaType] = {}
279305
links: dict[str, Link | Reference] | None = None
280306

281307

@@ -291,16 +317,17 @@ class Operation(ExtensibleOASBase):
291317
description: str | None = None
292318
external_docs: ExternalDocumentation | None = None
293319
operation_id: str
294-
parameters: list[Parameter | Reference] | None = None
320+
parameters: list[Parameter | Reference] = []
295321
request_body: RequestBody | Reference | None = None
296-
responses: Responses | None = None
322+
responses: Responses = {}
297323
callbacks: dict[str, Callback | Reference] | None = None
298324
deprecated: bool = False
299325
security: SecurityRequirements | None = None
300326
servers: list[Server] | None = None
301327

302328

303-
class PathItem(Reference, ExtensibleOASBase):
329+
class PathItem(ExtensibleOASBase):
330+
# TODO $ref
304331
get: Operation | None = None
305332
put: Operation | None = None
306333
post: Operation | None = None
@@ -310,17 +337,17 @@ class PathItem(Reference, ExtensibleOASBase):
310337
head: Operation | None = None
311338
trace: Operation | None = None
312339
servers: list[Server] | None = None
313-
parameters: list[Parameter | Reference] | None = None
340+
parameters: list[Parameter | Reference] = []
314341

315342

316343
class Components(ExtensibleOASBase):
317-
schemas: dict[str, Schema] | None = None
344+
schemas: dict[str, Schema] | None = {}
318345
responses: dict[str, Response | Reference] | None = None
319-
parameters: dict[str, Parameter | Reference] | None = None
346+
parameters: dict[str, Parameter | Reference] = {}
320347
examples: dict[str, Example | Reference] | None = None
321348
request_bodies: dict[str, RequestBody | Reference] | None = None
322349
headers: dict[str, Header | Reference] | None = None
323-
security_schemes: dict[str, SecurityScheme | Reference] | None = None
350+
security_schemes: dict[str, SecurityScheme | Reference] = {}
324351
links: dict[str, Link | Reference] | None = None
325352
callbacks: dict[str, Callback | Reference] | None = None
326353
path_items: dict[str, PathItem] | None = None
@@ -333,9 +360,9 @@ class OpenAPISpec(ExtensibleOASBase):
333360
servers: list[Server] | None = None
334361
# In the specification there is a Paths Object,
335362
# probably because paths as keys can get extra validation.
336-
paths: dict[str, PathItem] | None = None
363+
paths: dict[str, PathItem] = {}
337364
webhooks: dict[str, PathItem] | None = None
338-
components: Components | None = None
365+
components: Components = Components()
339366
security: SecurityRequirements | None = None
340367
tags: list[Tag] | None = None
341368
external_docs: ExternalDocumentation | None = None

pulp-glue/src/pulp_glue/common/schema.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import typing as t
55
from contextlib import suppress
66

7+
from pulp_glue.common.exceptions import SchemaError, ValidationError
78
from pulp_glue.common.i18n import get_translation
89

910
translation = get_translation(__package__)
@@ -13,14 +14,6 @@
1314
ISO_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
1415

1516

16-
class SchemaError(ValueError):
17-
pass
18-
19-
20-
class ValidationError(ValueError):
21-
pass
22-
23-
2417
class OpenApi3JsonEncoder(json.JSONEncoder):
2518
def default(self, o: t.Any) -> t.Any:
2619
if isinstance(o, datetime.datetime):

0 commit comments

Comments
 (0)