Previous topic

Next topic

This Page

Quick search

Primitive: These are basic models that can contain a single value at a
time. Primitive types are more-or-less present in any programming language
and all of them map to a well-defined Python type. Types like Integer,
Decimal, Float, String, Date are all primitives. See the
documentation for spyne.model.primitive for more info.

Enum: Custom types that can take only a pre-determined number of values.
It’s also possible to get enum-like behavior with primitives as well. Enums
are defined using the spyne.model.enum.Enum class.

Binary: Binary types are used to represent arbitrarily-long byte streams.
There are two binary types in Spyne: The
spyne.model.binary.ByteArray and the
spyne.model.binary.File class. While any sequence of str
instances can be used as ByteArray, the File only works with a
File.Value instance. Please see the relevant example below for a more
thorough explanation.

Fault: When an exception is thrown from the user code, it’s serialized
and returned to the client as a spyne.model.fault.Fault. If it is
not a subclass of Fault, the client will probably see this
as an internal error. Some of the most common exceptions that a web service
might need to throw can be found in the spyne.error module.

Before going into detail about each category of models, we will first talk
about an operation that applies to all models: Type Customization.

Model customization is how one adds declarative restrictions and other metadata
to a Spyne model. This model metadata is stored in a generic object called
Attributes. Every Spyne model has this object as a class attribute.

As an example, let’s customize the vanilla Unicode type to accept only
valid email strings:

Numbers are organized in a hierarchy, with the
spyne.model.primitive.Decimal type at the top.
In its vanilla state, the Decimal class is the arbitrary-precision,
arbitrary-size generic number type that will accept just any decimal
number.

For integers, we recommend you to use bounded types like
spyne.model.primitive.UnsignedInteger32 which can only contain a
32-bit unsigned integer. (Which is very popular as e.g. a primary key type
in a relational database.)

For floating-point numbers, use the Decimal type with a pre-defined scale
and precision. E.g. Decimal(16,4) can represent a 16-digit number in total
which can have up to 4 decimal digits, which could be used e.g. as a nice
monetary type. [1]

Note that it is your responsibility to make sure that the scale and precision
constraints are consistent with the values in the context of the decimal
package. See the decimal.getcontext() documentation for more
information.

It’s also possible to set range constraints (Decimal(gt=4,lt=10)) or
discrete values (UnsignedInteger8(values=[2,4,6,8]). Please see the
spyne.model.primitive documentation for more details regarding number
handling in Spyne.

There are two string types in Spyne: spyne.model.primitive.Unicode and
spyne.model.primitive.String whose native types are unicode and
str respectively.

Unlike the Python str, the Spyne String is not for arbitrary byte
streams.
You should not use it unless you are absolutely, positively sure that
you need to deal with text data with an unknown encoding.
In all other cases, you should just use the Unicode type. They actually
look the same from outside, this distinction is made just to properly deal
with the quirks surrounding Python-2’s unicode type.

Remember that you have the ByteArray and File types at your disposal
when you need to deal with arbitrary byte streams.

The String type will be just an alias for Unicode
once Spyne gets ported to Python 3. It might even be deprecated and removed in
the future, so make sure you are using either Unicode or ByteArray in
your interface definitions.

File, ByteArray, Unicode and String are all arbitrary-size in
their vanilla versions. Don’t forget to customize them with additional
restrictions when implementing public services.

Just like numbers, it’s also possible to place value-based constraints on
Strings (e.g. String(values=['red','green','blue']) ) but not lexical
constraints.

See also the configuration parameters of your favorite transport for more
information on request size restriction and other precautions against
potential abuse.

Spyne comes with six basic spatial types that are supported by popular packages
like PostGIS and
Shapely.

These are provided as Unicode subclasses that just define proper
constraints to force the incoming string to be compliant with the
Well known text (WKT)
format. Well known binary (WKB) format is not (yet?) supported.

The incoming types are not parsed, but you can use shapely.wkb.loads()
function to convert them to native geometric types.

There are types defined for convenience in the Xml Schema standard which are
just convenience types on top of the text types. They are implemented as they
are needed by Spyne users. The following are some of the more notable ones.

The spyne.model.enum.Enum type mimics the enum in C/C++ with some
additional type safety. It’s part of the Spyne’s SOAP heritage so its being
there is mostly for compatibility reasons. If you want to use it, go right
ahead, it will work. But you can get the same functionality by defining a
custom Unicode type via:

SomeUnicode=Unicode(values=['x','y','z'])

The equivalent Enum-based declaration would be as follows:

SomeEnum=Enum('x','y','z',type_name="SomeEnum")

These to would be serialized the same, yet their API is different. Lets look at
the following class definition:

Dealing with binary data has traditionally been a weak spot of most of the
serialization formats in use today. The best XML or MIME (email) does is
either base64 encoding or something similar, Json has no clue about binary
data (and many other things actually, but let’s just not go there now) and
SOAP, in all its bloatiness, has quite a few binary encoding options
available, yet none of the “optimized” ones are implemented in Spyne [2].

Spyne supports binary data on all of the protocols it implements, falling back
to base64 encoding where necessary. In terms of message size, the efficient
protocols are MessagePack and good old Http. But, as
MessagePack does not offer an incremental parsing/generation API in its Python
wrapper, (in other words, it’s not possible to parse the message without
having it all in memory) it’s best to use the
spyne.protocol.http.HttpRpc protocol when dealing with arbitrary-size
binary data.

A few points to consider:

HttpRpc only works with a Http transport.

HttpRpc supports only one file per request.

Not every http transport supports incremental parsing of incoming data.
(e.g. Twisted). Make sure to test your stack end-to-end to see how it
handles huge messages [3].

Now that all that is said, let’s look at the API that Spyne provides for
dealing with binary data.

Spyne offers two types:

spyne.model.binary.ByteArray is a simple type that contains
arbitrary data. It’s similar to Python’s own str in terms of
functionality, but it’s a sequence of str instances instead of just a
big str to be able to handle data in chunks using generators when
needed [4].

spyne.model.binary.File is a quirkier type that is mainly used to
deal with Http way of dealing with file uploads. Its native value is the
File.Value instance in spyne.model.binary.File. See its
documentation for more information.

Dealing with binary data with Spyne is not that hard – you just need to make
sure your data is parsed incrementally when you’re preparing to deal with
arbitrary-size binary data, which means you need to do careful testing as
different WSGI implementations behave differently.

If you want to use Python keywords as field names, or need leading underscores
in field names, or you just want your Spyne definition and other code to be
separate, you can do away with the metaclass magic and do this:

However, you still won’t get predictable field order, as you’re just assigning
a dict to the _type_info attribute. If you also need that, (which
becomes handy when you serialize your return value directly to HTML) you need
to pass a sequence of (field_name,field_type) tuples, like so:

The User can have an infinite number of permissions. If you need to put a
limit to that, you can do this:

permissions=Array(Permission.customize(max_occurs=15))

It is important to emphasize once more that Spyne restrictions are only
enforced for an incoming request when validation is enabled. If you want this
enforcement for every assignment, you do this the usual way by writing a
property setter.

It is equivalent to the Array type from an interface perspective –
the client will not notice any difference between an Iterable and an
Array as return type.

It’s just meant to signal the internediate machinery that the return value
could be a generator and must not be consumed unless returning data to
the client. This comes in handy for, e.g. custom loggers because they should
not try to log the return value (as that would mean consuming the generator).

You could use the Iterable marker in other places instead of Array
without any problems, but it’s really meant to be used as return types in
function definitions.

The second alternative to the Array notation is the following:

permissions=Permission.customize(max_occurs='unbounded')

The native value that you should return for both remain the same: a sequence
of the designated type. However, the exposed interface is slightly different
for Xml and friends (other protocols that ship with Spyne always assume the
second notation).

When you use Array, what really happens is that the customize() function
of the array type creates an in-place class that is equivalent to the
following:

When working with functions, you don’t need to return instances of the
CompexModel subclasses themselves. Anything that walks and quacks like the
designated return type will work just fine. Specifically, the returned object
should return appropriate values on getattr()s for field names in the
return type. Any exceptions thrown by the object’s __getattr__ method will
be logged and ignored.

However, it is important to return instances and not classes themselves. Due
to the way Spyne serialization works, the classes themselves will also work as
return values until you actually seeing funky responses under load in
production. Don’t do this! [5]

When implementing public Spyne services, the recommendation is to raise
instances of Fault subclasses for client errors, and let other exceptions
bubble up until they get logged and re-raised as server-side errors by the
protocol handlers.

Not all protocols and transports care about distinguishing client and server
exceptions. Http has 4xx codes for client-side (invalid request case) errors
and 5xx codes for server-side (legitimate request case) errors. SOAP uses
“Client.” and “Server.” prefixes in error codes to make this distinction.

To integrate common transport and protocol behavior easily to Spyne, some
common exceptions are defined in the spyne.error module. These are
then hardwired to some common Http response codes so that e.g. raising a
ResourceNotFoundError ends up setting the response code to 404.

You can look at the source code of the
spyne.protocol.ProtocolBase.fault_to_http_response_code() to see which
exceptions correspond to which return codes. This can be extended easily by
subclassing your transport and overriding the fault_to_http_response_code
function with your own version.

By the way, Spyne does not include types like
ISO-4217-compliant
‘currency’ and ‘monetary’ types. (See
http://www.w3.org/TR/2001/WD-xforms-20010608/slice4.html for more
information.) They are actually really easy to
implement. If you’re looking for a simple way to contribute, this would
be a nice place to start! Patches are welcome!

Technically, a simple str instance is also a sequence of str
instances. However, using a str as the value to ctx.out_string
would cause sending data in one-byte chunks, which is very inefficient.
See e.g. how HTTP’s chunked encoding works.