We are introducing the special oneOf and anyOf types in our Python SDKs.
Details
In OpenAPI, any request parameter or response payload could use union types i.e. OneOf/AnyOf, which provides the flexibility of accepting different types of request parameters and returning various types of response payload. Here is an example of an API call using union types,
paths:
/users/create:
post:
summary: Create a new user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
anyOf:
- $ref: '#/components/schemas/Accountant'
- $ref: '#/components/schemas/Manager'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/AccountantResponse'
- $ref: '#/components/schemas/ManagerResponse'
...
The API call, shown in the OpenApi format above, is a post-call that takes in either Accountant
or Manager
as request payload. Previously, union-types were not supported therefore the endpoint was expecting the combined schema UsersCreateRequest
as a parameter. Here is how the parameter initialization would look like when calling the endpoint,
- Before
- After
body = UsersCreateRequest(
account_id='A001',
first_name='John',
last_name='Doe',
manager_id='M001'
)
response = client.user_controller.create_user(body)
body = Accountant(
account_id='A001',
first_name='John',
last_name='Doe'
)
response = client.user_controller.create_user(body)
Now let's look at the response type of the same API call in the following example,
- Before
- After
response = client.user_controller.create_user(body)
assert isinstance(response, AccountantResponse) # False
assert isinstance(response, ManagerResponse) # False
response = client.user_controller.create_user(body)
assert isinstance(response, AccountantResponse) # True
assert isinstance(response, ManagerResponse) # False
In the above code snippet, createUser
will return either the AccountantResponse
or ManagerResponse
instance as response payload. Previously, it was considered a dynamic response which allowed any type of response payload. Now, with the introduction of OneOf and AnyOf support, we can assert that the response can be of either AccountantResponse
or ManagerResponse
.
What has Changed?
The Python SDK now has the ability to handle union types in an endpoint's request and response. The type validation for union types is also added to support the OneOf/AnyOf constraints.
Introducing UnionTypeLookUp
All union types (i.e. OneOf/AnyOf) instances defined in the spec are now being accessed through UnionTypeLookUp
. We introduced a class in utilities by the name of UnionTypeLookUp
which holds a dictionary of unionTypes. The responsibility of the UnionTypeLookUp
class is to provide the already in-place union-type instances. The instance returned by the UnionTypeLookUp
class, is then used for validation, deserialization, and serialization.
For the given endpoint, the union-type instance would look like,
{
'CreateUserBody': AnyOf(
[
LeafType(Accountant),
LeafType(Manager)
]
),
'CreateUserResponse': OneOf(
[
LeafType(AccountantResponse),
LeafType(ManagerResponse)
]
)
}
Changes in Models:
Now, any custom type (i.e. models or enums) participating as union-type, has the validate method to verify all required properties are present in the case of models and the provided value is a valid enum value in the case of enums.
Changes in Endpoints:
Endpoint Doc Changes:
The endpoint's doc string has been updated to describe union-type parameters or return types.
- Before
- After
def create_user(self,
body):
"""Does a POST request to /users/create.
Create a new user
Args:
body (UsersCreateRequest): TODO: type description here.
Returns:
mixed: Response from the API. User created successfully
Raises:
APIException: When an error occurs while fetching the data from
the remote API. This exception includes the HTTP Response
code, an error message, and the HTTP body that was received in
the request.
"""
def create_user(self,
body):
"""Does a POST request to /users/create.
Create a new user
Args:
body (Accountant | Manager): TODO: type description here.
Returns:
AccountantResponse | ManagerResponse: Response from the API. User
created successfully
Raises:
APIException: When an error occurs while fetching the data from
the remote API. This exception includes the HTTP Response
code, an error message, and the HTTP body that was received in
the request.
"""
Parameter Validation:
The union-type parameters are now being validated when sending the request.
- Before
- After
return super().new_api_call_builder.request(
RequestBuilder().server(Server.DEFAULT)
.path('/users/create')
.http_method(HttpMethodEnum.POST)
.body_param(Parameter()
.value(body))
.body_serializer(APIHelper.json_serialize)
).execute()
return super().new_api_call_builder.request(
RequestBuilder().server(Server.DEFAULT)
.path('/users/create')
.http_method(HttpMethodEnum.POST)
.body_param(Parameter()
.value(body)
.validator(lambda value: UnionTypeLookUp.get('CreateUserBody').validate(value)))
.body_serializer(APIHelper.json_serialize)
).execute()
Response Deserialization:
The union-type response is now being validated and then deserialized.
- Before
- After
return super().new_api_call_builder.response(
ResponseHandler()
.deserializer(APIHelper.dynamic_deserialize)
).execute()
return super().new_api_call_builder.response(
ResponseHandler()
.deserializer(lambda value: APIHelper.deserialize_union_type(
UnionTypeLookUp.get('CreateUserResponse'), value))
).execute()
Validation Exceptions:
We have introduced OneOfValidationException
and AnyOfValidationException
which will be thrown whenever an input with an invalid type is provided to an endpoint or invalid typed data is received via a response from the server. This feature will enforce type strictness and validation of requests and responses within the SDK.
Following are the trigger points for raising these exceptions:
OneOfValidationException
: It is raised when either more than one type is matched or no type is matched at all against the provided value.
AnyOfValidationException
: It is raised when no type matched at all against the provided value.
You can explicitly rescue the OneOfValidationException and AnyOfValidationException in the code snippet.
try:
result = client.user_controller.create_user(body)
print(result)
except OneOfValidationException as e:
print(e)
except AnyOfValidationException as e:
print(e)
except APIException as e:
print(e)
README Changes:
Now, the Python SDK README will reflect the support for union types in parameter types or response types.
- Before
- After
Portal Changes
We have added the capability to select and configure any case mentioned as union type through our API Code Playground on the portal. With this feature, you can now generate code samples for different cases of union types.