WSME-0.9.2/0000775000567000056710000000000013050557153013442 5ustar jenkinsjenkins00000000000000WSME-0.9.2/setup.cfg0000664000567000056710000000217613050557153015271 0ustar jenkinsjenkins00000000000000[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] universal = 0 [egg_info] tag_build = tag_date = 0 WSME-0.9.2/doc/0000775000567000056710000000000013050557153014207 5ustar jenkinsjenkins00000000000000WSME-0.9.2/doc/changes.rst0000664000567000056710000002737213050557000016353 0ustar jenkinsjenkins00000000000000Changes ======= 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. WSME-0.9.2/doc/index.rst0000664000567000056710000000042413050557000016037 0ustar jenkinsjenkins00000000000000 .. 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` WSME-0.9.2/doc/api.rst0000664000567000056710000000150413050557000015501 0ustar jenkinsjenkins00000000000000API === 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: WSME-0.9.2/doc/functions.rst0000664000567000056710000001310013050557000016733 0ustar jenkinsjenkins00000000000000Functions ========= 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. WSME-0.9.2/doc/make.bat0000664000567000056710000001004413050557000015602 0ustar jenkinsjenkins00000000000000@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 ^` where ^ 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 WSME-0.9.2/doc/conf.py0000664000567000056710000001772213050557000015506 0ustar jenkinsjenkins00000000000000# -*- 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 # " v 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 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') WSME-0.9.2/doc/document.rst0000664000567000056710000001032513050557000016547 0ustar jenkinsjenkins00000000000000Document 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:: 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 ` 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/ WSME-0.9.2/doc/_static/0000775000567000056710000000000013050557153015635 5ustar jenkinsjenkins00000000000000WSME-0.9.2/doc/_static/toggle.css0000664000567000056710000000020713050557000017616 0ustar jenkinsjenkins00000000000000dl.toggle dt { background-color: #eeffcc; border: 1px solid #ac9; display: inline; } dl.toggle dd { display: none; } WSME-0.9.2/doc/_static/toggle.js0000664000567000056710000000027713050557000017451 0ustar jenkinsjenkins00000000000000/*global $,document*/ $(document).ready(function () { "use strict"; $("dl.toggle > dt").click( function (event) { $(this).next().toggle(250); } ); }); WSME-0.9.2/doc/_static/wsme.css0000664000567000056710000000046713050557000017320 0ustar jenkinsjenkins00000000000000@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; } WSME-0.9.2/doc/requirements.txt0000664000567000056710000000005413050557000017461 0ustar jenkinsjenkins00000000000000sphinx cloud_sptheme -r ../requirements.txt WSME-0.9.2/doc/protocols.rst0000664000567000056710000002626213050557000016764 0ustar jenkinsjenkins00000000000000Protocols ========= 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 | | | | | | a string | +---------------+----------------------------------------+ | ``unicode`` | .. code-block:: xml | | | | | | a string | +---------------+----------------------------------------+ | ``int`` | .. code-block:: xml | | | | | | 5 | +---------------+----------------------------------------+ | ``float`` | .. code-block:: xml | | | | | | 3.14 | +---------------+----------------------------------------+ | ``bool`` | .. code-block:: xml | | | | | | true | +---------------+----------------------------------------+ | ``Decimal`` | .. code-block:: xml | | | | | | 5.46 | +---------------+----------------------------------------+ | ``date`` | .. code-block:: xml | | | | | | 2010-04-27 | +---------------+----------------------------------------+ | ``time`` | .. code-block:: xml | | | | | | 12:54:18 | +---------------+----------------------------------------+ | ``datetime`` | .. code-block:: xml | | | | | | 2010-04-27T12:54:18 | +---------------+----------------------------------------+ | Arrays | .. code-block:: xml | | | | | | | | | Dinausaurs | | | Rachel | | | | +---------------+----------------------------------------+ | None | .. code-block:: xml | | | | | | | +---------------+----------------------------------------+ | Complex types | .. code-block:: xml | | | | | | | | | 1 | | | Ross | | | | +---------------+----------------------------------------+ Return ~~~~~~ A xml tree with a top 'result' element. .. code-block:: xml 1 Ross Geller Errors ~~~~~~ A xml tree with a top 'error' element, having 'faultcode', 'faultstring' and 'debuginfo' subelements: .. code-block:: xml Client id is missing 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') WSME-0.9.2/doc/integrate.rst0000664000567000056710000002155313050557000016720 0ustar jenkinsjenkins00000000000000Integrating 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 `_ 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 `_ 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`. WSME-0.9.2/doc/Makefile0000664000567000056710000001122613050557000015640 0ustar jenkinsjenkins00000000000000# 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 ' where 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." WSME-0.9.2/doc/todo.rst0000664000567000056710000000077113050557000015702 0ustar jenkinsjenkins00000000000000TODO ==== 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). WSME-0.9.2/doc/types.rst0000664000567000056710000001610513050557000016077 0ustar jenkinsjenkins00000000000000Types ===== 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 ` -- for transporting binary data as base64 strings. - :class:`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 ` and :class:`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 ` -- This allows you to add more information about the attribute, for example if it is mandatory. - A :class:`wsproperty ` -- A special typed property. Works like standard ``property`` with additional properties like :class:`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 `. :: 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: WSME-0.9.2/doc/gettingstarted.rst0000664000567000056710000000056213050557000017763 0ustar jenkinsjenkins00000000000000Getting 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 WSME-0.9.2/ChangeLog0000664000567000056710000011273413050557153015224 0ustar jenkinsjenkins00000000000000CHANGES ======= 0.9.2 ----- * Remove white space between print () * Fix: Sphinx extension on Python3 0.9.1 ----- * Fix the setup.cfg metadata 0.9.0 ----- * Print exceptions raised from from_param methods * Fix pep8 issues and switch to py35 in tox.ini * [doc] Complete doc requirements * Add the 0.8.0 changes list * wsattr.__set__() catchs TypeError * Fix jenkins failure * Port test_cornice to Python 3 * Change the repositories from stackforge to openstack * Update .gitreview for new namespace 0.8.0 ----- * Return 400, if the query string is not a dict * rest: return 415 when content-type is invalid * json: raise ValueError invalid list or dict * Fixes exception path with the datatype is a Object * Update README formatting for release tools * Set up dependencies for cross-tests 0.7.0 ----- * Add instructions to configure cornice with WSME * Move ipaddr to netaddr * Add pytz as a dependency * Fix wrong reference to status argument in the docs * Added timezone support to parse_isodatetime * Complex types should check unexpected attributes * Replace deprecated assertEquals with assertEqual * Update changes doc * Ensure UserType objects are converted to basetype * Convert built-in types when passed as strings * Enable real testing of python 3.4 * Multiple protocol accept or content-type matching * Raise an InvalidInput if you get a ValueError from JSON data * Return a 400 status code on invalid JSON input * Remove unsupported python versions from setup.cfg * Clean up setup.py and add requirements.txt * Remove tab character from setup.cfg * Add full MIT license * Fix i18n when formatting exception * Converts prints to logging.debug calls * Change client-side error logging to debug * Pecan: Make it possible to use the Response to return a non-default return type * Fixing spelling error on MIME Type Errors and PEP8 * Improve Accept and Content-Type handling * Fix pep8 w503 errors * Correct pep8 errors from imports in weird places * several fixes for SOAP protocol * [doc] Update changes list 0.6.4 ----- * Include tests in the source distribution (so pecan can download and run them) 0.6.3 ----- * Disable universal wheels 0.6.2 ----- * Fix passing Dict/Array based UserType as params * Document next version changes * Allow non-auto-registered complex type * Make the flask adapter working with flask.ext.restful * Avoid Sphinx 1.3x in the tests * Doc: status= -> status_code= * Minor documentation edits * Fix tox configuration * Add support for manually specifying supported content types in @wsmeexpose * Fix broken sphinx tests * fix errors/warnings in tests * Fix validation of IPv{4,6}AddressType * Fix printing object reference on StringType * Use APIPATH_MAXLEN from the right module 0.6.1 ----- * one more update for 0.6.1 change list * Fix error: variable 'kw' referenced before assignment * Fix pep8 errors * Update change list for 0.6.1 release * 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 --- * 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 ----- * Add changes entry for 0.5b6 * json: convert value to string before encoding * Run Flask tests by default * Validate body when using Pecan * Remove MANIFEST.in * Return a ClientSideError if unable to convert data * Add custom error code to ClientSideError * doc: remove useless validate import * Enable and fix Sphinx tests * Handle [] {} for body in rest protocols * types: fix error return when None is in Enum * Add a test environment against pecan's development (master) branch * pecantest: remove useless config.py * Include Pecan tests in default Python environment * Add a test case for wsattr default * Handle mandatory attributes * Remove various usage of sys.exc_info() * Minor code cleanups 0.5b5 ----- * update the b5 release date * Add improved support for HTTP response codes in cornice apps * Remove version number from setup.cfg 0.5b4 ----- * Update tox config to allow packaging jobs to work 0.5b3 ----- * The cornice adapter will not make it in 0.5b3 * Completed Changelog * Add improved support for HTTP response codes in TG 1.x apps * Add improved support for HTTP response codes in flask apps * Require the ordereddict package for py26 * pep8 fixes, including a declaration in tox.ini for running flake8 * Remove py25 and add 26 support to tox * Remove a deprecated flag from tox.ini * Changelog for version 0.5b3 * Fix BaseMeta with six >= 1.4.0 * Fix for returned status code and .gitignore * Add a py33 tox target * Fix attributes sorting based on source code * Sort set in type exception * Switch to pbr * Setup a .gitignore file * Fix a little syntax error * Update README and package metadata to reflect gerrit review process * Add .gitreview file * Merged in sileht/wsme/sileht/unicode-clientsideerror (pull request #16) * Support unicode message in ClientSideError * Fix issue #11 in the pecan adapter * Add a unittest to reproduce issue #11 * Require a python2.5 compatible version of Jinja2 * The Response object can now carry error details. Not sure about this though, it needs refinements * Fix inner null objects in the extdirect protocol * Fix returning objects with object attributes set to 'None' * More --nologcapture to run tests so that suds does not get in the way * use --nologcapture to avoid a nasty failure when suds do some logging * Use assertEquals so we see what is the wrong value if any * Merged in asalkeld/wsme-2 (pull request #15) * Test changing the default status_code in pecan * Add a test to make sure we can use the Response from pecan * Change version to 0.5b3 (may change to 0.5 directly) * pecan: Make it possible to use the Response to return non-default status codes * Added tag 0.5b2 for changeset d3e5eee0b150 0.5b2 ----- * Prepare next release * Install mini-doc now use pip * wsmeext.cornice now handle errors properly * Tests cornice resource * Include missing files * wsmeext.cornice.signature can now decorate resource class methods * Pecan adapter: Debug mode (which returns the exception tracebacks to the client) can be enabled by the pecan application configuration * Explicitely ignore the routines when scanning a class * Fix a problem when a complex type has a 'attr' attribute, due to the DataHolder __slots__ list construction, which leads to a DataHolder having a wsattr * Please flake8 * 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 '_' * Allow a wsme.types.Base child class to override the default 'wsattr' class by having a __wsattrclass__ class attribute * If dateutil is present, dateutil.parser is used to aparse the iso dates * The rest encodings now use the parse_iso[date|time] functions of wsme.utils * Fix ClientSideError constructor * The cornice adapter now handle the url matched parameters * Fix the ClientSideError constructor so that it is propertly displayed in the backtrace * Small documentation improvements * Use a cyrilic unicode sample in the demo instead of japanese so that the pdf build is easier * Add a chapter on the use of HostRequest * Improve the documentation * Add the last change to the changelog * Move the missing argument verification in a wsme.runtime module so that it can be used in all adapters * Move the function documentation code to a separate function * A new HostRequest type can be used to have the host framework request object passed to the exposed functions * Create some (incomplete) tests for the cornice adapter and fix it * Add tests and fix check ordering in flask.py * Backed out changeset d6facd75c051 * Fix typo * Fix typo * Re-iterate status_code code * Merged cdevienne/wsme into default * Document the Flask adapter * Fix array parameters support in the Flask adapter * Fixup support for content types in other places then Headers * Support override of response format via request.dataformat * Add support for passing status_code or getting it from request * Fix the way the TG adapter calls wsme.rest.args.get_args * Fix the way the pecan adapter calls wsme.rest.args.get_args * Various fixes for the flask adapter * Add more tests * Downgrade webtest for tg11 and tg15 tests that are python2.5 based * Readd webtest for python 3 * More python2.5 workaround * Workaround issues with python2.5 environment tests * Fix the serverside error test * flask is now part of the default test suite * Test & fix Server-side errors * Flask simple call now works * Remove ipdb * New flask adapter + test (run "tox -e flask" to test) * update the Changelog for next version + update version number * Fix a bug when the only parameter of a function is a 'body' parameter * Add a test to reproduce the bug reported by Endre Karlson * Add a test for the body= parameter of wsexpose * Re add the dev and build tags on version * Don't use the validate decorator in the first-page example * Remove the WSME-Soap dependency * Add missing modules to the packages list * Made the summary shorter (see issue #6) * Add a google analytics id * Added tag 0.5b1 for changeset 359199eb4e09 0.5b1 ----- * Merging a dead branch (messed up with 'amend') * Merging a dead branch (messed up with 'amend') * Merging a dead branch (messed up with 'amend') * Merging a dead branch (messed up with 'amend') * Merging a dead branch (messed up with 'amend') * Merging a dead branch (messed up with 'amend') * Update the changelog and add a requirements file to give readthedoc a try * amends 9c4e1f9a0c129cbb690bdd0459530c793aa3273b * Document the tg1x and cornice adapters * Document the tg1x and cornice adapters * Start documenting the new integration approach * Rewrite a bit the introduction text * Now use the awesome 'Cloud' sphinx theme * Do not test py25 with std json * Do not test py32 with sqlalchemy 0.5 anymore * Update the dependencies in the documentation * Don't install wsme-sqlalchemy and wsme-extdirect to build the doc anymore * Fix a python3 w/o lxml issue with binary serialization in the soap protocol * Merged in wsme-sqlalchemy * Add missing dependencies * Now use toxgen to produce the tox.ini file * Add missing __init__.py in wsmeext.tests * add tests for file property of File * fix binary type tests for python 3 * add tests for file types and fix a python 3 issue with handling files coming from fieldstorage objects * add tests for weakref conversions for type references * test invalid float values * add tests for binary encoding and decoding * add tests for ArrayType validation * amends a32bd89f8984b13f22a9fe5b66b881c91308e459 * tox -e doc now produce the zipped html documentation too * tox -e doc now produce the zipped html documentation too * Tell about the __body__ parameter in the changelog * Fix the wsmeext.sphinxext module name * Fix the wsmeext.sphinxext module name * Add a 'doc' testenv that builds the documentation * Add a 'doc' testenv that builds the documentation * Complete the changelog for version 0.5b1 * Complete the changelog for version 0.5b1 * Set version to 0.5b1 * Set version to 0.5b1 * Use nosetests --with-coverage instead of coverage run for testenv that tests wsmeext submodules (I have issues with the namespace module) * Simplify __body__ extraction from params * Fix unicode values read from json input on python 2.5 * Don't read the body if content_length is 0-like * Choose float values that have no rounding issue on python 2.5 for the float multiplication test * Python 3 compatibility * Rename the body argument to __body__ in tests + now tests for request body single argument * Now rely on wsme.rest.args to parse the parameters, which avoids a lot of code duplication * Now handle __body__ parameter, which needed to add a mimetype parameter to the different args_from_* functions * amends 1ea5bc68101a7f4075553df49fe58ba0b250316b * Add a test for multiply exposed functions * Add a test for multiply exposed functions * Don't use assertIn as it is supported from python 2.7 only * Use assertEquals(a, b) instead of assert a == b * Moved non-core features to the wsmeext namespace package * Pecan adapter is now at 100% of code coverage * Fix code coverage for pecan tests * Improve code coverage for the TG 1.5 adapter * Code cleaning + better code coverage of the TG1.1 adapter * Show missing lines in coverage reports * Now generate coverage reports for individual testenv * Fix the tg 1.x tests * Ignore all the tests reports * Merged in cmcdaniel/wsme/empty_strings3 (pull request #11: restxml empty string fixes) * Fix Python 3 compatibility * restxml empty string fixes * Merged in cmcdaniel/wsme/json_strictness (pull request #8: rest protocol detection: test Accept header first; use startswith for Content-Type match) * rest protocol detection: test Accept header first; use startswith for Content-Type match * Merged in cmcdaniel/wsme/bool_fromsoap (pull request #7: Handle bool from xml properly; add setbool and getbool unit tests.) * Handle bool from xml properly; add setbool and getbool unit tests * Add a new parameter 'ignore_extra_args' to the @signature decorator (and its frontends @wsexpose) * Fix the TG 1.x adapters, the resquest.params are now needed by the get_args function * Now supports non-indexed arrays of objects as GET parameters * Fix array as input GET parameters in the pecan adapter * Update the changelog * Mention the additional protocols in the intro * Better mentionning of the framework independance * Merged in dhellmann/wsme-sphinx (pull request #5) * Move the check for an empty body up * Add samples_slot option to TypeDocumenter * Fixes for sphinxext * Add sample() method to ArrayType and DictType * Roll back previous change to the root XML tag name for sample data * Copy properties to decorator * Fix error handling tests for pecan * Add a test for client-side errors * Allow adapters to use the format_exception function, and use it in the Pecan adapter. /\!\ the response status is not properly changed by the decorator yet * Allow the method autodocumenter (.. autofunction) to work without a Service parent (ie without a WSRoot). Added the path & method parameters that NOT considered for now * PEP8 * Update datetypename() to work with DictType and ArrayType instances * Merged in dhellmann/wsme-validate (pull request #2) * show the docstring for a type before the formatted sample values * use tag name 'result' for sample data to match data returned by services * tighten up validate_value logic and allow string promotion to integers * ignore all coverage output files * restrict the types that can be promoted to float * ignore emacs temporary files * Fix samples for functions parameters and return types * Fix xml & json samples by autotype * resolve types in pecan.wsexpose. It is a temporary solution * resolve types in pecan.wsexpose. It is a temporary solution * Merged in dhellmann/wsme-validate (pull request #1) * Fix the encode_result call * allow type promotion to float * Use the new encode_result functions of the rest encoding modules * Add unittests for returning array and dict of objects as attributes * Add unittests for returning array and dict of objects as attributes * Fix the __eq__ operator for ArrayType * Fix the __eq__ operator for ArrayType * Remove useless imports * Remove useless imports * Adapter for turbogears 1.1 * Adding a tg 2.1 test case (not enabled for now) * Reorganise the tg1x adapters, and make the tg 1.5 adapter able to handle multiprotocol on 'native' rest * The tg1 adapter can now expose rest functions outside the WSRoot _and_ enable other other protocols. Soap is tested * Change the scan_api interface. It now yield the original function and static args (typically the 'self' attribute). Thanks to that the lookup_function method of WSRoot can access functions outside the WSRoot * Fix the module names * args_from_body now ignore empty bodies * Python <2.7 compat * Python 3 compat * Remove the parse_arg tests (parse_arg does not exist anymore), fix the json nest_result option handling, and fix the encode_sample tests and implementations * Rework the rest implementation. We now have a single protocol that can manupulate different dataformat thanks to the helpers provided by the xml, json and args modules (which will be used by the adapters too). Some corner cases still don't pass the unittest, and some code cleaning is required * Add a test for the pecan adapter * Move around the REST implementation : wsme.protocols.commons -> wsme.rest.args, wsme.protocols.rest -> wsme.rest.protocol, wsme.protocols.restxml/json -> wsme.rest.xml/json, wsme.protocols.__init__ -> wsme.protocol * Start working on a better tg 1.1 integration. Need to rework the rest implementation to make it easier (especially the body parsing) * Don't use wsme.release anymore * Change version to avoid dependencies problems in the CI * Make DummyProtocol inherit from Protocol because it now needs a 'iter_routes' function * wsme.protocols.expose now accept templated paths, and can expose a single function several times * Introduce a new decoratore wsme.protocol.expose, which replaces the clumsy former pexpose * Reorganise the decorators. expose and validate are now in wsme.rest, and ws.api.signature becomes the raw decorator to declare a function signature. Got rid of the 'pexpose' decorator, which will be replaced later by a better way * The pecan adapter is now tested * Make json the default format * Rename WSRoot.scan_api to WSRoot._scan_api to avoid infinite recursion by scan_api * Start working on adding protocols when used as a cornice complement * Cornice extension: Fix function args preparation, and choose the renderer based on the 'Accept' header * Adapter for cornice * Split get_args in several functions to make adapters implementation easier * Add a paramter 'multiple_expose' to the expose decorator * merge * Added tag 0.4 for changeset f06e004ca8e4 0.4 --- * Add a test for one item long text arrays * Get WSME version from the package, not wsme.release * Update the changelog * Fix the sample include line numbers * Remove the b1 tag, 0.4 is about to be released * Move the imports to avoid cross-import problems (all this needs some rework) * Add a default value to FunctionDefinition.body_type * Rest protocols can now take parameters from url + a parameter from the body by adding a parameter body= to expose() * Add an adapter for pecan * Add a helper function for adapters that need to convert a function arguments * Get rid of the function wrapper. The only case it was needed is for exposing a function several times, but this case can be handled differently. I may reintroduce it as an option * Introduce a new decorator: 'sig', which combines expose and validate in a single decorator * Now use six.with_metaclass to create the Base type in a python 2/3 compatible way * spore.getdesc does not take a request anymore but only the host url (it makes the tests simplier) * Add a little demo of a SPORE client to call a function of the demo program * Fix the spore 'base_url' attribute * Fix the request headers log * Test SPORE crud function descriptions * Python 3 compat * Fix the spore test, as some functions were added by restjson * Initial implementation of SPORE description of the api (fetch /api.spore to get it) * Fix test_default_usertype * Test text to bytes auto-conversion * text and bytes attributes now convert values from/to bytes/text when needed (ascii only conversion) * Remove the now useless test_release (the release module was removed) * Still need to specify the requires int the setup.py, as d2to1 does not seem to handle python_version dependant metadata * Now using d2to1, which simplifies a lot the setup.py * Added tag 0.4b1 for changeset 5ad01afed877 0.4b1 ----- * Update the change log (preparing release 0.4b1 * To avoid any array or dict duplication, use set() instead of list() for the registry array_types and dict_types attributes * Fix ArrayType __eq__ operator so that array types are not duplicated in the registry * Add more list corner cases to test the soap behavior with empty arrays * Allow 'None' to be set on an Array attribute * Mention the new wsme.types.Base type in the changelog * Fix the demo so that we can use the soap client again, and changed the function names in the client (the soap function naming scheme just changed) * Fix the array and dict registering. The register() function has to be rethought, as resolve_type is doing more, and the ArrayType and DictType are in the game * Mention the wsme.wsgi replacement by wsme.WSRoot.wsgiapp in the changelog * Python 3 compatibility * Fix the Enum constructor, and document the change * Documents the File type * Rename FileType to File, and make it a complex type instead of a native one * Rest protocols should now accept multipart/form-data posts (see issue #4) * New type: FileType. Supports file inputs in forms + documents and demonstrate it in the demo. Should solve issue #4 * Mention wsme.types.Base * The Base type for complex types now has a constructor that takes attribute values as kwargs * Now use WSRoot.wsgiapp() instead of wsme.wsgi.adapt() * Change the wsgi example to match the new way of obtaining a wsgi app from a WSRoot. Add a bottle integration example * completing the wsgiapp docstring * funcproxy now copy the function name * Remove the wsgi adapter, it is now a function on WSRoot * Fix a problem with protocol specific paths * Rest protocols now make use of the http method to select the function is needed * Use request.content_lenght to check for a request.body existence before accessing it * Little english mistake fix * resolve_type now always returns regular datatypes, never weak refs. Generally speaking weakrefs should remain inside the registry * Registry.lookup does not return weakrefs anymore. resolve_type does it instead * Python 3 compatibility * Adapt tests and fix remaining issues with the list/dict handling changes * Fix various bugs revealed by the soap unit tests * Improve dict and array types handling by introducing DictType and ArrayType. Nested structures should have a better behavior * Fix the list of complex types handling (dicts are not ok yet, and lists still not complet imo) * Test attributes which are lists of complex types * Got lost in metaclass __new__/__init__ choice. Yet another attempt using both (needed by wsme-sqlalchemy) * My last commit was a mistake : it is better to use __new__, so that the class registering is done the later * Use the metaclass __init__ instead of __new__ so that the class inspection occurs later, making it possible to add attributes in a sub-metaclass __new__ of __init__ * Use py2 & py3 compatible metaclass * Attempting a on-demand resolution of datatypes references, so we don't need to call resolve_references anymore. It works with python 2, but not yet python 3 (some weakref issues) * Add a type Registry. It mainly allow to have easy cross-referenced types by setting the attribute datatype to the class name instead of the class instance. It may allow to have separate registry for different apis in the future * Update the demo to reproduce issue #3 * Completing the Changelog * Add a test for unset attributes * Add tests for the sphinx extension, raising the total coverage over 95% * Fix Unset attributes serialisation * Reproduce a bug with unset attributes serialisation to xml * Introduce an adapter for tg 1.5 + unittest. It needs more realistic tests though * Add a b1 version tag * Now test the tg1 adapter * Improve code coverage * Simplifie the tox steps (no need for an initial clean) * Now cover wsme.protocols.__init__ * Now tests wsme.release * restjson is now 100% covered by tests * Add --show-missing to the coverage report * Add wsme.tests to the egg dist * merge * Set version to 0.4, and update the documentation * tox now combine code coverage results * Point to the jenkins general dashboard instead of the wsme job * Make the webob requirements more precise depending on the python version Add Python 3.2 to the classifiers * webob 1.2b4 is not out yet.. * Require the latest WebOb for python 3 * Completed the python 3 port * Python 3 port in progress * Remove debug print statement * Fix the binary type decoding * Clarified the bytes/text types handling. Now all the restjson tests pass on python 2 and 3 * The syntax is now python 3.2 compliant. A lot of tests still fail, I need to rethink how unicode / non-unicode are handled * Remove a debug print statement * Porting restjson to Python 3.2 (in progress) * Python 3.2 port * Python 3.2 port * Python 3.2 compat * Python 3.2 compat * Reduce the default envlist * test_types unit tests now successfully pass under python 3.2 * Added a tox configuration file to ease the python 3 port * Fix the README.rst filename * Correct a sentence * Change a bit the short description to make it more explicit * Add a link to the Changelog in the main description * Update the TODO list * Adding the precise Python versions and implementation classifiers * Add a missing indent * Make the examples more compact * Remove the .. highlight:: directives to remains plain-rst compatible * Attempt a rename of README to README.rst to force bb rendering * Add a small code sample at the very beginning of the documentation * Added tag 0.3 for changeset 603c8586b076 0.3 --- * Preparing the 0.3 release * Update the changelog * The function documenter now add parameters and return value samples * Reached 100% test coverage of wsme.utils, which makes an overall coverage of 98% for the wsme module * wsme.types is now 100% covered by unit tests * Slightly improve wsme.root coverage * Fixed nil date/time decoding from xml * restxml now has a decent code coverage * simplejson and native json dumps formated output behave differently. Adapting the unittest that depends on it * More tests and coverage * Now check for unknown arguments in request params * Enable code coverage by default * Improve wsme.protocols.rest code coverage * 100% test coverage for wsme.protocols.commons * Improve detection of double-exposed functions + added a unittest * Improve documentation on types * Mention toggle.css and toggle.js installation in the documentation * Change version * Update the sentense about Sphinx integration features * handle_signature should return a value * Start implementing xref on types * Remove debug print statements * Implements webpath and namespace detection * The service directive now handle a namespace * Now auto document the function parameters * Document a bit the sphinx extension * The sphinx ext is now able to basicaly autodocument the functions (just retrivieve the docstrings for now * load wsme/release.py in a python 3 friendly way * Now add data samples for the wanted protocols on the data types * Introduce a Protocol base class, and add a method to render a sample data (used by the documentation tool) * Use the agogo theme options instead of overloading the css * Re-put the agogo theme (sphinxdoc was just a test * Start working on the sphinx extension * Add the release dates * Added tag 0.3b2 for changeset d5eab01bf491 0.3b2 ----- * Prepare the next release * TG1 server.webpath mechanics makes it impossible to use a filter on the controller itself. So we put it on the root controller and carrefully change the WSRoot webpath so that everything works properly * BugFix: if no Content-Type header is present, read_arguments would fail * Completing the changelog * Better handling of errors on protocol selection * Don't stop if the body is application/x-www-form-urlencoded encoded and the request has params (for other encoding it would mean both params and a body were provided) * Add a cherrypy filter in the tg1 adapter so that the body is not parsed by cherrypy. This makes Webob happier when reading itself the body * Fix response status code transmission in the TG1 adapter * :class:`wsattr` now takes a 'default' parameter * Fix nested dict/list structures * Change version number * Update the documentation * handle dict and UserType as input from forms * Added tag 0.3b1 for changeset ebe2c6f228ad 0.3b1 ----- * Avoid using a weakref.proxy for CallContext.request because we need to get a real ref to the request at some point in the tests * Test the 'division by zero' message in a smarter way so it adapts itself to the python version * Fix the test_enum test (the error message now contains the attribute name * Rename WSRoot.transaction to WSRoot._transaction to avoid scan_api failure in some cases * Add a per-call transaction management * Remove an empty file * Make the int and long types validation interchangeable * Fix dictionnary values validation * Fix: the dict attributes were not registered correctly * Now supports dictionnaries in addition to arrays * Mention the doc in the changes * Document a bit * Preparing next release * The restjson return values are not nested in the result attribute of an object. The former behavior can be obtained with the nest_result protocol option * Code cleaning (thanks to flake8) * Add a setperson function to test complex function arguments * Better handling of function arguments as params (POST or GET) * Don't output Unset values * Fix the named attributes dump in restxml tests * Fix the test_setnamedattrsobj test * Code cleaning * Implement and test the named attributes in the rest protocols * Add a 'name' attribute on wsattr and wsproperty * Unset now evaluate as False when converted to bool * Add the ShinningPanda jenkins URL * Fix user types, str and None values encoding/decoding * Fix registering of class inheriting from an already registerd class * Reproduce a bug when a Child complex type is registered after its Parent (it's not actually) * Fix Unset values validation * Add 'Unset' to the wsme module * Fix parse_[date][time] that were using the Invalid exception from formencode * Fix array attributes validation * updated the todo list * Added tag 0.2.0 for changeset cfb5efc624f5 0.2.0 ----- * Time for a 0.2.0 * Documented REST+XML and a bit SOAP * Document REST+Json * More documentation for the next release * Enum now takes values as args instead of a list * binary is now a UserType, which simplify most of the protocols implementation * Now supports user types (non-complex types that are base on native types), the first on being Enum * Documented the ExtDirect protocol * Completed the change list * Add type validation on complex type attributes assignment * pep8 now likes wsme * Split the controller module into api, protocols and root * Improved the complex type handling by using python Descriptors for attributes. They also carry the attribute name, so that the _wsme_attributes is now a list of wsattr/wsproperty instead of a list of (name, wsattr/wsproperty) * Bugfix: a complex type used only in validate and never in expose was not registered * The decorators now wrap the exposed function so that children classes can expose the parent functions with different signatures * Fix DummyProtocol * Fix: Complex types normal properties were not ignored * Completed the change list * Completed the change list. version -> 0.2.0 * Protocols can now implement batch-calls * Add a function to retrieve an attribute definition of a complex type * Fix self reference complex type registration * Add a pyramid integration example * Add a link to the documentation * Move the links to the README file * Move the titles to the README file * Now read the long_description and the doc introduction from the README file * Fix inspection of classes with inheritance * The complex types attribute are now reset to 'Unset' when inspected * Add the authentication question to the todo list * Update to todo list * The ziphtml target now zip the doc in _build instead of _build/html * Added tag 0.1.1 for changeset c17de432c185 0.1.1 ----- * Prepare the 0.1.1 release * Include the soap client example in the documentation * Completed the soap client example now that wsme-soap seems to work * Add a dependency on WSME-Soap * Add misc test functions * Test nested controllers * The function path now contains the function name * Remove the path attribute of FunctionDefinition, since a same FunctionDefinition could appears at different paths in an api tree * Rename list_calls to iter_calls and makes it an iterator * Prepare next release (should be soon) * Fix the little demo * Use addprotocol to that the protocol tests can pass options at instanciation * We now have a CallContext that follows a function execution from the path extraction to the result encoding * Fix the mandatory/default argument properties detection, and make the content-type detection more permissive * array attributes were potentially not registered * Rectify the integration examples * More test coverage + code cleaning * Fix a test * add WSRoot members doc * Update change list * Added tag 0.1.0 for changeset b0019e486c80 0.1.0 ----- * Going 0.1.0 now that at least 1 application is runnging wsme. We are still in alpha state though * Protocols are now stored in a list so that the order is considered when selecting the right protocol for a request * expose now takes extra options that can be used by the protocols * getprotocol now takes the options as named arguments, which make it easier to use from outside * Remove the soap protocol (I am moving it to wsme-soap * Fix doc * WSRoot.addprotocol now takes keywords parameters and transmit them to getprotocol for instanciating the protocol * Update the change list and clean the demo * Added tag 0.1.0a4 for changeset b38c56a2b913 0.1.0a4 ------- * Update the doc and the version number * Protocols are now found through entry points * 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 * Add the todo page to the index * Completing the changes list for 0.1.0a3 * Add a little 'Install' section * Add a ziphtml target for preparing the documentation to be uploaded to pypi * Added tag 0.1.0a3 for changeset 86466da44f44 0.1.0a3 ------- * Add links to the source code and issue tracker * Make the feature-list clearer and add the mailing list address * Update the contact mail and version * Documented most of the api * Improve code coverage of restjson * Improve ClientSideError interface + raise wsme.exc coverage to 100% * wswe.controller code coverage is back to 100% * More code coverage, and fixed a bug with wsme.wsgi.WSRoot.clone * More documentation * Add a WSGI adapter * Added an adapter for TG1 * Added tag 0.1.0a2 for changeset 0eae00db9384 0.1.0a2 ------- * Prepare a new release * Cleaning my test * Add arrays support in soap * Add array support to restxml * Add array support to restjson * Added tag 0.1.0a1 for changeset 2bd203a084dc 0.1.0a1 ------- * Going 0.1.0a1 * Fix README filename * Add a reame file * Completed the packaging, we should now be able to do an alpha release * Documenting * Add a setup.cfg * Completing setup.py * Initial documentation * Move the protocols to a dedicated module, and their activation more explicit * completed the setup informations * Made the demo work with suds * Add a complex type to the demo * Add a small soap client for the demo app, so that we can see the wsdl generation is _not_ working properly... to be continued * Adapt the rest xml tests * Adapted the restjson tests to the changes I made for the soap tests * The soap test case now fully pass. We are getting closer to a working implementation, but I think I messed up the namespaces * Start implementing a 'fromsoap' set of functions * A few more fixes on tests and return values in soap before diving into arguments feeding * Missing arguments are now detected by the controller * Fix a few tests * A few utility functions * Most of the return types now works with soap * Making progress on the soap implementation. Will have to choose between Genshi and ElementTree though, it is getting anoying to juggle with both * Adapt the rest protocol implementation to the changes I did for the soap protocol * Working on the soap protocol * Fix a few dummy errors * Worked on the wsdl generation. It looks like it should although it lacks the basic types handling and arrays definitions * Embedded the wsdl genshi template from tgws + start testing the soap protocol * Start working on the soap protocol * PEP8fying * Move as much as possible the request handling code out of the protocol * One can now test the rest api with a web browser * Share more code between restjson and restxml * Fix some issues with encoding * Add a small demo, and fix a few problems * PEP8 compliance * Most of the types now works as argument and return type in rest+xml and rest+json * PEP8 compliance * Handle binary and decimal return types * Use generic to prepare the json output so that non-structured custom types can be added * rest+xml now handle the basic return types * Add unittests for rest+xml * Continue working on the rest-xml tests and implementation + changed the RestProtocol interface * Start working on the rest-xml tests and implementation * better testing (+ fixes) of sort_attributes * test & fix the forced attribute order feature * pep8 * Structured types basically work with rest+json * Rename AttrDef to wsattr, and introduce wsproperty * Implementing the structured types inspection * Start working on the non-trivial types handling * rename WSRoot.debug to WSRoot._debug to avoid an infinite recursion when scanning the api (which will need a better implementation anyway) * Continuing the rest+json implementation * the rest+json protocol starts to work with basic return types * Lowercased the project name * Start implementing the rest json proto * Completed the register test so we have 100% of test coverage * Renamed EWS to WSME because ews is already taken on pypi * A first working implemetation for the core controller code WSME-0.9.2/tox-tmpl.ini0000664000567000056710000000640613050557000015724 0ustar jenkinsjenkins00000000000000# 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 WSME-0.9.2/wsme/0000775000567000056710000000000013050557153014415 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsme/rest/0000775000567000056710000000000013050557153015372 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsme/rest/json.py0000664000567000056710000002313413050557000016707 0ustar jenkinsjenkins00000000000000"""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) WSME-0.9.2/wsme/rest/xml.py0000664000567000056710000002017513050557000016540 0ustar jenkinsjenkins00000000000000from __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[0-2][0-9]):(?P[0-5][0-9]):(?P[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) WSME-0.9.2/wsme/rest/protocol.py0000664000567000056710000001055513050557000017602 0ustar jenkinsjenkins00000000000000import 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 WSME-0.9.2/wsme/rest/__init__.py0000664000567000056710000000406113050557000017473 0ustar jenkinsjenkins00000000000000import 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 WSME-0.9.2/wsme/rest/args.py0000664000567000056710000002234413050557000016674 0ustar jenkinsjenkins00000000000000import 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[^\.])' % 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\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[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 WSME-0.9.2/wsme/spore.py0000664000567000056710000000336713050557000016117 0ustar jenkinsjenkins00000000000000from 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) WSME-0.9.2/wsme/root.py0000664000567000056710000002736713050557000015760 0ustar jenkinsjenkins00000000000000import 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(""" %(content)s """) 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 `_-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 `_ (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('
%s
') % content.replace(b('>'), b('>')) .replace(b('<'), b('<'))) WSME-0.9.2/wsme/protocol.py0000664000567000056710000001001713050557000016616 0ustar jenkinsjenkins00000000000000import 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 WSME-0.9.2/wsme/api.py0000664000567000056710000001647113050557000015540 0ustar jenkinsjenkins00000000000000import 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 WSME-0.9.2/wsme/utils.py0000664000567000056710000000633413050557000016124 0ustar jenkinsjenkins00000000000000import 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-?\d{4,})-(?P\d{2})-(?P\d{2})' time_re = r'(?P\d{2}):(?P\d{2}):(?P\d{2})' + \ r'(\.(?P\d+))?' tz_re = r'((?P[+-])(?P\d{2}):(?P\d{2}))' + \ r'|(?PZ)' 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 WSME-0.9.2/wsme/__init__.py0000664000567000056710000000037413050557000016521 0ustar jenkinsjenkins00000000000000from 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' ] WSME-0.9.2/wsme/exc.py0000664000567000056710000000541213050557000015537 0ustar jenkinsjenkins00000000000000import 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) WSME-0.9.2/wsme/types.py0000664000567000056710000006113213050557000016125 0ustar jenkinsjenkins00000000000000import 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() ` and :class:`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) WSME-0.9.2/wsme/tests/0000775000567000056710000000000013050557153015557 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsme/tests/test_restjson.py0000664000567000056710000006261313050557000021036 0ustar jenkinsjenkins00000000000000import 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" WSME-0.9.2/wsme/tests/test_root.py0000664000567000056710000000737613050557000020157 0ustar jenkinsjenkins00000000000000# 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" WSME-0.9.2/wsme/tests/test_api.py0000664000567000056710000003142613050557000017736 0ustar jenkinsjenkins00000000000000# 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']) WSME-0.9.2/wsme/tests/test_exc.py0000664000567000056710000000175113050557000017742 0ustar jenkinsjenkins00000000000000# 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 WSME-0.9.2/wsme/tests/protocol.py0000664000567000056710000005105613050557000017770 0ustar jenkinsjenkins00000000000000# 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) WSME-0.9.2/wsme/tests/test_restxml.py0000664000567000056710000001536113050557000020663 0ustar jenkinsjenkins00000000000000import 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(""" 5 test """) 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('\n 2\n'), 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('2'), 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(''), 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(''), x WSME-0.9.2/wsme/tests/test_utils.py0000664000567000056710000000642313050557000020324 0ustar jenkinsjenkins00000000000000import 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" ) WSME-0.9.2/wsme/tests/test_types.py0000664000567000056710000005115013050557000020325 0ustar jenkinsjenkins00000000000000import 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)) WSME-0.9.2/wsme/tests/test_protocols_commons.py0000664000567000056710000001145013050557000022737 0ustar jenkinsjenkins00000000000000# 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)) WSME-0.9.2/wsme/tests/test_protocols.py0000664000567000056710000000345013050557000021205 0ustar jenkinsjenkins00000000000000# 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') WSME-0.9.2/wsme/tests/__init__.py0000664000567000056710000000000013050557000017645 0ustar jenkinsjenkins00000000000000WSME-0.9.2/wsme/tests/test_spore.py0000664000567000056710000000270213050557000020310 0ustar jenkinsjenkins00000000000000import 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'] WSME-0.9.2/wsme/runtime.py0000664000567000056710000000042613050557000016443 0ustar jenkinsjenkins00000000000000from 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) WSME-0.9.2/AUTHORS0000664000567000056710000000271213050557153014514 0ustar jenkinsjenkins00000000000000119Vik Angus Salkeld Chad Lung Chang Bo Guo Chris Dent Christophe de Vienne Christophe de Vienne Craig McDaniel Doug Hellmann Doug Hellmann Doug Hellmann Dustin J. Mitchell Endre Karlson Ilya Kharin James Page Jason Myers Jeremy Stanley Julien Danjou Ken'ichi Ohmichi Lan Qi song Louis Taylor Louis Taylor Lucas Alvares Gomes Mehdi Abaakouk Mehdi Abaakouk Michael Krotscheck Ryan Petrello Sanu Madhavan Sascha Peilicke Stéphane Bisinger Sławek Ehlert Victor Stinner Vladyslav Drok Yuriy Zveryanskyy arati.mahimane aviau gordon chung houming-wang venkatamahesh WSME-0.9.2/PKG-INFO0000664000567000056710000001165013050557153014542 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: WSME Version: 0.9.2 Summary: Simplify the writing of REST APIs, and extend them with additional protocols. Home-page: http://git.openstack.org/cgit/openstack/wsme Author: Christophe de Vienne Author-email: python-wsme@googlegroups.com License: MIT Description: 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:///ws/hello.json?who=you`` - ``"Hello you !"`` * - ``http:///ws/hello.xml`` - ``Hello World !`` * - ``http:///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/ Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Software Development :: Libraries :: Python Modules WSME-0.9.2/WSME.egg-info/0000775000567000056710000000000013050557153015707 5ustar jenkinsjenkins00000000000000WSME-0.9.2/WSME.egg-info/pbr.json0000664000567000056710000000005613050557153017366 0ustar jenkinsjenkins00000000000000{"git_version": "9f84e4c", "is_release": true}WSME-0.9.2/WSME.egg-info/requires.txt0000664000567000056710000000007313050557153020307 0ustar jenkinsjenkins00000000000000six>=1.9.0 WebOb>=1.2.3 simplegeneric pytz netaddr>=0.7.12 WSME-0.9.2/WSME.egg-info/namespace_packages.txt0000664000567000056710000000001013050557153022231 0ustar jenkinsjenkins00000000000000wsmeext WSME-0.9.2/WSME.egg-info/PKG-INFO0000664000567000056710000001165013050557153017007 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: WSME Version: 0.9.2 Summary: Simplify the writing of REST APIs, and extend them with additional protocols. Home-page: http://git.openstack.org/cgit/openstack/wsme Author: Christophe de Vienne Author-email: python-wsme@googlegroups.com License: MIT Description: 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:///ws/hello.json?who=you`` - ``"Hello you !"`` * - ``http:///ws/hello.xml`` - ``Hello World !`` * - ``http:///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/ Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Software Development :: Libraries :: Python Modules WSME-0.9.2/WSME.egg-info/dependency_links.txt0000664000567000056710000000000113050557153021755 0ustar jenkinsjenkins00000000000000 WSME-0.9.2/WSME.egg-info/top_level.txt0000664000567000056710000000001513050557153020435 0ustar jenkinsjenkins00000000000000wsme wsmeext WSME-0.9.2/WSME.egg-info/SOURCES.txt0000664000567000056710000000502313050557153017573 0ustar jenkinsjenkins00000000000000.hgtags AUTHORS ChangeLog LICENSE README.rst requirements-py3.txt requirements.txt setup.cfg setup.py tests tox-tmpl.ini tox.ini toxgen.py WSME.egg-info/PKG-INFO WSME.egg-info/SOURCES.txt WSME.egg-info/dependency_links.txt WSME.egg-info/entry_points.txt WSME.egg-info/namespace_packages.txt WSME.egg-info/not-zip-safe WSME.egg-info/pbr.json WSME.egg-info/requires.txt WSME.egg-info/top_level.txt doc/Makefile doc/api.rst doc/changes.rst doc/conf.py doc/document.rst doc/functions.rst doc/gettingstarted.rst doc/index.rst doc/integrate.rst doc/make.bat doc/protocols.rst doc/requirements.txt doc/todo.rst doc/types.rst doc/_static/toggle.css doc/_static/toggle.js doc/_static/wsme.css examples/demo/client.py examples/demo/demo.py examples/demo/setup.cfg examples/demo/setup.py examples/demo/sporeclient.py tests/test_cornice.py tests/test_flask.py tests/test_sphinxext.py tests/test_tg1.py tests/test_tg15.py tests/pecantest/setup.cfg tests/pecantest/setup.py tests/pecantest/test/__init__.py tests/pecantest/test/app.py tests/pecantest/test/controllers/__init__.py tests/pecantest/test/controllers/root.py tests/pecantest/test/controllers/ws.py tests/pecantest/test/model/__init__.py tests/pecantest/test/tests/__init__.py tests/pecantest/test/tests/config.py tests/pecantest/test/tests/test_ws.py tests/rest/test_args.py tests/sphinxexample/conf.py tests/sphinxexample/document.rst tests/sphinxexample/index.rst wsme/__init__.py wsme/api.py wsme/exc.py wsme/protocol.py wsme/root.py wsme/runtime.py wsme/spore.py wsme/types.py wsme/utils.py wsme/rest/__init__.py wsme/rest/args.py wsme/rest/json.py wsme/rest/protocol.py wsme/rest/xml.py wsme/tests/__init__.py wsme/tests/protocol.py wsme/tests/test_api.py wsme/tests/test_exc.py wsme/tests/test_protocols.py wsme/tests/test_protocols_commons.py wsme/tests/test_restjson.py wsme/tests/test_restxml.py wsme/tests/test_root.py wsme/tests/test_spore.py wsme/tests/test_types.py wsme/tests/test_utils.py wsmeext/__init__.py wsmeext/cornice.py wsmeext/flask.py wsmeext/pecan.py wsmeext/sphinxext.py wsmeext/tg1.py wsmeext/tg11.py wsmeext/tg15.py wsmeext/extdirect/__init__.py wsmeext/extdirect/datastore.py wsmeext/extdirect/protocol.py wsmeext/extdirect/sadatastore.py wsmeext/soap/__init__.py wsmeext/soap/protocol.py wsmeext/soap/simplegeneric.py wsmeext/soap/wsdl.py wsmeext/sqlalchemy/__init__.py wsmeext/sqlalchemy/controllers.py wsmeext/sqlalchemy/types.py wsmeext/tests/__init__.py wsmeext/tests/test_extdirect.py wsmeext/tests/test_soap.py wsmeext/tests/test_sqlalchemy_controllers.py wsmeext/tests/test_sqlalchemy_types.pyWSME-0.9.2/WSME.egg-info/entry_points.txt0000664000567000056710000000033713050557153021210 0ustar jenkinsjenkins00000000000000[wsme.protocols] extdirect = wsmeext.extdirect:ExtDirectProtocol rest = wsme.rest.protocol:RestProtocol restjson = wsme.rest.protocol:RestProtocol restxml = wsme.rest.protocol:RestProtocol soap = wsmeext.soap:SoapProtocol WSME-0.9.2/WSME.egg-info/not-zip-safe0000664000567000056710000000000113050557147020140 0ustar jenkinsjenkins00000000000000 WSME-0.9.2/setup.py0000664000567000056710000000012013050557000015134 0ustar jenkinsjenkins00000000000000import setuptools setuptools.setup( setup_requires=['pbr'], pbr=True ) WSME-0.9.2/README.rst0000664000567000056710000000630613050557000015125 0ustar jenkinsjenkins00000000000000Web 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:///ws/hello.json?who=you`` - ``"Hello you !"`` * - ``http:///ws/hello.xml`` - ``Hello World !`` * - ``http:///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/ WSME-0.9.2/requirements.txt0000664000567000056710000000007313050557000016715 0ustar jenkinsjenkins00000000000000six>=1.9.0 WebOb>=1.2.3 simplegeneric pytz netaddr>=0.7.12 WSME-0.9.2/.hgtags0000664000567000056710000000122613050557000014710 0ustar jenkinsjenkins000000000000002bd203a084dcc785257b35e7231b2021722f60de 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 WSME-0.9.2/LICENSE0000664000567000056710000000202613050557000014436 0ustar jenkinsjenkins00000000000000The 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. WSME-0.9.2/toxgen.py0000664000567000056710000001077513050557000015321 0ustar jenkinsjenkins00000000000000""" 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() WSME-0.9.2/examples/0000775000567000056710000000000013050557153015260 5ustar jenkinsjenkins00000000000000WSME-0.9.2/examples/demo/0000775000567000056710000000000013050557153016204 5ustar jenkinsjenkins00000000000000WSME-0.9.2/examples/demo/setup.cfg0000664000567000056710000000012413050557000020011 0ustar jenkinsjenkins00000000000000[easy_install] find_links = http://www.owlfish.com/software/wsgiutils/download.html WSME-0.9.2/examples/demo/demo.py0000664000567000056710000000361013050557000017471 0ustar jenkinsjenkins00000000000000# 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() WSME-0.9.2/examples/demo/sporeclient.py0000664000567000056710000000070513050557000021076 0ustar jenkinsjenkins00000000000000import 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 WSME-0.9.2/examples/demo/setup.py0000664000567000056710000000023313050557000017703 0ustar jenkinsjenkins00000000000000from setuptools import setup setup(name='demo', install_requires=[ 'WSME', 'Bottle', 'Pygments', ], package=['demo']) WSME-0.9.2/examples/demo/client.py0000664000567000056710000000106213050557000020022 0ustar jenkinsjenkins00000000000000from 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) WSME-0.9.2/wsmeext/0000775000567000056710000000000013050557153015136 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/sphinxext.py0000664000567000056710000004302713050557000017537 0ustar jenkinsjenkins00000000000000import 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\w+)(\s+(?P\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'') 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), '' ) self.add_line(u'', '') 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') WSME-0.9.2/wsmeext/tg11.py0000664000567000056710000000241613050557000016256 0ustar jenkinsjenkins00000000000000from turbogears import config import cherrypy from cherrypy.filters.basefilter import BaseFilter from turbogears.startup import call_on_startup, call_on_shutdown from wsmeext.tg1 import wsexpose, wsvalidate import wsmeext.tg1 __all__ = ['adapt', 'wsexpose', 'wsvalidate'] class WSMECherrypyFilter(BaseFilter): def __init__(self, controller): self.controller = controller self.webpath = None def on_start_resource(self): path = cherrypy.request.path if path.startswith(self.controller._wsroot._webpath): cherrypy.request.processRequestBody = False def adapt(wsroot): wsroot._scan_api = wsmeext.tg1.scan_api controller = wsmeext.tg1.Controller(wsroot) filter_ = WSMECherrypyFilter(controller) def install_filter(): filter_.webpath = config.get('server.webpath') or '' controller._wsroot._webpath = \ filter_.webpath + controller._wsroot._webpath cherrypy.root._cp_filters.append(filter_) def uninstall_filter(): cherrypy.root._cp_filters.remove(filter_) controller._wsroot._webpath = \ controller._wsroot._webpath[len(filter_.webpath):] call_on_startup.append(install_filter) call_on_shutdown.insert(0, uninstall_filter) return controller WSME-0.9.2/wsmeext/flask.py0000664000567000056710000000623513050557000016605 0ustar jenkinsjenkins00000000000000from __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 WSME-0.9.2/wsmeext/extdirect/0000775000567000056710000000000013050557153017131 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/extdirect/protocol.py0000664000567000056710000003105413050557000021336 0ustar jenkinsjenkins00000000000000import 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) WSME-0.9.2/wsmeext/extdirect/__init__.py0000664000567000056710000000010113050557000021221 0ustar jenkinsjenkins00000000000000from wsmeext.extdirect.protocol import ExtDirectProtocol # noqa WSME-0.9.2/wsmeext/extdirect/sadatastore.py0000664000567000056710000000112413050557000022002 0ustar jenkinsjenkins00000000000000from 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 ) WSME-0.9.2/wsmeext/extdirect/datastore.py0000664000567000056710000000576013050557000021470 0ustar jenkinsjenkins00000000000000import 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,), {} ) WSME-0.9.2/wsmeext/tg1.py0000664000567000056710000001214113050557000016171 0ustar jenkinsjenkins00000000000000try: import json except ImportError: import simplejson as json # noqa import functools import sys import cherrypy import webob from turbogears import expose, util import turbogears.view from wsme.rest import validate as wsvalidate import wsme.api import wsme.rest import wsme.rest.args import wsme.rest.json from wsme.utils import is_valid_code import inspect APIPATH_MAXLEN = 50 __all__ = ['wsexpose', 'wsvalidate'] def wsexpose(*args, **kwargs): tg_json_expose = expose( 'wsmejson:', accept_format='application/json', content_type='application/json', tg_format='json' ) tg_altjson_expose = expose( 'wsmejson:', accept_format='text/javascript', content_type='application/json' ) tg_xml_expose = expose( 'wsmexml:', accept_format='text/xml', content_type='text/xml', tg_format='xml' ) sig = wsme.signature(*args, **kwargs) def decorate(f): sig(f) funcdef = wsme.api.FunctionDefinition.get(f) @functools.wraps(f) def callfunction(self, *args, **kwargs): args, kwargs = wsme.rest.args.get_args( funcdef, args, kwargs, cherrypy.request.params, None, cherrypy.request.body, cherrypy.request.headers['Content-Type'] ) if funcdef.pass_request: kwargs[funcdef.pass_request] = cherrypy.request try: result = f(self, *args, **kwargs) except: try: exception_info = sys.exc_info() orig_exception = exception_info[1] if isinstance(orig_exception, cherrypy.HTTPError): orig_code = getattr(orig_exception, 'status', None) else: orig_code = getattr(orig_exception, 'code', None) data = wsme.api.format_exception(exception_info) finally: del exception_info cherrypy.response.status = 500 if data['faultcode'] == 'client': cherrypy.response.status = 400 elif orig_code and is_valid_code(orig_code): cherrypy.response.status = orig_code accept = cherrypy.request.headers.get('Accept', "").lower() accept = util.simplify_http_accept_header(accept) decorators = {'text/xml': wsme.rest.xml.encode_error} return decorators.get( accept, wsme.rest.json.encode_error )(None, data) return dict( datatype=funcdef.return_type, result=result ) callfunction = tg_xml_expose(callfunction) callfunction = tg_altjson_expose(callfunction) callfunction = tg_json_expose(callfunction) callfunction._wsme_original_function = f return callfunction return decorate class AutoJSONTemplate(object): def __init__(self, extra_vars_func=None, options=None): pass def render(self, info, format="json", fragment=False, template=None): "Renders the template to a string using the provided info." return wsme.rest.json.encode_result( info['result'], info['datatype'] ) def get_content_type(self, user_agent): return "application/json" class AutoXMLTemplate(object): def __init__(self, extra_vars_func=None, options=None): pass def render(self, info, format="json", fragment=False, template=None): "Renders the template to a string using the provided info." return wsme.rest.xml.encode_result( info['result'], info['datatype'] ) def get_content_type(self, user_agent): return "text/xml" turbogears.view.engines['wsmejson'] = AutoJSONTemplate(turbogears.view.stdvars) turbogears.view.engines['wsmexml'] = AutoXMLTemplate(turbogears.view.stdvars) class Controller(object): def __init__(self, wsroot): self._wsroot = wsroot @expose() def default(self, *args, **kw): req = webob.Request(cherrypy.request.wsgi_environ) res = self._wsroot._handle_request(req) cherrypy.response.header_list = res.headerlist cherrypy.response.status = res.status return res.body 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._wsme_original_function, [controller] 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 def scan_api(root=None): return _scan_api(cherrypy.root) WSME-0.9.2/wsmeext/soap/0000775000567000056710000000000013050557153016100 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/soap/protocol.py0000664000567000056710000003571013050557000020310 0ustar jenkinsjenkins00000000000000""" 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 WSME-0.9.2/wsmeext/soap/simplegeneric.py0000664000567000056710000000574713050557000021304 0ustar jenkinsjenkins00000000000000import 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()) WSME-0.9.2/wsmeext/soap/__init__.py0000664000567000056710000000016313050557000020200 0ustar jenkinsjenkins00000000000000from __future__ import absolute_import from wsmeext.soap.protocol import SoapProtocol __all__ = ['SoapProtocol'] WSME-0.9.2/wsmeext/soap/wsdl.py0000664000567000056710000002155113050557000017416 0ustar jenkinsjenkins00000000000000import 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) WSME-0.9.2/wsmeext/__init__.py0000664000567000056710000000007713050557000017242 0ustar jenkinsjenkins00000000000000import pkg_resources pkg_resources.declare_namespace(__name__) WSME-0.9.2/wsmeext/sqlalchemy/0000775000567000056710000000000013050557153017300 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/sqlalchemy/__init__.py0000664000567000056710000000000013050557000021366 0ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/sqlalchemy/types.py0000664000567000056710000001573313050557000021016 0ustar jenkinsjenkins00000000000000import 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 WSME-0.9.2/wsmeext/sqlalchemy/controllers.py0000664000567000056710000000524013050557000022210 0ustar jenkinsjenkins00000000000000from 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,), {} ) WSME-0.9.2/wsmeext/tg15.py0000664000567000056710000000074113050557000016261 0ustar jenkinsjenkins00000000000000import cherrypy from wsmeext.tg1 import wsexpose, wsvalidate import wsmeext.tg1 __all__ = ['adapt', 'wsexpose', 'wsvalidate'] def scan_api(root=None): for baseurl, instance in cherrypy.tree.apps.items(): path = [token for token in baseurl.split('/') if token] for i in wsmeext.tg1._scan_api(instance.root, path): yield i def adapt(wsroot): wsroot._scan_api = scan_api controller = wsmeext.tg1.Controller(wsroot) return controller WSME-0.9.2/wsmeext/tests/0000775000567000056710000000000013050557153016300 5ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/tests/test_extdirect.py0000664000567000056710000001622513050557000021701 0ustar jenkinsjenkins00000000000000import 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 } }] } WSME-0.9.2/wsmeext/tests/test_sqlalchemy_types.py0000664000567000056710000000337313050557000023274 0ustar jenkinsjenkins00000000000000import datetime import wsmeext.sqlalchemy.types from wsme.types import text, Unset, isarray from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Date, ForeignKey from sqlalchemy.orm import relation from six import u SABase = declarative_base() class SomeClass(SABase): __tablename__ = 'some_table' id = Column(Integer, primary_key=True) name = Column(String(50)) adate = Column(Date) def test_complextype(): class AType(wsmeext.sqlalchemy.types.Base): __saclass__ = SomeClass assert AType.id.datatype is int assert AType.name.datatype is text assert AType.adate.datatype is datetime.date a = AType() s = SomeClass(name=u('aname'), adate=datetime.date(2012, 6, 26)) assert s.name == u('aname') a.from_instance(s) assert a.name == u('aname') assert a.adate == datetime.date(2012, 6, 26) a.name = u('test') del a.adate assert a.adate is Unset a.to_instance(s) assert s.name == u('test') assert s.adate == datetime.date(2012, 6, 26) def test_generate(): class A(SABase): __tablename__ = 'a' id = Column(Integer, primary_key=True) name = Column(String(50)) _b_id = Column(ForeignKey('b.id')) b = relation('B') class B(SABase): __tablename__ = 'b' id = Column(Integer, primary_key=True) name = Column(String(50)) alist = relation(A) newtypes = wsmeext.sqlalchemy.types.generate_types(A, B) assert newtypes['A'].id.datatype is int assert newtypes['A'].b.datatype is newtypes['B'] assert newtypes['B'].id.datatype is int assert isarray(newtypes['B'].alist.datatype) assert newtypes['B'].alist.datatype.item_type is newtypes['A'] WSME-0.9.2/wsmeext/tests/test_soap.py0000664000567000056710000002714213050557000020650 0ustar jenkinsjenkins00000000000000import 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 = """ <%(method)s> %(params)s """ % 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. WSME-0.9.2/wsmeext/tests/test_sqlalchemy_controllers.py0000664000567000056710000001457413050557000024503 0ustar jenkinsjenkins00000000000000import datetime try: import json except ImportError: import simplejson as json from webtest import TestApp from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, Unicode, Date, ForeignKey from sqlalchemy.orm import relation from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, scoped_session from wsme import WSRoot import wsme.types from wsmeext.sqlalchemy.types import generate_types from wsmeext.sqlalchemy.controllers import CRUDController from six import u engine = create_engine('sqlite:///') DBSession = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine)) DBBase = declarative_base() registry = wsme.types.Registry() class DBPerson(DBBase): __tablename__ = 'person' id = Column(Integer, primary_key=True) name = Column(Unicode(50)) birthdate = Column(Date) addresses = relation('DBAddress') class DBAddress(DBBase): __tablename__ = 'address' id = Column(Integer, primary_key=True) _person_id = Column('person_id', ForeignKey(DBPerson.id)) street = Column(Unicode(50)) city = Column(Unicode(50)) person = relation(DBPerson) globals().update( generate_types(DBPerson, DBAddress, makename=lambda s: s[2:], registry=registry)) class PersonController(CRUDController): __saclass__ = DBPerson __dbsession__ = DBSession __registry__ = registry class AddressController(CRUDController): __saclass__ = DBAddress __dbsession__ = DBSession __registry__ = registry class Root(WSRoot): __registry__ = registry person = PersonController() address = AddressController() class TestCRUDController(): def setUp(self): DBBase.metadata.create_all(DBSession.bind) self.root = Root() self.root.getapi() self.root.addprotocol('restjson') self.app = TestApp(self.root.wsgiapp()) def tearDown(self): DBBase.metadata.drop_all(DBSession.bind) def test_create(self): data = dict(data=dict( name=u('Pierre-Joseph'), birthdate=u('1809-01-15') )) r = self.app.post('/person/create', json.dumps(data), headers={'Content-Type': 'application/json'}) r = json.loads(r.text) print(r) assert r['name'] == u('Pierre-Joseph') assert r['birthdate'] == u('1809-01-15') def test_PUT(self): data = dict(data=dict( name=u('Pierre-Joseph'), birthdate=u('1809-01-15') )) r = self.app.put('/person', json.dumps(data), headers={'Content-Type': 'application/json'}) r = json.loads(r.text) print(r) assert r['name'] == u('Pierre-Joseph') assert r['birthdate'] == u('1809-01-15') def test_read(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id r = self.app.post('/person/read', '{"ref": {"id": %s}}' % pid, headers={'Content-Type': 'application/json'}) r = json.loads(r.text) print(r) assert r['name'] == u('Pierre-Joseph') assert r['birthdate'] == u('1809-01-15') def test_GET(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id r = self.app.get('/person?ref.id=%s' % pid, headers={'Accept': 'application/json'}) r = json.loads(r.text) print(r) assert r['name'] == u('Pierre-Joseph') assert r['birthdate'] == u('1809-01-15') def test_GET_bad_accept(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id r = self.app.get('/person?ref.id=%s' % pid, headers={'Accept': 'text/plain'}, status=406) assert r.text == ("Unacceptable Accept type: text/plain not in " "['application/json', 'text/javascript', " "'application/javascript', 'text/xml']") def test_update(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id data = { "id": pid, "name": u('Pierre-Joseph Proudon') } r = self.app.post('/person/update', json.dumps(dict(data=data)), headers={'Content-Type': 'application/json'}) r = json.loads(r.text) print(r) assert r['name'] == u('Pierre-Joseph Proudon') assert r['birthdate'] == u('1809-01-15') def test_POST(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id data = { "id": pid, "name": u('Pierre-Joseph Proudon') } r = self.app.post('/person', json.dumps(dict(data=data)), headers={'Content-Type': 'application/json'}) r = json.loads(r.text) print(r) assert r['name'] == u('Pierre-Joseph Proudon') assert r['birthdate'] == u('1809-01-15') def test_delete(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id r = self.app.post('/person/delete', json.dumps( dict(ref=dict(id=pid))), headers={ 'Content-Type': 'application/json' }) print(r) assert DBSession.query(DBPerson).get(pid) is None def test_DELETE(self): p = DBPerson( name=u('Pierre-Joseph'), birthdate=datetime.date(1809, 1, 15)) DBSession.add(p) DBSession.flush() pid = p.id r = self.app.delete('/person?ref.id=%s' % pid, headers={'Content-Type': 'application/json'}) print(r) assert DBSession.query(DBPerson).get(pid) is None def test_nothing(self): pass WSME-0.9.2/wsmeext/tests/__init__.py0000664000567000056710000000000013050557000020366 0ustar jenkinsjenkins00000000000000WSME-0.9.2/wsmeext/pecan.py0000664000567000056710000001041713050557000016570 0ustar jenkinsjenkins00000000000000from __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 WSME-0.9.2/wsmeext/cornice.py0000664000567000056710000001241313050557000017122 0ustar jenkinsjenkins00000000000000""" 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') WSME-0.9.2/tests/0000775000567000056710000000000013050557153014604 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/rest/0000775000567000056710000000000013050557153015561 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/rest/test_args.py0000664000567000056710000000101213050557000020107 0ustar jenkinsjenkins00000000000000import 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, ((), {})) WSME-0.9.2/tests/test_tg15.py0000664000567000056710000001210713050557000016765 0ustar jenkinsjenkins00000000000000import 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 == "50" 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 == ("Client" "(400, 'Cannot divide by zero!')" "") 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') ) WSME-0.9.2/tests/test_flask.py0000664000567000056710000001244713050557000017314 0ustar jenkinsjenkins00000000000000# 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/') @signature(Model, text) def get_model(name): return Model(name=name) @test_app.route('/models//secret') @signature(Model, text) def model_secret(name): abort(403) @test_app.route('/models//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'Client' b'403: Forbidden' b'') 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'Client' b'FOO!' b'') 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() WSME-0.9.2/tests/sphinxexample/0000775000567000056710000000000013050557153017471 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/sphinxexample/index.rst0000664000567000056710000000003313050557000021315 0ustar jenkinsjenkins00000000000000.. toctree:: document WSME-0.9.2/tests/sphinxexample/conf.py0000664000567000056710000001640113050557000020761 0ustar jenkinsjenkins00000000000000# -*- 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 # " v 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 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' ] WSME-0.9.2/tests/sphinxexample/document.rst0000664000567000056710000000124513050557000022032 0ustar jenkinsjenkins00000000000000API 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 ` .. 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/ WSME-0.9.2/tests/test_cornice.py0000664000567000056710000001111113050557000017621 0ustar jenkinsjenkins00000000000000import 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'1first' ) 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 = 'new' resp = self.app.post( '/users', data, headers={"Content-Type": "text/xml"} ) self.assertEqual( resp.body, b'2new' ) 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) WSME-0.9.2/tests/pecantest/0000775000567000056710000000000013050557153016572 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/setup.cfg0000664000567000056710000000012013050557000020373 0ustar jenkinsjenkins00000000000000[nosetests] match=^test where=test nocapture=1 cover-package=test cover-erase=1 WSME-0.9.2/tests/pecantest/test/0000775000567000056710000000000013050557153017551 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/test/controllers/0000775000567000056710000000000013050557153022117 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/test/controllers/root.py0000664000567000056710000000104513050557000023443 0ustar jenkinsjenkins00000000000000from 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 WSME-0.9.2/tests/pecantest/test/controllers/__init__.py0000664000567000056710000000000013050557000024205 0ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/test/controllers/ws.py0000664000567000056710000000741113050557000023114 0ustar jenkinsjenkins00000000000000# 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 d’un révolutionnaire pour servir à " u"l’histoire 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 d’un révolutionnaire pour servir à " u"l’histoire 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=[])] WSME-0.9.2/tests/pecantest/test/app.py0000664000567000056710000000070513050557000020674 0ustar jenkinsjenkins00000000000000from 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) ) WSME-0.9.2/tests/pecantest/test/model/0000775000567000056710000000000013050557153020651 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/test/model/__init__.py0000664000567000056710000000003313050557000022745 0ustar jenkinsjenkins00000000000000def init_model(): pass WSME-0.9.2/tests/pecantest/test/__init__.py0000664000567000056710000000000013050557000021637 0ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/test/tests/0000775000567000056710000000000013050557153020713 5ustar jenkinsjenkins00000000000000WSME-0.9.2/tests/pecantest/test/tests/config.py0000664000567000056710000000111213050557000022514 0ustar jenkinsjenkins00000000000000# 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 WSME-0.9.2/tests/pecantest/test/tests/__init__.py0000664000567000056710000000100013050557000023002 0ustar jenkinsjenkins00000000000000import 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) WSME-0.9.2/tests/pecantest/test/tests/test_ws.py0000664000567000056710000002137213050557000022751 0ustar jenkinsjenkins00000000000000from 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 '1' in body assert 'aname' 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 'Client' 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 'Client' 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 'Server' 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 'Client' 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 '1' in res.body.decode('utf-8') assert 'aname' in res.body.decode('utf-8') assert '' 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'] WSME-0.9.2/tests/pecantest/setup.py0000664000567000056710000000076213050557000020300 0ustar jenkinsjenkins00000000000000# -*- 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']) ) WSME-0.9.2/tests/test_sphinxext.py0000664000567000056710000000272113050557000020240 0ustar jenkinsjenkins00000000000000import 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)') WSME-0.9.2/tests/test_tg1.py0000664000567000056710000001272013050557000016701 0ustar jenkinsjenkins00000000000000import 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 == "50" 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 == ("Server" "(400, 'Cannot divide by zero!')" "") 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') ) WSME-0.9.2/tox.ini0000664000567000056710000011144713050557000014754 0ustar jenkinsjenkins00000000000000[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 WSME-0.9.2/requirements-py3.txt0000664000567000056710000000007313050557000017426 0ustar jenkinsjenkins00000000000000six>=1.9.0 WebOb>=1.2.3 simplegeneric pytz netaddr>=0.7.12