-
-
Notifications
You must be signed in to change notification settings - Fork 227
/
Copy pathtest_noisy_refs.py
89 lines (75 loc) · 2.92 KB
/
test_noisy_refs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# If a field may be reference (`Union[Reference, OtherType]`) and the dictionary
# being processed for it contains "$ref", it seems like it should preferentially
# be parsed as a `Reference`[1]. Since the models are defined with
# `extra="allow"`, Pydantic won't guarantee this parse if the dictionary is in
# an unspecified sense a "better match" for `OtherType`[2], e.g., perhaps if it
# has several more fields matching that type versus the single match for `$ref`.
#
# We can use a discriminated union to force parsing these dictionaries as
# `Reference`s.
#
# References:
# [1] https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
# [2] https://docs.pydantic.dev/latest/concepts/unions/#smart-mode
from typing import Annotated, TypeVar, Union, get_args, get_origin
import pytest
from pydantic import TypeAdapter
from openapi_python_client.schema.openapi_schema_pydantic import (
Callback,
Example,
Header,
Link,
Parameter,
PathItem,
Reference,
RequestBody,
Response,
Schema,
SecurityScheme,
)
try:
from openapi_python_client.schema.openapi_schema_pydantic.reference import ReferenceOr
except ImportError:
T = TypeVar("T")
ReferenceOr = Union[Reference, T]
def get_example(base_type):
schema = base_type.model_json_schema()
if "examples" in schema:
return schema["examples"][0]
if "$defs" in schema:
return schema["$defs"][base_type.__name__]["examples"][0]
raise TypeError(f"No example found for {base_type.__name__}")
def deannotate_type(t):
while get_origin(t) is Annotated:
t = get_args(t)[0]
return t
# The following types occur in various models, so we want to make sure they
# parse properly. They are verified to /fail/ to parse as of commit 3bd12f86.
@pytest.mark.parametrize(
("ref_or_type", "get_example_fn"),
[
(ReferenceOr[Callback], lambda t: {"test1": get_example(PathItem), "test2": get_example(PathItem)}),
(ReferenceOr[Example], get_example),
(ReferenceOr[Header], get_example),
(ReferenceOr[Link], get_example),
(ReferenceOr[Parameter], get_example),
(ReferenceOr[RequestBody], get_example),
(ReferenceOr[Response], get_example),
(ReferenceOr[Schema], get_example),
(ReferenceOr[SecurityScheme], get_example),
],
)
def test_type(ref_or_type, get_example_fn):
base_type = None
for maybe_annotated_type in get_args(deannotate_type(ref_or_type)):
each_type = deannotate_type(maybe_annotated_type)
if each_type is not Reference:
base_type = each_type
break
assert base_type is not None
example = get_example_fn(base_type)
parsed = TypeAdapter(ref_or_type).validate_python(example)
assert type(parsed) is get_origin(base_type) or base_type
example["$ref"] = "ref"
parsed = TypeAdapter(ref_or_type).validate_python(example)
assert type(parsed) is Reference