Documented most of the api

This commit is contained in:
Christophe de Vienne 2011-10-11 16:15:13 +02:00
parent f3e8a2435b
commit 22f9ea3d67
9 changed files with 283 additions and 19 deletions

View File

@ -1,8 +1,39 @@
API
===
.. autoclass:: wsme.controller.WSRoot
Public API
----------
:mod:`wsme` -- Essentials
~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsme
.. autoclass:: wsme.expose
.. autoclass:: wsme.validate
.. autoclass:: wsme.wsproperty
.. autoclass:: wsme.wsattr
Internals
---------
:mod:`wsme.types` -- Types
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: wsme.types
:members: register_type
:mod:`wsme.controller` -- Controller
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: wsme.controller
:members: scan_api, FunctionArgument, FunctionDefinition, WSRoot
:mod:`wsme.rest` -- REST protocol commons
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: wsme.rest
:members:

View File

@ -4,14 +4,16 @@ Changes
next
----
* Add specialised WSRoot classes for easy integration as a
WSGI Application (:class:`wsme.wsgi.WSRoot`) or a
TurboGears 1.x controller (:class:`wsme.tg1.WSRoot`).
* Add specialised WSRoot classes for easy integration as a
WSGI Application (:class:`wsme.wsgi.WSRoot`) or a
TurboGears 1.x controller (:class:`wsme.tg1.WSRoot`).
* Improve the documentation.
0.1.0a2 (2011-10-07)
--------------------
* Added support for arrays in all the protocols
* Added support for arrays in all the protocols
0.1.0a1 (2011-10-04)
--------------------

View File

@ -25,7 +25,7 @@ sys.path.insert(0, os.path.abspath('..'))
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@ -92,7 +92,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'agogo'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@ -215,3 +215,6 @@ man_pages = [
('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation',
[u'Christophe de Vienne'], 1)
]
autodoc_member_order = 'bysource'

View File

@ -1,20 +1,23 @@
Protocols
=========
REST+Json
---------
:mod:`wsme.protocols.restjson` -- REST+Json
-------------------------------------------
.. autoclass:: wsme.protocols.restjson.RestJsonProtocol
.. automodule:: wsme.protocols.restjson
:members:
REST+XML
--------
:mod:`wsme.protocols.restxml` -- REST+XML
-----------------------------------------
.. autoclass:: wsme.protocols.restxml.RestXmlProtocol
.. automodule:: wsme.protocols.restxml
:members:
SOAP
----
:mod:`wsme.protocols.soap` -- SOAP
----------------------------------
.. autoclass:: wsme.protocols.soap.SoapProtocol
.. automodule:: wsme.protocols.soap
:members:

View File

@ -32,6 +32,10 @@ html_body = """
def scan_api(controller, path=[]):
"""
Recursively iterate a controller api entries, while setting
their :attr:`FunctionDefinition.path`.
"""
for name in dir(controller):
if name.startswith('_'):
continue
@ -50,25 +54,56 @@ def scan_api(controller, path=[]):
class FunctionArgument(object):
"""
An argument definition of an api entry
"""
def __init__(self, name, datatype, mandatory, default):
#: argument name
self.name = name
#: Data type
self.datatype = datatype
#: True if the argument is mandatory
self.mandatory = mandatory
#: Default value if argument is omitted
self.default = default
class FunctionDefinition(object):
"""
An api entry definition
"""
def __init__(self, func):
#: Function name
self.name = func.__name__
#: Function documentation
self.doc = func.__doc__
#: Return type
self.return_type = None
#: The function arguments (list of :class:`FunctionArgument`)
self.arguments = []
#: True if this function is exposed by a protocol and not in
#: the api tree, which means it is not part of the api.
self.protocol_specific = False
#: Override the contenttype of the returned value.
#: Make sense only with :attr:`protocol_specific` functions.
self.contenttype = None
#: Path of the function in the api tree.
self.path = None
@classmethod
def get(cls, func):
"""
Returns the :class:`FunctionDefinition` of a method.
"""
fd = getattr(func, '_wsme_definition', None)
if fd is None:
fd = FunctionDefinition(func)
@ -76,6 +111,9 @@ class FunctionDefinition(object):
return fd
def get_arg(self, name):
"""
Returns a :class:`FunctionArgument` from its name
"""
for arg in self.arguments:
if arg.name == name:
return arg
@ -88,6 +126,18 @@ def register_protocol(protocol):
class expose(object):
"""
Decorator that expose a function.
:param return_type: Return type of the function
Example::
class MyController(object):
@expose(int)
def getint(self):
return 1
"""
def __init__(self, return_type=None):
self.return_type = return_type
register_type(return_type)
@ -113,8 +163,20 @@ class pexpose(object):
class validate(object):
def __init__(self, *args, **kw):
self.param_types = args
"""
Decorator that define the arguments types of a function.
Example::
class MyController(object):
@expose(str)
@validate(datetime.date, datetime.time)
def format(self, d, t):
return d.isoformat() + ' ' + t.isoformat()
"""
def __init__(self, *param_types):
self.param_types = param_types
def __call__(self, func):
fd = FunctionDefinition.get(func)
@ -133,6 +195,12 @@ class validate(object):
class WSRoot(object):
"""
Root controller for webservices.
:param protocols: A list of protocols to enable (see :meth:`addprotocol`)
:param webpath: The web path where the webservice is published.
"""
def __init__(self, protocols=[], webpath=''):
self._debug = True
self._webpath = webpath
@ -143,12 +211,23 @@ class WSRoot(object):
self._api = None
def addprotocol(self, protocol):
"""
Enable a new protocol on the controller.
:param protocol: A registered protocol name or an instance
of a protocol.
"""
if isinstance(protocol, str):
protocol = registered_protocols[protocol]()
self.protocols[protocol.name] = protocol
protocol.root = weakref.proxy(self)
def getapi(self):
"""
Returns the api description.
:rtype: list of :class:`FunctionDefinition`
"""
if self._api is None:
self._api = [i for i in scan_api(self)]
return self._api

View File

@ -1,3 +1,6 @@
"""
REST+Json protocol implementation.
"""
import base64
import datetime
import decimal
@ -16,6 +19,20 @@ except ImportError:
@generic
def tojson(datatype, value):
"""
A generic converter from python to jsonify-able datatypes.
If a non-complex user specific type is to be used in the api,
a specific tojson should be added::
from wsme.protocol.restjson import tojson
myspecialtype = object()
@tojson.when_object(myspecialtype)
def myspecialtype_tojson(datatype, value):
return str(value)
"""
if wsme.types.iscomplex(datatype):
d = dict()
for name, attr in wsme.types.list_attributes(datatype):
@ -56,6 +73,21 @@ def datetime_tojson(datatype, value):
@generic
def fromjson(datatype, value):
"""
A generic converter from json base types to python datatype.
If a non-complex user specific type is to be used in the api,
a specific fromjson should be added::
from wsme.protocol.restjson import fromjson
class MySpecialType(object):
pass
@fromjson.when_object(MySpecialType)
def myspecialtype_fromjson(datatype, value):
return MySpecialType(value)
"""
if value is None:
return None
if wsme.types.iscomplex(datatype):
@ -98,6 +130,14 @@ def binary_fromjson(datatype, value):
class RestJsonProtocol(RestProtocol):
"""
REST+Json protocol.
.. autoattribute:: name
.. autoattribute:: dataformat
.. autoattribute:: content_types
"""
name = 'REST+Json'
dataformat = 'json'
content_types = [

View File

@ -20,6 +20,25 @@ time_re = re.compile(r'(?P<h>[0-2][0-9]):(?P<m>[0-5][0-9]):(?P<s>[0-6][0-9])')
@generic
def toxml(datatype, key, value):
"""
A generic converter from python to xml elements.
If a non-complex user specific type is to be used in the api,
a specific toxml should be added::
from wsme.protocol.restxml import toxml
myspecialtype = object()
@toxml.when_object(myspecialtype)
def myspecialtype_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
el.text = str(value)
return el
"""
el = et.Element(key)
if value is None:
el.set('nil', 'true')
@ -34,6 +53,23 @@ def toxml(datatype, key, value):
@generic
def fromxml(datatype, element):
"""
A generic converter from xml elements to python datatype.
If a non-complex user specific type is to be used in the api,
a specific fromxml should be added::
from wsme.protocol.restxml import fromxml
class MySpecialType(object):
pass
@fromxml.when_object(MySpecialType)
def myspecialtype_fromxml(datatype, element):
if element.get('nil', False):
return None
return MySpecialType(element.text)
"""
if element.get('nil', False):
return None
if wsme.types.iscomplex(datatype):
@ -130,6 +166,13 @@ def binary_fromxml(datatype, element):
class RestXmlProtocol(RestProtocol):
"""
REST+XML protocol.
.. autoattribute:: name
.. autoattribute:: dataformat
.. autoattribute:: content_types
"""
name = 'REST+XML'
dataformat = 'xml'
content_types = ['text/xml']

View File

@ -95,7 +95,22 @@ def make_soap_element(datatype, tag, value):
@generic
def tosoap(datatype, tag, value):
"""Converts a value into xml Element objects for inclusion in the SOAP
response output"""
response output (after adding the type to the type_registry).
If a non-complex user specific type is to be used in the api,
a specific toxml should be added::
from wsme.protocol.soap import tosoap, make_soap_element, type_registry
class MySpecialType(object):
pass
type_registry[MySpecialType] = 'xsd:MySpecialType'
@tosoap.when_object(MySpecialType)
def myspecialtype_tosoap(datatype, tag, value):
return make_soap_element(datatype, tag, str(value))
"""
return make_soap_element(datatype, tag, value)
@ -130,6 +145,12 @@ def None_tosoap(datatype, tag, value):
@generic
def fromsoap(datatype, el, ns):
"""
A generic converter from soap elements to python datatype.
If a non-complex user specific type is to be used in the api,
a specific fromsoap should be added.
"""
if el.get(nil_qn) == 'true':
return None
soaptype = el.get(type_qn)
@ -188,6 +209,13 @@ def binary_fromsoap(datatype, el, ns):
class SoapProtocol(object):
"""
REST+XML protocol.
.. autoattribute:: name
.. autoattribute:: dataformat
.. autoattribute:: content_types
"""
name = 'SOAP'
content_types = ['application/soap+xml']

View File

@ -19,6 +19,20 @@ def iscomplex(datatype):
class wsproperty(property):
"""
A specialised :class:`property` to define typed-property on complex types.
Example::
class MyComplexType(object):
def get_aint(self):
return self._aint
def set_aint(self, value):
assert avalue < 10 # Dummy input validation
self._aint = value
aint = wsproperty(int, get_aint, set_aint, mandatory=True)
"""
def __init__(self, datatype, fget, fset=None,
mandatory=False, doc=None):
property.__init__(self, fget, fset)
@ -27,6 +41,16 @@ class wsproperty(property):
class wsattr(object):
"""
Complex type attribute definition when the datatype only is not
enough.
Example::
class MyComplexType(object):
optionalvalue = int
mandatoryvalue = wsattr(int, mandatory=True)
"""
def __init__(self, datatype, mandatory=False):
self.datatype = datatype
self.mandatory = mandatory
@ -100,6 +124,14 @@ def inspect_class(class_):
def register_type(class_):
"""
Make sure a type is registered.
It is automatically called by :class:`expose() <wsme.expose>`
and :class:`validate() <wsme.validate>`.
Unless you want to control when the class inspection is done there
is no need to call it.
"""
if class_ is None or \
class_ in native_types or \
hasattr(class_, '_wsme_attributes'):
@ -118,6 +150,9 @@ def register_type(class_):
def list_attributes(class_):
"""
Returns a list of a complex type attributes.
"""
if not hasattr(class_, '_wsme_attributes'):
register_type(class_)
return class_._wsme_attributes