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

Support Union type as response model #233

Open
TimoGlastra opened this issue Sep 3, 2021 · 2 comments
Open

Support Union type as response model #233

TimoGlastra opened this issue Sep 3, 2021 · 2 comments

Comments

@TimoGlastra
Copy link

TimoGlastra commented Sep 3, 2021

Is your feature request related to a problem? Please describe.

It would be nice if Uplink could support Union types as response model. We're using Uplink (with Pydantic) to automatically generate a client. An example method (simplified) that it will generate is as follows:

    @returns.json()
    @json
    @post("/schemas")
    def __publish_schema(
        self,
        *,
        conn_id: Query = None,
        create_transaction_for_endorser: Query = None,
        body: Body(type=SchemaSendRequest) = {}
    ) -> Union[TxnOrSchemaSendResult, SchemaSendResult]:
        """Internal uplink method for publish_schema"""

This will throw an error: TypeError: issubclass() arg 1 must be a class because Union is not a class (see stack trace below for more info)

Describe the solution you'd like

It would be nice if uplink could support Union types as response models. Pydantic has this feature, where you need to make sure to put the in the right order, as the first schema that matches will be taken. Something similar (maybe leaning on the implementation in Pydantic) would be a great addition I think. Pydantic feature docs: https://pydantic-docs.helpmanual.io/usage/types/#unions

Additional context

Error log when using Union type:

Traceback (most recent call last):
  File "test.py", line 20, in <module>
    event_loop.run_until_complete(run())
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "test.py", line 9, in run
    schema = await client.schema.publish_schema(
  File "/Users/timoglastra/Developer/Work/Animo/Projects/Yoma/aries-cloudcontroller-python/aries_cloudcontroller/api/schema.py", line 57, in publish_schema
    return await self.__publish_schema(
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/builder.py", line 95, in __call__
    self._request_definition.define_request(request_builder, args, kwargs)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/commands.py", line 287, in define_request
    self._method_handler.handle_builder(request_builder)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/decorators.py", line 62, in handle_builder
    annotation.modify_request(request_builder)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/returns.py", line 64, in modify_request
    converter = request_builder.get_converter(
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/helpers.py", line 96, in get_converter
    return self._converter_registry[converter_key](*args, **kwargs)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/converters/__init__.py", line 54, in __call__
    converter = self._converter_factory(*args, **kwargs)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/converters/__init__.py", line 114, in chain
    converter = func(factory)(*args, **kwargs)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/converters/typing_.py", line 128, in create_response_body_converter
    return self._base_converter(type_)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/site-packages/uplink/converters/typing_.py", line 122, in _base_converter
    if issubclass(type_.__origin__, self.typing.Sequence):
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/typing.py", line 774, in __subclasscheck__
    return issubclass(cls, self.__origin__)
  File "/Users/timoglastra/.pyenv/versions/3.8.10/lib/python3.8/abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class
@TimoGlastra
Copy link
Author

For anyone else running into this issue, I've managed to fix it using a custom pydantic converter with a wrapper model class. This leverages the union support of pydantic:

        if typing.get_origin(self._model) is Union:

             class Container(BaseModel):
                 v: self._model

             data = {"v": data}
             return Container.parse_obj(data).v

https://github.com/didx-xyz/aries-cloudcontroller-python/pull/71/files#diff-26a4b54a23153cafa5f939c580250b6a143fdc57767837a3b35b535a8199ce8aR70-R77

@EvaSDK
Copy link

EvaSDK commented Jan 5, 2023

FTR, I shared the hopefully generic implementation I came up with combining various solutions found here and in the discussion #255.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants