Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: Ib3976cdfab839242694ce603451ef38407650721
This commit is contained in:
Tony Breeds
2017-09-12 16:13:46 -06:00
parent 9f84e4c7c5
commit 30a526a8b3
105 changed files with 14 additions and 16098 deletions

29
.gitignore vendored
View File

@@ -1,29 +0,0 @@
*.pyc
*.pyo
*.egg-info
*.swp
*.orig
~*
*~
.coverage*
coverage*.xml
.noseids
.tox
.test_sphinxext
nosetests*.xml
build
dist
doc/_build
d2to1-*.egg
WSME.egg-info/
# Files created by pbr
AUTHORS
ChangeLog
pbr*.egg
# Cross-test logs
cross-test-*.log

View File

@@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/wsme.git

14
.hgtags
View File

@@ -1,14 +0,0 @@
2bd203a084dcc785257b35e7231b2021722f60de 0.1.0a1
0eae00db9384d52cc4a82c09ab207d631ecb82e4 0.1.0a2
86466da44f44a97b379c7b8e94c371526be0eb9f 0.1.0a3
b38c56a2b9130d8fade7be22c8ac66a45fa77a6e 0.1.0a4
b0019e486c807bafe412ebaa6eb9bd9ab656c81c 0.1.0
c17de432c1857cfa059816d0db332bcdabea0c82 0.1.1
cfb5efc624f55710c987c7795501f3dd44a01078 0.2.0
ebe2c6f228ad4a365fbda9418f3e113d542390f0 0.3b1
d5eab01bf49192df2e0f24c78ca4936073e45b19 0.3b2
603c8586b076f5cf9b70b6cd82578dba7226e0c7 0.3
5ad01afed8779bb5a384802a2ec7d6ed0186c7d5 0.4b1
f06e004ca8e4013bf94df0cdade23b01742b0ec0 0.4
359199eb4e0999b5920eadfa40038013cd360df6 0.5b1
d3e5eee0b150048762169ff20ee25b43aa0369fa 0.5b2

19
LICENSE
View File

@@ -1,19 +0,0 @@
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14
README Normal file
View File

@@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@@ -1,112 +0,0 @@
Web Services Made Easy
======================
Introduction
------------
Web Services Made Easy (WSME) simplifies the writing of REST web services
by providing simple yet powerful typing, removing the need to directly
manipulate the request and the response objects.
WSME can work standalone or on top of your favorite Python web
(micro)framework, so you can use both your preferred way of routing your REST
requests and most of the features of WSME that rely on the typing system like:
- Alternate protocols, including those supporting batch-calls
- Easy documentation through a Sphinx_ extension
WSME is originally a rewrite of TGWebServices
with a focus on extensibility, framework-independance and better type handling.
How Easy ?
~~~~~~~~~~
Here is a standalone wsgi example::
from wsme import WSRoot, expose
class MyService(WSRoot):
@expose(unicode, unicode) # First parameter is the return type,
# then the function argument types
def hello(self, who=u'World'):
return u"Hello {0} !".format(who)
ws = MyService(protocols=['restjson', 'restxml', 'soap'])
application = ws.wsgiapp()
With this published at the ``/ws`` path of your application, you can access
your hello function in various protocols:
.. list-table::
:header-rows: 1
* - URL
- Returns
* - ``http://<server>/ws/hello.json?who=you``
- ``"Hello you !"``
* - ``http://<server>/ws/hello.xml``
- ``<result>Hello World !</result>``
* - ``http://<server>/ws/api.wsdl``
- A WSDL description for any SOAP client.
Main features
~~~~~~~~~~~~~
- Very simple API.
- Supports user-defined simple and complex types.
- Multi-protocol : REST+Json, REST+XML, SOAP, ExtDirect and more to come.
- Extensible : easy to add more protocols or more base types.
- Framework independence : adapters are provided to easily integrate
your API in any web framework, for example a wsgi container,
Pecan_, TurboGears_, Flask_, cornice_...
- Very few runtime dependencies: webob, simplegeneric. Optionnaly lxml and
simplejson if you need better performances.
- Integration in `Sphinx`_ for making clean documentation with
``wsmeext.sphinxext``.
.. _Pecan: http://pecanpy.org/
.. _TurboGears: http://www.turbogears.org/
.. _Flask: http://flask.pocoo.org/
.. _cornice: http://pypi.python.org/pypi/cornice
Install
~~~~~~~
::
pip install WSME
or, if you do not have pip on your system or virtualenv
::
easy_install WSME
Changes
~~~~~~~
- Read the `Changelog`_
Getting Help
~~~~~~~~~~~~
- Read the `WSME Documentation`_.
- Questions about WSME should go to the `python-wsme mailinglist`_.
Contribute
~~~~~~~~~~
* Documentation: http://packages.python.org/WSME/
* Source: http://git.openstack.org/cgit/openstack/wsme
* Bugs: https://bugs.launchpad.net/wsme/+bugs
* Code review: https://review.openstack.org/#/q/project:openstack/wsme,n,z
.. _Changelog: http://packages.python.org/WSME/changes.html
.. _python-wsme mailinglist: http://groups.google.com/group/python-wsme
.. _WSME Documentation: http://packages.python.org/WSME/
.. _WSME issue tracker: https://bugs.launchpad.net/wsme/+bugs
.. _Sphinx: http://sphinx.pocoo.org/

View File

@@ -1,134 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
ziphtml: html
rm -f $(BUILDDIR)/wsme-documentation.zip
cd $(BUILDDIR)/html && zip -r ../wsme-documentation.zip .
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/WebServicesMadeEasy"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WebServicesMadeEasy"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@@ -1,10 +0,0 @@
dl.toggle dt {
background-color: #eeffcc;
border: 1px solid #ac9;
display: inline;
}
dl.toggle dd {
display: none;
}

View File

@@ -1,9 +0,0 @@
/*global $,document*/
$(document).ready(function () {
"use strict";
$("dl.toggle > dt").click(
function (event) {
$(this).next().toggle(250);
}
);
});

27
doc/_static/wsme.css vendored
View File

@@ -1,27 +0,0 @@
@import "agogo.css";
table.docutils {
margin: 0;
padding: 0;
border: 1;
}
table.docutils th {
margin: 0;
padding: 0;
border: 0;
}
table.docutils thead tr {
}
table.docutils td {
margin: 0;
padding: 0;
border: 0;
}
table.docutils tr.row-odd {
background: #EEEEEC;
}

View File

@@ -1,45 +0,0 @@
API
===
Public API
----------
:mod:`wsme` -- Essentials
~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsme
.. autoclass:: signature([return_type, [arg0_type, [arg1_type, ... ] ] ], body=None, status_code=None)
.. autoclass:: wsme.types.Base
.. autoclass:: wsattr
.. autoclass:: wsproperty
.. data:: Unset
Default value of the complex type attributes.
.. autoclass:: WSRoot
:members:
Internals
---------
:mod:`wsme.types` -- Types
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: wsme.types
:members: register_type
:mod:`wsme.api` -- API related api
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: wsme.api
:members: FunctionArgument, FunctionDefinition
:mod:`wsme.rest.args` -- REST protocol argument handling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: wsme.rest.args
:members:

View File

@@ -1,433 +0,0 @@
Changes
=======
0.8.0 (2015-08-25)
------------------
Changes that may break your app:
* Returns 400 if unexpected attributes are added to complex types (#1277571).
Other changes:
* Returns 415 when Content-Type is invalid (#1419110)
* Returns 400 if a complex input type is not a json object (#1423634)
* Fix error reports with ArrayType and DictType invalid inputs (#1428185, #1428628)
* Update README
0.7.0 (2015-05-13)
------------------
* Ensure UserType objects are converted to basetype
* Convert built-in types when passed as strings
* Multiple protocol accept or content-type matching
* Raise an InvalidInput if you get a ValueError from JSON data
* Remove unsupported python versions from setup.cfg
* Clean up setup.py and add requirements.txt
* Add full MIT license
* Fix i18n when formatting exception
* Cleanup up logging
* Make it possible to use the Response to return a non-default return type
* several fixes for SOAP protocol
0.6.4 (2014-11-20)
------------------
- Include tests in the source distribution
0.6.3 (2014-11-19)
------------------
- Disable universal wheels
0.6.2 (2014-11-18)
------------------
* Flask adapter complex types now supports flask.ext.restful
* Allow disabling complex types auto-register
* Documentation edits
* Various documentation build fixes
* Fix passing Dict and Array based UserType as params
0.6.1 (2014-05-02)
------------------
* Fix error: variable 'kw' referenced before assignment
* Fix default handling for zero values
* Fixing spelling mistakes
* A proper check of UuidType
* pecan: cleanup, use global vars and staticmethod
* args_from_args() to work with an instance of UserType
0.6 (2014-02-06)
----------------
* Add 'readonly' parameter to wsattr
* Fix typos in documents and comments
* Support dynamic types
* Support building wheels (PEP-427)
* Fix a typo in the types documentation
* Add IntegerType and some classes for validation
* Use assertRaises() for negative tests
* Remove the duplicated error message from Enum
* Drop description from 403 flask test case
* Fix SyntaxWarning under Python 3
0.5b6 (2013-10-16)
------------------
* Add improved support for HTTP response codes in cornice apps.
* Handle mandatory attributes
* Fix error code returned when None is used in an Enum
* Handle list and dict for body type in REST protocol
* Fix Sphinx for Python 3
* Add custom error code to ClientSideError
* Return a ClientSideError if unable to convert data
* Validate body when using Pecan
0.5b5 (2013-09-16)
------------------
More packaging fixes.
0.5b4 (2013-09-11)
------------------
Fixes some release-related files for the stackforge release process.
No user-facing bug fixes or features over what 0.5b3 provides.
0.5b3 (2013-09-04)
------------------
The project moved to stackforge. Mind the new URLs for the repository, bug
report etc (see the documentation).
* Allow non-default status code return with the pecan adapter
(Angus Salked).
* Fix returning objects with object attributes set to None on rest-json
& ExtDirect.
* Allow error details to be set on the Response object (experimental !).
* Fix: Content-Type header is not set anymore when the return type is None
on the pecan adapter.
* Support unicode message in ClientSideError (Mehdi Abaakouk).
* Use pbr instead of d2to1 (Julien Danjou).
* Python 3.3 support (Julien Danjou).
* Pecan adapter: returned status can now be set on exceptions (Vitaly
Kostenko).
* TG adapters: returned status can be set on exceptions (Ryan
Petrello).
* six >= 1.4.0 support (Julien Danjou).
* Require ordereddict from pypi for python < 2.6 (Ryan Petrello).
* Make the code PEP8 compliant (Ryan Petrello).
0.5b2 (2013-04-18)
------------------
* Changed the way datas of complex types are stored. In previous versions, an
attribute was added to the type for each attribute, its name being the
attribute name prefixed with '_'.
Starting with this version, a single attribute _wsme_dataholder is added to
the instance.
The motivation behind this change is to avoid adding too many attributes to
the object.
* Add a special type 'HostRequest' that allow a function to ask for the host
framework request object in its arguments.
* Pecan adapter: Debug mode (which returns the exception tracebacks to the
client) can be enabled by the pecan application configuration.
* New adapter: wsmeext.flask, for the Flask_ framework.
.. _Flask: http://flask.pocoo.org/
* Fix: the cornice adapter was not usable.
* Fix: Submodules of wsmeext were missing in the packages.
* Fix: The demo app was still depending on the WSME-Soap package (which has
been merged into WSME in 0.5b1).
* Fix: A function with only on 'body' parameter would fail when being called.
* Fix: Missing arguments were poorly reported by the frameworks adapters.
0.5b1 (2013-01-30)
------------------
* Introduce a new kind of adapters that rely on the framework routing.
Adapters are provided for Pecan, TurboGears and cornice.
* Reorganised the rest protocol implementation to ease the implementation of
adapters that rely only on the host framework routing system.
* The default rest ``@expose`` decorator does not wrap the decorated function
anymore. If needed to expose a same function several times, a parameter
``multiple_expose=True`` has been introduced.
* Remove the wsme.release module
* Fix == operator on ArrayType
* Adapted the wsme.sphinxext module to work with the function exposed by the
``wsme.pecan`` adapter.
* Allow promotion of ``int`` to ``float`` on float attributes (Doug Hellman)
* Add a ``samples_slot`` option to the ``.. autotype`` directive to
choose where the data samples whould be inserted (Doug Hellman).
* Add ``sample()`` to ArrayType and DictType (Doug Hellman).
* New syntax for object arrays as GET parameters, without brackets. Ex:
``?o.f1=a&o.f1=b&o.f2=c&o.f2=d`` is an array of two objects:
[{'f1': 'a', 'f2': 'c']}, {'f1': 'b', 'f2': 'd']}.
* @signature (and its @wsexpose frontends) has a new parameter:
``ignore_extra_args``.
* Fix boolean as input type support in the soap implementation (Craig
McDaniel).
* Fix empty/nil strings distinction in soap (Craig McDaniel).
* Improved unittests code coverage.
* Ported the soap implementation to python 3.
* Moved non-core features (adapters, sphinx extension) to the ``wsmeext`` module.
* Change the GET parameter name for passing the request body as a parameter
is now from 'body' to '__body__'
* The soap, extdirect and sqlalchemy packages have been merged into the main
package.
* Changed the documentation theme to "Cloud".
0.4 (2012-10-15)
----------------
* Automatically converts unicode strings to/from ascii bytes.
* Use d2to1 to simplify setup.py.
* Implements the SPORE specification.
* Fixed a few things in the documentation
0.4b1 (2012-09-14)
------------------
* Now supports Python 3.2
* String types handling is clearer.
* New :class:`wsme.types.File` type.
* Supports cross-referenced types.
* Various bugfixes.
* Tests code coverage is now over 95%.
* RESTful protocol can now use the http method.
* UserTypes can now be given a name that will be used in the
documentation.
* Complex types can inherit :class:`wsme.types.Base`. They will
have a default constructor and be registered automatically.
* Removed the wsme.wsgi.adapt function if favor of
:meth:`wsme.WSRoot.wsgiapp`
Extensions
~~~~~~~~~~
wsme-soap
* Function names now starts with a lowercase letter.
* Fixed issues with arrays (issue #3).
* Fixed empty array handling.
wsme-sqlalchemy
This new extension makes it easy to create webservices on top
of a SQLAlchemy set of mapped classes.
wsme-extdirect
* Implements server-side DataStore
(:class:`wsmeext.extdirect.datastore.DataStoreController`).
* Add Store and Model javascript definition auto-generation
* Add Store server-side based on SQLAlchemy mapped classes
(:class:`wsmeext.extdirect.sadatastore.SADataStoreController`).
0.3 (2012-04-20)
----------------
* Initial Sphinx integration.
0.3b2 (2012-03-29)
------------------
* Fixed issues with the TG1 adapter.
* Now handle dict and UserType types as GET/POST params.
* Better handling of application/x-www-form-urlencoded encoded POSTs
in rest protocols.
* :class:`wsattr` now takes a 'default' parameter that will be returned
instead of 'Unset' if no value has been set.
0.3b1 (2012-01-19)
------------------
* Per-call database transaction handling.
* :class:`Unset` is now imported in the wsme module
* Attributes of complex types can now have a different name in
the public api and in the implementation.
* Complex arguments can now be sent as GET/POST params in the rest
protocols.
* The restjson protocol do not nest the results in an object anymore.
* Improved the documentation
* Fix array attributes validation.
* Fix date|time parsing errors.
* Fix Unset values validation.
* Fix registering of complex types inheriting form already
registered complex types.
* Fix user types, str and None values encoding/decoding.
0.2.0 (2011-10-29)
------------------
* Added batch-calls abilities.
* Introduce a :class:`UnsetType` and a :data:`Unset` constant
so that non-mandatory attributes can remain unset (which is
different from null).
* Fix: If a complex type was only used as an input type, it was
not registered.
* Add support for user types.
* Add an Enum type (which is a user type).
* The 'binary' type is now a user type.
* Complex types:
- Fix inspection of complex types with inheritance.
- Fix inspection of self-referencing complex types.
- wsattr is now a python Descriptor, which makes it possible
to retrieve the attribute definition on a class while
manipulating values on the instance.
- Add strong type validation on assignment (made possible by
the use of Descriptors).
* ExtDirect:
- Implements batch calls
- Fix None values conversion
- Fix transaction result : 'action' and 'method' were missing.
0.1.1 (2011-10-20)
------------------
* Changed the internal API by introducing a CallContext object.
It makes it easier to implement some protocols that have
a transaction or call id that has to be returned. It will also
make it possible to implement batch-calls in a later version.
* More test coverage.
* Fix a problem with array attribute types not being registered.
* Fix the mandatory / default detection on function arguments.
* Fix issues with the SOAP protocol implementation which should now
work properly with a suds client.
* Fix issues with the ExtDirect protocol implementation.
0.1.0 (2011-10-14)
------------------
* Protocol insertion order now influence the protocol selection
* Move the soap protocol implementation in a separate lib,
WSME-Soap
* Introduce a new protocol ExtDirect in the WSME-ExtDirect lib.
0.1.0a4 (2011-10-12)
--------------------
* Change the way framework adapters works. Now the adapter modules
have a simple adapt function that adapt a :class:`wsme.WSRoot`
instance. This way a same root can be integrated in several
framework.
* Protocol lookup now use entry points in the group ``[wsme.protocols]``.
0.1.0a3 (2011-10-11)
--------------------
* 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.
* More unit tests and code-coverage.
0.1.0a2 (2011-10-07)
--------------------
* Added support for arrays in all the protocols
0.1.0a1 (2011-10-04)
--------------------
Initial public release.

View File

@@ -1,259 +0,0 @@
# -*- coding: utf-8 -*-
#
# Web Services Made Easy documentation build configuration file, created by
# sphinx-quickstart on Sun Oct 2 20:27:45 2011.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# 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', 'sphinx.ext.viewcode', 'wsmeext.sphinxext',
'sphinx.ext.intersphinx']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Web Services Made Easy'
copyright = u'2011, Christophe de Vienne'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
import pkg_resources
wsmedist = pkg_resources.require('WSME')[0]
version = wsmedist.version
# The short X.Y version.
version = '.'.join(version.split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = 'agogo'
#html_theme_options = {
# "pagewidth": "60em",
# "documentwidth": "40em",
#}
#html_style = 'wsme.css'
import cloud_sptheme as csp
html_theme = 'cloud'
html_theme_path = [csp.get_theme_dir()]
html_theme_options = {
"roottarget": "index",
"googleanalytics_id": "UA-8510502-6"
}
# 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
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = "WSME %s" % release
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = "WSME"
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'WebServicesMadeEasydoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
latex_paper_size = 'a4'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation',
u'Christophe de Vienne', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
latex_preamble = '''
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
'''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation',
[u'Christophe de Vienne'], 1)
]
autodoc_member_order = 'bysource'
wsme_protocols = [
'restjson', 'restxml', 'soap', 'extdirect'
]
intersphinx_mapping = {
'python': ('http://docs.python.org/', None),
'six': ('http://packages.python.org/six/', None),
}
def setup(app):
# confval directive taken from the sphinx doc
app.add_object_type('confval', 'confval',
objname='configuration value',
indextemplate='pair: %s; configuration value')

View File

@@ -1,190 +0,0 @@
Document your API
=================
Web services without a proper documentation are usually useless.
To make it easy to document your own API, WSME provides a Sphinx_ extension.
Install the extension
---------------------
Here we consider that you already quick-started a sphinx project.
#. In your ``conf.py`` file, add ``'ext'`` to your extensions,
and optionally set the enabled protocols.
.. code-block:: python
extensions = ['ext']
wsme_protocols = ['restjson', 'restxml', 'extdirect']
#. Copy :download:`toggle.js <_static/toggle.js>`
and :download:`toggle.css <_static/toggle.css>`
in your _static directory.
The ``wsme`` domain
-------------------
The extension will add a new Sphinx domain providing a few directives.
Config values
~~~~~~~~~~~~~
.. confval:: wsme_protocols
A list of strings that are WSME protocol names. If provided by an
additional package (for example WSME-Soap or WSME-ExtDirect), that package must
be installed.
The types and services generated documentation will include code samples
for each of these protocols.
.. confval:: wsme_root
A string that is the full name of the service root controller.
It will be used
to determinate the relative path of the other controllers when they
are autodocumented, and calculate the complete webpath of the other
controllers.
.. confval:: wsme_webpath
A string that is the webpath where the :confval:`wsme_root` is mounted.
Directives
~~~~~~~~~~
.. rst:directive:: .. root:: <WSRoot full path>
Define the service root controller for this documentation source file.
To set it globally, see :confval:`wsme_root`.
A ``webpath`` option allows override of :confval:`wsme_webpath`.
Example:
.. code-block:: rst
.. wsme:root:: myapp.controllers.MyWSRoot
:webpath: /api
.. rst:directive:: .. service:: name/space/ServiceName
Declare a service.
.. rst:directive:: .. type:: MyComplexType
Equivalent to the :rst:dir:`py:class` directive to document a complex type
.. rst:directive:: .. attribute:: aname
Equivalent to the :rst:dir:`py:attribute` directive to document a complex type
attribute. It takes an additional ``:type:`` field.
Example
~~~~~~~
.. list-table::
:header-rows: 1
* - Source
- Result
* - .. code-block:: rst
.. wsme:root:: wsmeext.sphinxext.SampleService
:webpath: /api
.. wsme:type:: MyType
.. wsme:attribute:: test
:type: int
.. wsme:service:: name/space/SampleService
.. wsme:function:: doit
- .. wsme:root:: wsmeext.sphinxext.SampleService
:webpath: /api
.. wsme:type:: MyType
.. wsme:attribute:: test
:type: int
.. wsme:service:: name/space/SampleService
.. wsme:function:: getType
Returns a :wsme:type:`MyType <MyType>`
Autodoc directives
~~~~~~~~~~~~~~~~~~
Theses directives scan your code to generate the documentation from the
docstrings and your API types and controllers.
.. rst:directive:: .. autotype:: myapp.MyType
Generate the myapp.MyType documentation.
.. rst:directive:: .. autoattribute:: myapp.MyType.aname
Generate the myapp.MyType.aname documentation.
.. rst:directive:: .. autoservice:: myapp.MyService
Generate the myapp.MyService documentation.
.. rst:directive:: .. autofunction:: myapp.MyService.myfunction
Generate the myapp.MyService.myfunction documentation.
Full Example
------------
Python source
~~~~~~~~~~~~~
.. literalinclude:: ../wsmeext/sphinxext.py
:lines: 69-96
:language: python
Documentation source
~~~~~~~~~~~~~~~~~~~~
.. code-block:: rst
.. default-domain:: wsmeext
.. type:: int
An integer
.. autotype:: wsmeext.sphinxext.SampleType
:members:
.. autoservice:: wsmeext.sphinxext.SampleService
:members:
Result
~~~~~~
.. default-domain:: wsmeext
.. type:: int
An integer
.. autotype:: wsmeext.sphinxext.SampleType
:members:
.. autoservice:: wsmeext.sphinxext.SampleService
:members:
.. _Sphinx: http://sphinx.pocoo.org/

View File

@@ -1,204 +0,0 @@
Functions
=========
WSME is based on the idea that most of the time the input and output of web
services are actually strictly typed. It uses this idea to ease the
implementation of the actual functions by handling those input/output.
It also proposes alternate protocols on top of a proper REST api.
This chapter explains in detail how to 'sign' a function with WSME.
The decorators
--------------
Depending on the framework you are using, you will have to use either a
@\ :class:`wsme.signature` decorator or a @\ :class:`wsme.wsexpose` decorator.
@signature
~~~~~~~~~~
The base @\ :class:`wsme.signature` decorator defines the return and argument types
of the function, and if needed a few more options.
The Flask and Cornice adapters both propose a specific version of it, which
also wrap the function so that it becomes suitable for the host framework.
In any case, the use of @\ :class:`wsme.signature` has the same meaning: tell WSME what is the
signature of the function.
@wsexpose
~~~~~~~~~
The native Rest implementation, and the TG and Pecan adapters add a @\ :class:`wsme.wsexpose`
decorator.
It does what @\ :class:`wsme.signature` does, *and* exposes the function in the routing system
of the host framework.
This decorator is generally used in an object-dispatch routing context.
.. note::
Since both decorators play the same role, the rest of this
document will alway use @signature.
Signing a function
------------------
Signing a function is just a matter of decorating it with @signature:
.. code-block:: python
@signature(int, int, int)
def multiply(a, b):
return a * b
In this trivial example, we tell WSME that the 'multiply' function returns an
integer, and takes two integer parameters.
WSME will match the argument types by order to determine the exact type of each
named argument. This is important since most of the web service protocols don't
provide strict argument ordering but only named parameters.
Optional arguments
~~~~~~~~~~~~~~~~~~
Defining an argument as optional is done by providing a default value:
.. code-block:: python
@signature(int, int, int):
def increment(value, delta=1):
return value + delta
In this example, the caller may omit the 'delta' argument, and no
'MissingArgument' error will be raised.
Additionally, this argument will be documented as optional by the sphinx
extension.
Body argument
~~~~~~~~~~~~~
When defining a Rest CRUD API, we generally have a URL to which we POST data.
For example:
.. code-block:: python
@signature(Author, Author)
def update_author(data):
# ...
return data
Such a function will take at least one parameter, 'data', that is a structured
type. With the default way of handling parameters, the body of the request
would look like this:
.. code-block:: javascript
{
"data":
{
"id": 1,
"name": "Pierre-Joseph"
}
}
If you think (and you should) that it has one extra level of nesting, the 'body'
argument is here for you::
@signature(Author, body=Author)
def update_author(data):
# ...
return data
With this syntax, we can now post a simpler body:
.. code-block:: javascript
{
"id": 1,
"name": "Pierre-Joseph"
}
Note that this does not prevent the function from having multiple parameters; it just requires
the body argument to be the last:
.. code-block:: python
@signature(Author, bool, body=Author)
def update_author(force_update=False, data=None):
# ...
return data
In this case, the other arguments can be passed in the URL, in addition to the
body parameter. For example, a POST on ``/author/SOMEID?force_update=true``.
Status code
~~~~~~~~~~~
The default status codes returned by WSME are 200, 400 (if the client sends invalid
inputs) and 500 (for server-side errors).
Since a proper Rest API should use different return codes (201, etc), one can
use the 'status_code=' option of @signature to do so.
.. code-block:: python
@signature(Author, body=Author, status_code=201)
def create_author(data):
# ...
return data
Of course this code will only be used if no error occurs.
In case the function needs to change the status code on a per-request basis, it
can return a :class:`wsme.Response` object, allowing it to override the status
code:
.. code-block:: python
@signature(Author, body=Author, status_code=202)
def update_author(data):
# ...
response = Response(data)
if transaction_finished_and_successful:
response.status_code = 200
return response
Extra arguments
~~~~~~~~~~~~~~~
The default behavior of WSME is to reject requests that give extra/unknown
arguments. In some (rare) cases, this is undesirable.
Adding 'ignore_extra_args=True' to @signature changes this behavior.
.. note::
If using this option seems to solve your problem, please think twice
before using it!
Accessing the request
~~~~~~~~~~~~~~~~~~~~~
Most of the time direct access to the request object should not be needed, but
in some cases it is.
On frameworks that propose a global access to the current request it is not an
issue, but on frameworks like pyramid it is not the way to go.
To handle this use case, WSME has a special type, :class:`HostRequest`:
.. code-block:: python
from wsme.types import HostRequest
@signature(Author, HostRequest, body=Author)
def create_author(request, newauthor):
# ...
return newauthor
In this example, the request object of the host framework will be passed as the
``request`` parameter of the create_author function.

View File

@@ -1,15 +0,0 @@
Getting Started
===============
For now here is just a working example.
You can find it in the examples directory of the source distribution.
.. literalinclude:: ../examples/demo/demo.py
:language: python
When running this example, the following soap client can interrogate
the web services:
.. literalinclude:: ../examples/demo/client.py
:language: python

View File

@@ -1,27 +0,0 @@
.. include:: ../README.rst
Contents
--------
.. toctree::
:maxdepth: 2
gettingstarted
api
types
functions
protocols
integrate
document
todo
changes
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -1,342 +0,0 @@
Integrating with a Framework
============================
General considerations
----------------------
Using WSME within another framework providing its own REST capabilities is
generally done by using a specific decorator to declare the function signature,
in addition to the framework's own way of declaring exposed functions.
This decorator can have two different names depending on the adapter.
``@wsexpose``
This decorator will declare the function signature *and*
take care of calling the adequate decorators of the framework.
Generally this decorator is provided for frameworks that use
object-dispatch controllers, such as :ref:`adapter-pecan` and
:ref:`adapter-tg1`.
``@signature``
This decorator only sets the function signature and returns a function
that can be used by the host framework as a REST request target.
Generally this decorator is provided for frameworks that expect functions
taking a request object as a single parameter and returning a response
object. This is the case for :ref:`adapter-cornice` and
:ref:`adapter-flask`.
If you want to enable additional protocols, you will need to
mount a :class:`WSRoot` instance somewhere in the application, generally
``/ws``. This subpath will then handle the additional protocols. In a future
version, a WSGI middleware will probably play this role.
.. note::
Not all the adapters are at the same level of maturity.
WSGI Application
----------------
The :func:`wsme.WSRoot.wsgiapp` function of WSRoot returns a WSGI
application.
Example
~~~~~~~
The following example assumes the REST protocol will be entirely handled by
WSME, which is the case if you write a WSME standalone application.
.. code-block:: python
from wsme import WSRoot, expose
class MyRoot(WSRoot):
@expose(unicode)
def helloworld(self):
return u"Hello World !"
root = MyRoot(protocols=['restjson'])
application = root.wsgiapp()
.. _adapter-cornice:
Cornice
-------
.. _cornice: http://cornice.readthedocs.org/en/latest/
*"* Cornice_ *provides helpers to build & document REST-ish Web Services with
Pyramid, with decent default behaviors. It takes care of following the HTTP
specification in an automated way where possible."*
:mod:`wsmeext.cornice` -- Cornice adapter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsmeext.cornice
.. function:: signature
Declare the parameters of a function and returns a function suitable for
cornice (ie that takes a request and returns a response).
Configuration
~~~~~~~~~~~~~
To use WSME with Cornice you have to add a configuration option to your Pyramid application.
.. code-block:: python
from pyramid.config import Configurator
def make_app():
config = Configurator()
config.include("cornice")
config.include("wsmeext.cornice") # This includes WSME cornice support
# ...
return config.make_wsgi_app()
Example
~~~~~~~
.. code-block:: python
from cornice import Service
from wsmeext.cornice import signature
import wsme.types
hello = Service(name='hello', path='/', description="Simplest app")
class Info(wsme.types.Base):
message = wsme.types.text
@hello.get()
@signature(Info)
def get_info():
"""Returns Hello in JSON or XML."""
return Info(message='Hello World')
@hello.post()
@signature(None, Info)
def set_info(info):
print("Got a message: %s" % info.message)
.. _adapter-flask:
Flask
-----
*"Flask is a microframework for Python based on Werkzeug, Jinja 2 and good
intentions. And before you ask: It's BSD licensed! "*
.. warning::
Flask support is limited to function signature handling. It does not
support additional protocols. This is a temporary limitation, if you have
needs on that matter please tell us at python-wsme@googlegroups.com.
:mod:`wsmeext.flask` -- Flask adapter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsmeext.flask
.. function:: signature(return_type, \*arg_types, \*\*options)
See @\ :func:`signature` for parameters documentation.
Can be used on a function before routing it with flask.
Example
~~~~~~~
.. code-block:: python
from wsmeext.flask import signature
@app.route('/multiply')
@signature(int, int, int)
def multiply(a, b):
return a * b
.. _adapter-pecan:
Pecan
-----
*"*\ Pecan_ *was created to fill a void in the Python web-framework world
a very lightweight framework that provides object-dispatch style routing.
Pecan does not aim to be a "full stack" framework, and therefore includes
no out of the box support for things like sessions or databases. Pecan
instead focuses on HTTP itself."*
.. warning::
A pecan application is not able to mount another WSGI application on a
subpath. For that reason, additional protocols are not supported for now,
until WSME provides a middleware that can do the same as a mounted
WSRoot.
:mod:`wsmeext.pecan` -- Pecan adapter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsmeext.pecan
.. function:: wsexpose(return_type, \*arg_types, \*\*options)
See @\ :func:`signature` for parameters documentation.
Can be used on any function of a pecan
`RestController <http://pecan.readthedocs.org/en/latest/rest.html>`_
instead of the expose decorator from Pecan.
Configuration
~~~~~~~~~~~~~
WSME can be configured through the application configation, by adding a 'wsme'
configuration entry in ``config.py``:
.. code-block:: python
wsme = {
'debug': True
}
Valid configuration variables are :
- ``'debug'``: Whether or not to include exception tracebacks in the returned
server-side errors.
Example
~~~~~~~
The `example <http://pecan.readthedocs.org/en/latest/rest.html#nesting-restcontroller>`_ from the Pecan documentation becomes:
.. code-block:: python
from wsmeext.pecan import wsexpose
class BooksController(RestController):
@wsexpose(Book, int, int)
def get(self, author_id, id):
# ..
@wsexpose(Book, int, int, body=Book)
def put(self, author_id, id, book):
# ..
class AuthorsController(RestController):
books = BooksController()
.. _Pecan: http://pecanpy.org/
.. _adapter-tg1:
Turbogears 1.x
--------------
The TG adapters have an api very similar to TGWebServices. Migrating from it
should be straightforward (a little howto migrate would not hurt though, and it
will be written as soon as possible).
:mod:`wsmeext.tg11` -- TG 1.1 adapter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsmeext.tg11
.. function:: wsexpose(return_type, \*arg_types, \*\*options)
See @\ :func:`signature` for parameters documentation.
Can be used on any function of a controller
instead of the expose decorator from TG.
.. function:: wsvalidate(\*arg_types)
Set the argument types of an exposed function. This decorator is provided
so that WSME is an almost drop-in replacement for TGWebServices. If
starting from scratch you can use \ :func:`wsexpose` only
.. function:: adapt(wsroot)
Returns a TG1 controller instance that publish a :class:`wsme.WSRoot`.
It can then be mounted on a TG1 controller.
Because the adapt function modifies the cherrypy filters of the controller
the 'webpath' of the WSRoot instance must be consistent with the path it
will be mounted on.
:mod:`wsmeext.tg15` -- TG 1.5 adapter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. module:: wsmeext.tg15
This adapter has the exact same api as :mod:`wsmeext.tg11`.
Example
~~~~~~~
In a freshly quickstarted tg1 application (let's say, wsmedemo), you can add
REST-ish functions anywhere in your controller tree. Here directly on the root,
in controllers.py:
.. code-block:: python
# ...
# For tg 1.5, import from wsmeext.tg15 instead :
from wsmeext.tg11 import wsexpose, WSRoot
class Root(controllers.RootController):
# Having a WSRoot on /ws is only required to enable additional
# protocols. For REST-only services, it can be ignored.
ws = adapt(
WSRoot(webpath='/ws', protocols=['soap'])
)
@wsexpose(int, int, int)
def multiply(self, a, b):
return a * b
.. _TurboGears: http://www.turbogears.org/
Other frameworks
----------------
Bottle
~~~~~~
No adapter is provided yet but it should not be hard to write one, by taking
example on the cornice adapter.
This example only show how to mount a WSRoot inside a bottle application.
.. code-block:: python
import bottle
import wsme
class MyRoot(wsme.WSRoot):
@wsme.expose(unicode)
def helloworld(self):
return u"Hello World !"
root = MyRoot(webpath='/ws', protocols=['restjson'])
bottle.mount('/ws', root.wsgiapp())
bottle.run()
Pyramid
~~~~~~~
The recommended way of using WSME inside Pyramid is to use
:ref:`adapter-cornice`.

View File

@@ -1,155 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\WebServicesMadeEasy.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\WebServicesMadeEasy.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View File

@@ -1,366 +0,0 @@
Protocols
=========
In this document the same webservice example will be used to
illustrate the different protocols. Its source code is in the
last chapter (:ref:`protocols-the-example`).
REST
----
.. note::
This chapter applies for all adapters, not just the native REST
implementation.
The two REST protocols share common characterics.
Each function corresponds to distinct webpath that starts with the
root webpath, followed by the controllers names if any, and finally
the function name.
The example's exposed functions will be mapped to the following paths:
- ``/ws/persons/create``
- ``/ws/persons/get``
- ``/ws/persons/list``
- ``/ws/persons/update``
- ``/ws/persons/destroy``
In addition to this trivial function mapping, a `method` option can
be given to the `expose` decorator. In such a case, the function
name can be omitted by the caller, and the dispatch will look at the
HTTP method used in the request to select the correct function.
The function parameters can be transmitted in two ways (if using
the HTTP method to select the function, one way or the other
may be usable) :
#. As a GET query string or POST form parameters.
Simple types are straight forward :
``/ws/person/get?id=5``
Complex types can be transmitted this way:
``/ws/person/update?p.id=1&p.name=Ross&p.hobbies[0]=Dinausaurs&p.hobbies[1]=Rachel``
#. In a Json or XML encoded POST body (see below)
The result will be returned Json or XML encoded (see below).
In case of error, a 400 or 500 status code is returned, and the
response body contains details about the error (see below).
REST+Json
---------
:name: ``'restjson'``
Implements a REST+Json protocol.
This protocol is selected if:
- The request content-type is either 'text/javascript' or 'application/json'
- The request 'Accept' header contains 'text/javascript' or 'application/json'
- A trailing '.json' is added to the path
- A 'wsmeproto=restjson' is added in the query string
Options
~~~~~~~
:nest_result: Nest the encoded result in a result param of an object.
For example, a result of ``2`` would be ``{'result': 2}``
Types
~~~~~
+---------------+-------------------------------+
| Type | Json type |
+===============+===============================+
| ``str`` | String |
+---------------+-------------------------------+
| ``unicode`` | String |
+---------------+-------------------------------+
| ``int`` | Number |
+---------------+-------------------------------+
| ``float`` | Number |
+---------------+-------------------------------+
| ``bool`` | Boolean |
+---------------+-------------------------------+
| ``Decimal`` | String |
+---------------+-------------------------------+
| ``date`` | String (YYYY-MM-DD) |
+---------------+-------------------------------+
| ``time`` | String (hh:mm:ss) |
+---------------+-------------------------------+
| ``datetime`` | String (YYYY-MM-DDThh:mm:ss) |
+---------------+-------------------------------+
| Arrays | Array |
+---------------+-------------------------------+
| None | null |
+---------------+-------------------------------+
| Complex types | Object |
+---------------+-------------------------------+
Return
~~~~~~
The Json encoded result when the response code is 200, or a Json object
with error properties ('faulcode', 'faultstring' and 'debuginfo' if
available) on error.
For example, the '/ws/person/get' result looks like:
.. code-block:: javascript
{
'id': 2
'fistname': 'Monica',
'lastname': 'Geller',
'age': 28,
'hobbies': [
'Food',
'Cleaning'
]
}
And in case of error:
.. code-block:: javascript
{
'faultcode': 'Client',
'faultstring': 'id is missing'
}
REST+XML
--------
:name: ``'restxml'``
This protocol is selected if
- The request content-type is 'text/xml'
- The request 'Accept' header contains 'text/xml'
- A trailing '.xml' is added to the path
- A 'wsmeproto=restxml' is added in the query string
Types
~~~~~
+---------------+----------------------------------------+
| Type | XML example |
+===============+========================================+
| ``str`` | .. code-block:: xml |
| | |
| | <value>a string</value> |
+---------------+----------------------------------------+
| ``unicode`` | .. code-block:: xml |
| | |
| | <value>a string</value> |
+---------------+----------------------------------------+
| ``int`` | .. code-block:: xml |
| | |
| | <value>5</value> |
+---------------+----------------------------------------+
| ``float`` | .. code-block:: xml |
| | |
| | <value>3.14</value> |
+---------------+----------------------------------------+
| ``bool`` | .. code-block:: xml |
| | |
| | <value>true</value> |
+---------------+----------------------------------------+
| ``Decimal`` | .. code-block:: xml |
| | |
| | <value>5.46</value> |
+---------------+----------------------------------------+
| ``date`` | .. code-block:: xml |
| | |
| | <value>2010-04-27</value> |
+---------------+----------------------------------------+
| ``time`` | .. code-block:: xml |
| | |
| | <value>12:54:18</value> |
+---------------+----------------------------------------+
| ``datetime`` | .. code-block:: xml |
| | |
| | <value>2010-04-27T12:54:18</value> |
+---------------+----------------------------------------+
| Arrays | .. code-block:: xml |
| | |
| | <value> |
| | <item>Dinausaurs<item> |
| | <item>Rachel<item> |
| | </value> |
+---------------+----------------------------------------+
| None | .. code-block:: xml |
| | |
| | <value nil="true"/> |
+---------------+----------------------------------------+
| Complex types | .. code-block:: xml |
| | |
| | <value> |
| | <id>1</id> |
| | <fistname>Ross</fistname> |
| | </value> |
+---------------+----------------------------------------+
Return
~~~~~~
A xml tree with a top 'result' element.
.. code-block:: xml
<result>
<id>1</id>
<firstname>Ross</firstname>
<lastname>Geller</lastname>
</result>
Errors
~~~~~~
A xml tree with a top 'error' element, having 'faultcode', 'faultstring'
and 'debuginfo' subelements:
.. code-block:: xml
<error>
<faultcode>Client</faultcode>
<faultstring>id is missing</faultstring>
</error>
SOAP
----
:name: ``'soap'``
Implements the SOAP protocol.
A wsdl definition of the webservice is available at the 'api.wsdl' subpath.
(``/ws/api.wsdl`` in our example).
The protocol is selected if the request matches one of the following condition:
- The Content-Type is 'application/soap+xml'
- A header 'Soapaction' is present
Options
~~~~~~~
:tns: Type namespace
ExtDirect
---------
:name: ``extdirect``
Implements the `Ext Direct`_ protocol.
The provider definition is made available at the ``/extdirect/api.js`` subpath.
The router url is ``/extdirect/router[/subnamespace]``.
Options
~~~~~~~
:namespace: Base namespace of the api. Used for the provider definition.
:params_notation: Default notation for function call parameters. Can be
overriden for individual functions by adding the
``extdirect_params_notation`` extra option to @expose.
The possible notations are :
- ``'named'`` -- The function will take only one object parameter
in which each property will be one of the parameters.
- ``'positional'`` -- The function will take as many parameters as
the function has, and their position will determine which parameter
they are.
expose extra options
~~~~~~~~~~~~~~~~~~~~
:extdirect_params_notation: Override the params_notation for a particular
function.
.. _Ext Direct: http://www.sencha.com/products/extjs/extdirect
.. _protocols-the-example:
The example
-----------
In this document the same webservice example will be used to
illustrate the different protocols:
.. code-block:: python
class Person(object):
id = int
lastname = unicode
firstname = unicode
age = int
hobbies = [unicode]
def __init__(self, id=None, lastname=None, firstname=None, age=None,
hobbies=None):
if id:
self.id = id
if lastname:
self.lastname = lastname
if firstname:
self.firstname = firstname
if age:
self.age = age
if hobbies:
self.hobbies = hobbies
persons = {
1: Person(1, "Geller", "Ross", 30, ["Dinosaurs", "Rachel"]),
2: Person(2, "Geller", "Monica", 28, ["Food", "Cleaning"])
}
class PersonController(object):
@expose(Person)
@validate(int)
def get(self, id):
return persons[id]
@expose([Person])
def list(self):
return persons.values()
@expose(Person)
@validate(Person)
def update(self, p):
if p.id is Unset:
raise ClientSideError("id is missing")
persons[p.id] = p
return p
@expose(Person)
@validate(Person)
def create(self, p):
if p.id is not Unset:
raise ClientSideError("I don't want an id")
p.id = max(persons.keys()) + 1
persons[p.id] = p
return p
@expose()
@validate(int)
def destroy(self, id):
if id not in persons:
raise ClientSideError("Unknown ID")
class WS(WSRoot):
person = PersonController()
root = WS(webpath='ws')

View File

@@ -1,3 +0,0 @@
sphinx
cloud_sptheme
-r ../requirements.txt

View File

@@ -1,31 +0,0 @@
TODO
====
WSME is a work in progress. Here is a list of things that should
be done :
- Use gevents for batch-calls
- Implement new protocols :
- json-rpc
- xml-rpc
- Implement adapters for other frameworks :
- TurboGears 2
- Pylons
- CherryPy
- Flask
- others ?
- Add unittests for adapters
- Address the authentication subject (which should be handled by
some other wsgi framework/middleware, but a little integration
could help).

View File

@@ -1,246 +0,0 @@
Types
=====
Three kinds of data types can be used as input or output by WSME.
Native types
------------
The native types are a fixed set of standard Python types that
different protocols map to their own basic types.
The native types are :
- .. wsme:type:: bytes
A pure-ascii string (:py:class:`wsme.types.bytes` which is
:py:class:`str` in Python 2 and :py:class:`bytes` in Python 3).
- .. wsme:type:: text
A unicode string (:py:class:`wsme.types.text` which is
:py:class:`unicode` in Python 2 and :py:class:`str` in Python 3).
- .. wsme:type:: int
An integer (:py:class:`int`)
- .. wsme:type:: float
A float (:py:class:`float`)
- .. wsme:type:: bool
A boolean (:py:class:`bool`)
- .. wsme:type:: Decimal
A fixed-width decimal (:py:class:`decimal.Decimal`)
- .. wsme:type:: date
A date (:py:class:`datetime.date`)
- .. wsme:type:: datetime
A date and time (:py:class:`datetime.datetime`)
- .. wsme:type:: time
A time (:py:class:`datetime.time`)
- Arrays -- This is a special case. When stating a list
datatype, always state its content type as the unique element
of a list. Example::
class SomeWebService(object):
@expose([str])
def getlist(self):
return ['a', 'b', 'c']
- Dictionaries -- Statically typed mappings are allowed. When exposing
a dictionary datatype, you can specify the key and value types,
with a restriction on the key value that must be a 'pod' type.
Example::
class SomeType(object):
amap = {str: SomeOthertype}
There are other types that are supported out of the box. See the
:ref:`pre-defined-user-types`.
User types
----------
User types allow you to define new, almost-native types.
The idea is that you may have Python data that should be transported as base
types by the different protocols, but needs conversion to/from these base types,
or needs to validate data integrity.
To define a user type, you just have to inherit from
:class:`wsme.types.UserType` and instantiate your new class. This instance
will be your new type and can be used as @\ :class:`wsme.expose` or
@\ :class:`wsme.validate` parameters.
Note that protocols can choose to specifically handle a user type or
a base class of user types. This is case with the two pre-defined
user types, :class:`wsme.types.Enum` and :data:`wsme.types.binary`.
.. _pre-defined-user-types:
Pre-defined user types
~~~~~~~~~~~~~~~~~~~~~~
WSME provides some pre-defined user types:
- :class:`binary <wsme.types.binary>` -- for transporting binary data as
base64 strings.
- :class:`Enum <wsme.types.Enum>` -- enforce that the values belongs to a
pre-defined list of values.
These types are good examples of how to define user types. Have
a look at their source code!
Here is a little example that combines :class:`binary <wsme.types.binary>`
and :class:`Enum <wsme.types.Enum>`::
ImageKind = Enum(str, 'jpeg', 'gif')
class Image(object):
name = unicode
kind = ImageKind
data = binary
.. data:: wsme.types.binary
The :class:`wsme.types.BinaryType` instance to use when you need to
transfer base64 encoded data.
.. autoclass:: wsme.types.BinaryType
.. autoclass:: wsme.types.Enum
Complex types
-------------
Complex types are structured types. They are defined as simple Python classes
and will be mapped to adequate structured types in the various protocols.
A base class for structured types is provided, :class:`wsme.types.Base`,
but is not mandatory. The only thing it adds is a default constructor.
The attributes that are set at the class level will be used by WSME to discover
the structure. These attributes can be:
- A datatype -- Any native, user or complex type.
- A :class:`wsattr <wsme.wsattr>` -- This allows you to add more information about
the attribute, for example if it is mandatory.
- A :class:`wsproperty <wsme.wsproperty>` -- A special typed property. Works
like standard ``property`` with additional properties like
:class:`wsattr <wsme.wsattr>`.
Attributes having a leading '_' in their name will be ignored, as well as the
attributes that are not in the above list. This means the type can have methods,
they will not get in the way.
Example
~~~~~~~
::
Gender = wsme.types.Enum(str, 'male', 'female')
Title = wsme.types.Enum(str, 'M', 'Mrs')
class Person(wsme.types.Base):
lastname = wsme.types.wsattr(unicode, mandatory=True)
firstname = wsme.types.wsattr(unicode, mandatory=True)
age = int
gender = Gender
title = Title
hobbies = [unicode]
Rules
~~~~~
A few things you should know about complex types:
- The class must have a default constructor --
Since instances of the type will be created by the protocols when
used as input types, they must be instantiable without any argument.
- Complex types are registered automatically
(and thus inspected) as soon a they are used in expose or validate,
even if they are nested in another complex type.
If for some reason you need to control when type is inspected, you
can use :func:`wsme.types.register_type`.
- The datatype attributes will be replaced.
When using the 'short' way of defining attributes, ie setting a
simple data type, they will be replaced by a wsattr instance.
So, when you write::
class Person(object):
name = unicode
After type registration the class will actually be equivalent to::
class Person(object):
name = wsattr(unicode)
You can still access the datatype by accessing the attribute on the
class, along with the other wsattr properties::
class Person(object):
name = unicode
register_type(Person)
assert Person.name.datatype is unicode
assert Person.name.key == "name"
assert Person.name.mandatory is False
- The default value of instance attributes is
:data:`Unset <wsme.Unset>`.
::
class Person(object):
name = wsattr(unicode)
p = Person()
assert p.name is Unset
This allows the protocol to make a clear distinction between null values
that will be transmitted, and unset values that will not be transmitted.
For input values, it allows the code to know if the values were, or were not,
sent by the caller.
- When 2 complex types refer to each other, their names can be
used as datatypes to avoid adding attributes afterwards:
::
class A(object):
b = wsattr('B')
class B(object):
a = wsattr(A)
Predefined Types
~~~~~~~~~~~~~~~~
.. default-domain:: wsme
- .. autotype:: wsme.types.File
:members:

View File

@@ -1,32 +0,0 @@
from suds.client import Client
url = 'http://127.0.0.1:8080/ws/api.wsdl'
client = Client(url, cache=None)
print client
print client.service.multiply(4, 5)
print client.service.helloworld()
print client.service.getperson()
p = client.service.listpersons()
print repr(p)
p = client.service.setpersons(p)
print repr(p)
p = client.factory.create('ns0:Person')
p.id = 4
print p
a = client.factory.create('ns0:Person_Array')
print a
a = client.service.setpersons(a)
print repr(a)
a.item.append(p)
print repr(a)
a = client.service.setpersons(a)
print repr(a)

View File

@@ -1,104 +0,0 @@
# coding=utf8
"""
A mini-demo of what wsme can do.
To run it::
python setup.py develop
Then::
python demo.py
"""
from wsme import WSRoot, expose, validate
from wsme.types import File
import bottle
from six import u
import logging
class Person(object):
id = int
firstname = unicode
lastname = unicode
hobbies = [unicode]
def __repr__(self):
return "Person(%s, %s %s, %s)" % (
self.id,
self.firstname, self.lastname,
self.hobbies
)
class DemoRoot(WSRoot):
@expose(int)
@validate(int, int)
def multiply(self, a, b):
return a * b
@expose(File)
@validate(File)
def echofile(self, afile):
return afile
@expose(unicode)
def helloworld(self):
return u"Здраво, свете (<- Hello World in Serbian !)"
@expose(Person)
def getperson(self):
p = Person()
p.id = 12
p.firstname = u'Ross'
p.lastname = u'Geler'
p.hobbies = []
print p
return p
@expose([Person])
def listpersons(self):
p = Person()
p.id = 12
p.firstname = u('Ross')
p.lastname = u('Geler')
r = [p]
p = Person()
p.id = 13
p.firstname = u('Rachel')
p.lastname = u('Green')
r.append(p)
print r
return r
@expose(Person)
@validate(Person)
def setperson(self, person):
return person
@expose([Person])
@validate([Person])
def setpersons(self, persons):
print persons
return persons
root = DemoRoot(webpath='/ws')
root.addprotocol('soap',
tns='http://example.com/demo',
typenamespace='http://example.com/demo/types',
baseURL='http://127.0.0.1:8080/ws/',
)
root.addprotocol('restjson')
bottle.mount('/ws/', root.wsgiapp())
logging.basicConfig(level=logging.DEBUG)
bottle.run()

View File

@@ -1,2 +0,0 @@
[easy_install]
find_links = http://www.owlfish.com/software/wsgiutils/download.html

View File

@@ -1,9 +0,0 @@
from setuptools import setup
setup(name='demo',
install_requires=[
'WSME',
'Bottle',
'Pygments',
],
package=['demo'])

View File

@@ -1,18 +0,0 @@
import spyre
import spyre.middleware
class CTypeHeader(spyre.middleware.Middleware):
def __call__(self, env):
env.setdefault('spore.headers', [])
env['spore.headers'].extend([
('Accept', 'application/json'),
('Content-Type', 'application/json')
])
demo = spyre.new_from_url('http://127.0.0.1:8080/ws/api.spore')
demo.enable(CTypeHeader)
demo.enable('format.Json')
print demo.helloworld().content

View File

@@ -1,5 +0,0 @@
six>=1.9.0
WebOb>=1.2.3
simplegeneric
pytz
netaddr>=0.7.12

View File

@@ -1,5 +0,0 @@
six>=1.9.0
WebOb>=1.2.3
simplegeneric
pytz
netaddr>=0.7.12

View File

@@ -1,51 +0,0 @@
[metadata]
name = WSME
author = Christophe de Vienne
author-email = python-wsme@googlegroups.com
summary = Simplify the writing of REST APIs, and extend them with additional protocols.
description-file = README.rst
url = http://git.openstack.org/cgit/openstack/wsme
license = MIT
classifier =
Development Status :: 3 - Alpha
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3.4
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
License :: OSI Approved :: MIT License
Topic :: Internet :: WWW/HTTP :: WSGI
Topic :: Software Development :: Libraries :: Python Modules
[entry_points]
wsme.protocols =
rest = wsme.rest.protocol:RestProtocol
restjson = wsme.rest.protocol:RestProtocol
restxml = wsme.rest.protocol:RestProtocol
soap = wsmeext.soap:SoapProtocol
extdirect = wsmeext.extdirect:ExtDirectProtocol
[files]
packages =
wsme
wsmeext
namespace_packages =
wsmeext
extra_files =
setup.py
README.rst
tests
[wheel]
# WSME has different requirements depending on the version of Python
# being used, so we cannot build universal wheels.
universal = 0

View File

@@ -1,6 +0,0 @@
import setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True
)

View File

@@ -1,6 +0,0 @@
[nosetests]
match=^test
where=test
nocapture=1
cover-package=test
cover-erase=1

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
setup(
name = 'test',
version = '0.1',
description = '',
author = '',
author_email = '',
install_requires = [
"pecan",
],
test_suite = 'test',
zip_safe = False,
include_package_data = True,
packages = find_packages(exclude=['ez_setup'])
)

View File

@@ -1,15 +0,0 @@
from pecan import make_app
from test import model
def setup_app(config):
model.init_model()
return make_app(
config.app.root,
static_root = config.app.static_root,
template_path = config.app.template_path,
logging = getattr(config, 'logging', {}),
debug = getattr(config.app, 'debug', False),
force_canonical = getattr(config.app, 'force_canonical', True)
)

View File

@@ -1,21 +0,0 @@
from pecan import expose
from webob.exc import status_map
from .ws import AuthorsController
from wsmeext.pecan import wsexpose
class RootController(object):
authors = AuthorsController()
@expose('error.html')
def error(self, status):
try:
status = int(status)
except ValueError: # pragma: no cover
status = 500
message = getattr(status_map.get(status), 'explanation', '')
return dict(status=status, message=message)
@wsexpose()
def divide_by_zero(self):
1 / 0

View File

@@ -1,150 +0,0 @@
# encoding=utf8
from pecan.rest import RestController
from wsme.types import Base, text, wsattr
import wsme
import wsmeext.pecan
import six
class Author(Base):
id = int
firstname = text
books = wsattr(['Book'])
@staticmethod
def validate(author):
if author.firstname == 'Robert':
raise wsme.exc.ClientSideError("I don't like this author!")
return author
class Book(Base):
id = int
name = text
author = wsattr('Author')
class BookNotFound(Exception):
message = "Book with ID={id} Not Found"
code = 404
def __init__(self, id):
message = self.message.format(id=id)
super(BookNotFound, self).__init__(message)
class NonHttpException(Exception):
message = "Internal Exception for Book ID={id}"
code = 684
def __init__(self, id):
message = self.message.format(id=id)
super(NonHttpException, self).__init__(message)
class BooksController(RestController):
@wsmeext.pecan.wsexpose(Book, int, int)
def get(self, author_id, id):
book = Book(
name=u"Les Confessions dun révolutionnaire pour servir à "
u"lhistoire de la révolution de février",
author=Author(lastname=u"Proudhon")
)
return book
@wsmeext.pecan.wsexpose(Book, int, int, body=Book)
def put(self, author_id, id, book=None):
book.id = id
book.author = Author(id=author_id)
return book
class Criterion(Base):
op = text
attrname = text
value = text
class AuthorsController(RestController):
_custom_actions = {
'json_only': ['GET'],
'xml_only': ['GET']
}
books = BooksController()
@wsmeext.pecan.wsexpose([Author], [six.text_type], [Criterion])
def get_all(self, q=None, r=None):
if q:
return [
Author(id=i, firstname=value)
for i, value in enumerate(q)
]
if r:
return [
Author(id=i, firstname=c.value)
for i, c in enumerate(r)
]
return [
Author(id=1, firstname=u'FirstName')
]
@wsmeext.pecan.wsexpose(Author, int)
def get(self, id):
if id == 999:
raise wsme.exc.ClientSideError('Wrong ID')
if id == 998:
raise BookNotFound(id)
if id == 997:
raise NonHttpException(id)
if id == 996:
raise wsme.exc.ClientSideError('Disabled ID', status_code=403)
if id == 911:
return wsme.api.Response(Author(),
status_code=401)
if id == 912:
return wsme.api.Response(None, status_code=204)
if id == 913:
return wsme.api.Response('foo', status_code=200, return_type=text)
author = Author()
author.id = id
author.firstname = u"aname"
author.books = [
Book(
name=u"Les Confessions dun révolutionnaire pour servir à "
u"lhistoire de la révolution de février",
)
]
return author
@wsmeext.pecan.wsexpose(Author, body=Author, status_code=201)
def post(self, author):
author.id = 10
return author
@wsmeext.pecan.wsexpose(None, int)
def delete(self, author_id):
print("Deleting", author_id)
@wsmeext.pecan.wsexpose(Book, int, body=Author)
def put(self, author_id, author=None):
return author
@wsmeext.pecan.wsexpose([Author], rest_content_types=('json',))
def json_only(self):
return [Author(id=1, firstname=u"aname", books=[])]
@wsmeext.pecan.wsexpose([Author], rest_content_types=('xml',))
def xml_only(self):
return [Author(id=1, firstname=u"aname", books=[])]

View File

@@ -1,2 +0,0 @@
def init_model():
pass

View File

@@ -1,22 +0,0 @@
import os
from unittest import TestCase
from pecan import set_config
from pecan import testing
__all__ = ['FunctionalTest']
class FunctionalTest(TestCase):
"""
Used for functional tests where you need to test your
literal application and its integration with the framework.
"""
def setUp(self):
self.app = testing.load_test_app(os.path.join(
os.path.dirname(__file__),
'config.py'
))
def tearDown(self):
set_config({}, overwrite=True)

View File

@@ -1,24 +0,0 @@
# Server Specific Configurations
server = {
'port' : '8080',
'host' : '0.0.0.0'
}
# Pecan Application Configurations
app = {
'root' : 'test.controllers.root.RootController',
'modules' : ['test'],
'static_root' : '%(confdir)s/../../public',
'template_path' : '%(confdir)s/../templates',
'errors' : {
'404' : '/error/404',
'__force_dict__' : True
}
}
# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

View File

@@ -1,247 +0,0 @@
from six.moves import http_client
from test.tests import FunctionalTest
import json
import pecan
import six
used_status_codes = [400, 401, 403, 404, 500]
http_response_messages = {}
for code in used_status_codes:
http_response_messages[code] = '%s %s' % (code, http_client.responses[code])
class TestWS(FunctionalTest):
def test_get_all(self):
self.app.get('/authors')
def test_optional_array_param(self):
r = self.app.get('/authors?q=a&q=b')
l = json.loads(r.body.decode('utf-8'))
assert len(l) == 2
assert l[0]['firstname'] == 'a'
assert l[1]['firstname'] == 'b'
def test_optional_indexed_array_param(self):
r = self.app.get('/authors?q[0]=a&q[1]=b')
l = json.loads(r.body.decode('utf-8'))
assert len(l) == 2
assert l[0]['firstname'] == 'a'
assert l[1]['firstname'] == 'b'
def test_options_object_array_param(self):
r = self.app.get('/authors?r.value=a&r.value=b')
l = json.loads(r.body.decode('utf-8'))
assert len(l) == 2
assert l[0]['firstname'] == 'a'
assert l[1]['firstname'] == 'b'
def test_options_indexed_object_array_param(self):
r = self.app.get('/authors?r[0].value=a&r[1].value=b')
l = json.loads(r.body.decode('utf-8'))
assert len(l) == 2
assert l[0]['firstname'] == 'a'
assert l[1]['firstname'] == 'b'
def test_get_author(self):
a = self.app.get(
'/authors/1.json',
)
a = json.loads(a.body.decode('utf-8'))
assert a['id'] == 1
assert a['firstname'] == 'aname'
a = self.app.get(
'/authors/1.xml',
)
body = a.body.decode('utf-8')
assert '<id>1</id>' in body
assert '<firstname>aname</firstname>' in body
def test_post_body_parameter_validation(self):
res = self.app.post(
'/authors', '{"firstname": "Robert"}',
headers={"Content-Type": "application/json"},
expect_errors=True
)
self.assertEqual(res.status_int, 400)
a = json.loads(res.body.decode('utf-8'))
self.assertEqual(a['faultcode'], 'Client')
self.assertEqual(a['faultstring'], "I don't like this author!")
def test_post_body_parameter(self):
res = self.app.post(
'/authors', '{"firstname": "test"}',
headers={"Content-Type": "application/json"}
)
assert res.status_int == 201
a = json.loads(res.body.decode('utf-8'))
assert a['id'] == 10
assert a['firstname'] == 'test'
def test_put_parameter_validate(self):
res = self.app.put(
'/authors/foobar', '{"firstname": "test"}',
headers={"Content-Type": "application/json"},
expect_errors=True
)
self.assertEqual(res.status_int, 400)
a = json.loads(res.body.decode('utf-8'))
self.assertEqual(
a['faultstring'],
"Invalid input for field/attribute author_id. "
"Value: 'foobar'. unable to convert to int. Error: invalid "
"literal for int() with base 10: 'foobar'")
def test_clientsideerror(self):
expected_status_code = 400
expected_status = http_response_messages[expected_status_code]
res = self.app.get(
'/authors/999.json',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Client'
res = self.app.get(
'/authors/999.xml',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
def test_custom_clientside_error(self):
expected_status_code = 404
expected_status = http_response_messages[expected_status_code]
res = self.app.get(
'/authors/998.json',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Client'
res = self.app.get(
'/authors/998.xml',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
def test_custom_non_http_clientside_error(self):
expected_status_code = 500
expected_status = http_response_messages[expected_status_code]
res = self.app.get(
'/authors/997.json',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Server'
res = self.app.get(
'/authors/997.xml',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8')
def test_clientsideerror_status_code(self):
expected_status_code = 403
expected_status = http_response_messages[expected_status_code]
res = self.app.get(
'/authors/996.json',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Client'
res = self.app.get(
'/authors/996.xml',
expect_errors=True
)
self.assertEqual(res.status, expected_status)
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
def test_non_default_response(self):
expected_status_code = 401
expected_status = http_response_messages[expected_status_code]
res = self.app.get(
'/authors/911.json',
expect_errors=True
)
self.assertEqual(res.status_int, expected_status_code)
self.assertEqual(res.status, expected_status)
def test_non_default_response_return_type(self):
res = self.app.get(
'/authors/913',
)
self.assertEqual(res.status_int, 200)
self.assertEqual(res.body, b'"foo"')
self.assertEqual(res.content_length, 5)
def test_non_default_response_return_type_no_content(self):
res = self.app.get(
'/authors/912',
)
self.assertEqual(res.status_int, 204)
self.assertEqual(res.body, b'')
self.assertEqual(res.content_length, 0)
def test_serversideerror(self):
expected_status_code = 500
expected_status = http_response_messages[expected_status_code]
res = self.app.get('/divide_by_zero.json', expect_errors=True)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Server'
assert a['debuginfo'] is None
def test_serversideerror_with_debug(self):
expected_status_code = 500
expected_status = http_response_messages[expected_status_code]
pecan.set_config({'wsme': {'debug': True}})
res = self.app.get('/divide_by_zero.json', expect_errors=True)
self.assertEqual(res.status, expected_status)
a = json.loads(res.body.decode('utf-8'))
assert a['faultcode'] == 'Server'
assert a['debuginfo'].startswith('Traceback (most recent call last):')
def test_json_only(self):
res = self.app.get('/authors/json_only.json')
assert res.status_int == 200
body = json.loads(res.body.decode('utf-8'))
assert len(body) == 1
assert body[0]['firstname'] == u"aname"
assert body[0]['books'] == []
assert body[0]['id'] == 1
res = self.app.get('/authors/json_only.xml', expect_errors=True)
def test_xml_only(self):
res = self.app.get('/authors/xml_only.xml')
assert res.status_int == 200
assert '<id>1</id>' in res.body.decode('utf-8')
assert '<firstname>aname</firstname>' in res.body.decode('utf-8')
assert '<books />' in res.body.decode('utf-8')
res = self.app.get('/authors/xml_only.json', expect_errors=True)
def test_body_parameter(self):
res = self.app.put(
'/authors/1/books/2.json',
'{"name": "Alice au pays des merveilles"}',
headers={"Content-Type": "application/json"}
)
book = json.loads(res.body.decode('utf-8'))
assert book['id'] == 2
assert book['author']['id'] == 1
def test_no_content_type_if_no_return_type(self):
if six.PY3:
self.skipTest(
"This test does not work in Python 3 until https://review.openstack.org/#/c/48439/ is merged")
res = self.app.delete('/authors/4')
assert "Content-Type" not in res.headers, res.headers['Content-Type']

View File

@@ -1,20 +0,0 @@
import mock
import unittest
from wsme import exc
from wsme.rest import args
from wsme.rest import json
class TestArgs(unittest.TestCase):
def test_args_from_body(self):
funcdef = mock.MagicMock()
body = mock.MagicMock()
mimetype = "application/json"
funcdef.ignore_extra_args = True
json.parse = mock.MagicMock()
json.parse.side_effect = (exc.UnknownArgument(""))
resp = args.args_from_body(funcdef, body, mimetype)
self.assertEqual(resp, ((), {}))

View File

@@ -1,232 +0,0 @@
# -*- coding: utf-8 -*-
#
# Web Services Made Easy documentation build configuration file, created by
# sphinx-quickstart on Sun Oct 2 20:27:45 2011.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# 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', 'wsmeext.sphinxext']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'wsmeext.sphinxext Test'
copyright = u'2011, Christophe de Vienne'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
import pkg_resources
dist = pkg_resources.require('WSME')[0]
# The short X.Y version.
version = '.'.join(dist.version[:2])
# The full version, including alpha/beta/rc tags.
release = dist.version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'agogo'
html_theme_options = {
"pagewidth": "60em",
"documentwidth": "40em",
}
html_style = 'wsme.css'
# 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
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = "WSME %s" % release
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'WebServicesMadeEasydoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation',
u'Christophe de Vienne', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation',
[u'Christophe de Vienne'], 1)
]
autodoc_member_order = 'bysource'
wsme_protocols = [
'restjson', 'restxml'
]

View File

@@ -1,43 +0,0 @@
API Documentation test
======================
Example
~~~~~~~
.. wsme:root:: wsmeext.sphinxext.SampleService
:webpath: /api
.. wsme:type:: MyType
.. wsme:attribute:: test
:type: int
.. wsme:service:: name/space/SampleService
.. wsme:function:: getType
Returns a :wsme:type:`MyType <MyType>`
.. default-domain:: wsme
.. type:: int
An integer
.. autotype:: wsmeext.sphinxext.SampleType
:members:
.. autoservice:: wsmeext.sphinxext.SampleService
:members:
.. autotype:: test_sphinxext.ASampleType
:members:
.. autotype:: wsme.types.bytes
.. autotype:: wsme.types.text
.. _Sphinx: http://sphinx.pocoo.org/

View File

@@ -1,3 +0,0 @@
.. toctree::
document

View File

@@ -1,183 +0,0 @@
import unittest
import json
import webtest
from cornice import Service
from cornice import resource
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPUnauthorized
from wsme.types import text, Base, HostRequest
from wsmeext.cornice import signature
class User(Base):
id = int
name = text
users = Service(name='users', path='/users')
@users.get()
@signature([User])
def users_get():
return [User(id=1, name='first')]
@users.post()
@signature(User, body=User)
def users_create(data):
data.id = 2
return data
secret = Service(name='secrets', path='/secret')
@secret.get()
@signature()
def secret_get():
raise HTTPUnauthorized()
divide = Service(name='divide', path='/divide')
@divide.get()
@signature(int, int, int)
def do_divide(a, b):
return a / b
needrequest = Service(name='needrequest', path='/needrequest')
@needrequest.get()
@signature(bool, HostRequest)
def needrequest_get(request):
assert request.path == '/needrequest', request.path
return True
class Author(Base):
authorId = int
name = text
@resource.resource(collection_path='/author', path='/author/{authorId}')
class AuthorResource(object):
def __init__(self, request):
self.request = request
@signature(Author, int)
def get(self, authorId):
return Author(authorId=authorId, name="Author %s" % authorId)
@signature(Author, int, body=Author)
def post(self, authorId, data):
data.authorId = authorId
return data
@signature([Author], text)
def collection_get(self, where=None):
return [
Author(authorId=1, name="Author 1"),
Author(authorId=2, name="Author 2"),
Author(authorId=3, name="Author 3")
]
def make_app():
config = Configurator()
config.include("cornice")
config.include("wsmeext.cornice")
config.scan("test_cornice")
return config.make_wsgi_app()
class WSMECorniceTestCase(unittest.TestCase):
def setUp(self):
self.app = webtest.TestApp(make_app())
def test_get_json_list(self):
resp = self.app.get('/users')
self.assertEqual(
resp.body,
b'[{"id": 1, "name": "first"}]'
)
def test_get_xml_list(self):
resp = self.app.get('/users', headers={"Accept": "text/xml"})
self.assertEqual(
resp.body,
b'<result><item><id>1</id><name>first</name></item></result>'
)
def test_post_json_data(self):
data = json.dumps({"name": "new"})
resp = self.app.post(
'/users', data,
headers={"Content-Type": "application/json"}
)
self.assertEqual(
resp.body,
b'{"id": 2, "name": "new"}'
)
def test_post_xml_data(self):
data = '<data><name>new</name></data>'
resp = self.app.post(
'/users', data,
headers={"Content-Type": "text/xml"}
)
self.assertEqual(
resp.body,
b'<result><id>2</id><name>new</name></result>'
)
def test_pass_request(self):
resp = self.app.get('/needrequest')
assert resp.json is True
def test_resource_collection_get(self):
resp = self.app.get('/author')
assert len(resp.json) == 3
assert resp.json[0]['name'] == 'Author 1'
assert resp.json[1]['name'] == 'Author 2'
assert resp.json[2]['name'] == 'Author 3'
def test_resource_get(self):
resp = self.app.get('/author/5')
assert resp.json['name'] == 'Author 5'
def test_resource_post(self):
resp = self.app.post(
'/author/5',
json.dumps({"name": "Author 5"}),
headers={"Content-Type": "application/json"}
)
assert resp.json['authorId'] == 5
assert resp.json['name'] == 'Author 5'
def test_server_error(self):
resp = self.app.get('/divide?a=1&b=0', expect_errors=True)
self.assertEqual(resp.json['faultcode'], 'Server')
self.assertEqual(resp.status_code, 500)
def test_client_error(self):
resp = self.app.get(
'/divide?a=1&c=0',
headers={'Accept': 'application/json'},
expect_errors=True
)
self.assertEqual(resp.json['faultcode'], 'Client')
self.assertEqual(resp.status_code, 400)
def test_runtime_error(self):
resp = self.app.get(
'/secret',
headers={'Accept': 'application/json'},
expect_errors=True
)
self.assertEqual(resp.json['faultcode'], 'Client')
self.assertEqual(resp.status_code, 401)

View File

@@ -1,208 +0,0 @@
# encoding=utf8
import unittest
from flask import Flask, json, abort
from flask.ext import restful
from wsmeext.flask import signature
from wsme.api import Response
from wsme.types import Base, text
class Model(Base):
id = int
name = text
class Criterion(Base):
op = text
attr = text
value = text
test_app = Flask(__name__)
api = restful.Api(test_app)
@test_app.route('/multiply')
@signature(int, int, int)
def multiply(a, b):
return a * b
@test_app.route('/divide_by_zero')
@signature(None)
def divide_by_zero():
return 1 / 0
@test_app.route('/models')
@signature([Model], [Criterion])
def list_models(q=None):
if q:
name = q[0].value
else:
name = 'first'
return [Model(name=name)]
@test_app.route('/models/<name>')
@signature(Model, text)
def get_model(name):
return Model(name=name)
@test_app.route('/models/<name>/secret')
@signature(Model, text)
def model_secret(name):
abort(403)
@test_app.route('/models/<name>/custom-error')
@signature(Model, text)
def model_custom_error(name):
class CustomError(Exception):
code = 412
raise CustomError("FOO!")
@test_app.route('/models', methods=['POST'])
@signature(Model, body=Model)
def post_model(body):
return Model(name=body.name)
@test_app.route('/status_sig')
@signature(int, status_code=201)
def get_status_sig():
return 1
@test_app.route('/status_response')
@signature(int)
def get_status_response():
return Response(1, status_code=201)
class RestFullApi(restful.Resource):
@signature(Model)
def get(self):
return Model(id=1, name=u"Gérard")
@signature(int, body=Model)
def post(self, model):
return model.id
api.add_resource(RestFullApi, '/restful')
class FlaskrTestCase(unittest.TestCase):
def setUp(self):
test_app.config['TESTING'] = True
self.app = test_app.test_client()
def tearDown(self):
pass
def test_multiply(self):
r = self.app.get('/multiply?a=2&b=5')
assert r.data == b'10', r.data
def test_get_model(self):
resp = self.app.get('/models/test')
assert resp.status_code == 200
def test_list_models(self):
resp = self.app.get('/models')
assert resp.status_code == 200
def test_array_parameter(self):
resp = self.app.get('/models?q.op=%3D&q.attr=name&q.value=second')
assert resp.status_code == 200
self.assertEqual(
resp.data, b'[{"name": "second"}]'
)
def test_post_model(self):
resp = self.app.post('/models', data={"body.name": "test"})
assert resp.status_code == 200
resp = self.app.post(
'/models',
data=json.dumps({"name": "test"}),
content_type="application/json"
)
assert resp.status_code == 200
def test_get_status_sig(self):
resp = self.app.get('/status_sig')
assert resp.status_code == 201
def test_get_status_response(self):
resp = self.app.get('/status_response')
assert resp.status_code == 201
def test_custom_clientside_error(self):
r = self.app.get(
'/models/test/secret',
headers={'Accept': 'application/json'}
)
assert r.status_code == 403, r.status_code
assert json.loads(r.data)['faultstring'] == '403: Forbidden'
r = self.app.get(
'/models/test/secret',
headers={'Accept': 'application/xml'}
)
assert r.status_code == 403, r.status_code
assert r.data == (b'<error><faultcode>Client</faultcode>'
b'<faultstring>403: Forbidden</faultstring>'
b'<debuginfo /></error>')
def test_custom_non_http_clientside_error(self):
r = self.app.get(
'/models/test/custom-error',
headers={'Accept': 'application/json'}
)
assert r.status_code == 412, r.status_code
assert json.loads(r.data)['faultstring'] == 'FOO!'
r = self.app.get(
'/models/test/custom-error',
headers={'Accept': 'application/xml'}
)
assert r.status_code == 412, r.status_code
assert r.data == (b'<error><faultcode>Client</faultcode>'
b'<faultstring>FOO!</faultstring>'
b'<debuginfo /></error>')
def test_serversideerror(self):
r = self.app.get('/divide_by_zero')
assert r.status_code == 500
data = json.loads(r.data)
self.assertEqual(data['debuginfo'], None)
self.assertEqual(data['faultcode'], 'Server')
self.assertIn('by zero', data['faultstring'])
def test_restful_get(self):
r = self.app.get('/restful', headers={'Accept': 'application/json'})
self.assertEqual(r.status_code, 200)
data = json.loads(r.data)
self.assertEqual(data['id'], 1)
self.assertEqual(data['name'], u"Gérard")
def test_restful_post(self):
r = self.app.post(
'/restful',
data=json.dumps({'id': 5, 'name': u'Huguette'}),
headers={
'Accept': 'application/json',
'Content-Type': 'application/json'})
self.assertEqual(r.status_code, 200)
data = json.loads(r.data)
self.assertEqual(data, 5)
if __name__ == '__main__':
test_app.run()

View File

@@ -1,51 +0,0 @@
import unittest
import sphinx
import os.path
import wsme.types
from wsmeext import sphinxext
docpath = os.path.join(
os.path.dirname(__file__),
'sphinxexample')
class ASampleType(object):
somebytes = wsme.types.bytes
sometext = wsme.types.text
someint = int
class TestSphinxExt(unittest.TestCase):
def test_buildhtml(self):
if not os.path.exists('.test_sphinxext/'):
os.makedirs('.test_sphinxext/')
try:
sphinx.main([
'',
'-b', 'html',
'-d', '.test_sphinxext/doctree',
docpath,
'.test_sphinxext/html'
])
assert Exception("Should raise SystemExit 0")
except SystemExit as e:
assert e.code == 0
class TestDataTypeName(unittest.TestCase):
def test_user_type(self):
self.assertEqual(sphinxext.datatypename(ASampleType),
'ASampleType')
def test_dict_type(self):
d = wsme.types.DictType(str, str)
self.assertEqual(sphinxext.datatypename(d), 'dict(str: str)')
d = wsme.types.DictType(str, ASampleType)
self.assertEqual(sphinxext.datatypename(d), 'dict(str: ASampleType)')
def test_array_type(self):
d = wsme.types.ArrayType(str)
self.assertEqual(sphinxext.datatypename(d), 'list(str)')
d = wsme.types.ArrayType(ASampleType)
self.assertEqual(sphinxext.datatypename(d), 'list(ASampleType)')

View File

@@ -1,196 +0,0 @@
import wsmeext.tg11
from wsme import WSRoot
from wsmeext.tg11 import wsexpose, wsvalidate
import wsmeext.tg1
from turbogears.controllers import RootController
import cherrypy
import unittest
import simplejson
from wsmeext.tests import test_soap
class WSController(WSRoot):
pass
class Subcontroller(object):
@wsexpose(int, int, int)
def add(self, a, b):
return a + b
class Root(RootController):
class UselessSubClass:
# This class is here only to make sure wsmeext.tg1.scan_api
# does its job properly
pass
ws = WSController(webpath='/ws')
ws.addprotocol(
'soap',
tns=test_soap.tns,
typenamespace=test_soap.typenamespace,
baseURL='/ws/'
)
ws = wsmeext.tg11.adapt(ws)
@wsexpose(int)
@wsvalidate(int, int)
def multiply(self, a, b):
return a * b
@wsexpose(int)
@wsvalidate(int, int)
def divide(self, a, b):
if b == 0:
raise cherrypy.HTTPError(400, 'Cannot divide by zero!')
return a / b
sub = Subcontroller()
from turbogears import testutil, config, startup
class TestController(unittest.TestCase):
root = Root
def setUp(self):
"Tests the output of the index method"
self.app = testutil.make_app(self.root)
testutil.start_server()
def tearDown(self):
# implementation copied from turbogears.testutil.stop_server.
# The only change is that cherrypy.root is set to None
# AFTER stopTurbogears has been called so that wsmeext.tg11
# can correctly uninstall its filter.
if config.get("cp_started"):
cherrypy.server.stop()
config.update({"cp_started": False})
if config.get("server_started"):
startup.stopTurboGears()
config.update({"server_started": False})
def test_restcall(self):
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json'}
)
print response
assert simplejson.loads(response.body) == 50
response = self.app.post("/sub/add",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json'}
)
print response
assert simplejson.loads(response.body) == 15
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json', 'Accept': 'application/json'}
)
print response
assert simplejson.loads(response.body) == 50
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json', 'Accept': 'text/javascript'}
)
print response
assert simplejson.loads(response.body) == 50
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json',
'Accept': 'text/xml'}
)
print response
assert response.body == "<result>50</result>"
def test_custom_clientside_error(self):
response = self.app.post(
"/divide",
simplejson.dumps({'a': 5, 'b': 0}),
{'Content-Type': 'application/json', 'Accept': 'application/json'},
expect_errors=True
)
assert response.status_int == 400
assert simplejson.loads(response.body) == {
"debuginfo": None,
"faultcode": "Server",
"faultstring": "(400, 'Cannot divide by zero!')"
}
response = self.app.post(
"/divide",
simplejson.dumps({'a': 5, 'b': 0}),
{'Content-Type': 'application/json', 'Accept': 'text/xml'},
expect_errors=True
)
assert response.status_int == 400
assert response.body == ("<error><faultcode>Server</faultcode>"
"<faultstring>(400, 'Cannot divide by zero!')"
"</faultstring><debuginfo /></error>")
def test_soap_wsdl(self):
ts = test_soap.TestSOAP('test_wsdl')
ts.app = self.app
ts.ws_path = '/ws/'
ts.run()
#wsdl = self.app.get('/ws/api.wsdl').body
#print wsdl
#assert 'multiply' in wsdl
def test_soap_call(self):
ts = test_soap.TestSOAP('test_wsdl')
ts.app = self.app
ts.ws_path = '/ws/'
print ts.ws_path
assert ts.call('multiply', a=5, b=10, _rt=int) == 50
def test_scan_api_loops(self):
class MyRoot(object):
pass
MyRoot.loop = MyRoot()
root = MyRoot()
api = list(wsmeext.tg1._scan_api(root))
print(api)
self.assertEqual(len(api), 0)
def test_scan_api_maxlen(self):
class ARoot(object):
pass
def make_subcontrollers(n):
c = type('Controller%s' % n, (object,), {})
return c
c = ARoot
for n in xrange(55):
subc = make_subcontrollers(n)
c.sub = subc()
c = subc
root = ARoot()
self.assertRaises(ValueError, list, wsmeext.tg1._scan_api(root))
def test_templates_content_type(self):
self.assertEqual(
"application/json",
wsmeext.tg1.AutoJSONTemplate().get_content_type('dummy')
)
self.assertEqual(
"text/xml",
wsmeext.tg1.AutoXMLTemplate().get_content_type('dummy')
)

View File

@@ -1,177 +0,0 @@
import wsmeext.tg15
from wsme import WSRoot
from turbogears.controllers import RootController
import cherrypy
from wsmeext.tests import test_soap
import simplejson
class Subcontroller(object):
@wsmeext.tg15.wsexpose(int, int, int)
def add(self, a, b):
return a + b
class Root(RootController):
class UselessSubClass:
# This class is here only to make sure wsmeext.tg1.scan_api
# does its job properly
pass
sub = Subcontroller()
ws = WSRoot(webpath='/ws')
ws.addprotocol('soap',
tns=test_soap.tns,
typenamespace=test_soap.typenamespace,
baseURL='/ws/'
)
ws = wsmeext.tg15.adapt(ws)
@wsmeext.tg15.wsexpose(int)
@wsmeext.tg15.wsvalidate(int, int)
def multiply(self, a, b):
return a * b
@wsmeext.tg15.wsexpose(int)
@wsmeext.tg15.wsvalidate(int, int)
def divide(self, a, b):
if b == 0:
raise cherrypy.HTTPError(400, 'Cannot divide by zero!')
return a / b
from turbogears import testutil
class TestController(testutil.TGTest):
root = Root
# def setUp(self):
# "Tests the output of the index method"
# self.app = testutil.make_app(self.root)
# #print cherrypy.root
# testutil.start_server()
# def tearDown(self):
# # implementation copied from turbogears.testutil.stop_server.
# # The only change is that cherrypy.root is set to None
# # AFTER stopTurbogears has been called so that wsmeext.tg15
# # can correctly uninstall its filter.
# if config.get("cp_started"):
# cherrypy.server.stop()
# config.update({"cp_started": False})
#
# if config.get("server_started"):
# startup.stopTurboGears()
# config.update({"server_started": False})
def test_restcall(self):
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json'}
)
print response
assert simplejson.loads(response.body) == 50
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json', 'Accept': 'application/json'}
)
print response
assert simplejson.loads(response.body) == 50
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json', 'Accept': 'text/javascript'}
)
print response
assert simplejson.loads(response.body) == 50
response = self.app.post("/multiply",
simplejson.dumps({'a': 5, 'b': 10}),
{'Content-Type': 'application/json',
'Accept': 'text/xml'}
)
print response
assert response.body == "<result>50</result>"
def test_custom_clientside_error(self):
response = self.app.post(
"/divide",
simplejson.dumps({'a': 5, 'b': 0}),
{'Content-Type': 'application/json', 'Accept': 'application/json'},
expect_errors=True
)
assert response.status_int == 400
assert simplejson.loads(response.body) == {
"debuginfo": None,
"faultcode": "Client",
"faultstring": "(400, 'Cannot divide by zero!')"
}
response = self.app.post(
"/divide",
simplejson.dumps({'a': 5, 'b': 0}),
{'Content-Type': 'application/json', 'Accept': 'text/xml'},
expect_errors=True
)
assert response.status_int == 400
assert response.body == ("<error><faultcode>Client</faultcode>"
"<faultstring>(400, 'Cannot divide by zero!')"
"</faultstring><debuginfo /></error>")
def test_soap_wsdl(self):
wsdl = self.app.get('/ws/api.wsdl').body
print wsdl
assert 'multiply' in wsdl
def test_soap_call(self):
ts = test_soap.TestSOAP('test_wsdl')
ts.app = self.app
ts.ws_path = '/ws/'
print ts.ws_path
assert ts.call('multiply', a=5, b=10, _rt=int) == 50
def test_scan_api_loops(self):
class MyRoot(object):
pass
MyRoot.loop = MyRoot()
root = MyRoot()
api = list(wsmeext.tg1._scan_api(root))
print(api)
self.assertEqual(len(api), 0)
def test_scan_api_maxlen(self):
class ARoot(object):
pass
def make_subcontrollers(n):
c = type('Controller%s' % n, (object,), {})
return c
c = ARoot
for n in xrange(55):
subc = make_subcontrollers(n)
c.sub = subc()
c = subc
root = ARoot()
self.assertRaises(ValueError, list, wsmeext.tg1._scan_api(root))
def test_templates_content_type(self):
self.assertEqual(
"application/json",
wsmeext.tg1.AutoJSONTemplate().get_content_type('dummy')
)
self.assertEqual(
"text/xml",
wsmeext.tg1.AutoXMLTemplate().get_content_type('dummy')
)

View File

@@ -1,143 +0,0 @@
# content of: tox.ini , put in same dir as setup.py
[tox]
envlist = py27,py27-nolxml,pypy,tg11,tg15,cornice,cornice-py3,coverage,py35,py35-nolxml,pecan-dev27,pecan-dev35,pep8
[common]
testtools=
nose
coverage < 3.99
pbr
webtest
basedeps=
transaction
pecan
cloud_sptheme
Sphinx < 1.2.99
Flask
flask-restful
[axes]
python=py27,py35,pypy
sqlalchemy=sa5,sa6,sa7*
lxml=lxml*,nolxml
json=json*,simplejson
[axis:python]
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
commands=
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
[axis:python:py27]
basepython=python2.7
[axis:python:py35]
basepython=python3.5
[axis:sqlalchemy:sa5]
deps=
SQLAlchemy<=0.5.99
[axis:sqlalchemy:sa6]
deps=
SQLAlchemy<=0.6.99
[axis:sqlalchemy:sa7]
deps=
SQLAlchemy<=0.7.99
[axis:json:simplejson]
deps=
simplejson
[axis:lxml:lxml]
deps=
lxml
[testenv]
setenv=
COVERAGE_FILE=.coverage.{envname}
[testenv:cornice]
basepython=python2.7
usedevelop=True
deps=
pbr
nose
webtest
coverage < 3.99
cornice
commands=
{envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py
[testenv:cornice-py3]
basepython = python3.5
usedevelop = {[testenv:cornice]usedevelop}
deps = {[testenv:cornice]deps}
# disable hash randomization
setenv =
PYTHONHASHSEED=0
commands =
{envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py
[testenv:pecan-dev-base]
deps=
{[common]testtools}
transaction
suds-jurko
https://github.com/stackforge/pecan/zipball/master
[testenv:pecan-dev27]
basepython=python2.7
deps={[testenv:pecan-dev-base]deps}
commands=
{envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs}
[testenv:pecan-dev35]
basepython=python3.5
deps={[testenv:pecan-dev-base]deps}
commands=
{envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs}
[testenv:coverage]
basepython=python
deps=
coverage < 3.99
setenv=
COVERAGE_FILE=.coverage
commands=
{envbindir}/coverage erase
{envbindir}/coverage combine
{envbindir}/coverage xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/protocols/*.py wsmeext/*.py
[testenv:doc]
deps=
cloud_sptheme
Sphinx < 1.2.99
changedir=
doc
commands=
make clean ziphtml
[testenv:pep8]
deps = flake8
commands = flake8 wsme wsmeext setup.py
# Generic environment for running commands like packaging
[testenv:venv]
commands = {posargs}
usedevelop=True
deps =
pbr
oslo.config
oslotest

932
tox.ini
View File

@@ -1,932 +0,0 @@
[tox]
envlist = py27,py27-nolxml,pypy,tg11,tg15,cornice,cornice-py3,coverage,py35,py35-nolxml,pecan-dev27,pecan-dev35,pep8
[common]
testtools =
nose
coverage < 3.99
pbr
webtest
basedeps =
transaction
pecan
cloud_sptheme
Sphinx < 1.2.99
Flask
flask-restful
[testenv]
setenv =
COVERAGE_FILE=.coverage.{envname}
[testenv:cornice]
basepython = python2.7
usedevelop = True
deps =
pbr
nose
webtest
coverage < 3.99
cornice
commands =
{envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py
[testenv:cornice-py3]
basepython = python3.5
usedevelop = {[testenv:cornice]usedevelop}
deps = {[testenv:cornice]deps}
setenv =
PYTHONHASHSEED=0
commands =
{envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py
[testenv:pecan-dev-base]
deps =
{[common]testtools}
transaction
suds-jurko
https://github.com/stackforge/pecan/zipball/master
[testenv:pecan-dev27]
basepython = python2.7
deps = {[testenv:pecan-dev-base]deps}
commands =
{envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs}
[testenv:pecan-dev35]
basepython = python3.5
deps = {[testenv:pecan-dev-base]deps}
commands =
{envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs}
[testenv:coverage]
basepython = python
deps =
coverage < 3.99
setenv =
COVERAGE_FILE=.coverage
commands =
{envbindir}/coverage erase
{envbindir}/coverage combine
{envbindir}/coverage xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/protocols/*.py wsmeext/*.py
[testenv:doc]
deps =
cloud_sptheme
Sphinx < 1.2.99
changedir =
doc
commands =
make clean ziphtml
[testenv:pep8]
deps = flake8
commands = flake8 wsme wsmeext setup.py
[testenv:venv]
commands = {posargs}
usedevelop = True
deps =
pbr
oslo.config
oslotest
[testenv:py27-sa5-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
basepython = python2.7
[testenv:py27-sa5]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
basepython = python2.7
[testenv:py27-sa5-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
simplejson
basepython = python2.7
[testenv:py27-sa5-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
simplejson
basepython = python2.7
[testenv:py27-sa5-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
basepython = python2.7
[testenv:py27-sa5-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
basepython = python2.7
[testenv:py27-sa5-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
simplejson
basepython = python2.7
[testenv:py27-sa6-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
basepython = python2.7
[testenv:py27-sa6]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
basepython = python2.7
[testenv:py27-sa6-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
simplejson
basepython = python2.7
[testenv:py27-sa6-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
simplejson
basepython = python2.7
[testenv:py27-sa6-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
basepython = python2.7
[testenv:py27-sa6-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
basepython = python2.7
[testenv:py27-sa6-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
simplejson
basepython = python2.7
[testenv:py27-sa7-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
basepython = python2.7
[testenv:py27]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
basepython = python2.7
[testenv:py27-sa7-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
simplejson
basepython = python2.7
[testenv:py27-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
simplejson
basepython = python2.7
[testenv:py27-sa7-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
basepython = python2.7
[testenv:py27-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
basepython = python2.7
[testenv:py27-sa7-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
simplejson
basepython = python2.7
[testenv:py27-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
simplejson
basepython = python2.7
[testenv:py35-sa5-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
basepython = python3.5
[testenv:py35-sa5]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
basepython = python3.5
[testenv:py35-sa5-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
simplejson
basepython = python3.5
[testenv:py35-sa5-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
simplejson
basepython = python3.5
[testenv:py35-sa5-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
basepython = python3.5
[testenv:py35-sa5-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
basepython = python3.5
[testenv:py35-sa5-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
simplejson
basepython = python3.5
[testenv:py35-sa6-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
basepython = python3.5
[testenv:py35-sa6]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
basepython = python3.5
[testenv:py35-sa6-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
simplejson
basepython = python3.5
[testenv:py35-sa6-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
simplejson
basepython = python3.5
[testenv:py35-sa6-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
basepython = python3.5
[testenv:py35-sa6-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
basepython = python3.5
[testenv:py35-sa6-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
simplejson
basepython = python3.5
[testenv:py35-sa7-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
basepython = python3.5
[testenv:py35]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
basepython = python3.5
[testenv:py35-sa7-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
simplejson
basepython = python3.5
[testenv:py35-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
simplejson
basepython = python3.5
[testenv:py35-sa7-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
basepython = python3.5
[testenv:py35-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
basepython = python3.5
[testenv:py35-sa7-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
simplejson
basepython = python3.5
[testenv:py35-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
simplejson
basepython = python3.5
[testenv:pypy-sa5-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
[testenv:pypy-sa5]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
[testenv:pypy-sa5-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
simplejson
[testenv:pypy-sa5-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
lxml
simplejson
[testenv:pypy-sa5-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
[testenv:pypy-sa5-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
[testenv:pypy-sa5-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.5.99
simplejson
[testenv:pypy-sa6-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
[testenv:pypy-sa6]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
[testenv:pypy-sa6-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
simplejson
[testenv:pypy-sa6-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
lxml
simplejson
[testenv:pypy-sa6-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
[testenv:pypy-sa6-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
[testenv:pypy-sa6-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.6.99
simplejson
[testenv:pypy-sa7-lxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
[testenv:pypy]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
[testenv:pypy-sa7-lxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
simplejson
[testenv:pypy-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
lxml
simplejson
[testenv:pypy-sa7-nolxml-json]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
[testenv:pypy-nolxml]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
[testenv:pypy-sa7-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
simplejson
[testenv:pypy-nolxml-simplejson]
commands =
{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs}
{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py
{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py
deps =
{[common]testtools}
{[common]basedeps}
suds-jurko
SQLAlchemy<=0.7.99
simplejson

144
toxgen.py
View File

@@ -1,144 +0,0 @@
"""
Produce a tox.ini file from a template config file.
The template config file is a standard tox.ini file with additional sections.
Theses sections will be combined to create new testenv: sections if they do
not exists yet.
See REAME.rst for more detail.
"""
import itertools
import collections
import optparse
try:
from configparser import ConfigParser
except:
from ConfigParser import ConfigParser # noqa
parser = optparse.OptionParser(epilog=__doc__)
parser.add_option('-i', '--input', dest='input',
default='tox-tmpl.ini', metavar='FILE')
parser.add_option('-o', '--output', dest='output',
default='tox.ini', metavar='FILE')
class AxisItem(object):
def __init__(self, axis, name, config):
self.axis = axis
self.isdefault = name[-1] == '*'
self.name = name[:-1] if self.isdefault else name
self.load(config)
def load(self, config):
sectionname = 'axis:%s:%s' % (self.axis.name, self.name)
if config.has_section(sectionname):
self.options = collections.OrderedDict(config.items(sectionname))
else:
self.options = collections.OrderedDict()
for name, value in self.axis.defaults.items():
if name not in self.options:
self.options[name] = value
class Axis(object):
def __init__(self, name, config):
self.name = name
self.load(config)
def load(self, config):
self.items = collections.OrderedDict()
values = config.get('axes', self.name).split(',')
if config.has_section('axis:%s' % self.name):
self.defaults = collections.OrderedDict(
config.items('axis:%s' % self.name)
)
else:
self.defaults = {}
for value in values:
self.items[value.strip('*')] = AxisItem(self, value, config)
def render(incfg):
axes = collections.OrderedDict()
if incfg.has_section('axes'):
for axis in incfg.options('axes'):
axes[axis] = Axis(axis, incfg)
out = ConfigParser()
for section in incfg.sections():
if section == 'axes' or section.startswith('axis:'):
continue
out.add_section(section)
for name, value in incfg.items(section):
out.set(section, name, value)
for combination in itertools.product(
*[axis.items.keys() for axis in axes.values()]):
options = collections.OrderedDict()
section_name = (
'testenv:' + '-'.join([item for item in combination if item])
)
section_alt_name = (
'testenv:' + '-'.join([
itemname
for axis, itemname in zip(axes.values(), combination)
if itemname and not axis.items[itemname].isdefault
])
)
if section_alt_name == section_name:
section_alt_name = None
axes_items = [
'%s:%s' % (axis, itemname)
for axis, itemname in zip(axes, combination)
]
for axis, itemname in zip(axes.values(), combination):
axis_options = axis.items[itemname].options
if 'constraints' in axis_options:
constraints = axis_options['constraints'].split('\n')
for c in constraints:
if c.startswith('!') and c[1:] in axes_items:
continue
for name, value in axis_options.items():
if name in options:
options[name] += value
else:
options[name] = value
constraints = options.pop('constraints', '').split('\n')
neg_constraints = [c[1:] for c in constraints if c and c[0] == '!']
if not set(neg_constraints).isdisjoint(axes_items):
continue
if not out.has_section(section_name):
out.add_section(section_name)
if (section_alt_name and not out.has_section(section_alt_name)):
out.add_section(section_alt_name)
for name, value in reversed(options.items()):
if not out.has_option(section_name, name):
out.set(section_name, name, value)
if section_alt_name and not out.has_option(section_alt_name, name):
out.set(section_alt_name, name, value)
return out
def main():
options, args = parser.parse_args()
tmpl = ConfigParser()
tmpl.read(options.input)
with open(options.output, 'wb') as outfile:
render(tmpl).write(outfile)
if __name__ == '__main__':
main()

View File

@@ -1,10 +0,0 @@
from wsme.api import signature
from wsme.rest import expose, validate
from wsme.root import WSRoot
from wsme.types import wsattr, wsproperty, Unset
__all__ = [
'expose', 'validate', 'signature',
'WSRoot',
'wsattr', 'wsproperty', 'Unset'
]

View File

@@ -1,237 +0,0 @@
import traceback
import functools
import inspect
import logging
import six
import wsme.exc
import wsme.types
from wsme import utils
log = logging.getLogger(__name__)
def iswsmefunction(f):
return hasattr(f, '_wsme_definition')
def wrapfunc(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
wrapper._wsme_original_func = f
return wrapper
def getargspec(f):
f = getattr(f, '_wsme_original_func', f)
return inspect.getargspec(f)
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
def resolve_type(self, registry):
self.datatype = registry.resolve_type(self.datatype)
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 = []
#: If the body carry the datas of a single argument, its type
self.body_type = None
#: Status code
self.status_code = 200
#: True if extra arguments should be ignored, NOT inserted in
#: the kwargs of the function and not raise UnknownArgument
#: exceptions
self.ignore_extra_args = False
#: name of the function argument to pass the host request object.
#: Should be set by using the :class:`wsme.types.HostRequest` type
#: in the function @\ :function:`signature`
self.pass_request = False
#: Dictionnary of protocol-specific options.
self.extra_options = None
@staticmethod
def get(func):
"""
Returns the :class:`FunctionDefinition` of a method.
"""
if not hasattr(func, '_wsme_definition'):
fd = FunctionDefinition(func)
func._wsme_definition = fd
return func._wsme_definition
def get_arg(self, name):
"""
Returns a :class:`FunctionArgument` from its name
"""
for arg in self.arguments:
if arg.name == name:
return arg
return None
def resolve_types(self, registry):
self.return_type = registry.resolve_type(self.return_type)
self.body_type = registry.resolve_type(self.body_type)
for arg in self.arguments:
arg.resolve_type(registry)
def set_options(self, body=None, ignore_extra_args=False, status_code=200,
rest_content_types=('json', 'xml'), **extra_options):
self.body_type = body
self.status_code = status_code
self.ignore_extra_args = ignore_extra_args
self.rest_content_types = rest_content_types
self.extra_options = extra_options
def set_arg_types(self, argspec, arg_types):
args, varargs, keywords, defaults = argspec
if args[0] == 'self':
args = args[1:]
arg_types = list(arg_types)
if self.body_type is not None:
arg_types.append(self.body_type)
for i, argname in enumerate(args):
datatype = arg_types[i]
mandatory = defaults is None or i < (len(args) - len(defaults))
default = None
if not mandatory:
default = defaults[i - (len(args) - len(defaults))]
if datatype is wsme.types.HostRequest:
self.pass_request = argname
else:
self.arguments.append(FunctionArgument(argname, datatype,
mandatory, default))
class signature(object):
"""Decorator that specify the argument types of an exposed function.
:param return_type: Type of the value returned by the function
:param argN: Type of the Nth argument
:param body: If the function takes a final argument that is supposed to be
the request body by itself, its type.
:param status_code: HTTP return status code of the function.
:param ignore_extra_args: Allow extra/unknow arguments (default to False)
Most of the time this decorator is not supposed to be used directly,
unless you are not using WSME on top of another framework.
If an adapter is used, it will provide either a specialised version of this
decororator, either a new decorator named @wsexpose that takes the same
parameters (it will in addition expose the function, hence its name).
"""
def __init__(self, *types, **options):
self.return_type = types[0] if types else None
self.arg_types = []
if len(types) > 1:
self.arg_types.extend(types[1:])
if 'body' in options:
self.arg_types.append(options['body'])
self.wrap = options.pop('wrap', False)
self.options = options
def __call__(self, func):
argspec = getargspec(func)
if self.wrap:
func = wrapfunc(func)
fd = FunctionDefinition.get(func)
if fd.extra_options is not None:
raise ValueError("This function is already exposed")
fd.return_type = self.return_type
fd.set_options(**self.options)
if self.arg_types:
fd.set_arg_types(argspec, self.arg_types)
return func
sig = signature
class Response(object):
"""
Object to hold the "response" from a view function
"""
def __init__(self, obj, status_code=None, error=None,
return_type=wsme.types.Unset):
#: Store the result object from the view
self.obj = obj
#: Store an optional status_code
self.status_code = status_code
#: Return error details
#: Must be a dictionnary with the following keys: faultcode,
#: faultstring and an optional debuginfo
self.error = error
#: Return type
#: Type of the value returned by the function
#: If the return type is wsme.types.Unset it will be ignored
#: and the default return type will prevail.
self.return_type = return_type
def format_exception(excinfo, debug=False):
"""Extract informations that can be sent to the client."""
error = excinfo[1]
code = getattr(error, 'code', None)
if code and utils.is_valid_code(code) and utils.is_client_error(code):
faultstring = (error.faultstring if hasattr(error, 'faultstring')
else six.text_type(error))
r = dict(faultcode="Client",
faultstring=faultstring)
log.debug("Client-side error: %s" % r['faultstring'])
r['debuginfo'] = None
return r
else:
faultstring = six.text_type(error)
debuginfo = "\n".join(traceback.format_exception(*excinfo))
log.error('Server-side error: "%s". Detail: \n%s' % (
faultstring, debuginfo))
r = dict(faultcode="Server", faultstring=faultstring)
if debug:
r['debuginfo'] = debuginfo
else:
r['debuginfo'] = None
return r

View File

@@ -1,92 +0,0 @@
import six
from wsme.utils import _
class ClientSideError(RuntimeError):
def __init__(self, msg=None, status_code=400):
self.msg = msg
self.code = status_code
super(ClientSideError, self).__init__(self.faultstring)
@property
def faultstring(self):
if self.msg is None:
return str(self)
elif isinstance(self.msg, six.text_type):
return self.msg
else:
return six.u(self.msg)
class InvalidInput(ClientSideError):
def __init__(self, fieldname, value, msg=''):
self.fieldname = fieldname
self.value = value
super(InvalidInput, self).__init__(msg)
@property
def faultstring(self):
return _(six.u(
"Invalid input for field/attribute %s. Value: '%s'. %s")
) % (self.fieldname, self.value, self.msg)
class MissingArgument(ClientSideError):
def __init__(self, argname, msg=''):
self.argname = argname
super(MissingArgument, self).__init__(msg)
@property
def faultstring(self):
return _(six.u('Missing argument: "%s"%s')) % (
self.argname, self.msg and ": " + self.msg or "")
class UnknownArgument(ClientSideError):
def __init__(self, argname, msg=''):
self.argname = argname
super(UnknownArgument, self).__init__(msg)
@property
def faultstring(self):
return _(six.u('Unknown argument: "%s"%s')) % (
self.argname, self.msg and ": " + self.msg or "")
class UnknownFunction(ClientSideError):
def __init__(self, name):
self.name = name
super(UnknownFunction, self).__init__()
@property
def faultstring(self):
return _(six.u("Unknown function name: %s")) % (self.name)
class UnknownAttribute(ClientSideError):
def __init__(self, fieldname, attributes, msg=''):
self.fieldname = fieldname
self.attributes = attributes
self.msg = msg
super(UnknownAttribute, self).__init__(self.msg)
@property
def faultstring(self):
error = _("Unknown attribute for argument %(argn)s: %(attrs)s")
if len(self.attributes) > 1:
error = _("Unknown attributes for argument %(argn)s: %(attrs)s")
str_attrs = ", ".join(self.attributes)
return error % {'argn': self.fieldname, 'attrs': str_attrs}
def add_fieldname(self, name):
"""Add a fieldname to concatenate the full name.
Add a fieldname so that the whole hierarchy is displayed. Successive
calls to this method will prepend ``name`` to the hierarchy of names.
"""
if self.fieldname is not None:
self.fieldname = "{}.{}".format(name, self.fieldname)
else:
self.fieldname = name
super(UnknownAttribute, self).__init__(self.msg)

View File

@@ -1,147 +0,0 @@
import weakref
import pkg_resources
from wsme.exc import ClientSideError
__all__ = [
'CallContext',
'register_protocol', 'getprotocol',
]
registered_protocols = {}
def _cfg(f):
cfg = getattr(f, '_cfg', None)
if cfg is None:
f._cfg = cfg = {}
return cfg
class expose(object):
def __init__(self, path, content_type):
self.path = path
self.content_type = content_type
def __call__(self, func):
func.exposed = True
cfg = _cfg(func)
cfg['content-type'] = self.content_type
cfg.setdefault('paths', []).append(self.path)
return func
class CallContext(object):
def __init__(self, request):
self._request = weakref.ref(request)
self.path = None
self.func = None
self.funcdef = None
@property
def request(self):
return self._request()
class ObjectDict(object):
def __init__(self, obj):
self.obj = obj
def __getitem__(self, name):
return getattr(self.obj, name)
class Protocol(object):
name = None
displayname = None
content_types = []
def resolve_path(self, path):
if '$' in path:
from string import Template
s = Template(path)
path = s.substitute(ObjectDict(self))
return path
def iter_routes(self):
for attrname in dir(self):
attr = getattr(self, attrname)
if getattr(attr, 'exposed', False):
for path in _cfg(attr)['paths']:
yield self.resolve_path(path), attr
def accept(self, request):
return request.headers.get('Content-Type') in self.content_types
def iter_calls(self, request):
pass
def extract_path(self, context):
pass
def read_arguments(self, context):
pass
def encode_result(self, context, result):
pass
def encode_sample_value(self, datatype, value, format=False):
return ('none', 'N/A')
def encode_sample_params(self, params, format=False):
return ('none', 'N/A')
def encode_sample_result(self, datatype, value, format=False):
return ('none', 'N/A')
def register_protocol(protocol):
registered_protocols[protocol.name] = protocol
def getprotocol(name, **options):
protocol_class = registered_protocols.get(name)
if protocol_class is None:
for entry_point in pkg_resources.iter_entry_points(
'wsme.protocols', name):
if entry_point.name == name:
protocol_class = entry_point.load()
if protocol_class is None:
raise ValueError("Cannot find protocol '%s'" % name)
registered_protocols[name] = protocol_class
return protocol_class(**options)
def media_type_accept(request, content_types):
"""Validate media types against request.method.
When request.method is GET or HEAD compare with the Accept header.
When request.method is POST, PUT or PATCH compare with the Content-Type
header.
When request.method is DELETE media type is irrelevant, so return True.
"""
if request.method in ['GET', 'HEAD']:
if request.accept:
if request.accept.best_match(content_types):
return True
error_message = ('Unacceptable Accept type: %s not in %s'
% (request.accept, content_types))
raise ClientSideError(error_message, status_code=406)
elif request.method in ['PUT', 'POST', 'PATCH']:
content_type = request.headers.get('Content-Type')
if content_type:
for ct in content_types:
if request.headers.get('Content-Type', '').startswith(ct):
return True
error_message = ('Unacceptable Content-Type: %s not in %s'
% (content_type, content_types))
raise ClientSideError(error_message, status_code=415)
else:
raise ClientSideError('missing Content-Type header')
elif request.method in ['DELETE']:
return True
return False

View File

@@ -1,78 +0,0 @@
import inspect
import wsme.api
APIPATH_MAXLEN = 20
class expose(object):
def __init__(self, *args, **kwargs):
self.signature = wsme.api.signature(*args, **kwargs)
def __call__(self, func):
return self.signature(func)
@classmethod
def with_method(cls, method, *args, **kwargs):
kwargs['method'] = method
return cls(*args, **kwargs)
@classmethod
def get(cls, *args, **kwargs):
return cls.with_method('GET', *args, **kwargs)
@classmethod
def post(cls, *args, **kwargs):
return cls.with_method('POST', *args, **kwargs)
@classmethod
def put(cls, *args, **kwargs):
return cls.with_method('PUT', *args, **kwargs)
@classmethod
def delete(cls, *args, **kwargs):
return cls.with_method('DELETE', *args, **kwargs)
class validate(object):
"""
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):
argspec = wsme.api.getargspec(func)
fd = wsme.api.FunctionDefinition.get(func)
fd.set_arg_types(argspec, self.param_types)
return func
def scan_api(controller, path=[], objects=[]):
"""
Recursively iterate a controller api entries.
"""
for name in dir(controller):
if name.startswith('_'):
continue
a = getattr(controller, name)
if a in objects:
continue
if inspect.ismethod(a):
if wsme.api.iswsmefunction(a):
yield path + [name], a, []
elif inspect.isclass(a):
continue
else:
if len(path) > APIPATH_MAXLEN:
raise ValueError("Path is too long: " + str(path))
for i in scan_api(a, path + [name], objects + [a]):
yield i

View File

@@ -1,310 +0,0 @@
import cgi
import datetime
import re
from simplegeneric import generic
from wsme.exc import ClientSideError, UnknownArgument, InvalidInput
from wsme.types import iscomplex, list_attributes, Unset
from wsme.types import UserType, ArrayType, DictType, File
from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime
import wsme.runtime
from six import moves
ARRAY_MAX_SIZE = 1000
@generic
def from_param(datatype, value):
return datatype(value) if value is not None else None
@from_param.when_object(datetime.date)
def date_from_param(datatype, value):
return parse_isodate(value) if value else None
@from_param.when_object(datetime.time)
def time_from_param(datatype, value):
return parse_isotime(value) if value else None
@from_param.when_object(datetime.datetime)
def datetime_from_param(datatype, value):
return parse_isodatetime(value) if value else None
@from_param.when_object(File)
def filetype_from_param(datatype, value):
if isinstance(value, cgi.FieldStorage):
return File(fieldstorage=value)
return File(content=value)
@from_param.when_type(UserType)
def usertype_from_param(datatype, value):
return datatype.frombasetype(
from_param(datatype.basetype, value))
@from_param.when_type(ArrayType)
def array_from_param(datatype, value):
if value is None:
return value
return [
from_param(datatype.item_type, item)
for item in value
]
@generic
def from_params(datatype, params, path, hit_paths):
if iscomplex(datatype) and datatype is not File:
objfound = False
for key in params:
if key.startswith(path + '.'):
objfound = True
break
if objfound:
r = datatype()
for attrdef in list_attributes(datatype):
value = from_params(
attrdef.datatype,
params, '%s.%s' % (path, attrdef.key), hit_paths
)
if value is not Unset:
setattr(r, attrdef.key, value)
return r
else:
if path in params:
hit_paths.add(path)
return from_param(datatype, params[path])
return Unset
@from_params.when_type(ArrayType)
def array_from_params(datatype, params, path, hit_paths):
if hasattr(params, 'getall'):
# webob multidict
def getall(params, path):
return params.getall(path)
elif hasattr(params, 'getlist'):
# werkzeug multidict
def getall(params, path): # noqa
return params.getlist(path)
if path in params:
hit_paths.add(path)
return [
from_param(datatype.item_type, value)
for value in getall(params, path)]
if iscomplex(datatype.item_type):
attributes = set()
r = re.compile('^%s\.(?P<attrname>[^\.])' % re.escape(path))
for p in params.keys():
m = r.match(p)
if m:
attributes.add(m.group('attrname'))
if attributes:
value = []
for attrdef in list_attributes(datatype.item_type):
attrpath = '%s.%s' % (path, attrdef.key)
hit_paths.add(attrpath)
attrvalues = getall(params, attrpath)
if len(value) < len(attrvalues):
value[-1:] = [
datatype.item_type()
for i in moves.range(len(attrvalues) - len(value))
]
for i, attrvalue in enumerate(attrvalues):
setattr(
value[i],
attrdef.key,
from_param(attrdef.datatype, attrvalue)
)
return value
indexes = set()
r = re.compile('^%s\[(?P<index>\d+)\]' % re.escape(path))
for p in params.keys():
m = r.match(p)
if m:
indexes.add(int(m.group('index')))
if not indexes:
return Unset
indexes = list(indexes)
indexes.sort()
return [from_params(datatype.item_type, params,
'%s[%s]' % (path, index), hit_paths)
for index in indexes]
@from_params.when_type(DictType)
def dict_from_params(datatype, params, path, hit_paths):
keys = set()
r = re.compile('^%s\[(?P<key>[a-zA-Z0-9_\.]+)\]' % re.escape(path))
for p in params.keys():
m = r.match(p)
if m:
keys.add(from_param(datatype.key_type, m.group('key')))
if not keys:
return Unset
return dict((
(key, from_params(datatype.value_type,
params, '%s[%s]' % (path, key), hit_paths))
for key in keys))
@from_params.when_type(UserType)
def usertype_from_params(datatype, params, path, hit_paths):
value = from_params(datatype.basetype, params, path, hit_paths)
if value is not Unset:
return datatype.frombasetype(value)
return Unset
def args_from_args(funcdef, args, kwargs):
newargs = []
for argdef, arg in zip(funcdef.arguments[:len(args)], args):
try:
newargs.append(from_param(argdef.datatype, arg))
except Exception as e:
if isinstance(argdef.datatype, UserType):
datatype_name = argdef.datatype.name
elif isinstance(argdef.datatype, type):
datatype_name = argdef.datatype.__name__
else:
datatype_name = argdef.datatype.__class__.__name__
raise InvalidInput(
argdef.name,
arg,
"unable to convert to %(datatype)s. Error: %(error)s" % {
'datatype': datatype_name, 'error': e})
newkwargs = {}
for argname, value in kwargs.items():
newkwargs[argname] = from_param(
funcdef.get_arg(argname).datatype, value
)
return newargs, newkwargs
def args_from_params(funcdef, params):
kw = {}
hit_paths = set()
for argdef in funcdef.arguments:
value = from_params(
argdef.datatype, params, argdef.name, hit_paths)
if value is not Unset:
kw[argdef.name] = value
paths = set(params.keys())
unknown_paths = paths - hit_paths
if '__body__' in unknown_paths:
unknown_paths.remove('__body__')
if not funcdef.ignore_extra_args and unknown_paths:
raise UnknownArgument(', '.join(unknown_paths))
return [], kw
def args_from_body(funcdef, body, mimetype):
from wsme.rest import json as restjson
from wsme.rest import xml as restxml
if funcdef.body_type is not None:
datatypes = {funcdef.arguments[-1].name: funcdef.body_type}
else:
datatypes = dict(((a.name, a.datatype) for a in funcdef.arguments))
if not body:
return (), {}
if mimetype == "application/x-www-form-urlencoded":
# the parameters should have been parsed in params
return (), {}
elif mimetype in restjson.accept_content_types:
dataformat = restjson
elif mimetype in restxml.accept_content_types:
dataformat = restxml
else:
raise ClientSideError("Unknown mimetype: %s" % mimetype,
status_code=415)
try:
kw = dataformat.parse(
body, datatypes, bodyarg=funcdef.body_type is not None
)
except UnknownArgument:
if not funcdef.ignore_extra_args:
raise
kw = {}
return (), kw
def combine_args(funcdef, akw, allow_override=False):
newargs, newkwargs = [], {}
for args, kwargs in akw:
for i, arg in enumerate(args):
n = funcdef.arguments[i].name
if not allow_override and n in newkwargs:
raise ClientSideError(
"Parameter %s was given several times" % n)
newkwargs[n] = arg
for name, value in kwargs.items():
n = str(name)
if not allow_override and n in newkwargs:
raise ClientSideError(
"Parameter %s was given several times" % n)
newkwargs[n] = value
return newargs, newkwargs
def get_args(funcdef, args, kwargs, params, form, body, mimetype):
"""Combine arguments from :
* the host framework args and kwargs
* the request params
* the request body
Note that the host framework args and kwargs can be overridden
by arguments from params of body
"""
# get the body from params if not given directly
if not body and '__body__' in params:
body = params['__body__']
# extract args from the host args and kwargs
from_args = args_from_args(funcdef, args, kwargs)
# extract args from the request parameters
from_params = args_from_params(funcdef, params)
# extract args from the form parameters
if form:
from_form_params = args_from_params(funcdef, form)
else:
from_form_params = (), {}
# extract args from the request body
from_body = args_from_body(funcdef, body, mimetype)
# combine params and body arguments
from_params_and_body = combine_args(
funcdef,
(from_params, from_form_params, from_body)
)
args, kwargs = combine_args(
funcdef,
(from_args, from_params_and_body),
allow_override=True
)
wsme.runtime.check_arguments(funcdef, args, kwargs)
return args, kwargs

View File

@@ -1,328 +0,0 @@
"""REST+Json protocol implementation."""
from __future__ import absolute_import
import datetime
import decimal
import six
from simplegeneric import generic
import wsme.exc
import wsme.types
from wsme.types import Unset
import wsme.utils
try:
import simplejson as json
except ImportError:
import json # noqa
content_type = 'application/json'
accept_content_types = [
content_type,
'text/javascript',
'application/javascript'
]
ENUM_TRUE = ('true', 't', 'yes', 'y', 'on', '1')
ENUM_FALSE = ('false', 'f', 'no', 'n', 'off', '0')
@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 value is None:
return None
if wsme.types.iscomplex(datatype):
d = dict()
for attr in wsme.types.list_attributes(datatype):
attr_value = getattr(value, attr.key)
if attr_value is not Unset:
d[attr.name] = tojson(attr.datatype, attr_value)
return d
elif wsme.types.isusertype(datatype):
return tojson(datatype.basetype, datatype.tobasetype(value))
return value
@tojson.when_object(wsme.types.bytes)
def bytes_tojson(datatype, value):
if value is None:
return None
return value.decode('ascii')
@tojson.when_type(wsme.types.ArrayType)
def array_tojson(datatype, value):
if value is None:
return None
return [tojson(datatype.item_type, item) for item in value]
@tojson.when_type(wsme.types.DictType)
def dict_tojson(datatype, value):
if value is None:
return None
return dict((
(tojson(datatype.key_type, item[0]),
tojson(datatype.value_type, item[1]))
for item in value.items()
))
@tojson.when_object(decimal.Decimal)
def decimal_tojson(datatype, value):
if value is None:
return None
return str(value)
@tojson.when_object(datetime.date)
def date_tojson(datatype, value):
if value is None:
return None
return value.isoformat()
@tojson.when_object(datetime.time)
def time_tojson(datatype, value):
if value is None:
return None
return value.isoformat()
@tojson.when_object(datetime.datetime)
def datetime_tojson(datatype, value):
if value is None:
return None
return value.isoformat()
@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):
obj = datatype()
attributes = wsme.types.list_attributes(datatype)
# Here we check that all the attributes in the value are also defined
# in our type definition, otherwise we raise an Error.
v_keys = set(value.keys())
a_keys = set(adef.name for adef in attributes)
if not v_keys <= a_keys:
raise wsme.exc.UnknownAttribute(None, v_keys - a_keys)
for attrdef in attributes:
if attrdef.name in value:
try:
val_fromjson = fromjson(attrdef.datatype,
value[attrdef.name])
except wsme.exc.UnknownAttribute as e:
e.add_fieldname(attrdef.name)
raise
if getattr(attrdef, 'readonly', False):
raise wsme.exc.InvalidInput(attrdef.name, val_fromjson,
"Cannot set read only field.")
setattr(obj, attrdef.key, val_fromjson)
elif attrdef.mandatory:
raise wsme.exc.InvalidInput(attrdef.name, None,
"Mandatory field missing.")
return wsme.types.validate_value(datatype, obj)
elif wsme.types.isusertype(datatype):
value = datatype.frombasetype(
fromjson(datatype.basetype, value))
return value
@fromjson.when_type(wsme.types.ArrayType)
def array_fromjson(datatype, value):
if value is None:
return None
if not isinstance(value, list):
raise ValueError("Value not a valid list: %s" % value)
return [fromjson(datatype.item_type, item) for item in value]
@fromjson.when_type(wsme.types.DictType)
def dict_fromjson(datatype, value):
if value is None:
return None
if not isinstance(value, dict):
raise ValueError("Value not a valid dict: %s" % value)
return dict((
(fromjson(datatype.key_type, item[0]),
fromjson(datatype.value_type, item[1]))
for item in value.items()))
@fromjson.when_object(six.binary_type)
def str_fromjson(datatype, value):
if (isinstance(value, six.string_types) or
isinstance(value, six.integer_types) or
isinstance(value, float)):
return six.text_type(value).encode('utf8')
@fromjson.when_object(wsme.types.text)
def text_fromjson(datatype, value):
if value is not None and isinstance(value, wsme.types.bytes):
return wsme.types.text(value)
return value
@fromjson.when_object(*six.integer_types + (float,))
def numeric_fromjson(datatype, value):
"""Convert string object to built-in types int, long or float."""
if value is None:
return None
return datatype(value)
@fromjson.when_object(bool)
def bool_fromjson(datatype, value):
"""Convert to bool, restricting strings to just unambiguous values."""
if value is None:
return None
if isinstance(value, six.integer_types + (bool,)):
return bool(value)
if value in ENUM_TRUE:
return True
if value in ENUM_FALSE:
return False
raise ValueError("Value not an unambiguous boolean: %s" % value)
@fromjson.when_object(decimal.Decimal)
def decimal_fromjson(datatype, value):
if value is None:
return None
return decimal.Decimal(value)
@fromjson.when_object(datetime.date)
def date_fromjson(datatype, value):
if value is None:
return None
return wsme.utils.parse_isodate(value)
@fromjson.when_object(datetime.time)
def time_fromjson(datatype, value):
if value is None:
return None
return wsme.utils.parse_isotime(value)
@fromjson.when_object(datetime.datetime)
def datetime_fromjson(datatype, value):
if value is None:
return None
return wsme.utils.parse_isodatetime(value)
def parse(s, datatypes, bodyarg, encoding='utf8'):
jload = json.load
if not hasattr(s, 'read'):
if six.PY3 and isinstance(s, six.binary_type):
s = s.decode(encoding)
jload = json.loads
try:
jdata = jload(s)
except ValueError:
raise wsme.exc.ClientSideError("Request is not in valid JSON format")
if bodyarg:
argname = list(datatypes.keys())[0]
try:
kw = {argname: fromjson(datatypes[argname], jdata)}
except ValueError as e:
raise wsme.exc.InvalidInput(argname, jdata, e.args[0])
except wsme.exc.UnknownAttribute as e:
# We only know the fieldname at this level, not in the
# called function. We fill in this information here.
e.add_fieldname(argname)
raise
else:
kw = {}
extra_args = []
if not isinstance(jdata, dict):
raise wsme.exc.ClientSideError("Request must be a JSON dict")
for key in jdata:
if key not in datatypes:
extra_args.append(key)
else:
try:
kw[key] = fromjson(datatypes[key], jdata[key])
except ValueError as e:
raise wsme.exc.InvalidInput(key, jdata[key], e.args[0])
except wsme.exc.UnknownAttribute as e:
# We only know the fieldname at this level, not in the
# called function. We fill in this information here.
e.add_fieldname(key)
raise
if extra_args:
raise wsme.exc.UnknownArgument(', '.join(extra_args))
return kw
def encode_result(value, datatype, **options):
jsondata = tojson(datatype, value)
if options.get('nest_result', False):
jsondata = {options.get('nested_result_attrname', 'result'): jsondata}
return json.dumps(jsondata)
def encode_error(context, errordetail):
return json.dumps(errordetail)
def encode_sample_value(datatype, value, format=False):
r = tojson(datatype, value)
content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0,
sort_keys=format)
return ('javascript', content)
def encode_sample_params(params, format=False):
kw = {}
for name, datatype, value in params:
kw[name] = tojson(datatype, value)
content = json.dumps(kw, ensure_ascii=False, indent=4 if format else 0,
sort_keys=format)
return ('javascript', content)
def encode_sample_result(datatype, value, format=False):
r = tojson(datatype, value)
content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0,
sort_keys=format)
return ('javascript', content)

View File

@@ -1,133 +0,0 @@
import os.path
import logging
from wsme.utils import OrderedDict
from wsme.protocol import CallContext, Protocol, media_type_accept
import wsme.rest
import wsme.rest.args
import wsme.runtime
log = logging.getLogger(__name__)
class RestProtocol(Protocol):
name = 'rest'
displayname = 'REST'
dataformats = ['json', 'xml']
content_types = ['application/json', 'text/xml']
def __init__(self, dataformats=None):
if dataformats is None:
dataformats = RestProtocol.dataformats
self.dataformats = OrderedDict()
self.content_types = []
for dataformat in dataformats:
__import__('wsme.rest.' + dataformat)
dfmod = getattr(wsme.rest, dataformat)
self.dataformats[dataformat] = dfmod
self.content_types.extend(dfmod.accept_content_types)
def accept(self, request):
for dataformat in self.dataformats:
if request.path.endswith('.' + dataformat):
return True
return media_type_accept(request, self.content_types)
def iter_calls(self, request):
context = CallContext(request)
context.outformat = None
ext = os.path.splitext(request.path.split('/')[-1])[1]
inmime = request.content_type
outmime = request.accept.best_match(self.content_types)
outformat = None
informat = None
for dfname, df in self.dataformats.items():
if ext == '.' + dfname:
outformat = df
if not inmime:
informat = df
if outformat is None and request.accept:
for dfname, df in self.dataformats.items():
if outmime in df.accept_content_types:
outformat = df
if not inmime:
informat = df
if outformat is None:
for dfname, df in self.dataformats.items():
if inmime == df.content_type:
outformat = df
context.outformat = outformat
context.outformat_options = {
'nest_result': getattr(self, 'nest_result', False)
}
if not inmime and informat:
inmime = informat.content_type
log.debug("Inferred input type: %s" % inmime)
context.inmime = inmime
yield context
def extract_path(self, context):
path = context.request.path
assert path.startswith(self.root._webpath)
path = path[len(self.root._webpath):]
path = path.strip('/').split('/')
for dataformat in self.dataformats:
if path[-1].endswith('.' + dataformat):
path[-1] = path[-1][:-len(dataformat) - 1]
# Check if the path is actually a function, and if not
# see if the http method make a difference
# TODO Re-think the function lookup phases. Here we are
# doing the job that will be done in a later phase, which
# is sub-optimal
for p, fdef in self.root.getapi():
if p == path:
return path
# No function at this path. Now check for function that have
# this path as a prefix, and declared an http method
for p, fdef in self.root.getapi():
if len(p) == len(path) + 1 and p[:len(path)] == path and \
fdef.extra_options.get('method') == context.request.method:
return p
return path
def read_arguments(self, context):
request = context.request
funcdef = context.funcdef
body = None
if request.content_length not in (None, 0, '0'):
body = request.body
if not body and '__body__' in request.params:
body = request.params['__body__']
args, kwargs = wsme.rest.args.combine_args(
funcdef,
(wsme.rest.args.args_from_params(funcdef, request.params),
wsme.rest.args.args_from_body(funcdef, body, context.inmime))
)
wsme.runtime.check_arguments(funcdef, args, kwargs)
return kwargs
def encode_result(self, context, result):
out = context.outformat.encode_result(
result, context.funcdef.return_type,
**context.outformat_options
)
return out
def encode_error(self, context, errordetail):
out = context.outformat.encode_error(
context, errordetail
)
return out

View File

@@ -1,298 +0,0 @@
from __future__ import absolute_import
import datetime
import six
import xml.etree.ElementTree as et
from simplegeneric import generic
import wsme.types
from wsme.exc import UnknownArgument, InvalidInput
import re
content_type = 'text/xml'
accept_content_types = [
content_type,
]
time_re = re.compile(r'(?P<h>[0-2][0-9]):(?P<m>[0-5][0-9]):(?P<s>[0-6][0-9])')
def xml_indent(elem, level=0):
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
for e in elem:
xml_indent(e, level + 1)
if not e.tail or not e.tail.strip():
e.tail = i
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
@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')
else:
if wsme.types.isusertype(datatype):
return toxml(datatype.basetype,
key, datatype.tobasetype(value))
elif wsme.types.iscomplex(datatype):
for attrdef in datatype._wsme_attributes:
attrvalue = getattr(value, attrdef.key)
if attrvalue is not wsme.types.Unset:
el.append(toxml(attrdef.datatype, attrdef.name,
attrvalue))
else:
el.text = six.text_type(value)
return el
@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.isusertype(datatype):
return datatype.frombasetype(fromxml(datatype.basetype, element))
if wsme.types.iscomplex(datatype):
obj = datatype()
for attrdef in wsme.types.list_attributes(datatype):
sub = element.find(attrdef.name)
if sub is not None:
val_fromxml = fromxml(attrdef.datatype, sub)
if getattr(attrdef, 'readonly', False):
raise InvalidInput(attrdef.name, val_fromxml,
"Cannot set read only field.")
setattr(obj, attrdef.key, val_fromxml)
elif attrdef.mandatory:
raise InvalidInput(attrdef.name, None,
"Mandatory field missing.")
return wsme.types.validate_value(datatype, obj)
if datatype is wsme.types.bytes:
return element.text.encode('ascii')
return datatype(element.text)
@toxml.when_type(wsme.types.ArrayType)
def array_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
for item in value:
el.append(toxml(datatype.item_type, 'item', item))
return el
@toxml.when_type(wsme.types.DictType)
def dict_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
for item in value.items():
key = toxml(datatype.key_type, 'key', item[0])
value = toxml(datatype.value_type, 'value', item[1])
node = et.Element('item')
node.append(key)
node.append(value)
el.append(node)
return el
@toxml.when_object(wsme.types.bytes)
def bytes_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
el.text = value.decode('ascii')
return el
@toxml.when_object(bool)
def bool_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
el.text = value and 'true' or 'false'
return el
@toxml.when_object(datetime.date)
def date_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
el.text = value.isoformat()
return el
@toxml.when_object(datetime.datetime)
def datetime_toxml(datatype, key, value):
el = et.Element(key)
if value is None:
el.set('nil', 'true')
else:
el.text = value.isoformat()
return el
@fromxml.when_type(wsme.types.ArrayType)
def array_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return [
fromxml(datatype.item_type, item)
for item in element.findall('item')
]
@fromxml.when_object(bool)
def bool_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return element.text.lower() != 'false'
@fromxml.when_type(wsme.types.DictType)
def dict_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return dict((
(fromxml(datatype.key_type, item.find('key')),
fromxml(datatype.value_type, item.find('value')))
for item in element.findall('item')))
@fromxml.when_object(wsme.types.text)
def unicode_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return wsme.types.text(element.text) if element.text else six.u('')
@fromxml.when_object(datetime.date)
def date_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return wsme.utils.parse_isodate(element.text)
@fromxml.when_object(datetime.time)
def time_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return wsme.utils.parse_isotime(element.text)
@fromxml.when_object(datetime.datetime)
def datetime_fromxml(datatype, element):
if element.get('nil') == 'true':
return None
return wsme.utils.parse_isodatetime(element.text)
def parse(s, datatypes, bodyarg):
if hasattr(s, 'read'):
tree = et.parse(s)
else:
tree = et.fromstring(s)
if bodyarg:
name = list(datatypes.keys())[0]
return {name: fromxml(datatypes[name], tree)}
else:
kw = {}
extra_args = []
for sub in tree:
if sub.tag not in datatypes:
extra_args.append(sub.tag)
kw[sub.tag] = fromxml(datatypes[sub.tag], sub)
if extra_args:
raise UnknownArgument(', '.join(extra_args))
return kw
def encode_result(value, datatype, **options):
return et.tostring(toxml(
datatype, options.get('nested_result_attrname', 'result'), value
))
def encode_error(context, errordetail):
el = et.Element('error')
et.SubElement(el, 'faultcode').text = errordetail['faultcode']
et.SubElement(el, 'faultstring').text = errordetail['faultstring']
if 'debuginfo' in errordetail:
et.SubElement(el, 'debuginfo').text = errordetail['debuginfo']
return et.tostring(el)
def encode_sample_value(datatype, value, format=False):
r = toxml(datatype, 'value', value)
if format:
xml_indent(r)
content = et.tostring(r)
return ('xml', content)
def encode_sample_params(params, format=False):
node = et.Element('parameters')
for name, datatype, value in params:
node.append(toxml(datatype, name, value))
if format:
xml_indent(node)
content = et.tostring(node)
return ('xml', content)
def encode_sample_result(datatype, value, format=False):
r = toxml(datatype, 'result', value)
if format:
xml_indent(r)
content = et.tostring(r)
return ('xml', content)

View File

@@ -1,372 +0,0 @@
import logging
import sys
import weakref
from six import u, b
import six
import webob
from wsme.exc import ClientSideError, UnknownFunction
from wsme.protocol import getprotocol
from wsme.rest import scan_api
from wsme import spore
import wsme.api
import wsme.types
log = logging.getLogger(__name__)
html_body = u("""
<html>
<head>
<style type='text/css'>
%(css)s
</style>
</head>
<body>
%(content)s
</body>
</html>
""")
def default_prepare_response_body(request, results):
r = None
sep = None
for value in results:
if sep is None:
if isinstance(value, six.text_type):
sep = u('\n')
r = u('')
else:
sep = b('\n')
r = b('')
else:
r += sep
r += value
return r
class DummyTransaction:
def commit(self):
pass
def abort(self):
pass
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.
:type transaction: A `transaction
<http://pypi.python.org/pypi/transaction>`_-like
object or ``True``.
:param transaction: If specified, a transaction will be created and
handled on a per-call base.
This option *can* be enabled along with `repoze.tm2
<http://pypi.python.org/pypi/repoze.tm2>`_
(it will only make it void).
If ``True``, the default :mod:`transaction`
module will be imported and used.
"""
__registry__ = wsme.types.registry
def __init__(self, protocols=[], webpath='', transaction=None,
scan_api=scan_api):
self._debug = True
self._webpath = webpath
self.protocols = []
self._scan_api = scan_api
self._transaction = transaction
if self._transaction is True:
import transaction
self._transaction = transaction
for protocol in protocols:
self.addprotocol(protocol)
self._api = None
def wsgiapp(self):
"""Returns a wsgi application"""
from webob.dec import wsgify
return wsgify(self._handle_request)
def begin(self):
if self._transaction:
return self._transaction.begin()
else:
return DummyTransaction()
def addprotocol(self, protocol, **options):
"""
Enable a new protocol on the controller.
:param protocol: A registered protocol name or an instance
of a protocol.
"""
if isinstance(protocol, str):
protocol = getprotocol(protocol, **options)
self.protocols.append(protocol)
protocol.root = weakref.proxy(self)
def getapi(self):
"""
Returns the api description.
:rtype: list of (path, :class:`FunctionDefinition`)
"""
if self._api is None:
self._api = [
(path, f, f._wsme_definition, args)
for path, f, args in self._scan_api(self)
]
for path, f, fdef, args in self._api:
fdef.resolve_types(self.__registry__)
return [
(path, fdef)
for path, f, fdef, args in self._api
]
def _get_protocol(self, name):
for protocol in self.protocols:
if protocol.name == name:
return protocol
def _select_protocol(self, request):
log.debug("Selecting a protocol for the following request :\n"
"headers: %s\nbody: %s", request.headers.items(),
request.content_length and (
request.content_length > 512 and
request.body[:512] or
request.body) or '')
protocol = None
error = ClientSideError(status_code=406)
path = str(request.path)
assert path.startswith(self._webpath)
path = path[len(self._webpath) + 1:]
if 'wsmeproto' in request.params:
return self._get_protocol(request.params['wsmeproto'])
else:
for p in self.protocols:
try:
if p.accept(request):
protocol = p
break
except ClientSideError as e:
error = e
# If we could not select a protocol, we raise the last exception
# that we got, or the default one.
if not protocol:
raise error
return protocol
def _do_call(self, protocol, context):
request = context.request
request.calls.append(context)
try:
if context.path is None:
context.path = protocol.extract_path(context)
if context.path is None:
raise ClientSideError(u(
'The %s protocol was unable to extract a function '
'path from the request') % protocol.name)
context.func, context.funcdef, args = \
self._lookup_function(context.path)
kw = protocol.read_arguments(context)
args = list(args)
txn = self.begin()
try:
result = context.func(*args, **kw)
txn.commit()
except:
txn.abort()
raise
else:
# TODO make sure result type == a._wsme_definition.return_type
return protocol.encode_result(context, result)
except Exception as e:
infos = wsme.api.format_exception(sys.exc_info(), self._debug)
if isinstance(e, ClientSideError):
request.client_errorcount += 1
request.client_last_status_code = e.code
else:
request.server_errorcount += 1
return protocol.encode_error(context, infos)
def find_route(self, path):
for p in self.protocols:
for routepath, func in p.iter_routes():
if path.startswith(routepath):
return routepath, func
return None, None
def _handle_request(self, request):
res = webob.Response()
res_content_type = None
path = request.path
if path.startswith(self._webpath):
path = path[len(self._webpath):]
routepath, func = self.find_route(path)
if routepath:
content = func()
if isinstance(content, six.text_type):
res.text = content
elif isinstance(content, six.binary_type):
res.body = content
res.content_type = func._cfg['content-type']
return res
if request.path == self._webpath + '/api.spore':
res.body = spore.getdesc(self, request.host_url)
res.content_type = 'application/json'
return res
try:
msg = None
error_status = 500
protocol = self._select_protocol(request)
except ClientSideError as e:
error_status = e.code
msg = e.faultstring
protocol = None
except Exception as e:
msg = ("Unexpected error while selecting protocol: %s" % str(e))
log.exception(msg)
protocol = None
error_status = 500
if protocol is None:
if not msg:
msg = ("None of the following protocols can handle this "
"request : %s" % ','.join((
p.name for p in self.protocols)))
res.status = error_status
res.content_type = 'text/plain'
try:
res.text = u(msg)
except TypeError:
res.text = msg
log.error(msg)
return res
request.calls = []
request.client_errorcount = 0
request.client_last_status_code = None
request.server_errorcount = 0
try:
context = None
if hasattr(protocol, 'prepare_response_body'):
prepare_response_body = protocol.prepare_response_body
else:
prepare_response_body = default_prepare_response_body
body = prepare_response_body(request, (
self._do_call(protocol, context)
for context in protocol.iter_calls(request)))
if isinstance(body, six.text_type):
res.text = body
else:
res.body = body
if len(request.calls) == 1:
if hasattr(protocol, 'get_response_status'):
res.status = protocol.get_response_status(request)
else:
if request.client_errorcount == 1:
res.status = request.client_last_status_code
elif request.client_errorcount:
res.status = 400
elif request.server_errorcount:
res.status = 500
else:
res.status = 200
else:
res.status = protocol.get_response_status(request)
res_content_type = protocol.get_response_contenttype(request)
except ClientSideError as e:
request.server_errorcount += 1
res.status = e.code
res.text = e.faultstring
except Exception:
infos = wsme.api.format_exception(sys.exc_info(), self._debug)
request.server_errorcount += 1
res.text = protocol.encode_error(context, infos)
res.status = 500
if res_content_type is None:
# Attempt to correctly guess what content-type we should return.
ctypes = [ct for ct in protocol.content_types if ct]
if ctypes:
res_content_type = request.accept.best_match(ctypes)
# If not we will attempt to convert the body to an accepted
# output format.
if res_content_type is None:
if "text/html" in request.accept:
res.text = self._html_format(res.body, protocol.content_types)
res_content_type = "text/html"
# TODO should we consider the encoding asked by
# the web browser ?
res.headers['Content-Type'] = "%s; charset=UTF-8" % res_content_type
return res
def _lookup_function(self, path):
if not self._api:
self.getapi()
for fpath, f, fdef, args in self._api:
if path == fpath:
return f, fdef, args
raise UnknownFunction('/'.join(path))
def _html_format(self, content, content_types):
try:
from pygments import highlight
from pygments.lexers import get_lexer_for_mimetype
from pygments.formatters import HtmlFormatter
lexer = None
for ct in content_types:
try:
lexer = get_lexer_for_mimetype(ct)
break
except:
pass
if lexer is None:
raise ValueError("No lexer found")
formatter = HtmlFormatter()
return html_body % dict(
css=formatter.get_style_defs(),
content=highlight(content, lexer, formatter).encode('utf8'))
except Exception as e:
log.warning(
"Could not pygment the content because of the following "
"error :\n%s" % e)
return html_body % dict(
css='',
content=u('<pre>%s</pre>') %
content.replace(b('>'), b('&gt;'))
.replace(b('<'), b('&lt;')))

View File

@@ -1,9 +0,0 @@
from wsme.exc import MissingArgument
def check_arguments(funcdef, args, kw):
"""Check if some arguments are missing"""
assert len(args) == 0
for arg in funcdef.arguments:
if arg.mandatory and arg.name not in kw:
raise MissingArgument(arg.name)

View File

@@ -1,64 +0,0 @@
from wsme import types
try:
import simplejson as json
except ImportError:
import json # noqa
def getdesc(root, host_url=''):
methods = {}
for path, funcdef in root.getapi():
method = funcdef.extra_options.get('method', None)
name = '_'.join(path)
if method is not None:
path = path[:-1]
else:
method = 'GET'
for argdef in funcdef.arguments:
if types.iscomplex(argdef.datatype) \
or types.isarray(argdef.datatype) \
or types.isdict(argdef.datatype):
method = 'POST'
break
required_params = []
optional_params = []
for argdef in funcdef.arguments:
if method == 'GET' and argdef.mandatory:
required_params.append(argdef.name)
else:
optional_params.append(argdef.name)
methods[name] = {
'method': method,
'path': '/'.join(path)
}
if required_params:
methods[name]['required_params'] = required_params
if optional_params:
methods[name]['optional_params'] = optional_params
if funcdef.doc:
methods[name]['documentation'] = funcdef.doc
formats = []
for p in root.protocols:
if p.name == 'restxml':
formats.append('xml')
if p.name == 'restjson':
formats.append('json')
api = {
'base_url': host_url + root._webpath,
'version': '0.1',
'name': getattr(root, 'name', 'name'),
'authority': '',
'formats': [
'json',
'xml'
],
'methods': methods
}
return json.dumps(api, indent=4)

View File

View File

@@ -1,720 +0,0 @@
# coding=utf-8
import unittest
import warnings
import datetime
import decimal
import six
from six import u, b
from webtest import TestApp
from wsme import WSRoot, Unset
from wsme import expose, validate
import wsme.types
import wsme.utils
warnings.filterwarnings('ignore', module='webob.dec')
binarysample = b('\x00\xff\x43')
try:
1 / 0
except ZeroDivisionError as e:
zerodivisionerrormsg = str(e)
class CallException(RuntimeError):
def __init__(self, faultcode, faultstring, debuginfo):
self.faultcode = faultcode
self.faultstring = faultstring
self.debuginfo = debuginfo
def __str__(self):
return 'faultcode=%s, faultstring=%s, debuginfo=%s' % (
self.faultcode, self.faultstring, self.debuginfo
)
myenumtype = wsme.types.Enum(wsme.types.bytes, 'v1', 'v2')
class NestedInner(object):
aint = int
def __init__(self, aint=None):
self.aint = aint
class NestedOuter(object):
inner = NestedInner
inner_array = wsme.types.wsattr([NestedInner])
inner_dict = {wsme.types.text: NestedInner}
def __init__(self):
self.inner = NestedInner(0)
class NamedAttrsObject(object):
def __init__(self, v1=Unset, v2=Unset):
self.attr_1 = v1
self.attr_2 = v2
attr_1 = wsme.types.wsattr(int, name='attr.1')
attr_2 = wsme.types.wsattr(int, name='attr.2')
class CustomObject(object):
aint = int
name = wsme.types.text
class ExtendedInt(wsme.types.UserType):
basetype = int
name = "Extended integer"
class NestedInnerApi(object):
@expose(bool)
def deepfunction(self):
return True
class NestedOuterApi(object):
inner = NestedInnerApi()
class ReturnTypes(object):
@expose(wsme.types.bytes)
def getbytes(self):
return b("astring")
@expose(wsme.types.text)
def gettext(self):
return u('\xe3\x81\xae')
@expose(int)
def getint(self):
return 2
@expose(float)
def getfloat(self):
return 3.14159265
@expose(decimal.Decimal)
def getdecimal(self):
return decimal.Decimal('3.14159265')
@expose(datetime.date)
def getdate(self):
return datetime.date(1994, 1, 26)
@expose(bool)
def getbooltrue(self):
return True
@expose(bool)
def getboolfalse(self):
return False
@expose(datetime.time)
def gettime(self):
return datetime.time(12, 0, 0)
@expose(datetime.datetime)
def getdatetime(self):
return datetime.datetime(1994, 1, 26, 12, 0, 0)
@expose(wsme.types.binary)
def getbinary(self):
return binarysample
@expose(NestedOuter)
def getnested(self):
n = NestedOuter()
return n
@expose([wsme.types.bytes])
def getbytesarray(self):
return [b("A"), b("B"), b("C")]
@expose([NestedOuter])
def getnestedarray(self):
return [NestedOuter(), NestedOuter()]
@expose({wsme.types.bytes: NestedOuter})
def getnesteddict(self):
return {b('a'): NestedOuter(), b('b'): NestedOuter()}
@expose(NestedOuter)
def getobjectarrayattribute(self):
obj = NestedOuter()
obj.inner_array = [NestedInner(12), NestedInner(13)]
return obj
@expose(NestedOuter)
def getobjectdictattribute(self):
obj = NestedOuter()
obj.inner_dict = {
'12': NestedInner(12),
'13': NestedInner(13)
}
return obj
@expose(myenumtype)
def getenum(self):
return b('v2')
@expose(NamedAttrsObject)
def getnamedattrsobj(self):
return NamedAttrsObject(5, 6)
class ArgTypes(object):
def assertEqual(self, a, b):
if not (a == b):
raise AssertionError('%s != %s' % (a, b))
def assertIsInstance(self, value, v_type):
assert isinstance(value, v_type), ("%s is not instance of type %s" %
(value, v_type))
@expose(wsme.types.bytes)
@validate(wsme.types.bytes)
def setbytes(self, value):
print(repr(value))
self.assertEqual(type(value), wsme.types.bytes)
return value
@expose(wsme.types.text)
@validate(wsme.types.text)
def settext(self, value):
print(repr(value))
self.assertEqual(type(value), wsme.types.text)
return value
@expose(wsme.types.text)
@validate(wsme.types.text)
def settextnone(self, value):
print(repr(value))
self.assertEqual(type(value), type(None))
return value
@expose(bool)
@validate(bool)
def setbool(self, value):
print(repr(value))
self.assertEqual(type(value), bool)
return value
@expose(int)
@validate(int)
def setint(self, value):
print(repr(value))
self.assertEqual(type(value), int)
return value
@expose(float)
@validate(float)
def setfloat(self, value):
print(repr(value))
self.assertEqual(type(value), float)
return value
@expose(decimal.Decimal)
@validate(decimal.Decimal)
def setdecimal(self, value):
print(repr(value))
self.assertEqual(type(value), decimal.Decimal)
return value
@expose(datetime.date)
@validate(datetime.date)
def setdate(self, value):
print(repr(value))
self.assertEqual(type(value), datetime.date)
return value
@expose(datetime.time)
@validate(datetime.time)
def settime(self, value):
print(repr(value))
self.assertEqual(type(value), datetime.time)
return value
@expose(datetime.datetime)
@validate(datetime.datetime)
def setdatetime(self, value):
print(repr(value))
self.assertEqual(type(value), datetime.datetime)
return value
@expose(wsme.types.binary)
@validate(wsme.types.binary)
def setbinary(self, value):
print(repr(value))
self.assertEqual(type(value), six.binary_type)
return value
@expose([wsme.types.bytes])
@validate([wsme.types.bytes])
def setbytesarray(self, value):
print(repr(value))
self.assertEqual(type(value), list)
self.assertEqual(type(value[0]), wsme.types.bytes)
return value
@expose([wsme.types.text])
@validate([wsme.types.text])
def settextarray(self, value):
print(repr(value))
self.assertEqual(type(value), list)
self.assertEqual(type(value[0]), wsme.types.text)
return value
@expose([datetime.datetime])
@validate([datetime.datetime])
def setdatetimearray(self, value):
print(repr(value))
self.assertEqual(type(value), list)
self.assertEqual(type(value[0]), datetime.datetime)
return value
@expose(NestedOuter)
@validate(NestedOuter)
def setnested(self, value):
print(repr(value))
self.assertEqual(type(value), NestedOuter)
return value
@expose([NestedOuter])
@validate([NestedOuter])
def setnestedarray(self, value):
print(repr(value))
self.assertEqual(type(value), list)
self.assertEqual(type(value[0]), NestedOuter)
return value
@expose({wsme.types.bytes: NestedOuter})
@validate({wsme.types.bytes: NestedOuter})
def setnesteddict(self, value):
print(repr(value))
self.assertEqual(type(value), dict)
self.assertEqual(type(list(value.keys())[0]), wsme.types.bytes)
self.assertEqual(type(list(value.values())[0]), NestedOuter)
return value
@expose(myenumtype)
@validate(myenumtype)
def setenum(self, value):
print(value)
self.assertEqual(type(value), wsme.types.bytes)
return value
@expose(NamedAttrsObject)
@validate(NamedAttrsObject)
def setnamedattrsobj(self, value):
print(value)
self.assertEqual(type(value), NamedAttrsObject)
self.assertEqual(value.attr_1, 10)
self.assertEqual(value.attr_2, 20)
return value
@expose(CustomObject)
@validate(CustomObject)
def setcustomobject(self, value):
self.assertIsInstance(value, CustomObject)
self.assertIsInstance(value.name, wsme.types.text)
self.assertIsInstance(value.aint, int)
return value
@expose(ExtendedInt())
@validate(ExtendedInt())
def setextendedint(self, value):
self.assertEqual(isinstance(value, ExtendedInt.basetype), True)
return value
class BodyTypes(object):
def assertEqual(self, a, b):
if not (a == b):
raise AssertionError('%s != %s' % (a, b))
@expose(int, body={wsme.types.text: int})
@validate(int)
def setdict(self, body):
print(body)
self.assertEqual(type(body), dict)
self.assertEqual(type(body['test']), int)
self.assertEqual(body['test'], 10)
return body['test']
@expose(int, body=[int])
@validate(int)
def setlist(self, body):
print(body)
self.assertEqual(type(body), list)
self.assertEqual(type(body[0]), int)
self.assertEqual(body[0], 10)
return body[0]
class WithErrors(object):
@expose()
def divide_by_zero(self):
1 / 0
class MiscFunctions(object):
@expose(int)
@validate(int, int)
def multiply(self, a, b):
return a * b
class WSTestRoot(WSRoot):
argtypes = ArgTypes()
returntypes = ReturnTypes()
bodytypes = BodyTypes()
witherrors = WithErrors()
nested = NestedOuterApi()
misc = MiscFunctions()
def reset(self):
self._touched = False
@expose()
def touch(self):
self._touched = True
class ProtocolTestCase(unittest.TestCase):
protocol_options = {}
def assertTypedEquals(self, a, b, convert):
if isinstance(a, six.string_types):
a = convert(a)
if isinstance(b, six.string_types):
b = convert(b)
self.assertEqual(a, b)
def assertDateEquals(self, a, b):
self.assertTypedEquals(a, b, wsme.utils.parse_isodate)
def assertTimeEquals(self, a, b):
self.assertTypedEquals(a, b, wsme.utils.parse_isotime)
def assertDateTimeEquals(self, a, b):
self.assertTypedEquals(a, b, wsme.utils.parse_isodatetime)
def assertIntEquals(self, a, b):
self.assertTypedEquals(a, b, int)
def assertFloatEquals(self, a, b):
self.assertTypedEquals(a, b, float)
def assertDecimalEquals(self, a, b):
self.assertTypedEquals(a, b, decimal.Decimal)
def setUp(self):
if self.__class__.__name__ != 'ProtocolTestCase':
self.root = WSTestRoot()
self.root.getapi()
self.root.addprotocol(self.protocol, **self.protocol_options)
self.app = TestApp(self.root.wsgiapp())
def test_invalid_path(self):
try:
res = self.call('invalid_function')
print(res)
assert "No error raised"
except CallException as e:
self.assertEqual(e.faultcode, 'Client')
self.assertEqual(e.faultstring.lower(),
u('unknown function name: invalid_function'))
def test_serverside_error(self):
try:
res = self.call('witherrors/divide_by_zero')
print(res)
assert "No error raised"
except CallException as e:
self.assertEqual(e.faultcode, 'Server')
self.assertEqual(e.faultstring, zerodivisionerrormsg)
assert e.debuginfo is not None
def test_serverside_error_nodebug(self):
self.root._debug = False
try:
res = self.call('witherrors/divide_by_zero')
print(res)
assert "No error raised"
except CallException as e:
self.assertEqual(e.faultcode, 'Server')
self.assertEqual(e.faultstring, zerodivisionerrormsg)
assert e.debuginfo is None
def test_touch(self):
r = self.call('touch')
assert r is None, r
def test_return_bytes(self):
r = self.call('returntypes/getbytes', _rt=wsme.types.bytes)
self.assertEqual(r, b('astring'))
def test_return_text(self):
r = self.call('returntypes/gettext', _rt=wsme.types.text)
self.assertEqual(r, u('\xe3\x81\xae'))
def test_return_int(self):
r = self.call('returntypes/getint')
self.assertIntEquals(r, 2)
def test_return_float(self):
r = self.call('returntypes/getfloat')
self.assertFloatEquals(r, 3.14159265)
def test_return_decimal(self):
r = self.call('returntypes/getdecimal')
self.assertDecimalEquals(r, '3.14159265')
def test_return_bool_true(self):
r = self.call('returntypes/getbooltrue', _rt=bool)
assert r
def test_return_bool_false(self):
r = self.call('returntypes/getboolfalse', _rt=bool)
assert not r
def test_return_date(self):
r = self.call('returntypes/getdate')
self.assertDateEquals(r, datetime.date(1994, 1, 26))
def test_return_time(self):
r = self.call('returntypes/gettime')
self.assertTimeEquals(r, datetime.time(12))
def test_return_datetime(self):
r = self.call('returntypes/getdatetime')
self.assertDateTimeEquals(r, datetime.datetime(1994, 1, 26, 12))
def test_return_binary(self):
r = self.call('returntypes/getbinary', _rt=wsme.types.binary)
self.assertEqual(r, binarysample)
def test_return_nested(self):
r = self.call('returntypes/getnested', _rt=NestedOuter)
self.assertEqual(r, {'inner': {'aint': 0}})
def test_return_bytesarray(self):
r = self.call('returntypes/getbytesarray', _rt=[six.binary_type])
self.assertEqual(r, [b('A'), b('B'), b('C')])
def test_return_nestedarray(self):
r = self.call('returntypes/getnestedarray', _rt=[NestedOuter])
self.assertEqual(r, [{'inner': {'aint': 0}}, {'inner': {'aint': 0}}])
def test_return_nesteddict(self):
r = self.call('returntypes/getnesteddict',
_rt={wsme.types.bytes: NestedOuter})
self.assertEqual(r, {
b('a'): {'inner': {'aint': 0}},
b('b'): {'inner': {'aint': 0}}
})
def test_return_objectarrayattribute(self):
r = self.call('returntypes/getobjectarrayattribute', _rt=NestedOuter)
self.assertEqual(r, {
'inner': {'aint': 0},
'inner_array': [{'aint': 12}, {'aint': 13}]
})
def test_return_objectdictattribute(self):
r = self.call('returntypes/getobjectdictattribute', _rt=NestedOuter)
self.assertEqual(r, {
'inner': {'aint': 0},
'inner_dict': {
'12': {'aint': 12},
'13': {'aint': 13}
}
})
def test_return_enum(self):
r = self.call('returntypes/getenum', _rt=myenumtype)
self.assertEqual(r, b('v2'), r)
def test_return_namedattrsobj(self):
r = self.call('returntypes/getnamedattrsobj', _rt=NamedAttrsObject)
self.assertEqual(r, {'attr.1': 5, 'attr.2': 6})
def test_setbytes(self):
assert self.call('argtypes/setbytes', value=b('astring'),
_rt=wsme.types.bytes) == b('astring')
def test_settext(self):
assert self.call('argtypes/settext', value=u('\xe3\x81\xae'),
_rt=wsme.types.text) == u('\xe3\x81\xae')
def test_settext_empty(self):
assert self.call('argtypes/settext', value=u(''),
_rt=wsme.types.text) == u('')
def test_settext_none(self):
self.assertEqual(
None,
self.call('argtypes/settextnone', value=None, _rt=wsme.types.text)
)
def test_setint(self):
r = self.call('argtypes/setint', value=3, _rt=int)
self.assertEqual(r, 3)
def test_setfloat(self):
assert self.call('argtypes/setfloat', value=3.54,
_rt=float) == 3.54
def test_setbool_true(self):
r = self.call('argtypes/setbool', value=True, _rt=bool)
assert r
def test_setbool_false(self):
r = self.call('argtypes/setbool', value=False, _rt=bool)
assert not r
def test_setdecimal(self):
value = decimal.Decimal('3.14')
assert self.call('argtypes/setdecimal', value=value,
_rt=decimal.Decimal) == value
def test_setdate(self):
value = datetime.date(2008, 4, 6)
r = self.call('argtypes/setdate', value=value,
_rt=datetime.date)
self.assertEqual(r, value)
def test_settime(self):
value = datetime.time(12, 12, 15)
r = self.call('argtypes/settime', value=value,
_rt=datetime.time)
self.assertEqual(r, datetime.time(12, 12, 15))
def test_setdatetime(self):
value = datetime.datetime(2008, 4, 6, 12, 12, 15)
r = self.call('argtypes/setdatetime', value=value,
_rt=datetime.datetime)
self.assertEqual(r, datetime.datetime(2008, 4, 6, 12, 12, 15))
def test_setbinary(self):
value = binarysample
r = self.call('argtypes/setbinary', value=(value, wsme.types.binary),
_rt=wsme.types.binary) == value
print(r)
def test_setnested(self):
value = {'inner': {'aint': 54}}
r = self.call('argtypes/setnested',
value=(value, NestedOuter),
_rt=NestedOuter)
self.assertEqual(r, value)
def test_setnested_nullobj(self):
value = {'inner': None}
r = self.call(
'argtypes/setnested',
value=(value, NestedOuter),
_rt=NestedOuter
)
self.assertEqual(r, value)
def test_setbytesarray(self):
value = [b("1"), b("2"), b("three")]
r = self.call('argtypes/setbytesarray',
value=(value, [wsme.types.bytes]),
_rt=[wsme.types.bytes])
self.assertEqual(r, value)
def test_settextarray(self):
value = [u("1")]
r = self.call('argtypes/settextarray',
value=(value, [wsme.types.text]),
_rt=[wsme.types.text])
self.assertEqual(r, value)
def test_setdatetimearray(self):
value = [
datetime.datetime(2008, 3, 6, 12, 12, 15),
datetime.datetime(2008, 4, 6, 2, 12, 15),
]
r = self.call('argtypes/setdatetimearray',
value=(value, [datetime.datetime]),
_rt=[datetime.datetime])
self.assertEqual(r, value)
def test_setnestedarray(self):
value = [
{'inner': {'aint': 54}},
{'inner': {'aint': 55}},
]
r = self.call('argtypes/setnestedarray',
value=(value, [NestedOuter]),
_rt=[NestedOuter])
self.assertEqual(r, value)
def test_setnesteddict(self):
value = {
b('o1'): {'inner': {'aint': 54}},
b('o2'): {'inner': {'aint': 55}},
}
r = self.call('argtypes/setnesteddict',
value=(value, {six.binary_type: NestedOuter}),
_rt={six.binary_type: NestedOuter})
print(r)
self.assertEqual(r, value)
def test_setenum(self):
value = b('v1')
r = self.call('argtypes/setenum', value=value,
_rt=myenumtype)
self.assertEqual(r, value)
def test_setnamedattrsobj(self):
value = {'attr.1': 10, 'attr.2': 20}
r = self.call('argtypes/setnamedattrsobj',
value=(value, NamedAttrsObject),
_rt=NamedAttrsObject)
self.assertEqual(r, value)
def test_nested_api(self):
r = self.call('nested/inner/deepfunction', _rt=bool)
assert r is True
def test_missing_argument(self):
try:
r = self.call('argtypes/setdatetime')
print(r)
assert "No error raised"
except CallException as e:
self.assertEqual(e.faultcode, 'Client')
self.assertEqual(e.faultstring, u('Missing argument: "value"'))
def test_misc_multiply(self):
self.assertEqual(self.call('misc/multiply', a=5, b=2, _rt=int), 10)
def test_html_format(self):
res = self.call('argtypes/setdatetime', _accept="text/html",
_no_result_decode=True)
self.assertEqual(res.content_type, 'text/html')
class RestOnlyProtocolTestCase(ProtocolTestCase):
def test_body_list(self):
r = self.call('bodytypes/setlist', body=([10], [int]), _rt=int)
self.assertEqual(r, 10)
def test_body_dict(self):
r = self.call('bodytypes/setdict',
body=({'test': 10}, {wsme.types.text: int}),
_rt=int)
self.assertEqual(r, 10)

View File

@@ -1,419 +0,0 @@
# encoding=utf8
from six import b
try:
import unittest2 as unittest
except ImportError:
import unittest
import webtest
from wsme import WSRoot, expose, validate
from wsme.rest import scan_api
from wsme import types
from wsme import exc
import wsme.api as wsme_api
import wsme.types
from wsme.tests.test_protocols import DummyProtocol
class TestController(unittest.TestCase):
def test_expose(self):
class MyWS(WSRoot):
@expose(int)
def getint(self):
return 1
assert MyWS.getint._wsme_definition.return_type == int
def test_validate(self):
class ComplexType(object):
attr = int
class MyWS(object):
@expose(int)
@validate(int, int, int)
def add(self, a, b, c=0):
return a + b + c
@expose(bool)
@validate(ComplexType)
def setcplx(self, obj):
pass
MyWS.add._wsme_definition.resolve_types(wsme.types.registry)
MyWS.setcplx._wsme_definition.resolve_types(wsme.types.registry)
args = MyWS.add._wsme_definition.arguments
assert args[0].name == 'a'
assert args[0].datatype == int
assert args[0].mandatory
assert args[0].default is None
assert args[1].name == 'b'
assert args[1].datatype == int
assert args[1].mandatory
assert args[1].default is None
assert args[2].name == 'c'
assert args[2].datatype == int
assert not args[2].mandatory
assert args[2].default == 0
assert types.iscomplex(ComplexType)
def test_validate_enum_with_none(self):
class Version(object):
number = types.Enum(str, 'v1', 'v2', None)
class MyWS(WSRoot):
@expose(str)
@validate(Version)
def setcplx(self, version):
pass
r = MyWS(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/setcplx', params={'version': {'number': 'arf'}},
expect_errors=True,
headers={'Accept': 'application/json'})
self.assertTrue(
res.json_body['faultstring'].startswith(
"Invalid input for field/attribute number. Value: 'arf'. \
Value should be one of:"))
self.assertIn('v1', res.json_body['faultstring'])
self.assertIn('v2', res.json_body['faultstring'])
self.assertIn('None', res.json_body['faultstring'])
self.assertEqual(res.status_int, 400)
def test_validate_enum_with_wrong_type(self):
class Version(object):
number = types.Enum(str, 'v1', 'v2', None)
class MyWS(WSRoot):
@expose(str)
@validate(Version)
def setcplx(self, version):
pass
r = MyWS(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/setcplx', params={'version': {'number': 1}},
expect_errors=True,
headers={'Accept': 'application/json'})
self.assertTrue(
res.json_body['faultstring'].startswith(
"Invalid input for field/attribute number. Value: '1'. \
Value should be one of:"))
self.assertIn('v1', res.json_body['faultstring'])
self.assertIn('v2', res.json_body['faultstring'])
self.assertIn('None', res.json_body['faultstring'])
self.assertEqual(res.status_int, 400)
def test_scan_api(self):
class NS(object):
@expose(int)
@validate(int, int)
def multiply(self, a, b):
return a * b
class MyRoot(WSRoot):
ns = NS()
r = MyRoot()
api = list(scan_api(r))
assert len(api) == 1
path, fd, args = api[0]
assert path == ['ns', 'multiply']
assert fd._wsme_definition.name == 'multiply'
assert args == []
def test_scan_subclass(self):
class MyRoot(WSRoot):
class SubClass(object):
pass
r = MyRoot()
api = list(scan_api(r))
assert len(api) == 0
def test_scan_api_too_deep(self):
class Loop(object):
pass
l = Loop()
for i in range(0, 21):
nl = Loop()
nl.l = l
l = nl
class MyRoot(WSRoot):
loop = l
r = MyRoot()
try:
list(scan_api(r))
assert False, "ValueError not raised"
except ValueError as e:
assert str(e).startswith("Path is too long")
def test_handle_request(self):
class MyRoot(WSRoot):
@expose()
def touch(self):
pass
p = DummyProtocol()
r = MyRoot(protocols=[p])
app = webtest.TestApp(r.wsgiapp())
res = app.get('/')
assert p.lastreq.path == '/'
assert p.hits == 1
res = app.get('/touch?wsmeproto=dummy')
assert p.lastreq.path == '/touch'
assert p.hits == 2
class NoPathProto(DummyProtocol):
def extract_path(self, request):
return None
p = NoPathProto()
r = MyRoot(protocols=[p])
app = webtest.TestApp(r.wsgiapp())
res = app.get('/', expect_errors=True)
print(res.status, res.body)
assert res.status_int == 400
def test_no_available_protocol(self):
r = WSRoot()
app = webtest.TestApp(r.wsgiapp())
res = app.get('/', expect_errors=True)
print(res.status_int)
assert res.status_int == 406
print(res.body)
assert res.body.find(
b("None of the following protocols can handle this request")) != -1
def test_return_content_type_guess(self):
class DummierProto(DummyProtocol):
content_types = ['text/xml', 'text/plain']
r = WSRoot([DummierProto()])
app = webtest.TestApp(r.wsgiapp())
res = app.get('/', expect_errors=True, headers={
'Accept': 'text/xml,q=0.8'})
assert res.status_int == 400
assert res.content_type == 'text/xml', res.content_type
res = app.get('/', expect_errors=True, headers={
'Accept': 'text/plain'})
assert res.status_int == 400
assert res.content_type == 'text/plain', res.content_type
def test_double_expose(self):
try:
class MyRoot(WSRoot):
@expose()
@expose()
def atest(self):
pass
assert False, "A ValueError should have been raised"
except ValueError:
pass
def test_multiple_expose(self):
class MyRoot(WSRoot):
def multiply(self, a, b):
return a * b
mul_int = expose(int, int, int, wrap=True)(multiply)
mul_float = expose(
float, float, float,
wrap=True)(multiply)
mul_string = expose(
wsme.types.text, wsme.types.text, int,
wrap=True)(multiply)
r = MyRoot(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.get('/mul_int?a=2&b=5', headers={
'Accept': 'application/json'
})
self.assertEqual(res.body, b('10'))
res = app.get('/mul_float?a=1.2&b=2.9', headers={
'Accept': 'application/json'
})
self.assertEqual(res.body, b('3.48'))
res = app.get('/mul_string?a=hello&b=2', headers={
'Accept': 'application/json'
})
self.assertEqual(res.body, b('"hellohello"'))
def test_wsattr_mandatory(self):
class ComplexType(object):
attr = wsme.types.wsattr(int, mandatory=True)
class MyRoot(WSRoot):
@expose(int, body=ComplexType)
@validate(ComplexType)
def clx(self, a):
return a.attr
r = MyRoot(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/clx', params={}, expect_errors=True,
headers={'Accept': 'application/json'})
self.assertEqual(res.status_int, 400)
def test_wsattr_readonly(self):
class ComplexType(object):
attr = wsme.types.wsattr(int, readonly=True)
class MyRoot(WSRoot):
@expose(int, body=ComplexType)
@validate(ComplexType)
def clx(self, a):
return a.attr
r = MyRoot(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/clx', params={'attr': 1005}, expect_errors=True,
headers={'Accept': 'application/json'})
self.assertIn('Cannot set read only field.',
res.json_body['faultstring'])
self.assertIn('1005', res.json_body['faultstring'])
self.assertEqual(res.status_int, 400)
def test_wsattr_default(self):
class ComplexType(object):
attr = wsme.types.wsattr(wsme.types.Enum(str, 'or', 'and'),
default='and')
class MyRoot(WSRoot):
@expose(int)
@validate(ComplexType)
def clx(self, a):
return a.attr
r = MyRoot(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/clx', params={}, expect_errors=True,
headers={'Accept': 'application/json'})
self.assertEqual(res.status_int, 400)
def test_wsproperty_mandatory(self):
class ComplexType(object):
def foo(self):
pass
attr = wsme.types.wsproperty(int, foo, foo, mandatory=True)
class MyRoot(WSRoot):
@expose(int, body=ComplexType)
@validate(ComplexType)
def clx(self, a):
return a.attr
r = MyRoot(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/clx', params={}, expect_errors=True,
headers={'Accept': 'application/json'})
self.assertEqual(res.status_int, 400)
def test_validate_enum_mandatory(self):
class Version(object):
number = wsme.types.wsattr(wsme.types.Enum(str, 'v1', 'v2'),
mandatory=True)
class MyWS(WSRoot):
@expose(str)
@validate(Version)
def setcplx(self, version):
pass
r = MyWS(['restjson'])
app = webtest.TestApp(r.wsgiapp())
res = app.post_json('/setcplx', params={'version': {}},
expect_errors=True,
headers={'Accept': 'application/json'})
self.assertEqual(res.status_int, 400)
class TestFunctionDefinition(unittest.TestCase):
def test_get_arg(self):
def myfunc(self):
pass
fd = wsme_api.FunctionDefinition(wsme_api.FunctionDefinition)
fd.arguments.append(wsme_api.FunctionArgument('a', int, True, None))
assert fd.get_arg('a').datatype is int
assert fd.get_arg('b') is None
class TestFormatException(unittest.TestCase):
def _test_format_exception(self, exception, debug=False):
fake_exc_info = (None, exception, None)
return wsme_api.format_exception(fake_exc_info, debug=debug)
def test_format_client_exception(self):
faultstring = 'boom'
ret = self._test_format_exception(exc.ClientSideError(faultstring))
self.assertIsNone(ret['debuginfo'])
self.assertEqual('Client', ret['faultcode'])
self.assertEqual(faultstring, ret['faultstring'])
def test_format_client_exception_unicode(self):
faultstring = u'\xc3\xa3o'
ret = self._test_format_exception(exc.ClientSideError(faultstring))
self.assertIsNone(ret['debuginfo'])
self.assertEqual('Client', ret['faultcode'])
self.assertEqual(faultstring, ret['faultstring'])
def test_format_server_exception(self):
faultstring = 'boom'
ret = self._test_format_exception(Exception(faultstring))
self.assertIsNone(ret['debuginfo'])
self.assertEqual('Server', ret['faultcode'])
self.assertEqual(faultstring, ret['faultstring'])
def test_format_server_exception_unicode(self):
faultstring = u'\xc3\xa3o'
ret = self._test_format_exception(Exception(faultstring))
self.assertIsNone(ret['debuginfo'])
self.assertEqual('Server', ret['faultcode'])
self.assertEqual(faultstring, ret['faultstring'])
def test_format_server_exception_debug(self):
faultstring = 'boom'
ret = self._test_format_exception(Exception(faultstring), debug=True)
# assert debuginfo is populated
self.assertIsNotNone(ret['debuginfo'])
self.assertEqual('Server', ret['faultcode'])
self.assertEqual(faultstring, ret['faultstring'])

View File

@@ -1,40 +0,0 @@
# encoding=utf8
from wsme.exc import (ClientSideError, InvalidInput, MissingArgument,
UnknownArgument)
from six import u
def test_clientside_error():
e = ClientSideError("Test")
assert e.faultstring == u("Test")
def test_unicode_clientside_error():
e = ClientSideError(u("\u30d5\u30a1\u30b7\u30ea"))
assert e.faultstring == u("\u30d5\u30a1\u30b7\u30ea")
def test_invalidinput():
e = InvalidInput('field', 'badvalue', "error message")
assert e.faultstring == u(
"Invalid input for field/attribute field. Value: 'badvalue'. "
"error message"
), e.faultstring
def test_missingargument():
e = MissingArgument('argname', "error message")
assert e.faultstring == \
u('Missing argument: "argname": error message'), e.faultstring
def test_unknownargument():
e = UnknownArgument('argname', "error message")
assert e.faultstring == \
u('Unknown argument: "argname": error message'), e.faultstring

View File

@@ -1,70 +0,0 @@
# encoding=utf8
import unittest
from wsme import WSRoot
from wsme.protocol import getprotocol, CallContext, Protocol
import wsme.protocol
class DummyProtocol(Protocol):
name = 'dummy'
content_types = ['', None]
def __init__(self):
self.hits = 0
def accept(self, req):
return True
def iter_calls(self, req):
yield CallContext(req)
def extract_path(self, context):
return ['touch']
def read_arguments(self, context):
self.lastreq = context.request
self.hits += 1
return {}
def encode_result(self, context, result):
return str(result)
def encode_error(self, context, infos):
return str(infos)
def test_getprotocol():
try:
getprotocol('invalid')
assert False, "ValueError was not raised"
except ValueError:
pass
class TestProtocols(unittest.TestCase):
def test_register_protocol(self):
wsme.protocol.register_protocol(DummyProtocol)
assert wsme.protocol.registered_protocols['dummy'] == DummyProtocol
r = WSRoot()
assert len(r.protocols) == 0
r.addprotocol('dummy')
assert len(r.protocols) == 1
assert r.protocols[0].__class__ == DummyProtocol
r = WSRoot(['dummy'])
assert len(r.protocols) == 1
assert r.protocols[0].__class__ == DummyProtocol
def test_Protocol(self):
p = wsme.protocol.Protocol()
assert p.iter_calls(None) is None
assert p.extract_path(None) is None
assert p.read_arguments(None) is None
assert p.encode_result(None, None) is None
assert p.encode_sample_value(None, None) == ('none', 'N/A')
assert p.encode_sample_params(None) == ('none', 'N/A')
assert p.encode_sample_result(None, None) == ('none', 'N/A')

View File

@@ -1,159 +0,0 @@
# encoding=utf8
import datetime
import unittest
from wsme.api import FunctionArgument, FunctionDefinition
from wsme.rest.args import from_param, from_params, args_from_args
from wsme.exc import InvalidInput
from wsme.types import UserType, Unset, ArrayType, DictType, Base
class MyBaseType(Base):
test = str
class MyUserType(UserType):
basetype = str
class DictBasedUserType(UserType):
basetype = DictType(int, int)
class TestProtocolsCommons(unittest.TestCase):
def test_from_param_date(self):
assert from_param(datetime.date, '2008-02-28') == \
datetime.date(2008, 2, 28)
def test_from_param_time(self):
assert from_param(datetime.time, '12:14:56') == \
datetime.time(12, 14, 56)
def test_from_param_datetime(self):
assert from_param(datetime.datetime, '2009-12-23T12:14:56') == \
datetime.datetime(2009, 12, 23, 12, 14, 56)
def test_from_param_usertype(self):
assert from_param(MyUserType(), 'test') == 'test'
def test_from_params_empty(self):
assert from_params(str, {}, '', set()) is Unset
def test_from_params_native_array(self):
class params(dict):
def getall(self, path):
return ['1', '2']
p = params({'a': []})
assert from_params(ArrayType(int), p, 'a', set()) == [1, 2]
def test_from_params_empty_array(self):
assert from_params(ArrayType(int), {}, 'a', set()) is Unset
def test_from_params_dict(self):
value = from_params(
DictType(int, str),
{'a[2]': 'a2', 'a[3]': 'a3'},
'a',
set()
)
assert value == {2: 'a2', 3: 'a3'}, value
def test_from_params_dict_unset(self):
assert from_params(DictType(int, str), {}, 'a', set()) is Unset
def test_from_params_usertype(self):
value = from_params(
DictBasedUserType(),
{'a[2]': '2'},
'a',
set()
)
self.assertEqual(value, {2: 2})
def test_args_from_args_usertype(self):
class FakeType(UserType):
name = 'fake-type'
basetype = int
fake_type = FakeType()
fd = FunctionDefinition(FunctionDefinition)
fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, 0))
new_args = args_from_args(fd, [1], {})
self.assertEqual([1], new_args[0])
# can't convert str to int
try:
args_from_args(fd, ['invalid-argument'], {})
except InvalidInput as e:
assert fake_type.name in str(e)
else:
self.fail('Should have thrown an InvalidInput')
def test_args_from_args_custom_exc(self):
class FakeType(UserType):
name = 'fake-type'
basetype = int
def validate(self, value):
if value < 10:
raise ValueError('should be greater than 10')
def frombasetype(self, value):
self.validate(value)
fake_type = FakeType()
fd = FunctionDefinition(FunctionDefinition)
fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, 0))
try:
args_from_args(fd, [9], {})
except InvalidInput as e:
assert fake_type.name in str(e)
assert 'Error: should be greater than 10' in str(e)
else:
self.fail('Should have thrown an InvalidInput')
def test_args_from_args_array_type(self):
fake_type = ArrayType(MyBaseType)
fd = FunctionDefinition(FunctionDefinition)
fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, []))
try:
args_from_args(fd, [['invalid-argument']], {})
except InvalidInput as e:
assert ArrayType.__name__ in str(e)
else:
self.fail('Should have thrown an InvalidInput')
class ArgTypeConversion(unittest.TestCase):
def test_int_zero(self):
self.assertEqual(0, from_param(int, 0))
self.assertEqual(0, from_param(int, '0'))
def test_int_nonzero(self):
self.assertEqual(1, from_param(int, 1))
self.assertEqual(1, from_param(int, '1'))
def test_int_none(self):
self.assertEqual(None, from_param(int, None))
def test_float_zero(self):
self.assertEqual(0.0, from_param(float, 0))
self.assertEqual(0.0, from_param(float, 0.0))
self.assertEqual(0.0, from_param(float, '0'))
self.assertEqual(0.0, from_param(float, '0.0'))
def test_float_nonzero(self):
self.assertEqual(1.0, from_param(float, 1))
self.assertEqual(1.0, from_param(float, 1.0))
self.assertEqual(1.0, from_param(float, '1'))
self.assertEqual(1.0, from_param(float, '1.0'))
def test_float_none(self):
self.assertEqual(None, from_param(float, None))

View File

@@ -1,779 +0,0 @@
import base64
import datetime
import decimal
import wsme.tests.protocol
try:
import simplejson as json
except:
import json # noqa
from wsme.rest.json import fromjson, tojson, parse
from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate
from wsme.types import isarray, isdict, isusertype, register_type
from wsme.types import UserType, ArrayType, DictType
from wsme.rest import expose, validate
from wsme.exc import ClientSideError, InvalidInput
import six
from six import b, u
if six.PY3:
from urllib.parse import urlencode
else:
from urllib import urlencode # noqa
def prepare_value(value, datatype):
if isinstance(datatype, list):
return [prepare_value(item, datatype[0]) for item in value]
if isinstance(datatype, dict):
key_type, value_type = list(datatype.items())[0]
return dict((
(prepare_value(item[0], key_type),
prepare_value(item[1], value_type))
for item in value.items()
))
if datatype in (datetime.date, datetime.time, datetime.datetime):
return value.isoformat()
if datatype == decimal.Decimal:
return str(value)
if datatype == wsme.types.binary:
return base64.encodestring(value).decode('ascii')
if datatype == wsme.types.bytes:
return value.decode('ascii')
return value
def prepare_result(value, datatype):
print(value, datatype)
if value is None:
return None
if datatype == wsme.types.binary:
return base64.decodestring(value.encode('ascii'))
if isusertype(datatype):
datatype = datatype.basetype
if isinstance(datatype, list):
return [prepare_result(item, datatype[0]) for item in value]
if isarray(datatype):
return [prepare_result(item, datatype.item_type) for item in value]
if isinstance(datatype, dict):
return dict((
(prepare_result(item[0], list(datatype.keys())[0]),
prepare_result(item[1], list(datatype.values())[0]))
for item in value.items()
))
if isdict(datatype):
return dict((
(prepare_result(item[0], datatype.key_type),
prepare_result(item[1], datatype.value_type))
for item in value.items()
))
if datatype == datetime.date:
return parse_isodate(value)
if datatype == datetime.time:
return parse_isotime(value)
if datatype == datetime.datetime:
return parse_isodatetime(value)
if hasattr(datatype, '_wsme_attributes'):
for attr in datatype._wsme_attributes:
if attr.key not in value:
continue
value[attr.key] = prepare_result(value[attr.key], attr.datatype)
return value
if datatype == wsme.types.bytes:
return value.encode('ascii')
if type(value) != datatype:
print(type(value), datatype)
return datatype(value)
return value
class CustomInt(UserType):
basetype = int
name = "custom integer"
class Obj(wsme.types.Base):
id = int
name = wsme.types.text
class NestedObj(wsme.types.Base):
o = Obj
class CRUDResult(object):
data = Obj
message = wsme.types.text
def __init__(self, data=wsme.types.Unset, message=wsme.types.Unset):
self.data = data
self.message = message
class MiniCrud(object):
@expose(CRUDResult, method='PUT')
@validate(Obj)
def create(self, data):
print(repr(data))
return CRUDResult(data, u('create'))
@expose(CRUDResult, method='GET', ignore_extra_args=True)
@validate(Obj)
def read(self, ref):
print(repr(ref))
if ref.id == 1:
ref.name = u('test')
return CRUDResult(ref, u('read'))
@expose(CRUDResult, method='POST')
@validate(Obj)
def update(self, data):
print(repr(data))
return CRUDResult(data, u('update'))
@expose(CRUDResult, wsme.types.text, body=Obj)
def update_with_body(self, msg, data):
print(repr(data))
return CRUDResult(data, msg)
@expose(CRUDResult, method='DELETE')
@validate(Obj)
def delete(self, ref):
print(repr(ref))
if ref.id == 1:
ref.name = u('test')
return CRUDResult(ref, u('delete'))
wsme.tests.protocol.WSTestRoot.crud = MiniCrud()
class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
protocol = 'restjson'
def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False,
body=None, **kw):
if body:
if isinstance(body, tuple):
body, datatype = body
else:
datatype = type(body)
body = prepare_value(body, datatype)
content = json.dumps(body)
else:
for key in kw:
if isinstance(kw[key], tuple):
value, datatype = kw[key]
else:
value = kw[key]
datatype = type(value)
kw[key] = prepare_value(value, datatype)
content = json.dumps(kw)
headers = {
'Content-Type': 'application/json',
}
if _accept is not None:
headers["Accept"] = _accept
res = self.app.post(
'/' + fpath,
content,
headers=headers,
expect_errors=True)
print("Received:", res.body)
if _no_result_decode:
return res
r = json.loads(res.text)
if res.status_int == 200:
if _rt and r:
r = prepare_result(r, _rt)
return r
else:
raise wsme.tests.protocol.CallException(
r['faultcode'],
r['faultstring'],
r.get('debuginfo')
)
return json.loads(res.text)
def test_fromjson(self):
assert fromjson(str, None) is None
def test_keyargs(self):
r = self.app.get('/argtypes/setint.json?value=2')
print(r)
assert json.loads(r.text) == 2
nestedarray = 'value[0].inner.aint=54&value[1].inner.aint=55'
r = self.app.get('/argtypes/setnestedarray.json?' + nestedarray)
print(r)
assert json.loads(r.text) == [
{'inner': {'aint': 54}},
{'inner': {'aint': 55}}]
def test_form_urlencoded_args(self):
params = {
'value[0].inner.aint': 54,
'value[1].inner.aint': 55
}
body = urlencode(params)
r = self.app.post(
'/argtypes/setnestedarray.json',
body,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
print(r)
assert json.loads(r.text) == [
{'inner': {'aint': 54}},
{'inner': {'aint': 55}}]
def test_body_and_params(self):
r = self.app.post('/argtypes/setint.json?value=2', '{"value": 2}',
headers={"Content-Type": "application/json"},
expect_errors=True)
print(r)
assert r.status_int == 400
assert json.loads(r.text)['faultstring'] == \
"Parameter value was given several times"
def test_inline_body(self):
params = urlencode({'__body__': '{"value": 4}'})
r = self.app.get('/argtypes/setint.json?' + params)
print(r)
assert json.loads(r.text) == 4
def test_empty_body(self):
params = urlencode({'__body__': ''})
r = self.app.get('/returntypes/getint.json?' + params)
print(r)
assert json.loads(r.text) == 2
def test_invalid_content_type_body(self):
r = self.app.post('/argtypes/setint.json', '{"value": 2}',
headers={"Content-Type": "application/invalid"},
expect_errors=True)
print(r)
assert r.status_int == 415
assert json.loads(r.text)['faultstring'] == \
"Unknown mimetype: application/invalid"
def test_invalid_json_body(self):
r = self.app.post('/argtypes/setint.json', '{"value": 2',
headers={"Content-Type": "application/json"},
expect_errors=True)
print(r)
assert r.status_int == 400
assert json.loads(r.text)['faultstring'] == \
"Request is not in valid JSON format"
def test_unknown_arg(self):
r = self.app.post('/returntypes/getint.json', '{"a": 2}',
headers={"Content-Type": "application/json"},
expect_errors=True)
print(r)
assert r.status_int == 400
assert json.loads(r.text)['faultstring'].startswith(
"Unknown argument:"
)
r = self.app.get('/returntypes/getint.json?a=2', expect_errors=True)
print(r)
assert r.status_int == 400
assert json.loads(r.text)['faultstring'].startswith(
"Unknown argument:"
)
def test_set_custom_object(self):
r = self.app.post(
'/argtypes/setcustomobject',
'{"value": {"aint": 2, "name": "test"}}',
headers={"Content-Type": "application/json"}
)
self.assertEqual(r.status_int, 200)
self.assertEqual(r.json, {'aint': 2, 'name': 'test'})
def test_set_extended_int(self):
r = self.app.post(
'/argtypes/setextendedint',
'{"value": 3}',
headers={"Content-Type": "application/json"}
)
self.assertEqual(r.status_int, 200)
self.assertEqual(r.json, 3)
def test_unset_attrs(self):
class AType(object):
attr = int
wsme.types.register_type(AType)
j = tojson(AType, AType())
assert j == {}
def test_array_tojson(self):
assert tojson([int], None) is None
assert tojson([int], []) == []
assert tojson([str], ['1', '4']) == ['1', '4']
def test_dict_tojson(self):
assert tojson({int: str}, None) is None
assert tojson({int: str}, {5: '5'}) == {5: '5'}
def test_None_tojson(self):
for dt in (datetime.date, datetime.time, datetime.datetime,
decimal.Decimal):
assert tojson(dt, None) is None
def test_None_fromjson(self):
for dt in (str, int, datetime.date, datetime.time, datetime.datetime,
decimal.Decimal, [int], {int: int}):
assert fromjson(dt, None) is None
def test_parse_valid_date(self):
j = parse('{"a": "2011-01-01"}', {'a': datetime.date}, False)
assert isinstance(j['a'], datetime.date)
def test_invalid_root_dict_fromjson(self):
try:
parse('["invalid"]', {'a': ArrayType(str)}, False)
assert False
except Exception as e:
assert isinstance(e, ClientSideError)
assert e.msg == "Request must be a JSON dict"
def test_invalid_list_fromjson(self):
jlist = "invalid"
try:
parse('{"a": "%s"}' % jlist, {'a': ArrayType(str)}, False)
assert False
except Exception as e:
assert isinstance(e, InvalidInput)
assert e.fieldname == 'a'
assert e.value == jlist
assert e.msg == "Value not a valid list: %s" % jlist
def test_invalid_dict_fromjson(self):
jdict = "invalid"
try:
parse('{"a": "%s"}' % jdict, {'a': DictType(str, str)}, False)
assert False
except Exception as e:
assert isinstance(e, InvalidInput)
assert e.fieldname == 'a'
assert e.value == jdict
assert e.msg == "Value not a valid dict: %s" % jdict
def test_invalid_date_fromjson(self):
jdate = "2015-01-invalid"
try:
parse('{"a": "%s"}' % jdate, {'a': datetime.date}, False)
assert False
except Exception as e:
assert isinstance(e, InvalidInput)
assert e.fieldname == 'a'
assert e.value == jdate
assert e.msg == "'%s' is not a legal date value" % jdate
def test_parse_valid_date_bodyarg(self):
j = parse('"2011-01-01"', {'a': datetime.date}, True)
assert isinstance(j['a'], datetime.date)
def test_invalid_date_fromjson_bodyarg(self):
jdate = "2015-01-invalid"
try:
parse('"%s"' % jdate, {'a': datetime.date}, True)
assert False
except Exception as e:
assert isinstance(e, InvalidInput)
assert e.fieldname == 'a'
assert e.value == jdate
assert e.msg == "'%s' is not a legal date value" % jdate
def test_valid_str_to_builtin_fromjson(self):
types = six.integer_types + (bool, float)
value = '2'
for t in types:
for ba in True, False:
jd = '%s' if ba else '{"a": %s}'
i = parse(jd % value, {'a': t}, ba)
self.assertEqual(
i, {'a': t(value)},
"Parsed value does not correspond for %s: "
"%s != {'a': %s}" % (
t, repr(i), repr(t(value))
)
)
self.assertIsInstance(i['a'], t)
def test_valid_int_fromjson(self):
value = 2
for ba in True, False:
jd = '%d' if ba else '{"a": %d}'
i = parse(jd % value, {'a': int}, ba)
self.assertEqual(i, {'a': 2})
self.assertIsInstance(i['a'], int)
def test_valid_num_to_float_fromjson(self):
values = 2, 2.3
for v in values:
for ba in True, False:
jd = '%f' if ba else '{"a": %f}'
i = parse(jd % v, {'a': float}, ba)
self.assertEqual(i, {'a': float(v)})
self.assertIsInstance(i['a'], float)
def test_invalid_str_to_buitin_fromjson(self):
types = six.integer_types + (float, bool)
value = '2a'
for t in types:
for ba in True, False:
jd = '"%s"' if ba else '{"a": "%s"}'
try:
parse(jd % value, {'a': t}, ba)
assert False, (
"Value '%s' should not parse correctly for %s." %
(value, t)
)
except ClientSideError as e:
self.assertIsInstance(e, InvalidInput)
self.assertEqual(e.fieldname, 'a')
self.assertEqual(e.value, value)
def test_ambiguous_to_bool(self):
amb_values = ('', 'randomstring', '2', '-32', 'not true')
for value in amb_values:
for ba in True, False:
jd = '"%s"' if ba else '{"a": "%s"}'
try:
parse(jd % value, {'a': bool}, ba)
assert False, (
"Value '%s' should not parse correctly for %s." %
(value, bool)
)
except ClientSideError as e:
self.assertIsInstance(e, InvalidInput)
self.assertEqual(e.fieldname, 'a')
self.assertEqual(e.value, value)
def test_true_strings_to_bool(self):
true_values = ('true', 't', 'yes', 'y', 'on', '1')
for value in true_values:
for ba in True, False:
jd = '"%s"' if ba else '{"a": "%s"}'
i = parse(jd % value, {'a': bool}, ba)
self.assertIsInstance(i['a'], bool)
self.assertTrue(i['a'])
def test_false_strings_to_bool(self):
false_values = ('false', 'f', 'no', 'n', 'off', '0')
for value in false_values:
for ba in True, False:
jd = '"%s"' if ba else '{"a": "%s"}'
i = parse(jd % value, {'a': bool}, ba)
self.assertIsInstance(i['a'], bool)
self.assertFalse(i['a'])
def test_true_ints_to_bool(self):
true_values = (1, 5, -3)
for value in true_values:
for ba in True, False:
jd = '%d' if ba else '{"a": %d}'
i = parse(jd % value, {'a': bool}, ba)
self.assertIsInstance(i['a'], bool)
self.assertTrue(i['a'])
def test_false_ints_to_bool(self):
value = 0
for ba in True, False:
jd = '%d' if ba else '{"a": %d}'
i = parse(jd % value, {'a': bool}, ba)
self.assertIsInstance(i['a'], bool)
self.assertFalse(i['a'])
def test_valid_simple_custom_type_fromjson(self):
value = 2
for ba in True, False:
jd = '"%d"' if ba else '{"a": "%d"}'
i = parse(jd % value, {'a': CustomInt()}, ba)
self.assertEqual(i, {'a': 2})
self.assertIsInstance(i['a'], int)
def test_invalid_simple_custom_type_fromjson(self):
value = '2b'
for ba in True, False:
jd = '"%s"' if ba else '{"a": "%s"}'
try:
i = parse(jd % value, {'a': CustomInt()}, ba)
self.assertEqual(i, {'a': 2})
except ClientSideError as e:
self.assertIsInstance(e, InvalidInput)
self.assertEqual(e.fieldname, 'a')
self.assertEqual(e.value, value)
self.assertEqual(
e.msg,
"invalid literal for int() with base 10: '%s'" % value
)
def test_parse_unexpected_attribute(self):
o = {
"id": "1",
"name": "test",
"other": "unknown",
"other2": "still unknown",
}
for ba in True, False:
jd = o if ba else {"o": o}
try:
parse(json.dumps(jd), {'o': Obj}, ba)
raise AssertionError("Object should not parse correcty.")
except wsme.exc.UnknownAttribute as e:
self.assertEqual(e.attributes, set(['other', 'other2']))
def test_parse_unexpected_nested_attribute(self):
no = {
"o": {
"id": "1",
"name": "test",
"other": "unknown",
},
}
for ba in False, True:
jd = no if ba else {"no": no}
try:
parse(json.dumps(jd), {'no': NestedObj}, ba)
except wsme.exc.UnknownAttribute as e:
self.assertEqual(e.attributes, set(['other']))
self.assertEqual(e.fieldname, "no.o")
def test_nest_result(self):
self.root.protocols[0].nest_result = True
r = self.app.get('/returntypes/getint.json')
print(r)
assert json.loads(r.text) == {"result": 2}
def test_encode_sample_value(self):
class MyType(object):
aint = int
astr = str
register_type(MyType)
v = MyType()
v.aint = 4
v.astr = 's'
r = wsme.rest.json.encode_sample_value(MyType, v, True)
print(r)
assert r[0] == ('javascript')
assert r[1] == json.dumps({'aint': 4, 'astr': 's'}, ensure_ascii=False,
indent=4, sort_keys=True)
def test_bytes_tojson(self):
assert tojson(wsme.types.bytes, None) is None
assert tojson(wsme.types.bytes, b('ascii')) == u('ascii')
def test_encode_sample_params(self):
r = wsme.rest.json.encode_sample_params(
[('a', int, 2)], True
)
assert r[0] == 'javascript', r[0]
assert r[1] == '''{
"a": 2
}''', r[1]
def test_encode_sample_result(self):
r = wsme.rest.json.encode_sample_result(
int, 2, True
)
assert r[0] == 'javascript', r[0]
assert r[1] == '''2'''
def test_PUT(self):
data = {"id": 1, "name": u("test")}
content = json.dumps(dict(data=data))
headers = {
'Content-Type': 'application/json',
}
res = self.app.put(
'/crud',
content,
headers=headers,
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
assert result['message'] == "create"
def test_GET(self):
headers = {
'Accept': 'application/json',
}
res = self.app.get(
'/crud?ref.id=1',
headers=headers,
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
def test_GET_complex_accept(self):
headers = {
'Accept': 'text/html,application/xml;q=0.9,*/*;q=0.8'
}
res = self.app.get(
'/crud?ref.id=1',
headers=headers,
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
def test_GET_complex_choose_xml(self):
headers = {
'Accept': 'text/html,text/xml;q=0.9,*/*;q=0.8'
}
res = self.app.get(
'/crud?ref.id=1',
headers=headers,
expect_errors=False)
print("Received:", res.body)
assert res.content_type == 'text/xml'
def test_GET_complex_accept_no_match(self):
headers = {
'Accept': 'text/html,application/xml;q=0.9'
}
res = self.app.get(
'/crud?ref.id=1',
headers=headers,
status=406)
print("Received:", res.body)
assert res.body == b("Unacceptable Accept type: "
"text/html, application/xml;q=0.9 not in "
"['application/json', 'text/javascript', "
"'application/javascript', 'text/xml']")
def test_GET_bad_simple_accept(self):
headers = {
'Accept': 'text/plain',
}
res = self.app.get(
'/crud?ref.id=1',
headers=headers,
status=406)
print("Received:", res.body)
assert res.body == b("Unacceptable Accept type: text/plain not in "
"['application/json', 'text/javascript', "
"'application/javascript', 'text/xml']")
def test_POST(self):
headers = {
'Content-Type': 'application/json',
}
res = self.app.post(
'/crud',
json.dumps(dict(data=dict(id=1, name=u('test')))),
headers=headers,
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
assert result['message'] == "update"
def test_POST_bad_content_type(self):
headers = {
'Content-Type': 'text/plain',
}
res = self.app.post(
'/crud',
json.dumps(dict(data=dict(id=1, name=u('test')))),
headers=headers,
status=415)
print("Received:", res.body)
assert res.body == b("Unacceptable Content-Type: text/plain not in "
"['application/json', 'text/javascript', "
"'application/javascript', 'text/xml']")
def test_DELETE(self):
res = self.app.delete(
'/crud.json?ref.id=1',
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
assert result['message'] == "delete"
def test_extra_arguments(self):
headers = {
'Accept': 'application/json',
}
res = self.app.get(
'/crud?ref.id=1&extraarg=foo',
headers=headers,
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
assert result['message'] == "read"
def test_unexpected_extra_arg(self):
headers = {
'Content-Type': 'application/json',
}
data = {"id": 1, "name": "test"}
content = json.dumps({"data": data, "other": "unexpected"})
res = self.app.put(
'/crud',
content,
headers=headers,
expect_errors=True)
self.assertEqual(res.status_int, 400)
def test_unexpected_extra_attribute(self):
"""Expect a failure if we send an unexpected object attribute."""
headers = {
'Content-Type': 'application/json',
}
data = {"id": 1, "name": "test", "other": "unexpected"}
content = json.dumps({"data": data})
res = self.app.put(
'/crud',
content,
headers=headers,
expect_errors=True)
self.assertEqual(res.status_int, 400)
def test_body_arg(self):
headers = {
'Content-Type': 'application/json',
}
res = self.app.post(
'/crud/update_with_body?msg=hello',
json.dumps(dict(id=1, name=u('test'))),
headers=headers,
expect_errors=False)
print("Received:", res.body)
result = json.loads(res.text)
print(result)
assert result['data']['id'] == 1
assert result['data']['name'] == u("test")
assert result['message'] == "hello"

View File

@@ -1,211 +0,0 @@
import decimal
import datetime
import base64
from six import u, b
import six
import wsme.tests.protocol
from wsme.utils import parse_isodatetime, parse_isodate, parse_isotime
from wsme.types import isarray, isdict, isusertype, register_type
from wsme.rest.xml import fromxml, toxml
try:
import xml.etree.ElementTree as et
except:
import cElementTree as et # noqa
def dumpxml(key, obj, datatype=None):
el = et.Element(key)
if isinstance(obj, tuple):
obj, datatype = obj
if isinstance(datatype, list):
for item in obj:
el.append(dumpxml('item', item, datatype[0]))
elif isinstance(datatype, dict):
key_type, value_type = list(datatype.items())[0]
for item in obj.items():
node = et.SubElement(el, 'item')
node.append(dumpxml('key', item[0], key_type))
node.append(dumpxml('value', item[1], value_type))
elif datatype == wsme.types.binary:
el.text = base64.encodestring(obj).decode('ascii')
elif isinstance(obj, wsme.types.bytes):
el.text = obj.decode('ascii')
elif isinstance(obj, wsme.types.text):
el.text = obj
elif type(obj) in (int, float, bool, decimal.Decimal):
el.text = six.text_type(obj)
elif type(obj) in (datetime.date, datetime.time, datetime.datetime):
el.text = obj.isoformat()
elif isinstance(obj, type(None)):
el.set('nil', 'true')
elif hasattr(datatype, '_wsme_attributes'):
for attr in datatype._wsme_attributes:
name = attr.name
if name not in obj:
continue
o = obj[name]
el.append(dumpxml(name, o, attr.datatype))
elif type(obj) == dict:
for name, value in obj.items():
el.append(dumpxml(name, value))
print(obj, datatype, et.tostring(el))
return el
def loadxml(el, datatype):
print(el, datatype, len(el))
if el.get('nil') == 'true':
return None
if isinstance(datatype, list):
return [loadxml(item, datatype[0]) for item in el.findall('item')]
elif isarray(datatype):
return [
loadxml(item, datatype.item_type) for item in el.findall('item')
]
elif isinstance(datatype, dict):
key_type, value_type = list(datatype.items())[0]
return dict((
(loadxml(item.find('key'), key_type),
loadxml(item.find('value'), value_type))
for item in el.findall('item')
))
elif isdict(datatype):
return dict((
(loadxml(item.find('key'), datatype.key_type),
loadxml(item.find('value'), datatype.value_type))
for item in el.findall('item')
))
elif isdict(datatype):
return dict((
(loadxml(item.find('key'), datatype.key_type),
loadxml(item.find('value'), datatype.value_type))
for item in el.findall('item')
))
elif len(el):
d = {}
for attr in datatype._wsme_attributes:
name = attr.name
child = el.find(name)
print(name, attr, child)
if child is not None:
d[name] = loadxml(child, attr.datatype)
print(d)
return d
else:
if datatype == wsme.types.binary:
return base64.decodestring(el.text.encode('ascii'))
if isusertype(datatype):
datatype = datatype.basetype
if datatype == datetime.date:
return parse_isodate(el.text)
if datatype == datetime.time:
return parse_isotime(el.text)
if datatype == datetime.datetime:
return parse_isodatetime(el.text)
if datatype == wsme.types.text:
return datatype(el.text if el.text else u(''))
if datatype == bool:
return el.text.lower() != 'false'
if datatype is None:
return el.text
if datatype is wsme.types.bytes:
return el.text.encode('ascii')
return datatype(el.text)
class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase):
protocol = 'restxml'
def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False,
body=None, **kw):
if body:
el = dumpxml('body', body)
else:
el = dumpxml('parameters', kw)
content = et.tostring(el)
headers = {
'Content-Type': 'text/xml',
}
if _accept is not None:
headers['Accept'] = _accept
res = self.app.post(
'/' + fpath,
content,
headers=headers,
expect_errors=True)
print("Received:", res.body)
if _no_result_decode:
return res
el = et.fromstring(res.body)
if el.tag == 'error':
raise wsme.tests.protocol.CallException(
el.find('faultcode').text,
el.find('faultstring').text,
el.find('debuginfo') is not None and
el.find('debuginfo').text or None
)
else:
return loadxml(et.fromstring(res.body), _rt)
def test_encode_sample_value(self):
class MyType(object):
aint = int
atext = wsme.types.text
register_type(MyType)
value = MyType()
value.aint = 5
value.atext = u('test')
language, sample = wsme.rest.xml.encode_sample_value(
MyType, value, True)
print(language, sample)
assert language == 'xml'
assert sample == b("""<value>
<aint>5</aint>
<atext>test</atext>
</value>""")
def test_encode_sample_params(self):
lang, content = wsme.rest.xml.encode_sample_params(
[('a', int, 2)], True)
assert lang == 'xml', lang
assert content == b('<parameters>\n <a>2</a>\n</parameters>'), content
def test_encode_sample_result(self):
lang, content = wsme.rest.xml.encode_sample_result(int, 2, True)
assert lang == 'xml', lang
assert content == b('<result>2</result>'), content
def test_nil_fromxml(self):
for dt in (
str, [int], {int: str}, bool,
datetime.date, datetime.time, datetime.datetime):
e = et.Element('value', nil='true')
assert fromxml(dt, e) is None
def test_nil_toxml(self):
for dt in (
wsme.types.bytes,
[int], {int: str}, bool,
datetime.date, datetime.time, datetime.datetime):
x = et.tostring(toxml(dt, 'value', None))
assert x == b('<value nil="true" />'), x
def test_unset_attrs(self):
class AType(object):
someattr = wsme.types.bytes
wsme.types.register_type(AType)
x = et.tostring(toxml(AType, 'value', AType()))
assert x == b('<value />'), x

View File

@@ -1,122 +0,0 @@
# encoding=utf8
import unittest
from wsme import WSRoot
import wsme.protocol
import wsme.rest.protocol
from wsme.root import default_prepare_response_body
from six import b, u
from webob import Request
class TestRoot(unittest.TestCase):
def test_default_transaction(self):
import transaction
root = WSRoot(transaction=True)
assert root._transaction is transaction
txn = root.begin()
txn.abort()
def test_default_prepare_response_body(self):
default_prepare_response_body(None, [b('a')]) == b('a')
default_prepare_response_body(None, [b('a'), b('b')]) == b('a\nb')
default_prepare_response_body(None, [u('a')]) == u('a')
default_prepare_response_body(None, [u('a'), u('b')]) == u('a\nb')
def test_protocol_selection_error(self):
class P(wsme.protocol.Protocol):
name = "test"
def accept(self, r):
raise Exception('test')
root = WSRoot()
root.addprotocol(P())
from webob import Request
req = Request.blank('/test?check=a&check=b&name=Bob')
res = root._handle_request(req)
assert res.status_int == 500
assert res.content_type == 'text/plain'
assert (res.text ==
'Unexpected error while selecting protocol: test'), req.text
def test_protocol_selection_accept_mismatch(self):
"""Verify that we get a 406 error on wrong Accept header."""
class P(wsme.protocol.Protocol):
name = "test"
def accept(self, r):
return False
root = WSRoot()
root.addprotocol(wsme.rest.protocol.RestProtocol())
root.addprotocol(P())
req = Request.blank('/test?check=a&check=b&name=Bob')
req.method = 'GET'
res = root._handle_request(req)
assert res.status_int == 406
assert res.content_type == 'text/plain'
assert res.text.startswith(
'None of the following protocols can handle this request'
), req.text
def test_protocol_selection_content_type_mismatch(self):
"""Verify that we get a 415 error on wrong Content-Type header."""
class P(wsme.protocol.Protocol):
name = "test"
def accept(self, r):
return False
root = WSRoot()
root.addprotocol(wsme.rest.protocol.RestProtocol())
root.addprotocol(P())
req = Request.blank('/test?check=a&check=b&name=Bob')
req.method = 'POST'
req.headers['Content-Type'] = "test/unsupported"
res = root._handle_request(req)
assert res.status_int == 415
assert res.content_type == 'text/plain'
assert res.text.startswith(
'Unacceptable Content-Type: test/unsupported not in'
), req.text
def test_protocol_selection_get_method(self):
class P(wsme.protocol.Protocol):
name = "test"
def accept(self, r):
return True
root = WSRoot()
root.addprotocol(wsme.rest.protocol.RestProtocol())
root.addprotocol(P())
req = Request.blank('/test?check=a&check=b&name=Bob')
req.method = 'GET'
req.headers['Accept'] = 'test/fake'
p = root._select_protocol(req)
assert p.name == "test"
def test_protocol_selection_post_method(self):
class P(wsme.protocol.Protocol):
name = "test"
def accept(self, r):
return True
root = WSRoot()
root.addprotocol(wsme.rest.protocol.RestProtocol())
root.addprotocol(P())
req = Request.blank('/test?check=a&check=b&name=Bob')
req.headers['Content-Type'] = 'test/fake'
req.method = 'POST'
p = root._select_protocol(req)
assert p.name == "test"

View File

@@ -1,51 +0,0 @@
import unittest
try:
import simplejson as json
except ImportError:
import json
from wsme.tests.protocol import WSTestRoot
import wsme.tests.test_restjson
import wsme.spore
class TestSpore(unittest.TestCase):
def test_spore(self):
spore = wsme.spore.getdesc(WSTestRoot())
print(spore)
spore = json.loads(spore)
assert len(spore['methods']) == 51, str(len(spore['methods']))
m = spore['methods']['argtypes_setbytesarray']
assert m['path'] == 'argtypes/setbytesarray', m['path']
assert m['optional_params'] == ['value']
assert m['method'] == 'POST'
m = spore['methods']['argtypes_setdecimal']
assert m['path'] == 'argtypes/setdecimal'
assert m['required_params'] == ['value']
assert m['method'] == 'GET'
m = spore['methods']['crud_create']
assert m['path'] == 'crud'
assert m['method'] == 'PUT'
assert m['optional_params'] == ['data']
m = spore['methods']['crud_read']
assert m['path'] == 'crud'
assert m['method'] == 'GET'
assert m['required_params'] == ['ref']
m = spore['methods']['crud_update']
assert m['path'] == 'crud'
assert m['method'] == 'POST'
assert m['optional_params'] == ['data']
m = spore['methods']['crud_delete']
assert m['path'] == 'crud'
assert m['method'] == 'DELETE'
assert m['optional_params'] == ['ref']

View File

@@ -1,667 +0,0 @@
import re
try:
import unittest2 as unittest
except ImportError:
import unittest
import six
from wsme import exc
from wsme import types
def gen_class():
d = {}
exec('''class tmp(object): pass''', d)
return d['tmp']
class TestTypes(unittest.TestCase):
def setUp(self):
types.registry = types.Registry()
def test_default_usertype(self):
class MyType(types.UserType):
basetype = str
My = MyType()
assert My.validate('a') == 'a'
assert My.tobasetype('a') == 'a'
assert My.frombasetype('a') == 'a'
def test_unset(self):
u = types.Unset
assert not u
def test_flat_type(self):
class Flat(object):
aint = int
abytes = six.binary_type
atext = six.text_type
afloat = float
types.register_type(Flat)
assert len(Flat._wsme_attributes) == 4
attrs = Flat._wsme_attributes
print(attrs)
assert attrs[0].key == 'aint'
assert attrs[0].name == 'aint'
assert isinstance(attrs[0], types.wsattr)
assert attrs[0].datatype == int
assert attrs[0].mandatory is False
assert attrs[1].key == 'abytes'
assert attrs[1].name == 'abytes'
assert attrs[2].key == 'atext'
assert attrs[2].name == 'atext'
assert attrs[3].key == 'afloat'
assert attrs[3].name == 'afloat'
def test_private_attr(self):
class WithPrivateAttrs(object):
_private = 12
types.register_type(WithPrivateAttrs)
assert len(WithPrivateAttrs._wsme_attributes) == 0
def test_attribute_order(self):
class ForcedOrder(object):
_wsme_attr_order = ('a2', 'a1', 'a3')
a1 = int
a2 = int
a3 = int
types.register_type(ForcedOrder)
print(ForcedOrder._wsme_attributes)
assert ForcedOrder._wsme_attributes[0].key == 'a2'
assert ForcedOrder._wsme_attributes[1].key == 'a1'
assert ForcedOrder._wsme_attributes[2].key == 'a3'
c = gen_class()
print(c)
types.register_type(c)
del c._wsme_attributes
c.a2 = int
c.a1 = int
c.a3 = int
types.register_type(c)
assert c._wsme_attributes[0].key == 'a1', c._wsme_attributes[0].key
assert c._wsme_attributes[1].key == 'a2'
assert c._wsme_attributes[2].key == 'a3'
def test_wsproperty(self):
class WithWSProp(object):
def __init__(self):
self._aint = 0
def get_aint(self):
return self._aint
def set_aint(self, value):
self._aint = value
aint = types.wsproperty(int, get_aint, set_aint, mandatory=True)
types.register_type(WithWSProp)
print(WithWSProp._wsme_attributes)
assert len(WithWSProp._wsme_attributes) == 1
a = WithWSProp._wsme_attributes[0]
assert a.key == 'aint'
assert a.datatype == int
assert a.mandatory
o = WithWSProp()
o.aint = 12
assert o.aint == 12
def test_nested(self):
class Inner(object):
aint = int
class Outer(object):
inner = Inner
types.register_type(Outer)
assert hasattr(Inner, '_wsme_attributes')
assert len(Inner._wsme_attributes) == 1
def test_inspect_with_inheritance(self):
class Parent(object):
parent_attribute = int
class Child(Parent):
child_attribute = int
types.register_type(Parent)
types.register_type(Child)
assert len(Child._wsme_attributes) == 2
def test_selfreftype(self):
class SelfRefType(object):
pass
SelfRefType.parent = SelfRefType
types.register_type(SelfRefType)
def test_inspect_with_property(self):
class AType(object):
@property
def test(self):
return 'test'
types.register_type(AType)
assert len(AType._wsme_attributes) == 0
assert AType().test == 'test'
def test_enum(self):
aenum = types.Enum(str, 'v1', 'v2')
assert aenum.basetype is str
class AType(object):
a = aenum
types.register_type(AType)
assert AType.a.datatype is aenum
obj = AType()
obj.a = 'v1'
assert obj.a == 'v1', repr(obj.a)
self.assertRaisesRegexp(exc.InvalidInput,
"Invalid input for field/attribute a. \
Value: 'v3'. Value should be one of: v., v.",
setattr,
obj,
'a',
'v3')
def test_attribute_validation(self):
class AType(object):
alist = [int]
aint = int
types.register_type(AType)
obj = AType()
obj.alist = [1, 2, 3]
assert obj.alist == [1, 2, 3]
obj.aint = 5
assert obj.aint == 5
self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', 12)
self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', [2, 'a'])
def test_attribute_validation_minimum(self):
class ATypeInt(object):
attr = types.IntegerType(minimum=1, maximum=5)
types.register_type(ATypeInt)
obj = ATypeInt()
obj.attr = 2
# comparison between 'zero' value and intger minimum (1) raises a
# TypeError which must be wrapped into an InvalidInput exception
self.assertRaises(exc.InvalidInput, setattr, obj, 'attr', 'zero')
def test_text_attribute_conversion(self):
class SType(object):
atext = types.text
abytes = types.bytes
types.register_type(SType)
obj = SType()
obj.atext = six.b('somebytes')
assert obj.atext == six.u('somebytes')
assert isinstance(obj.atext, types.text)
obj.abytes = six.u('sometext')
assert obj.abytes == six.b('sometext')
assert isinstance(obj.abytes, types.bytes)
def test_named_attribute(self):
class ABCDType(object):
a_list = types.wsattr([int], name='a.list')
astr = str
types.register_type(ABCDType)
assert len(ABCDType._wsme_attributes) == 2
attrs = ABCDType._wsme_attributes
assert attrs[0].key == 'a_list', attrs[0].key
assert attrs[0].name == 'a.list', attrs[0].name
assert attrs[1].key == 'astr', attrs[1].key
assert attrs[1].name == 'astr', attrs[1].name
def test_wsattr_del(self):
class MyType(object):
a = types.wsattr(int)
types.register_type(MyType)
value = MyType()
value.a = 5
assert value.a == 5
del value.a
assert value.a is types.Unset
def test_validate_dict(self):
assert types.validate_value({int: str}, {1: '1', 5: '5'})
self.assertRaises(ValueError, types.validate_value,
{int: str}, [])
assert types.validate_value({int: str}, {'1': '1', 5: '5'})
self.assertRaises(ValueError, types.validate_value,
{int: str}, {1: 1, 5: '5'})
def test_validate_list_valid(self):
assert types.validate_value([int], [1, 2])
assert types.validate_value([int], ['5'])
def test_validate_list_empty(self):
assert types.validate_value([int], []) == []
def test_validate_list_none(self):
v = types.ArrayType(int)
assert v.validate(None) is None
def test_validate_list_invalid_member(self):
self.assertRaises(ValueError, types.validate_value, [int],
['not-a-number'])
def test_validate_list_invalid_type(self):
self.assertRaises(ValueError, types.validate_value, [int], 1)
def test_validate_float(self):
self.assertEqual(types.validate_value(float, 1), 1.0)
self.assertEqual(types.validate_value(float, '1'), 1.0)
self.assertEqual(types.validate_value(float, 1.1), 1.1)
self.assertRaises(ValueError, types.validate_value, float, [])
self.assertRaises(ValueError, types.validate_value, float,
'not-a-float')
def test_validate_int(self):
self.assertEqual(types.validate_value(int, 1), 1)
self.assertEqual(types.validate_value(int, '1'), 1)
self.assertEqual(types.validate_value(int, six.u('1')), 1)
self.assertRaises(ValueError, types.validate_value, int, 1.1)
def test_validate_integer_type(self):
v = types.IntegerType(minimum=1, maximum=10)
v.validate(1)
v.validate(5)
v.validate(10)
self.assertRaises(ValueError, v.validate, 0)
self.assertRaises(ValueError, v.validate, 11)
def test_validate_string_type(self):
v = types.StringType(min_length=1, max_length=10,
pattern='^[a-zA-Z0-9]*$')
v.validate('1')
v.validate('12345')
v.validate('1234567890')
self.assertRaises(ValueError, v.validate, '')
self.assertRaises(ValueError, v.validate, '12345678901')
# Test a pattern validation
v.validate('a')
v.validate('A')
self.assertRaises(ValueError, v.validate, '_')
def test_validate_string_type_precompile(self):
precompile = re.compile('^[a-zA-Z0-9]*$')
v = types.StringType(min_length=1, max_length=10,
pattern=precompile)
# Test a pattern validation
v.validate('a')
v.validate('A')
self.assertRaises(ValueError, v.validate, '_')
def test_validate_string_type_pattern_exception_message(self):
regex = '^[a-zA-Z0-9]*$'
v = types.StringType(pattern=regex)
try:
v.validate('_')
self.assertFail()
except ValueError as e:
self.assertIn(regex, str(e))
def test_validate_ipv4_address_type(self):
v = types.IPv4AddressType()
self.assertEqual(v.validate('127.0.0.1'), '127.0.0.1')
self.assertEqual(v.validate('192.168.0.1'), '192.168.0.1')
self.assertEqual(v.validate(u'8.8.1.1'), u'8.8.1.1')
self.assertRaises(ValueError, v.validate, '')
self.assertRaises(ValueError, v.validate, 'foo')
self.assertRaises(ValueError, v.validate,
'2001:0db8:bd05:01d2:288a:1fc0:0001:10ee')
self.assertRaises(ValueError, v.validate, '1.2.3')
def test_validate_ipv6_address_type(self):
v = types.IPv6AddressType()
self.assertEqual(v.validate('0:0:0:0:0:0:0:1'),
'0:0:0:0:0:0:0:1')
self.assertEqual(v.validate(u'0:0:0:0:0:0:0:1'), u'0:0:0:0:0:0:0:1')
self.assertEqual(v.validate('2001:0db8:bd05:01d2:288a:1fc0:0001:10ee'),
'2001:0db8:bd05:01d2:288a:1fc0:0001:10ee')
self.assertRaises(ValueError, v.validate, '')
self.assertRaises(ValueError, v.validate, 'foo')
self.assertRaises(ValueError, v.validate, '192.168.0.1')
self.assertRaises(ValueError, v.validate, '0:0:0:0:0:0:1')
def test_validate_uuid_type(self):
v = types.UuidType()
self.assertEqual(v.validate('6a0a707c-45ef-4758-b533-e55adddba8ce'),
'6a0a707c-45ef-4758-b533-e55adddba8ce')
self.assertEqual(v.validate('6a0a707c45ef4758b533e55adddba8ce'),
'6a0a707c-45ef-4758-b533-e55adddba8ce')
self.assertRaises(ValueError, v.validate, '')
self.assertRaises(ValueError, v.validate, 'foo')
self.assertRaises(ValueError, v.validate,
'6a0a707c-45ef-4758-b533-e55adddba8ce-a')
def test_register_invalid_array(self):
self.assertRaises(ValueError, types.register_type, [])
self.assertRaises(ValueError, types.register_type, [int, str])
self.assertRaises(AttributeError, types.register_type, [1])
def test_register_invalid_dict(self):
self.assertRaises(ValueError, types.register_type, {})
self.assertRaises(ValueError, types.register_type,
{int: str, str: int})
self.assertRaises(ValueError, types.register_type,
{types.Unset: str})
def test_list_attribute_no_auto_register(self):
class MyType(object):
aint = int
assert not hasattr(MyType, '_wsme_attributes')
self.assertRaises(TypeError, types.list_attributes, MyType)
assert not hasattr(MyType, '_wsme_attributes')
def test_list_of_complextypes(self):
class A(object):
bs = types.wsattr(['B'])
class B(object):
i = int
types.register_type(A)
types.register_type(B)
assert A.bs.datatype.item_type is B
def test_cross_referenced_types(self):
class A(object):
b = types.wsattr('B')
class B(object):
a = A
types.register_type(A)
types.register_type(B)
assert A.b.datatype is B
def test_base(self):
class B1(types.Base):
b2 = types.wsattr('B2')
class B2(types.Base):
b2 = types.wsattr('B2')
assert B1.b2.datatype is B2, repr(B1.b2.datatype)
assert B2.b2.datatype is B2
def test_base_init(self):
class C1(types.Base):
s = six.text_type
c = C1(s=six.u('test'))
assert c.s == six.u('test')
def test_array_eq(self):
l = [types.ArrayType(str)]
assert types.ArrayType(str) in l
def test_array_sample(self):
s = types.ArrayType(str).sample()
assert isinstance(s, list)
assert s
assert s[0] == ''
def test_dict_sample(self):
s = types.DictType(str, str).sample()
assert isinstance(s, dict)
assert s
assert s == {'': ''}
def test_binary_to_base(self):
import base64
assert types.binary.tobasetype(None) is None
expected = base64.encodestring(six.b('abcdef'))
assert types.binary.tobasetype(six.b('abcdef')) == expected
def test_binary_from_base(self):
import base64
assert types.binary.frombasetype(None) is None
encoded = base64.encodestring(six.b('abcdef'))
assert types.binary.frombasetype(encoded) == six.b('abcdef')
def test_wsattr_weakref_datatype(self):
# If the datatype inside the wsattr ends up a weakref, it
# should be converted to the real type when accessed again by
# the property getter.
import weakref
a = types.wsattr(int)
a.datatype = weakref.ref(int)
assert a.datatype is int
def test_wsattr_list_datatype(self):
# If the datatype inside the wsattr ends up a list of weakrefs
# to types, it should be converted to the real types when
# accessed again by the property getter.
import weakref
a = types.wsattr(int)
a.datatype = [weakref.ref(int)]
assert isinstance(a.datatype, list)
assert a.datatype[0] is int
def test_file_get_content_by_reading(self):
class buffer:
def read(self):
return 'abcdef'
f = types.File(file=buffer())
assert f.content == 'abcdef'
def test_file_content_overrides_file(self):
class buffer:
def read(self):
return 'from-file'
f = types.File(content='from-content', file=buffer())
assert f.content == 'from-content'
def test_file_setting_content_discards_file(self):
class buffer:
def read(self):
return 'from-file'
f = types.File(file=buffer())
f.content = 'from-content'
assert f.content == 'from-content'
def test_file_field_storage(self):
class buffer:
def read(self):
return 'from-file'
class fieldstorage:
filename = 'static.json'
file = buffer()
type = 'application/json'
f = types.File(fieldstorage=fieldstorage)
assert f.content == 'from-file'
def test_file_field_storage_value(self):
class buffer:
def read(self):
return 'from-file'
class fieldstorage:
filename = 'static.json'
file = None
type = 'application/json'
value = 'from-value'
f = types.File(fieldstorage=fieldstorage)
assert f.content == 'from-value'
def test_file_property_file(self):
class buffer:
def read(self):
return 'from-file'
buf = buffer()
f = types.File(file=buf)
assert f.file is buf
def test_file_property_content(self):
class buffer:
def read(self):
return 'from-file'
f = types.File(content=six.b('from-content'))
assert f.file.read() == six.b('from-content')
def test_unregister(self):
class TempType(object):
pass
types.registry.register(TempType)
v = types.registry.lookup('TempType')
self.assertIs(v, TempType)
types.registry._unregister(TempType)
after = types.registry.lookup('TempType')
self.assertIs(after, None)
def test_unregister_twice(self):
class TempType(object):
pass
types.registry.register(TempType)
v = types.registry.lookup('TempType')
self.assertIs(v, TempType)
types.registry._unregister(TempType)
# Second call should not raise an exception
types.registry._unregister(TempType)
after = types.registry.lookup('TempType')
self.assertIs(after, None)
def test_unregister_array_type(self):
class TempType(object):
pass
t = [TempType]
types.registry.register(t)
self.assertNotEqual(types.registry.array_types, set())
types.registry._unregister(t)
self.assertEqual(types.registry.array_types, set())
def test_unregister_array_type_twice(self):
class TempType(object):
pass
t = [TempType]
types.registry.register(t)
self.assertNotEqual(types.registry.array_types, set())
types.registry._unregister(t)
# Second call should not raise an exception
types.registry._unregister(t)
self.assertEqual(types.registry.array_types, set())
def test_unregister_dict_type(self):
class TempType(object):
pass
t = {str: TempType}
types.registry.register(t)
self.assertNotEqual(types.registry.dict_types, set())
types.registry._unregister(t)
self.assertEqual(types.registry.dict_types, set())
def test_unregister_dict_type_twice(self):
class TempType(object):
pass
t = {str: TempType}
types.registry.register(t)
self.assertNotEqual(types.registry.dict_types, set())
types.registry._unregister(t)
# Second call should not raise an exception
types.registry._unregister(t)
self.assertEqual(types.registry.dict_types, set())
def test_reregister(self):
class TempType(object):
pass
types.registry.register(TempType)
v = types.registry.lookup('TempType')
self.assertIs(v, TempType)
types.registry.reregister(TempType)
after = types.registry.lookup('TempType')
self.assertIs(after, TempType)
def test_reregister_and_add_attr(self):
class TempType(object):
pass
types.registry.register(TempType)
attrs = types.list_attributes(TempType)
self.assertEqual(attrs, [])
TempType.one = str
types.registry.reregister(TempType)
after = types.list_attributes(TempType)
self.assertNotEqual(after, [])
def test_dynamicbase_add_attributes(self):
class TempType(types.DynamicBase):
pass
types.registry.register(TempType)
attrs = types.list_attributes(TempType)
self.assertEqual(attrs, [])
TempType.add_attributes(one=str)
after = types.list_attributes(TempType)
self.assertEqual(len(after), 1)
def test_dynamicbase_add_attributes_second(self):
class TempType(types.DynamicBase):
pass
types.registry.register(TempType)
attrs = types.list_attributes(TempType)
self.assertEqual(attrs, [])
TempType.add_attributes(one=str)
TempType.add_attributes(two=int)
after = types.list_attributes(TempType)
self.assertEqual(len(after), 2)
def test_non_registered_complex_type(self):
class TempType(types.Base):
__registry__ = None
self.assertFalse(types.iscomplex(TempType))
types.registry.register(TempType)
self.assertTrue(types.iscomplex(TempType))

View File

@@ -1,97 +0,0 @@
import datetime
import unittest
import pytz
from wsme import utils
class TestUtils(unittest.TestCase):
def test_parse_isodate(self):
good_dates = [
('2008-02-01', datetime.date(2008, 2, 1)),
('2009-01-04', datetime.date(2009, 1, 4)),
]
ill_formatted_dates = [
'24-12-2004'
]
out_of_range_dates = [
'0000-00-00',
'2012-02-30',
]
for s, d in good_dates:
assert utils.parse_isodate(s) == d
for s in ill_formatted_dates + out_of_range_dates:
self.assertRaises(ValueError, utils.parse_isodate, s)
def test_parse_isotime(self):
good_times = [
('12:03:54', datetime.time(12, 3, 54)),
('23:59:59.000004', datetime.time(23, 59, 59, 4)),
('01:02:03+00:00', datetime.time(1, 2, 3, 0, pytz.UTC)),
('01:02:03+23:59', datetime.time(1, 2, 3, 0,
pytz.FixedOffset(1439))),
('01:02:03-23:59', datetime.time(1, 2, 3, 0,
pytz.FixedOffset(-1439))),
]
ill_formatted_times = [
'24-12-2004'
]
out_of_range_times = [
'32:12:00',
'00:54:60',
'01:02:03-24:00',
'01:02:03+24:00',
]
for s, t in good_times:
assert utils.parse_isotime(s) == t
for s in ill_formatted_times + out_of_range_times:
self.assertRaises(ValueError, utils.parse_isotime, s)
def test_parse_isodatetime(self):
good_datetimes = [
('2008-02-12T12:03:54',
datetime.datetime(2008, 2, 12, 12, 3, 54)),
('2012-05-14T23:59:59.000004',
datetime.datetime(2012, 5, 14, 23, 59, 59, 4)),
('1856-07-10T01:02:03+00:00',
datetime.datetime(1856, 7, 10, 1, 2, 3, 0, pytz.UTC)),
('1856-07-10T01:02:03+23:59',
datetime.datetime(1856, 7, 10, 1, 2, 3, 0,
pytz.FixedOffset(1439))),
('1856-07-10T01:02:03-23:59',
datetime.datetime(1856, 7, 10, 1, 2, 3, 0,
pytz.FixedOffset(-1439))),
]
ill_formatted_datetimes = [
'24-32-2004',
'1856-07-10+33:00'
]
out_of_range_datetimes = [
'2008-02-12T32:12:00',
'2012-13-12T00:54:60',
]
for s, t in good_datetimes:
assert utils.parse_isodatetime(s) == t
for s in ill_formatted_datetimes + out_of_range_datetimes:
self.assertRaises(ValueError, utils.parse_isodatetime, s)
def test_validator_with_valid_code(self):
valid_code = 404
self.assertTrue(
utils.is_valid_code(valid_code),
"Valid status code not detected"
)
def test_validator_with_invalid_int_code(self):
invalid_int_code = 648
self.assertFalse(
utils.is_valid_code(invalid_int_code),
"Invalid status code not detected"
)
def test_validator_with_invalid_str_code(self):
invalid_str_code = '404'
self.assertFalse(
utils.is_valid_code(invalid_str_code),
"Invalid status code not detected"
)

View File

@@ -1,840 +0,0 @@
import base64
import datetime
import decimal
import inspect
import logging
import netaddr
import re
import six
import sys
import uuid
import weakref
from wsme import exc
log = logging.getLogger(__name__)
#: The 'str' (python 2) or 'bytes' (python 3) type.
#: Its use should be restricted to
#: pure ascii strings as the protocols will generally not be
#: be able to send non-unicode strings.
#: To transmit binary strings, use the :class:`binary` type
bytes = six.binary_type
#: Unicode string.
text = six.text_type
class ArrayType(object):
def __init__(self, item_type):
if iscomplex(item_type):
self._item_type = weakref.ref(item_type)
else:
self._item_type = item_type
def __hash__(self):
return hash(self.item_type)
def __eq__(self, other):
return isinstance(other, ArrayType) \
and self.item_type == other.item_type
def sample(self):
return [getattr(self.item_type, 'sample', self.item_type)()]
@property
def item_type(self):
if isinstance(self._item_type, weakref.ref):
return self._item_type()
else:
return self._item_type
def validate(self, value):
if value is None:
return
if not isinstance(value, list):
raise ValueError("Wrong type. Expected '[%s]', got '%s'" % (
self.item_type, type(value)
))
return [
validate_value(self.item_type, item)
for item in value
]
class DictType(object):
def __init__(self, key_type, value_type):
if key_type not in pod_types:
raise ValueError("Dictionaries key can only be a pod type")
self.key_type = key_type
if iscomplex(value_type):
self._value_type = weakref.ref(value_type)
else:
self._value_type = value_type
def __hash__(self):
return hash((self.key_type, self.value_type))
def sample(self):
key = getattr(self.key_type, 'sample', self.key_type)()
value = getattr(self.value_type, 'sample', self.value_type)()
return {key: value}
@property
def value_type(self):
if isinstance(self._value_type, weakref.ref):
return self._value_type()
else:
return self._value_type
def validate(self, value):
if not isinstance(value, dict):
raise ValueError("Wrong type. Expected '{%s: %s}', got '%s'" % (
self.key_type, self.value_type, type(value)
))
return dict((
(
validate_value(self.key_type, key),
validate_value(self.value_type, v)
) for key, v in value.items()
))
class UserType(object):
basetype = None
name = None
def validate(self, value):
return value
def tobasetype(self, value):
return value
def frombasetype(self, value):
return value
def isusertype(class_):
return isinstance(class_, UserType)
class BinaryType(UserType):
"""
A user type that use base64 strings to carry binary data.
"""
basetype = bytes
name = 'binary'
def tobasetype(self, value):
if value is None:
return None
return base64.encodestring(value)
def frombasetype(self, value):
if value is None:
return None
return base64.decodestring(value)
#: The binary almost-native type
binary = BinaryType()
class IntegerType(UserType):
"""
A simple integer type. Can validate a value range.
:param minimum: Possible minimum value
:param maximum: Possible maximum value
Example::
Price = IntegerType(minimum=1)
"""
basetype = int
name = "integer"
def __init__(self, minimum=None, maximum=None):
self.minimum = minimum
self.maximum = maximum
@staticmethod
def frombasetype(value):
return int(value) if value is not None else None
def validate(self, value):
if self.minimum is not None and value < self.minimum:
error = 'Value should be greater or equal to %s' % self.minimum
raise ValueError(error)
if self.maximum is not None and value > self.maximum:
error = 'Value should be lower or equal to %s' % self.maximum
raise ValueError(error)
return value
class StringType(UserType):
"""
A simple string type. Can validate a length and a pattern.
:param min_length: Possible minimum length
:param max_length: Possible maximum length
:param pattern: Possible string pattern
Example::
Name = StringType(min_length=1, pattern='^[a-zA-Z ]*$')
"""
basetype = six.string_types
name = "string"
def __init__(self, min_length=None, max_length=None, pattern=None):
self.min_length = min_length
self.max_length = max_length
if isinstance(pattern, six.string_types):
self.pattern = re.compile(pattern)
else:
self.pattern = pattern
def validate(self, value):
if not isinstance(value, self.basetype):
error = 'Value should be string'
raise ValueError(error)
if self.min_length is not None and len(value) < self.min_length:
error = 'Value should have a minimum character requirement of %s' \
% self.min_length
raise ValueError(error)
if self.max_length is not None and len(value) > self.max_length:
error = 'Value should have a maximum character requirement of %s' \
% self.max_length
raise ValueError(error)
if self.pattern is not None and not self.pattern.search(value):
error = 'Value should match the pattern %s' % self.pattern.pattern
raise ValueError(error)
return value
class IPv4AddressType(UserType):
"""
A simple IPv4 type.
"""
basetype = six.string_types
name = "ipv4address"
@staticmethod
def validate(value):
try:
netaddr.IPAddress(value, version=4, flags=netaddr.INET_PTON)
except netaddr.AddrFormatError:
error = 'Value should be IPv4 format'
raise ValueError(error)
else:
return value
class IPv6AddressType(UserType):
"""
A simple IPv6 type.
This type represents IPv6 addresses in the short format.
"""
basetype = six.string_types
name = "ipv6address"
@staticmethod
def validate(value):
try:
netaddr.IPAddress(value, version=6, flags=netaddr.INET_PTON)
except netaddr.AddrFormatError:
error = 'Value should be IPv6 format'
raise ValueError(error)
else:
return value
class UuidType(UserType):
"""
A simple UUID type.
This type allows not only UUID having dashes but also UUID not
having dashes. For example, '6a0a707c-45ef-4758-b533-e55adddba8ce'
and '6a0a707c45ef4758b533e55adddba8ce' are distinguished as valid.
"""
basetype = six.string_types
name = "uuid"
@staticmethod
def validate(value):
try:
return six.text_type((uuid.UUID(value)))
except (TypeError, ValueError, AttributeError):
error = 'Value should be UUID format'
raise ValueError(error)
class Enum(UserType):
"""
A simple enumeration type. Can be based on any non-complex type.
:param basetype: The actual data type
:param values: A set of possible values
If nullable, 'None' should be added the values set.
Example::
Gender = Enum(str, 'male', 'female')
Specie = Enum(str, 'cat', 'dog')
"""
def __init__(self, basetype, *values, **kw):
self.basetype = basetype
self.values = set(values)
name = kw.pop('name', None)
if name is None:
name = "Enum(%s)" % ', '.join((six.text_type(v) for v in values))
self.name = name
def validate(self, value):
if value not in self.values:
raise ValueError("Value should be one of: %s" %
', '.join(map(six.text_type, self.values)))
return value
def tobasetype(self, value):
return value
def frombasetype(self, value):
return value
class UnsetType(object):
if sys.version < '3':
def __nonzero__(self):
return False
else:
def __bool__(self):
return False
def __repr__(self):
return 'Unset'
Unset = UnsetType()
#: A special type that corresponds to the host framework request object.
#: It can only be used in the function parameters, and if so the request object
#: of the host framework will be passed to the function.
HostRequest = object()
pod_types = six.integer_types + (
bytes, text, float, bool)
dt_types = (datetime.date, datetime.time, datetime.datetime)
extra_types = (binary, decimal.Decimal)
native_types = pod_types + dt_types + extra_types
# The types for which we allow promotion to certain numbers.
_promotable_types = six.integer_types + (text, bytes)
def iscomplex(datatype):
return inspect.isclass(datatype) \
and '_wsme_attributes' in datatype.__dict__
def isarray(datatype):
return isinstance(datatype, ArrayType)
def isdict(datatype):
return isinstance(datatype, DictType)
def validate_value(datatype, value):
if value in (Unset, None):
return value
# Try to promote the data type to one of our complex types.
if isinstance(datatype, list):
datatype = ArrayType(datatype[0])
elif isinstance(datatype, dict):
datatype = DictType(*list(datatype.items())[0])
# If the datatype has its own validator, use that.
if hasattr(datatype, 'validate'):
return datatype.validate(value)
# Do type promotion/conversion and data validation for builtin
# types.
v_type = type(value)
if datatype in six.integer_types:
if v_type in _promotable_types:
try:
# Try to turn the value into an int
value = datatype(value)
except ValueError:
# An error is raised at the end of the function
# when the types don't match.
pass
elif datatype is float and v_type in _promotable_types:
try:
value = float(value)
except ValueError:
# An error is raised at the end of the function
# when the types don't match.
pass
elif datatype is text and isinstance(value, bytes):
value = value.decode()
elif datatype is bytes and isinstance(value, text):
value = value.encode()
if not isinstance(value, datatype):
raise ValueError(
"Wrong type. Expected '%s', got '%s'" % (
datatype, v_type
))
return value
class wsproperty(property):
"""
A specialised :class:`property` to define typed-property on complex types.
Example::
class MyComplexType(wsme.types.Base):
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, name=None):
property.__init__(self, fget, fset)
#: The property name in the parent python class
self.key = None
#: The attribute name on the public of the api.
#: Defaults to :attr:`key`
self.name = name
#: property data type
self.datatype = datatype
#: True if the property is mandatory
self.mandatory = mandatory
class wsattr(object):
"""
Complex type attribute definition.
Example::
class MyComplexType(wsme.types.Base):
optionalvalue = int
mandatoryvalue = wsattr(int, mandatory=True)
named_value = wsattr(int, name='named.value')
After inspection, the non-wsattr attributes will be replaced, and
the above class will be equivalent to::
class MyComplexType(wsme.types.Base):
optionalvalue = wsattr(int)
mandatoryvalue = wsattr(int, mandatory=True)
"""
def __init__(self, datatype, mandatory=False, name=None, default=Unset,
readonly=False):
#: The attribute name in the parent python class.
#: Set by :func:`inspect_class`
self.key = None # will be set by class inspection
#: The attribute name on the public of the api.
#: Defaults to :attr:`key`
self.name = name
self._datatype = (datatype,)
#: True if the attribute is mandatory
self.mandatory = mandatory
#: Default value. The attribute will return this instead
#: of :data:`Unset` if no value has been set.
self.default = default
#: If True value cannot be set from json/xml input data
self.readonly = readonly
self.complextype = None
def _get_dataholder(self, instance):
dataholder = getattr(instance, '_wsme_dataholder', None)
if dataholder is None:
dataholder = instance._wsme_DataHolderClass()
instance._wsme_dataholder = dataholder
return dataholder
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(
self._get_dataholder(instance),
self.key,
self.default
)
def __set__(self, instance, value):
try:
value = validate_value(self.datatype, value)
except (ValueError, TypeError) as e:
raise exc.InvalidInput(self.name, value, six.text_type(e))
dataholder = self._get_dataholder(instance)
if value is Unset:
if hasattr(dataholder, self.key):
delattr(dataholder, self.key)
else:
setattr(dataholder, self.key, value)
def __delete__(self, instance):
self.__set__(instance, Unset)
def _get_datatype(self):
if isinstance(self._datatype, tuple):
self._datatype = \
self.complextype().__registry__.resolve_type(self._datatype[0])
if isinstance(self._datatype, weakref.ref):
return self._datatype()
if isinstance(self._datatype, list):
return [
item() if isinstance(item, weakref.ref) else item
for item in self._datatype
]
return self._datatype
def _set_datatype(self, datatype):
self._datatype = datatype
#: attribute data type. Can be either an actual type,
#: or a type name, in which case the actual type will be
#: determined when needed (generally just before scanning the api).
datatype = property(_get_datatype, _set_datatype)
def iswsattr(attr):
if inspect.isfunction(attr) or inspect.ismethod(attr):
return False
if isinstance(attr, property) and not isinstance(attr, wsproperty):
return False
return True
def sort_attributes(class_, attributes):
"""Sort a class attributes list.
3 mechanisms are attempted :
#. Look for a _wsme_attr_order attribute on the class_. This allow
to define an arbitrary order of the attributes (useful for
generated types).
#. Access the object source code to find the declaration order.
#. Sort by alphabetically"""
if not len(attributes):
return
attrs = dict((a.key, a) for a in attributes)
if hasattr(class_, '_wsme_attr_order'):
names_order = class_._wsme_attr_order
else:
names = attrs.keys()
names_order = []
try:
lines = []
for cls in inspect.getmro(class_):
if cls is object:
continue
lines[len(lines):] = inspect.getsourcelines(cls)[0]
for line in lines:
line = line.strip().replace(" ", "")
if '=' in line:
aname = line[:line.index('=')]
if aname in names and aname not in names_order:
names_order.append(aname)
if len(names_order) < len(names):
names_order.extend((
name for name in names if name not in names_order))
assert len(names_order) == len(names)
except (TypeError, IOError):
names_order = list(names)
names_order.sort()
attributes[:] = [attrs[name] for name in names_order]
def inspect_class(class_):
"""Extract a list of (name, wsattr|wsproperty) for the given class_"""
attributes = []
for name, attr in inspect.getmembers(class_, iswsattr):
if name.startswith('_'):
continue
if inspect.isroutine(attr):
continue
if isinstance(attr, (wsattr, wsproperty)):
attrdef = attr
else:
if attr not in native_types and (
inspect.isclass(attr) or
isinstance(attr, (list, dict))):
register_type(attr)
attrdef = getattr(class_, '__wsattrclass__', wsattr)(attr)
attrdef.key = name
if attrdef.name is None:
attrdef.name = name
attrdef.complextype = weakref.ref(class_)
attributes.append(attrdef)
setattr(class_, name, attrdef)
sort_attributes(class_, attributes)
return attributes
def list_attributes(class_):
"""
Returns a list of a complex type attributes.
"""
if not iscomplex(class_):
raise TypeError("%s is not a registered type")
return class_._wsme_attributes
def make_dataholder(class_):
# the slots are computed outside the class scope to avoid
# 'attr' to pullute the class namespace, which leads to weird
# things if one of the slots is named 'attr'.
slots = [attr.key for attr in class_._wsme_attributes]
class DataHolder(object):
__slots__ = slots
DataHolder.__name__ = class_.__name__ + 'DataHolder'
return DataHolder
class Registry(object):
def __init__(self):
self._complex_types = []
self.array_types = set()
self.dict_types = set()
@property
def complex_types(self):
return [t() for t in self._complex_types if t()]
def register(self, 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 \
isusertype(class_) or iscomplex(class_) or \
isarray(class_) or isdict(class_):
return class_
if isinstance(class_, list):
if len(class_) != 1:
raise ValueError("Cannot register type %s" % repr(class_))
dt = ArrayType(class_[0])
self.register(dt.item_type)
self.array_types.add(dt)
return dt
if isinstance(class_, dict):
if len(class_) != 1:
raise ValueError("Cannot register type %s" % repr(class_))
dt = DictType(*list(class_.items())[0])
self.register(dt.value_type)
self.dict_types.add(dt)
return dt
class_._wsme_attributes = None
class_._wsme_attributes = inspect_class(class_)
class_._wsme_DataHolderClass = make_dataholder(class_)
class_.__registry__ = self
self._complex_types.append(weakref.ref(class_))
return class_
def reregister(self, class_):
"""Register a type which may already have been registered.
"""
self._unregister(class_)
return self.register(class_)
def _unregister(self, class_):
"""Remove a previously registered type.
"""
# Clear the existing attribute reference so it is rebuilt if
# the class is registered again later.
if hasattr(class_, '_wsme_attributes'):
del class_._wsme_attributes
# FIXME(dhellmann): This method does not recurse through the
# types like register() does. Should it?
if isinstance(class_, list):
at = ArrayType(class_[0])
try:
self.array_types.remove(at)
except KeyError:
pass
elif isinstance(class_, dict):
key_type, value_type = list(class_.items())[0]
self.dict_types = set(
dt for dt in self.dict_types
if (dt.key_type, dt.value_type) != (key_type, value_type)
)
# We can't use remove() here because the items in
# _complex_types are weakref objects pointing to the classes,
# so we can't compare with them directly.
self._complex_types = [
ct for ct in self._complex_types
if ct() is not class_
]
def lookup(self, typename):
log.debug('Lookup %s' % typename)
modname = None
if '.' in typename:
modname, typename = typename.rsplit('.', 1)
for ct in self._complex_types:
ct = ct()
if ct is not None and typename == ct.__name__ and (
modname is None or modname == ct.__module__):
return ct
def resolve_type(self, type_):
if isinstance(type_, six.string_types):
return self.lookup(type_)
if isinstance(type_, list):
type_ = ArrayType(type_[0])
if isinstance(type_, dict):
type_ = DictType(list(type_.keys())[0], list(type_.values())[0])
if isinstance(type_, ArrayType):
type_ = ArrayType(self.resolve_type(type_.item_type))
self.array_types.add(type_)
elif isinstance(type_, DictType):
type_ = DictType(
type_.key_type,
self.resolve_type(type_.value_type)
)
self.dict_types.add(type_)
else:
type_ = self.register(type_)
return type_
# Default type registry
registry = Registry()
def register_type(class_):
return registry.register(class_)
class BaseMeta(type):
def __new__(cls, name, bases, dct):
if bases and bases[0] is not object and '__registry__' not in dct:
dct['__registry__'] = registry
return type.__new__(cls, name, bases, dct)
def __init__(cls, name, bases, dct):
if bases and bases[0] is not object and cls.__registry__:
cls.__registry__.register(cls)
class Base(six.with_metaclass(BaseMeta)):
"""Base type for complex types"""
def __init__(self, **kw):
for key, value in kw.items():
if hasattr(self, key):
setattr(self, key, value)
class File(Base):
"""A complex type that represents a file.
In the particular case of protocol accepting form encoded data as
input, File can be loaded from a form file field.
"""
#: The file name
filename = wsattr(text)
#: Mime type of the content
contenttype = wsattr(text)
def _get_content(self):
if self._content is None and self._file:
self._content = self._file.read()
return self._content
def _set_content(self, value):
self._content = value
self._file = None
#: File content
content = wsproperty(binary, _get_content, _set_content)
def __init__(self, filename=None, file=None, content=None,
contenttype=None, fieldstorage=None):
self.filename = filename
self.contenttype = contenttype
self._file = file
self._content = content
if fieldstorage is not None:
if fieldstorage.file:
self._file = fieldstorage.file
self.filename = fieldstorage.filename
self.contenttype = text(fieldstorage.type)
else:
self._content = fieldstorage.value
@property
def file(self):
if self._file is None and self._content:
self._file = six.BytesIO(self._content)
return self._file
class DynamicBase(Base):
"""Base type for complex types for which all attributes are not
defined when the class is constructed.
This class is meant to be used as a base for types that have
properties added after the main class is created, such as by
loading plugins.
"""
@classmethod
def add_attributes(cls, **attrs):
"""Add more attributes
The arguments should be valid Python attribute names
associated with a type for the new attribute.
"""
for n, t in attrs.items():
setattr(cls, n, t)
cls.__registry__.reregister(cls)

View File

@@ -1,118 +0,0 @@
import decimal
import datetime
import pytz
import re
from six.moves import builtins, http_client
try:
import dateutil.parser
except:
dateutil = None # noqa
date_re = r'(?P<year>-?\d{4,})-(?P<month>\d{2})-(?P<day>\d{2})'
time_re = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})' + \
r'(\.(?P<sec_frac>\d+))?'
tz_re = r'((?P<tz_sign>[+-])(?P<tz_hour>\d{2}):(?P<tz_min>\d{2}))' + \
r'|(?P<tz_z>Z)'
datetime_re = re.compile(
'%sT%s(%s)?' % (date_re, time_re, tz_re))
date_re = re.compile(date_re)
time_re = re.compile('%s(%s)?' % (time_re, tz_re))
if hasattr(builtins, '_'):
_ = builtins._
else:
def _(s):
return s
def parse_isodate(value):
m = date_re.match(value)
if m is None:
raise ValueError("'%s' is not a legal date value" % (value))
try:
return datetime.date(
int(m.group('year')),
int(m.group('month')),
int(m.group('day')))
except ValueError:
raise ValueError("'%s' is a out-of-range date" % (value))
def parse_isotime(value):
m = time_re.match(value)
if m is None:
raise ValueError("'%s' is not a legal time value" % (value))
try:
ms = 0
if m.group('sec_frac') is not None:
f = decimal.Decimal('0.' + m.group('sec_frac'))
f = str(f.quantize(decimal.Decimal('0.000001')))
ms = int(f[2:])
tz = _parse_tzparts(m.groupdict())
return datetime.time(
int(m.group('hour')),
int(m.group('min')),
int(m.group('sec')),
ms,
tz)
except ValueError:
raise ValueError("'%s' is a out-of-range time" % (value))
def parse_isodatetime(value):
if dateutil:
return dateutil.parser.parse(value)
m = datetime_re.match(value)
if m is None:
raise ValueError("'%s' is not a legal datetime value" % (value))
try:
ms = 0
if m.group('sec_frac') is not None:
f = decimal.Decimal('0.' + m.group('sec_frac'))
f = f.quantize(decimal.Decimal('0.000001'))
ms = int(str(f)[2:])
tz = _parse_tzparts(m.groupdict())
return datetime.datetime(
int(m.group('year')),
int(m.group('month')),
int(m.group('day')),
int(m.group('hour')),
int(m.group('min')),
int(m.group('sec')),
ms,
tz)
except ValueError:
raise ValueError("'%s' is a out-of-range datetime" % (value))
def _parse_tzparts(parts):
if 'tz_z' in parts and parts['tz_z'] == 'Z':
return pytz.UTC
if 'tz_min' not in parts or not parts['tz_min']:
return None
tz_minute_offset = (int(parts['tz_hour']) * 60 + int(parts['tz_min']))
tz_multiplier = -1 if parts['tz_sign'] == '-' else 1
return pytz.FixedOffset(tz_multiplier * tz_minute_offset)
def is_valid_code(code_value):
"""
This function checks if incoming value in http response codes range.
"""
return code_value in http_client.responses
def is_client_error(code):
""" Checks client error code (RFC 2616)."""
return 400 <= code < 500
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict # noqa

View File

@@ -1,2 +0,0 @@
import pkg_resources
pkg_resources.declare_namespace(__name__)

View File

@@ -1,168 +0,0 @@
"""
WSME for cornice
Activate it::
config.include('wsme.cornice')
And use it::
@hello.get()
@wsexpose(Message, wsme.types.text)
def get_hello(who=u'World'):
return Message(text='Hello %s' % who)
"""
from __future__ import absolute_import
import inspect
import sys
import wsme
from wsme.rest import json as restjson
from wsme.rest import xml as restxml
import wsme.runtime
import wsme.api
import functools
from wsme.rest.args import (
args_from_args, args_from_params, args_from_body, combine_args
)
class WSMEJsonRenderer(object):
def __init__(self, info):
pass
def __call__(self, data, context):
response = context['request'].response
response.content_type = 'application/json'
if 'faultcode' in data:
if 'orig_code' in data:
response.status_code = data['orig_code']
elif data['faultcode'] == 'Client':
response.status_code = 400
else:
response.status_code = 500
return restjson.encode_error(None, data)
obj = data['result']
if isinstance(obj, wsme.api.Response):
response.status_code = obj.status_code
if obj.error:
return restjson.encode_error(None, obj.error)
obj = obj.obj
return restjson.encode_result(obj, data['datatype'])
class WSMEXmlRenderer(object):
def __init__(self, info):
pass
def __call__(self, data, context):
response = context['request'].response
if 'faultcode' in data:
if data['faultcode'] == 'Client':
response.status_code = 400
else:
response.status_code = 500
return restxml.encode_error(None, data)
response.content_type = 'text/xml'
return restxml.encode_result(data['result'], data['datatype'])
def get_outputformat(request):
df = None
if 'Accept' in request.headers:
if 'application/json' in request.headers['Accept']:
df = 'json'
elif 'text/xml' in request.headers['Accept']:
df = 'xml'
if df is None and 'Content-Type' in request.headers:
if 'application/json' in request.headers['Content-Type']:
df = 'json'
elif 'text/xml' in request.headers['Content-Type']:
df = 'xml'
return df if df else 'json'
def signature(*args, **kwargs):
sig = wsme.signature(*args, **kwargs)
def decorate(f):
args = inspect.getargspec(f)[0]
with_self = args[0] == 'self' if args else False
f = sig(f)
funcdef = wsme.api.FunctionDefinition.get(f)
funcdef.resolve_types(wsme.types.registry)
@functools.wraps(f)
def callfunction(*args):
if with_self:
if len(args) == 1:
self = args[0]
request = self.request
elif len(args) == 2:
self, request = args
else:
raise ValueError("Cannot do anything with these arguments")
else:
request = args[0]
request.override_renderer = 'wsme' + get_outputformat(request)
try:
args, kwargs = combine_args(funcdef, (
args_from_args(funcdef, (), request.matchdict),
args_from_params(funcdef, request.params),
args_from_body(funcdef, request.body, request.content_type)
))
wsme.runtime.check_arguments(funcdef, args, kwargs)
if funcdef.pass_request:
kwargs[funcdef.pass_request] = request
if with_self:
args.insert(0, self)
result = f(*args, **kwargs)
return {
'datatype': funcdef.return_type,
'result': result
}
except:
try:
exception_info = sys.exc_info()
orig_exception = exception_info[1]
orig_code = getattr(orig_exception, 'code', None)
data = wsme.api.format_exception(exception_info)
if orig_code is not None:
data['orig_code'] = orig_code
return data
finally:
del exception_info
callfunction.wsme_func = f
return callfunction
return decorate
def scan_api(root=None):
from cornice.service import get_services
for service in get_services():
for method, func, options in service.definitions:
wsme_func = getattr(func, 'wsme_func')
basepath = service.path.split('/')
if basepath and not basepath[0]:
del basepath[0]
if wsme_func:
yield (
basepath + [method.lower()],
wsme_func._wsme_definition
)
def includeme(config):
import pyramid.wsgi
wsroot = wsme.WSRoot(scan_api=scan_api, webpath='/ws')
wsroot.addprotocol('extdirect')
config.add_renderer('wsmejson', WSMEJsonRenderer)
config.add_renderer('wsmexml', WSMEXmlRenderer)
config.add_route('wsme', '/ws/*path')
config.add_view(pyramid.wsgi.wsgiapp(wsroot.wsgiapp()), route_name='wsme')

View File

@@ -1 +0,0 @@
from wsmeext.extdirect.protocol import ExtDirectProtocol # noqa

View File

@@ -1,121 +0,0 @@
import wsme
import wsme.types
try:
import simplejson as json
except ImportError:
import json
class ReadResultBase(wsme.types.Base):
total = int
success = bool
message = wsme.types.text
def make_readresult(datatype):
ReadResult = type(
datatype.__name__ + 'ReadResult',
(ReadResultBase,), {
'data': [datatype]
}
)
return ReadResult
class DataStoreControllerMeta(type):
def __init__(cls, name, bases, dct):
if cls.__datatype__ is None:
return
if getattr(cls, '__readresulttype__', None) is None:
cls.__readresulttype__ = make_readresult(cls.__datatype__)
cls.create = wsme.expose(
cls.__readresulttype__,
extdirect_params_notation='positional')(cls.create)
cls.create = wsme.validate(cls.__datatype__)(cls.create)
cls.read = wsme.expose(
cls.__readresulttype__,
extdirect_params_notation='named')(cls.read)
cls.read = wsme.validate(str, str, int, int, int)(cls.read)
cls.update = wsme.expose(
cls.__readresulttype__,
extdirect_params_notation='positional')(cls.update)
cls.update = wsme.validate(cls.__datatype__)(cls.update)
cls.destroy = wsme.expose(
cls.__readresulttype__,
extdirect_params_notation='positional')(cls.destroy)
cls.destroy = wsme.validate(cls.__idtype__)(cls.destroy)
class DataStoreControllerMixin(object):
__datatype__ = None
__idtype__ = int
__readresulttype__ = None
def create(self, obj):
pass
def read(self, query=None, sort=None, page=None, start=None, limit=None):
pass
def update(self, obj):
pass
def destroy(self, obj_id):
pass
def model(self):
tpl = """
Ext.define('%(appns)s.model.%(classname)s', {
extend: 'Ext.data.Model',
fields: %(fields)s,
proxy: {
type: 'direct',
api: {
create: %(appns)s.%(controllerns)s.create,
read: %(appns)s.%(controllerns)s.read,
update: %(appns)s.%(controllerns)s.update,
destroy: %(appns)s.%(controllerns)s.destroy
},
reader: {
root: 'data'
}
}
});
"""
fields = [
attr.name for attr in self.__datatype__._wsme_attributes
]
d = {
'appns': 'Demo',
'controllerns': 'stores.' + self.__datatype__.__name__.lower(),
'classname': self.__datatype__.__name__,
'fields': json.dumps(fields)
}
return tpl % d
def store(self):
tpl = """
Ext.define('%(appns)s.store.%(classname)s', {
extend: 'Ext.data.Store',
model: '%(appns)s.model.%(classname)s'
});
"""
d = {
'appns': 'Demo',
'classname': self.__datatype__.__name__,
}
return tpl % d
DataStoreController = DataStoreControllerMeta(
'DataStoreController',
(DataStoreControllerMixin,), {}
)

View File

@@ -1,450 +0,0 @@
import datetime
import decimal
from simplegeneric import generic
from wsme.exc import ClientSideError
from wsme.protocol import CallContext, Protocol, expose
from wsme.utils import parse_isodate, parse_isodatetime, parse_isotime
from wsme.rest.args import from_params
from wsme.types import iscomplex, isusertype, list_attributes, Unset
import wsme.types
try:
import simplejson as json
except ImportError:
import json # noqa
from six import u
class APIDefinitionGenerator(object):
tpl = """\
Ext.ns("%(rootns)s");
if (!%(rootns)s.wsroot) {
%(rootns)s.wsroot = "%(webpath)s.
}
%(descriptors)s
Ext.syncRequire(['Ext.direct.*'], function() {
%(providers)s
});
"""
descriptor_tpl = """\
Ext.ns("%(fullns)s");
%(fullns)s.Descriptor = {
"url": %(rootns)s.wsroot + "extdirect/router/%(ns)s",
"namespace": "%(fullns)s",
"type": "remoting",
"actions": %(actions)s
"enableBuffer": true
};
"""
provider_tpl = """\
Ext.direct.Manager.addProvider(%(fullns)s.Descriptor);
"""
def __init__(self):
pass
def render(self, rootns, webpath, namespaces, fullns):
descriptors = u('')
for ns in sorted(namespaces):
descriptors += self.descriptor_tpl % {
'ns': ns,
'rootns': rootns,
'fullns': fullns(ns),
'actions': '\n'.join((
' ' * 4 + line
for line
in json.dumps(namespaces[ns], indent=4).split('\n')
))
}
providers = u('')
for ns in sorted(namespaces):
providers += self.provider_tpl % {
'fullns': fullns(ns)
}
r = self.tpl % {
'rootns': rootns,
'webpath': webpath,
'descriptors': descriptors,
'providers': providers,
}
return r
@generic
def fromjson(datatype, value):
if value is None:
return None
if iscomplex(datatype):
newvalue = datatype()
for attrdef in list_attributes(datatype):
if attrdef.name in value:
setattr(newvalue, attrdef.key,
fromjson(attrdef.datatype, value[attrdef.name]))
value = newvalue
elif isusertype(datatype):
value = datatype.frombasetype(fromjson(datatype.basetype, value))
return value
@generic
def tojson(datatype, value):
if value is None:
return value
if iscomplex(datatype):
d = {}
for attrdef in list_attributes(datatype):
attrvalue = getattr(value, attrdef.key)
if attrvalue is not Unset:
d[attrdef.name] = tojson(attrdef.datatype, attrvalue)
value = d
elif isusertype(datatype):
value = tojson(datatype.basetype, datatype.tobasetype(value))
return value
@fromjson.when_type(wsme.types.ArrayType)
def array_fromjson(datatype, value):
return [fromjson(datatype.item_type, item) for item in value]
@tojson.when_type(wsme.types.ArrayType)
def array_tojson(datatype, value):
if value is None:
return value
return [tojson(datatype.item_type, item) for item in value]
@fromjson.when_type(wsme.types.DictType)
def dict_fromjson(datatype, value):
if value is None:
return value
return dict((
(fromjson(datatype.key_type, key),
fromjson(datatype.value_type, value))
for key, value in value.items()
))
@tojson.when_type(wsme.types.DictType)
def dict_tojson(datatype, value):
if value is None:
return value
return dict((
(tojson(datatype.key_type, key),
tojson(datatype.value_type, value))
for key, value in value.items()
))
@tojson.when_object(wsme.types.bytes)
def bytes_tojson(datatype, value):
if value is None:
return value
return value.decode('ascii')
# raw strings
@fromjson.when_object(wsme.types.bytes)
def bytes_fromjson(datatype, value):
if value is not None:
value = value.encode('ascii')
return value
# unicode strings
@fromjson.when_object(wsme.types.text)
def text_fromjson(datatype, value):
if isinstance(value, wsme.types.bytes):
return value.decode('utf-8')
return value
# datetime.time
@fromjson.when_object(datetime.time)
def time_fromjson(datatype, value):
if value is None or value == '':
return None
return parse_isotime(value)
@tojson.when_object(datetime.time)
def time_tojson(datatype, value):
if value is None:
return value
return value.isoformat()
# datetime.date
@fromjson.when_object(datetime.date)
def date_fromjson(datatype, value):
if value is None or value == '':
return None
return parse_isodate(value)
@tojson.when_object(datetime.date)
def date_tojson(datatype, value):
if value is None:
return value
return value.isoformat()
# datetime.datetime
@fromjson.when_object(datetime.datetime)
def datetime_fromjson(datatype, value):
if value is None or value == '':
return None
return parse_isodatetime(value)
@tojson.when_object(datetime.datetime)
def datetime_tojson(datatype, value):
if value is None:
return value
return value.isoformat()
# decimal.Decimal
@fromjson.when_object(decimal.Decimal)
def decimal_fromjson(datatype, value):
if value is None:
return value
return decimal.Decimal(value)
@tojson.when_object(decimal.Decimal)
def decimal_tojson(datatype, value):
if value is None:
return value
return str(value)
class ExtCallContext(CallContext):
def __init__(self, request, namespace, calldata):
super(ExtCallContext, self).__init__(request)
self.namespace = namespace
self.tid = calldata['tid']
self.action = calldata['action']
self.method = calldata['method']
self.params = calldata['data']
class FormExtCallContext(CallContext):
def __init__(self, request, namespace):
super(FormExtCallContext, self).__init__(request)
self.namespace = namespace
self.tid = request.params['extTID']
self.action = request.params['extAction']
self.method = request.params['extMethod']
self.params = []
class ExtDirectProtocol(Protocol):
"""
ExtDirect protocol.
For more detail on the protocol, see
http://www.sencha.com/products/extjs/extdirect.
.. autoattribute:: name
.. autoattribute:: content_types
"""
name = 'extdirect'
displayname = 'ExtDirect'
content_types = ['application/json', 'text/javascript']
def __init__(self, namespace='', params_notation='named', nsfolder=None):
self.namespace = namespace
self.appns, self.apins = namespace.rsplit('.', 2) \
if '.' in namespace else (namespace, '')
self.default_params_notation = params_notation
self.appnsfolder = nsfolder
@property
def api_alias(self):
if self.appnsfolder:
alias = '/%s/%s.js' % (
self.appnsfolder,
self.apins.replace('.', '/'))
return alias
def accept(self, req):
path = req.path
assert path.startswith(self.root._webpath)
path = path[len(self.root._webpath):]
return (
path == self.api_alias or
path == "/extdirect/api" or
path.startswith("/extdirect/router")
)
def iter_calls(self, req):
path = req.path
assert path.startswith(self.root._webpath)
path = path[len(self.root._webpath):].strip()
assert path.startswith('/extdirect/router'), path
path = path[17:].strip('/')
if path:
namespace = path.split('.')
else:
namespace = []
if 'extType' in req.params:
req.wsme_extdirect_batchcall = False
yield FormExtCallContext(req, namespace)
else:
data = json.loads(req.body.decode('utf8'))
req.wsme_extdirect_batchcall = isinstance(data, list)
if not req.wsme_extdirect_batchcall:
data = [data]
req.callcount = len(data)
for call in data:
yield ExtCallContext(req, namespace, call)
def extract_path(self, context):
path = list(context.namespace)
if context.action:
path.append(context.action)
path.append(context.method)
return path
def read_std_arguments(self, context):
funcdef = context.funcdef
notation = funcdef.extra_options.get('extdirect_params_notation',
self.default_params_notation)
args = context.params
if notation == 'positional':
kw = dict(
(argdef.name, fromjson(argdef.datatype, arg))
for argdef, arg in zip(funcdef.arguments, args)
)
elif notation == 'named':
if len(args) == 0:
args = [{}]
elif len(args) > 1:
raise ClientSideError(
"Named arguments: takes a single object argument")
args = args[0]
kw = dict(
(argdef.name, fromjson(argdef.datatype, args[argdef.name]))
for argdef in funcdef.arguments if argdef.name in args
)
else:
raise ValueError("Invalid notation: %s" % notation)
return kw
def read_form_arguments(self, context):
kw = {}
for argdef in context.funcdef.arguments:
value = from_params(argdef.datatype, context.request.params,
argdef.name, set())
if value is not Unset:
kw[argdef.name] = value
return kw
def read_arguments(self, context):
if isinstance(context, ExtCallContext):
kwargs = self.read_std_arguments(context)
elif isinstance(context, FormExtCallContext):
kwargs = self.read_form_arguments(context)
wsme.runtime.check_arguments(context.funcdef, (), kwargs)
return kwargs
def encode_result(self, context, result):
return json.dumps({
'type': 'rpc',
'tid': context.tid,
'action': context.action,
'method': context.method,
'result': tojson(context.funcdef.return_type, result)
})
def encode_error(self, context, infos):
return json.dumps({
'type': 'exception',
'tid': context.tid,
'action': context.action,
'method': context.method,
'message': '%(faultcode)s: %(faultstring)s' % infos,
'where': infos['debuginfo']})
def prepare_response_body(self, request, results):
r = ",\n".join(results)
if request.wsme_extdirect_batchcall:
return "[\n%s\n]" % r
else:
return r
def get_response_status(self, request):
return 200
def get_response_contenttype(self, request):
return "text/javascript"
def fullns(self, ns):
return ns and '%s.%s' % (self.namespace, ns) or self.namespace
@expose('/extdirect/api', "text/javascript")
@expose('${api_alias}', "text/javascript")
def api(self):
namespaces = {}
for path, funcdef in self.root.getapi():
if len(path) > 1:
namespace = '.'.join(path[:-2])
action = path[-2]
else:
namespace = ''
action = ''
if namespace not in namespaces:
namespaces[namespace] = {}
if action not in namespaces[namespace]:
namespaces[namespace][action] = []
notation = funcdef.extra_options.get('extdirect_params_notation',
self.default_params_notation)
method = {
'name': funcdef.name}
if funcdef.extra_options.get('extdirect_formhandler', False):
method['formHandler'] = True
method['len'] = 1 if notation == 'named' \
else len(funcdef.arguments)
namespaces[namespace][action].append(method)
webpath = self.root._webpath
if webpath and not webpath.endswith('/'):
webpath += '/'
return APIDefinitionGenerator().render(
namespaces=namespaces,
webpath=webpath,
rootns=self.namespace,
fullns=self.fullns,
)
def encode_sample_value(self, datatype, value, format=False):
r = tojson(datatype, value)
content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0,
sort_keys=format)
return ('javascript', content)

View File

@@ -1,19 +0,0 @@
from wsmeext.extdirect import datastore
class SADataStoreController(datastore.DataStoreController):
__dbsession__ = None
__datatype__ = None
def read(self, query=None, sort=None, page=None, start=None, limit=None):
q = self.__dbsession__.query(self.__datatype__.__saclass__)
total = q.count()
if start is not None and limit is not None:
q = q.slice(start, limit)
return self.__readresulttype__(
data=[
self.__datatype__(o) for o in q
],
success=True,
total=total
)

View File

@@ -1,108 +0,0 @@
from __future__ import absolute_import
import functools
import logging
import sys
import inspect
import wsme
import wsme.api
import wsme.rest.json
import wsme.rest.xml
import wsme.rest.args
from wsme.utils import is_valid_code
import flask
log = logging.getLogger(__name__)
TYPES = {
'application/json': wsme.rest.json,
'application/xml': wsme.rest.xml,
'text/xml': wsme.rest.xml
}
def get_dataformat():
if 'Accept' in flask.request.headers:
for t in TYPES:
if t in flask.request.headers['Accept']:
return TYPES[t]
# Look for the wanted data format in the request.
req_dataformat = getattr(flask.request, 'response_type', None)
if req_dataformat in TYPES:
return TYPES[req_dataformat]
log.info('''Could not determine what format is wanted by the
caller, falling back to json''')
return wsme.rest.json
def signature(*args, **kw):
sig = wsme.signature(*args, **kw)
def decorator(f):
args = inspect.getargspec(f)[0]
ismethod = args and args[0] == 'self'
sig(f)
funcdef = wsme.api.FunctionDefinition.get(f)
funcdef.resolve_types(wsme.types.registry)
@functools.wraps(f)
def wrapper(*args, **kwargs):
if ismethod:
self, args = args[0], args[1:]
args, kwargs = wsme.rest.args.get_args(
funcdef, args, kwargs,
flask.request.args, flask.request.form,
flask.request.data,
flask.request.mimetype
)
if funcdef.pass_request:
kwargs[funcdef.pass_request] = flask.request
dataformat = get_dataformat()
try:
if ismethod:
args = [self] + list(args)
result = f(*args, **kwargs)
# NOTE: Support setting of status_code with default 20
status_code = funcdef.status_code
if isinstance(result, wsme.api.Response):
status_code = result.status_code
result = result.obj
res = flask.make_response(
dataformat.encode_result(
result,
funcdef.return_type
)
)
res.mimetype = dataformat.content_type
res.status_code = status_code
except:
try:
exception_info = sys.exc_info()
orig_exception = exception_info[1]
orig_code = getattr(orig_exception, 'code', None)
data = wsme.api.format_exception(exception_info)
finally:
del exception_info
res = flask.make_response(dataformat.encode_error(None, data))
if data['faultcode'] == 'client':
res.status_code = 400
elif orig_code and is_valid_code(orig_code):
res.status_code = orig_code
else:
res.status_code = 500
return res
wrapper.wsme_func = f
return wrapper
return decorator

View File

@@ -1,142 +0,0 @@
from __future__ import absolute_import
import functools
import inspect
import sys
import wsme
import wsme.rest.args
import wsme.rest.json
import wsme.rest.xml
import pecan
from wsme.utils import is_valid_code
class JSonRenderer(object):
@staticmethod
def __init__(path, extra_vars):
pass
@staticmethod
def render(template_path, namespace):
if 'faultcode' in namespace:
return wsme.rest.json.encode_error(None, namespace)
return wsme.rest.json.encode_result(
namespace['result'],
namespace['datatype']
)
class XMLRenderer(object):
@staticmethod
def __init__(path, extra_vars):
pass
@staticmethod
def render(template_path, namespace):
if 'faultcode' in namespace:
return wsme.rest.xml.encode_error(None, namespace)
return wsme.rest.xml.encode_result(
namespace['result'],
namespace['datatype']
)
pecan.templating._builtin_renderers['wsmejson'] = JSonRenderer
pecan.templating._builtin_renderers['wsmexml'] = XMLRenderer
pecan_json_decorate = pecan.expose(
template='wsmejson:',
content_type='application/json',
generic=False)
pecan_xml_decorate = pecan.expose(
template='wsmexml:',
content_type='application/xml',
generic=False
)
pecan_text_xml_decorate = pecan.expose(
template='wsmexml:',
content_type='text/xml',
generic=False
)
def wsexpose(*args, **kwargs):
sig = wsme.signature(*args, **kwargs)
def decorate(f):
sig(f)
funcdef = wsme.api.FunctionDefinition.get(f)
funcdef.resolve_types(wsme.types.registry)
@functools.wraps(f)
def callfunction(self, *args, **kwargs):
return_type = funcdef.return_type
try:
args, kwargs = wsme.rest.args.get_args(
funcdef, args, kwargs, pecan.request.params, None,
pecan.request.body, pecan.request.content_type
)
if funcdef.pass_request:
kwargs[funcdef.pass_request] = pecan.request
result = f(self, *args, **kwargs)
# NOTE: Support setting of status_code with default 201
pecan.response.status = funcdef.status_code
if isinstance(result, wsme.api.Response):
pecan.response.status = result.status_code
# NOTE(lucasagomes): If the return code is 204
# (No Response) we have to make sure that we are not
# returning anything in the body response and the
# content-length is 0
if result.status_code == 204:
return_type = None
elif not isinstance(result.return_type,
wsme.types.UnsetType):
return_type = result.return_type
result = result.obj
except:
try:
exception_info = sys.exc_info()
orig_exception = exception_info[1]
orig_code = getattr(orig_exception, 'code', None)
data = wsme.api.format_exception(
exception_info,
pecan.conf.get('wsme', {}).get('debug', False)
)
finally:
del exception_info
if orig_code and is_valid_code(orig_code):
pecan.response.status = orig_code
else:
pecan.response.status = 500
return data
if return_type is None:
pecan.request.pecan['content_type'] = None
pecan.response.content_type = None
return ''
return dict(
datatype=return_type,
result=result
)
if 'xml' in funcdef.rest_content_types:
pecan_xml_decorate(callfunction)
pecan_text_xml_decorate(callfunction)
if 'json' in funcdef.rest_content_types:
pecan_json_decorate(callfunction)
pecan.util._cfg(callfunction)['argspec'] = inspect.getargspec(f)
callfunction._wsme_definition = funcdef
return callfunction
return decorate

View File

@@ -1,5 +0,0 @@
from __future__ import absolute_import
from wsmeext.soap.protocol import SoapProtocol
__all__ = ['SoapProtocol']

View File

@@ -1,478 +0,0 @@
"""
A SOAP implementation for wsme.
Parts of the code were taken from the tgwebservices soap implmentation.
"""
from __future__ import absolute_import
import pkg_resources
import datetime
import decimal
import base64
import logging
import six
from wsmeext.soap.simplegeneric import generic
from wsmeext.soap.wsdl import WSDLGenerator
try:
from lxml import etree as ET
use_lxml = True
except ImportError:
from xml.etree import cElementTree as ET # noqa
use_lxml = False
from wsme.protocol import CallContext, Protocol, expose
import wsme.types
import wsme.runtime
from wsme import exc
from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime
log = logging.getLogger(__name__)
xsd_ns = 'http://www.w3.org/2001/XMLSchema'
xsi_ns = 'http://www.w3.org/2001/XMLSchema-instance'
soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/'
if not use_lxml:
ET.register_namespace('soap', soapenv_ns)
type_qn = '{%s}type' % xsi_ns
nil_qn = '{%s}nil' % xsi_ns
Envelope_qn = '{%s}Envelope' % soapenv_ns
Body_qn = '{%s}Body' % soapenv_ns
Fault_qn = '{%s}Fault' % soapenv_ns
faultcode_qn = '{%s}faultcode' % soapenv_ns
faultstring_qn = '{%s}faultstring' % soapenv_ns
detail_qn = '{%s}detail' % soapenv_ns
type_registry = {
wsme.types.bytes: 'xs:string',
wsme.types.text: 'xs:string',
int: 'xs:int',
float: "xs:float",
bool: "xs:boolean",
datetime.datetime: "xs:dateTime",
datetime.date: "xs:date",
datetime.time: "xs:time",
decimal.Decimal: "xs:decimal",
wsme.types.binary: "xs:base64Binary",
}
if not six.PY3:
type_registry[long] = "xs:long"
array_registry = {
wsme.types.text: "String_Array",
wsme.types.bytes: "String_Array",
int: "Int_Array",
float: "Float_Array",
bool: "Boolean_Array",
}
if not six.PY3:
array_registry[long] = "Long_Array"
def soap_array(datatype, ns):
if datatype.item_type in array_registry:
name = array_registry[datatype.item_type]
else:
name = soap_type(datatype.item_type, False) + '_Array'
if ns:
name = 'types:' + name
return name
def soap_type(datatype, ns):
name = None
if wsme.types.isarray(datatype):
return soap_array(datatype, ns)
if wsme.types.isdict(datatype):
return None
if datatype in type_registry:
stype = type_registry[datatype]
if not ns:
stype = stype[3:]
return stype
if wsme.types.iscomplex(datatype):
name = datatype.__name__
if name and ns:
name = 'types:' + name
return name
if wsme.types.isusertype(datatype):
return soap_type(datatype.basetype, ns)
def soap_fname(path, funcdef):
return "".join([path[0]] + [i.capitalize() for i in path[1:]])
class SoapEncoder(object):
def __init__(self, types_ns):
self.types_ns = types_ns
def make_soap_element(self, datatype, tag, value, xsitype=None):
el = ET.Element(tag)
if value is None:
el.set(nil_qn, 'true')
elif xsitype is not None:
el.set(type_qn, xsitype)
el.text = value
elif wsme.types.isusertype(datatype):
return self.tosoap(datatype.basetype, tag,
datatype.tobasetype(value))
elif wsme.types.iscomplex(datatype):
el.set(type_qn, 'types:%s' % (datatype.__name__))
for attrdef in wsme.types.list_attributes(datatype):
attrvalue = getattr(value, attrdef.key)
if attrvalue is not wsme.types.Unset:
el.append(self.tosoap(
attrdef.datatype,
'{%s}%s' % (self.types_ns, attrdef.name),
attrvalue
))
else:
el.set(type_qn, type_registry.get(datatype))
if not isinstance(value, wsme.types.text):
value = wsme.types.text(value)
el.text = value
return el
@generic
def tosoap(self, datatype, tag, value):
"""Converts a value into xml Element objects for inclusion in the SOAP
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] = 'xs:MySpecialType'
@tosoap.when_object(MySpecialType)
def myspecialtype_tosoap(datatype, tag, value):
return make_soap_element(datatype, tag, str(value))
"""
return self.make_soap_element(datatype, tag, value)
@tosoap.when_type(wsme.types.ArrayType)
def array_tosoap(self, datatype, tag, value):
el = ET.Element(tag)
el.set(type_qn, soap_array(datatype, self.types_ns))
if value is None:
el.set(nil_qn, 'true')
elif len(value) == 0:
el.append(ET.Element('item'))
else:
for item in value:
el.append(self.tosoap(datatype.item_type, 'item', item))
return el
@tosoap.when_object(bool)
def bool_tosoap(self, datatype, tag, value):
return self.make_soap_element(
datatype,
tag,
'true' if value is True else 'false' if value is False else None
)
@tosoap.when_object(wsme.types.bytes)
def bytes_tosoap(self, datatype, tag, value):
log.debug('(bytes_tosoap, %s, %s, %s, %s)', datatype,
tag, value, type(value))
if isinstance(value, wsme.types.bytes):
value = value.decode('ascii')
return self.make_soap_element(datatype, tag, value)
@tosoap.when_object(datetime.datetime)
def datetime_tosoap(self, datatype, tag, value):
return self.make_soap_element(
datatype,
tag,
value is not None and value.isoformat() or None
)
@tosoap.when_object(wsme.types.binary)
def binary_tosoap(self, datatype, tag, value):
log.debug("(%s, %s, %s)", datatype, tag, value)
value = base64.encodestring(value) if value is not None else None
if six.PY3:
value = value.decode('ascii')
return self.make_soap_element(
datatype.basetype, tag, value, 'xs:base64Binary'
)
@tosoap.when_object(None)
def None_tosoap(self, datatype, tag, value):
return self.make_soap_element(datatype, tag, None)
@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
if datatype in type_registry:
value = datatype(el.text)
elif wsme.types.isusertype(datatype):
value = datatype.frombasetype(
fromsoap(datatype.basetype, el, ns))
else:
value = datatype()
for attr in wsme.types.list_attributes(datatype):
child = el.find('{%s}%s' % (ns['type'], attr.name))
if child is not None:
setattr(value, attr.key, fromsoap(attr.datatype, child, ns))
return value
@fromsoap.when_type(wsme.types.ArrayType)
def array_fromsoap(datatype, el, ns):
if len(el) == 1:
if datatype.item_type \
not in wsme.types.pod_types + wsme.types.dt_types \
and len(el[0]) == 0:
return []
return [fromsoap(datatype.item_type, child, ns) for child in el]
@fromsoap.when_object(wsme.types.bytes)
def bytes_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:string'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return el.text.encode('ascii') if el.text else six.b('')
@fromsoap.when_object(wsme.types.text)
def text_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:string'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return datatype(el.text if el.text else '')
@fromsoap.when_object(bool)
def bool_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:boolean'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return el.text.lower() != 'false'
@fromsoap.when_object(datetime.date)
def date_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:date'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return parse_isodate(el.text)
@fromsoap.when_object(datetime.time)
def time_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:time'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return parse_isotime(el.text)
@fromsoap.when_object(datetime.datetime)
def datetime_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:dateTime'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return parse_isodatetime(el.text)
@fromsoap.when_object(wsme.types.binary)
def binary_fromsoap(datatype, el, ns):
if el.get(nil_qn) == 'true':
return None
if el.get(type_qn) not in (None, 'xs:base64Binary'):
raise exc.InvalidInput(el.tag, ET.tostring(el))
return base64.decodestring(el.text.encode('ascii'))
class SoapProtocol(Protocol):
"""
SOAP protocol.
.. autoattribute:: name
.. autoattribute:: content_types
"""
name = 'soap'
displayname = 'SOAP'
content_types = ['application/soap+xml']
ns = {
"soap": "http://www.w3.org/2001/12/soap-envelope",
"soapenv": "http://schemas.xmlsoap.org/soap/envelope/",
"soapenc": "http://schemas.xmlsoap.org/soap/encoding/",
}
def __init__(self, tns=None, typenamespace=None, baseURL=None,
servicename='MyApp'):
self.tns = tns
self.typenamespace = typenamespace
self.servicename = servicename
self.baseURL = baseURL
self._name_mapping = {}
self.encoder = SoapEncoder(typenamespace)
def get_name_mapping(self, service=None):
if service not in self._name_mapping:
self._name_mapping[service] = dict(
(soap_fname(path, f), path)
for path, f in self.root.getapi()
if service is None or (path and path[0] == service)
)
return self._name_mapping[service]
def accept(self, req):
for ct in self.content_types:
if req.headers['Content-Type'].startswith(ct):
return True
if req.headers.get("Soapaction"):
return True
return False
def iter_calls(self, request):
yield CallContext(request)
def extract_path(self, context):
request = context.request
el = ET.fromstring(request.body)
body = el.find('{%(soapenv)s}Body' % self.ns)
# Extract the service name from the tns
message = list(body)[0]
fname = message.tag
if fname.startswith('{%s}' % self.typenamespace):
fname = fname[len(self.typenamespace) + 2:]
mapping = self.get_name_mapping()
if fname not in mapping:
raise exc.UnknownFunction(fname)
path = mapping[fname]
context.soap_message = message
return path
return None
def read_arguments(self, context):
kw = {}
if not hasattr(context, 'soap_message'):
return kw
msg = context.soap_message
for param in msg:
# FIX for python2.6 (only for lxml)
if use_lxml and isinstance(param, ET._Comment):
continue
name = param.tag[len(self.typenamespace) + 2:]
arg = context.funcdef.get_arg(name)
value = fromsoap(arg.datatype, param, {
'type': self.typenamespace,
})
kw[name] = value
wsme.runtime.check_arguments(context.funcdef, (), kw)
return kw
def soap_response(self, path, funcdef, result):
r = ET.Element('{%s}%sResponse' % (
self.typenamespace, soap_fname(path, funcdef)
))
log.debug('(soap_response, %s, %s)', funcdef.return_type, result)
r.append(self.encoder.tosoap(
funcdef.return_type, '{%s}result' % self.typenamespace, result
))
return r
def encode_result(self, context, result):
log.debug('(encode_result, %s)', result)
if use_lxml:
envelope = ET.Element(
Envelope_qn,
nsmap={'xs': xsd_ns, 'types': self.typenamespace}
)
else:
envelope = ET.Element(Envelope_qn, {
'xmlns:xs': xsd_ns,
'xmlns:types': self.typenamespace
})
body = ET.SubElement(envelope, Body_qn)
body.append(self.soap_response(context.path, context.funcdef, result))
s = ET.tostring(envelope)
return s
def get_template(self, name):
return pkg_resources.resource_string(
__name__, '%s.html' % name)
def encode_error(self, context, infos):
envelope = ET.Element(Envelope_qn)
body = ET.SubElement(envelope, Body_qn)
fault = ET.SubElement(body, Fault_qn)
ET.SubElement(fault, faultcode_qn).text = infos['faultcode']
ET.SubElement(fault, faultstring_qn).text = infos['faultstring']
if 'debuginfo' in infos:
ET.SubElement(fault, detail_qn).text = infos['debuginfo']
s = ET.tostring(envelope)
return s
@expose('/api.wsdl', 'text/xml')
def api_wsdl(self, service=None):
if service is None:
servicename = self.servicename
else:
servicename = self.servicename + service.capitalize()
return WSDLGenerator(
tns=self.tns,
types_ns=self.typenamespace,
soapenc=self.ns['soapenc'],
service_name=servicename,
complex_types=self.root.__registry__.complex_types,
funclist=self.root.getapi(),
arrays=self.root.__registry__.array_types,
baseURL=self.baseURL,
soap_array=soap_array,
soap_type=soap_type,
soap_fname=soap_fname,
).generate(True)
def encode_sample_value(self, datatype, value, format=False):
r = self.encoder.make_soap_element(datatype, 'value', value)
if format:
xml_indent(r)
return ('xml', unicode(r))
def xml_indent(elem, level=0):
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
for e in elem:
xml_indent(e, level + 1)
if not e.tail or not e.tail.strip():
e.tail = i
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i

View File

@@ -1,107 +0,0 @@
import inspect
__all__ = ["generic"]
try:
from types import ClassType, InstanceType
classtypes = type, ClassType
except ImportError:
classtypes = type
InstanceType = None
def generic(func, argpos=None):
"""Create a simple generic function"""
if argpos is None:
if hasattr(func, 'argpos'):
argpos = func.argpos
else:
argnames = inspect.getargspec(func)[0]
if argnames and argnames[0] == 'self':
argpos = 1
else:
argpos = 0
_sentinel = object()
def _by_class(*args, **kw):
cls = args[argpos].__class__
for t in type(cls.__name__, (cls, object), {}).__mro__:
f = _gbt(t, _sentinel)
if f is not _sentinel:
return f(*args, **kw)
else:
return func(*args, **kw)
_by_type = {object: func, InstanceType: _by_class}
_gbt = _by_type.get
def when_type(*types):
"""Decorator to add a method that will be called for the given types"""
for t in types:
if not isinstance(t, classtypes):
raise TypeError(
"%r is not a type or class" % (t,)
)
def decorate(f):
for t in types:
if _by_type.setdefault(t, f) is not f:
raise TypeError(
"%r already has method for type %r" % (func, t)
)
return f
return decorate
_by_object = {}
_gbo = _by_object.get
def when_object(*obs):
"""Decorator to add a method to be called for the given object(s)"""
def decorate(f):
for o in obs:
if _by_object.setdefault(id(o), (o, f))[1] is not f:
raise TypeError(
"%r already has method for object %r" % (func, o)
)
return f
return decorate
def dispatch(*args, **kw):
f = _gbo(id(args[argpos]), _sentinel)
if f is _sentinel:
for t in type(args[argpos]).__mro__:
f = _gbt(t, _sentinel)
if f is not _sentinel:
return f(*args, **kw)
else:
return func(*args, **kw)
else:
return f[1](*args, **kw)
dispatch.__name__ = func.__name__
dispatch.__dict__ = func.__dict__.copy()
dispatch.__doc__ = func.__doc__
dispatch.__module__ = func.__module__
dispatch.when_type = when_type
dispatch.when_object = when_object
dispatch.default = func
dispatch.has_object = lambda o: id(o) in _by_object
dispatch.has_type = lambda t: t in _by_type
dispatch.argpos = argpos
return dispatch
def test_suite():
import doctest
return doctest.DocFileSuite(
'README.txt',
optionflags=doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE,
)
if __name__ == '__main__':
import unittest
r = unittest.TextTestRunner()
r.run(test_suite())

View File

@@ -1,297 +0,0 @@
import six
import wsme.types
try:
from lxml import etree as ET
use_lxml = True
except ImportError:
from xml.etree import cElementTree as ET # noqa
use_lxml = False
def xml_tostring(el, pretty_print=False):
if use_lxml:
return ET.tostring(el, pretty_print=pretty_print)
return ET.tostring(el)
class NS(object):
def __init__(self, url):
self.url = url
def __call__(self, name):
return self.qn(name)
def __str__(self):
return self.url
def qn(self, name):
return '{%s}%s' % (self.url, name)
wsdl_ns = NS("http://schemas.xmlsoap.org/wsdl/")
soap_ns = NS("http://schemas.xmlsoap.org/wsdl/soap/")
xs_ns = NS("http://www.w3.org/2001/XMLSchema")
soapenc_ns = NS("http://schemas.xmlsoap.org/soap/encoding/")
class WSDLGenerator(object):
def __init__(
self,
tns,
types_ns,
soapenc,
service_name,
complex_types,
funclist,
arrays,
baseURL,
soap_array,
soap_type,
soap_fname):
self.tns = NS(tns)
self.types_ns = NS(types_ns)
self.soapenc = soapenc
self.service_name = service_name
self.complex_types = complex_types
self.funclist = funclist
self.arrays = arrays
self.baseURL = baseURL or ''
self.soap_array = soap_array
self.soap_fname = soap_fname
self.soap_type = soap_type
def gen_complex_type(self, cls):
complexType = ET.Element(xs_ns('complexType'))
complexType.set('name', cls.__name__)
sequence = ET.SubElement(complexType, xs_ns('sequence'))
for attrdef in wsme.types.list_attributes(cls):
soap_type = self.soap_type(attrdef.datatype, str(self.types_ns))
if soap_type is None:
continue
element = ET.SubElement(sequence, xs_ns('element'))
element.set('name', attrdef.name)
element.set('type', soap_type)
element.set('minOccurs', '1' if attrdef.mandatory else '0')
element.set('maxOccurs', '1')
return complexType
def gen_array(self, array):
complexType = ET.Element(xs_ns('complexType'))
complexType.set('name', self.soap_array(array, False))
ET.SubElement(
ET.SubElement(complexType, xs_ns('sequence')),
xs_ns('element'),
name='item',
maxOccurs='unbounded',
nillable='true',
type=self.soap_type(array.item_type, self.types_ns)
)
return complexType
def gen_function_types(self, path, funcdef):
args_el = ET.Element(
xs_ns('element'),
name=self.soap_fname(path, funcdef)
)
sequence = ET.SubElement(
ET.SubElement(args_el, xs_ns('complexType')),
xs_ns('sequence')
)
for farg in funcdef.arguments:
t = self.soap_type(farg.datatype, True)
if t is None:
continue
element = ET.SubElement(
sequence, xs_ns('element'),
name=farg.name,
type=self.soap_type(farg.datatype, True)
)
if not farg.mandatory:
element.set('minOccurs', '0')
response_el = ET.Element(
xs_ns('element'),
name=self.soap_fname(path, funcdef) + 'Response'
)
element = ET.SubElement(
ET.SubElement(
ET.SubElement(
response_el,
xs_ns('complexType')
),
xs_ns('sequence')
),
xs_ns('element'),
name='result'
)
return_soap_type = self.soap_type(funcdef.return_type, True)
if return_soap_type is not None:
element.set('type', return_soap_type)
return args_el, response_el
def gen_types(self):
types = ET.Element(wsdl_ns('types'))
schema = ET.SubElement(types, xs_ns('schema'))
schema.set('elementFormDefault', 'qualified')
schema.set('targetNamespace', str(self.types_ns))
for cls in self.complex_types:
schema.append(self.gen_complex_type(cls))
for array in self.arrays:
schema.append(self.gen_array(array))
for path, funcdef in self.funclist:
schema.extend(self.gen_function_types(path, funcdef))
return types
def gen_functions(self):
messages = []
binding = ET.Element(
wsdl_ns('binding'),
name='%s_Binding' % self.service_name,
type='tns:%s_PortType' % self.service_name
)
ET.SubElement(
binding,
soap_ns('binding'),
style='document',
transport='http://schemas.xmlsoap.org/soap/http'
)
portType = ET.Element(
wsdl_ns('portType'),
name='%s_PortType' % self.service_name
)
for path, funcdef in self.funclist:
soap_fname = self.soap_fname(path, funcdef)
# message
req_message = ET.Element(
wsdl_ns('message'),
name=soap_fname + 'Request',
xmlns=str(self.types_ns)
)
ET.SubElement(
req_message,
wsdl_ns('part'),
name='parameters',
element='types:%s' % soap_fname
)
messages.append(req_message)
res_message = ET.Element(
wsdl_ns('message'),
name=soap_fname + 'Response',
xmlns=str(self.types_ns)
)
ET.SubElement(
res_message,
wsdl_ns('part'),
name='parameters',
element='types:%sResponse' % soap_fname
)
messages.append(res_message)
# portType/operation
operation = ET.SubElement(
portType,
wsdl_ns('operation'),
name=soap_fname
)
if funcdef.doc:
ET.SubElement(
operation,
wsdl_ns('documentation')
).text = funcdef.doc
ET.SubElement(
operation, wsdl_ns('input'),
message='tns:%sRequest' % soap_fname
)
ET.SubElement(
operation, wsdl_ns('output'),
message='tns:%sResponse' % soap_fname
)
# binding/operation
operation = ET.SubElement(
binding,
wsdl_ns('operation'),
name=soap_fname
)
ET.SubElement(
operation,
soap_ns('operation'),
soapAction=soap_fname
)
ET.SubElement(
ET.SubElement(
operation,
wsdl_ns('input')
),
soap_ns('body'),
use='literal'
)
ET.SubElement(
ET.SubElement(
operation,
wsdl_ns('output')
),
soap_ns('body'),
use='literal'
)
return messages + [portType, binding]
def gen_service(self):
service = ET.Element(wsdl_ns('service'), name=self.service_name)
ET.SubElement(
service,
wsdl_ns('documentation')
).text = six.u('WSDL File for %s') % self.service_name
ET.SubElement(
ET.SubElement(
service,
wsdl_ns('port'),
binding='tns:%s_Binding' % self.service_name,
name='%s_PortType' % self.service_name
),
soap_ns('address'),
location=self.baseURL
)
return service
def gen_definitions(self):
attrib = {
'name': self.service_name,
'targetNamespace': str(self.tns)
}
if use_lxml:
definitions = ET.Element(
wsdl_ns('definitions'),
attrib=attrib,
nsmap={
'xs': str(xs_ns),
'soap': str(soap_ns),
'types': str(self.types_ns),
'tns': str(self.tns)
}
)
else:
definitions = ET.Element(wsdl_ns('definitions'), **attrib)
definitions.set('xmlns:types', str(self.types_ns))
definitions.set('xmlns:tns', str(self.tns))
definitions.set('name', self.service_name)
definitions.append(self.gen_types())
definitions.extend(self.gen_functions())
definitions.append(self.gen_service())
return definitions
def generate(self, format=False):
return xml_tostring(self.gen_definitions(), pretty_print=format)

View File

@@ -1,591 +0,0 @@
import inspect
import re
import sys
import six
from sphinx import addnodes
from sphinx.ext import autodoc
from sphinx.domains.python import PyClasslike, PyClassmember
from sphinx.domains import Domain, ObjType
from sphinx.directives import ObjectDescription
from sphinx.util.docfields import Field
from sphinx.util.nodes import make_refnode
from sphinx.roles import XRefRole
from sphinx.locale import l_, _
from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives
import wsme
import wsme.types
import wsme.rest.json
import wsme.rest.xml
field_re = re.compile(r':(?P<field>\w+)(\s+(?P<name>\w+))?:')
def datatypename(datatype):
if isinstance(datatype, wsme.types.UserType):
return datatype.name
if isinstance(datatype, wsme.types.DictType):
return 'dict(%s: %s)' % (datatypename(datatype.key_type),
datatypename(datatype.value_type))
if isinstance(datatype, wsme.types.ArrayType):
return 'list(%s)' % datatypename(datatype.item_type)
return datatype.__name__
def make_sample_object(datatype):
if datatype is wsme.types.bytes:
return six.b('samplestring')
if datatype is wsme.types.text:
return u'sample unicode'
if datatype is int:
return 5
sample_obj = getattr(datatype, 'sample', datatype)()
return sample_obj
def get_protocols(names):
names = list(names)
protocols = []
if 'rest' in names:
names.remove('rest')
protocols.extend('restjson', 'restxml')
if 'restjson' in names:
names.remove('restjson')
protocols.append(('Json', wsme.rest.json))
if 'restxml' in names:
names.remove('restxml')
protocols.append(('XML', wsme.rest.xml))
for name in names:
p = wsme.protocol.getprotocol(name)
protocols.append((p.displayname or p.name, p))
return protocols
class SampleType(object):
"""A Sample Type"""
#: A Int
aint = int
def __init__(self, aint=None):
if aint:
self.aint = aint
@classmethod
def sample(cls):
return cls(10)
class SampleService(wsme.WSRoot):
@wsme.expose(SampleType)
@wsme.validate(SampleType, int, str)
def change_aint(data, aint, dummy='useless'):
"""
:param aint: The new value
:return: The data object with its aint field value changed.
"""
data.aint = aint
return data
def getroot(env, force=False):
root = env.temp_data.get('wsme:root')
if not force and root:
return root
rootpath = env.temp_data.get('wsme:rootpath', env.app.config.wsme_root)
if rootpath is None:
return None
modname, classname = rootpath.rsplit('.', 1)
__import__(modname)
module = sys.modules[modname]
root = getattr(module, classname)
env.temp_data['wsme:root'] = root
return root
def scan_services(service, path=[]):
has_functions = False
for name in dir(service):
if name.startswith('_'):
continue
a = getattr(service, name)
if inspect.ismethod(a):
if hasattr(a, '_wsme_definition'):
has_functions = True
if inspect.isclass(a):
continue
if len(path) > wsme.rest.APIPATH_MAXLEN:
raise ValueError("Path is too long: " + str(path))
for value in scan_services(a, path + [name]):
yield value
if has_functions:
yield service, path
def find_service_path(env, service):
root = getroot(env)
if service == root:
return []
for s, path in scan_services(root):
if s == service:
return path
return None
class TypeDirective(PyClasslike):
def get_index_text(self, modname, name_cls):
return _('%s (webservice type)') % name_cls[0]
def add_target_and_index(self, name_cls, sig, signode):
ret = super(TypeDirective, self).add_target_and_index(
name_cls, sig, signode
)
name = name_cls[0]
types = self.env.domaindata['wsme']['types']
if name in types:
self.state_machine.reporter.warning(
'duplicate type description of %s ' % name)
types[name] = self.env.docname
return ret
class AttributeDirective(PyClassmember):
doc_field_types = [
Field('datatype', label=l_('Type'), has_arg=False,
names=('type', 'datatype'))
]
def check_samples_slot(value):
"""Validate the samples_slot option to the TypeDocumenter.
Valid positions are 'before-docstring' and
'after-docstring'. Using the explicit 'none' disables sample
output. The default is after-docstring.
"""
if not value:
return 'after-docstring'
val = directives.choice(
value,
('none', # do not include
'before-docstring', # show samples then docstring
'after-docstring', # show docstring then samples
))
return val
class TypeDocumenter(autodoc.ClassDocumenter):
objtype = 'type'
directivetype = 'type'
domain = 'wsme'
required_arguments = 1
default_samples_slot = 'after-docstring'
option_spec = dict(
autodoc.ClassDocumenter.option_spec,
**{'protocols': lambda l: [v.strip() for v in l.split(',')],
'samples-slot': check_samples_slot,
})
@staticmethod
def can_document_member(member, membername, isattr, parent):
# we don't want to be automaticaly used
# TODO check if the member is registered an an exposed type
return False
def format_name(self):
return self.object.__name__
def format_signature(self):
return u''
def add_directive_header(self, sig):
super(TypeDocumenter, self).add_directive_header(sig)
# remove the :module: option that was added by ClassDocumenter
if ':module:' in self.directive.result[-1]:
self.directive.result.pop()
def import_object(self):
if super(TypeDocumenter, self).import_object():
wsme.types.register_type(self.object)
return True
else:
return False
def add_content(self, more_content, no_docstring=False):
# Check where to include the samples
samples_slot = self.options.samples_slot or self.default_samples_slot
def add_docstring():
super(TypeDocumenter, self).add_content(
more_content, no_docstring)
def add_samples():
protocols = get_protocols(
self.options.protocols or self.env.app.config.wsme_protocols
)
content = []
if protocols:
sample_obj = make_sample_object(self.object)
content.extend([
l_(u'Data samples:'),
u'',
u'.. cssclass:: toggle',
u''
])
for name, protocol in protocols:
language, sample = protocol.encode_sample_value(
self.object, sample_obj, format=True)
content.extend([
name,
u' .. code-block:: ' + language,
u'',
])
content.extend(
u' ' * 8 + line
for line in six.text_type(sample).split('\n'))
for line in content:
self.add_line(line, u'<wsmeext.sphinxext')
self.add_line(u'', '<wsmeext.sphinxext>')
if samples_slot == 'after-docstring':
add_docstring()
add_samples()
elif samples_slot == 'before-docstring':
add_samples()
add_docstring()
else:
add_docstring()
class AttributeDocumenter(autodoc.AttributeDocumenter):
datatype = None
domain = 'wsme'
@staticmethod
def can_document_member(member, membername, isattr, parent):
return isinstance(parent, TypeDocumenter)
def import_object(self):
success = super(AttributeDocumenter, self).import_object()
if success:
self.datatype = self.object.datatype
return success
def add_content(self, more_content, no_docstring=False):
self.add_line(
u':type: %s' % datatypename(self.datatype),
'<wsmeext.sphinxext>'
)
self.add_line(u'', '<wsmeext.sphinxext>')
super(AttributeDocumenter, self).add_content(
more_content, no_docstring)
def add_directive_header(self, sig):
super(AttributeDocumenter, self).add_directive_header(sig)
class RootDirective(Directive):
"""
This directive is to tell what class is the Webservice root
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'webpath': directives.unchanged
}
def run(self):
env = self.state.document.settings.env
rootpath = self.arguments[0].strip()
env.temp_data['wsme:rootpath'] = rootpath
if 'wsme:root' in env.temp_data:
del env.temp_data['wsme:root']
if 'webpath' in self.options:
env.temp_data['wsme:webpath'] = self.options['webpath']
return []
class ServiceDirective(ObjectDescription):
name = 'service'
optional_arguments = 1
def handle_signature(self, sig, signode):
path = sig.split('/')
namespace = '/'.join(path[:-1])
if namespace and not namespace.endswith('/'):
namespace += '/'
servicename = path[-1]
if not namespace and not servicename:
servicename = '/'
signode += addnodes.desc_annotation('service ', 'service ')
if namespace:
signode += addnodes.desc_addname(namespace, namespace)
signode += addnodes.desc_name(servicename, servicename)
return sig
class ServiceDocumenter(autodoc.ClassDocumenter):
domain = 'wsme'
objtype = 'service'
directivetype = 'service'
def add_directive_header(self, sig):
super(ServiceDocumenter, self).add_directive_header(sig)
# remove the :module: option that was added by ClassDocumenter
if ':module:' in self.directive.result[-1]:
self.directive.result.pop()
def format_signature(self):
return u''
def format_name(self):
path = find_service_path(self.env, self.object)
if path is None:
return
return '/' + '/'.join(path)
class FunctionDirective(PyClassmember):
name = 'function'
objtype = 'function'
def get_signature_prefix(self, sig):
return 'function '
def document_function(funcdef, docstrings=None, protocols=['restjson']):
"""A helper function to complete a function documentation with return and
parameter types"""
# If the function doesn't have a docstring, add an empty list
# so the default behaviors below work correctly.
if not docstrings:
docstrings = [[]]
found_params = set()
for si, docstring in enumerate(docstrings):
for i, line in enumerate(docstring):
m = field_re.match(line)
if m and m.group('field') == 'param':
found_params.add(m.group('name'))
next_param_pos = (0, 0)
for arg in funcdef.arguments:
content = [
u':type %s: :wsme:type:`%s`' % (
arg.name, datatypename(arg.datatype))
]
if arg.name not in found_params:
content.insert(0, u':param %s: ' % (arg.name))
pos = next_param_pos
else:
for si, docstring in enumerate(docstrings):
for i, line in enumerate(docstring):
m = field_re.match(line)
if m and m.group('field') == 'param' \
and m.group('name') == arg.name:
pos = (si, i + 1)
break
docstring = docstrings[pos[0]]
docstring[pos[1]:pos[1]] = content
next_param_pos = (pos[0], pos[1] + len(content))
if funcdef.return_type:
content = [
u':rtype: %s' % datatypename(funcdef.return_type)
]
pos = None
for si, docstring in enumerate(docstrings):
for i, line in enumerate(docstring):
m = field_re.match(line)
if m and m.group('field') == 'return':
pos = (si, i + 1)
break
else:
pos = next_param_pos
docstring = docstrings[pos[0]]
docstring[pos[1]:pos[1]] = content
codesamples = []
if protocols:
params = []
for arg in funcdef.arguments:
params.append((
arg.name,
arg.datatype,
make_sample_object(arg.datatype)
))
codesamples.extend([
u':%s:' % l_(u'Parameters samples'),
u' .. cssclass:: toggle',
u''
])
for name, protocol in protocols:
language, sample = protocol.encode_sample_params(
params, format=True)
codesamples.extend([
u' ' * 4 + name,
u' .. code-block:: ' + language,
u'',
])
codesamples.extend((
u' ' * 12 + line
for line in six.text_type(sample).split('\n')
))
if funcdef.return_type:
codesamples.extend([
u':%s:' % l_(u'Return samples'),
u' .. cssclass:: toggle',
u''
])
sample_obj = make_sample_object(funcdef.return_type)
for name, protocol in protocols:
language, sample = protocol.encode_sample_result(
funcdef.return_type, sample_obj, format=True)
codesamples.extend([
u' ' * 4 + name,
u' .. code-block:: ' + language,
u'',
])
codesamples.extend((
u' ' * 12 + line
for line in six.text_type(sample).split('\n')
))
docstrings[0:0] = [codesamples]
return docstrings
class FunctionDocumenter(autodoc.MethodDocumenter):
domain = 'wsme'
directivetype = 'function'
objtype = 'function'
priority = 1
option_spec = {
'path': directives.unchanged,
'method': directives.unchanged
}
@staticmethod
def can_document_member(member, membername, isattr, parent):
return (isinstance(parent, ServiceDocumenter) and
wsme.api.iswsmefunction(member))
def import_object(self):
ret = super(FunctionDocumenter, self).import_object()
self.directivetype = 'function'
self.wsme_fd = wsme.api.FunctionDefinition.get(self.object)
self.retann = datatypename(self.wsme_fd.return_type)
return ret
def format_args(self):
args = [arg.name for arg in self.wsme_fd.arguments]
defaults = [
arg.default
for arg in self.wsme_fd.arguments if not arg.mandatory
]
return inspect.formatargspec(args, defaults=defaults)
def get_doc(self, encoding=None):
"""Inject the type and param fields into the docstrings so that the
user can add its own param fields to document the parameters"""
docstrings = super(FunctionDocumenter, self).get_doc(encoding)
protocols = get_protocols(
self.options.protocols or self.env.app.config.wsme_protocols
)
return document_function(
self.wsme_fd, docstrings, protocols
)
def add_content(self, more_content, no_docstring=False):
super(FunctionDocumenter, self).add_content(more_content, no_docstring)
def format_name(self):
return self.wsme_fd.name
def add_directive_header(self, sig):
super(FunctionDocumenter, self).add_directive_header(sig)
# remove the :module: option that was added by ClassDocumenter
if ':module:' in self.directive.result[-1]:
self.directive.result.pop()
class WSMEDomain(Domain):
name = 'wsme'
label = 'WSME'
object_types = {
'type': ObjType(l_('type'), 'type', 'obj'),
'service': ObjType(l_('service'), 'service', 'obj')
}
directives = {
'type': TypeDirective,
'attribute': AttributeDirective,
'service': ServiceDirective,
'root': RootDirective,
'function': FunctionDirective,
}
roles = {
'type': XRefRole()
}
initial_data = {
'types': {}, # fullname -> docname
}
def clear_doc(self, docname):
keys = list(self.data['types'].keys())
for key in keys:
value = self.data['types'][key]
if value == docname:
del self.data['types'][key]
def resolve_xref(self, env, fromdocname, builder,
type, target, node, contnode):
if target not in self.data['types']:
return None
todocname = self.data['types'][target]
return make_refnode(
builder, fromdocname, todocname, target, contnode, target)
def setup(app):
app.add_domain(WSMEDomain)
app.add_autodocumenter(TypeDocumenter)
app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(ServiceDocumenter)
app.add_autodocumenter(FunctionDocumenter)
app.add_config_value('wsme_root', None, 'env')
app.add_config_value('wsme_webpath', '/', 'env')
app.add_config_value('wsme_protocols', ['restjson', 'restxml'], 'env')
app.add_javascript('toggle.js')
app.add_stylesheet('toggle.css')

View File

@@ -1,97 +0,0 @@
from wsme.rest import expose, validate
import wsme.types
from wsmeext.sqlalchemy.types import SQLAlchemyRegistry
class CRUDControllerMeta(type):
def __init__(cls, name, bases, dct):
if cls.__saclass__ is not None:
if cls.__registry__ is None:
cls.__registry__ = wsme.types.registry
if cls.__wstype__ is None:
cls.__wstype__ = cls.__registry__.resolve_type(
SQLAlchemyRegistry.get(
cls.__registry__).getdatatype(cls.__saclass__))
cls.create = expose(
cls.__wstype__,
method='PUT',
wrap=True
)(cls.create)
cls.create = validate(cls.__wstype__)(cls.create)
cls.read = expose(
cls.__wstype__,
method='GET',
wrap=True
)(cls.read)
cls.read = validate(cls.__wstype__)(cls.read)
cls.update = expose(
cls.__wstype__,
method='POST',
wrap=True
)(cls.update)
cls.update = validate(cls.__wstype__)(cls.update)
cls.delete = expose(
method='DELETE',
wrap=True
)(cls.delete)
cls.delete = validate(cls.__wstype__)(cls.delete)
super(CRUDControllerMeta, cls).__init__(name, bases, dct)
class CRUDControllerBase(object):
__registry__ = None
__saclass__ = None
__wstype__ = None
__dbsession__ = None
def _create_one(self, data):
obj = self.__saclass__()
data.to_instance(obj)
self.__dbsession__.add(obj)
return obj
def _get_one(self, ref):
q = self.__dbsession__.query(self.__saclass__)
q = q.filter(ref.get_ref_criterion())
return q.one()
def _update_one(self, data):
obj = self._get_one(data)
if obj is None:
raise ValueError("No match for data=%s" % data)
data.to_instance(obj)
return obj
def _delete(self, ref):
obj = self._get_one(ref)
self.__dbsession__.delete(obj)
def create(self, data):
obj = self._create_one(data)
self.__dbsession__.flush()
return self.__wstype__(obj)
def read(self, ref):
obj = self._get_one(ref)
return self.__wstype__(obj)
def update(self, data):
obj = self._update_one(data)
self.__dbsession__.flush()
return self.__wstype__(obj)
def delete(self, ref):
self._delete(ref)
self.__dbsession__.flush()
return None
CRUDController = CRUDControllerMeta(
'CRUDController', (CRUDControllerBase,), {}
)

View File

@@ -1,200 +0,0 @@
import datetime
import decimal
import logging
import six
from sqlalchemy.orm import class_mapper
from sqlalchemy.orm.properties import ColumnProperty, RelationProperty
import sqlalchemy.types
import wsme.types
log = logging.getLogger(__name__)
class SQLAlchemyRegistry(object):
@classmethod
def get(cls, registry):
if not hasattr(registry, 'sqlalchemy'):
registry.sqlalchemy = cls()
return registry.sqlalchemy
def __init__(self):
self.types = {}
self.satypeclasses = {
sqlalchemy.types.Integer: int,
sqlalchemy.types.Boolean: bool,
sqlalchemy.types.Float: float,
sqlalchemy.types.Numeric: decimal.Decimal,
sqlalchemy.types.Date: datetime.date,
sqlalchemy.types.Time: datetime.time,
sqlalchemy.types.DateTime: datetime.datetime,
sqlalchemy.types.String: wsme.types.text,
sqlalchemy.types.Unicode: wsme.types.text,
}
def getdatatype(self, sadatatype):
if sadatatype.__class__ in self.satypeclasses:
return self.satypeclasses[sadatatype.__class__]
elif sadatatype in self.types:
return self.types[sadatatype]
else:
return sadatatype.__name__
def register_saclass(registry, saclass, typename=None):
"""Associate a webservice type name to a SQLAlchemy mapped class.
The default typename if the saclass name itself.
"""
if typename is None:
typename = saclass.__name__
SQLAlchemyRegistry.get(registry).types[saclass] = typename
class wsattr(wsme.types.wsattr):
def __init__(self, datatype, saproperty=None, **kw):
super(wsattr, self).__init__(datatype, **kw)
self.saname = saproperty.key
self.saproperty = saproperty
self.isrelation = isinstance(saproperty, RelationProperty)
def make_wsattr(registry, saproperty):
datatype = None
if isinstance(saproperty, ColumnProperty):
if len(saproperty.columns) > 1:
log.warning("Cannot handle multi-column ColumnProperty")
return None
datatype = SQLAlchemyRegistry.get(registry).getdatatype(
saproperty.columns[0].type)
elif isinstance(saproperty, RelationProperty):
other_saclass = saproperty.mapper.class_
datatype = SQLAlchemyRegistry.get(registry).getdatatype(other_saclass)
if saproperty.uselist:
datatype = [datatype]
else:
log.warning("Don't know how to handle %s attributes" %
saproperty.__class__)
if datatype:
return wsattr(datatype, saproperty)
class BaseMeta(wsme.types.BaseMeta):
def __new__(cls, name, bases, dct):
if '__registry__' not in dct:
dct['__registry__'] = wsme.types.registry
return type.__new__(cls, name, bases, dct)
def __init__(cls, name, bases, dct):
saclass = getattr(cls, '__saclass__', None)
if saclass:
mapper = class_mapper(saclass)
cls._pkey_attrs = []
cls._ref_attrs = []
for prop in mapper.iterate_properties:
key = prop.key
if hasattr(cls, key):
continue
if key.startswith('_'):
continue
attr = make_wsattr(cls.__registry__, prop)
if attr is not None:
setattr(cls, key, attr)
if attr and isinstance(prop, ColumnProperty) and \
prop.columns[0] in mapper.primary_key:
cls._pkey_attrs.append(attr)
cls._ref_attrs.append(attr)
register_saclass(cls.__registry__, cls.__saclass__, cls.__name__)
super(BaseMeta, cls).__init__(name, bases, dct)
class Base(six.with_metaclass(BaseMeta, wsme.types.Base)):
def __init__(self, instance=None, keyonly=False, attrs=None, eagerload=[]):
if instance:
self.from_instance(instance, keyonly, attrs, eagerload)
def from_instance(self, instance, keyonly=False, attrs=None, eagerload=[]):
if keyonly:
attrs = self._pkey_attrs + self._ref_attrs
for attr in self._wsme_attributes:
if not isinstance(attr, wsattr):
continue
if attrs and not attr.isrelation and attr.name not in attrs:
continue
if attr.isrelation and attr.name not in eagerload:
continue
value = getattr(instance, attr.saname)
if attr.isrelation:
attr_keyonly = attr.name not in eagerload
attr_attrs = None
attr_eagerload = []
if not attr_keyonly:
attr_attrs = [
aname[len(attr.name) + 1:]
for aname in attrs
if aname.startswith(attr.name + '.')
]
attr_eagerload = [
aname[len(attr.name) + 1:]
for aname in eagerload
if aname.startswith(attr.name + '.')
]
if attr.saproperty.uselist:
value = [
attr.datatype.item_type(
o,
keyonly=attr_keyonly,
attrs=attr_attrs,
eagerload=attr_eagerload
)
for o in value
]
else:
value = attr.datatype(
value,
keyonly=attr_keyonly,
attrs=attr_attrs,
eagerload=attr_eagerload
)
attr.__set__(self, value)
def to_instance(self, instance):
for attr in self._wsme_attributes:
if isinstance(attr, wsattr):
value = attr.__get__(self, self.__class__)
if value is not wsme.types.Unset:
setattr(instance, attr.saname, value)
def get_ref_criterion(self):
"""Returns a criterion that match a database object
having the pkey/ref attribute values of this webservice object"""
criterions = []
for attr in self._pkey_attrs + self._ref_attrs:
value = attr.__get__(self, self.__class__)
if value is not wsme.types.Unset:
criterions.append(attr.saproperty == value)
def generate_types(*classes, **kw):
registry = kw.pop('registry', wsme.types.registry)
prefix = kw.pop('prefix', '')
postfix = kw.pop('postfix', '')
makename = kw.pop('makename', lambda s: prefix + s + postfix)
newtypes = {}
for c in classes:
if isinstance(c, list):
newtypes.update(generate_types(c))
else:
name = makename(c.__name__)
newtypes[name] = BaseMeta(name, (Base, ), {
'__saclass__': c,
'__registry__': registry
})
return newtypes

View File

@@ -1,243 +0,0 @@
import base64
import datetime
import decimal
try:
import simplejson as json
except ImportError:
import json # noqa
import wsme.tests.protocol
from wsme.utils import parse_isodatetime, parse_isodate, parse_isotime
from wsme.types import isarray, isdict, isusertype
import six
if six.PY3:
from urllib.parse import urlencode
else:
from urllib import urlencode # noqa
def encode_arg(value):
if isinstance(value, tuple):
value, datatype = value
else:
datatype = type(value)
if isinstance(datatype, list):
value = [encode_arg((item, datatype[0])) for item in value]
elif isinstance(datatype, dict):
key_type, value_type = list(datatype.items())[0]
value = dict((
(encode_arg((key, key_type)),
encode_arg((value, value_type)))
for key, value in value.items()
))
elif datatype in (datetime.date, datetime.time, datetime.datetime):
value = value.isoformat()
elif datatype == wsme.types.binary:
value = base64.encodestring(value).decode('ascii')
elif datatype == wsme.types.bytes:
value = value.decode('ascii')
elif datatype == decimal.Decimal:
value = str(value)
return value
def decode_result(value, datatype):
if value is None:
return None
if datatype == wsme.types.binary:
value = base64.decodestring(value.encode('ascii'))
return value
if isusertype(datatype):
datatype = datatype.basetype
if isinstance(datatype, list):
value = [decode_result(item, datatype[0]) for item in value]
elif isarray(datatype):
value = [decode_result(item, datatype.item_type) for item in value]
elif isinstance(datatype, dict):
key_type, value_type = list(datatype.items())[0]
value = dict((
(decode_result(key, key_type),
decode_result(value, value_type))
for key, value in value.items()
))
elif isdict(datatype):
key_type, value_type = datatype.key_type, datatype.value_type
value = dict((
(decode_result(key, key_type),
decode_result(value, value_type))
for key, value in value.items()
))
elif datatype == datetime.time:
value = parse_isotime(value)
elif datatype == datetime.date:
value = parse_isodate(value)
elif datatype == datetime.datetime:
value = parse_isodatetime(value)
elif hasattr(datatype, '_wsme_attributes'):
for attr in datatype._wsme_attributes:
if attr.key not in value:
continue
value[attr.key] = decode_result(value[attr.key], attr.datatype)
elif datatype == decimal.Decimal:
value = decimal.Decimal(value)
elif datatype == wsme.types.bytes:
value = value.encode('ascii')
elif datatype is not None and type(value) != datatype:
value = datatype(value)
return value
class TestExtDirectProtocol(wsme.tests.protocol.ProtocolTestCase):
protocol = 'extdirect'
protocol_options = {
'namespace': 'MyNS.api',
'nsfolder': 'app'
}
def call(self, fname, _rt=None, _no_result_decode=False, _accept=None,
**kw):
path = fname.split('/')
try:
func, funcdef, args = self.root._lookup_function(path)
arguments = funcdef.arguments
except:
arguments = []
if len(path) == 1:
ns, action, fname = '', '', path[0]
elif len(path) == 2:
ns, action, fname = '', path[0], path[1]
else:
ns, action, fname = '.'.join(path[:-2]), path[-2], path[-1]
print(kw)
args = [
dict(
(arg.name, encode_arg(kw[arg.name]))
for arg in arguments if arg.name in kw
)
]
print("args =", args)
data = json.dumps({
'type': 'rpc',
'tid': 0,
'action': action,
'method': fname,
'data': args,
})
print(data)
headers = {'Content-Type': 'application/json'}
if _accept:
headers['Accept'] = _accept
res = self.app.post('/extdirect/router/%s' % ns, data, headers=headers,
expect_errors=True)
print(res.body)
if _no_result_decode:
return res
data = json.loads(res.text)
if data['type'] == 'rpc':
r = data['result']
return decode_result(r, _rt)
elif data['type'] == 'exception':
faultcode, faultstring = data['message'].split(': ', 1)
debuginfo = data.get('where')
raise wsme.tests.protocol.CallException(
faultcode, faultstring, debuginfo)
def test_api_alias(self):
assert self.root._get_protocol('extdirect').api_alias == '/app/api.js'
def test_get_api(self):
res = self.app.get('/app/api.js')
print(res.body)
assert res.body
def test_positional(self):
self.root._get_protocol('extdirect').default_params_notation = \
'positional'
data = json.dumps({
'type': 'rpc',
'tid': 0,
'action': 'misc',
'method': 'multiply',
'data': [2, 5],
})
headers = {'Content-Type': 'application/json'}
res = self.app.post('/extdirect/router', data, headers=headers)
print(res.body)
data = json.loads(res.text)
assert data['type'] == 'rpc'
r = data['result']
assert r == 10
def test_batchcall(self):
data = json.dumps([{
'type': 'rpc',
'tid': 1,
'action': 'argtypes',
'method': 'setdate',
'data': [{'value': '2011-04-06'}],
}, {
'type': 'rpc',
'tid': 2,
'action': 'returntypes',
'method': 'getbytes',
'data': []
}])
print(data)
headers = {'Content-Type': 'application/json'}
res = self.app.post('/extdirect/router', data, headers=headers)
print(res.body)
rdata = json.loads(res.text)
assert len(rdata) == 2
assert rdata[0]['tid'] == 1
assert rdata[0]['result'] == '2011-04-06'
assert rdata[1]['tid'] == 2
assert rdata[1]['result'] == 'astring'
def test_form_call(self):
params = {
'value[0].inner.aint': 54,
'value[1].inner.aint': 55,
'extType': 'rpc',
'extTID': 1,
'extAction': 'argtypes',
'extMethod': 'setnestedarray',
}
body = urlencode(params)
r = self.app.post(
'/extdirect/router',
body,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
print(r)
assert json.loads(r.text) == {
"tid": "1",
"action": "argtypes",
"type": "rpc",
"method": "setnestedarray",
"result": [{
"inner": {
"aint": 54
}
}, {
"inner": {
"aint": 55
}
}]
}

View File

@@ -1,423 +0,0 @@
import decimal
import datetime
import base64
import six
import wsme.tests.protocol
try:
import xml.etree.ElementTree as et
except:
import cElementTree as et # noqa
import suds.cache
import suds.client
import suds.transport
import wsme.utils
class XDecimal(suds.xsd.sxbuiltin.XBuiltin):
def translate(self, value, topython=True):
if topython:
if isinstance(value, six.string_types) and len(value):
return decimal.Decimal(value)
else:
if isinstance(value, (decimal.Decimal, int, float)):
return str(value)
return value
suds.xsd.sxbuiltin.Factory.tags['decimal'] = XDecimal
class WebtestSudsTransport(suds.transport.Transport):
def __init__(self, app):
suds.transport.Transport.__init__(self)
self.app = app
def open(self, request):
res = self.app.get(request.url, headers=request.headers)
return six.BytesIO(res.body)
def send(self, request):
res = self.app.post(
request.url,
request.message,
headers=dict((
(key, str(value)) for key, value in request.headers.items()
)),
expect_errors=True
)
return suds.transport.Reply(
res.status_int,
dict(res.headers),
res.body
)
class SudsCache(suds.cache.Cache):
def __init__(self):
self.d = {}
def get(self, id):
return self.d.get(id)
def getf(self, id):
b = self.get(id)
if b is not None:
return six.StringIO(self.get(id))
def put(self, id, bfr):
self.d[id] = bfr
def putf(self, id, fp):
self.put(id, fp.read())
def purge(self, id):
try:
del self.d[id]
except:
pass
def clear(self, id):
self.d = {}
sudscache = SudsCache()
tns = "http://foo.bar.baz/soap/"
typenamespace = "http://foo.bar.baz/types/"
soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/'
xsi_ns = 'http://www.w3.org/2001/XMLSchema-instance'
body_qn = '{%s}Body' % soapenv_ns
fault_qn = '{%s}Fault' % soapenv_ns
faultcode_qn = '{%s}faultcode' % soapenv_ns
faultstring_qn = '{%s}faultstring' % soapenv_ns
faultdetail_qn = '{%s}detail' % soapenv_ns
type_qn = '{%s}type' % xsi_ns
nil_qn = '{%s}nil' % xsi_ns
def build_soap_message(method, params=""):
message = """<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<soap:Body xmlns="%(typenamespace)s">
<%(method)s>
%(params)s
</%(method)s>
</soap:Body>
</soap:Envelope>
""" % dict(method=method, params=params, typenamespace=typenamespace)
return message
python_types = {
int: ('xs:int', str),
float: ('xs:float', str),
bool: ('xs:boolean', str),
wsme.types.bytes: (
'xs:string',
lambda x: x.decode('ascii') if isinstance(x, wsme.types.bytes) else x
),
wsme.types.text: ('xs:string', wsme.types.text),
wsme.types.binary: (
'xs:base64Binary',
lambda x: base64.encodestring(x).decode('ascii')
),
decimal.Decimal: ('xs:decimal', str),
datetime.date: ('xs:date', datetime.date.isoformat),
datetime.time: ('xs:time', datetime.time.isoformat),
datetime.datetime: ('xs:dateTime', datetime.datetime.isoformat),
}
array_types = {
wsme.types.bytes: "String_Array",
wsme.types.text: "String_Array",
int: "Int_Array",
float: "Float_Array",
bool: "Boolean_Array",
datetime.datetime: "dateTime_Array"
}
if not six.PY3:
array_types[long] = "Long_Array"
def tosoap(tag, value):
el = et.Element(tag)
if isinstance(value, tuple):
value, datatype = value
else:
datatype = type(value)
if value is None:
el.set('xsi:nil', 'true')
return el
if datatype in python_types:
stype, conv = python_types[datatype]
el.text = conv(value)
el.set('xsi:type', stype)
el.text = str(value)
return el
def tosuds(client, value):
if value is None:
return None
if isinstance(value, tuple):
value, datatype = value
else:
datatype = type(value)
if value is None:
return None
if isinstance(datatype, list):
if datatype[0] in array_types:
tname = array_types[datatype[0]]
else:
tname = datatype[0].__name__ + '_Array'
o = client.factory.create('types:' + tname)
o.item = [tosuds(client, (item, datatype[0])) for item in value]
return o
elif datatype in python_types:
return python_types[datatype][1](value)
else:
o = client.factory.create('types:' + datatype.__name__)
for attr in datatype._wsme_attributes:
if attr.name in value:
setattr(
o, attr.name,
tosuds(client, (value[attr.name], attr.datatype))
)
return o
def read_bool(value):
return value == 'true'
soap_types = {
'xs:string': wsme.types.text,
'xs:int': int,
'xs:long': int if six.PY3 else long,
'xs:float': float,
'xs:decimal': decimal.Decimal,
'xs:boolean': read_bool,
'xs:date': wsme.utils.parse_isodate,
'xs:time': wsme.utils.parse_isotime,
'xs:dateTime': wsme.utils.parse_isodatetime,
'xs:base64Binary': base64.decodestring,
}
def fromsoap(el):
if el.get(nil_qn) == 'true':
return None
t = el.get(type_qn)
if t == 'xs:string':
return wsme.types.text(el.text if el.text else '')
if t in soap_types:
return soap_types[t](el.text)
elif t and t.endswith('_Array'):
return [fromsoap(i) for i in el]
else:
d = {}
for child in el:
name = child.tag
assert name.startswith('{%s}' % typenamespace), name
name = name[len(typenamespace) + 2:]
d[name] = fromsoap(child)
return d
def tobytes(value):
if isinstance(value, wsme.types.text):
value = value.encode()
return value
def tobin(value):
value = base64.decodestring(value.encode())
return value
fromsuds_types = {
wsme.types.binary: tobin,
wsme.types.bytes: tobytes,
decimal.Decimal: decimal.Decimal,
}
def fromsuds(dt, value):
if value is None:
return None
if isinstance(dt, list):
return [fromsuds(dt[0], item) for item in value.item]
if wsme.types.isarray(dt):
return [fromsuds(dt.item_type, item) for item in value.item]
if wsme.types.isusertype(dt) and dt not in fromsuds_types:
dt = dt.basetype
if dt in fromsuds_types:
print(dt, value)
value = fromsuds_types[dt](value)
print(value)
return value
if wsme.types.iscomplex(dt):
d = {}
for attrdef in dt._wsme_attributes:
if not hasattr(value, attrdef.name):
continue
d[attrdef.name] = fromsuds(
attrdef.datatype, getattr(value, attrdef.name)
)
return d
return value
class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
protocol = 'soap'
protocol_options = dict(tns=tns, typenamespace=typenamespace)
ws_path = '/'
_sudsclient = None
def setUp(self):
wsme.tests.protocol.ProtocolTestCase.setUp(self)
def test_simple_call(self):
message = build_soap_message('touch')
print(message)
res = self.app.post(
self.ws_path,
message,
headers={"Content-Type": "application/soap+xml; charset=utf-8"},
expect_errors=True
)
print(res.body)
assert res.status.startswith('200')
def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False,
**kw):
if _no_result_decode or _accept or self._testMethodName in (
'test_missing_argument', 'test_invalid_path', 'test_settext_empty',
'test_settext_none'
):
return self.raw_call(fpath, _rt, _accept, _no_result_decode, **kw)
path = fpath.strip('/').split('/')
methodname = ''.join([path[0]] + [i.capitalize() for i in path[1:]])
m = getattr(self.sudsclient.service, methodname)
kw = dict((
(key, tosuds(self.sudsclient, value)) for key, value in kw.items()
))
print(kw)
try:
return fromsuds(_rt, m(**kw))
except suds.WebFault as exc:
raise wsme.tests.protocol.CallException(
exc.fault.faultcode,
exc.fault.faultstring,
getattr(exc.fault, 'detail', None) or None
)
def raw_call(self, fpath, _rt=None, _accept=None, _no_result_decode=False,
**kw):
path = fpath.strip('/').split('/')
methodname = ''.join([path[0]] + [i.capitalize() for i in path[1:]])
# get the actual definition so we can build the adequate request
if kw:
el = et.Element('parameters')
for key, value in kw.items():
el.append(tosoap(key, value))
params = six.b("\n").join(et.tostring(el) for el in el)
else:
params = ""
methodname = ''.join([path[0]] + [i.capitalize() for i in path[1:]])
message = build_soap_message(methodname, params)
print(message)
headers = {"Content-Type": "application/soap+xml; charset=utf-8"}
if _accept is not None:
headers['Accept'] = _accept
res = self.app.post(
self.ws_path,
message,
headers=headers,
expect_errors=True
)
print("Status: ", res.status, "Received:", res.body)
if _no_result_decode:
return res
el = et.fromstring(res.body)
body = el.find(body_qn)
print(body)
if res.status_int == 200:
response_tag = '{%s}%sResponse' % (typenamespace, methodname)
r = body.find(response_tag)
result = r.find('{%s}result' % typenamespace)
print("Result element: ", result)
return fromsoap(result)
elif res.status_int == 400:
fault = body.find(fault_qn)
raise wsme.tests.protocol.CallException(
fault.find(faultcode_qn).text,
fault.find(faultstring_qn).text,
"")
elif res.status_int == 500:
fault = body.find(fault_qn)
raise wsme.tests.protocol.CallException(
fault.find(faultcode_qn).text,
fault.find(faultstring_qn).text,
fault.find(faultdetail_qn) is not None and
fault.find(faultdetail_qn).text or None)
@property
def sudsclient(self):
if self._sudsclient is None:
self._sudsclient = suds.client.Client(
self.ws_path + 'api.wsdl',
transport=WebtestSudsTransport(self.app),
cache=sudscache
)
return self._sudsclient
def test_wsdl(self):
c = self.sudsclient
assert c.wsdl.tns[1] == tns, c.wsdl.tns
sd = c.sd[0]
assert len(sd.ports) == 1
port, methods = sd.ports[0]
self.assertEqual(len(methods), 51)
methods = dict(methods)
assert 'returntypesGettext' in methods
print(methods)
assert methods['argtypesSettime'][0][0] == 'value'
def test_return_nesteddict(self):
pass
def test_setnesteddict(self):
pass
def test_return_objectdictattribute(self):
pass
def test_setnested_nullobj(self):
pass # TODO write a soap adapted version of this test.

Some files were not shown because too many files have changed in this diff Show More