With regards to Spyne Models and Native Python Types, let's assume I have two models, Company
and Employee
:
# server.py
from spyne import (
Iterable, ComplexModel, Unicode, Integer,
)
class Employee(ComplexModel):
name = Unicode
salary = Integer
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Company(ComplexModel):
name = Unicode
employees = Iterable(Employee)
def __init__(self, name, employees):
self.name = name
self.employees = employees
Now I can create a web service that returns this data:
# server.py
from spyne import (
Application, ServiceBase, rpc
)
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
from wsgiref.simple_server import make_server
import logging
class Service(ServiceBase):
@rpc(_returns=Company)
def get_company(ctx):
company = {
"name": "My Company",
"employees": [
Employee("Me", 0),
Employee("My friend", 10000),
]
}
return Company(**company)
application = Application(
[Service],
"targetnamespace",
in_protocol=Soap11(validator="lxml"),
out_protocol=Soap11(),
)
wsgi_application = WsgiApplication(application)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
logging.getLogger("spyne.protocol.xml").setLevel(logging.DEBUG)
logging.info("Listening to http://127.0.0.1:8000")
logging.info("WSDL is at: http://localhost:8000/?wsdl")
server = make_server("127.0.0.1", 8000, wsgi_application)
server.serve_forever()
I run this server and it waits for requests:
$ python server.py
INFO:root:Listening to http://127.0.0.1:8000
INFO:root:WSDL is at: http://localhost:8000/?wsdl
Now I can use zeep
to send a request:
# client.py
from zeep.client import Client
client = Client("http://localhost:8000/?wsdl")
service = client.service
response = service.get_company()
print(response)
And the result is just as expected:
$ python client.py
{
'name': 'My Company',
'employees': {
'Employee': [
{
'name': 'Me',
'salary': 0
},
{
'name': 'My friend',
'salary': 10000
}
]
}
}
Now, since Employee
belongs to Company
, it makes sense for it to be inside the company's class, and it's cleaner and more maintainable:
class Company(ComplexModel):
class Employee(ComplexModel):
name = Unicode
salary = Integer
def __init__(self, name, salary):
self.name = name
self.salary = salary
name = Unicode
employees = Iterable(Employee)
def __init__(self, name, employees):
self.name = name
self.employees = employees
But now when I create the service:
class Service(ServiceBase):
@rpc(_returns=Company)
def get_company(ctx):
company = {
"name": "My Company",
"employees": [
Company.Employee("Me", 0),
Company.Employee("My friend", 10000),
]
}
return Company(**company)
The client (python client.py
) gives this error:
TypeError: 'NoneType' object is not iterable
And the server (python server.py
) gives this error:
TypeError: Argument must be bytes or unicode, got 'ModelBaseMeta'
How can I fix this?
You don't. This is not supposed to work.
The following:
class Company(ComplexModel):
class Employee(ComplexModel):
# ...
# ...
is the same as the following pseudocode:
class EmployeeTopLevel(ComplexModel):
# ...
class Company(ComplexModel):
Employee = EmployeeTopLevel
# ...
You now probably see why this is a bad idea. ModelBase
metaclass does not support nesting of ModelBase children. You are trying to use classes for what packages and modules were designed to do. You should define your classes at the module level.
Here's a workaround though:
class Company(ComplexModel):
class Employee(ComplexModel):
name = Unicode
salary = Integer
def __init__(self, name, salary):
self.name = name
self.salary = salary
_type_info = [
('name', Unicode),
('employees', Iterable(Employee)),
]
def __init__(self, name, employees):
self.name = name
self.employees = employees
This works because it skips the field detection logic of the model metaclass and uses directly the user-supplied _type_info
list.