Skip to content

Commit

Permalink
feat(union-types): adds support for OneOf and AnyOf (#41)
Browse files Browse the repository at this point in the history
This commit adds support for union types that are OneOf and AnyOf types. There are 4 new classes (OneOf, AnyOf, LeafType, UnionTypeContext) that are participating as generic algorithms for validating requests and responses containing union types. You can read more about union types [here](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/). By using those classes, the Python core library now has the handling for union types. This also adds support for the serialization of nested maps and arrays.
  • Loading branch information
sufyankhanrao authored Jul 24, 2023
1 parent f338272 commit 89b3f40
Show file tree
Hide file tree
Showing 44 changed files with 3,732 additions and 132 deletions.
35 changes: 20 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,28 @@ pip install apimatic-core
| [`EndpointLogger`](apimatic_core/logger/endpoint_logger.py) | A class to provide logging for an HTTP request |

## Types
| Name | Description |
|------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| [`SerializationFormats`](apimatic_core/types/array_serialization_format.py) | An Enumeration of Array serialization formats |
| [`DateTimeFormat`](apimatic_core/types/datetime_format.py ) | An Enumeration of Date Time formats |
| [`ErrorCase`](apimatic_core/types/error_case.py ) | A class to represent Exception types |
| [`FileWrapper`](apimatic_core/types/file_wrapper.py) | A wrapper to allow passing in content type for file uploads |
| [`Parameter`](apimatic_core/types/parameter.py ) | A class to represent information about a Parameter passed in an endpoint |
| [`XmlAttributes`](apimatic_core/types/xml_attributes.py ) | A class to represent information about a XML Parameter passed in an endpoint |
| Name | Description |
|-------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| [`SerializationFormats`](apimatic_core/types/array_serialization_format.py) | An Enumeration of Array serialization formats |
| [`DateTimeFormat`](apimatic_core/types/datetime_format.py ) | An Enumeration of Date Time formats |
| [`ErrorCase`](apimatic_core/types/error_case.py ) | A class to represent Exception types |
| [`FileWrapper`](apimatic_core/types/file_wrapper.py) | A wrapper to allow passing in content type for file uploads |
| [`Parameter`](apimatic_core/types/parameter.py ) | A class to represent information about a Parameter passed in an endpoint |
| [`XmlAttributes`](apimatic_core/types/xml_attributes.py ) | A class to represent information about a XML Parameter passed in an endpoint |
| [`OneOf`](apimatic_core/types/union_types/one_of.py ) | A class to represent information about OneOf union types |
| [`AnyOf`](apimatic_core/types/union_types/any_of.py ) | A class to represent information about AnyOf union types |
| [`LeafType`](apimatic_core/types/union_types/leaf_type.py ) | A class to represent the case information in an OneOf or AnyOf union type |

## Utilities
| Name | Description |
|--------------------------------------------------------------------|--------------------------------------------------------------------------------------|
| [`ApiHelper`](apimatic_core/utilities/api_helper.py) | A Helper Class with various functions associated with making an API Call |
| [`AuthHelper`](apimatic_core/utilities/auth_helper.py) | A Helper Class with various functions associated with authentication in API Calls |
| [`ComparisonHelper`](apimatic_core/utilities/comparison_helper.py) | A Helper Class used for the comparison of expected and actual API response |
| [` FileHelper`](apimatic_core/utilities/file_helper.py) | A Helper Class for files |
| [`XmlHelper`](apimatic_core/utilities/xml_helper.py ) | A Helper class that holds utility methods for xml serialization and deserialization. |
| Name | Description |
|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| [`ApiHelper`](apimatic_core/utilities/api_helper.py) | A Helper Class with various functions associated with making an API Call |
| [`AuthHelper`](apimatic_core/utilities/auth_helper.py) | A Helper Class with various functions associated with authentication in API Calls |
| [`ComparisonHelper`](apimatic_core/utilities/comparison_helper.py) | A Helper Class used for the comparison of expected and actual API response |
| [`FileHelper`](apimatic_core/utilities/file_helper.py) | A Helper Class for files |
| [`XmlHelper`](apimatic_core/utilities/xml_helper.py ) | A Helper class that holds utility methods for xml serialization and deserialization. |
| [`DateTimeHelper`](apimatic_core/utilities/datetime_helper.py ) | A Helper class that holds utility methods for validation of different datetime formats. |
| [`UnionTypeHelper`](apimatic_core/utilities/union_type_helper.py ) | A Helper class that holds utility methods for deserialization and validation of OneOf/AnyOf union types. |

## Links
* [apimatic-core-interfaces](https://pypi.org/project/apimatic-core-interfaces/)
Expand Down
3 changes: 2 additions & 1 deletion apimatic_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
'utilities',
'factories',
'types',
'logger'
'logger',
'exceptions'
]
2 changes: 1 addition & 1 deletion apimatic_core/authentication/multiple/auth_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def with_auth_managers(self, auth_managers):

return self

def is_valid(self):
def is_valid(self): # pragma: no cover
...

def apply(self, http_request):
Expand Down
4 changes: 4 additions & 0 deletions apimatic_core/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__all__ = [
'oneof_validation_exception',
'anyof_validation_exception'
]
5 changes: 5 additions & 0 deletions apimatic_core/exceptions/anyof_validation_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

class AnyOfValidationException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
5 changes: 5 additions & 0 deletions apimatic_core/exceptions/oneof_validation_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

class OneOfValidationException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from apimatic_core.factories.http_response_factory import HttpResponseFactory


class HttpClientConfiguration(object):
class HttpClientConfiguration(object): # pragma: no cover
"""A class used for configuring the SDK by a user.
"""

Expand Down
6 changes: 2 additions & 4 deletions apimatic_core/http/http_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ class HttpCallBack(object):
"""

def on_before_request(self,
request):
def on_before_request(self, request): # pragma: no cover
"""The controller will call this method before making the HttpRequest.
Args:
Expand All @@ -19,8 +18,7 @@ def on_before_request(self,
"""
raise NotImplementedError("This method has not been implemented.")

def on_after_response(self,
http_response):
def on_after_response(self, http_response): # pragma: no cover
"""The controller will call this method after making the HttpRequest.
Args:
Expand Down
2 changes: 1 addition & 1 deletion apimatic_core/http/request/http_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def add_header(self, name, value):
"""
self.headers[name] = value

def add_parameter(self, name, value):
def add_parameter(self, name, value): # pragma: no cover
""" Add a parameter to the HttpRequest.
Args:
Expand Down
7 changes: 0 additions & 7 deletions apimatic_core/request_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def __init__(
self._additional_query_params = {}
self._multipart_params = []
self._body_param = None
self._should_wrap_body_param = None
self._body_serializer = None
self._auth = None
self._array_serialization_format = SerializationFormats.INDEXED
Expand Down Expand Up @@ -90,10 +89,6 @@ def body_param(self, body_param):
self._body_param = body_param.get_value()
return self

def should_wrap_body_param(self, should_wrap_body_param):
self._should_wrap_body_param = should_wrap_body_param
return self

def body_serializer(self, body_serializer):
self._body_serializer = body_serializer
return self
Expand Down Expand Up @@ -184,8 +179,6 @@ def process_body_params(self):
self.add_additional_form_params()
return ApiHelper.form_encode_parameters(self._form_params, self._array_serialization_format)
elif self._body_param is not None and self._body_serializer:
if self._should_wrap_body_param:
return self._body_serializer(self.resolve_body_param(), self._should_wrap_body_param)
return self._body_serializer(self.resolve_body_param())
elif self._body_param is not None and not self._body_serializer:
return self.resolve_body_param()
Expand Down
3 changes: 2 additions & 1 deletion apimatic_core/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
'error_case',
'file_wrapper',
'array_serialization_format',
'xml_attributes'
'xml_attributes',
'union_types'
]
8 changes: 8 additions & 0 deletions apimatic_core/types/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(
self._is_required = False
self._should_encode = False
self._default_content_type = None
self._validator = None

def key(self, key):
self._key = key
Expand All @@ -42,7 +43,14 @@ def default_content_type(self, default_content_type):
self._default_content_type = default_content_type
return self

def validator(self, validator):
self._validator = validator
return self

def validate(self):
if self._is_required and self._value is None:
raise ValueError("Required parameter {} cannot be None.".format(self._key))

if self._validator is not None and self._validator(self._value):
return

6 changes: 6 additions & 0 deletions apimatic_core/types/union_types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__all__ = [
"any_of",
"one_of",
"union_type_context",
"leaf_type"
]
63 changes: 63 additions & 0 deletions apimatic_core/types/union_types/any_of.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from apimatic_core_interfaces.types.union_type import UnionType
from apimatic_core.types.union_types.union_type_context import UnionTypeContext
from apimatic_core.utilities.union_type_helper import UnionTypeHelper


class AnyOf(UnionType):

def __init__(self, union_types, union_type_context: UnionTypeContext = UnionTypeContext()):
super(AnyOf, self).__init__(union_types, union_type_context)
self.collection_cases = None

def validate(self, value):
context = self._union_type_context
UnionTypeHelper.update_nested_flag_for_union_types(self._union_types)
is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(context,
[nested_type.get_context()
for nested_type in self._union_types])

if value is None and is_optional_or_nullable:
self.is_valid = True
return self

if value is None:
self.is_valid = False
self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages,
self.get_context().is_nested, False)
return self

self._validate_value_against_case(value, context)

if not self.is_valid:
self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages,
self.get_context().is_nested, False)

return self

def deserialize(self, value):
if value is None:
return None

return UnionTypeHelper.deserialize_value(value, self._union_type_context, self.collection_cases,
self._union_types)

def _validate_value_against_case(self, value, context):
if context.is_array() and context.is_dict() and context.is_array_of_dict():
self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, value,
False)
elif context.is_array() and context.is_dict():
self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, value,
False)
elif context.is_array():
self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, value, False)
elif context.is_dict():
self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, value, False)
else:
self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1

def __deepcopy__(self, memo={}):
copy_object = AnyOf(self._union_types, self._union_type_context)
copy_object.is_valid = self.is_valid
copy_object.collection_cases = self.collection_cases
copy_object.error_messages = self.error_messages
return copy_object
Loading

0 comments on commit 89b3f40

Please sign in to comment.