openapi-python-client 0.5.2__tar.gz → 0.5.3__tar.gz
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.
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/CHANGELOG.md +16 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/PKG-INFO +2 -2
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/__init__.py +6 -3
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/errors.py +5 -1
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/openapi.py +6 -1
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/properties.py +110 -35
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/async_endpoint_module.pyi +9 -3
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/endpoint_macros.pyi +13 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/endpoint_module.pyi +9 -3
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/date_property.pyi +2 -2
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/datetime_property.pyi +2 -2
- openapi-python-client-0.5.3/openapi_python_client/templates/property_templates/dict_property.pyi +17 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/union_property.pyi +3 -3
- openapi-python-client-0.5.3/openapi_python_client/utils.py +36 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/pyproject.toml +7 -2
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/setup.py +2 -2
- openapi-python-client-0.5.2/openapi_python_client/utils.py +0 -13
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/LICENSE +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/README.md +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/__main__.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/cli.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/config.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/__init__.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/reference.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/responses.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/py.typed +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/LICENSE +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/README.md +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/__init__.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/components.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/contact.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/discriminator.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/encoding.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/example.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/external_documentation.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/header.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/info.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/license.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/link.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/media_type.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/oauth_flow.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/oauth_flows.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/open_api.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/operation.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/parameter.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/path_item.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/paths.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/reference.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/request_body.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/response.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/responses.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/schema.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/security_requirement.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/security_scheme.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/server.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/server_variable.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/tag.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/xml.py +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/.gitignore +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/README.md +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/client.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/enum.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/errors.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/model.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/models_init.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/package_init.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/enum_property.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/file_property.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/list_property.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/property_templates/ref_property.pyi +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/pyproject.toml +0 -0
- {openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/types.py +0 -0
|
@@ -5,6 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
## 0.5.3 - 2020-08-13
|
|
9
|
+
### Security
|
|
10
|
+
- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)
|
|
11
|
+
- All values that get placed into python files (everything from enum names, to endpoint descriptions, to default values) are validated and/or saniziatied to address arbitrary code execution vulnerabilities (CVE-2020-15142)
|
|
12
|
+
|
|
13
|
+
### Changes
|
|
14
|
+
- Due to security concerns/implementation complexities, default values are temporarily unsupported for any `RefProperty` that doesn't refer to an enum.
|
|
15
|
+
- Defaults for properties must now be valid values for their respective type (e.g. "example string" is an invalid default for an `integer` type property, and the function for an endpoint using it would fail to generate and be skipped).
|
|
16
|
+
|
|
17
|
+
### Additions
|
|
18
|
+
- Added support for header parameters (#117)
|
|
19
|
+
|
|
20
|
+
### Fixes
|
|
21
|
+
- JSON bodies will now be assigned correctly in generated clients(#139 & #147). Thanks @pawamoy!
|
|
22
|
+
|
|
23
|
+
|
|
8
24
|
## 0.5.2 - 2020-08-06
|
|
9
25
|
### Additions
|
|
10
26
|
- Added `project_name_override` and `package_name_override` config options to override the name of the generated project/package (#123)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: openapi-python-client
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Generate modern Python clients from OpenAPI
|
|
5
5
|
Home-page: https://github.com/triaxtec/openapi-python-client
|
|
6
6
|
License: MIT
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Software Development :: Code Generators
|
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Dist: black (>=19.10b0,<20.0)
|
|
20
20
|
Requires-Dist: colorama (>=0.4.3,<0.5.0); sys_platform == "win32"
|
|
21
|
-
Requires-Dist: httpx (>=0.13
|
|
21
|
+
Requires-Dist: httpx (>=0.13,<0.15)
|
|
22
22
|
Requires-Dist: importlib_metadata (>=1.6.0,<2.0.0); python_version == "3.7"
|
|
23
23
|
Requires-Dist: isort (>=5.0.5,<6.0.0)
|
|
24
24
|
Requires-Dist: jinja2 (>=2.11.1,<3.0.0)
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/__init__.py
RENAMED
|
@@ -83,7 +83,7 @@ def _get_document(*, url: Optional[str], path: Optional[Path]) -> Union[Dict[str
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
class Project:
|
|
86
|
-
TEMPLATE_FILTERS = {"snakecase": utils.snake_case}
|
|
86
|
+
TEMPLATE_FILTERS = {"snakecase": utils.snake_case, "kebabcase": utils.kebab_case}
|
|
87
87
|
project_name_override: Optional[str] = None
|
|
88
88
|
package_name_override: Optional[str] = None
|
|
89
89
|
|
|
@@ -91,12 +91,14 @@ class Project:
|
|
|
91
91
|
self.openapi: GeneratorData = openapi
|
|
92
92
|
self.env: Environment = Environment(loader=PackageLoader(__package__), trim_blocks=True, lstrip_blocks=True)
|
|
93
93
|
|
|
94
|
-
self.project_name: str = self.project_name_override or f"{openapi.title
|
|
94
|
+
self.project_name: str = self.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
|
|
95
95
|
self.project_dir: Path = Path.cwd() / self.project_name
|
|
96
96
|
|
|
97
97
|
self.package_name: str = self.package_name_override or self.project_name.replace("-", "_")
|
|
98
98
|
self.package_dir: Path = self.project_dir / self.package_name
|
|
99
|
-
self.package_description: str =
|
|
99
|
+
self.package_description: str = utils.remove_string_escapes(
|
|
100
|
+
f"A client library for accessing {self.openapi.title}"
|
|
101
|
+
)
|
|
100
102
|
self.version: str = openapi.version
|
|
101
103
|
|
|
102
104
|
self.env.filters.update(self.TEMPLATE_FILTERS)
|
|
@@ -231,6 +233,7 @@ class Project:
|
|
|
231
233
|
endpoint_template = self.env.get_template("endpoint_module.pyi")
|
|
232
234
|
async_endpoint_template = self.env.get_template("async_endpoint_module.pyi")
|
|
233
235
|
for tag, collection in self.openapi.endpoint_collections_by_tag.items():
|
|
236
|
+
tag = utils.snake_case(tag)
|
|
234
237
|
module_path = api_dir / f"{tag}.py"
|
|
235
238
|
module_path.write_text(endpoint_template.render(collection=collection))
|
|
236
239
|
async_module_path = async_api_dir / f"{tag}.py"
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/errors.py
RENAMED
|
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
-
__all__ = ["GeneratorError", "ParseError", "PropertyError"]
|
|
5
|
+
__all__ = ["GeneratorError", "ParseError", "PropertyError", "ValidationError"]
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
8
|
|
|
@@ -37,3 +37,7 @@ class PropertyError(ParseError):
|
|
|
37
37
|
""" Error raised when there's a problem creating a Property """
|
|
38
38
|
|
|
39
39
|
header = "Problem creating a Property: "
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ValidationError(Exception):
|
|
43
|
+
pass
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/openapi.py
RENAMED
|
@@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Set, Union
|
|
|
8
8
|
from pydantic import ValidationError
|
|
9
9
|
|
|
10
10
|
from .. import schema as oai
|
|
11
|
+
from .. import utils
|
|
11
12
|
from .errors import GeneratorError, ParseError, PropertyError
|
|
12
13
|
from .properties import EnumProperty, Property, property_from_data
|
|
13
14
|
from .reference import Reference
|
|
@@ -19,6 +20,7 @@ class ParameterLocation(str, Enum):
|
|
|
19
20
|
|
|
20
21
|
QUERY = "query"
|
|
21
22
|
PATH = "path"
|
|
23
|
+
HEADER = "header"
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def import_string_from_reference(reference: Reference, prefix: str = "") -> str:
|
|
@@ -78,6 +80,7 @@ class Endpoint:
|
|
|
78
80
|
relative_imports: Set[str] = field(default_factory=set)
|
|
79
81
|
query_parameters: List[Property] = field(default_factory=list)
|
|
80
82
|
path_parameters: List[Property] = field(default_factory=list)
|
|
83
|
+
header_parameters: List[Property] = field(default_factory=list)
|
|
81
84
|
responses: List[Response] = field(default_factory=list)
|
|
82
85
|
form_body_reference: Optional[Reference] = None
|
|
83
86
|
json_body: Optional[Property] = None
|
|
@@ -164,6 +167,8 @@ class Endpoint:
|
|
|
164
167
|
endpoint.query_parameters.append(prop)
|
|
165
168
|
elif param.param_in == ParameterLocation.PATH:
|
|
166
169
|
endpoint.path_parameters.append(prop)
|
|
170
|
+
elif param.param_in == ParameterLocation.HEADER:
|
|
171
|
+
endpoint.header_parameters.append(prop)
|
|
167
172
|
else:
|
|
168
173
|
return ParseError(data=param, detail="Parameter must be declared in path or query")
|
|
169
174
|
return endpoint
|
|
@@ -178,7 +183,7 @@ class Endpoint:
|
|
|
178
183
|
endpoint = Endpoint(
|
|
179
184
|
path=path,
|
|
180
185
|
method=method,
|
|
181
|
-
description=data.description,
|
|
186
|
+
description=utils.remove_string_escapes(data.description) if data.description else "",
|
|
182
187
|
name=data.operationId,
|
|
183
188
|
requires_security=bool(data.security),
|
|
184
189
|
tag=tag,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import InitVar, dataclass, field
|
|
4
|
+
from datetime import date, datetime
|
|
4
5
|
from typing import Any, ClassVar, Dict, Generic, List, Optional, Set, TypeVar, Union
|
|
5
6
|
|
|
6
7
|
from .. import schema as oai
|
|
7
8
|
from .. import utils
|
|
8
|
-
from .errors import PropertyError
|
|
9
|
+
from .errors import PropertyError, ValidationError
|
|
9
10
|
from .reference import Reference
|
|
10
11
|
|
|
11
12
|
|
|
@@ -19,6 +20,9 @@ class Property:
|
|
|
19
20
|
templates/property_templates and must contain two macros: construct and transform. Construct will be used to
|
|
20
21
|
build this property from JSON data (a response from an API). Transform will be used to convert this property
|
|
21
22
|
to JSON data (when sending a request to the API).
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ValidationError: Raised when the default value fails to be converted to the expected type
|
|
22
26
|
"""
|
|
23
27
|
|
|
24
28
|
name: str
|
|
@@ -32,10 +36,16 @@ class Property:
|
|
|
32
36
|
|
|
33
37
|
def __post_init__(self) -> None:
|
|
34
38
|
self.python_name = utils.snake_case(self.name)
|
|
39
|
+
if self.default is not None:
|
|
40
|
+
self.default = self._validate_default(default=self.default)
|
|
41
|
+
|
|
42
|
+
def _validate_default(self, default: Any) -> Any:
|
|
43
|
+
""" Check that the default value is valid for the property's type + perform any necessary sanitization """
|
|
44
|
+
raise ValidationError
|
|
35
45
|
|
|
36
|
-
def get_type_string(self) -> str:
|
|
46
|
+
def get_type_string(self, no_optional: bool = False) -> str:
|
|
37
47
|
""" Get a string representation of type that should be used when declaring this property """
|
|
38
|
-
if self.required:
|
|
48
|
+
if self.required or no_optional:
|
|
39
49
|
return self._type_string
|
|
40
50
|
return f"Optional[{self._type_string}]"
|
|
41
51
|
|
|
@@ -74,10 +84,8 @@ class StringProperty(Property):
|
|
|
74
84
|
|
|
75
85
|
_type_string: ClassVar[str] = "str"
|
|
76
86
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
if self.default is not None:
|
|
80
|
-
self.default = f'"{self.default}"'
|
|
87
|
+
def _validate_default(self, default: Any) -> str:
|
|
88
|
+
return f'"{utils.remove_string_escapes(default)}"'
|
|
81
89
|
|
|
82
90
|
|
|
83
91
|
@dataclass
|
|
@@ -86,7 +94,7 @@ class DateTimeProperty(Property):
|
|
|
86
94
|
A property of type datetime.datetime
|
|
87
95
|
"""
|
|
88
96
|
|
|
89
|
-
_type_string: ClassVar[str] = "datetime"
|
|
97
|
+
_type_string: ClassVar[str] = "datetime.datetime"
|
|
90
98
|
template: ClassVar[str] = "datetime_property.pyi"
|
|
91
99
|
|
|
92
100
|
def get_imports(self, *, prefix: str) -> Set[str]:
|
|
@@ -97,15 +105,23 @@ class DateTimeProperty(Property):
|
|
|
97
105
|
prefix: A prefix to put before any relative (local) module names.
|
|
98
106
|
"""
|
|
99
107
|
imports = super().get_imports(prefix=prefix)
|
|
100
|
-
imports.update({"
|
|
108
|
+
imports.update({"import datetime", "from typing import cast"})
|
|
101
109
|
return imports
|
|
102
110
|
|
|
111
|
+
def _validate_default(self, default: Any) -> str:
|
|
112
|
+
for format_string in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"):
|
|
113
|
+
try:
|
|
114
|
+
return repr(datetime.strptime(default, format_string))
|
|
115
|
+
except (TypeError, ValueError):
|
|
116
|
+
continue
|
|
117
|
+
raise ValidationError
|
|
118
|
+
|
|
103
119
|
|
|
104
120
|
@dataclass
|
|
105
121
|
class DateProperty(Property):
|
|
106
122
|
""" A property of type datetime.date """
|
|
107
123
|
|
|
108
|
-
_type_string: ClassVar[str] = "date"
|
|
124
|
+
_type_string: ClassVar[str] = "datetime.date"
|
|
109
125
|
template: ClassVar[str] = "date_property.pyi"
|
|
110
126
|
|
|
111
127
|
def get_imports(self, *, prefix: str) -> Set[str]:
|
|
@@ -116,9 +132,15 @@ class DateProperty(Property):
|
|
|
116
132
|
prefix: A prefix to put before any relative (local) module names.
|
|
117
133
|
"""
|
|
118
134
|
imports = super().get_imports(prefix=prefix)
|
|
119
|
-
imports.update({"
|
|
135
|
+
imports.update({"import datetime", "from typing import cast"})
|
|
120
136
|
return imports
|
|
121
137
|
|
|
138
|
+
def _validate_default(self, default: Any) -> str:
|
|
139
|
+
try:
|
|
140
|
+
return repr(date.fromisoformat(default))
|
|
141
|
+
except (TypeError, ValueError) as e:
|
|
142
|
+
raise ValidationError() from e
|
|
143
|
+
|
|
122
144
|
|
|
123
145
|
@dataclass
|
|
124
146
|
class FileProperty(Property):
|
|
@@ -146,6 +168,12 @@ class FloatProperty(Property):
|
|
|
146
168
|
default: Optional[float] = None
|
|
147
169
|
_type_string: ClassVar[str] = "float"
|
|
148
170
|
|
|
171
|
+
def _validate_default(self, default: Any) -> float:
|
|
172
|
+
try:
|
|
173
|
+
return float(default)
|
|
174
|
+
except (TypeError, ValueError) as e:
|
|
175
|
+
raise ValidationError() from e
|
|
176
|
+
|
|
149
177
|
|
|
150
178
|
@dataclass
|
|
151
179
|
class IntProperty(Property):
|
|
@@ -154,6 +182,12 @@ class IntProperty(Property):
|
|
|
154
182
|
default: Optional[int] = None
|
|
155
183
|
_type_string: ClassVar[str] = "int"
|
|
156
184
|
|
|
185
|
+
def _validate_default(self, default: Any) -> int:
|
|
186
|
+
try:
|
|
187
|
+
return int(default)
|
|
188
|
+
except (TypeError, ValueError) as e:
|
|
189
|
+
raise ValidationError() from e
|
|
190
|
+
|
|
157
191
|
|
|
158
192
|
@dataclass
|
|
159
193
|
class BooleanProperty(Property):
|
|
@@ -161,6 +195,10 @@ class BooleanProperty(Property):
|
|
|
161
195
|
|
|
162
196
|
_type_string: ClassVar[str] = "bool"
|
|
163
197
|
|
|
198
|
+
def _validate_default(self, default: Any) -> bool:
|
|
199
|
+
# no try/except needed as anything that comes from the initial load from json/yaml will be boolable
|
|
200
|
+
return bool(default)
|
|
201
|
+
|
|
164
202
|
|
|
165
203
|
InnerProp = TypeVar("InnerProp", bound=Property)
|
|
166
204
|
|
|
@@ -172,14 +210,9 @@ class ListProperty(Property, Generic[InnerProp]):
|
|
|
172
210
|
inner_property: InnerProp
|
|
173
211
|
template: ClassVar[str] = "list_property.pyi"
|
|
174
212
|
|
|
175
|
-
def
|
|
176
|
-
super().__post_init__()
|
|
177
|
-
if self.default is not None:
|
|
178
|
-
self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
|
|
179
|
-
|
|
180
|
-
def get_type_string(self) -> str:
|
|
213
|
+
def get_type_string(self, no_optional: bool = False) -> str:
|
|
181
214
|
""" Get a string representation of type that should be used when declaring this property """
|
|
182
|
-
if self.required:
|
|
215
|
+
if self.required or no_optional:
|
|
183
216
|
return f"List[{self.inner_property.get_type_string()}]"
|
|
184
217
|
return f"Optional[List[{self.inner_property.get_type_string()}]]"
|
|
185
218
|
|
|
@@ -198,6 +231,16 @@ class ListProperty(Property, Generic[InnerProp]):
|
|
|
198
231
|
imports.add("from typing import cast")
|
|
199
232
|
return imports
|
|
200
233
|
|
|
234
|
+
def _validate_default(self, default: Any) -> str:
|
|
235
|
+
if not isinstance(default, list):
|
|
236
|
+
raise ValidationError()
|
|
237
|
+
|
|
238
|
+
default = list(map(self.inner_property._validate_default, default))
|
|
239
|
+
if isinstance(self.inner_property, RefProperty): # Fix enums to use the actual value
|
|
240
|
+
default = str(default).replace("'", "")
|
|
241
|
+
|
|
242
|
+
return f"field(default_factory=lambda: cast({self.get_type_string()}, {default}))"
|
|
243
|
+
|
|
201
244
|
|
|
202
245
|
@dataclass
|
|
203
246
|
class UnionProperty(Property):
|
|
@@ -206,11 +249,11 @@ class UnionProperty(Property):
|
|
|
206
249
|
inner_properties: List[Property]
|
|
207
250
|
template: ClassVar[str] = "union_property.pyi"
|
|
208
251
|
|
|
209
|
-
def get_type_string(self) -> str:
|
|
252
|
+
def get_type_string(self, no_optional: bool = False) -> str:
|
|
210
253
|
""" Get a string representation of type that should be used when declaring this property """
|
|
211
254
|
inner_types = [p.get_type_string() for p in self.inner_properties]
|
|
212
255
|
inner_prop_string = ", ".join(inner_types)
|
|
213
|
-
if self.required:
|
|
256
|
+
if self.required or no_optional:
|
|
214
257
|
return f"Union[{inner_prop_string}]"
|
|
215
258
|
return f"Optional[Union[{inner_prop_string}]]"
|
|
216
259
|
|
|
@@ -227,6 +270,15 @@ class UnionProperty(Property):
|
|
|
227
270
|
imports.add("from typing import Union")
|
|
228
271
|
return imports
|
|
229
272
|
|
|
273
|
+
def _validate_default(self, default: Any) -> Any:
|
|
274
|
+
for property in self.inner_properties:
|
|
275
|
+
try:
|
|
276
|
+
val = property._validate_default(default)
|
|
277
|
+
return val
|
|
278
|
+
except ValidationError:
|
|
279
|
+
continue
|
|
280
|
+
raise ValidationError()
|
|
281
|
+
|
|
230
282
|
|
|
231
283
|
_existing_enums: Dict[str, EnumProperty] = {}
|
|
232
284
|
|
|
@@ -242,7 +294,6 @@ class EnumProperty(Property):
|
|
|
242
294
|
template: ClassVar[str] = "enum_property.pyi"
|
|
243
295
|
|
|
244
296
|
def __post_init__(self, title: str) -> None: # type: ignore
|
|
245
|
-
super().__post_init__()
|
|
246
297
|
reference = Reference.from_ref(title)
|
|
247
298
|
dedup_counter = 0
|
|
248
299
|
while reference.class_name in _existing_enums:
|
|
@@ -253,9 +304,7 @@ class EnumProperty(Property):
|
|
|
253
304
|
reference = Reference.from_ref(f"{reference.class_name}{dedup_counter}")
|
|
254
305
|
|
|
255
306
|
self.reference = reference
|
|
256
|
-
|
|
257
|
-
if self.default is not None:
|
|
258
|
-
self.default = f"{self.reference.class_name}.{inverse_values[self.default]}"
|
|
307
|
+
super().__post_init__()
|
|
259
308
|
_existing_enums[self.reference.class_name] = self
|
|
260
309
|
|
|
261
310
|
@staticmethod
|
|
@@ -268,10 +317,10 @@ class EnumProperty(Property):
|
|
|
268
317
|
""" Get all the EnumProperties that have been registered keyed by class name """
|
|
269
318
|
return _existing_enums.get(name)
|
|
270
319
|
|
|
271
|
-
def get_type_string(self) -> str:
|
|
320
|
+
def get_type_string(self, no_optional: bool = False) -> str:
|
|
272
321
|
""" Get a string representation of type that should be used when declaring this property """
|
|
273
322
|
|
|
274
|
-
if self.required:
|
|
323
|
+
if self.required or no_optional:
|
|
275
324
|
return self.reference.class_name
|
|
276
325
|
return f"Optional[{self.reference.class_name}]"
|
|
277
326
|
|
|
@@ -298,10 +347,18 @@ class EnumProperty(Property):
|
|
|
298
347
|
key = f"VALUE_{i}"
|
|
299
348
|
if key in output:
|
|
300
349
|
raise ValueError(f"Duplicate key {key} in Enum")
|
|
301
|
-
|
|
350
|
+
sanitized_key = utils.fix_keywords(utils.sanitize(key))
|
|
351
|
+
output[sanitized_key] = utils.remove_string_escapes(value)
|
|
302
352
|
|
|
303
353
|
return output
|
|
304
354
|
|
|
355
|
+
def _validate_default(self, default: Any) -> str:
|
|
356
|
+
inverse_values = {v: k for k, v in self.values.items()}
|
|
357
|
+
try:
|
|
358
|
+
return f"{self.reference.class_name}.{inverse_values[default]}"
|
|
359
|
+
except KeyError as e:
|
|
360
|
+
raise ValidationError() from e
|
|
361
|
+
|
|
305
362
|
|
|
306
363
|
@dataclass
|
|
307
364
|
class RefProperty(Property):
|
|
@@ -316,9 +373,9 @@ class RefProperty(Property):
|
|
|
316
373
|
return "enum_property.pyi"
|
|
317
374
|
return "ref_property.pyi"
|
|
318
375
|
|
|
319
|
-
def get_type_string(self) -> str:
|
|
376
|
+
def get_type_string(self, no_optional: bool = False) -> str:
|
|
320
377
|
""" Get a string representation of type that should be used when declaring this property """
|
|
321
|
-
if self.required:
|
|
378
|
+
if self.required or no_optional:
|
|
322
379
|
return self.reference.class_name
|
|
323
380
|
return f"Optional[{self.reference.class_name}]"
|
|
324
381
|
|
|
@@ -339,17 +396,20 @@ class RefProperty(Property):
|
|
|
339
396
|
)
|
|
340
397
|
return imports
|
|
341
398
|
|
|
399
|
+
def _validate_default(self, default: Any) -> Any:
|
|
400
|
+
enum = EnumProperty.get_enum(self.reference.class_name)
|
|
401
|
+
if enum:
|
|
402
|
+
return enum._validate_default(default)
|
|
403
|
+
else:
|
|
404
|
+
raise ValidationError
|
|
405
|
+
|
|
342
406
|
|
|
343
407
|
@dataclass
|
|
344
408
|
class DictProperty(Property):
|
|
345
409
|
""" Property that is a general Dict """
|
|
346
410
|
|
|
347
411
|
_type_string: ClassVar[str] = "Dict[Any, Any]"
|
|
348
|
-
|
|
349
|
-
def __post_init__(self) -> None:
|
|
350
|
-
super().__post_init__()
|
|
351
|
-
if self.default is not None:
|
|
352
|
-
self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
|
|
412
|
+
template: ClassVar[str] = "dict_property.pyi"
|
|
353
413
|
|
|
354
414
|
def get_imports(self, *, prefix: str) -> Set[str]:
|
|
355
415
|
"""
|
|
@@ -365,6 +425,11 @@ class DictProperty(Property):
|
|
|
365
425
|
imports.add("from typing import cast")
|
|
366
426
|
return imports
|
|
367
427
|
|
|
428
|
+
def _validate_default(self, default: Any) -> str:
|
|
429
|
+
if isinstance(default, dict):
|
|
430
|
+
return repr(default)
|
|
431
|
+
raise ValidationError
|
|
432
|
+
|
|
368
433
|
|
|
369
434
|
def _string_based_property(
|
|
370
435
|
name: str, required: bool, data: oai.Schema
|
|
@@ -381,10 +446,11 @@ def _string_based_property(
|
|
|
381
446
|
return StringProperty(name=name, default=data.default, required=required, pattern=data.pattern)
|
|
382
447
|
|
|
383
448
|
|
|
384
|
-
def
|
|
449
|
+
def _property_from_data(
|
|
385
450
|
name: str, required: bool, data: Union[oai.Reference, oai.Schema]
|
|
386
451
|
) -> Union[Property, PropertyError]:
|
|
387
452
|
""" Generate a Property from the OpenAPI dictionary representation of it """
|
|
453
|
+
name = utils.remove_string_escapes(name)
|
|
388
454
|
if isinstance(data, oai.Reference):
|
|
389
455
|
return RefProperty(name=name, required=required, reference=Reference.from_ref(data.ref), default=None)
|
|
390
456
|
if data.enum:
|
|
@@ -423,3 +489,12 @@ def property_from_data(
|
|
|
423
489
|
elif data.type == "object":
|
|
424
490
|
return DictProperty(name=name, required=required, default=data.default)
|
|
425
491
|
return PropertyError(data=data, detail=f"unknown type {data.type}")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def property_from_data(
|
|
495
|
+
name: str, required: bool, data: Union[oai.Reference, oai.Schema]
|
|
496
|
+
) -> Union[Property, PropertyError]:
|
|
497
|
+
try:
|
|
498
|
+
return _property_from_data(name=name, required=required, data=data)
|
|
499
|
+
except ValidationError:
|
|
500
|
+
return PropertyError(detail="Failed to validate default value", data=data)
|
|
@@ -11,7 +11,7 @@ from ..errors import ApiResponseError
|
|
|
11
11
|
{% endfor %}
|
|
12
12
|
{% for endpoint in collection.endpoints %}
|
|
13
13
|
|
|
14
|
-
{% from "endpoint_macros.pyi" import query_params, json_body, return_type %}
|
|
14
|
+
{% from "endpoint_macros.pyi" import header_params, query_params, json_body, return_type %}
|
|
15
15
|
|
|
16
16
|
async def {{ endpoint.name | snakecase }}(
|
|
17
17
|
*,
|
|
@@ -41,6 +41,9 @@ async def {{ endpoint.name | snakecase }}(
|
|
|
41
41
|
{% for parameter in endpoint.query_parameters %}
|
|
42
42
|
{{ parameter.to_string() }},
|
|
43
43
|
{% endfor %}
|
|
44
|
+
{% for parameter in endpoint.header_parameters %}
|
|
45
|
+
{{ parameter.to_string() }},
|
|
46
|
+
{% endfor %}
|
|
44
47
|
{{ return_type(endpoint) }}
|
|
45
48
|
""" {{ endpoint.description }} """
|
|
46
49
|
url = "{}{{ endpoint.path }}".format(
|
|
@@ -50,13 +53,16 @@ async def {{ endpoint.name | snakecase }}(
|
|
|
50
53
|
{% endfor %}
|
|
51
54
|
)
|
|
52
55
|
|
|
56
|
+
headers: Dict[str, Any] = client.get_headers()
|
|
57
|
+
{{ header_params(endpoint) | indent(4) }}
|
|
58
|
+
|
|
53
59
|
{{ query_params(endpoint) | indent(4) }}
|
|
54
60
|
{{ json_body(endpoint) | indent(4) }}
|
|
55
61
|
|
|
56
62
|
async with httpx.AsyncClient() as _client:
|
|
57
63
|
response = await _client.{{ endpoint.method }}(
|
|
58
64
|
url=url,
|
|
59
|
-
headers=
|
|
65
|
+
headers=headers,
|
|
60
66
|
{% if endpoint.form_body_reference %}
|
|
61
67
|
data=asdict(form_data),
|
|
62
68
|
{% endif %}
|
|
@@ -64,7 +70,7 @@ async def {{ endpoint.name | snakecase }}(
|
|
|
64
70
|
files=multipart_data.to_dict(),
|
|
65
71
|
{% endif %}
|
|
66
72
|
{% if endpoint.json_body %}
|
|
67
|
-
json={{ endpoint.json_body.python_name }},
|
|
73
|
+
json={{ "json_" + endpoint.json_body.python_name }},
|
|
68
74
|
{% endif %}
|
|
69
75
|
{% if endpoint.query_parameters %}
|
|
70
76
|
params=params,
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
{% macro header_params(endpoint) %}
|
|
2
|
+
{% if endpoint.header_parameters %}
|
|
3
|
+
{% for parameter in endpoint.header_parameters %}
|
|
4
|
+
{% if parameter.required %}
|
|
5
|
+
headers["{{ parameter.python_name | kebabcase}}"] = {{ parameter.python_name }}
|
|
6
|
+
{% else %}
|
|
7
|
+
if {{ parameter.python_name }} is not None:
|
|
8
|
+
headers["{{ parameter.python_name | kebabcase}}"] = {{ parameter.python_name }}
|
|
9
|
+
{% endif %}
|
|
10
|
+
{% endfor %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% endmacro %}
|
|
13
|
+
|
|
1
14
|
{% macro query_params(endpoint) %}
|
|
2
15
|
{% if endpoint.query_parameters %}
|
|
3
16
|
{% for property in endpoint.query_parameters %}
|
|
@@ -11,7 +11,7 @@ from ..errors import ApiResponseError
|
|
|
11
11
|
{% endfor %}
|
|
12
12
|
{% for endpoint in collection.endpoints %}
|
|
13
13
|
|
|
14
|
-
{% from "endpoint_macros.pyi" import query_params, json_body, return_type %}
|
|
14
|
+
{% from "endpoint_macros.pyi" import header_params, query_params, json_body, return_type %}
|
|
15
15
|
|
|
16
16
|
def {{ endpoint.name | snakecase }}(
|
|
17
17
|
*,
|
|
@@ -41,6 +41,9 @@ def {{ endpoint.name | snakecase }}(
|
|
|
41
41
|
{% for parameter in endpoint.query_parameters %}
|
|
42
42
|
{{ parameter.to_string() }},
|
|
43
43
|
{% endfor %}
|
|
44
|
+
{% for parameter in endpoint.header_parameters %}
|
|
45
|
+
{{ parameter.to_string() }},
|
|
46
|
+
{% endfor %}
|
|
44
47
|
{{ return_type(endpoint) }}
|
|
45
48
|
""" {{ endpoint.description }} """
|
|
46
49
|
url = "{}{{ endpoint.path }}".format(
|
|
@@ -50,6 +53,9 @@ def {{ endpoint.name | snakecase }}(
|
|
|
50
53
|
{%- endfor -%}
|
|
51
54
|
)
|
|
52
55
|
|
|
56
|
+
headers: Dict[str, Any] = client.get_headers()
|
|
57
|
+
{{ header_params(endpoint) | indent(4) }}
|
|
58
|
+
|
|
53
59
|
{{ query_params(endpoint) | indent(4) }}
|
|
54
60
|
|
|
55
61
|
{{ json_body(endpoint) | indent(4) }}
|
|
@@ -57,7 +63,7 @@ def {{ endpoint.name | snakecase }}(
|
|
|
57
63
|
|
|
58
64
|
response = httpx.{{ endpoint.method }}(
|
|
59
65
|
url=url,
|
|
60
|
-
headers=
|
|
66
|
+
headers=headers,
|
|
61
67
|
{% if endpoint.form_body_reference %}
|
|
62
68
|
data=asdict(form_data),
|
|
63
69
|
{% endif %}
|
|
@@ -65,7 +71,7 @@ def {{ endpoint.name | snakecase }}(
|
|
|
65
71
|
files=multipart_data.to_dict(),
|
|
66
72
|
{% endif %}
|
|
67
73
|
{% if endpoint.json_body %}
|
|
68
|
-
json={{ endpoint.json_body.python_name }},
|
|
74
|
+
json={{ "json_" + endpoint.json_body.python_name }},
|
|
69
75
|
{% endif %}
|
|
70
76
|
{% if endpoint.query_parameters %}
|
|
71
77
|
params=params,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{% macro construct(property, source) %}
|
|
2
2
|
{% if property.required %}
|
|
3
|
-
{{ property.python_name }} = date.fromisoformat({{ source }})
|
|
3
|
+
{{ property.python_name }} = datetime.date.fromisoformat({{ source }})
|
|
4
4
|
{% else %}
|
|
5
5
|
{{ property.python_name }} = None
|
|
6
6
|
if {{ source }} is not None:
|
|
7
|
-
{{ property.python_name }} = date.fromisoformat(cast(str, {{ source }}))
|
|
7
|
+
{{ property.python_name }} = datetime.date.fromisoformat(cast(str, {{ source }}))
|
|
8
8
|
{% endif %}
|
|
9
9
|
{% endmacro %}
|
|
10
10
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{% macro construct(property, source) %}
|
|
2
2
|
{% if property.required %}
|
|
3
|
-
{{ property.python_name }} = datetime.fromisoformat({{ source }})
|
|
3
|
+
{{ property.python_name }} = datetime.datetime.fromisoformat({{ source }})
|
|
4
4
|
{% else %}
|
|
5
5
|
{{ property.python_name }} = None
|
|
6
6
|
if {{ source }} is not None:
|
|
7
|
-
{{ property.python_name }} = datetime.fromisoformat(cast(str, {{ source }}))
|
|
7
|
+
{{ property.python_name }} = datetime.datetime.fromisoformat(cast(str, {{ source }}))
|
|
8
8
|
{% endif %}
|
|
9
9
|
{% endmacro %}
|
|
10
10
|
|
openapi-python-client-0.5.3/openapi_python_client/templates/property_templates/dict_property.pyi
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% macro construct(property, source) %}
|
|
2
|
+
{% if property.required %}
|
|
3
|
+
{{ property.python_name }} = {{ source }}
|
|
4
|
+
{% else %}
|
|
5
|
+
{{ property.python_name }} = None
|
|
6
|
+
if {{ source }} is not None:
|
|
7
|
+
{{ property.python_name }} = {{ source }}
|
|
8
|
+
{% endif %}
|
|
9
|
+
{% endmacro %}
|
|
10
|
+
|
|
11
|
+
{% macro transform(property, source, destination) %}
|
|
12
|
+
{% if property.required %}
|
|
13
|
+
{{ destination }} = {{ source }}
|
|
14
|
+
{% else %}
|
|
15
|
+
{{ destination }} = {{ source }} if {{ source }} else None
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% endmacro %}
|
|
@@ -24,13 +24,13 @@ def _parse_{{ property.python_name }}(data: Dict[str, Any]) -> {{ property.get_t
|
|
|
24
24
|
{% macro transform(property, source, destination) %}
|
|
25
25
|
{% if not property.required %}
|
|
26
26
|
if {{ source }} is None:
|
|
27
|
-
{{ destination }} = None
|
|
27
|
+
{{ destination }}: {{ property.get_type_string() }} = None
|
|
28
28
|
{% endif %}
|
|
29
29
|
{% for inner_property in property.inner_properties %}
|
|
30
30
|
{% if loop.first and property.required %}{# No if None statement before this #}
|
|
31
|
-
if isinstance({{ source }}, {{ inner_property.get_type_string() }}):
|
|
31
|
+
if isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
|
|
32
32
|
{% elif not loop.last %}
|
|
33
|
-
elif isinstance({{ source }}, {{ inner_property.get_type_string() }}):
|
|
33
|
+
elif isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
|
|
34
34
|
{% else %}
|
|
35
35
|
else:
|
|
36
36
|
{% endif %}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from keyword import iskeyword
|
|
3
|
+
|
|
4
|
+
import stringcase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def sanitize(value: str) -> str:
|
|
8
|
+
return re.sub(r"[^\w _\-]+", "", value)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def fix_keywords(value: str) -> str:
|
|
12
|
+
if iskeyword(value):
|
|
13
|
+
return f"{value}_"
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def group_title(value: str) -> str:
|
|
18
|
+
value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ \-_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
|
|
19
|
+
value = re.sub(r"(^|[ _-])([A-Z])", lambda m: m.group(1) + m.group(2).lower(), value)
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def snake_case(value: str) -> str:
|
|
24
|
+
return fix_keywords(stringcase.snakecase(group_title(sanitize(value))))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def pascal_case(value: str) -> str:
|
|
28
|
+
return fix_keywords(stringcase.pascalcase(sanitize(value)))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def kebab_case(value: str) -> str:
|
|
32
|
+
return fix_keywords(stringcase.spinalcase(group_title(sanitize(value))))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def remove_string_escapes(value: str) -> str:
|
|
36
|
+
return value.replace('"', r"\"")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "openapi-python-client"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.3"
|
|
4
4
|
description = "Generate modern Python clients from OpenAPI"
|
|
5
5
|
repository = "https://github.com/triaxtec/openapi-python-client"
|
|
6
6
|
license = "MIT"
|
|
@@ -25,7 +25,7 @@ stringcase = "^1.2.0"
|
|
|
25
25
|
typer = ">=0.1,<0.4"
|
|
26
26
|
colorama = {version = "^0.4.3", markers = "sys_platform == 'win32'"}
|
|
27
27
|
shellingham = "^1.3.2"
|
|
28
|
-
httpx = "
|
|
28
|
+
httpx = ">=0.13,<0.15"
|
|
29
29
|
black = "^19.10b0"
|
|
30
30
|
isort = "^5.0.5"
|
|
31
31
|
pyyaml = "^5.3.1"
|
|
@@ -58,6 +58,11 @@ isort .\
|
|
|
58
58
|
openapi = "python -m end_to_end_tests.fastapi_app"
|
|
59
59
|
gm = "python -m end_to_end_tests.regen_golden_master"
|
|
60
60
|
e2e = "pytest openapi_python_client end_to_end_tests"
|
|
61
|
+
oge = """
|
|
62
|
+
task openapi\
|
|
63
|
+
&& task gm\
|
|
64
|
+
&& task e2e\
|
|
65
|
+
"""
|
|
61
66
|
|
|
62
67
|
[tool.black]
|
|
63
68
|
line-length = 120
|
|
@@ -12,7 +12,7 @@ package_data = \
|
|
|
12
12
|
|
|
13
13
|
install_requires = \
|
|
14
14
|
['black>=19.10b0,<20.0',
|
|
15
|
-
'httpx>=0.13
|
|
15
|
+
'httpx>=0.13,<0.15',
|
|
16
16
|
'isort>=5.0.5,<6.0.0',
|
|
17
17
|
'jinja2>=2.11.1,<3.0.0',
|
|
18
18
|
'pydantic>=1.6.1,<2.0.0',
|
|
@@ -30,7 +30,7 @@ entry_points = \
|
|
|
30
30
|
|
|
31
31
|
setup_kwargs = {
|
|
32
32
|
'name': 'openapi-python-client',
|
|
33
|
-
'version': '0.5.
|
|
33
|
+
'version': '0.5.3',
|
|
34
34
|
'description': 'Generate modern Python clients from OpenAPI',
|
|
35
35
|
'long_description': '[](https://circleci.com/gh/triaxtec/openapi-python-client)\n[](https://codecov.io/gh/triaxtec/openapi-python-client)\n[](https://pypi.python.org/pypi/openapi-python-client/)\n[](https://lbesson.mit-license.org/)\n[](https://mypy.readthedocs.io/en/stable/introduction.html)\n[](https://github.com/ambv/black)\n\n\n# openapi-python-client\nGenerate modern Python clients from OpenAPI\n\n**This project is still in development and does not support all OpenAPI features**\n\n## Why This?\nThe Python clients generated by openapi-generator support Python 2 and therefore come with a lot of baggage. This tool \naims to generate clients which:\n1. Use all the latest and greatest Python features like type annotations and dataclasses\n1. Don\'t carry around a bunch of compatibility code for older version of Python (e.g. the `six` package)\n1. Have better documentation and more obvious usage instructions\n\nAdditionally, because this generator is written in Python, it should be more accessible to contribution by the people \nusing it (Python developers).\n\n## Installation\nI recommend you install with [pipx](https://pipxproject.github.io/pipx/) so you don\'t conflict with any other packages \nyou might have: `pipx install openapi-python-client`.\n\nBetter yet, use `pipx run openapi-python-client <normal params / options>` to always use the latest version of the generator.\n\nYou can install with normal pip if you want to though: `pip install openapi-python-client`\n\nThen, if you want tab completion: `openapi-python-client --install-completion`\n\n## Usage\n### Create a new client\n`openapi-python-client generate --url https://my.api.com/openapi.json`\n\nThis will generate a new client library named based on the title in your OpenAPI spec. For example, if the title \nof your API is "My API", the expected output will be "my-api-client". If a folder already exists by that name, you\'ll \nget an error.\n\n### Update an existing client\n`openapi-python-client update --url https://my.api.com/openapi.json`\n\n> For more usage details run `openapi-python-client --help` or read [usage](usage.md)\n\n## What You Get\n1. A `pyproject.toml` file with some basic metadata intended to be used with [Poetry].\n1. A `README.md` you\'ll most definitely need to update with your project\'s details\n1. A Python module named just like the auto-generated project name (e.g. "my_api_client") which contains:\n 1. A `client` module which will have both a `Client` class and an `AuthenticatedClient` class. You\'ll need these \n for calling the functions in the `api` module.\n 1. An `api` module which will contain one module for each tag in your OpenAPI spec, as well as a `default` module \n for endpoints without a tag. Each of these modules in turn contains one function for calling each endpoint.\n 1. A `models` module which has all the classes defined by the various schemas in your OpenAPI spec\n \nFor a full example you can look at the `test_end_to_end` directory which has a declared [FastAPI](https://fastapi.tiangolo.com/) \nserver and the resulting openapi.json file in the "fastapi" directory. "golden-master" is the generated client from that \nOpenAPI document.\n \n## OpenAPI features supported\n1. All HTTP Methods\n1. JSON and form bodies, path and query parameters\n1. File uploads with multipart/form-data bodies\n1. float, string, int, date, datetime, string enums, and custom schemas or lists containing any of those\n1. html/text or application/json responses containing any of the previous types\n1. Bearer token security\n\n## Configuration\nYou can pass a YAML (or JSON) file to openapi-python-client with the `--config` option in order to change some behavior.\nThe following parameters are supported:\n\n### class_overrides\nUsed to change the name of generated model classes. This param should be a mapping of existing class name \n(usually a key in the "schemas" section of your OpenAPI document) to class_name and module_name. As an example, if the \nname of the a model in OpenAPI (and therefore the generated class name) was something like "_PrivateInternalLongName" \nand you want the generated client\'s model to be called "ShortName" in a module called "short_name" you could do this:\n\nExample:\n```yaml\nclass_overrides:\n _PrivateInternalLongName:\n class_name: ShortName\n module_name: short_name\n```\n\nThe easiest way to find what needs to be overridden is probably to generate your client and go look at everything in the\n models folder.\n \n ### project_name_override and package_name_override\nUsed to change the name of generated client library project/package. If the project name is changed but an override for the package name\nisn\'t provided, the package name will be converted from the project name using the standard convention (replacing `-`\'s with `_`\'s).\n\nExample:\n```yaml\nproject_name_override: my-special-project-name\npackage_name_override: my_extra_special_package_name\n```\n\n\n[CHANGELOG.md]: CHANGELOG.md\n[Poetry]: https://python-poetry.org/\n',
|
|
36
36
|
'author': 'Dylan Anthony',
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
import stringcase
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def snake_case(value: str) -> str:
|
|
7
|
-
value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ -_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
|
|
8
|
-
value = re.sub(r"(^|[ _-])([A-Z])", lambda m: m.group(1) + m.group(2).lower(), value)
|
|
9
|
-
return stringcase.snakecase(value)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def pascal_case(value: str) -> str:
|
|
13
|
-
return stringcase.pascalcase(value)
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/parser/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/LICENSE
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/README.md
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/contact.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/encoding.py
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/example.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/header.py
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/info.py
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/license.py
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/link.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/open_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/paths.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/response.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/server.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/tag.py
RENAMED
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/schema/xml.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/enum.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi-python-client-0.5.2 → openapi-python-client-0.5.3}/openapi_python_client/templates/types.py
RENAMED
|
File without changes
|