Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,23 @@ Currently, the following languages/frameworks are supported:
- instantiating models
- sending to endpoints
- receiving from endpoints
- Type hints for all model inputs
- Type hints for accessing properties in object instances so some_val in some_val = some_inst['someKey'] will have the correct type hint
- Type hints for accessing array items in array instances so some_val in some_val = array_inst[0] will have the correct type hint
- Endpoints have input and response type hints
- Type hints on
- all model inputs in `__new__`
- accessing properties in object instances so some_val in some_val = some_inst['someKey'] will have the correct type hint
- accessing array items in array instances so some_val in some_val = array_inst[0] will have the correct type hint
- endpoint inputs + responses
- Format support for: int32, int64, float, double, binary, date, datetime, uuid
- Invalid (in python) property names supported like `from`, `1var`, `hi-there` etc in
- schema property names
- endpoint parameter names
- Openapi spec inline schemas supported at any depth
- If needed, validation of some json schema keywords can be deactivated via a configuration class
- Payload values are not coerced when validated, so a datetime value can pass other validations that describe the payload only as type string
- String transmission of numbers supported with type: string, format: number, value can be accessed as a Decimal with inst.as_decimal_oapg
- Format support for: int32, int64, float, double, binary, date, datetime
- Multiple content types supported for request and response bodies
- Endpoint response always also includes the urllib3.HTTPResponse
- Endpoint response deserialization can be skipped with the skip_deserialization argument
- Invalid (in python) property names supported like self, from etc
- Validated payload instances subclass all validated schemas so no need to run validate twice, just use isinstance(some_inst, SomeSchemaClass)

And many more!
- [Docs for the python generator](https://github.com/openapi-json-schema-tools/openapi-json-schema-generator/blob/master/docs/generators/python.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -997,12 +997,13 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)
}
}
// clone this so we can change some properties on it
CodegenProperty schemaProp = cp.getSchema().clone();
CodegenProperty schemaProp = cp.getSchema();
// parameters may have valid python names like some_val or invalid ones like Content-Type
// we always set nameInSnakeCase to null so special handling will not be done for these names
// invalid python names will be handled in python by using a TypedDict which will allow us to have a type hint
// for keys that cannot be variable names to the schema baseName
if (schemaProp != null) {
schemaProp = schemaProp.clone();
schemaProp.nameInSnakeCase = null;
schemaProp.baseName = toModelName(cp.baseName) + "Schema";
cp.setSchema(schemaProp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class RequestField(RequestFieldBase):


class JSONEncoder(json.JSONEncoder):
compact_separators = (',', ':')

def default(self, obj):
if isinstance(obj, str):
return str(obj)
Expand Down Expand Up @@ -320,8 +322,25 @@ class StyleSimpleSerializer(ParameterSerializerBase):
)


class JSONDetector:
"""
Works for:
application/json
application/json; charset=UTF-8
application/json-patch+json
application/geo+json
"""
__json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")

@classmethod
def _content_type_is_json(cls, content_type: str) -> bool:
if cls.__json_content_type_pattern.match(content_type):
return True
return False


@dataclass
class ParameterBase:
class ParameterBase(JSONDetector):
name: str
in_type: ParameterInType
required: bool
Expand All @@ -348,7 +367,6 @@ class ParameterBase:
}
__disallowed_header_names = {'Accept', 'Content-Type', 'Authorization'}
_json_encoder = JSONEncoder()
_json_content_type = 'application/json'

@classmethod
def __verify_style_to_in_type(cls, style: typing.Optional[ParameterStyle], in_type: ParameterInType):
Expand Down Expand Up @@ -395,8 +413,11 @@ class ParameterBase:

def _serialize_json(
self,
in_data: typing.Union[None, int, float, str, bool, dict, list]
in_data: typing.Union[None, int, float, str, bool, dict, list],
eliminate_whitespace: bool = False
) -> str:
if eliminate_whitespace:
return json.dumps(in_data, separators=self._json_encoder.compact_separators)
return json.dumps(in_data)


Expand Down Expand Up @@ -491,7 +512,7 @@ class PathParameter(ParameterBase, StyleSimpleSerializer):
for content_type, schema in self.content.items():
cast_in_data = schema(in_data)
cast_in_data = self._json_encoder.default(cast_in_data)
if content_type == self._json_content_type:
if self._content_type_is_json(content_type):
value = self._serialize_json(cast_in_data)
return self._to_dict(self.name, value)
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
Expand All @@ -509,7 +530,7 @@ class QueryParameter(ParameterBase, StyleFormSerializer):
schema: typing.Optional[typing.Type[Schema]] = None,
content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None
):
used_style = ParameterStyle.FORM if style is None and content is None and schema else style
used_style = ParameterStyle.FORM if style is None else style
used_explode = self._get_default_explode(used_style) if explode is None else explode

super().__init__(
Expand Down Expand Up @@ -572,8 +593,6 @@ class QueryParameter(ParameterBase, StyleFormSerializer):
return self._to_dict(self.name, value)

def get_prefix_separator_iterator(self) -> typing.Optional[PrefixSeparatorIterator]:
if not self.schema:
return None
if self.style is ParameterStyle.FORM:
return PrefixSeparatorIterator('?', '&')
elif self.style is ParameterStyle.SPACE_DELIMITED:
Expand Down Expand Up @@ -612,12 +631,17 @@ class QueryParameter(ParameterBase, StyleFormSerializer):
elif self.style is ParameterStyle.PIPE_DELIMITED:
return self.__serialize_pipe_delimited(cast_in_data, prefix_separator_iterator)
# self.content will be length one
if prefix_separator_iterator is None:
prefix_separator_iterator = self.get_prefix_separator_iterator()
for content_type, schema in self.content.items():
cast_in_data = schema(in_data)
cast_in_data = self._json_encoder.default(cast_in_data)
if content_type == self._json_content_type:
value = self._serialize_json(cast_in_data)
return self._to_dict(self.name, value)
if self._content_type_is_json(content_type):
value = self._serialize_json(cast_in_data, eliminate_whitespace=True)
return self._to_dict(
self.name,
next(prefix_separator_iterator) + self.name + '=' + quote(value)
)
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))


Expand Down Expand Up @@ -676,7 +700,7 @@ class CookieParameter(ParameterBase, StyleFormSerializer):
for content_type, schema in self.content.items():
cast_in_data = schema(in_data)
cast_in_data = self._json_encoder.default(cast_in_data)
if content_type == self._json_content_type:
if self._content_type_is_json(content_type):
value = self._serialize_json(cast_in_data)
return self._to_dict(self.name, value)
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
Expand Down Expand Up @@ -733,7 +757,7 @@ class HeaderParameter(ParameterBase, StyleSimpleSerializer):
for content_type, schema in self.content.items():
cast_in_data = schema(in_data)
cast_in_data = self._json_encoder.default(cast_in_data)
if content_type == self._json_content_type:
if self._content_type_is_json(content_type):
value = self._serialize_json(cast_in_data)
return self.__to_headers(((self.name, value),))
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
Expand Down Expand Up @@ -796,23 +820,6 @@ class ApiResponseWithoutDeserialization(ApiResponse):
headers: typing.Union[Unset, typing.List[HeaderParameter]] = unset


class JSONDetector:
"""
Works for:
application/json
application/json; charset=UTF-8
application/json-patch+json
application/geo+json
"""
__json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")

@classmethod
def _content_type_is_json(cls, content_type: str) -> bool:
if cls.__json_content_type_pattern.match(content_type):
return True
return False


class OpenApiResponse(JSONDetector):
__filename_content_disposition_pattern = re.compile('filename="(.+?)"')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,156 +21,16 @@ from . import path
{{/unless}}
{{#with operation}}
{{#if queryParams}}
# query params
{{#each queryParams}}
{{#with schema}}
{{> model_templates/schema }}
{{/with}}
{{/each}}
RequestRequiredQueryParams = typing_extensions.TypedDict(
'RequestRequiredQueryParams',
{
{{#each queryParams}}
{{#if required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/if}}
{{/each}}
}
)
RequestOptionalQueryParams = typing_extensions.TypedDict(
'RequestOptionalQueryParams',
{
{{#each queryParams}}
{{#unless required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/unless}}
{{/each}}
},
total=False
)


class RequestQueryParams(RequestRequiredQueryParams, RequestOptionalQueryParams):
pass


{{#each queryParams}}
{{> endpoint_parameter }}
{{/each}}
{{> endpoint_parameter_schema_and_def xParams=queryParams xParamsName="Query" }}
{{/if}}
{{#if headerParams}}
# header params
{{#each headerParams}}
{{#with schema}}
{{> model_templates/schema }}
{{/with}}
{{/each}}
RequestRequiredHeaderParams = typing_extensions.TypedDict(
'RequestRequiredHeaderParams',
{
{{#each headerParams}}
{{#if required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/if}}
{{/each}}
}
)
RequestOptionalHeaderParams = typing_extensions.TypedDict(
'RequestOptionalHeaderParams',
{
{{#each headerParams}}
{{#unless required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/unless}}
{{/each}}
},
total=False
)


class RequestHeaderParams(RequestRequiredHeaderParams, RequestOptionalHeaderParams):
pass


{{#each headerParams}}
{{> endpoint_parameter }}
{{/each}}
{{> endpoint_parameter_schema_and_def xParams=headerParams xParamsName="Header" }}
{{/if}}
{{#if pathParams}}
# path params
{{#each pathParams}}
{{#with schema}}
{{> model_templates/schema }}
{{/with}}
{{/each}}
RequestRequiredPathParams = typing_extensions.TypedDict(
'RequestRequiredPathParams',
{
{{#each pathParams}}
{{#if required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/if}}
{{/each}}
}
)
RequestOptionalPathParams = typing_extensions.TypedDict(
'RequestOptionalPathParams',
{
{{#each pathParams}}
{{#unless required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/unless}}
{{/each}}
},
total=False
)


class RequestPathParams(RequestRequiredPathParams, RequestOptionalPathParams):
pass


{{#each pathParams}}
{{> endpoint_parameter }}
{{/each}}
{{> endpoint_parameter_schema_and_def xParams=pathParams xParamsName="Path" }}
{{/if}}
{{#if cookieParams}}
# cookie params
{{#each cookieParams}}
{{#with schema}}
{{> model_templates/schema }}
{{/with}}
{{/each}}
RequestRequiredCookieParams = typing_extensions.TypedDict(
'RequestRequiredCookieParams',
{
{{#each cookieParams}}
{{#if required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/if}}
{{/each}}
}
)
RequestOptionalCookieParams = typing_extensions.TypedDict(
'RequestOptionalCookieParams',
{
{{#each cookieParams}}
{{#unless required}}
'{{baseName}}': {{#with schema}}typing.Union[{{baseName}}, {{> model_templates/schema_python_types }}],{{/with}}
{{/unless}}
{{/each}}
},
total=False
)


class RequestCookieParams(RequestRequiredCookieParams, RequestOptionalCookieParams):
pass


{{#each cookieParams}}
{{> endpoint_parameter }}
{{/each}}
{{> endpoint_parameter_schema_and_def xParams=cookieParams xParamsName="Cookie" }}
{{/if}}
{{#with bodyParam}}
# body param
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ request_{{#if isQueryParam}}query{{/if}}{{#if isPathParam}}path{{/if}}{{#if isHe
style=api_client.ParameterStyle.{{style}},
{{/if}}
{{#if schema}}
{{#with schema}}
{{#with schema}}
schema={{baseName}},
{{/with}}
{{/with}}
{{/if}}
{{#if getContent}}
content={
{{#each getContent}}
"{{@key}}": {{#with this}}{{#with schema}}{{baseName}}{{/with}}{{/with}},
{{/each}}
},
{{/if}}
{{#if required}}
required=True,
Expand Down
Loading