arskom / spyne

A transport agnostic sync/async RPC library that focuses on exposing services with a well-defined API using popular protocols.
http://spyne.io
Other
1.13k stars 313 forks source link

Types introduced in service end up in spyne.model.primitive namespace #569

Open tlandschoff-scale opened 6 years ago

tlandschoff-scale commented 6 years ago

Hello world,

we are working on upgrading our application from spyne 2.9 to spyne 2.13 (for Python 3 support). While working on this, we noticed that spyne now misplaces the local types introduced for our service.

Here is a full example:

#!/usr/bin/env python

import logging
import uuid

from spyne.application import Application
from spyne.decorator import srpc
from spyne.interface import Wsdl11
from spyne.protocol.soap import Soap11
from spyne.service import ServiceBase
from spyne.model.primitive import Unicode

UUIDType = Unicode(
    type_name="UUIDType",
    pattern=r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}",
)

namespace = uuid.UUID("2faf5f8c-08be-445c-b228-fd33dd6b943b")

class ServiceWithNewType(ServiceBase):
    @srpc(UUIDType, _returns=UUIDType)
    def derive_uuid(original):
        return str(uuid.uuid3(namespace, original))

if __name__=='__main__':
    logging.basicConfig(level=logging.DEBUG)
    app = Application([ServiceWithNewType], 'expected.namespace', in_protocol=Soap11(), out_protocol=Soap11())
    app.transport = 'null.spyne'

    wsdl = Wsdl11(app.interface)
    wsdl.build_interface_document('URL')
    wsdl_str = wsdl.get_interface_document()
    print(wsdl_str)

Here is the WSDL that is generated by spyne 2.9 and spyne 2.12: generated_wsdl.zip This was created by running

$ pip install spyne==2.9.3
$ python service_type_misplaced.py | xmllint -format - > service_type_spyne2_9.wsdl
$ pip install spyne==2.12.14
$ python service_type_misplaced.py | xmllint -format - > service_type_spyne2_12.wsdl

Here is the diff between both WSDL files: WSDL_Diff_Generator.pdf

I think the old behaviour makes much more sense and would rather like it back. Trying to come up with a patch...

Greetings, Torsten

tlandschoff-scale commented 6 years ago

BTW: This can be worked around by explicitly giving the namespace like this:

UUIDType = Unicode(
    type_name="UUIDType",
    pattern=r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}",
    __namespace__="expected.namespace",
)

This makes the wsdl of spyne 2.9 and spyne 2.12 match up again for this example.

tlandschoff-scale commented 6 years ago

FYI: The behaviour in spyne master at 049b7899b6498346a3a1d74e58fa55b7cfe24abc is unchanged.

plq commented 6 years ago

this could be the result of calling resolve_namespace() too early somewhere.

I think the old behaviour makes much more sense

agreed

and would rather like it back. Trying to come up with a patch...

please do as I can't say I have time to look at this right now.

tlandschoff-scale commented 6 years ago

Edit: Simplified the stack trace, dropping pytest and debugger frames.

this could be the result of calling resolve_namespace() too early somewhere.

You bet. Here is the traceback of that call:

  File "/home/torsten.landschoff/workspace/spyne/spyne/test/model/test_primitive.py", line 574, in test_new_type
    custom_type = Unicode(pattern='123')
  File "/home/torsten.landschoff/workspace/spyne/spyne/model/primitive/string.py", line 86, in __new__
    retval = SimpleModel.__new__(cls,  ** kwargs)
  File "/home/torsten.landschoff/workspace/spyne/spyne/model/_base.py", line 803, in __new__
    return cls.customize(**kwargs)
  File "/home/torsten.landschoff/workspace/spyne/spyne/model/_base.py", line 821, in customize
    retval.resolve_namespace(retval, kwargs.get('__namespace__'))

It appears to me that commit 7029e18959f37b2f66133db4be5dc1f4f97a383f changed the behaviour to call resolve_namespace immediately when customizing a type.

plq commented 6 years ago

2013?! Honestly?! shows how much spyne's soap protocol gets used I guess :)

tlandschoff-scale commented 6 years ago

I guess it's more about how many people care about the generated WSDL. After all, whatever the namespace, this would stil work.

tlandschoff-scale commented 6 years ago

BTW: I just noted this comment in resolve_namespace:

    def resolve_namespace(cls, default_ns, tags=None):
        """This call finalizes the namespace assignment. The default namespace
        is not available until the application calls populate_interface method
        of the interface generator.
        """

Given this comment, the changes in 7029e18959f37b2f66133db4be5dc1f4f97a383f appear to be wrong in that resolve_namespace is now called immediately when creating a new type.

plq commented 6 years ago

Given this comment, the changes in 7029e18 appear to be wrong in that resolve_namespace is now called immediately when creating a new type.

yes, this needs to be fixed.

AndTheDaysGoBy commented 1 year ago

I believe I'm experience a similar issue except with complex types. In particular, the ValueError of classes X and Y have conflicting names appears. The format being, the first class scoped to the module in which the model was declared. For example,

<class 'my_web_service.models.OrderResponse'>

and the second to the spyne namespace

<class 'spyne.model.complex.OrderResponse'>

conflicting on the name

<class '{the namespace}OrderResponse>

as naturally both have the same base name, as they're the same class, and the namespace, for the same reason.

Ah, sorry. Investigating, the issue stems from something else. tl;dr, I had a WSDL, to be able to in-place swap this service as much as possible, I was fiddling with spyne to get as equivalent a WSDL as possible. One issue was response element name which is determined by the function name (which I wanted to keep distinct and in snake-case). I attempted to use the out_message_name parameter to override this, similar to how _in_message_name and _operation_name allow you to override the input.

However, overriding in this manner, when the class name of the return type matches what that message name would be results in a conflict.

For those who have the element sharing a name with a namespaced type, the work arounds are rename your function call to match. That won't run into the issue. However, if you're like me and wanted to keep the function name distinct and in snake-case, either override and re-implement the rpc method or fork spyne and edit this line: https://github.com/arskom/spyne/blob/599d80f571f8b8a18d206c0bbb0953392a603e1c/spyne/decorator.py#L493C24-L493C24 to take operation_name instead of function_name. This too won't run into the collision issue.

I'm unsure if the latter is desired as PR, so I'll just leave the idea out here.