Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/add remote reference support rebased #2

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e983f04
parser / properties / resolve local $ref
Nementon Feb 7, 2021
49c1ed1
parser/ propertoes / add `LazyReferencePropertyProxy`: lazy resolve r…
Nementon Feb 19, 2021
b27a003
test / parser / properties / correct references arranged data, use lo…
Nementon Feb 20, 2021
32698b0
test / parser / properties / add `LazyReferencePropertyProxy` tests
Nementon Feb 20, 2021
f7940b6
parser / properties / tiny code clean up
Nementon Feb 20, 2021
7b373d2
parser / properties / use lazy reference only for reference to themself
Nementon Feb 21, 2021
ebb8e62
test / parser / properties / correct `LazyReferencePropertyProxy` tests
Nementon Feb 21, 2021
0f445ac
parser / propertie / add support for indirect reference to itself res…
Nementon Feb 21, 2021
1970fd6
parser / properties / rename `_reference_name` -> `_reference_pointer…
Nementon Feb 21, 2021
ce6bb1e
test / parser / properties / correct API breaking changes
Nementon Feb 21, 2021
d3d7bbe
parser / properties / tests cleanup + behaviour fixes
Nementon Feb 21, 2021
1de3190
parser / properties / LazyReferencePropertyProxy: quote resolved type…
Nementon Feb 21, 2021
c7a1371
parser / properties / LazyReferencePropertyProxy: enforce nullable to…
Nementon Feb 21, 2021
8334c03
golden records / regen / add indirect, self, indirect-self model refe…
Nementon Feb 21, 2021
d0a2e6e
update CHANGELOG
Nementon Feb 21, 2021
1e996d2
bootstrap schemas resolver
Nementon Feb 5, 2021
b183460
__init__ / _get_document: use SchemaResolver
Nementon Feb 5, 2021
cedb946
resolver / wip resolve remote ref to local ones
Nementon Feb 6, 2021
e38a6ad
correct tests breaking changes
Nementon Feb 10, 2021
b60c382
resolver / refactor (squash me)
Nementon Feb 11, 2021
229f75f
resolver / add reference tests
Nementon Feb 11, 2021
325a8a7
resolver / add data_loader tests
Nementon Feb 11, 2021
582115c
resolver / add schema_resolver tests (wip)
Nementon Feb 11, 2021
91eb471
resovler / add pointer tests
Nementon Feb 12, 2021
08beec5
resolver / refactor (squash me)
Nementon Feb 14, 2021
97a2e56
resolver / add schema_resolver tests (squash me)
Nementon Feb 14, 2021
ca2dc41
fix absolute paths feature bugs
p1-alexandrevaney Mar 8, 2021
250fa6a
add collision_resolver
p1-alexandrevaney Mar 12, 2021
bb61e8a
fix collision issues with same schema in two different filesé
p1-alexandrevaney Mar 15, 2021
5fafa0a
do not crash on retry of key modification
p1-alexandrevaney Mar 15, 2021
8383bb3
find collision of 2 same object at different place
p1-alexandrevaney Mar 15, 2021
692bd1a
use tokens instead of splitting paths
p1-alexandrevaney Mar 15, 2021
aa04afe
remove ref key when replacing it
p1-alexandrevaney Mar 16, 2021
6b584b6
fix build_schema bug when model points to loaded ref
p1-alexandrevaney Mar 18, 2021
0ce86cd
add json loading to data_loader
p1-alexandrevaney Mar 18, 2021
2b3c125
dont use resolved schema in collision resolver
p1-alexandrevaney Mar 18, 2021
aae98e7
improve collision resolver and resolved schema tests
p1-alexandrevaney Mar 19, 2021
a624a88
fix data_load json test
p1-alexandrevaney Mar 19, 2021
59fb49b
add new tests for coverage purposes
p1-alexandrevaney Mar 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Additions

- Add support of properties indirect reference ($ref) resolution; Add support of inner properties direct and indirect reference resolution to its owner model/enum (#329). Thanks @p1-ra!
- New `--meta` command line option for specifying what type of metadata should be generated:
- `poetry` is the default value, same behavior you're used to in previous versions
- `setup` will generate a pyproject.toml with no Poetry information, and instead create a `setup.py` with the
Expand Down
39 changes: 39 additions & 0 deletions end_to_end_tests/golden-record-custom/custom_e2e/models/a_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class AModel:
a_nullable_date: Optional[datetime.date]
required_nullable: Optional[str]
nullable_model: Optional[AModelNullableModel]
an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET
direct_ref_to_itself: Union["AModel", Unset] = UNSET
indirect_ref_to_itself: Union["AModel", Unset] = UNSET
nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
a_not_required_date: Union[Unset, datetime.date] = UNSET
attr_1_leading_digit: Union[Unset, str] = UNSET
Expand All @@ -48,6 +51,18 @@ def to_dict(self) -> Dict[str, Any]:
required_not_nullable = self.required_not_nullable
model = self.model.to_dict()

an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET
if not isinstance(self.an_enum_indirect_ref, Unset):
an_enum_indirect_ref = self.an_enum_indirect_ref

direct_ref_to_itself: Union[Unset, Dict[str, Any]] = UNSET
if not isinstance(self.direct_ref_to_itself, Unset):
direct_ref_to_itself = self.direct_ref_to_itself.to_dict()

indirect_ref_to_itself: Union[Unset, Dict[str, Any]] = UNSET
if not isinstance(self.indirect_ref_to_itself, Unset):
indirect_ref_to_itself = self.indirect_ref_to_itself.to_dict()

nested_list_of_enums: Union[Unset, List[Any]] = UNSET
if not isinstance(self.nested_list_of_enums, Unset):
nested_list_of_enums = []
Expand Down Expand Up @@ -94,6 +109,12 @@ def to_dict(self) -> Dict[str, Any]:
"nullable_model": nullable_model,
}
)
if an_enum_indirect_ref is not UNSET:
field_dict["an_enum_indirect_ref"] = an_enum_indirect_ref
if direct_ref_to_itself is not UNSET:
field_dict["direct_ref_to_itself"] = direct_ref_to_itself
if indirect_ref_to_itself is not UNSET:
field_dict["indirect_ref_to_itself"] = indirect_ref_to_itself
if nested_list_of_enums is not UNSET:
field_dict["nested_list_of_enums"] = nested_list_of_enums
if a_not_required_date is not UNSET:
Expand Down Expand Up @@ -137,6 +158,21 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat

model = AModelModel.from_dict(d.pop("model"))

an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET
_an_enum_indirect_ref = d.pop("an_enum_indirect_ref", UNSET)
if not isinstance(_an_enum_indirect_ref, Unset):
an_enum_indirect_ref = AnEnum(_an_enum_indirect_ref)

direct_ref_to_itself: Union[AModel, Unset] = UNSET
_direct_ref_to_itself = d.pop("direct_ref_to_itself", UNSET)
if not isinstance(_direct_ref_to_itself, Unset):
direct_ref_to_itself = AModel.from_dict(_direct_ref_to_itself)

indirect_ref_to_itself: Union[AModel, Unset] = UNSET
_indirect_ref_to_itself = d.pop("indirect_ref_to_itself", UNSET)
if not isinstance(_indirect_ref_to_itself, Unset):
indirect_ref_to_itself = AModel.from_dict(_indirect_ref_to_itself)

nested_list_of_enums = []
_nested_list_of_enums = d.pop("nested_list_of_enums", UNSET)
for nested_list_of_enums_item_data in _nested_list_of_enums or []:
Expand Down Expand Up @@ -188,6 +224,9 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
a_date=a_date,
required_not_nullable=required_not_nullable,
model=model,
an_enum_indirect_ref=an_enum_indirect_ref,
direct_ref_to_itself=direct_ref_to_itself,
indirect_ref_to_itself=indirect_ref_to_itself,
nested_list_of_enums=nested_list_of_enums,
a_nullable_date=a_nullable_date,
a_not_required_date=a_not_required_date,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class AModel:
a_nullable_date: Optional[datetime.date]
required_nullable: Optional[str]
nullable_model: Optional[AModelNullableModel]
an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET
direct_ref_to_itself: Union["AModel", Unset] = UNSET
indirect_ref_to_itself: Union["AModel", Unset] = UNSET
nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
a_not_required_date: Union[Unset, datetime.date] = UNSET
attr_1_leading_digit: Union[Unset, str] = UNSET
Expand All @@ -48,6 +51,18 @@ def to_dict(self) -> Dict[str, Any]:
required_not_nullable = self.required_not_nullable
model = self.model.to_dict()

an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET
if not isinstance(self.an_enum_indirect_ref, Unset):
an_enum_indirect_ref = self.an_enum_indirect_ref

direct_ref_to_itself: Union[Unset, Dict[str, Any]] = UNSET
if not isinstance(self.direct_ref_to_itself, Unset):
direct_ref_to_itself = self.direct_ref_to_itself.to_dict()

indirect_ref_to_itself: Union[Unset, Dict[str, Any]] = UNSET
if not isinstance(self.indirect_ref_to_itself, Unset):
indirect_ref_to_itself = self.indirect_ref_to_itself.to_dict()

nested_list_of_enums: Union[Unset, List[Any]] = UNSET
if not isinstance(self.nested_list_of_enums, Unset):
nested_list_of_enums = []
Expand Down Expand Up @@ -94,6 +109,12 @@ def to_dict(self) -> Dict[str, Any]:
"nullable_model": nullable_model,
}
)
if an_enum_indirect_ref is not UNSET:
field_dict["an_enum_indirect_ref"] = an_enum_indirect_ref
if direct_ref_to_itself is not UNSET:
field_dict["direct_ref_to_itself"] = direct_ref_to_itself
if indirect_ref_to_itself is not UNSET:
field_dict["indirect_ref_to_itself"] = indirect_ref_to_itself
if nested_list_of_enums is not UNSET:
field_dict["nested_list_of_enums"] = nested_list_of_enums
if a_not_required_date is not UNSET:
Expand Down Expand Up @@ -137,6 +158,21 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat

model = AModelModel.from_dict(d.pop("model"))

an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET
_an_enum_indirect_ref = d.pop("an_enum_indirect_ref", UNSET)
if not isinstance(_an_enum_indirect_ref, Unset):
an_enum_indirect_ref = AnEnum(_an_enum_indirect_ref)

direct_ref_to_itself: Union[AModel, Unset] = UNSET
_direct_ref_to_itself = d.pop("direct_ref_to_itself", UNSET)
if not isinstance(_direct_ref_to_itself, Unset):
direct_ref_to_itself = AModel.from_dict(_direct_ref_to_itself)

indirect_ref_to_itself: Union[AModel, Unset] = UNSET
_indirect_ref_to_itself = d.pop("indirect_ref_to_itself", UNSET)
if not isinstance(_indirect_ref_to_itself, Unset):
indirect_ref_to_itself = AModel.from_dict(_indirect_ref_to_itself)

nested_list_of_enums = []
_nested_list_of_enums = d.pop("nested_list_of_enums", UNSET)
for nested_list_of_enums_item_data in _nested_list_of_enums or []:
Expand Down Expand Up @@ -188,6 +224,9 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
a_date=a_date,
required_not_nullable=required_not_nullable,
model=model,
an_enum_indirect_ref=an_enum_indirect_ref,
direct_ref_to_itself=direct_ref_to_itself,
indirect_ref_to_itself=indirect_ref_to_itself,
nested_list_of_enums=nested_list_of_enums,
a_nullable_date=a_nullable_date,
a_not_required_date=a_not_required_date,
Expand Down
23 changes: 22 additions & 1 deletion end_to_end_tests/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,15 @@
"required": ["an_enum_value", "aCamelDateTime", "a_date", "a_nullable_date", "required_nullable", "required_not_nullable", "model", "nullable_model"],
"type": "object",
"properties": {
"an_enum_indirect_ref": {
"$ref": "#/components/schemas/AnEnumDeeperIndirectReference"
},
"direct_ref_to_itself": {
"$ref": "#/components/schemas/AModel"
},
"indirect_ref_to_itself": {
"$ref": "#/components/schemas/AModelDeeperIndirectReference"
},
"an_enum_value": {
"$ref": "#/components/schemas/AnEnum"
},
Expand Down Expand Up @@ -716,7 +725,7 @@
"a_not_required_date": {
"title": "A Nullable Date",
"type": "string",
"format": "date",
"format": "date"
},
"1_leading_digit": {
"title": "Leading Digit",
Expand Down Expand Up @@ -782,11 +791,23 @@
"description": "A Model for testing all the ways custom objects can be used ",
"additionalProperties": false
},
"AModelIndirectReference": {
"$ref": "#/components/schemas/AModel"
},
"AModelDeeperIndirectReference": {
"$ref": "#/components/schemas/AModelIndirectReference"
},
"AnEnumIndirectReference": {
"$ref": "#/components/schemas/AnEnum"
},
"AnEnum": {
"title": "AnEnum",
"enum": ["FIRST_VALUE", "SECOND_VALUE"],
"description": "For testing Enums in all the ways they can be used "
},
"AnEnumDeeperIndirectReference": {
"$ref": "#/components/schemas/AnEnumIndirectReference"
},
"AnIntEnum": {
"title": "AnIntEnum",
"enum": [-1, 1, 2],
Expand Down
30 changes: 16 additions & 14 deletions openapi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
import shutil
import subprocess
import sys
import urllib
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Optional, Sequence, Union
from typing import Any, Dict, Optional, Sequence, Union, cast

import httpcore
import httpx
import yaml
from jinja2 import BaseLoader, ChoiceLoader, Environment, FileSystemLoader, PackageLoader

from openapi_python_client import utils

from .parser import GeneratorData, import_string_from_reference
from .parser.errors import GeneratorError
from .resolver.schema_resolver import SchemaResolver
from .utils import snake_case

if sys.version_info.minor < 8: # version did not exist before 3.8, need to use a backport
Expand Down Expand Up @@ -318,20 +319,21 @@ def update_existing_client(


def _get_document(*, url: Optional[str], path: Optional[Path]) -> Union[Dict[str, Any], GeneratorError]:
yaml_bytes: bytes
if url is not None and path is not None:
return GeneratorError(header="Provide URL or Path, not both.")
if url is not None:
try:
response = httpx.get(url)
yaml_bytes = response.content
except (httpx.HTTPError, httpcore.NetworkError):
return GeneratorError(header="Could not get OpenAPI document from provided URL")
elif path is not None:
yaml_bytes = path.read_bytes()
else:

if url is None and path is None:
return GeneratorError(header="No URL or Path provided")

source = cast(Union[str, Path], (url if url is not None else path))
try:
return yaml.safe_load(yaml_bytes)
except yaml.YAMLError:
resolver = SchemaResolver(source)
result = resolver.resolve()
if len(result.errors) > 0:
return GeneratorError(header="; ".join(result.errors))
except (httpx.HTTPError, httpcore.NetworkError, urllib.error.URLError):
return GeneratorError(header="Could not get OpenAPI document from provided URL")
except Exception:
return GeneratorError(header="Invalid YAML from provided source")

return result.schema
Loading