diff options
106 files changed, 3 insertions, 15601 deletions
diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 726575b..0000000 --- a/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -*.pyc -*.pyo -*.egg-info -*.swp -*.orig -~* -*~ -.coverage* -coverage*.xml -.noseids -.tox -.test_sphinxext -nosetests*.xml - -build -dist - -doc/_build -d2to1-*.egg - -WSME.egg-info/ - -# Files created by pbr -AUTHORS -ChangeLog -pbr*.egg - -# Cross-test logs -cross-test-*.log diff --git a/.gitreview b/.gitreview deleted file mode 100644 index f6182b4..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/wsme.git diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 081c1f7..0000000 --- a/.hgtags +++ /dev/null @@ -1,14 +0,0 @@ -2bd203a084dcc785257b35e7231b2021722f60de 0.1.0a1 -0eae00db9384d52cc4a82c09ab207d631ecb82e4 0.1.0a2 -86466da44f44a97b379c7b8e94c371526be0eb9f 0.1.0a3 -b38c56a2b9130d8fade7be22c8ac66a45fa77a6e 0.1.0a4 -b0019e486c807bafe412ebaa6eb9bd9ab656c81c 0.1.0 -c17de432c1857cfa059816d0db332bcdabea0c82 0.1.1 -cfb5efc624f55710c987c7795501f3dd44a01078 0.2.0 -ebe2c6f228ad4a365fbda9418f3e113d542390f0 0.3b1 -d5eab01bf49192df2e0f24c78ca4936073e45b19 0.3b2 -603c8586b076f5cf9b70b6cd82578dba7226e0c7 0.3 -5ad01afed8779bb5a384802a2ec7d6ed0186c7d5 0.4b1 -f06e004ca8e4013bf94df0cdade23b01742b0ec0 0.4 -359199eb4e0999b5920eadfa40038013cd360df6 0.5b1 -d3e5eee0b150048762169ff20ee25b43aa0369fa 0.5b2 diff --git a/.zuul.yaml b/.zuul.yaml deleted file mode 100644 index f42aa59..0000000 --- a/.zuul.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- project: - templates: - - openstack-python-jobs - - openstack-python36-jobs - - openstack-python37-jobs diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d13cc4b..0000000 --- a/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..171f73a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# This repo has moved to OpenDev + +It can now be found at [https://opendev.org/x/wsme](https://opendev.org/x/wsme) diff --git a/README.rst b/README.rst deleted file mode 100644 index 5b0e2a3..0000000 --- a/README.rst +++ /dev/null @@ -1,112 +0,0 @@ -Web Services Made Easy -====================== - -Introduction ------------- - -Web Services Made Easy (WSME) simplifies the writing of REST web services -by providing simple yet powerful typing, removing the need to directly -manipulate the request and the response objects. - -WSME can work standalone or on top of your favorite Python web -(micro)framework, so you can use both your preferred way of routing your REST -requests and most of the features of WSME that rely on the typing system like: - -- Alternate protocols, including those supporting batch-calls -- Easy documentation through a Sphinx_ extension - -WSME is originally a rewrite of TGWebServices -with a focus on extensibility, framework-independance and better type handling. - -How Easy ? -~~~~~~~~~~ - -Here is a standalone wsgi example:: - - from wsme import WSRoot, expose - - class MyService(WSRoot): - @expose(unicode, unicode) # First parameter is the return type, - # then the function argument types - def hello(self, who=u'World'): - return u"Hello {0} !".format(who) - - ws = MyService(protocols=['restjson', 'restxml', 'soap']) - application = ws.wsgiapp() - -With this published at the ``/ws`` path of your application, you can access -your hello function in various protocols: - -.. list-table:: - :header-rows: 1 - - * - URL - - Returns - - * - ``http://<server>/ws/hello.json?who=you`` - - ``"Hello you !"`` - - * - ``http://<server>/ws/hello.xml`` - - ``<result>Hello World !</result>`` - - * - ``http://<server>/ws/api.wsdl`` - - A WSDL description for any SOAP client. - - -Main features -~~~~~~~~~~~~~ - -- Very simple API. -- Supports user-defined simple and complex types. -- Multi-protocol : REST+Json, REST+XML, SOAP, ExtDirect and more to come. -- Extensible : easy to add more protocols or more base types. -- Framework independence : adapters are provided to easily integrate - your API in any web framework, for example a wsgi container, - Pecan_, TurboGears_, Flask_, cornice_... -- Very few runtime dependencies: webob, simplegeneric. Optionnaly lxml and - simplejson if you need better performances. -- Integration in `Sphinx`_ for making clean documentation with - ``wsmeext.sphinxext``. - -.. _Pecan: http://pecanpy.org/ -.. _TurboGears: http://www.turbogears.org/ -.. _Flask: http://flask.pocoo.org/ -.. _cornice: http://pypi.python.org/pypi/cornice - -Install -~~~~~~~ - -:: - - pip install WSME - -or, if you do not have pip on your system or virtualenv - -:: - - easy_install WSME - -Changes -~~~~~~~ - -- Read the `Changelog`_ - -Getting Help -~~~~~~~~~~~~ - -- Read the `WSME Documentation`_. -- Questions about WSME should go to the `python-wsme mailinglist`_. - -Contribute -~~~~~~~~~~ - -* Documentation: http://packages.python.org/WSME/ -* Source: http://git.openstack.org/cgit/openstack/wsme -* Bugs: https://bugs.launchpad.net/wsme/+bugs -* Code review: https://review.openstack.org/#/q/project:openstack/wsme,n,z - -.. _Changelog: http://packages.python.org/WSME/changes.html -.. _python-wsme mailinglist: http://groups.google.com/group/python-wsme -.. _WSME Documentation: http://packages.python.org/WSME/ -.. _WSME issue tracker: https://bugs.launchpad.net/wsme/+bugs -.. _Sphinx: http://sphinx.pocoo.org/ diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 8cf843c..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,134 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - -help: - @echo "Please use \`make <target>' where <target> is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -ziphtml: html - rm -f $(BUILDDIR)/wsme-documentation.zip - cd $(BUILDDIR)/html && zip -r ../wsme-documentation.zip . - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/WebServicesMadeEasy" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WebServicesMadeEasy" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/_static/toggle.css b/doc/_static/toggle.css deleted file mode 100644 index df87bdc..0000000 --- a/doc/_static/toggle.css +++ /dev/null @@ -1,10 +0,0 @@ -dl.toggle dt { - background-color: #eeffcc; - border: 1px solid #ac9; - display: inline; -} - -dl.toggle dd { - display: none; -} - diff --git a/doc/_static/toggle.js b/doc/_static/toggle.js deleted file mode 100644 index 0c779ac..0000000 --- a/doc/_static/toggle.js +++ /dev/null @@ -1,9 +0,0 @@ -/*global $,document*/ -$(document).ready(function () { - "use strict"; - $("dl.toggle > dt").click( - function (event) { - $(this).next().toggle(250); - } - ); -}); diff --git a/doc/_static/wsme.css b/doc/_static/wsme.css deleted file mode 100644 index 09675f6..0000000 --- a/doc/_static/wsme.css +++ /dev/null @@ -1,27 +0,0 @@ -@import "agogo.css"; - -table.docutils { - margin: 0; - padding: 0; - border: 1; -} - -table.docutils th { - margin: 0; - padding: 0; - border: 0; -} - -table.docutils thead tr { -} - -table.docutils td { - margin: 0; - padding: 0; - border: 0; -} - -table.docutils tr.row-odd { - background: #EEEEEC; -} - diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index dc61f98..0000000 --- a/doc/api.rst +++ /dev/null @@ -1,45 +0,0 @@ -API -=== - -Public API ----------- - -:mod:`wsme` -- Essentials -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsme - -.. autoclass:: signature([return_type, [arg0_type, [arg1_type, ... ] ] ], body=None, status_code=None) - -.. autoclass:: wsme.types.Base -.. autoclass:: wsattr -.. autoclass:: wsproperty - -.. data:: Unset - - Default value of the complex type attributes. - -.. autoclass:: WSRoot - :members: - -Internals ---------- - -:mod:`wsme.types` -- Types -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: wsme.types - :members: register_type - -:mod:`wsme.api` -- API related api -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: wsme.api - :members: FunctionArgument, FunctionDefinition - -:mod:`wsme.rest.args` -- REST protocol argument handling -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: wsme.rest.args - :members: - diff --git a/doc/changes.rst b/doc/changes.rst deleted file mode 100644 index 4ee6432..0000000 --- a/doc/changes.rst +++ /dev/null @@ -1,439 +0,0 @@ -Changes -======= - -0.9.4 (future) --------------- - -* SQLAlchemy support is deprecated and will be removed in one of the next - releases. It has never actually worked to begin with. - -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. diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index c4b5696..0000000 --- a/doc/conf.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Web Services Made Easy documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 2 20:27:45 2011. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'wsmeext.sphinxext', - 'sphinx.ext.intersphinx'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Web Services Made Easy' -copyright = u'2011, Christophe de Vienne' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -import pkg_resources -wsmedist = pkg_resources.require('WSME')[0] -version = wsmedist.version - -# The short X.Y version. -version = '.'.join(version.split('.')[:2]) -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -#html_theme = 'agogo' -#html_theme_options = { -# "pagewidth": "60em", -# "documentwidth": "40em", -#} - -#html_style = 'wsme.css' - -import cloud_sptheme as csp - -html_theme = 'cloud' -html_theme_path = [csp.get_theme_dir()] - -html_theme_options = { - "roottarget": "index", - "googleanalytics_id": "UA-8510502-6" -} - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# "<project> v<release> documentation". -html_title = "WSME %s" % release - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = "WSME" - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a <link> tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'WebServicesMadeEasydoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -latex_paper_size = 'a4' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation', - u'Christophe de Vienne', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -latex_preamble = ''' -\usepackage[T2A]{fontenc} -\usepackage[utf8]{inputenc} -''' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation', - [u'Christophe de Vienne'], 1) -] - - -autodoc_member_order = 'bysource' - -wsme_protocols = [ - 'restjson', 'restxml', 'soap', 'extdirect' -] - -intersphinx_mapping = { - 'python': ('http://docs.python.org/', None), - 'six': ('http://packages.python.org/six/', None), -} - - -def setup(app): - # confval directive taken from the sphinx doc - app.add_object_type('confval', 'confval', - objname='configuration value', - indextemplate='pair: %s; configuration value') diff --git a/doc/document.rst b/doc/document.rst deleted file mode 100644 index 39a54f2..0000000 --- a/doc/document.rst +++ /dev/null @@ -1,190 +0,0 @@ -Document your API -================= - -Web services without a proper documentation are usually useless. - -To make it easy to document your own API, WSME provides a Sphinx_ extension. - -Install the extension ---------------------- - -Here we consider that you already quick-started a sphinx project. - -#. In your ``conf.py`` file, add ``'ext'`` to your extensions, - and optionally set the enabled protocols. - - .. code-block:: python - - extensions = ['ext'] - - wsme_protocols = ['restjson', 'restxml', 'extdirect'] - -#. Copy :download:`toggle.js <_static/toggle.js>` - and :download:`toggle.css <_static/toggle.css>` - in your _static directory. - -The ``wsme`` domain -------------------- - -The extension will add a new Sphinx domain providing a few directives. - -Config values -~~~~~~~~~~~~~ - -.. confval:: wsme_protocols - - A list of strings that are WSME protocol names. If provided by an - additional package (for example WSME-Soap or WSME-ExtDirect), that package must - be installed. - - The types and services generated documentation will include code samples - for each of these protocols. - -.. confval:: wsme_root - - A string that is the full name of the service root controller. - It will be used - to determinate the relative path of the other controllers when they - are autodocumented, and calculate the complete webpath of the other - controllers. - -.. confval:: wsme_webpath - - A string that is the webpath where the :confval:`wsme_root` is mounted. - -Directives -~~~~~~~~~~ - -.. rst:directive:: .. root:: <WSRoot full path> - - Define the service root controller for this documentation source file. - To set it globally, see :confval:`wsme_root`. - - A ``webpath`` option allows override of :confval:`wsme_webpath`. - - Example: - - .. code-block:: rst - - .. wsme:root:: myapp.controllers.MyWSRoot - :webpath: /api - -.. rst:directive:: .. service:: name/space/ServiceName - - Declare a service. - -.. rst:directive:: .. type:: MyComplexType - - Equivalent to the :rst:dir:`py:class` directive to document a complex type - -.. rst:directive:: .. attribute:: aname - - Equivalent to the :rst:dir:`py:attribute` directive to document a complex type - attribute. It takes an additional ``:type:`` field. - -Example -~~~~~~~ - -.. list-table:: - :header-rows: 1 - - * - Source - - Result - - * - .. code-block:: rst - - .. wsme:root:: wsmeext.sphinxext.SampleService - :webpath: /api - - .. wsme:type:: MyType - - .. wsme:attribute:: test - - :type: int - - .. wsme:service:: name/space/SampleService - - .. wsme:function:: doit - - - .. wsme:root:: wsmeext.sphinxext.SampleService - :webpath: /api - - .. wsme:type:: MyType - - .. wsme:attribute:: test - - :type: int - - .. wsme:service:: name/space/SampleService - - .. wsme:function:: getType - - Returns a :wsme:type:`MyType <MyType>` - - -Autodoc directives -~~~~~~~~~~~~~~~~~~ - -Theses directives scan your code to generate the documentation from the -docstrings and your API types and controllers. - -.. rst:directive:: .. autotype:: myapp.MyType - - Generate the myapp.MyType documentation. - -.. rst:directive:: .. autoattribute:: myapp.MyType.aname - - Generate the myapp.MyType.aname documentation. - -.. rst:directive:: .. autoservice:: myapp.MyService - - Generate the myapp.MyService documentation. - -.. rst:directive:: .. autofunction:: myapp.MyService.myfunction - - Generate the myapp.MyService.myfunction documentation. - -Full Example ------------- - -Python source -~~~~~~~~~~~~~ - -.. literalinclude:: ../wsmeext/sphinxext.py - :lines: 69-96 - :language: python - -Documentation source -~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: rst - - .. default-domain:: wsmeext - - .. type:: int - - An integer - - .. autotype:: wsmeext.sphinxext.SampleType - :members: - - .. autoservice:: wsmeext.sphinxext.SampleService - :members: - -Result -~~~~~~ - -.. default-domain:: wsmeext - -.. type:: int - - An integer - -.. autotype:: wsmeext.sphinxext.SampleType - :members: - -.. autoservice:: wsmeext.sphinxext.SampleService - :members: - - -.. _Sphinx: http://sphinx.pocoo.org/ diff --git a/doc/functions.rst b/doc/functions.rst deleted file mode 100644 index da56d25..0000000 --- a/doc/functions.rst +++ /dev/null @@ -1,204 +0,0 @@ -Functions -========= - -WSME is based on the idea that most of the time the input and output of web -services are actually strictly typed. It uses this idea to ease the -implementation of the actual functions by handling those input/output. -It also proposes alternate protocols on top of a proper REST api. - -This chapter explains in detail how to 'sign' a function with WSME. - -The decorators --------------- - -Depending on the framework you are using, you will have to use either a -@\ :class:`wsme.signature` decorator or a @\ :class:`wsme.wsexpose` decorator. - -@signature -~~~~~~~~~~ - -The base @\ :class:`wsme.signature` decorator defines the return and argument types -of the function, and if needed a few more options. - -The Flask and Cornice adapters both propose a specific version of it, which -also wrap the function so that it becomes suitable for the host framework. - -In any case, the use of @\ :class:`wsme.signature` has the same meaning: tell WSME what is the -signature of the function. - -@wsexpose -~~~~~~~~~ - -The native Rest implementation, and the TG and Pecan adapters add a @\ :class:`wsme.wsexpose` -decorator. - -It does what @\ :class:`wsme.signature` does, *and* exposes the function in the routing system -of the host framework. - -This decorator is generally used in an object-dispatch routing context. - -.. note:: - - Since both decorators play the same role, the rest of this - document will alway use @signature. - -Signing a function ------------------- - -Signing a function is just a matter of decorating it with @signature: - -.. code-block:: python - - @signature(int, int, int) - def multiply(a, b): - return a * b - -In this trivial example, we tell WSME that the 'multiply' function returns an -integer, and takes two integer parameters. - -WSME will match the argument types by order to determine the exact type of each -named argument. This is important since most of the web service protocols don't -provide strict argument ordering but only named parameters. - -Optional arguments -~~~~~~~~~~~~~~~~~~ - -Defining an argument as optional is done by providing a default value: - -.. code-block:: python - - @signature(int, int, int): - def increment(value, delta=1): - return value + delta - -In this example, the caller may omit the 'delta' argument, and no -'MissingArgument' error will be raised. - -Additionally, this argument will be documented as optional by the sphinx -extension. - -Body argument -~~~~~~~~~~~~~ - -When defining a Rest CRUD API, we generally have a URL to which we POST data. - -For example: - -.. code-block:: python - - @signature(Author, Author) - def update_author(data): - # ... - return data - -Such a function will take at least one parameter, 'data', that is a structured -type. With the default way of handling parameters, the body of the request -would look like this: - -.. code-block:: javascript - - { - "data": - { - "id": 1, - "name": "Pierre-Joseph" - } - } - -If you think (and you should) that it has one extra level of nesting, the 'body' -argument is here for you:: - - @signature(Author, body=Author) - def update_author(data): - # ... - return data - -With this syntax, we can now post a simpler body: - -.. code-block:: javascript - - { - "id": 1, - "name": "Pierre-Joseph" - } - -Note that this does not prevent the function from having multiple parameters; it just requires -the body argument to be the last: - -.. code-block:: python - - @signature(Author, bool, body=Author) - def update_author(force_update=False, data=None): - # ... - return data - -In this case, the other arguments can be passed in the URL, in addition to the -body parameter. For example, a POST on ``/author/SOMEID?force_update=true``. - -Status code -~~~~~~~~~~~ - -The default status codes returned by WSME are 200, 400 (if the client sends invalid -inputs) and 500 (for server-side errors). - -Since a proper Rest API should use different return codes (201, etc), one can -use the 'status_code=' option of @signature to do so. - -.. code-block:: python - - @signature(Author, body=Author, status_code=201) - def create_author(data): - # ... - return data - -Of course this code will only be used if no error occurs. - -In case the function needs to change the status code on a per-request basis, it -can return a :class:`wsme.Response` object, allowing it to override the status -code: - -.. code-block:: python - - @signature(Author, body=Author, status_code=202) - def update_author(data): - # ... - response = Response(data) - if transaction_finished_and_successful: - response.status_code = 200 - return response - -Extra arguments -~~~~~~~~~~~~~~~ - -The default behavior of WSME is to reject requests that give extra/unknown -arguments. In some (rare) cases, this is undesirable. - -Adding 'ignore_extra_args=True' to @signature changes this behavior. - -.. note:: - - If using this option seems to solve your problem, please think twice - before using it! - -Accessing the request -~~~~~~~~~~~~~~~~~~~~~ - -Most of the time direct access to the request object should not be needed, but -in some cases it is. - -On frameworks that propose a global access to the current request it is not an -issue, but on frameworks like pyramid it is not the way to go. - -To handle this use case, WSME has a special type, :class:`HostRequest`: - -.. code-block:: python - - from wsme.types import HostRequest - - @signature(Author, HostRequest, body=Author) - def create_author(request, newauthor): - # ... - return newauthor - -In this example, the request object of the host framework will be passed as the -``request`` parameter of the create_author function. diff --git a/doc/gettingstarted.rst b/doc/gettingstarted.rst deleted file mode 100644 index 7a35600..0000000 --- a/doc/gettingstarted.rst +++ /dev/null @@ -1,15 +0,0 @@ -Getting Started -=============== - -For now here is just a working example. -You can find it in the examples directory of the source distribution. - -.. literalinclude:: ../examples/demo/demo.py - :language: python - -When running this example, the following soap client can interrogate -the web services: - -.. literalinclude:: ../examples/demo/client.py - :language: python - diff --git a/doc/index.rst b/doc/index.rst deleted file mode 100644 index 7819d57..0000000 --- a/doc/index.rst +++ /dev/null @@ -1,27 +0,0 @@ - -.. include:: ../README.rst - -Contents --------- - -.. toctree:: - :maxdepth: 2 - - gettingstarted - api - types - functions - protocols - integrate - document - - todo - changes - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/doc/integrate.rst b/doc/integrate.rst deleted file mode 100644 index 5adaaca..0000000 --- a/doc/integrate.rst +++ /dev/null @@ -1,342 +0,0 @@ -Integrating with a Framework -============================ - -General considerations ----------------------- - -Using WSME within another framework providing its own REST capabilities is -generally done by using a specific decorator to declare the function signature, -in addition to the framework's own way of declaring exposed functions. - -This decorator can have two different names depending on the adapter. - -``@wsexpose`` - This decorator will declare the function signature *and* - take care of calling the adequate decorators of the framework. - - Generally this decorator is provided for frameworks that use - object-dispatch controllers, such as :ref:`adapter-pecan` and - :ref:`adapter-tg1`. - -``@signature`` - This decorator only sets the function signature and returns a function - that can be used by the host framework as a REST request target. - - Generally this decorator is provided for frameworks that expect functions - taking a request object as a single parameter and returning a response - object. This is the case for :ref:`adapter-cornice` and - :ref:`adapter-flask`. - -If you want to enable additional protocols, you will need to -mount a :class:`WSRoot` instance somewhere in the application, generally -``/ws``. This subpath will then handle the additional protocols. In a future -version, a WSGI middleware will probably play this role. - -.. note:: - - Not all the adapters are at the same level of maturity. - -WSGI Application ----------------- - -The :func:`wsme.WSRoot.wsgiapp` function of WSRoot returns a WSGI -application. - -Example -~~~~~~~ - -The following example assumes the REST protocol will be entirely handled by -WSME, which is the case if you write a WSME standalone application. - -.. code-block:: python - - from wsme import WSRoot, expose - - - class MyRoot(WSRoot): - @expose(unicode) - def helloworld(self): - return u"Hello World !" - - root = MyRoot(protocols=['restjson']) - application = root.wsgiapp() - - -.. _adapter-cornice: - -Cornice -------- - -.. _cornice: http://cornice.readthedocs.org/en/latest/ - - *"* Cornice_ *provides helpers to build & document REST-ish Web Services with - Pyramid, with decent default behaviors. It takes care of following the HTTP - specification in an automated way where possible."* - - -:mod:`wsmeext.cornice` -- Cornice adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.cornice - -.. function:: signature - - Declare the parameters of a function and returns a function suitable for - cornice (ie that takes a request and returns a response). - -Configuration -~~~~~~~~~~~~~ - -To use WSME with Cornice you have to add a configuration option to your Pyramid application. - -.. code-block:: python - - from pyramid.config import Configurator - - - def make_app(): - config = Configurator() - config.include("cornice") - config.include("wsmeext.cornice") # This includes WSME cornice support - # ... - return config.make_wsgi_app() - -Example -~~~~~~~ - -.. code-block:: python - - from cornice import Service - from wsmeext.cornice import signature - import wsme.types - - hello = Service(name='hello', path='/', description="Simplest app") - - class Info(wsme.types.Base): - message = wsme.types.text - - - @hello.get() - @signature(Info) - def get_info(): - """Returns Hello in JSON or XML.""" - return Info(message='Hello World') - - - @hello.post() - @signature(None, Info) - def set_info(info): - print("Got a message: %s" % info.message) - - -.. _adapter-flask: - -Flask ------ - - *"Flask is a microframework for Python based on Werkzeug, Jinja 2 and good - intentions. And before you ask: It's BSD licensed! "* - - -.. warning:: - - Flask support is limited to function signature handling. It does not - support additional protocols. This is a temporary limitation, if you have - needs on that matter please tell us at python-wsme@googlegroups.com. - - -:mod:`wsmeext.flask` -- Flask adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.flask - -.. function:: signature(return_type, \*arg_types, \*\*options) - - See @\ :func:`signature` for parameters documentation. - - Can be used on a function before routing it with flask. - -Example -~~~~~~~ - -.. code-block:: python - - from wsmeext.flask import signature - - @app.route('/multiply') - @signature(int, int, int) - def multiply(a, b): - return a * b - -.. _adapter-pecan: - -Pecan ------ - - *"*\ Pecan_ *was created to fill a void in the Python web-framework world – - a very lightweight framework that provides object-dispatch style routing. - Pecan does not aim to be a "full stack" framework, and therefore includes - no out of the box support for things like sessions or databases. Pecan - instead focuses on HTTP itself."* - -.. warning:: - - A pecan application is not able to mount another WSGI application on a - subpath. For that reason, additional protocols are not supported for now, - until WSME provides a middleware that can do the same as a mounted - WSRoot. - -:mod:`wsmeext.pecan` -- Pecan adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.pecan - -.. function:: wsexpose(return_type, \*arg_types, \*\*options) - - See @\ :func:`signature` for parameters documentation. - - Can be used on any function of a pecan - `RestController <http://pecan.readthedocs.org/en/latest/rest.html>`_ - instead of the expose decorator from Pecan. - -Configuration -~~~~~~~~~~~~~ - -WSME can be configured through the application configation, by adding a 'wsme' -configuration entry in ``config.py``: - -.. code-block:: python - - wsme = { - 'debug': True - } - -Valid configuration variables are : - -- ``'debug'``: Whether or not to include exception tracebacks in the returned - server-side errors. - -Example -~~~~~~~ - -The `example <http://pecan.readthedocs.org/en/latest/rest.html#nesting-restcontroller>`_ from the Pecan documentation becomes: - -.. code-block:: python - - from wsmeext.pecan import wsexpose - - class BooksController(RestController): - @wsexpose(Book, int, int) - def get(self, author_id, id): - # .. - - @wsexpose(Book, int, int, body=Book) - def put(self, author_id, id, book): - # .. - - class AuthorsController(RestController): - books = BooksController() - -.. _Pecan: http://pecanpy.org/ - -.. _adapter-tg1: - -Turbogears 1.x --------------- - -The TG adapters have an api very similar to TGWebServices. Migrating from it -should be straightforward (a little howto migrate would not hurt though, and it -will be written as soon as possible). - -:mod:`wsmeext.tg11` -- TG 1.1 adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.tg11 - -.. function:: wsexpose(return_type, \*arg_types, \*\*options) - - See @\ :func:`signature` for parameters documentation. - - Can be used on any function of a controller - instead of the expose decorator from TG. - -.. function:: wsvalidate(\*arg_types) - - Set the argument types of an exposed function. This decorator is provided - so that WSME is an almost drop-in replacement for TGWebServices. If - starting from scratch you can use \ :func:`wsexpose` only - -.. function:: adapt(wsroot) - - Returns a TG1 controller instance that publish a :class:`wsme.WSRoot`. - It can then be mounted on a TG1 controller. - - Because the adapt function modifies the cherrypy filters of the controller - the 'webpath' of the WSRoot instance must be consistent with the path it - will be mounted on. - -:mod:`wsmeext.tg15` -- TG 1.5 adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.tg15 - -This adapter has the exact same api as :mod:`wsmeext.tg11`. - -Example -~~~~~~~ - -In a freshly quickstarted tg1 application (let's say, wsmedemo), you can add -REST-ish functions anywhere in your controller tree. Here directly on the root, -in controllers.py: - -.. code-block:: python - - # ... - - # For tg 1.5, import from wsmeext.tg15 instead : - from wsmeext.tg11 import wsexpose, WSRoot - - class Root(controllers.RootController): - # Having a WSRoot on /ws is only required to enable additional - # protocols. For REST-only services, it can be ignored. - ws = adapt( - WSRoot(webpath='/ws', protocols=['soap']) - ) - - @wsexpose(int, int, int) - def multiply(self, a, b): - return a * b - -.. _TurboGears: http://www.turbogears.org/ - -Other frameworks ----------------- - -Bottle -~~~~~~ - -No adapter is provided yet but it should not be hard to write one, by taking -example on the cornice adapter. - -This example only show how to mount a WSRoot inside a bottle application. - -.. code-block:: python - - import bottle - import wsme - - class MyRoot(wsme.WSRoot): - @wsme.expose(unicode) - def helloworld(self): - return u"Hello World !" - - root = MyRoot(webpath='/ws', protocols=['restjson']) - - bottle.mount('/ws', root.wsgiapp()) - bottle.run() - -Pyramid -~~~~~~~ - -The recommended way of using WSME inside Pyramid is to use -:ref:`adapter-cornice`. diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index a88e5fe..0000000 --- a/doc/make.bat +++ /dev/null @@ -1,155 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^<target^>` where ^<target^> is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\WebServicesMadeEasy.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\WebServicesMadeEasy.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/doc/protocols.rst b/doc/protocols.rst deleted file mode 100644 index a56a49d..0000000 --- a/doc/protocols.rst +++ /dev/null @@ -1,366 +0,0 @@ -Protocols -========= - -In this document the same webservice example will be used to -illustrate the different protocols. Its source code is in the -last chapter (:ref:`protocols-the-example`). - -REST ----- - -.. note:: - - This chapter applies for all adapters, not just the native REST - implementation. - -The two REST protocols share common characterics. - -Each function corresponds to distinct webpath that starts with the -root webpath, followed by the controllers names if any, and finally -the function name. - -The example's exposed functions will be mapped to the following paths: - -- ``/ws/persons/create`` -- ``/ws/persons/get`` -- ``/ws/persons/list`` -- ``/ws/persons/update`` -- ``/ws/persons/destroy`` - -In addition to this trivial function mapping, a `method` option can -be given to the `expose` decorator. In such a case, the function -name can be omitted by the caller, and the dispatch will look at the -HTTP method used in the request to select the correct function. - -The function parameters can be transmitted in two ways (if using -the HTTP method to select the function, one way or the other -may be usable) : - -#. As a GET query string or POST form parameters. - - Simple types are straight forward : - - ``/ws/person/get?id=5`` - - Complex types can be transmitted this way: - - ``/ws/person/update?p.id=1&p.name=Ross&p.hobbies[0]=Dinausaurs&p.hobbies[1]=Rachel`` - -#. In a Json or XML encoded POST body (see below) - -The result will be returned Json or XML encoded (see below). - -In case of error, a 400 or 500 status code is returned, and the -response body contains details about the error (see below). - -REST+Json ---------- - -:name: ``'restjson'`` - -Implements a REST+Json protocol. - -This protocol is selected if: - -- The request content-type is either 'text/javascript' or 'application/json' -- The request 'Accept' header contains 'text/javascript' or 'application/json' -- A trailing '.json' is added to the path -- A 'wsmeproto=restjson' is added in the query string - -Options -~~~~~~~ - -:nest_result: Nest the encoded result in a result param of an object. - For example, a result of ``2`` would be ``{'result': 2}`` - -Types -~~~~~ - -+---------------+-------------------------------+ -| Type | Json type | -+===============+===============================+ -| ``str`` | String | -+---------------+-------------------------------+ -| ``unicode`` | String | -+---------------+-------------------------------+ -| ``int`` | Number | -+---------------+-------------------------------+ -| ``float`` | Number | -+---------------+-------------------------------+ -| ``bool`` | Boolean | -+---------------+-------------------------------+ -| ``Decimal`` | String | -+---------------+-------------------------------+ -| ``date`` | String (YYYY-MM-DD) | -+---------------+-------------------------------+ -| ``time`` | String (hh:mm:ss) | -+---------------+-------------------------------+ -| ``datetime`` | String (YYYY-MM-DDThh:mm:ss) | -+---------------+-------------------------------+ -| Arrays | Array | -+---------------+-------------------------------+ -| None | null | -+---------------+-------------------------------+ -| Complex types | Object | -+---------------+-------------------------------+ - -Return -~~~~~~ - -The Json encoded result when the response code is 200, or a Json object -with error properties ('faulcode', 'faultstring' and 'debuginfo' if -available) on error. - -For example, the '/ws/person/get' result looks like: - -.. code-block:: javascript - - { - 'id': 2 - 'fistname': 'Monica', - 'lastname': 'Geller', - 'age': 28, - 'hobbies': [ - 'Food', - 'Cleaning' - ] - } - -And in case of error: - -.. code-block:: javascript - - { - 'faultcode': 'Client', - 'faultstring': 'id is missing' - } - -REST+XML --------- - -:name: ``'restxml'`` - -This protocol is selected if - -- The request content-type is 'text/xml' -- The request 'Accept' header contains 'text/xml' -- A trailing '.xml' is added to the path -- A 'wsmeproto=restxml' is added in the query string - -Types -~~~~~ - -+---------------+----------------------------------------+ -| Type | XML example | -+===============+========================================+ -| ``str`` | .. code-block:: xml | -| | | -| | <value>a string</value> | -+---------------+----------------------------------------+ -| ``unicode`` | .. code-block:: xml | -| | | -| | <value>a string</value> | -+---------------+----------------------------------------+ -| ``int`` | .. code-block:: xml | -| | | -| | <value>5</value> | -+---------------+----------------------------------------+ -| ``float`` | .. code-block:: xml | -| | | -| | <value>3.14</value> | -+---------------+----------------------------------------+ -| ``bool`` | .. code-block:: xml | -| | | -| | <value>true</value> | -+---------------+----------------------------------------+ -| ``Decimal`` | .. code-block:: xml | -| | | -| | <value>5.46</value> | -+---------------+----------------------------------------+ -| ``date`` | .. code-block:: xml | -| | | -| | <value>2010-04-27</value> | -+---------------+----------------------------------------+ -| ``time`` | .. code-block:: xml | -| | | -| | <value>12:54:18</value> | -+---------------+----------------------------------------+ -| ``datetime`` | .. code-block:: xml | -| | | -| | <value>2010-04-27T12:54:18</value> | -+---------------+----------------------------------------+ -| Arrays | .. code-block:: xml | -| | | -| | <value> | -| | <item>Dinausaurs<item> | -| | <item>Rachel<item> | -| | </value> | -+---------------+----------------------------------------+ -| None | .. code-block:: xml | -| | | -| | <value nil="true"/> | -+---------------+----------------------------------------+ -| Complex types | .. code-block:: xml | -| | | -| | <value> | -| | <id>1</id> | -| | <fistname>Ross</fistname> | -| | </value> | -+---------------+----------------------------------------+ - -Return -~~~~~~ - -A xml tree with a top 'result' element. - -.. code-block:: xml - - <result> - <id>1</id> - <firstname>Ross</firstname> - <lastname>Geller</lastname> - </result> - -Errors -~~~~~~ - -A xml tree with a top 'error' element, having 'faultcode', 'faultstring' -and 'debuginfo' subelements: - -.. code-block:: xml - - <error> - <faultcode>Client</faultcode> - <faultstring>id is missing</faultstring> - </error> - -SOAP ----- - -:name: ``'soap'`` - -Implements the SOAP protocol. - -A wsdl definition of the webservice is available at the 'api.wsdl' subpath. -(``/ws/api.wsdl`` in our example). - -The protocol is selected if the request matches one of the following condition: - -- The Content-Type is 'application/soap+xml' -- A header 'Soapaction' is present - -Options -~~~~~~~ - -:tns: Type namespace - -ExtDirect ---------- - -:name: ``extdirect`` - -Implements the `Ext Direct`_ protocol. - -The provider definition is made available at the ``/extdirect/api.js`` subpath. - -The router url is ``/extdirect/router[/subnamespace]``. - -Options -~~~~~~~ - -:namespace: Base namespace of the api. Used for the provider definition. -:params_notation: Default notation for function call parameters. Can be - overridden 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') - diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index 57dd1d9..0000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -sphinx -cloud_sptheme --r ../requirements.txt diff --git a/doc/todo.rst b/doc/todo.rst deleted file mode 100644 index 21a300b..0000000 --- a/doc/todo.rst +++ /dev/null @@ -1,31 +0,0 @@ -TODO -==== - -WSME is a work in progress. Here is a list of things that should -be done : - -- Use gevents for batch-calls - -- Implement new protocols : - - - json-rpc - - - xml-rpc - -- Implement adapters for other frameworks : - - - TurboGears 2 - - - Pylons - - - CherryPy - - - Flask - - - others ? - -- Add unittests for adapters - -- Address the authentication subject (which should be handled by - some other wsgi framework/middleware, but a little integration - could help). diff --git a/doc/types.rst b/doc/types.rst deleted file mode 100644 index 637a5b7..0000000 --- a/doc/types.rst +++ /dev/null @@ -1,246 +0,0 @@ -Types -===== - -Three kinds of data types can be used as input or output by WSME. - -Native types ------------- - -The native types are a fixed set of standard Python types that -different protocols map to their own basic types. - -The native types are : - - - .. wsme:type:: bytes - - A pure-ascii string (:py:class:`wsme.types.bytes` which is - :py:class:`str` in Python 2 and :py:class:`bytes` in Python 3). - - - - .. wsme:type:: text - - A unicode string (:py:class:`wsme.types.text` which is - :py:class:`unicode` in Python 2 and :py:class:`str` in Python 3). - - - .. wsme:type:: int - - An integer (:py:class:`int`) - - - .. wsme:type:: float - - A float (:py:class:`float`) - - - .. wsme:type:: bool - - A boolean (:py:class:`bool`) - - - .. wsme:type:: Decimal - - A fixed-width decimal (:py:class:`decimal.Decimal`) - - - .. wsme:type:: date - - A date (:py:class:`datetime.date`) - - - .. wsme:type:: datetime - - A date and time (:py:class:`datetime.datetime`) - - - .. wsme:type:: time - - A time (:py:class:`datetime.time`) - - - Arrays -- This is a special case. When stating a list - datatype, always state its content type as the unique element - of a list. Example:: - - class SomeWebService(object): - @expose([str]) - def getlist(self): - return ['a', 'b', 'c'] - - - Dictionaries -- Statically typed mappings are allowed. When exposing - a dictionary datatype, you can specify the key and value types, - with a restriction on the key value that must be a 'pod' type. - Example:: - - class SomeType(object): - amap = {str: SomeOthertype} - -There are other types that are supported out of the box. See the -:ref:`pre-defined-user-types`. - -User types ----------- - -User types allow you to define new, almost-native types. - -The idea is that you may have Python data that should be transported as base -types by the different protocols, but needs conversion to/from these base types, -or needs to validate data integrity. - -To define a user type, you just have to inherit from -:class:`wsme.types.UserType` and instantiate your new class. This instance -will be your new type and can be used as @\ :class:`wsme.expose` or -@\ :class:`wsme.validate` parameters. - -Note that protocols can choose to specifically handle a user type or -a base class of user types. This is case with the two pre-defined -user types, :class:`wsme.types.Enum` and :data:`wsme.types.binary`. - -.. _pre-defined-user-types: - -Pre-defined user types -~~~~~~~~~~~~~~~~~~~~~~ - -WSME provides some pre-defined user types: - -- :class:`binary <wsme.types.binary>` -- for transporting binary data as - base64 strings. -- :class:`Enum <wsme.types.Enum>` -- enforce that the values belongs to a - pre-defined list of values. - -These types are good examples of how to define user types. Have -a look at their source code! - -Here is a little example that combines :class:`binary <wsme.types.binary>` -and :class:`Enum <wsme.types.Enum>`:: - - ImageKind = Enum(str, 'jpeg', 'gif') - - class Image(object): - name = unicode - kind = ImageKind - data = binary - -.. data:: wsme.types.binary - - The :class:`wsme.types.BinaryType` instance to use when you need to - transfer base64 encoded data. - -.. autoclass:: wsme.types.BinaryType - -.. autoclass:: wsme.types.Enum - - -Complex types -------------- - -Complex types are structured types. They are defined as simple Python classes -and will be mapped to adequate structured types in the various protocols. - -A base class for structured types is provided, :class:`wsme.types.Base`, -but is not mandatory. The only thing it adds is a default constructor. - -The attributes that are set at the class level will be used by WSME to discover -the structure. These attributes can be: - - - A datatype -- Any native, user or complex type. - - A :class:`wsattr <wsme.wsattr>` -- This allows you to add more information about - the attribute, for example if it is mandatory. - - A :class:`wsproperty <wsme.wsproperty>` -- A special typed property. Works - like standard ``property`` with additional properties like - :class:`wsattr <wsme.wsattr>`. - -Attributes having a leading '_' in their name will be ignored, as well as the -attributes that are not in the above list. This means the type can have methods, -they will not get in the way. - -Example -~~~~~~~ - -:: - - Gender = wsme.types.Enum(str, 'male', 'female') - Title = wsme.types.Enum(str, 'M', 'Mrs') - - class Person(wsme.types.Base): - lastname = wsme.types.wsattr(unicode, mandatory=True) - firstname = wsme.types.wsattr(unicode, mandatory=True) - - age = int - gender = Gender - title = Title - - hobbies = [unicode] - -Rules -~~~~~ - -A few things you should know about complex types: - - - The class must have a default constructor -- - Since instances of the type will be created by the protocols when - used as input types, they must be instantiable without any argument. - - - Complex types are registered automatically - (and thus inspected) as soon a they are used in expose or validate, - even if they are nested in another complex type. - - If for some reason you need to control when type is inspected, you - can use :func:`wsme.types.register_type`. - - - The datatype attributes will be replaced. - - When using the 'short' way of defining attributes, ie setting a - simple data type, they will be replaced by a wsattr instance. - - So, when you write:: - - class Person(object): - name = unicode - - After type registration the class will actually be equivalent to:: - - class Person(object): - name = wsattr(unicode) - - You can still access the datatype by accessing the attribute on the - class, along with the other wsattr properties:: - - class Person(object): - name = unicode - - register_type(Person) - - assert Person.name.datatype is unicode - assert Person.name.key == "name" - assert Person.name.mandatory is False - - - The default value of instance attributes is - :data:`Unset <wsme.Unset>`. - - :: - - class Person(object): - name = wsattr(unicode) - - p = Person() - assert p.name is Unset - - This allows the protocol to make a clear distinction between null values - that will be transmitted, and unset values that will not be transmitted. - - For input values, it allows the code to know if the values were, or were not, - sent by the caller. - - - When 2 complex types refer to each other, their names can be - used as datatypes to avoid adding attributes afterwards: - - :: - - class A(object): - b = wsattr('B') - - class B(object): - a = wsattr(A) - - -Predefined Types -~~~~~~~~~~~~~~~~ - -.. default-domain:: wsme - -- .. autotype:: wsme.types.File - :members: - diff --git a/examples/demo/client.py b/examples/demo/client.py deleted file mode 100644 index 328f184..0000000 --- a/examples/demo/client.py +++ /dev/null @@ -1,32 +0,0 @@ -from suds.client import Client - -url = 'http://127.0.0.1:8080/ws/api.wsdl' - -client = Client(url, cache=None) - -print client - -print client.service.multiply(4, 5) -print client.service.helloworld() -print client.service.getperson() -p = client.service.listpersons() -print repr(p) -p = client.service.setpersons(p) -print repr(p) - -p = client.factory.create('ns0:Person') -p.id = 4 -print p - -a = client.factory.create('ns0:Person_Array') -print a - -a = client.service.setpersons(a) -print repr(a) - -a.item.append(p) -print repr(a) - -a = client.service.setpersons(a) -print repr(a) - diff --git a/examples/demo/demo.py b/examples/demo/demo.py deleted file mode 100644 index 7adec2a..0000000 --- a/examples/demo/demo.py +++ /dev/null @@ -1,104 +0,0 @@ -# coding=utf8 -""" -A mini-demo of what wsme can do. - -To run it:: - - python setup.py develop - -Then:: - - python demo.py -""" - -from wsme import WSRoot, expose, validate -from wsme.types import File - -import bottle - -from six import u - -import logging - - -class Person(object): - id = int - firstname = unicode - lastname = unicode - - hobbies = [unicode] - - def __repr__(self): - return "Person(%s, %s %s, %s)" % ( - self.id, - self.firstname, self.lastname, - self.hobbies - ) - - -class DemoRoot(WSRoot): - @expose(int) - @validate(int, int) - def multiply(self, a, b): - return a * b - - @expose(File) - @validate(File) - def echofile(self, afile): - return afile - - @expose(unicode) - def helloworld(self): - return u"Здраво, свете (<- Hello World in Serbian !)" - - @expose(Person) - def getperson(self): - p = Person() - p.id = 12 - p.firstname = u'Ross' - p.lastname = u'Geler' - p.hobbies = [] - print p - return p - - @expose([Person]) - def listpersons(self): - p = Person() - p.id = 12 - p.firstname = u('Ross') - p.lastname = u('Geler') - r = [p] - p = Person() - p.id = 13 - p.firstname = u('Rachel') - p.lastname = u('Green') - r.append(p) - print r - return r - - @expose(Person) - @validate(Person) - def setperson(self, person): - return person - - @expose([Person]) - @validate([Person]) - def setpersons(self, persons): - print persons - return persons - - -root = DemoRoot(webpath='/ws') - -root.addprotocol('soap', - tns='http://example.com/demo', - typenamespace='http://example.com/demo/types', - baseURL='http://127.0.0.1:8080/ws/', -) - -root.addprotocol('restjson') - -bottle.mount('/ws/', root.wsgiapp()) - -logging.basicConfig(level=logging.DEBUG) -bottle.run() diff --git a/examples/demo/setup.cfg b/examples/demo/setup.cfg deleted file mode 100644 index e815e43..0000000 --- a/examples/demo/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[easy_install] -find_links = http://www.owlfish.com/software/wsgiutils/download.html diff --git a/examples/demo/setup.py b/examples/demo/setup.py deleted file mode 100644 index 33e5161..0000000 --- a/examples/demo/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from setuptools import setup - -setup(name='demo', - install_requires=[ - 'WSME', - 'Bottle', - 'Pygments', - ], - package=['demo']) diff --git a/examples/demo/sporeclient.py b/examples/demo/sporeclient.py deleted file mode 100644 index d440a93..0000000 --- a/examples/demo/sporeclient.py +++ /dev/null @@ -1,18 +0,0 @@ -import spyre -import spyre.middleware - - -class CTypeHeader(spyre.middleware.Middleware): - def __call__(self, env): - env.setdefault('spore.headers', []) - env['spore.headers'].extend([ - ('Accept', 'application/json'), - ('Content-Type', 'application/json') - ]) - - -demo = spyre.new_from_url('http://127.0.0.1:8080/ws/api.spore') -demo.enable(CTypeHeader) -demo.enable('format.Json') - -print demo.helloworld().content diff --git a/requirements-py3.txt b/requirements-py3.txt deleted file mode 100644 index d15bd16..0000000 --- a/requirements-py3.txt +++ /dev/null @@ -1,5 +0,0 @@ -six>=1.9.0 -WebOb>=1.2.3 -simplegeneric -pytz -netaddr>=0.7.12 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d15bd16..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -six>=1.9.0 -WebOb>=1.2.3 -simplegeneric -pytz -netaddr>=0.7.12 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 78237d5..0000000 --- a/setup.cfg +++ /dev/null @@ -1,54 +0,0 @@ -[metadata] -name = WSME - -author = Christophe de Vienne -author-email = python-wsme@googlegroups.com - -summary = Simplify the writing of REST APIs, and extend them with additional protocols. - -description-file = README.rst - -url = http://git.openstack.org/cgit/openstack/wsme - -license = MIT - -classifier = - Development Status :: 3 - Alpha - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: Implementation :: CPython - Programming Language :: Python :: Implementation :: PyPy - License :: OSI Approved :: MIT License - Topic :: Internet :: WWW/HTTP :: WSGI - Topic :: Software Development :: Libraries :: Python Modules - -[entry_points] -wsme.protocols = - rest = wsme.rest.protocol:RestProtocol - restjson = wsme.rest.protocol:RestProtocol - restxml = wsme.rest.protocol:RestProtocol - soap = wsmeext.soap:SoapProtocol - extdirect = wsmeext.extdirect:ExtDirectProtocol - -[files] -packages = - wsme - wsmeext - -namespace_packages = - wsmeext - -extra_files = - setup.py - README.rst - tests - -[wheel] -# WSME has different requirements depending on the version of Python -# being used, so we cannot build universal wheels. -universal = 0 diff --git a/setup.py b/setup.py deleted file mode 100644 index a92d122..0000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -import setuptools - -setuptools.setup( - setup_requires=['pbr'], - pbr=True -) diff --git a/tests/pecantest/setup.cfg b/tests/pecantest/setup.cfg deleted file mode 100644 index 00ca220..0000000 --- a/tests/pecantest/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[nosetests] -match=^test -where=test -nocapture=1 -cover-package=test -cover-erase=1 diff --git a/tests/pecantest/setup.py b/tests/pecantest/setup.py deleted file mode 100644 index b901f62..0000000 --- a/tests/pecantest/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -try: - from setuptools import setup, find_packages -except ImportError: - from ez_setup import use_setuptools - use_setuptools() - from setuptools import setup, find_packages - -setup( - name = 'test', - version = '0.1', - description = '', - author = '', - author_email = '', - install_requires = [ - "pecan", - ], - test_suite = 'test', - zip_safe = False, - include_package_data = True, - packages = find_packages(exclude=['ez_setup']) -) diff --git a/tests/pecantest/test/__init__.py b/tests/pecantest/test/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/tests/pecantest/test/__init__.py +++ /dev/null diff --git a/tests/pecantest/test/app.py b/tests/pecantest/test/app.py deleted file mode 100644 index 727c357..0000000 --- a/tests/pecantest/test/app.py +++ /dev/null @@ -1,15 +0,0 @@ -from pecan import make_app -from test import model - -def setup_app(config): - - model.init_model() - - return make_app( - config.app.root, - static_root = config.app.static_root, - template_path = config.app.template_path, - logging = getattr(config, 'logging', {}), - debug = getattr(config.app, 'debug', False), - force_canonical = getattr(config.app, 'force_canonical', True) - ) diff --git a/tests/pecantest/test/controllers/__init__.py b/tests/pecantest/test/controllers/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/tests/pecantest/test/controllers/__init__.py +++ /dev/null diff --git a/tests/pecantest/test/controllers/root.py b/tests/pecantest/test/controllers/root.py deleted file mode 100644 index 6e87267..0000000 --- a/tests/pecantest/test/controllers/root.py +++ /dev/null @@ -1,21 +0,0 @@ -from pecan import expose -from webob.exc import status_map -from .ws import AuthorsController -from wsmeext.pecan import wsexpose - - -class RootController(object): - authors = AuthorsController() - - @expose('error.html') - def error(self, status): - try: - status = int(status) - except ValueError: # pragma: no cover - status = 500 - message = getattr(status_map.get(status), 'explanation', '') - return dict(status=status, message=message) - - @wsexpose() - def divide_by_zero(self): - 1 / 0 diff --git a/tests/pecantest/test/controllers/ws.py b/tests/pecantest/test/controllers/ws.py deleted file mode 100644 index b35a0c6..0000000 --- a/tests/pecantest/test/controllers/ws.py +++ /dev/null @@ -1,150 +0,0 @@ -# encoding=utf8 -from pecan.rest import RestController - -from wsme.types import Base, text, wsattr - -import wsme -import wsmeext.pecan - -import six - - -class Author(Base): - id = int - firstname = text - books = wsattr(['Book']) - - @staticmethod - def validate(author): - if author.firstname == 'Robert': - raise wsme.exc.ClientSideError("I don't like this author!") - return author - - -class Book(Base): - id = int - name = text - author = wsattr('Author') - - -class BookNotFound(Exception): - message = "Book with ID={id} Not Found" - code = 404 - - def __init__(self, id): - message = self.message.format(id=id) - super(BookNotFound, self).__init__(message) - - -class NonHttpException(Exception): - message = "Internal Exception for Book ID={id}" - code = 684 - - def __init__(self, id): - message = self.message.format(id=id) - super(NonHttpException, self).__init__(message) - - -class BooksController(RestController): - - @wsmeext.pecan.wsexpose(Book, int, int) - def get(self, author_id, id): - book = Book( - name=u"Les Confessions 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=[])] diff --git a/tests/pecantest/test/model/__init__.py b/tests/pecantest/test/model/__init__.py deleted file mode 100644 index ab4be6a..0000000 --- a/tests/pecantest/test/model/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -def init_model(): - pass diff --git a/tests/pecantest/test/tests/__init__.py b/tests/pecantest/test/tests/__init__.py deleted file mode 100644 index 04fa0a6..0000000 --- a/tests/pecantest/test/tests/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from unittest import TestCase -from pecan import set_config -from pecan import testing - -__all__ = ['FunctionalTest'] - - -class FunctionalTest(TestCase): - """ - Used for functional tests where you need to test your - literal application and its integration with the framework. - """ - - def setUp(self): - self.app = testing.load_test_app(os.path.join( - os.path.dirname(__file__), - 'config.py' - )) - - def tearDown(self): - set_config({}, overwrite=True) diff --git a/tests/pecantest/test/tests/config.py b/tests/pecantest/test/tests/config.py deleted file mode 100644 index 3f35dcf..0000000 --- a/tests/pecantest/test/tests/config.py +++ /dev/null @@ -1,24 +0,0 @@ -# Server Specific Configurations -server = { - 'port' : '8080', - 'host' : '0.0.0.0' -} - -# Pecan Application Configurations -app = { - 'root' : 'test.controllers.root.RootController', - 'modules' : ['test'], - 'static_root' : '%(confdir)s/../../public', - 'template_path' : '%(confdir)s/../templates', - 'errors' : { - '404' : '/error/404', - '__force_dict__' : True - } -} - -# Custom Configurations must be in Python dictionary format:: -# -# foo = {'bar':'baz'} -# -# All configurations are accessible at:: -# pecan.conf diff --git a/tests/pecantest/test/tests/test_ws.py b/tests/pecantest/test/tests/test_ws.py deleted file mode 100644 index ae06650..0000000 --- a/tests/pecantest/test/tests/test_ws.py +++ /dev/null @@ -1,247 +0,0 @@ -from six.moves import http_client -from test.tests import FunctionalTest -import json -import pecan -import six - - -used_status_codes = [400, 401, 403, 404, 500] -http_response_messages = {} -for code in used_status_codes: - http_response_messages[code] = '%s %s' % (code, http_client.responses[code]) - -class TestWS(FunctionalTest): - - def test_get_all(self): - self.app.get('/authors') - - def test_optional_array_param(self): - r = self.app.get('/authors?q=a&q=b') - l = json.loads(r.body.decode('utf-8')) - assert len(l) == 2 - assert l[0]['firstname'] == 'a' - assert l[1]['firstname'] == 'b' - - def test_optional_indexed_array_param(self): - r = self.app.get('/authors?q[0]=a&q[1]=b') - l = json.loads(r.body.decode('utf-8')) - assert len(l) == 2 - assert l[0]['firstname'] == 'a' - assert l[1]['firstname'] == 'b' - - def test_options_object_array_param(self): - r = self.app.get('/authors?r.value=a&r.value=b') - l = json.loads(r.body.decode('utf-8')) - assert len(l) == 2 - assert l[0]['firstname'] == 'a' - assert l[1]['firstname'] == 'b' - - def test_options_indexed_object_array_param(self): - r = self.app.get('/authors?r[0].value=a&r[1].value=b') - l = json.loads(r.body.decode('utf-8')) - assert len(l) == 2 - assert l[0]['firstname'] == 'a' - assert l[1]['firstname'] == 'b' - - def test_get_author(self): - a = self.app.get( - '/authors/1.json', - ) - a = json.loads(a.body.decode('utf-8')) - - assert a['id'] == 1 - assert a['firstname'] == 'aname' - - a = self.app.get( - '/authors/1.xml', - ) - body = a.body.decode('utf-8') - assert '<id>1</id>' in body - assert '<firstname>aname</firstname>' in body - - def test_post_body_parameter_validation(self): - res = self.app.post( - '/authors', '{"firstname": "Robert"}', - headers={"Content-Type": "application/json"}, - expect_errors=True - ) - self.assertEqual(res.status_int, 400) - a = json.loads(res.body.decode('utf-8')) - self.assertEqual(a['faultcode'], 'Client') - self.assertEqual(a['faultstring'], "I don't like this author!") - - def test_post_body_parameter(self): - res = self.app.post( - '/authors', '{"firstname": "test"}', - headers={"Content-Type": "application/json"} - ) - assert res.status_int == 201 - a = json.loads(res.body.decode('utf-8')) - assert a['id'] == 10 - assert a['firstname'] == 'test' - - def test_put_parameter_validate(self): - res = self.app.put( - '/authors/foobar', '{"firstname": "test"}', - headers={"Content-Type": "application/json"}, - expect_errors=True - ) - self.assertEqual(res.status_int, 400) - a = json.loads(res.body.decode('utf-8')) - self.assertEqual( - a['faultstring'], - "Invalid input for field/attribute author_id. " - "Value: 'foobar'. unable to convert to int. Error: invalid " - "literal for int() with base 10: 'foobar'") - - def test_clientsideerror(self): - expected_status_code = 400 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/999.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Client' - - res = self.app.get( - '/authors/999.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8') - - def test_custom_clientside_error(self): - expected_status_code = 404 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/998.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Client' - - res = self.app.get( - '/authors/998.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8') - - def test_custom_non_http_clientside_error(self): - expected_status_code = 500 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/997.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Server' - - res = self.app.get( - '/authors/997.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8') - - def test_clientsideerror_status_code(self): - expected_status_code = 403 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/996.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Client' - - res = self.app.get( - '/authors/996.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8') - - def test_non_default_response(self): - expected_status_code = 401 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/911.json', - expect_errors=True - ) - self.assertEqual(res.status_int, expected_status_code) - self.assertEqual(res.status, expected_status) - - def test_non_default_response_return_type(self): - res = self.app.get( - '/authors/913', - ) - self.assertEqual(res.status_int, 200) - self.assertEqual(res.body, b'"foo"') - self.assertEqual(res.content_length, 5) - - def test_non_default_response_return_type_no_content(self): - res = self.app.get( - '/authors/912', - ) - self.assertEqual(res.status_int, 204) - self.assertEqual(res.body, b'') - self.assertEqual(res.content_length, 0) - - def test_serversideerror(self): - expected_status_code = 500 - expected_status = http_response_messages[expected_status_code] - res = self.app.get('/divide_by_zero.json', expect_errors=True) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Server' - assert a['debuginfo'] is None - - def test_serversideerror_with_debug(self): - expected_status_code = 500 - expected_status = http_response_messages[expected_status_code] - pecan.set_config({'wsme': {'debug': True}}) - res = self.app.get('/divide_by_zero.json', expect_errors=True) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Server' - assert a['debuginfo'].startswith('Traceback (most recent call last):') - - def test_json_only(self): - res = self.app.get('/authors/json_only.json') - assert res.status_int == 200 - body = json.loads(res.body.decode('utf-8')) - assert len(body) == 1 - assert body[0]['firstname'] == u"aname" - assert body[0]['books'] == [] - assert body[0]['id'] == 1 - res = self.app.get('/authors/json_only.xml', expect_errors=True) - - def test_xml_only(self): - res = self.app.get('/authors/xml_only.xml') - assert res.status_int == 200 - assert '<id>1</id>' in res.body.decode('utf-8') - assert '<firstname>aname</firstname>' in res.body.decode('utf-8') - assert '<books />' in res.body.decode('utf-8') - res = self.app.get('/authors/xml_only.json', expect_errors=True) - - def test_body_parameter(self): - res = self.app.put( - '/authors/1/books/2.json', - '{"name": "Alice au pays des merveilles"}', - headers={"Content-Type": "application/json"} - ) - book = json.loads(res.body.decode('utf-8')) - assert book['id'] == 2 - assert book['author']['id'] == 1 - - def test_no_content_type_if_no_return_type(self): - if six.PY3: - self.skipTest( - "This test does not work in Python 3 until https://review.openstack.org/#/c/48439/ is merged") - res = self.app.delete('/authors/4') - assert "Content-Type" not in res.headers, res.headers['Content-Type'] diff --git a/tests/rest/test_args.py b/tests/rest/test_args.py deleted file mode 100644 index 4ae3246..0000000 --- a/tests/rest/test_args.py +++ /dev/null @@ -1,20 +0,0 @@ -import mock -import unittest - -from wsme import exc -from wsme.rest import args -from wsme.rest import json - - -class TestArgs(unittest.TestCase): - - def test_args_from_body(self): - - funcdef = mock.MagicMock() - body = mock.MagicMock() - mimetype = "application/json" - funcdef.ignore_extra_args = True - json.parse = mock.MagicMock() - json.parse.side_effect = (exc.UnknownArgument("")) - resp = args.args_from_body(funcdef, body, mimetype) - self.assertEqual(resp, ((), {})) diff --git a/tests/sphinxexample/conf.py b/tests/sphinxexample/conf.py deleted file mode 100644 index ec478dd..0000000 --- a/tests/sphinxexample/conf.py +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Web Services Made Easy documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 2 20:27:45 2011. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'wsmeext.sphinxext'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'wsmeext.sphinxext Test' -copyright = u'2011, Christophe de Vienne' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -import pkg_resources -dist = pkg_resources.require('WSME')[0] - -# The short X.Y version. -version = '.'.join(dist.version[:2]) -# The full version, including alpha/beta/rc tags. -release = dist.version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'agogo' -html_theme_options = { - "pagewidth": "60em", - "documentwidth": "40em", -} - -html_style = 'wsme.css' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# "<project> v<release> documentation". -html_title = "WSME %s" % release - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a <link> tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'WebServicesMadeEasydoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation', - u'Christophe de Vienne', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation', - [u'Christophe de Vienne'], 1) -] - - -autodoc_member_order = 'bysource' - -wsme_protocols = [ - 'restjson', 'restxml' -] diff --git a/tests/sphinxexample/document.rst b/tests/sphinxexample/document.rst deleted file mode 100644 index 41af798..0000000 --- a/tests/sphinxexample/document.rst +++ /dev/null @@ -1,43 +0,0 @@ -API Documentation test -====================== - -Example -~~~~~~~ - -.. wsme:root:: wsmeext.sphinxext.SampleService - :webpath: /api - -.. wsme:type:: MyType - - .. wsme:attribute:: test - - :type: int - -.. wsme:service:: name/space/SampleService - - .. wsme:function:: getType - - Returns a :wsme:type:`MyType <MyType>` - - -.. default-domain:: wsme - -.. type:: int - - An integer - -.. autotype:: wsmeext.sphinxext.SampleType - :members: - -.. autoservice:: wsmeext.sphinxext.SampleService - :members: - - -.. autotype:: test_sphinxext.ASampleType - :members: - -.. autotype:: wsme.types.bytes - -.. autotype:: wsme.types.text - -.. _Sphinx: http://sphinx.pocoo.org/ diff --git a/tests/sphinxexample/index.rst b/tests/sphinxexample/index.rst deleted file mode 100644 index 76d9c55..0000000 --- a/tests/sphinxexample/index.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. toctree:: - - document diff --git a/tests/test_cornice.py b/tests/test_cornice.py deleted file mode 100644 index 77a55e0..0000000 --- a/tests/test_cornice.py +++ /dev/null @@ -1,183 +0,0 @@ -import unittest -import json - -import webtest - -from cornice import Service -from cornice import resource -from pyramid.config import Configurator -from pyramid.httpexceptions import HTTPUnauthorized - -from wsme.types import text, Base, HostRequest -from wsmeext.cornice import signature - - -class User(Base): - id = int - name = text - -users = Service(name='users', path='/users') - - -@users.get() -@signature([User]) -def users_get(): - return [User(id=1, name='first')] - - -@users.post() -@signature(User, body=User) -def users_create(data): - data.id = 2 - return data - - -secret = Service(name='secrets', path='/secret') - - -@secret.get() -@signature() -def secret_get(): - raise HTTPUnauthorized() - - -divide = Service(name='divide', path='/divide') - - -@divide.get() -@signature(int, int, int) -def do_divide(a, b): - return a / b - -needrequest = Service(name='needrequest', path='/needrequest') - - -@needrequest.get() -@signature(bool, HostRequest) -def needrequest_get(request): - assert request.path == '/needrequest', request.path - return True - - -class Author(Base): - authorId = int - name = text - - -@resource.resource(collection_path='/author', path='/author/{authorId}') -class AuthorResource(object): - def __init__(self, request): - self.request = request - - @signature(Author, int) - def get(self, authorId): - return Author(authorId=authorId, name="Author %s" % authorId) - - @signature(Author, int, body=Author) - def post(self, authorId, data): - data.authorId = authorId - return data - - @signature([Author], text) - def collection_get(self, where=None): - return [ - Author(authorId=1, name="Author 1"), - Author(authorId=2, name="Author 2"), - Author(authorId=3, name="Author 3") - ] - - -def make_app(): - config = Configurator() - config.include("cornice") - config.include("wsmeext.cornice") - config.scan("test_cornice") - return config.make_wsgi_app() - - -class WSMECorniceTestCase(unittest.TestCase): - def setUp(self): - self.app = webtest.TestApp(make_app()) - - def test_get_json_list(self): - resp = self.app.get('/users') - self.assertEqual( - resp.body, - b'[{"id": 1, "name": "first"}]' - ) - - def test_get_xml_list(self): - resp = self.app.get('/users', headers={"Accept": "text/xml"}) - self.assertEqual( - resp.body, - b'<result><item><id>1</id><name>first</name></item></result>' - ) - - def test_post_json_data(self): - data = json.dumps({"name": "new"}) - resp = self.app.post( - '/users', data, - headers={"Content-Type": "application/json"} - ) - self.assertEqual( - resp.body, - b'{"id": 2, "name": "new"}' - ) - - def test_post_xml_data(self): - data = '<data><name>new</name></data>' - resp = self.app.post( - '/users', data, - headers={"Content-Type": "text/xml"} - ) - self.assertEqual( - resp.body, - b'<result><id>2</id><name>new</name></result>' - ) - - def test_pass_request(self): - resp = self.app.get('/needrequest') - assert resp.json is True - - def test_resource_collection_get(self): - resp = self.app.get('/author') - assert len(resp.json) == 3 - assert resp.json[0]['name'] == 'Author 1' - assert resp.json[1]['name'] == 'Author 2' - assert resp.json[2]['name'] == 'Author 3' - - def test_resource_get(self): - resp = self.app.get('/author/5') - assert resp.json['name'] == 'Author 5' - - def test_resource_post(self): - resp = self.app.post( - '/author/5', - json.dumps({"name": "Author 5"}), - headers={"Content-Type": "application/json"} - ) - assert resp.json['authorId'] == 5 - assert resp.json['name'] == 'Author 5' - - def test_server_error(self): - resp = self.app.get('/divide?a=1&b=0', expect_errors=True) - self.assertEqual(resp.json['faultcode'], 'Server') - self.assertEqual(resp.status_code, 500) - - def test_client_error(self): - resp = self.app.get( - '/divide?a=1&c=0', - headers={'Accept': 'application/json'}, - expect_errors=True - ) - self.assertEqual(resp.json['faultcode'], 'Client') - self.assertEqual(resp.status_code, 400) - - def test_runtime_error(self): - resp = self.app.get( - '/secret', - headers={'Accept': 'application/json'}, - expect_errors=True - ) - self.assertEqual(resp.json['faultcode'], 'Client') - self.assertEqual(resp.status_code, 401) diff --git a/tests/test_flask.py b/tests/test_flask.py deleted file mode 100644 index f039f45..0000000 --- a/tests/test_flask.py +++ /dev/null @@ -1,216 +0,0 @@ -# encoding=utf8 -import unittest -from flask import Flask, json, abort -import flask_restful as restful - -from wsmeext.flask import signature -from wsme.api import Response -from wsme.types import Base, text - - -class Model(Base): - id = int - name = text - - -class Criterion(Base): - op = text - attr = text - value = text - -test_app = Flask(__name__) -api = restful.Api(test_app) - - -@test_app.route('/multiply') -@signature(int, int, int) -def multiply(a, b): - return a * b - - -@test_app.route('/divide_by_zero') -@signature(None) -def divide_by_zero(): - return 1 / 0 - - -@test_app.route('/models') -@signature([Model], [Criterion]) -def list_models(q=None): - if q: - name = q[0].value - else: - name = 'first' - return [Model(name=name)] - - -@test_app.route('/models/<name>') -@signature(Model, text) -def get_model(name): - return Model(name=name) - - -@test_app.route('/models/<name>/secret') -@signature(Model, text) -def model_secret(name): - abort(403) - - -@test_app.route('/models/<name>/custom-error') -@signature(Model, text) -def model_custom_error(name): - class CustomError(Exception): - code = 412 - raise CustomError("FOO!") - - -@test_app.route('/models', methods=['POST']) -@signature(Model, body=Model) -def post_model(body): - return Model(name=body.name) - - -@test_app.route('/status_sig') -@signature(int, status_code=201) -def get_status_sig(): - return 1 - - -@test_app.route('/status_response') -@signature(int) -def get_status_response(): - return Response(1, status_code=201) - - -class RestFullApi(restful.Resource): - @signature(Model) - def get(self): - return Model(id=1, name=u"Gérard") - - @signature(int, body=Model) - def post(self, model): - return model.id - -api.add_resource(RestFullApi, '/restful') - - -class FlaskrTestCase(unittest.TestCase): - - def setUp(self): - test_app.config['TESTING'] = True - self.app = test_app.test_client() - - def tearDown(self): - pass - - def test_multiply(self): - r = self.app.get('/multiply?a=2&b=5') - assert r.data == b'10', r.data - - def test_get_model(self): - resp = self.app.get('/models/test') - assert resp.status_code == 200 - - def test_list_models(self): - resp = self.app.get('/models') - assert resp.status_code == 200 - - def test_array_parameter(self): - resp = self.app.get('/models?q.op=%3D&q.attr=name&q.value=second') - assert resp.status_code == 200 - self.assertEqual( - resp.data, b'[{"name": "second"}]' - ) - - def test_post_model(self): - resp = self.app.post('/models', data={"body.name": "test"}) - assert resp.status_code == 200 - resp = self.app.post( - '/models', - data=json.dumps({"name": "test"}), - content_type="application/json" - ) - assert resp.status_code == 200 - - def test_get_status_sig(self): - resp = self.app.get('/status_sig') - assert resp.status_code == 201 - - def test_get_status_response(self): - resp = self.app.get('/status_response') - assert resp.status_code == 201 - - def test_custom_clientside_error(self): - r = self.app.get( - '/models/test/secret', - headers={'Accept': 'application/json'} - ) - assert r.status_code == 403, r.status_code - assert '403 Forbidden:' in json.loads(r.data)['faultstring'] - - r = self.app.get( - '/models/test/secret', - headers={'Accept': 'application/xml'} - ) - assert r.status_code == 403, r.status_code - assert r.data == (b"<error><faultcode>Client</faultcode>" - b"<faultstring>403 Forbidden: You don't have the " - b"permission to access the requested resource. It " - b"is either read-protected or not readable by the " - b"server." - b"</faultstring><debuginfo /></error>") - - # NOTE(cdent): For reasons unclear, 'r' here has no value on data - # even though it does earlier in the stack. If works with Werkzeug - # <0.14.0 but not after. As WSME does not have test-requirement, nor - # pinning, so not a lot to do here. - @unittest.expectedFailure - def test_custom_non_http_clientside_error(self): - r = self.app.get( - '/models/test/custom-error', - headers={'Accept': 'application/json'} - ) - assert r.status_code == 412, r.status_code - assert json.loads(r.data)['faultstring'] == 'FOO!' - - r = self.app.get( - '/models/test/custom-error', - headers={'Accept': 'application/xml'} - ) - assert r.status_code == 412, r.status_code - assert r.data == (b'<error><faultcode>Client</faultcode>' - b'<faultstring>FOO!</faultstring>' - b'<debuginfo /></error>') - - def test_serversideerror(self): - r = self.app.get('/divide_by_zero') - assert r.status_code == 500 - data = json.loads(r.data) - self.assertEqual(data['debuginfo'], None) - self.assertEqual(data['faultcode'], 'Server') - self.assertIn('by zero', data['faultstring']) - - def test_restful_get(self): - r = self.app.get('/restful', headers={'Accept': 'application/json'}) - self.assertEqual(r.status_code, 200) - - data = json.loads(r.data) - - self.assertEqual(data['id'], 1) - self.assertEqual(data['name'], u"Gérard") - - def test_restful_post(self): - r = self.app.post( - '/restful', - data=json.dumps({'id': 5, 'name': u'Huguette'}), - headers={ - 'Accept': 'application/json', - 'Content-Type': 'application/json'}) - self.assertEqual(r.status_code, 200) - - data = json.loads(r.data) - - self.assertEqual(data, 5) - -if __name__ == '__main__': - test_app.run() diff --git a/tests/test_sphinxext.py b/tests/test_sphinxext.py deleted file mode 100644 index 78f80dd..0000000 --- a/tests/test_sphinxext.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest -import sphinx -import os.path - -import wsme.types -from wsmeext import sphinxext - -docpath = os.path.join( - os.path.dirname(__file__), - 'sphinxexample') - - -class ASampleType(object): - somebytes = wsme.types.bytes - sometext = wsme.types.text - someint = int - - -class TestSphinxExt(unittest.TestCase): - def test_buildhtml(self): - if not os.path.exists('.test_sphinxext/'): - os.makedirs('.test_sphinxext/') - try: - sphinx.main([ - '', - '-b', 'html', - '-d', '.test_sphinxext/doctree', - docpath, - '.test_sphinxext/html' - ]) - assert Exception("Should raise SystemExit 0") - except SystemExit as e: - assert e.code == 0 - - -class TestDataTypeName(unittest.TestCase): - def test_user_type(self): - self.assertEqual(sphinxext.datatypename(ASampleType), - 'ASampleType') - - def test_dict_type(self): - d = wsme.types.DictType(str, str) - self.assertEqual(sphinxext.datatypename(d), 'dict(str: str)') - d = wsme.types.DictType(str, ASampleType) - self.assertEqual(sphinxext.datatypename(d), 'dict(str: ASampleType)') - - def test_array_type(self): - d = wsme.types.ArrayType(str) - self.assertEqual(sphinxext.datatypename(d), 'list(str)') - d = wsme.types.ArrayType(ASampleType) - self.assertEqual(sphinxext.datatypename(d), 'list(ASampleType)') diff --git a/tests/test_tg1.py b/tests/test_tg1.py deleted file mode 100644 index 3a61827..0000000 --- a/tests/test_tg1.py +++ /dev/null @@ -1,196 +0,0 @@ -import wsmeext.tg11 -from wsme import WSRoot -from wsmeext.tg11 import wsexpose, wsvalidate -import wsmeext.tg1 - -from turbogears.controllers import RootController -import cherrypy - -import unittest - -import simplejson - - -from wsmeext.tests import test_soap - - -class WSController(WSRoot): - pass - - -class Subcontroller(object): - @wsexpose(int, int, int) - def add(self, a, b): - return a + b - - -class Root(RootController): - class UselessSubClass: - # This class is here only to make sure wsmeext.tg1.scan_api - # does its job properly - pass - - ws = WSController(webpath='/ws') - ws.addprotocol( - 'soap', - tns=test_soap.tns, - typenamespace=test_soap.typenamespace, - baseURL='/ws/' - ) - ws = wsmeext.tg11.adapt(ws) - - @wsexpose(int) - @wsvalidate(int, int) - def multiply(self, a, b): - return a * b - - @wsexpose(int) - @wsvalidate(int, int) - def divide(self, a, b): - if b == 0: - raise cherrypy.HTTPError(400, 'Cannot divide by zero!') - return a / b - - sub = Subcontroller() - -from turbogears import testutil, config, startup - - -class TestController(unittest.TestCase): - root = Root - - def setUp(self): - "Tests the output of the index method" - self.app = testutil.make_app(self.root) - testutil.start_server() - - def tearDown(self): - # implementation copied from turbogears.testutil.stop_server. - # The only change is that cherrypy.root is set to None - # AFTER stopTurbogears has been called so that wsmeext.tg11 - # can correctly uninstall its filter. - if config.get("cp_started"): - cherrypy.server.stop() - config.update({"cp_started": False}) - - if config.get("server_started"): - startup.stopTurboGears() - config.update({"server_started": False}) - - def test_restcall(self): - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json'} - ) - print response - assert simplejson.loads(response.body) == 50 - - response = self.app.post("/sub/add", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json'} - ) - print response - assert simplejson.loads(response.body) == 15 - - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json', 'Accept': 'application/json'} - ) - print response - assert simplejson.loads(response.body) == 50 - - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json', 'Accept': 'text/javascript'} - ) - print response - assert simplejson.loads(response.body) == 50 - - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json', - 'Accept': 'text/xml'} - ) - print response - assert response.body == "<result>50</result>" - - def test_custom_clientside_error(self): - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'application/json'}, - expect_errors=True - ) - assert response.status_int == 400 - assert simplejson.loads(response.body) == { - "debuginfo": None, - "faultcode": "Server", - "faultstring": "(400, 'Cannot divide by zero!')" - } - - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'text/xml'}, - expect_errors=True - ) - assert response.status_int == 400 - assert response.body == ("<error><faultcode>Server</faultcode>" - "<faultstring>(400, 'Cannot divide by zero!')" - "</faultstring><debuginfo /></error>") - - def test_soap_wsdl(self): - ts = test_soap.TestSOAP('test_wsdl') - ts.app = self.app - ts.ws_path = '/ws/' - ts.run() - #wsdl = self.app.get('/ws/api.wsdl').body - #print wsdl - #assert 'multiply' in wsdl - - def test_soap_call(self): - ts = test_soap.TestSOAP('test_wsdl') - ts.app = self.app - ts.ws_path = '/ws/' - - print ts.ws_path - assert ts.call('multiply', a=5, b=10, _rt=int) == 50 - - def test_scan_api_loops(self): - class MyRoot(object): - pass - - MyRoot.loop = MyRoot() - - root = MyRoot() - - api = list(wsmeext.tg1._scan_api(root)) - print(api) - - self.assertEqual(len(api), 0) - - def test_scan_api_maxlen(self): - class ARoot(object): - pass - - def make_subcontrollers(n): - c = type('Controller%s' % n, (object,), {}) - return c - - c = ARoot - for n in range(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') - ) diff --git a/tests/test_tg15.py b/tests/test_tg15.py deleted file mode 100644 index 91609d2..0000000 --- a/tests/test_tg15.py +++ /dev/null @@ -1,177 +0,0 @@ -import wsmeext.tg15 -from wsme import WSRoot - -from turbogears.controllers import RootController -import cherrypy - -from wsmeext.tests import test_soap - -import simplejson - - -class Subcontroller(object): - @wsmeext.tg15.wsexpose(int, int, int) - def add(self, a, b): - return a + b - - -class Root(RootController): - class UselessSubClass: - # This class is here only to make sure wsmeext.tg1.scan_api - # does its job properly - pass - - sub = Subcontroller() - - ws = WSRoot(webpath='/ws') - ws.addprotocol('soap', - tns=test_soap.tns, - typenamespace=test_soap.typenamespace, - baseURL='/ws/' - ) - ws = wsmeext.tg15.adapt(ws) - - @wsmeext.tg15.wsexpose(int) - @wsmeext.tg15.wsvalidate(int, int) - def multiply(self, a, b): - return a * b - - @wsmeext.tg15.wsexpose(int) - @wsmeext.tg15.wsvalidate(int, int) - def divide(self, a, b): - if b == 0: - raise cherrypy.HTTPError(400, 'Cannot divide by zero!') - return a / b - - -from turbogears import testutil - - -class TestController(testutil.TGTest): - root = Root - -# def setUp(self): -# "Tests the output of the index method" -# self.app = testutil.make_app(self.root) -# #print cherrypy.root -# testutil.start_server() - -# def tearDown(self): -# # implementation copied from turbogears.testutil.stop_server. -# # The only change is that cherrypy.root is set to None -# # AFTER stopTurbogears has been called so that wsmeext.tg15 -# # can correctly uninstall its filter. -# if config.get("cp_started"): -# cherrypy.server.stop() -# config.update({"cp_started": False}) -# -# if config.get("server_started"): -# startup.stopTurboGears() -# config.update({"server_started": False}) - - def test_restcall(self): - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json'} - ) - print response - assert simplejson.loads(response.body) == 50 - - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json', 'Accept': 'application/json'} - ) - print response - assert simplejson.loads(response.body) == 50 - - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json', 'Accept': 'text/javascript'} - ) - print response - assert simplejson.loads(response.body) == 50 - - response = self.app.post("/multiply", - simplejson.dumps({'a': 5, 'b': 10}), - {'Content-Type': 'application/json', - 'Accept': 'text/xml'} - ) - print response - assert response.body == "<result>50</result>" - - def test_custom_clientside_error(self): - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'application/json'}, - expect_errors=True - ) - assert response.status_int == 400 - assert simplejson.loads(response.body) == { - "debuginfo": None, - "faultcode": "Client", - "faultstring": "(400, 'Cannot divide by zero!')" - } - - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'text/xml'}, - expect_errors=True - ) - assert response.status_int == 400 - assert response.body == ("<error><faultcode>Client</faultcode>" - "<faultstring>(400, 'Cannot divide by zero!')" - "</faultstring><debuginfo /></error>") - - def test_soap_wsdl(self): - wsdl = self.app.get('/ws/api.wsdl').body - print wsdl - assert 'multiply' in wsdl - - def test_soap_call(self): - ts = test_soap.TestSOAP('test_wsdl') - ts.app = self.app - ts.ws_path = '/ws/' - - print ts.ws_path - assert ts.call('multiply', a=5, b=10, _rt=int) == 50 - - def test_scan_api_loops(self): - class MyRoot(object): - pass - - MyRoot.loop = MyRoot() - - root = MyRoot() - - api = list(wsmeext.tg1._scan_api(root)) - print(api) - - self.assertEqual(len(api), 0) - - def test_scan_api_maxlen(self): - class ARoot(object): - pass - - def make_subcontrollers(n): - c = type('Controller%s' % n, (object,), {}) - return c - - c = ARoot - for n in range(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') - ) diff --git a/tox-tmpl.ini b/tox-tmpl.ini deleted file mode 100644 index 692b570..0000000 --- a/tox-tmpl.ini +++ /dev/null @@ -1,141 +0,0 @@ -# content of: tox.ini , put in same dir as setup.py -[tox] -envlist = py27,py27-nolxml,pypy,cornice,cornice-py3,coverage,py36,py35,py36-nolxml,py35-nolxml,pecan-dev27,pecan-dev35,pecan-dev36,pep8 - -[common] -testtools= - nose - coverage < 3.99 - pbr - webtest -basedeps= - transaction - pecan - cloud_sptheme - Sphinx < 1.2.99 - Flask - flask-restful - SQLAlchemy<=0.7.99 - -[axes] -python=py27,py35,py36,pypy -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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py - -[axis:python:py27] -basepython=python2.7 - -[axis:python:py35] -basepython=python3.5 - -[axis:python:py36] -basepython=python3.6 - -[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.6 -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/pecan/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:pecan-dev36] -basepython=python3.6 -deps={[testenv:pecan-dev-base]deps} -commands= - {envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs} - -[testenv:coverage] -basepython=python3 -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] -basepython=python3 -deps= - cloud_sptheme - Sphinx < 1.2.99 - -changedir= - doc - -commands= - make clean ziphtml - -[testenv:pep8] -basepython=python3 -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 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 2509ec4..0000000 --- a/tox.ini +++ /dev/null @@ -1,406 +0,0 @@ -[tox] -envlist = py27,py27-nolxml,pypy,cornice,cornice-py3,coverage,py36,py35,py36-nolxml,py35-nolxml,pecan-dev27,pecan-dev35,pecan-dev36,pep8 - -[common] -testtools = - nose - coverage < 3.99 - pbr - webtest -basedeps = - transaction - pecan - cloud_sptheme - Sphinx < 1.2.99 - Flask - flask-restful - SQLAlchemy<=0.7.99 - -[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.6 -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/pecan/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:pecan-dev36] -basepython = python3.6 -deps = {[testenv:pecan-dev-base]deps} -commands = - {envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs} - -[testenv:coverage] -basepython = python3 -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] -basepython = python3 -deps = - cloud_sptheme - Sphinx < 1.2.99 -changedir = - doc -commands = - make clean ziphtml - -[testenv:pep8] -basepython = python3 -deps = flake8 -commands = flake8 wsme wsmeext setup.py - -[testenv:venv] -commands = {posargs} -usedevelop = True -deps = - pbr - oslo.config - oslotest - -[testenv:py27-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - 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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml -basepython = python2.7 - -[testenv:py27-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - 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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml - simplejson -basepython = python2.7 - -[testenv:py27-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko -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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko -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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - simplejson -basepython = python2.7 - -[testenv:py35-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - 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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml -basepython = python3.5 - -[testenv:py35-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - 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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml - simplejson -basepython = python3.5 - -[testenv:py35-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko -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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko -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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - simplejson -basepython = python3.5 - -[testenv:py36-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml -basepython = python3.6 - -[testenv:py36] -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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml -basepython = python3.6 - -[testenv:py36-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml - simplejson -basepython = python3.6 - -[testenv:py36-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml - simplejson -basepython = python3.6 - -[testenv:py36-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko -basepython = python3.6 - -[testenv:py36-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko -basepython = python3.6 - -[testenv:py36-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - simplejson -basepython = python3.6 - -[testenv:pypy-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - 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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml - -[testenv:pypy-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - 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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - lxml - simplejson - -[testenv:pypy-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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - -[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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - -[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 report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py -deps = - {[common]testtools} - {[common]basedeps} - suds-jurko - simplejson - diff --git a/toxgen.py b/toxgen.py deleted file mode 100644 index 862c80f..0000000 --- a/toxgen.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Produce a tox.ini file from a template config file. - -The template config file is a standard tox.ini file with additional sections. -Theses sections will be combined to create new testenv: sections if they do -not exists yet. - -See REAME.rst for more detail. -""" - -import itertools -import collections -import optparse - -try: - from configparser import ConfigParser -except: - from ConfigParser import ConfigParser # noqa - - -parser = optparse.OptionParser(epilog=__doc__) -parser.add_option('-i', '--input', dest='input', - default='tox-tmpl.ini', metavar='FILE') -parser.add_option('-o', '--output', dest='output', - default='tox.ini', metavar='FILE') - - -class AxisItem(object): - def __init__(self, axis, name, config): - self.axis = axis - self.isdefault = name[-1] == '*' - self.name = name[:-1] if self.isdefault else name - self.load(config) - - def load(self, config): - sectionname = 'axis:%s:%s' % (self.axis.name, self.name) - if config.has_section(sectionname): - self.options = collections.OrderedDict(config.items(sectionname)) - else: - self.options = collections.OrderedDict() - - for name, value in self.axis.defaults.items(): - if name not in self.options: - self.options[name] = value - - -class Axis(object): - def __init__(self, name, config): - self.name = name - self.load(config) - - def load(self, config): - self.items = collections.OrderedDict() - values = config.get('axes', self.name).split(',') - if config.has_section('axis:%s' % self.name): - self.defaults = collections.OrderedDict( - config.items('axis:%s' % self.name) - ) - else: - self.defaults = {} - for value in values: - self.items[value.strip('*')] = AxisItem(self, value, config) - - -def render(incfg): - axes = collections.OrderedDict() - - if incfg.has_section('axes'): - for axis in incfg.options('axes'): - axes[axis] = Axis(axis, incfg) - - out = ConfigParser() - for section in incfg.sections(): - if section == 'axes' or section.startswith('axis:'): - continue - out.add_section(section) - for name, value in incfg.items(section): - out.set(section, name, value) - - for combination in itertools.product( - *[axis.items.keys() for axis in axes.values()]): - options = collections.OrderedDict() - - section_name = ( - 'testenv:' + '-'.join([item for item in combination if item]) - ) - section_alt_name = ( - 'testenv:' + '-'.join([ - itemname - for axis, itemname in zip(axes.values(), combination) - if itemname and not axis.items[itemname].isdefault - ]) - ) - if section_alt_name == section_name: - section_alt_name = None - - axes_items = [ - '%s:%s' % (axis, itemname) - for axis, itemname in zip(axes, combination) - ] - - for axis, itemname in zip(axes.values(), combination): - axis_options = axis.items[itemname].options - if 'constraints' in axis_options: - constraints = axis_options['constraints'].split('\n') - for c in constraints: - if c.startswith('!') and c[1:] in axes_items: - continue - for name, value in axis_options.items(): - if name in options: - options[name] += value - else: - options[name] = value - - constraints = options.pop('constraints', '').split('\n') - neg_constraints = [c[1:] for c in constraints if c and c[0] == '!'] - if not set(neg_constraints).isdisjoint(axes_items): - continue - - if not out.has_section(section_name): - out.add_section(section_name) - - if (section_alt_name and not out.has_section(section_alt_name)): - out.add_section(section_alt_name) - - for name, value in reversed(options.items()): - if not out.has_option(section_name, name): - out.set(section_name, name, value) - if section_alt_name and not out.has_option(section_alt_name, name): - out.set(section_alt_name, name, value) - - return out - - -def main(): - options, args = parser.parse_args() - tmpl = ConfigParser() - tmpl.read(options.input) - with open(options.output, 'wb') as outfile: - render(tmpl).write(outfile) - - -if __name__ == '__main__': - main() diff --git a/wsme/__init__.py b/wsme/__init__.py deleted file mode 100644 index 35109ec..0000000 --- a/wsme/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from wsme.api import signature -from wsme.rest import expose, validate -from wsme.root import WSRoot -from wsme.types import wsattr, wsproperty, Unset - -__all__ = [ - 'expose', 'validate', 'signature', - 'WSRoot', - 'wsattr', 'wsproperty', 'Unset' -] diff --git a/wsme/api.py b/wsme/api.py deleted file mode 100644 index 67263a0..0000000 --- a/wsme/api.py +++ /dev/null @@ -1,237 +0,0 @@ -import traceback -import functools -import inspect -import logging -import six - -import wsme.exc -import wsme.types - -from wsme import utils - -log = logging.getLogger(__name__) - - -def iswsmefunction(f): - return hasattr(f, '_wsme_definition') - - -def wrapfunc(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - wrapper._wsme_original_func = f - return wrapper - - -def getargspec(f): - f = getattr(f, '_wsme_original_func', f) - return inspect.getargspec(f) - - -class FunctionArgument(object): - """ - An argument definition of an api entry - """ - def __init__(self, name, datatype, mandatory, default): - #: argument name - self.name = name - - #: Data type - self.datatype = datatype - - #: True if the argument is mandatory - self.mandatory = mandatory - - #: Default value if argument is omitted - self.default = default - - def resolve_type(self, registry): - self.datatype = registry.resolve_type(self.datatype) - - -class FunctionDefinition(object): - """ - An api entry definition - """ - def __init__(self, func): - #: Function name - self.name = func.__name__ - - #: Function documentation - self.doc = func.__doc__ - - #: Return type - self.return_type = None - - #: The function arguments (list of :class:`FunctionArgument`) - self.arguments = [] - - #: If the body carry the datas of a single argument, its type - self.body_type = None - - #: Status code - self.status_code = 200 - - #: True if extra arguments should be ignored, NOT inserted in - #: the kwargs of the function and not raise UnknownArgument - #: exceptions - self.ignore_extra_args = False - - #: name of the function argument to pass the host request object. - #: Should be set by using the :class:`wsme.types.HostRequest` type - #: in the function @\ :function:`signature` - self.pass_request = False - - #: Dictionnary of protocol-specific options. - self.extra_options = None - - @staticmethod - def get(func): - """ - Returns the :class:`FunctionDefinition` of a method. - """ - if not hasattr(func, '_wsme_definition'): - fd = FunctionDefinition(func) - func._wsme_definition = fd - - return func._wsme_definition - - def get_arg(self, name): - """ - Returns a :class:`FunctionArgument` from its name - """ - for arg in self.arguments: - if arg.name == name: - return arg - return None - - def resolve_types(self, registry): - self.return_type = registry.resolve_type(self.return_type) - self.body_type = registry.resolve_type(self.body_type) - for arg in self.arguments: - arg.resolve_type(registry) - - def set_options(self, body=None, ignore_extra_args=False, status_code=200, - rest_content_types=('json', 'xml'), **extra_options): - self.body_type = body - self.status_code = status_code - self.ignore_extra_args = ignore_extra_args - self.rest_content_types = rest_content_types - self.extra_options = extra_options - - def set_arg_types(self, argspec, arg_types): - args, varargs, keywords, defaults = argspec - if args[0] == 'self': - args = args[1:] - arg_types = list(arg_types) - if self.body_type is not None: - arg_types.append(self.body_type) - for i, argname in enumerate(args): - datatype = arg_types[i] - mandatory = defaults is None or i < (len(args) - len(defaults)) - default = None - if not mandatory: - default = defaults[i - (len(args) - len(defaults))] - if datatype is wsme.types.HostRequest: - self.pass_request = argname - else: - self.arguments.append(FunctionArgument(argname, datatype, - mandatory, default)) - - -class signature(object): - - """Decorator that specify the argument types of an exposed function. - - :param return_type: Type of the value returned by the function - :param argN: Type of the Nth argument - :param body: If the function takes a final argument that is supposed to be - the request body by itself, its type. - :param status_code: HTTP return status code of the function. - :param ignore_extra_args: Allow extra/unknow arguments (default to False) - - Most of the time this decorator is not supposed to be used directly, - unless you are not using WSME on top of another framework. - - If an adapter is used, it will provide either a specialised version of this - decororator, either a new decorator named @wsexpose that takes the same - parameters (it will in addition expose the function, hence its name). - """ - - def __init__(self, *types, **options): - self.return_type = types[0] if types else None - self.arg_types = [] - if len(types) > 1: - self.arg_types.extend(types[1:]) - if 'body' in options: - self.arg_types.append(options['body']) - self.wrap = options.pop('wrap', False) - self.options = options - - def __call__(self, func): - argspec = getargspec(func) - if self.wrap: - func = wrapfunc(func) - fd = FunctionDefinition.get(func) - if fd.extra_options is not None: - raise ValueError("This function is already exposed") - fd.return_type = self.return_type - fd.set_options(**self.options) - if self.arg_types: - fd.set_arg_types(argspec, self.arg_types) - return func - - -sig = signature - - -class Response(object): - """ - Object to hold the "response" from a view function - """ - def __init__(self, obj, status_code=None, error=None, - return_type=wsme.types.Unset): - #: Store the result object from the view - self.obj = obj - - #: Store an optional status_code - self.status_code = status_code - - #: Return error details - #: Must be a dictionnary with the following keys: faultcode, - #: faultstring and an optional debuginfo - self.error = error - - #: Return type - #: Type of the value returned by the function - #: If the return type is wsme.types.Unset it will be ignored - #: and the default return type will prevail. - self.return_type = return_type - - -def format_exception(excinfo, debug=False): - """Extract informations that can be sent to the client.""" - error = excinfo[1] - code = getattr(error, 'code', None) - if code and utils.is_valid_code(code) and utils.is_client_error(code): - faultstring = (error.faultstring if hasattr(error, 'faultstring') - else six.text_type(error)) - r = dict(faultcode="Client", - faultstring=faultstring) - log.debug("Client-side error: %s" % r['faultstring']) - r['debuginfo'] = None - return r - else: - faultstring = six.text_type(error) - debuginfo = "\n".join(traceback.format_exception(*excinfo)) - - log.error('Server-side error: "%s". Detail: \n%s' % ( - faultstring, debuginfo)) - - r = dict(faultcode="Server", faultstring=faultstring) - if debug: - r['debuginfo'] = debuginfo - else: - r['debuginfo'] = None - return r diff --git a/wsme/exc.py b/wsme/exc.py deleted file mode 100644 index 81dcbf8..0000000 --- a/wsme/exc.py +++ /dev/null @@ -1,92 +0,0 @@ -import six - -from wsme.utils import _ - - -class ClientSideError(RuntimeError): - def __init__(self, msg=None, status_code=400): - self.msg = msg - self.code = status_code - super(ClientSideError, self).__init__(self.faultstring) - - @property - def faultstring(self): - if self.msg is None: - return str(self) - elif isinstance(self.msg, six.text_type): - return self.msg - else: - return six.u(self.msg) - - -class InvalidInput(ClientSideError): - def __init__(self, fieldname, value, msg=''): - self.fieldname = fieldname - self.value = value - super(InvalidInput, self).__init__(msg) - - @property - def faultstring(self): - return _(six.u( - "Invalid input for field/attribute %s. Value: '%s'. %s") - ) % (self.fieldname, self.value, self.msg) - - -class MissingArgument(ClientSideError): - def __init__(self, argname, msg=''): - self.argname = argname - super(MissingArgument, self).__init__(msg) - - @property - def faultstring(self): - return _(six.u('Missing argument: "%s"%s')) % ( - self.argname, self.msg and ": " + self.msg or "") - - -class UnknownArgument(ClientSideError): - def __init__(self, argname, msg=''): - self.argname = argname - super(UnknownArgument, self).__init__(msg) - - @property - def faultstring(self): - return _(six.u('Unknown argument: "%s"%s')) % ( - self.argname, self.msg and ": " + self.msg or "") - - -class UnknownFunction(ClientSideError): - def __init__(self, name): - self.name = name - super(UnknownFunction, self).__init__() - - @property - def faultstring(self): - return _(six.u("Unknown function name: %s")) % (self.name) - - -class UnknownAttribute(ClientSideError): - def __init__(self, fieldname, attributes, msg=''): - self.fieldname = fieldname - self.attributes = attributes - self.msg = msg - super(UnknownAttribute, self).__init__(self.msg) - - @property - def faultstring(self): - error = _("Unknown attribute for argument %(argn)s: %(attrs)s") - if len(self.attributes) > 1: - error = _("Unknown attributes for argument %(argn)s: %(attrs)s") - str_attrs = ", ".join(self.attributes) - return error % {'argn': self.fieldname, 'attrs': str_attrs} - - def add_fieldname(self, name): - """Add a fieldname to concatenate the full name. - - Add a fieldname so that the whole hierarchy is displayed. Successive - calls to this method will prepend ``name`` to the hierarchy of names. - """ - if self.fieldname is not None: - self.fieldname = "{}.{}".format(name, self.fieldname) - else: - self.fieldname = name - super(UnknownAttribute, self).__init__(self.msg) diff --git a/wsme/protocol.py b/wsme/protocol.py deleted file mode 100644 index b0107ab..0000000 --- a/wsme/protocol.py +++ /dev/null @@ -1,147 +0,0 @@ -import weakref - -import pkg_resources - -from wsme.exc import ClientSideError - - -__all__ = [ - 'CallContext', - - 'register_protocol', 'getprotocol', -] - -registered_protocols = {} - - -def _cfg(f): - cfg = getattr(f, '_cfg', None) - if cfg is None: - f._cfg = cfg = {} - return cfg - - -class expose(object): - def __init__(self, path, content_type): - self.path = path - self.content_type = content_type - - def __call__(self, func): - func.exposed = True - cfg = _cfg(func) - cfg['content-type'] = self.content_type - cfg.setdefault('paths', []).append(self.path) - return func - - -class CallContext(object): - def __init__(self, request): - self._request = weakref.ref(request) - self.path = None - - self.func = None - self.funcdef = None - - @property - def request(self): - return self._request() - - -class ObjectDict(object): - def __init__(self, obj): - self.obj = obj - - def __getitem__(self, name): - return getattr(self.obj, name) - - -class Protocol(object): - name = None - displayname = None - content_types = [] - - def resolve_path(self, path): - if '$' in path: - from string import Template - s = Template(path) - path = s.substitute(ObjectDict(self)) - return path - - def iter_routes(self): - for attrname in dir(self): - attr = getattr(self, attrname) - if getattr(attr, 'exposed', False): - for path in _cfg(attr)['paths']: - yield self.resolve_path(path), attr - - def accept(self, request): - return request.headers.get('Content-Type') in self.content_types - - def iter_calls(self, request): - pass - - def extract_path(self, context): - pass - - def read_arguments(self, context): - pass - - def encode_result(self, context, result): - pass - - def encode_sample_value(self, datatype, value, format=False): - return ('none', 'N/A') - - def encode_sample_params(self, params, format=False): - return ('none', 'N/A') - - def encode_sample_result(self, datatype, value, format=False): - return ('none', 'N/A') - - -def register_protocol(protocol): - registered_protocols[protocol.name] = protocol - - -def getprotocol(name, **options): - protocol_class = registered_protocols.get(name) - if protocol_class is None: - for entry_point in pkg_resources.iter_entry_points( - 'wsme.protocols', name): - if entry_point.name == name: - protocol_class = entry_point.load() - if protocol_class is None: - raise ValueError("Cannot find protocol '%s'" % name) - registered_protocols[name] = protocol_class - return protocol_class(**options) - - -def media_type_accept(request, content_types): - """Validate media types against request.method. - - When request.method is GET or HEAD compare with the Accept header. - When request.method is POST, PUT or PATCH compare with the Content-Type - header. - When request.method is DELETE media type is irrelevant, so return True. - """ - if request.method in ['GET', 'HEAD']: - if request.accept: - if request.accept.best_match(content_types): - return True - error_message = ('Unacceptable Accept type: %s not in %s' - % (request.accept, content_types)) - raise ClientSideError(error_message, status_code=406) - elif request.method in ['PUT', 'POST', 'PATCH']: - content_type = request.headers.get('Content-Type') - if content_type: - for ct in content_types: - if request.headers.get('Content-Type', '').startswith(ct): - return True - error_message = ('Unacceptable Content-Type: %s not in %s' - % (content_type, content_types)) - raise ClientSideError(error_message, status_code=415) - else: - raise ClientSideError('missing Content-Type header') - elif request.method in ['DELETE']: - return True - return False diff --git a/wsme/rest/__init__.py b/wsme/rest/__init__.py deleted file mode 100644 index f35d6a9..0000000 --- a/wsme/rest/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -import inspect -import wsme.api - -APIPATH_MAXLEN = 20 - - -class expose(object): - def __init__(self, *args, **kwargs): - self.signature = wsme.api.signature(*args, **kwargs) - - def __call__(self, func): - return self.signature(func) - - @classmethod - def with_method(cls, method, *args, **kwargs): - kwargs['method'] = method - return cls(*args, **kwargs) - - @classmethod - def get(cls, *args, **kwargs): - return cls.with_method('GET', *args, **kwargs) - - @classmethod - def post(cls, *args, **kwargs): - return cls.with_method('POST', *args, **kwargs) - - @classmethod - def put(cls, *args, **kwargs): - return cls.with_method('PUT', *args, **kwargs) - - @classmethod - def delete(cls, *args, **kwargs): - return cls.with_method('DELETE', *args, **kwargs) - - -class validate(object): - """ - Decorator that define the arguments types of a function. - - - Example:: - - class MyController(object): - @expose(str) - @validate(datetime.date, datetime.time) - def format(self, d, t): - return d.isoformat() + ' ' + t.isoformat() - """ - def __init__(self, *param_types): - self.param_types = param_types - - def __call__(self, func): - argspec = wsme.api.getargspec(func) - fd = wsme.api.FunctionDefinition.get(func) - fd.set_arg_types(argspec, self.param_types) - return func - - -def scan_api(controller, path=[], objects=[]): - """ - Recursively iterate a controller api entries. - """ - for name in dir(controller): - if name.startswith('_'): - continue - a = getattr(controller, name) - if a in objects: - continue - if inspect.ismethod(a): - if wsme.api.iswsmefunction(a): - yield path + [name], a, [] - elif inspect.isclass(a): - continue - else: - if len(path) > APIPATH_MAXLEN: - raise ValueError("Path is too long: " + str(path)) - for i in scan_api(a, path + [name], objects + [a]): - yield i diff --git a/wsme/rest/args.py b/wsme/rest/args.py deleted file mode 100644 index 9dc16c7..0000000 --- a/wsme/rest/args.py +++ /dev/null @@ -1,310 +0,0 @@ -import cgi -import datetime -import re - -from simplegeneric import generic - -from wsme.exc import ClientSideError, UnknownArgument, InvalidInput - -from wsme.types import iscomplex, list_attributes, Unset -from wsme.types import UserType, ArrayType, DictType, File -from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime -import wsme.runtime - -from six import moves - -ARRAY_MAX_SIZE = 1000 - - -@generic -def from_param(datatype, value): - return datatype(value) if value is not None else None - - -@from_param.when_object(datetime.date) -def date_from_param(datatype, value): - return parse_isodate(value) if value else None - - -@from_param.when_object(datetime.time) -def time_from_param(datatype, value): - return parse_isotime(value) if value else None - - -@from_param.when_object(datetime.datetime) -def datetime_from_param(datatype, value): - return parse_isodatetime(value) if value else None - - -@from_param.when_object(File) -def filetype_from_param(datatype, value): - if isinstance(value, cgi.FieldStorage): - return File(fieldstorage=value) - return File(content=value) - - -@from_param.when_type(UserType) -def usertype_from_param(datatype, value): - return datatype.frombasetype( - from_param(datatype.basetype, value)) - - -@from_param.when_type(ArrayType) -def array_from_param(datatype, value): - if value is None: - return value - return [ - from_param(datatype.item_type, item) - for item in value - ] - - -@generic -def from_params(datatype, params, path, hit_paths): - if iscomplex(datatype) and datatype is not File: - objfound = False - for key in params: - if key.startswith(path + '.'): - objfound = True - break - if objfound: - r = datatype() - for attrdef in list_attributes(datatype): - value = from_params( - attrdef.datatype, - params, '%s.%s' % (path, attrdef.key), hit_paths - ) - if value is not Unset: - setattr(r, attrdef.key, value) - return r - else: - if path in params: - hit_paths.add(path) - return from_param(datatype, params[path]) - return Unset - - -@from_params.when_type(ArrayType) -def array_from_params(datatype, params, path, hit_paths): - if hasattr(params, 'getall'): - # webob multidict - def getall(params, path): - return params.getall(path) - elif hasattr(params, 'getlist'): - # werkzeug multidict - def getall(params, path): # noqa - return params.getlist(path) - if path in params: - hit_paths.add(path) - return [ - from_param(datatype.item_type, value) - for value in getall(params, path)] - - if iscomplex(datatype.item_type): - attributes = set() - r = re.compile(r'^%s\.(?P<attrname>[^\.])' % re.escape(path)) - for p in params.keys(): - m = r.match(p) - if m: - attributes.add(m.group('attrname')) - if attributes: - value = [] - for attrdef in list_attributes(datatype.item_type): - attrpath = '%s.%s' % (path, attrdef.key) - hit_paths.add(attrpath) - attrvalues = getall(params, attrpath) - if len(value) < len(attrvalues): - value[-1:] = [ - datatype.item_type() - for i in moves.range(len(attrvalues) - len(value)) - ] - for i, attrvalue in enumerate(attrvalues): - setattr( - value[i], - attrdef.key, - from_param(attrdef.datatype, attrvalue) - ) - return value - - indexes = set() - r = re.compile(r'^%s\[(?P<index>\d+)\]' % re.escape(path)) - - for p in params.keys(): - m = r.match(p) - if m: - indexes.add(int(m.group('index'))) - - if not indexes: - return Unset - - indexes = list(indexes) - indexes.sort() - - return [from_params(datatype.item_type, params, - '%s[%s]' % (path, index), hit_paths) - for index in indexes] - - -@from_params.when_type(DictType) -def dict_from_params(datatype, params, path, hit_paths): - - keys = set() - r = re.compile(r'^%s\[(?P<key>[a-zA-Z0-9_\.]+)\]' % re.escape(path)) - - for p in params.keys(): - m = r.match(p) - if m: - keys.add(from_param(datatype.key_type, m.group('key'))) - - if not keys: - return Unset - - return dict(( - (key, from_params(datatype.value_type, - params, '%s[%s]' % (path, key), hit_paths)) - for key in keys)) - - -@from_params.when_type(UserType) -def usertype_from_params(datatype, params, path, hit_paths): - value = from_params(datatype.basetype, params, path, hit_paths) - if value is not Unset: - return datatype.frombasetype(value) - return Unset - - -def args_from_args(funcdef, args, kwargs): - newargs = [] - for argdef, arg in zip(funcdef.arguments[:len(args)], args): - try: - newargs.append(from_param(argdef.datatype, arg)) - except Exception as e: - if isinstance(argdef.datatype, UserType): - datatype_name = argdef.datatype.name - elif isinstance(argdef.datatype, type): - datatype_name = argdef.datatype.__name__ - else: - datatype_name = argdef.datatype.__class__.__name__ - raise InvalidInput( - argdef.name, - arg, - "unable to convert to %(datatype)s. Error: %(error)s" % { - 'datatype': datatype_name, 'error': e}) - newkwargs = {} - for argname, value in kwargs.items(): - newkwargs[argname] = from_param( - funcdef.get_arg(argname).datatype, value - ) - return newargs, newkwargs - - -def args_from_params(funcdef, params): - kw = {} - hit_paths = set() - for argdef in funcdef.arguments: - value = from_params( - argdef.datatype, params, argdef.name, hit_paths) - if value is not Unset: - kw[argdef.name] = value - paths = set(params.keys()) - unknown_paths = paths - hit_paths - if '__body__' in unknown_paths: - unknown_paths.remove('__body__') - if not funcdef.ignore_extra_args and unknown_paths: - raise UnknownArgument(', '.join(unknown_paths)) - return [], kw - - -def args_from_body(funcdef, body, mimetype): - from wsme.rest import json as restjson - from wsme.rest import xml as restxml - - if funcdef.body_type is not None: - datatypes = {funcdef.arguments[-1].name: funcdef.body_type} - else: - datatypes = dict(((a.name, a.datatype) for a in funcdef.arguments)) - - if not body: - return (), {} - if mimetype == "application/x-www-form-urlencoded": - # the parameters should have been parsed in params - return (), {} - elif mimetype in restjson.accept_content_types: - dataformat = restjson - elif mimetype in restxml.accept_content_types: - dataformat = restxml - else: - raise ClientSideError("Unknown mimetype: %s" % mimetype, - status_code=415) - - try: - kw = dataformat.parse( - body, datatypes, bodyarg=funcdef.body_type is not None - ) - except UnknownArgument: - if not funcdef.ignore_extra_args: - raise - kw = {} - - return (), kw - - -def combine_args(funcdef, akw, allow_override=False): - newargs, newkwargs = [], {} - for args, kwargs in akw: - for i, arg in enumerate(args): - n = funcdef.arguments[i].name - if not allow_override and n in newkwargs: - raise ClientSideError( - "Parameter %s was given several times" % n) - newkwargs[n] = arg - for name, value in kwargs.items(): - n = str(name) - if not allow_override and n in newkwargs: - raise ClientSideError( - "Parameter %s was given several times" % n) - newkwargs[n] = value - return newargs, newkwargs - - -def get_args(funcdef, args, kwargs, params, form, body, mimetype): - """Combine arguments from : - * the host framework args and kwargs - * the request params - * the request body - - Note that the host framework args and kwargs can be overridden - by arguments from params of body - """ - # get the body from params if not given directly - if not body and '__body__' in params: - body = params['__body__'] - - # extract args from the host args and kwargs - from_args = args_from_args(funcdef, args, kwargs) - - # extract args from the request parameters - from_params = args_from_params(funcdef, params) - - # extract args from the form parameters - if form: - from_form_params = args_from_params(funcdef, form) - else: - from_form_params = (), {} - - # extract args from the request body - from_body = args_from_body(funcdef, body, mimetype) - - # combine params and body arguments - from_params_and_body = combine_args( - funcdef, - (from_params, from_form_params, from_body) - ) - - args, kwargs = combine_args( - funcdef, - (from_args, from_params_and_body), - allow_override=True - ) - wsme.runtime.check_arguments(funcdef, args, kwargs) - return args, kwargs diff --git a/wsme/rest/json.py b/wsme/rest/json.py deleted file mode 100644 index 48bd082..0000000 --- a/wsme/rest/json.py +++ /dev/null @@ -1,328 +0,0 @@ -"""REST+Json protocol implementation.""" -from __future__ import absolute_import -import datetime -import decimal - -import six - -from simplegeneric import generic - -import wsme.exc -import wsme.types -from wsme.types import Unset -import wsme.utils - - -try: - import simplejson as json -except ImportError: - import json # noqa - - -content_type = 'application/json' -accept_content_types = [ - content_type, - 'text/javascript', - 'application/javascript' -] -ENUM_TRUE = ('true', 't', 'yes', 'y', 'on', '1') -ENUM_FALSE = ('false', 'f', 'no', 'n', 'off', '0') - - -@generic -def tojson(datatype, value): - """ - A generic converter from python to jsonify-able datatypes. - - If a non-complex user specific type is to be used in the api, - a specific tojson should be added:: - - from wsme.protocol.restjson import tojson - - myspecialtype = object() - - @tojson.when_object(myspecialtype) - def myspecialtype_tojson(datatype, value): - return str(value) - """ - if value is None: - return None - if wsme.types.iscomplex(datatype): - d = dict() - for attr in wsme.types.list_attributes(datatype): - attr_value = getattr(value, attr.key) - if attr_value is not Unset: - d[attr.name] = tojson(attr.datatype, attr_value) - return d - elif wsme.types.isusertype(datatype): - return tojson(datatype.basetype, datatype.tobasetype(value)) - return value - - -@tojson.when_object(wsme.types.bytes) -def bytes_tojson(datatype, value): - if value is None: - return None - return value.decode('ascii') - - -@tojson.when_type(wsme.types.ArrayType) -def array_tojson(datatype, value): - if value is None: - return None - return [tojson(datatype.item_type, item) for item in value] - - -@tojson.when_type(wsme.types.DictType) -def dict_tojson(datatype, value): - if value is None: - return None - return dict(( - (tojson(datatype.key_type, item[0]), - tojson(datatype.value_type, item[1])) - for item in value.items() - )) - - -@tojson.when_object(decimal.Decimal) -def decimal_tojson(datatype, value): - if value is None: - return None - return str(value) - - -@tojson.when_object(datetime.date) -def date_tojson(datatype, value): - if value is None: - return None - return value.isoformat() - - -@tojson.when_object(datetime.time) -def time_tojson(datatype, value): - if value is None: - return None - return value.isoformat() - - -@tojson.when_object(datetime.datetime) -def datetime_tojson(datatype, value): - if value is None: - return None - return value.isoformat() - - -@generic -def fromjson(datatype, value): - """A generic converter from json base types to python datatype. - - If a non-complex user specific type is to be used in the api, - a specific fromjson should be added:: - - from wsme.protocol.restjson import fromjson - - class MySpecialType(object): - pass - - @fromjson.when_object(MySpecialType) - def myspecialtype_fromjson(datatype, value): - return MySpecialType(value) - """ - if value is None: - return None - if wsme.types.iscomplex(datatype): - obj = datatype() - attributes = wsme.types.list_attributes(datatype) - - # Here we check that all the attributes in the value are also defined - # in our type definition, otherwise we raise an Error. - v_keys = set(value.keys()) - a_keys = set(adef.name for adef in attributes) - if not v_keys <= a_keys: - raise wsme.exc.UnknownAttribute(None, v_keys - a_keys) - - for attrdef in attributes: - if attrdef.name in value: - try: - val_fromjson = fromjson(attrdef.datatype, - value[attrdef.name]) - except wsme.exc.UnknownAttribute as e: - e.add_fieldname(attrdef.name) - raise - if getattr(attrdef, 'readonly', False): - raise wsme.exc.InvalidInput(attrdef.name, val_fromjson, - "Cannot set read only field.") - setattr(obj, attrdef.key, val_fromjson) - elif attrdef.mandatory: - raise wsme.exc.InvalidInput(attrdef.name, None, - "Mandatory field missing.") - - return wsme.types.validate_value(datatype, obj) - elif wsme.types.isusertype(datatype): - value = datatype.frombasetype( - fromjson(datatype.basetype, value)) - return value - - -@fromjson.when_type(wsme.types.ArrayType) -def array_fromjson(datatype, value): - if value is None: - return None - if not isinstance(value, list): - raise ValueError("Value not a valid list: %s" % value) - return [fromjson(datatype.item_type, item) for item in value] - - -@fromjson.when_type(wsme.types.DictType) -def dict_fromjson(datatype, value): - if value is None: - return None - if not isinstance(value, dict): - raise ValueError("Value not a valid dict: %s" % value) - return dict(( - (fromjson(datatype.key_type, item[0]), - fromjson(datatype.value_type, item[1])) - for item in value.items())) - - -@fromjson.when_object(six.binary_type) -def str_fromjson(datatype, value): - if (isinstance(value, six.string_types) or - isinstance(value, six.integer_types) or - isinstance(value, float)): - return six.text_type(value).encode('utf8') - - -@fromjson.when_object(wsme.types.text) -def text_fromjson(datatype, value): - if value is not None and isinstance(value, wsme.types.bytes): - return wsme.types.text(value) - return value - - -@fromjson.when_object(*six.integer_types + (float,)) -def numeric_fromjson(datatype, value): - """Convert string object to built-in types int, long or float.""" - if value is None: - return None - return datatype(value) - - -@fromjson.when_object(bool) -def bool_fromjson(datatype, value): - """Convert to bool, restricting strings to just unambiguous values.""" - if value is None: - return None - if isinstance(value, six.integer_types + (bool,)): - return bool(value) - if value in ENUM_TRUE: - return True - if value in ENUM_FALSE: - return False - raise ValueError("Value not an unambiguous boolean: %s" % value) - - -@fromjson.when_object(decimal.Decimal) -def decimal_fromjson(datatype, value): - if value is None: - return None - return decimal.Decimal(value) - - -@fromjson.when_object(datetime.date) -def date_fromjson(datatype, value): - if value is None: - return None - return wsme.utils.parse_isodate(value) - - -@fromjson.when_object(datetime.time) -def time_fromjson(datatype, value): - if value is None: - return None - return wsme.utils.parse_isotime(value) - - -@fromjson.when_object(datetime.datetime) -def datetime_fromjson(datatype, value): - if value is None: - return None - return wsme.utils.parse_isodatetime(value) - - -def parse(s, datatypes, bodyarg, encoding='utf8'): - jload = json.load - if not hasattr(s, 'read'): - if six.PY3 and isinstance(s, six.binary_type): - s = s.decode(encoding) - jload = json.loads - try: - jdata = jload(s) - except ValueError: - raise wsme.exc.ClientSideError("Request is not in valid JSON format") - if bodyarg: - argname = list(datatypes.keys())[0] - try: - kw = {argname: fromjson(datatypes[argname], jdata)} - except ValueError as e: - raise wsme.exc.InvalidInput(argname, jdata, e.args[0]) - except wsme.exc.UnknownAttribute as e: - # We only know the fieldname at this level, not in the - # called function. We fill in this information here. - e.add_fieldname(argname) - raise - else: - kw = {} - extra_args = [] - if not isinstance(jdata, dict): - raise wsme.exc.ClientSideError("Request must be a JSON dict") - for key in jdata: - if key not in datatypes: - extra_args.append(key) - else: - try: - kw[key] = fromjson(datatypes[key], jdata[key]) - except ValueError as e: - raise wsme.exc.InvalidInput(key, jdata[key], e.args[0]) - except wsme.exc.UnknownAttribute as e: - # We only know the fieldname at this level, not in the - # called function. We fill in this information here. - e.add_fieldname(key) - raise - if extra_args: - raise wsme.exc.UnknownArgument(', '.join(extra_args)) - return kw - - -def encode_result(value, datatype, **options): - jsondata = tojson(datatype, value) - if options.get('nest_result', False): - jsondata = {options.get('nested_result_attrname', 'result'): jsondata} - return json.dumps(jsondata) - - -def encode_error(context, errordetail): - return json.dumps(errordetail) - - -def encode_sample_value(datatype, value, format=False): - r = tojson(datatype, value) - content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0, - sort_keys=format) - return ('javascript', content) - - -def encode_sample_params(params, format=False): - kw = {} - for name, datatype, value in params: - kw[name] = tojson(datatype, value) - content = json.dumps(kw, ensure_ascii=False, indent=4 if format else 0, - sort_keys=format) - return ('javascript', content) - - -def encode_sample_result(datatype, value, format=False): - r = tojson(datatype, value) - content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0, - sort_keys=format) - return ('javascript', content) diff --git a/wsme/rest/protocol.py b/wsme/rest/protocol.py deleted file mode 100644 index 5201ccf..0000000 --- a/wsme/rest/protocol.py +++ /dev/null @@ -1,133 +0,0 @@ -import os.path -import logging - -from wsme.utils import OrderedDict -from wsme.protocol import CallContext, Protocol, media_type_accept - -import wsme.rest -import wsme.rest.args -import wsme.runtime - -log = logging.getLogger(__name__) - - -class RestProtocol(Protocol): - name = 'rest' - displayname = 'REST' - dataformats = ['json', 'xml'] - content_types = ['application/json', 'text/xml'] - - def __init__(self, dataformats=None): - if dataformats is None: - dataformats = RestProtocol.dataformats - - self.dataformats = OrderedDict() - self.content_types = [] - - for dataformat in dataformats: - __import__('wsme.rest.' + dataformat) - dfmod = getattr(wsme.rest, dataformat) - self.dataformats[dataformat] = dfmod - self.content_types.extend(dfmod.accept_content_types) - - def accept(self, request): - for dataformat in self.dataformats: - if request.path.endswith('.' + dataformat): - return True - return media_type_accept(request, self.content_types) - - def iter_calls(self, request): - context = CallContext(request) - context.outformat = None - ext = os.path.splitext(request.path.split('/')[-1])[1] - inmime = request.content_type - outmime = request.accept.best_match(self.content_types) - - outformat = None - informat = None - for dfname, df in self.dataformats.items(): - if ext == '.' + dfname: - outformat = df - if not inmime: - informat = df - - if outformat is None and request.accept: - for dfname, df in self.dataformats.items(): - if outmime in df.accept_content_types: - outformat = df - if not inmime: - informat = df - - if outformat is None: - for dfname, df in self.dataformats.items(): - if inmime == df.content_type: - outformat = df - - context.outformat = outformat - context.outformat_options = { - 'nest_result': getattr(self, 'nest_result', False) - } - if not inmime and informat: - inmime = informat.content_type - log.debug("Inferred input type: %s" % inmime) - context.inmime = inmime - yield context - - def extract_path(self, context): - path = context.request.path - assert path.startswith(self.root._webpath) - path = path[len(self.root._webpath):] - path = path.strip('/').split('/') - - for dataformat in self.dataformats: - if path[-1].endswith('.' + dataformat): - path[-1] = path[-1][:-len(dataformat) - 1] - - # Check if the path is actually a function, and if not - # see if the http method make a difference - # TODO Re-think the function lookup phases. Here we are - # doing the job that will be done in a later phase, which - # is sub-optimal - for p, fdef in self.root.getapi(): - if p == path: - return path - - # No function at this path. Now check for function that have - # this path as a prefix, and declared an http method - for p, fdef in self.root.getapi(): - if len(p) == len(path) + 1 and p[:len(path)] == path and \ - fdef.extra_options.get('method') == context.request.method: - return p - - return path - - def read_arguments(self, context): - request = context.request - funcdef = context.funcdef - - body = None - if request.content_length not in (None, 0, '0'): - body = request.body - if not body and '__body__' in request.params: - body = request.params['__body__'] - - args, kwargs = wsme.rest.args.combine_args( - funcdef, - (wsme.rest.args.args_from_params(funcdef, request.params), - wsme.rest.args.args_from_body(funcdef, body, context.inmime)) - ) - wsme.runtime.check_arguments(funcdef, args, kwargs) - return kwargs - - def encode_result(self, context, result): - out = context.outformat.encode_result( - result, context.funcdef.return_type, - **context.outformat_options - ) - return out - - def encode_error(self, context, errordetail): - out = context.outformat.encode_error( - context, errordetail - ) - return out diff --git a/wsme/rest/xml.py b/wsme/rest/xml.py deleted file mode 100644 index 286afa7..0000000 --- a/wsme/rest/xml.py +++ /dev/null @@ -1,298 +0,0 @@ -from __future__ import absolute_import - -import datetime - -import six - -import xml.etree.ElementTree as et - -from simplegeneric import generic - -import wsme.types -from wsme.exc import UnknownArgument, InvalidInput - -import re - -content_type = 'text/xml' -accept_content_types = [ - content_type, -] - -time_re = re.compile(r'(?P<h>[0-2][0-9]):(?P<m>[0-5][0-9]):(?P<s>[0-6][0-9])') - - -def xml_indent(elem, level=0): - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - for e in elem: - xml_indent(e, level + 1) - if not e.tail or not e.tail.strip(): - e.tail = i - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - -@generic -def toxml(datatype, key, value): - """ - A generic converter from python to xml elements. - - If a non-complex user specific type is to be used in the api, - a specific toxml should be added:: - - from wsme.protocol.restxml import toxml - - myspecialtype = object() - - @toxml.when_object(myspecialtype) - def myspecialtype_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = str(value) - return el - """ - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - if wsme.types.isusertype(datatype): - return toxml(datatype.basetype, - key, datatype.tobasetype(value)) - elif wsme.types.iscomplex(datatype): - for attrdef in datatype._wsme_attributes: - attrvalue = getattr(value, attrdef.key) - if attrvalue is not wsme.types.Unset: - el.append(toxml(attrdef.datatype, attrdef.name, - attrvalue)) - else: - el.text = six.text_type(value) - return el - - -@generic -def fromxml(datatype, element): - """ - A generic converter from xml elements to python datatype. - - If a non-complex user specific type is to be used in the api, - a specific fromxml should be added:: - - from wsme.protocol.restxml import fromxml - - class MySpecialType(object): - pass - - @fromxml.when_object(MySpecialType) - def myspecialtype_fromxml(datatype, element): - if element.get('nil', False): - return None - return MySpecialType(element.text) - """ - if element.get('nil', False): - return None - if wsme.types.isusertype(datatype): - return datatype.frombasetype(fromxml(datatype.basetype, element)) - if wsme.types.iscomplex(datatype): - obj = datatype() - for attrdef in wsme.types.list_attributes(datatype): - sub = element.find(attrdef.name) - if sub is not None: - val_fromxml = fromxml(attrdef.datatype, sub) - if getattr(attrdef, 'readonly', False): - raise InvalidInput(attrdef.name, val_fromxml, - "Cannot set read only field.") - setattr(obj, attrdef.key, val_fromxml) - elif attrdef.mandatory: - raise InvalidInput(attrdef.name, None, - "Mandatory field missing.") - return wsme.types.validate_value(datatype, obj) - if datatype is wsme.types.bytes: - return element.text.encode('ascii') - return datatype(element.text) - - -@toxml.when_type(wsme.types.ArrayType) -def array_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - for item in value: - el.append(toxml(datatype.item_type, 'item', item)) - return el - - -@toxml.when_type(wsme.types.DictType) -def dict_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - for item in value.items(): - key = toxml(datatype.key_type, 'key', item[0]) - value = toxml(datatype.value_type, 'value', item[1]) - node = et.Element('item') - node.append(key) - node.append(value) - el.append(node) - return el - - -@toxml.when_object(wsme.types.bytes) -def bytes_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value.decode('ascii') - return el - - -@toxml.when_object(bool) -def bool_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value and 'true' or 'false' - return el - - -@toxml.when_object(datetime.date) -def date_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value.isoformat() - return el - - -@toxml.when_object(datetime.datetime) -def datetime_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value.isoformat() - return el - - -@fromxml.when_type(wsme.types.ArrayType) -def array_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return [ - fromxml(datatype.item_type, item) - for item in element.findall('item') - ] - - -@fromxml.when_object(bool) -def bool_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return element.text.lower() != 'false' - - -@fromxml.when_type(wsme.types.DictType) -def dict_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return dict(( - (fromxml(datatype.key_type, item.find('key')), - fromxml(datatype.value_type, item.find('value'))) - for item in element.findall('item'))) - - -@fromxml.when_object(wsme.types.text) -def unicode_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.types.text(element.text) if element.text else six.u('') - - -@fromxml.when_object(datetime.date) -def date_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.utils.parse_isodate(element.text) - - -@fromxml.when_object(datetime.time) -def time_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.utils.parse_isotime(element.text) - - -@fromxml.when_object(datetime.datetime) -def datetime_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.utils.parse_isodatetime(element.text) - - -def parse(s, datatypes, bodyarg): - if hasattr(s, 'read'): - tree = et.parse(s) - else: - tree = et.fromstring(s) - if bodyarg: - name = list(datatypes.keys())[0] - return {name: fromxml(datatypes[name], tree)} - else: - kw = {} - extra_args = [] - for sub in tree: - if sub.tag not in datatypes: - extra_args.append(sub.tag) - kw[sub.tag] = fromxml(datatypes[sub.tag], sub) - if extra_args: - raise UnknownArgument(', '.join(extra_args)) - return kw - - -def encode_result(value, datatype, **options): - return et.tostring(toxml( - datatype, options.get('nested_result_attrname', 'result'), value - )) - - -def encode_error(context, errordetail): - el = et.Element('error') - et.SubElement(el, 'faultcode').text = errordetail['faultcode'] - et.SubElement(el, 'faultstring').text = errordetail['faultstring'] - if 'debuginfo' in errordetail: - et.SubElement(el, 'debuginfo').text = errordetail['debuginfo'] - return et.tostring(el) - - -def encode_sample_value(datatype, value, format=False): - r = toxml(datatype, 'value', value) - if format: - xml_indent(r) - content = et.tostring(r) - return ('xml', content) - - -def encode_sample_params(params, format=False): - node = et.Element('parameters') - for name, datatype, value in params: - node.append(toxml(datatype, name, value)) - if format: - xml_indent(node) - content = et.tostring(node) - return ('xml', content) - - -def encode_sample_result(datatype, value, format=False): - r = toxml(datatype, 'result', value) - if format: - xml_indent(r) - content = et.tostring(r) - return ('xml', content) diff --git a/wsme/root.py b/wsme/root.py deleted file mode 100644 index e71f1f7..0000000 --- a/wsme/root.py +++ /dev/null @@ -1,372 +0,0 @@ -import logging -import sys -import weakref - -from six import u, b -import six - -import webob - -from wsme.exc import ClientSideError, UnknownFunction -from wsme.protocol import getprotocol -from wsme.rest import scan_api -from wsme import spore -import wsme.api -import wsme.types - -log = logging.getLogger(__name__) - -html_body = u(""" -<html> -<head> - <style type='text/css'> - %(css)s - </style> -</head> -<body> -%(content)s -</body> -</html> -""") - - -def default_prepare_response_body(request, results): - r = None - sep = None - for value in results: - if sep is None: - if isinstance(value, six.text_type): - sep = u('\n') - r = u('') - else: - sep = b('\n') - r = b('') - else: - r += sep - r += value - return r - - -class DummyTransaction: - def commit(self): - pass - - def abort(self): - pass - - -class WSRoot(object): - """ - Root controller for webservices. - - :param protocols: A list of protocols to enable (see :meth:`addprotocol`) - :param webpath: The web path where the webservice is published. - - :type transaction: A `transaction - <http://pypi.python.org/pypi/transaction>`_-like - object or ``True``. - :param transaction: If specified, a transaction will be created and - handled on a per-call base. - - This option *can* be enabled along with `repoze.tm2 - <http://pypi.python.org/pypi/repoze.tm2>`_ - (it will only make it void). - - If ``True``, the default :mod:`transaction` - module will be imported and used. - - """ - __registry__ = wsme.types.registry - - def __init__(self, protocols=[], webpath='', transaction=None, - scan_api=scan_api): - self._debug = True - self._webpath = webpath - self.protocols = [] - self._scan_api = scan_api - - self._transaction = transaction - if self._transaction is True: - import transaction - self._transaction = transaction - - for protocol in protocols: - self.addprotocol(protocol) - - self._api = None - - def wsgiapp(self): - """Returns a wsgi application""" - from webob.dec import wsgify - return wsgify(self._handle_request) - - def begin(self): - if self._transaction: - return self._transaction.begin() - else: - return DummyTransaction() - - def addprotocol(self, protocol, **options): - """ - Enable a new protocol on the controller. - - :param protocol: A registered protocol name or an instance - of a protocol. - """ - if isinstance(protocol, str): - protocol = getprotocol(protocol, **options) - self.protocols.append(protocol) - protocol.root = weakref.proxy(self) - - def getapi(self): - """ - Returns the api description. - - :rtype: list of (path, :class:`FunctionDefinition`) - """ - if self._api is None: - self._api = [ - (path, f, f._wsme_definition, args) - for path, f, args in self._scan_api(self) - ] - for path, f, fdef, args in self._api: - fdef.resolve_types(self.__registry__) - return [ - (path, fdef) - for path, f, fdef, args in self._api - ] - - def _get_protocol(self, name): - for protocol in self.protocols: - if protocol.name == name: - return protocol - - def _select_protocol(self, request): - log.debug("Selecting a protocol for the following request :\n" - "headers: %s\nbody: %s", request.headers.items(), - request.content_length and ( - request.content_length > 512 and - request.body[:512] or - request.body) or '') - protocol = None - error = ClientSideError(status_code=406) - path = str(request.path) - assert path.startswith(self._webpath) - path = path[len(self._webpath) + 1:] - if 'wsmeproto' in request.params: - return self._get_protocol(request.params['wsmeproto']) - else: - - for p in self.protocols: - try: - if p.accept(request): - protocol = p - break - except ClientSideError as e: - error = e - # If we could not select a protocol, we raise the last exception - # that we got, or the default one. - if not protocol: - raise error - return protocol - - def _do_call(self, protocol, context): - request = context.request - request.calls.append(context) - try: - if context.path is None: - context.path = protocol.extract_path(context) - - if context.path is None: - raise ClientSideError(u( - 'The %s protocol was unable to extract a function ' - 'path from the request') % protocol.name) - - context.func, context.funcdef, args = \ - self._lookup_function(context.path) - kw = protocol.read_arguments(context) - args = list(args) - - txn = self.begin() - try: - result = context.func(*args, **kw) - txn.commit() - except Exception: - 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 Exception: - pass - - if lexer is None: - raise ValueError("No lexer found") - formatter = HtmlFormatter() - return html_body % dict( - css=formatter.get_style_defs(), - content=highlight(content, lexer, formatter).encode('utf8')) - except Exception as e: - log.warning( - "Could not pygment the content because of the following " - "error :\n%s" % e) - return html_body % dict( - css='', - content=u('<pre>%s</pre>') % - content.replace(b('>'), b('>')) - .replace(b('<'), b('<'))) diff --git a/wsme/runtime.py b/wsme/runtime.py deleted file mode 100644 index e114d13..0000000 --- a/wsme/runtime.py +++ /dev/null @@ -1,9 +0,0 @@ -from wsme.exc import MissingArgument - - -def check_arguments(funcdef, args, kw): - """Check if some arguments are missing""" - assert len(args) == 0 - for arg in funcdef.arguments: - if arg.mandatory and arg.name not in kw: - raise MissingArgument(arg.name) diff --git a/wsme/spore.py b/wsme/spore.py deleted file mode 100644 index 8de0ed2..0000000 --- a/wsme/spore.py +++ /dev/null @@ -1,64 +0,0 @@ -from wsme import types - -try: - import simplejson as json -except ImportError: - import json # noqa - - -def getdesc(root, host_url=''): - methods = {} - - for path, funcdef in root.getapi(): - method = funcdef.extra_options.get('method', None) - name = '_'.join(path) - if method is not None: - path = path[:-1] - else: - method = 'GET' - for argdef in funcdef.arguments: - if types.iscomplex(argdef.datatype) \ - or types.isarray(argdef.datatype) \ - or types.isdict(argdef.datatype): - method = 'POST' - break - - required_params = [] - optional_params = [] - for argdef in funcdef.arguments: - if method == 'GET' and argdef.mandatory: - required_params.append(argdef.name) - else: - optional_params.append(argdef.name) - - methods[name] = { - 'method': method, - 'path': '/'.join(path) - } - if required_params: - methods[name]['required_params'] = required_params - if optional_params: - methods[name]['optional_params'] = optional_params - if funcdef.doc: - methods[name]['documentation'] = funcdef.doc - - formats = [] - for p in root.protocols: - if p.name == 'restxml': - formats.append('xml') - if p.name == 'restjson': - formats.append('json') - - api = { - 'base_url': host_url + root._webpath, - 'version': '0.1', - 'name': getattr(root, 'name', 'name'), - 'authority': '', - 'formats': [ - 'json', - 'xml' - ], - 'methods': methods - } - - return json.dumps(api, indent=4) diff --git a/wsme/tests/__init__.py b/wsme/tests/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/wsme/tests/__init__.py +++ /dev/null diff --git a/wsme/tests/protocol.py b/wsme/tests/protocol.py deleted file mode 100644 index 1c41f8c..0000000 --- a/wsme/tests/protocol.py +++ /dev/null @@ -1,720 +0,0 @@ -# coding=utf-8 - -import unittest -import warnings -import datetime -import decimal -import six - -from six import u, b - -from webtest import TestApp - -from wsme import WSRoot, Unset -from wsme import expose, validate -import wsme.types -import wsme.utils - -warnings.filterwarnings('ignore', module='webob.dec') - -binarysample = b('\x00\xff\x43') - -try: - 1 / 0 -except ZeroDivisionError as e: - zerodivisionerrormsg = str(e) - - -class CallException(RuntimeError): - def __init__(self, faultcode, faultstring, debuginfo): - self.faultcode = faultcode - self.faultstring = faultstring - self.debuginfo = debuginfo - - def __str__(self): - return 'faultcode=%s, faultstring=%s, debuginfo=%s' % ( - self.faultcode, self.faultstring, self.debuginfo - ) - - -myenumtype = wsme.types.Enum(wsme.types.bytes, 'v1', 'v2') - - -class NestedInner(object): - aint = int - - def __init__(self, aint=None): - self.aint = aint - - -class NestedOuter(object): - inner = NestedInner - inner_array = wsme.types.wsattr([NestedInner]) - inner_dict = {wsme.types.text: NestedInner} - - def __init__(self): - self.inner = NestedInner(0) - - -class NamedAttrsObject(object): - def __init__(self, v1=Unset, v2=Unset): - self.attr_1 = v1 - self.attr_2 = v2 - - attr_1 = wsme.types.wsattr(int, name='attr.1') - attr_2 = wsme.types.wsattr(int, name='attr.2') - - -class CustomObject(object): - aint = int - name = wsme.types.text - - -class ExtendedInt(wsme.types.UserType): - basetype = int - name = "Extended integer" - - -class NestedInnerApi(object): - @expose(bool) - def deepfunction(self): - return True - - -class NestedOuterApi(object): - inner = NestedInnerApi() - - -class ReturnTypes(object): - @expose(wsme.types.bytes) - def getbytes(self): - return b("astring") - - @expose(wsme.types.text) - def gettext(self): - return u('\xe3\x81\xae') - - @expose(int) - def getint(self): - return 2 - - @expose(float) - def getfloat(self): - return 3.14159265 - - @expose(decimal.Decimal) - def getdecimal(self): - return decimal.Decimal('3.14159265') - - @expose(datetime.date) - def getdate(self): - return datetime.date(1994, 1, 26) - - @expose(bool) - def getbooltrue(self): - return True - - @expose(bool) - def getboolfalse(self): - return False - - @expose(datetime.time) - def gettime(self): - return datetime.time(12, 0, 0) - - @expose(datetime.datetime) - def getdatetime(self): - return datetime.datetime(1994, 1, 26, 12, 0, 0) - - @expose(wsme.types.binary) - def getbinary(self): - return binarysample - - @expose(NestedOuter) - def getnested(self): - n = NestedOuter() - return n - - @expose([wsme.types.bytes]) - def getbytesarray(self): - return [b("A"), b("B"), b("C")] - - @expose([NestedOuter]) - def getnestedarray(self): - return [NestedOuter(), NestedOuter()] - - @expose({wsme.types.bytes: NestedOuter}) - def getnesteddict(self): - return {b('a'): NestedOuter(), b('b'): NestedOuter()} - - @expose(NestedOuter) - def getobjectarrayattribute(self): - obj = NestedOuter() - obj.inner_array = [NestedInner(12), NestedInner(13)] - return obj - - @expose(NestedOuter) - def getobjectdictattribute(self): - obj = NestedOuter() - obj.inner_dict = { - '12': NestedInner(12), - '13': NestedInner(13) - } - return obj - - @expose(myenumtype) - def getenum(self): - return b('v2') - - @expose(NamedAttrsObject) - def getnamedattrsobj(self): - return NamedAttrsObject(5, 6) - - -class ArgTypes(object): - def assertEqual(self, a, b): - if not (a == b): - raise AssertionError('%s != %s' % (a, b)) - - def assertIsInstance(self, value, v_type): - assert isinstance(value, v_type), ("%s is not instance of type %s" % - (value, v_type)) - - @expose(wsme.types.bytes) - @validate(wsme.types.bytes) - def setbytes(self, value): - print(repr(value)) - self.assertEqual(type(value), wsme.types.bytes) - return value - - @expose(wsme.types.text) - @validate(wsme.types.text) - def settext(self, value): - print(repr(value)) - self.assertEqual(type(value), wsme.types.text) - return value - - @expose(wsme.types.text) - @validate(wsme.types.text) - def settextnone(self, value): - print(repr(value)) - self.assertEqual(type(value), type(None)) - return value - - @expose(bool) - @validate(bool) - def setbool(self, value): - print(repr(value)) - self.assertEqual(type(value), bool) - return value - - @expose(int) - @validate(int) - def setint(self, value): - print(repr(value)) - self.assertEqual(type(value), int) - return value - - @expose(float) - @validate(float) - def setfloat(self, value): - print(repr(value)) - self.assertEqual(type(value), float) - return value - - @expose(decimal.Decimal) - @validate(decimal.Decimal) - def setdecimal(self, value): - print(repr(value)) - self.assertEqual(type(value), decimal.Decimal) - return value - - @expose(datetime.date) - @validate(datetime.date) - def setdate(self, value): - print(repr(value)) - self.assertEqual(type(value), datetime.date) - return value - - @expose(datetime.time) - @validate(datetime.time) - def settime(self, value): - print(repr(value)) - self.assertEqual(type(value), datetime.time) - return value - - @expose(datetime.datetime) - @validate(datetime.datetime) - def setdatetime(self, value): - print(repr(value)) - self.assertEqual(type(value), datetime.datetime) - return value - - @expose(wsme.types.binary) - @validate(wsme.types.binary) - def setbinary(self, value): - print(repr(value)) - self.assertEqual(type(value), six.binary_type) - return value - - @expose([wsme.types.bytes]) - @validate([wsme.types.bytes]) - def setbytesarray(self, value): - print(repr(value)) - self.assertEqual(type(value), list) - self.assertEqual(type(value[0]), wsme.types.bytes) - return value - - @expose([wsme.types.text]) - @validate([wsme.types.text]) - def settextarray(self, value): - print(repr(value)) - self.assertEqual(type(value), list) - self.assertEqual(type(value[0]), wsme.types.text) - return value - - @expose([datetime.datetime]) - @validate([datetime.datetime]) - def setdatetimearray(self, value): - print(repr(value)) - self.assertEqual(type(value), list) - self.assertEqual(type(value[0]), datetime.datetime) - return value - - @expose(NestedOuter) - @validate(NestedOuter) - def setnested(self, value): - print(repr(value)) - self.assertEqual(type(value), NestedOuter) - return value - - @expose([NestedOuter]) - @validate([NestedOuter]) - def setnestedarray(self, value): - print(repr(value)) - self.assertEqual(type(value), list) - self.assertEqual(type(value[0]), NestedOuter) - return value - - @expose({wsme.types.bytes: NestedOuter}) - @validate({wsme.types.bytes: NestedOuter}) - def setnesteddict(self, value): - print(repr(value)) - self.assertEqual(type(value), dict) - self.assertEqual(type(list(value.keys())[0]), wsme.types.bytes) - self.assertEqual(type(list(value.values())[0]), NestedOuter) - return value - - @expose(myenumtype) - @validate(myenumtype) - def setenum(self, value): - print(value) - self.assertEqual(type(value), wsme.types.bytes) - return value - - @expose(NamedAttrsObject) - @validate(NamedAttrsObject) - def setnamedattrsobj(self, value): - print(value) - self.assertEqual(type(value), NamedAttrsObject) - self.assertEqual(value.attr_1, 10) - self.assertEqual(value.attr_2, 20) - return value - - @expose(CustomObject) - @validate(CustomObject) - def setcustomobject(self, value): - self.assertIsInstance(value, CustomObject) - self.assertIsInstance(value.name, wsme.types.text) - self.assertIsInstance(value.aint, int) - return value - - @expose(ExtendedInt()) - @validate(ExtendedInt()) - def setextendedint(self, value): - self.assertEqual(isinstance(value, ExtendedInt.basetype), True) - return value - - -class BodyTypes(object): - def assertEqual(self, a, b): - if not (a == b): - raise AssertionError('%s != %s' % (a, b)) - - @expose(int, body={wsme.types.text: int}) - @validate(int) - def setdict(self, body): - print(body) - self.assertEqual(type(body), dict) - self.assertEqual(type(body['test']), int) - self.assertEqual(body['test'], 10) - return body['test'] - - @expose(int, body=[int]) - @validate(int) - def setlist(self, body): - print(body) - self.assertEqual(type(body), list) - self.assertEqual(type(body[0]), int) - self.assertEqual(body[0], 10) - return body[0] - - -class WithErrors(object): - @expose() - def divide_by_zero(self): - 1 / 0 - - -class MiscFunctions(object): - @expose(int) - @validate(int, int) - def multiply(self, a, b): - return a * b - - -class WSTestRoot(WSRoot): - argtypes = ArgTypes() - returntypes = ReturnTypes() - bodytypes = BodyTypes() - witherrors = WithErrors() - nested = NestedOuterApi() - misc = MiscFunctions() - - def reset(self): - self._touched = False - - @expose() - def touch(self): - self._touched = True - - -class ProtocolTestCase(unittest.TestCase): - protocol_options = {} - - def assertTypedEquals(self, a, b, convert): - if isinstance(a, six.string_types): - a = convert(a) - if isinstance(b, six.string_types): - b = convert(b) - self.assertEqual(a, b) - - def assertDateEquals(self, a, b): - self.assertTypedEquals(a, b, wsme.utils.parse_isodate) - - def assertTimeEquals(self, a, b): - self.assertTypedEquals(a, b, wsme.utils.parse_isotime) - - def assertDateTimeEquals(self, a, b): - self.assertTypedEquals(a, b, wsme.utils.parse_isodatetime) - - def assertIntEquals(self, a, b): - self.assertTypedEquals(a, b, int) - - def assertFloatEquals(self, a, b): - self.assertTypedEquals(a, b, float) - - def assertDecimalEquals(self, a, b): - self.assertTypedEquals(a, b, decimal.Decimal) - - def setUp(self): - if self.__class__.__name__ != 'ProtocolTestCase': - self.root = WSTestRoot() - self.root.getapi() - self.root.addprotocol(self.protocol, **self.protocol_options) - - self.app = TestApp(self.root.wsgiapp()) - - def test_invalid_path(self): - try: - res = self.call('invalid_function') - print(res) - assert "No error raised" - except CallException as e: - self.assertEqual(e.faultcode, 'Client') - self.assertEqual(e.faultstring.lower(), - u('unknown function name: invalid_function')) - - def test_serverside_error(self): - try: - res = self.call('witherrors/divide_by_zero') - print(res) - assert "No error raised" - except CallException as e: - self.assertEqual(e.faultcode, 'Server') - self.assertEqual(e.faultstring, zerodivisionerrormsg) - assert e.debuginfo is not None - - def test_serverside_error_nodebug(self): - self.root._debug = False - try: - res = self.call('witherrors/divide_by_zero') - print(res) - assert "No error raised" - except CallException as e: - self.assertEqual(e.faultcode, 'Server') - self.assertEqual(e.faultstring, zerodivisionerrormsg) - assert e.debuginfo is None - - def test_touch(self): - r = self.call('touch') - assert r is None, r - - def test_return_bytes(self): - r = self.call('returntypes/getbytes', _rt=wsme.types.bytes) - self.assertEqual(r, b('astring')) - - def test_return_text(self): - r = self.call('returntypes/gettext', _rt=wsme.types.text) - self.assertEqual(r, u('\xe3\x81\xae')) - - def test_return_int(self): - r = self.call('returntypes/getint') - self.assertIntEquals(r, 2) - - def test_return_float(self): - r = self.call('returntypes/getfloat') - self.assertFloatEquals(r, 3.14159265) - - def test_return_decimal(self): - r = self.call('returntypes/getdecimal') - self.assertDecimalEquals(r, '3.14159265') - - def test_return_bool_true(self): - r = self.call('returntypes/getbooltrue', _rt=bool) - assert r - - def test_return_bool_false(self): - r = self.call('returntypes/getboolfalse', _rt=bool) - assert not r - - def test_return_date(self): - r = self.call('returntypes/getdate') - self.assertDateEquals(r, datetime.date(1994, 1, 26)) - - def test_return_time(self): - r = self.call('returntypes/gettime') - self.assertTimeEquals(r, datetime.time(12)) - - def test_return_datetime(self): - r = self.call('returntypes/getdatetime') - self.assertDateTimeEquals(r, datetime.datetime(1994, 1, 26, 12)) - - def test_return_binary(self): - r = self.call('returntypes/getbinary', _rt=wsme.types.binary) - self.assertEqual(r, binarysample) - - def test_return_nested(self): - r = self.call('returntypes/getnested', _rt=NestedOuter) - self.assertEqual(r, {'inner': {'aint': 0}}) - - def test_return_bytesarray(self): - r = self.call('returntypes/getbytesarray', _rt=[six.binary_type]) - self.assertEqual(r, [b('A'), b('B'), b('C')]) - - def test_return_nestedarray(self): - r = self.call('returntypes/getnestedarray', _rt=[NestedOuter]) - self.assertEqual(r, [{'inner': {'aint': 0}}, {'inner': {'aint': 0}}]) - - def test_return_nesteddict(self): - r = self.call('returntypes/getnesteddict', - _rt={wsme.types.bytes: NestedOuter}) - self.assertEqual(r, { - b('a'): {'inner': {'aint': 0}}, - b('b'): {'inner': {'aint': 0}} - }) - - def test_return_objectarrayattribute(self): - r = self.call('returntypes/getobjectarrayattribute', _rt=NestedOuter) - self.assertEqual(r, { - 'inner': {'aint': 0}, - 'inner_array': [{'aint': 12}, {'aint': 13}] - }) - - def test_return_objectdictattribute(self): - r = self.call('returntypes/getobjectdictattribute', _rt=NestedOuter) - self.assertEqual(r, { - 'inner': {'aint': 0}, - 'inner_dict': { - '12': {'aint': 12}, - '13': {'aint': 13} - } - }) - - def test_return_enum(self): - r = self.call('returntypes/getenum', _rt=myenumtype) - self.assertEqual(r, b('v2'), r) - - def test_return_namedattrsobj(self): - r = self.call('returntypes/getnamedattrsobj', _rt=NamedAttrsObject) - self.assertEqual(r, {'attr.1': 5, 'attr.2': 6}) - - def test_setbytes(self): - assert self.call('argtypes/setbytes', value=b('astring'), - _rt=wsme.types.bytes) == b('astring') - - def test_settext(self): - assert self.call('argtypes/settext', value=u('\xe3\x81\xae'), - _rt=wsme.types.text) == u('\xe3\x81\xae') - - def test_settext_empty(self): - assert self.call('argtypes/settext', value=u(''), - _rt=wsme.types.text) == u('') - - def test_settext_none(self): - self.assertEqual( - None, - self.call('argtypes/settextnone', value=None, _rt=wsme.types.text) - ) - - def test_setint(self): - r = self.call('argtypes/setint', value=3, _rt=int) - self.assertEqual(r, 3) - - def test_setfloat(self): - assert self.call('argtypes/setfloat', value=3.54, - _rt=float) == 3.54 - - def test_setbool_true(self): - r = self.call('argtypes/setbool', value=True, _rt=bool) - assert r - - def test_setbool_false(self): - r = self.call('argtypes/setbool', value=False, _rt=bool) - assert not r - - def test_setdecimal(self): - value = decimal.Decimal('3.14') - assert self.call('argtypes/setdecimal', value=value, - _rt=decimal.Decimal) == value - - def test_setdate(self): - value = datetime.date(2008, 4, 6) - r = self.call('argtypes/setdate', value=value, - _rt=datetime.date) - self.assertEqual(r, value) - - def test_settime(self): - value = datetime.time(12, 12, 15) - r = self.call('argtypes/settime', value=value, - _rt=datetime.time) - self.assertEqual(r, datetime.time(12, 12, 15)) - - def test_setdatetime(self): - value = datetime.datetime(2008, 4, 6, 12, 12, 15) - r = self.call('argtypes/setdatetime', value=value, - _rt=datetime.datetime) - self.assertEqual(r, datetime.datetime(2008, 4, 6, 12, 12, 15)) - - def test_setbinary(self): - value = binarysample - r = self.call('argtypes/setbinary', value=(value, wsme.types.binary), - _rt=wsme.types.binary) == value - print(r) - - def test_setnested(self): - value = {'inner': {'aint': 54}} - r = self.call('argtypes/setnested', - value=(value, NestedOuter), - _rt=NestedOuter) - self.assertEqual(r, value) - - def test_setnested_nullobj(self): - value = {'inner': None} - r = self.call( - 'argtypes/setnested', - value=(value, NestedOuter), - _rt=NestedOuter - ) - self.assertEqual(r, value) - - def test_setbytesarray(self): - value = [b("1"), b("2"), b("three")] - r = self.call('argtypes/setbytesarray', - value=(value, [wsme.types.bytes]), - _rt=[wsme.types.bytes]) - self.assertEqual(r, value) - - def test_settextarray(self): - value = [u("1")] - r = self.call('argtypes/settextarray', - value=(value, [wsme.types.text]), - _rt=[wsme.types.text]) - self.assertEqual(r, value) - - def test_setdatetimearray(self): - value = [ - datetime.datetime(2008, 3, 6, 12, 12, 15), - datetime.datetime(2008, 4, 6, 2, 12, 15), - ] - r = self.call('argtypes/setdatetimearray', - value=(value, [datetime.datetime]), - _rt=[datetime.datetime]) - self.assertEqual(r, value) - - def test_setnestedarray(self): - value = [ - {'inner': {'aint': 54}}, - {'inner': {'aint': 55}}, - ] - r = self.call('argtypes/setnestedarray', - value=(value, [NestedOuter]), - _rt=[NestedOuter]) - self.assertEqual(r, value) - - def test_setnesteddict(self): - value = { - b('o1'): {'inner': {'aint': 54}}, - b('o2'): {'inner': {'aint': 55}}, - } - r = self.call('argtypes/setnesteddict', - value=(value, {six.binary_type: NestedOuter}), - _rt={six.binary_type: NestedOuter}) - print(r) - self.assertEqual(r, value) - - def test_setenum(self): - value = b('v1') - r = self.call('argtypes/setenum', value=value, - _rt=myenumtype) - self.assertEqual(r, value) - - def test_setnamedattrsobj(self): - value = {'attr.1': 10, 'attr.2': 20} - r = self.call('argtypes/setnamedattrsobj', - value=(value, NamedAttrsObject), - _rt=NamedAttrsObject) - self.assertEqual(r, value) - - def test_nested_api(self): - r = self.call('nested/inner/deepfunction', _rt=bool) - assert r is True - - def test_missing_argument(self): - try: - r = self.call('argtypes/setdatetime') - print(r) - assert "No error raised" - except CallException as e: - self.assertEqual(e.faultcode, 'Client') - self.assertEqual(e.faultstring, u('Missing argument: "value"')) - - def test_misc_multiply(self): - self.assertEqual(self.call('misc/multiply', a=5, b=2, _rt=int), 10) - - def test_html_format(self): - res = self.call('argtypes/setdatetime', _accept="text/html", - _no_result_decode=True) - self.assertEqual(res.content_type, 'text/html') - - -class RestOnlyProtocolTestCase(ProtocolTestCase): - def test_body_list(self): - r = self.call('bodytypes/setlist', body=([10], [int]), _rt=int) - self.assertEqual(r, 10) - - def test_body_dict(self): - r = self.call('bodytypes/setdict', - body=({'test': 10}, {wsme.types.text: int}), - _rt=int) - self.assertEqual(r, 10) diff --git a/wsme/tests/test_api.py b/wsme/tests/test_api.py deleted file mode 100644 index fc1e157..0000000 --- a/wsme/tests/test_api.py +++ /dev/null @@ -1,419 +0,0 @@ -# encoding=utf8 - -from six import b - -try: - import unittest2 as unittest -except ImportError: - import unittest -import webtest - -from wsme import WSRoot, expose, validate -from wsme.rest import scan_api -from wsme import types -from wsme import exc -import wsme.api as wsme_api -import wsme.types - -from wsme.tests.test_protocols import DummyProtocol - - -class TestController(unittest.TestCase): - def test_expose(self): - class MyWS(WSRoot): - @expose(int) - def getint(self): - return 1 - - assert MyWS.getint._wsme_definition.return_type == int - - def test_validate(self): - class ComplexType(object): - attr = int - - class MyWS(object): - @expose(int) - @validate(int, int, int) - def add(self, a, b, c=0): - return a + b + c - - @expose(bool) - @validate(ComplexType) - def setcplx(self, obj): - pass - - MyWS.add._wsme_definition.resolve_types(wsme.types.registry) - MyWS.setcplx._wsme_definition.resolve_types(wsme.types.registry) - args = MyWS.add._wsme_definition.arguments - - assert args[0].name == 'a' - assert args[0].datatype == int - assert args[0].mandatory - assert args[0].default is None - - assert args[1].name == 'b' - assert args[1].datatype == int - assert args[1].mandatory - assert args[1].default is None - - assert args[2].name == 'c' - assert args[2].datatype == int - assert not args[2].mandatory - assert args[2].default == 0 - - assert types.iscomplex(ComplexType) - - def test_validate_enum_with_none(self): - class Version(object): - number = types.Enum(str, 'v1', 'v2', None) - - class MyWS(WSRoot): - @expose(str) - @validate(Version) - def setcplx(self, version): - pass - - r = MyWS(['restjson']) - app = webtest.TestApp(r.wsgiapp()) - res = app.post_json('/setcplx', params={'version': {'number': 'arf'}}, - expect_errors=True, - headers={'Accept': 'application/json'}) - self.assertTrue( - res.json_body['faultstring'].startswith( - "Invalid input for field/attribute number. Value: 'arf'. \ -Value should be one of:")) - self.assertIn('v1', res.json_body['faultstring']) - self.assertIn('v2', res.json_body['faultstring']) - self.assertIn('None', res.json_body['faultstring']) - self.assertEqual(res.status_int, 400) - - def test_validate_enum_with_wrong_type(self): - class Version(object): - number = types.Enum(str, 'v1', 'v2', None) - - class MyWS(WSRoot): - @expose(str) - @validate(Version) - def setcplx(self, version): - pass - - r = MyWS(['restjson']) - app = webtest.TestApp(r.wsgiapp()) - res = app.post_json('/setcplx', params={'version': {'number': 1}}, - expect_errors=True, - headers={'Accept': 'application/json'}) - self.assertTrue( - res.json_body['faultstring'].startswith( - "Invalid input for field/attribute number. Value: '1'. \ -Value should be one of:")) - self.assertIn('v1', res.json_body['faultstring']) - self.assertIn('v2', res.json_body['faultstring']) - self.assertIn('None', res.json_body['faultstring']) - self.assertEqual(res.status_int, 400) - - def test_scan_api(self): - class NS(object): - @expose(int) - @validate(int, int) - def multiply(self, a, b): - return a * b - - class MyRoot(WSRoot): - ns = NS() - - r = MyRoot() - - api = list(scan_api(r)) - assert len(api) == 1 - path, fd, args = api[0] - assert path == ['ns', 'multiply'] - assert fd._wsme_definition.name == 'multiply' - assert args == [] - - def test_scan_subclass(self): - class MyRoot(WSRoot): - class SubClass(object): - pass - - r = MyRoot() - api = list(scan_api(r)) - - assert len(api) == 0 - - def test_scan_api_too_deep(self): - class Loop(object): - pass - - ell = Loop() - for i in range(0, 21): - nl = Loop() - nl.ell = ell - ell = nl - - class MyRoot(WSRoot): - loop = ell - - 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']) diff --git a/wsme/tests/test_exc.py b/wsme/tests/test_exc.py deleted file mode 100644 index ba903fb..0000000 --- a/wsme/tests/test_exc.py +++ /dev/null @@ -1,40 +0,0 @@ -# encoding=utf8 - -from wsme.exc import (ClientSideError, InvalidInput, MissingArgument, - UnknownArgument) -from six import u - - -def test_clientside_error(): - e = ClientSideError("Test") - - assert e.faultstring == u("Test") - - -def test_unicode_clientside_error(): - e = ClientSideError(u("\u30d5\u30a1\u30b7\u30ea")) - - assert e.faultstring == u("\u30d5\u30a1\u30b7\u30ea") - - -def test_invalidinput(): - e = InvalidInput('field', 'badvalue', "error message") - - assert e.faultstring == u( - "Invalid input for field/attribute field. Value: 'badvalue'. " - "error message" - ), e.faultstring - - -def test_missingargument(): - e = MissingArgument('argname', "error message") - - assert e.faultstring == \ - u('Missing argument: "argname": error message'), e.faultstring - - -def test_unknownargument(): - e = UnknownArgument('argname', "error message") - - assert e.faultstring == \ - u('Unknown argument: "argname": error message'), e.faultstring diff --git a/wsme/tests/test_protocols.py b/wsme/tests/test_protocols.py deleted file mode 100644 index f4b7270..0000000 --- a/wsme/tests/test_protocols.py +++ /dev/null @@ -1,70 +0,0 @@ -# encoding=utf8 - -import unittest - -from wsme import WSRoot -from wsme.protocol import getprotocol, CallContext, Protocol -import wsme.protocol - - -class DummyProtocol(Protocol): - name = 'dummy' - content_types = ['', None] - - def __init__(self): - self.hits = 0 - - def accept(self, req): - return True - - def iter_calls(self, req): - yield CallContext(req) - - def extract_path(self, context): - return ['touch'] - - def read_arguments(self, context): - self.lastreq = context.request - self.hits += 1 - return {} - - def encode_result(self, context, result): - return str(result) - - def encode_error(self, context, infos): - return str(infos) - - -def test_getprotocol(): - try: - getprotocol('invalid') - assert False, "ValueError was not raised" - except ValueError: - pass - - -class TestProtocols(unittest.TestCase): - def test_register_protocol(self): - wsme.protocol.register_protocol(DummyProtocol) - assert wsme.protocol.registered_protocols['dummy'] == DummyProtocol - - r = WSRoot() - assert len(r.protocols) == 0 - - r.addprotocol('dummy') - assert len(r.protocols) == 1 - assert r.protocols[0].__class__ == DummyProtocol - - r = WSRoot(['dummy']) - assert len(r.protocols) == 1 - assert r.protocols[0].__class__ == DummyProtocol - - def test_Protocol(self): - p = wsme.protocol.Protocol() - assert p.iter_calls(None) is None - assert p.extract_path(None) is None - assert p.read_arguments(None) is None - assert p.encode_result(None, None) is None - assert p.encode_sample_value(None, None) == ('none', 'N/A') - assert p.encode_sample_params(None) == ('none', 'N/A') - assert p.encode_sample_result(None, None) == ('none', 'N/A') diff --git a/wsme/tests/test_protocols_commons.py b/wsme/tests/test_protocols_commons.py deleted file mode 100644 index af5aa05..0000000 --- a/wsme/tests/test_protocols_commons.py +++ /dev/null @@ -1,159 +0,0 @@ -# encoding=utf8 - -import datetime -import unittest - -from wsme.api import FunctionArgument, FunctionDefinition -from wsme.rest.args import from_param, from_params, args_from_args -from wsme.exc import InvalidInput - -from wsme.types import UserType, Unset, ArrayType, DictType, Base - - -class MyBaseType(Base): - test = str - - -class MyUserType(UserType): - basetype = str - - -class DictBasedUserType(UserType): - basetype = DictType(int, int) - - -class TestProtocolsCommons(unittest.TestCase): - def test_from_param_date(self): - assert from_param(datetime.date, '2008-02-28') == \ - datetime.date(2008, 2, 28) - - def test_from_param_time(self): - assert from_param(datetime.time, '12:14:56') == \ - datetime.time(12, 14, 56) - - def test_from_param_datetime(self): - assert from_param(datetime.datetime, '2009-12-23T12:14:56') == \ - datetime.datetime(2009, 12, 23, 12, 14, 56) - - def test_from_param_usertype(self): - assert from_param(MyUserType(), 'test') == 'test' - - def test_from_params_empty(self): - assert from_params(str, {}, '', set()) is Unset - - def test_from_params_native_array(self): - class params(dict): - def getall(self, path): - return ['1', '2'] - p = params({'a': []}) - assert from_params(ArrayType(int), p, 'a', set()) == [1, 2] - - def test_from_params_empty_array(self): - assert from_params(ArrayType(int), {}, 'a', set()) is Unset - - def test_from_params_dict(self): - value = from_params( - DictType(int, str), - {'a[2]': 'a2', 'a[3]': 'a3'}, - 'a', - set() - ) - assert value == {2: 'a2', 3: 'a3'}, value - - def test_from_params_dict_unset(self): - assert from_params(DictType(int, str), {}, 'a', set()) is Unset - - def test_from_params_usertype(self): - value = from_params( - DictBasedUserType(), - {'a[2]': '2'}, - 'a', - set() - ) - self.assertEqual(value, {2: 2}) - - def test_args_from_args_usertype(self): - - class FakeType(UserType): - name = 'fake-type' - basetype = int - - fake_type = FakeType() - fd = FunctionDefinition(FunctionDefinition) - fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, 0)) - - new_args = args_from_args(fd, [1], {}) - self.assertEqual([1], new_args[0]) - - # can't convert str to int - try: - args_from_args(fd, ['invalid-argument'], {}) - except InvalidInput as e: - assert fake_type.name in str(e) - else: - self.fail('Should have thrown an InvalidInput') - - def test_args_from_args_custom_exc(self): - - class FakeType(UserType): - name = 'fake-type' - basetype = int - - def validate(self, value): - if value < 10: - raise ValueError('should be greater than 10') - - def frombasetype(self, value): - self.validate(value) - - fake_type = FakeType() - fd = FunctionDefinition(FunctionDefinition) - fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, 0)) - - try: - args_from_args(fd, [9], {}) - except InvalidInput as e: - assert fake_type.name in str(e) - assert 'Error: should be greater than 10' in str(e) - else: - self.fail('Should have thrown an InvalidInput') - - def test_args_from_args_array_type(self): - fake_type = ArrayType(MyBaseType) - fd = FunctionDefinition(FunctionDefinition) - fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, [])) - try: - args_from_args(fd, [['invalid-argument']], {}) - except InvalidInput as e: - assert ArrayType.__name__ in str(e) - else: - self.fail('Should have thrown an InvalidInput') - - -class ArgTypeConversion(unittest.TestCase): - - def test_int_zero(self): - self.assertEqual(0, from_param(int, 0)) - self.assertEqual(0, from_param(int, '0')) - - def test_int_nonzero(self): - self.assertEqual(1, from_param(int, 1)) - self.assertEqual(1, from_param(int, '1')) - - def test_int_none(self): - self.assertEqual(None, from_param(int, None)) - - def test_float_zero(self): - self.assertEqual(0.0, from_param(float, 0)) - self.assertEqual(0.0, from_param(float, 0.0)) - self.assertEqual(0.0, from_param(float, '0')) - self.assertEqual(0.0, from_param(float, '0.0')) - - def test_float_nonzero(self): - self.assertEqual(1.0, from_param(float, 1)) - self.assertEqual(1.0, from_param(float, 1.0)) - self.assertEqual(1.0, from_param(float, '1')) - self.assertEqual(1.0, from_param(float, '1.0')) - - def test_float_none(self): - self.assertEqual(None, from_param(float, None)) diff --git a/wsme/tests/test_restjson.py b/wsme/tests/test_restjson.py deleted file mode 100644 index 7afbb86..0000000 --- a/wsme/tests/test_restjson.py +++ /dev/null @@ -1,779 +0,0 @@ -import base64 -import datetime -import decimal - -import wsme.tests.protocol - -try: - import simplejson as json -except ImportError: - 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" diff --git a/wsme/tests/test_restxml.py b/wsme/tests/test_restxml.py deleted file mode 100644 index b677a65..0000000 --- a/wsme/tests/test_restxml.py +++ /dev/null @@ -1,211 +0,0 @@ -import decimal -import datetime -import base64 - -from six import u, b -import six - -import wsme.tests.protocol -from wsme.utils import parse_isodatetime, parse_isodate, parse_isotime -from wsme.types import isarray, isdict, isusertype, register_type - -from wsme.rest.xml import fromxml, toxml - -try: - import xml.etree.ElementTree as et -except ImportError: - import cElementTree as et # noqa - - -def dumpxml(key, obj, datatype=None): - el = et.Element(key) - if isinstance(obj, tuple): - obj, datatype = obj - if isinstance(datatype, list): - for item in obj: - el.append(dumpxml('item', item, datatype[0])) - elif isinstance(datatype, dict): - key_type, value_type = list(datatype.items())[0] - for item in obj.items(): - node = et.SubElement(el, 'item') - node.append(dumpxml('key', item[0], key_type)) - node.append(dumpxml('value', item[1], value_type)) - elif datatype == wsme.types.binary: - el.text = base64.encodestring(obj).decode('ascii') - elif isinstance(obj, wsme.types.bytes): - el.text = obj.decode('ascii') - elif isinstance(obj, wsme.types.text): - el.text = obj - elif type(obj) in (int, float, bool, decimal.Decimal): - el.text = six.text_type(obj) - elif type(obj) in (datetime.date, datetime.time, datetime.datetime): - el.text = obj.isoformat() - elif isinstance(obj, type(None)): - el.set('nil', 'true') - elif hasattr(datatype, '_wsme_attributes'): - for attr in datatype._wsme_attributes: - name = attr.name - if name not in obj: - continue - o = obj[name] - el.append(dumpxml(name, o, attr.datatype)) - elif type(obj) == dict: - for name, value in obj.items(): - el.append(dumpxml(name, value)) - print(obj, datatype, et.tostring(el)) - return el - - -def loadxml(el, datatype): - print(el, datatype, len(el)) - if el.get('nil') == 'true': - return None - if isinstance(datatype, list): - return [loadxml(item, datatype[0]) for item in el.findall('item')] - elif isarray(datatype): - return [ - loadxml(item, datatype.item_type) for item in el.findall('item') - ] - elif isinstance(datatype, dict): - key_type, value_type = list(datatype.items())[0] - return dict(( - (loadxml(item.find('key'), key_type), - loadxml(item.find('value'), value_type)) - for item in el.findall('item') - )) - elif isdict(datatype): - return dict(( - (loadxml(item.find('key'), datatype.key_type), - loadxml(item.find('value'), datatype.value_type)) - for item in el.findall('item') - )) - elif isdict(datatype): - return dict(( - (loadxml(item.find('key'), datatype.key_type), - loadxml(item.find('value'), datatype.value_type)) - for item in el.findall('item') - )) - elif len(el): - d = {} - for attr in datatype._wsme_attributes: - name = attr.name - child = el.find(name) - print(name, attr, child) - if child is not None: - d[name] = loadxml(child, attr.datatype) - print(d) - return d - else: - if datatype == wsme.types.binary: - return base64.decodestring(el.text.encode('ascii')) - if isusertype(datatype): - datatype = datatype.basetype - if datatype == datetime.date: - return parse_isodate(el.text) - if datatype == datetime.time: - return parse_isotime(el.text) - if datatype == datetime.datetime: - return parse_isodatetime(el.text) - if datatype == wsme.types.text: - return datatype(el.text if el.text else u('')) - if datatype == bool: - return el.text.lower() != 'false' - if datatype is None: - return el.text - if datatype is wsme.types.bytes: - return el.text.encode('ascii') - return datatype(el.text) - - -class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase): - protocol = 'restxml' - - def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False, - body=None, **kw): - if body: - el = dumpxml('body', body) - else: - el = dumpxml('parameters', kw) - content = et.tostring(el) - headers = { - 'Content-Type': 'text/xml', - } - if _accept is not None: - headers['Accept'] = _accept - res = self.app.post( - '/' + fpath, - content, - headers=headers, - expect_errors=True) - print("Received:", res.body) - - if _no_result_decode: - return res - - el = et.fromstring(res.body) - if el.tag == 'error': - raise wsme.tests.protocol.CallException( - el.find('faultcode').text, - el.find('faultstring').text, - el.find('debuginfo') is not None and - el.find('debuginfo').text or None - ) - - else: - return loadxml(et.fromstring(res.body), _rt) - - def test_encode_sample_value(self): - class MyType(object): - aint = int - atext = wsme.types.text - - register_type(MyType) - - value = MyType() - value.aint = 5 - value.atext = u('test') - - language, sample = wsme.rest.xml.encode_sample_value( - MyType, value, True) - print(language, sample) - - assert language == 'xml' - assert sample == b("""<value> - <aint>5</aint> - <atext>test</atext> -</value>""") - - def test_encode_sample_params(self): - lang, content = wsme.rest.xml.encode_sample_params( - [('a', int, 2)], True) - assert lang == 'xml', lang - assert content == b('<parameters>\n <a>2</a>\n</parameters>'), content - - def test_encode_sample_result(self): - lang, content = wsme.rest.xml.encode_sample_result(int, 2, True) - assert lang == 'xml', lang - assert content == b('<result>2</result>'), content - - def test_nil_fromxml(self): - for dt in ( - str, [int], {int: str}, bool, - datetime.date, datetime.time, datetime.datetime): - e = et.Element('value', nil='true') - assert fromxml(dt, e) is None - - def test_nil_toxml(self): - for dt in ( - wsme.types.bytes, - [int], {int: str}, bool, - datetime.date, datetime.time, datetime.datetime): - x = et.tostring(toxml(dt, 'value', None)) - assert x == b('<value nil="true" />'), x - - def test_unset_attrs(self): - class AType(object): - someattr = wsme.types.bytes - - wsme.types.register_type(AType) - - x = et.tostring(toxml(AType, 'value', AType())) - assert x == b('<value />'), x diff --git a/wsme/tests/test_root.py b/wsme/tests/test_root.py deleted file mode 100644 index 8ccb0c1..0000000 --- a/wsme/tests/test_root.py +++ /dev/null @@ -1,122 +0,0 @@ -# encoding=utf8 - -import unittest - -from wsme import WSRoot -import wsme.protocol -import wsme.rest.protocol -from wsme.root import default_prepare_response_body - -from six import b, u -from webob import Request - - -class TestRoot(unittest.TestCase): - def test_default_transaction(self): - import transaction - root = WSRoot(transaction=True) - assert root._transaction is transaction - - txn = root.begin() - txn.abort() - - def test_default_prepare_response_body(self): - default_prepare_response_body(None, [b('a')]) == b('a') - default_prepare_response_body(None, [b('a'), b('b')]) == b('a\nb') - default_prepare_response_body(None, [u('a')]) == u('a') - default_prepare_response_body(None, [u('a'), u('b')]) == u('a\nb') - - def test_protocol_selection_error(self): - class P(wsme.protocol.Protocol): - name = "test" - - def accept(self, r): - raise Exception('test') - - root = WSRoot() - root.addprotocol(P()) - - from webob import Request - req = Request.blank('/test?check=a&check=b&name=Bob') - res = root._handle_request(req) - assert res.status_int == 500 - assert res.content_type == 'text/plain' - assert (res.text == - 'Unexpected error while selecting protocol: test'), req.text - - def test_protocol_selection_accept_mismatch(self): - """Verify that we get a 406 error on wrong Accept header.""" - class P(wsme.protocol.Protocol): - name = "test" - - def accept(self, r): - return False - - root = WSRoot() - root.addprotocol(wsme.rest.protocol.RestProtocol()) - root.addprotocol(P()) - - req = Request.blank('/test?check=a&check=b&name=Bob') - req.method = 'GET' - res = root._handle_request(req) - assert res.status_int == 406 - assert res.content_type == 'text/plain' - assert res.text.startswith( - 'None of the following protocols can handle this request' - ), req.text - - def test_protocol_selection_content_type_mismatch(self): - """Verify that we get a 415 error on wrong Content-Type header.""" - class P(wsme.protocol.Protocol): - name = "test" - - def accept(self, r): - return False - - root = WSRoot() - root.addprotocol(wsme.rest.protocol.RestProtocol()) - root.addprotocol(P()) - - req = Request.blank('/test?check=a&check=b&name=Bob') - req.method = 'POST' - req.headers['Content-Type'] = "test/unsupported" - res = root._handle_request(req) - assert res.status_int == 415 - assert res.content_type == 'text/plain' - assert res.text.startswith( - 'Unacceptable Content-Type: test/unsupported not in' - ), req.text - - def test_protocol_selection_get_method(self): - class P(wsme.protocol.Protocol): - name = "test" - - def accept(self, r): - return True - - root = WSRoot() - root.addprotocol(wsme.rest.protocol.RestProtocol()) - root.addprotocol(P()) - - req = Request.blank('/test?check=a&check=b&name=Bob') - req.method = 'GET' - req.headers['Accept'] = 'test/fake' - p = root._select_protocol(req) - assert p.name == "test" - - def test_protocol_selection_post_method(self): - class P(wsme.protocol.Protocol): - name = "test" - - def accept(self, r): - return True - - root = WSRoot() - root.addprotocol(wsme.rest.protocol.RestProtocol()) - root.addprotocol(P()) - - req = Request.blank('/test?check=a&check=b&name=Bob') - req.headers['Content-Type'] = 'test/fake' - req.method = 'POST' - p = root._select_protocol(req) - assert p.name == "test" diff --git a/wsme/tests/test_spore.py b/wsme/tests/test_spore.py deleted file mode 100644 index d6509f3..0000000 --- a/wsme/tests/test_spore.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest - -try: - import simplejson as json -except ImportError: - import json - -from wsme.tests.protocol import WSTestRoot -import wsme.tests.test_restjson -import wsme.spore - - -class TestSpore(unittest.TestCase): - def test_spore(self): - spore = wsme.spore.getdesc(WSTestRoot()) - - print(spore) - - spore = json.loads(spore) - - assert len(spore['methods']) == 51, str(len(spore['methods'])) - - m = spore['methods']['argtypes_setbytesarray'] - assert m['path'] == 'argtypes/setbytesarray', m['path'] - assert m['optional_params'] == ['value'] - assert m['method'] == 'POST' - - m = spore['methods']['argtypes_setdecimal'] - assert m['path'] == 'argtypes/setdecimal' - assert m['required_params'] == ['value'] - assert m['method'] == 'GET' - - m = spore['methods']['crud_create'] - assert m['path'] == 'crud' - assert m['method'] == 'PUT' - assert m['optional_params'] == ['data'] - - m = spore['methods']['crud_read'] - assert m['path'] == 'crud' - assert m['method'] == 'GET' - assert m['required_params'] == ['ref'] - - m = spore['methods']['crud_update'] - assert m['path'] == 'crud' - assert m['method'] == 'POST' - assert m['optional_params'] == ['data'] - - m = spore['methods']['crud_delete'] - assert m['path'] == 'crud' - assert m['method'] == 'DELETE' - assert m['optional_params'] == ['ref'] diff --git a/wsme/tests/test_types.py b/wsme/tests/test_types.py deleted file mode 100644 index 4a07130..0000000 --- a/wsme/tests/test_types.py +++ /dev/null @@ -1,667 +0,0 @@ -import re -try: - import unittest2 as unittest -except ImportError: - import unittest -import six - -from wsme import exc -from wsme import types - - -def gen_class(): - d = {} - exec('''class tmp(object): pass''', d) - return d['tmp'] - - -class TestTypes(unittest.TestCase): - def setUp(self): - types.registry = types.Registry() - - def test_default_usertype(self): - class MyType(types.UserType): - basetype = str - - My = MyType() - - assert My.validate('a') == 'a' - assert My.tobasetype('a') == 'a' - assert My.frombasetype('a') == 'a' - - def test_unset(self): - u = types.Unset - - assert not u - - def test_flat_type(self): - class Flat(object): - aint = int - abytes = six.binary_type - atext = six.text_type - afloat = float - - types.register_type(Flat) - - assert len(Flat._wsme_attributes) == 4 - attrs = Flat._wsme_attributes - print(attrs) - - assert attrs[0].key == 'aint' - assert attrs[0].name == 'aint' - assert isinstance(attrs[0], types.wsattr) - assert attrs[0].datatype == int - assert attrs[0].mandatory is False - assert attrs[1].key == 'abytes' - assert attrs[1].name == 'abytes' - assert attrs[2].key == 'atext' - assert attrs[2].name == 'atext' - assert attrs[3].key == 'afloat' - assert attrs[3].name == 'afloat' - - def test_private_attr(self): - class WithPrivateAttrs(object): - _private = 12 - - types.register_type(WithPrivateAttrs) - - assert len(WithPrivateAttrs._wsme_attributes) == 0 - - def test_attribute_order(self): - class ForcedOrder(object): - _wsme_attr_order = ('a2', 'a1', 'a3') - a1 = int - a2 = int - a3 = int - - types.register_type(ForcedOrder) - - print(ForcedOrder._wsme_attributes) - assert ForcedOrder._wsme_attributes[0].key == 'a2' - assert ForcedOrder._wsme_attributes[1].key == 'a1' - assert ForcedOrder._wsme_attributes[2].key == 'a3' - - c = gen_class() - print(c) - types.register_type(c) - del c._wsme_attributes - - c.a2 = int - c.a1 = int - c.a3 = int - - types.register_type(c) - - assert c._wsme_attributes[0].key == 'a1', c._wsme_attributes[0].key - assert c._wsme_attributes[1].key == 'a2' - assert c._wsme_attributes[2].key == 'a3' - - def test_wsproperty(self): - class WithWSProp(object): - def __init__(self): - self._aint = 0 - - def get_aint(self): - return self._aint - - def set_aint(self, value): - self._aint = value - - aint = types.wsproperty(int, get_aint, set_aint, mandatory=True) - - types.register_type(WithWSProp) - - print(WithWSProp._wsme_attributes) - assert len(WithWSProp._wsme_attributes) == 1 - a = WithWSProp._wsme_attributes[0] - assert a.key == 'aint' - assert a.datatype == int - assert a.mandatory - - o = WithWSProp() - o.aint = 12 - - assert o.aint == 12 - - def test_nested(self): - class Inner(object): - aint = int - - class Outer(object): - inner = Inner - - types.register_type(Outer) - - assert hasattr(Inner, '_wsme_attributes') - assert len(Inner._wsme_attributes) == 1 - - def test_inspect_with_inheritance(self): - class Parent(object): - parent_attribute = int - - class Child(Parent): - child_attribute = int - - types.register_type(Parent) - types.register_type(Child) - - assert len(Child._wsme_attributes) == 2 - - def test_selfreftype(self): - class SelfRefType(object): - pass - - SelfRefType.parent = SelfRefType - - types.register_type(SelfRefType) - - def test_inspect_with_property(self): - class AType(object): - @property - def test(self): - return 'test' - - types.register_type(AType) - - assert len(AType._wsme_attributes) == 0 - assert AType().test == 'test' - - def test_enum(self): - aenum = types.Enum(str, 'v1', 'v2') - assert aenum.basetype is str - - class AType(object): - a = aenum - - types.register_type(AType) - - assert AType.a.datatype is aenum - - obj = AType() - obj.a = 'v1' - assert obj.a == 'v1', repr(obj.a) - - self.assertRaisesRegexp(exc.InvalidInput, - "Invalid input for field/attribute a. \ -Value: 'v3'. Value should be one of: v., v.", - setattr, - obj, - 'a', - 'v3') - - def test_attribute_validation(self): - class AType(object): - alist = [int] - aint = int - - types.register_type(AType) - - obj = AType() - - obj.alist = [1, 2, 3] - assert obj.alist == [1, 2, 3] - obj.aint = 5 - assert obj.aint == 5 - - self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', 12) - self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', [2, 'a']) - - def test_attribute_validation_minimum(self): - class ATypeInt(object): - attr = types.IntegerType(minimum=1, maximum=5) - - types.register_type(ATypeInt) - - obj = ATypeInt() - obj.attr = 2 - - # comparison between 'zero' value and intger minimum (1) raises a - # TypeError which must be wrapped into an InvalidInput exception - self.assertRaises(exc.InvalidInput, setattr, obj, 'attr', 'zero') - - def test_text_attribute_conversion(self): - class SType(object): - atext = types.text - abytes = types.bytes - - types.register_type(SType) - - obj = SType() - - obj.atext = six.b('somebytes') - assert obj.atext == six.u('somebytes') - assert isinstance(obj.atext, types.text) - - obj.abytes = six.u('sometext') - assert obj.abytes == six.b('sometext') - assert isinstance(obj.abytes, types.bytes) - - def test_named_attribute(self): - class ABCDType(object): - a_list = types.wsattr([int], name='a.list') - astr = str - - types.register_type(ABCDType) - - assert len(ABCDType._wsme_attributes) == 2 - attrs = ABCDType._wsme_attributes - - assert attrs[0].key == 'a_list', attrs[0].key - assert attrs[0].name == 'a.list', attrs[0].name - assert attrs[1].key == 'astr', attrs[1].key - assert attrs[1].name == 'astr', attrs[1].name - - def test_wsattr_del(self): - class MyType(object): - a = types.wsattr(int) - - types.register_type(MyType) - - value = MyType() - - value.a = 5 - assert value.a == 5 - del value.a - assert value.a is types.Unset - - def test_validate_dict(self): - assert types.validate_value({int: str}, {1: '1', 5: '5'}) - - self.assertRaises(ValueError, types.validate_value, - {int: str}, []) - - assert types.validate_value({int: str}, {'1': '1', 5: '5'}) - - self.assertRaises(ValueError, types.validate_value, - {int: str}, {1: 1, 5: '5'}) - - def test_validate_list_valid(self): - assert types.validate_value([int], [1, 2]) - assert types.validate_value([int], ['5']) - - def test_validate_list_empty(self): - assert types.validate_value([int], []) == [] - - def test_validate_list_none(self): - v = types.ArrayType(int) - assert v.validate(None) is None - - def test_validate_list_invalid_member(self): - self.assertRaises(ValueError, types.validate_value, [int], - ['not-a-number']) - - def test_validate_list_invalid_type(self): - self.assertRaises(ValueError, types.validate_value, [int], 1) - - def test_validate_float(self): - self.assertEqual(types.validate_value(float, 1), 1.0) - self.assertEqual(types.validate_value(float, '1'), 1.0) - self.assertEqual(types.validate_value(float, 1.1), 1.1) - self.assertRaises(ValueError, types.validate_value, float, []) - self.assertRaises(ValueError, types.validate_value, float, - 'not-a-float') - - def test_validate_int(self): - self.assertEqual(types.validate_value(int, 1), 1) - self.assertEqual(types.validate_value(int, '1'), 1) - self.assertEqual(types.validate_value(int, six.u('1')), 1) - self.assertRaises(ValueError, types.validate_value, int, 1.1) - - def test_validate_integer_type(self): - v = types.IntegerType(minimum=1, maximum=10) - v.validate(1) - v.validate(5) - v.validate(10) - self.assertRaises(ValueError, v.validate, 0) - self.assertRaises(ValueError, v.validate, 11) - - def test_validate_string_type(self): - v = types.StringType(min_length=1, max_length=10, - pattern='^[a-zA-Z0-9]*$') - v.validate('1') - v.validate('12345') - v.validate('1234567890') - self.assertRaises(ValueError, v.validate, '') - self.assertRaises(ValueError, v.validate, '12345678901') - - # Test a pattern validation - v.validate('a') - v.validate('A') - self.assertRaises(ValueError, v.validate, '_') - - def test_validate_string_type_precompile(self): - precompile = re.compile('^[a-zA-Z0-9]*$') - v = types.StringType(min_length=1, max_length=10, - pattern=precompile) - - # Test a pattern validation - v.validate('a') - v.validate('A') - self.assertRaises(ValueError, v.validate, '_') - - def test_validate_string_type_pattern_exception_message(self): - regex = '^[a-zA-Z0-9]*$' - v = types.StringType(pattern=regex) - try: - v.validate('_') - self.assertFail() - except ValueError as e: - self.assertIn(regex, str(e)) - - def test_validate_ipv4_address_type(self): - v = types.IPv4AddressType() - self.assertEqual(v.validate('127.0.0.1'), '127.0.0.1') - self.assertEqual(v.validate('192.168.0.1'), '192.168.0.1') - self.assertEqual(v.validate(u'8.8.1.1'), u'8.8.1.1') - self.assertRaises(ValueError, v.validate, '') - self.assertRaises(ValueError, v.validate, 'foo') - self.assertRaises(ValueError, v.validate, - '2001:0db8:bd05:01d2:288a:1fc0:0001:10ee') - self.assertRaises(ValueError, v.validate, '1.2.3') - - def test_validate_ipv6_address_type(self): - v = types.IPv6AddressType() - self.assertEqual(v.validate('0:0:0:0:0:0:0:1'), - '0:0:0:0:0:0:0:1') - self.assertEqual(v.validate(u'0:0:0:0:0:0:0:1'), u'0:0:0:0:0:0:0:1') - self.assertEqual(v.validate('2001:0db8:bd05:01d2:288a:1fc0:0001:10ee'), - '2001:0db8:bd05:01d2:288a:1fc0:0001:10ee') - self.assertRaises(ValueError, v.validate, '') - self.assertRaises(ValueError, v.validate, 'foo') - self.assertRaises(ValueError, v.validate, '192.168.0.1') - self.assertRaises(ValueError, v.validate, '0:0:0:0:0:0:1') - - def test_validate_uuid_type(self): - v = types.UuidType() - self.assertEqual(v.validate('6a0a707c-45ef-4758-b533-e55adddba8ce'), - '6a0a707c-45ef-4758-b533-e55adddba8ce') - self.assertEqual(v.validate('6a0a707c45ef4758b533e55adddba8ce'), - '6a0a707c-45ef-4758-b533-e55adddba8ce') - self.assertRaises(ValueError, v.validate, '') - self.assertRaises(ValueError, v.validate, 'foo') - self.assertRaises(ValueError, v.validate, - '6a0a707c-45ef-4758-b533-e55adddba8ce-a') - - def test_register_invalid_array(self): - self.assertRaises(ValueError, types.register_type, []) - self.assertRaises(ValueError, types.register_type, [int, str]) - self.assertRaises(AttributeError, types.register_type, [1]) - - def test_register_invalid_dict(self): - self.assertRaises(ValueError, types.register_type, {}) - self.assertRaises(ValueError, types.register_type, - {int: str, str: int}) - self.assertRaises(ValueError, types.register_type, - {types.Unset: str}) - - def test_list_attribute_no_auto_register(self): - class MyType(object): - aint = int - - assert not hasattr(MyType, '_wsme_attributes') - - self.assertRaises(TypeError, types.list_attributes, MyType) - - assert not hasattr(MyType, '_wsme_attributes') - - def test_list_of_complextypes(self): - class A(object): - bs = types.wsattr(['B']) - - class B(object): - i = int - - types.register_type(A) - types.register_type(B) - - assert A.bs.datatype.item_type is B - - def test_cross_referenced_types(self): - class A(object): - b = types.wsattr('B') - - class B(object): - a = A - - types.register_type(A) - types.register_type(B) - - assert A.b.datatype is B - - def test_base(self): - class B1(types.Base): - b2 = types.wsattr('B2') - - class B2(types.Base): - b2 = types.wsattr('B2') - - assert B1.b2.datatype is B2, repr(B1.b2.datatype) - assert B2.b2.datatype is B2 - - def test_base_init(self): - class C1(types.Base): - s = six.text_type - - c = C1(s=six.u('test')) - assert c.s == six.u('test') - - def test_array_eq(self): - ell = [types.ArrayType(str)] - assert types.ArrayType(str) in ell - - 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)) diff --git a/wsme/tests/test_utils.py b/wsme/tests/test_utils.py deleted file mode 100644 index f9464c8..0000000 --- a/wsme/tests/test_utils.py +++ /dev/null @@ -1,97 +0,0 @@ -import datetime -import unittest -import pytz - -from wsme import utils - - -class TestUtils(unittest.TestCase): - def test_parse_isodate(self): - good_dates = [ - ('2008-02-01', datetime.date(2008, 2, 1)), - ('2009-01-04', datetime.date(2009, 1, 4)), - ] - ill_formatted_dates = [ - '24-12-2004' - ] - out_of_range_dates = [ - '0000-00-00', - '2012-02-30', - ] - for s, d in good_dates: - assert utils.parse_isodate(s) == d - for s in ill_formatted_dates + out_of_range_dates: - self.assertRaises(ValueError, utils.parse_isodate, s) - - def test_parse_isotime(self): - good_times = [ - ('12:03:54', datetime.time(12, 3, 54)), - ('23:59:59.000004', datetime.time(23, 59, 59, 4)), - ('01:02:03+00:00', datetime.time(1, 2, 3, 0, pytz.UTC)), - ('01:02:03+23:59', datetime.time(1, 2, 3, 0, - pytz.FixedOffset(1439))), - ('01:02:03-23:59', datetime.time(1, 2, 3, 0, - pytz.FixedOffset(-1439))), - ] - ill_formatted_times = [ - '24-12-2004' - ] - out_of_range_times = [ - '32:12:00', - '00:54:60', - '01:02:03-24:00', - '01:02:03+24:00', - ] - for s, t in good_times: - assert utils.parse_isotime(s) == t - for s in ill_formatted_times + out_of_range_times: - self.assertRaises(ValueError, utils.parse_isotime, s) - - def test_parse_isodatetime(self): - good_datetimes = [ - ('2008-02-12T12:03:54', - datetime.datetime(2008, 2, 12, 12, 3, 54)), - ('2012-05-14T23:59:59.000004', - datetime.datetime(2012, 5, 14, 23, 59, 59, 4)), - ('1856-07-10T01:02:03+00:00', - datetime.datetime(1856, 7, 10, 1, 2, 3, 0, pytz.UTC)), - ('1856-07-10T01:02:03+23:59', - datetime.datetime(1856, 7, 10, 1, 2, 3, 0, - pytz.FixedOffset(1439))), - ('1856-07-10T01:02:03-23:59', - datetime.datetime(1856, 7, 10, 1, 2, 3, 0, - pytz.FixedOffset(-1439))), - ] - ill_formatted_datetimes = [ - '24-32-2004', - '1856-07-10+33:00' - ] - out_of_range_datetimes = [ - '2008-02-12T32:12:00', - '2012-13-12T00:54:60', - ] - for s, t in good_datetimes: - assert utils.parse_isodatetime(s) == t - for s in ill_formatted_datetimes + out_of_range_datetimes: - self.assertRaises(ValueError, utils.parse_isodatetime, s) - - def test_validator_with_valid_code(self): - valid_code = 404 - self.assertTrue( - utils.is_valid_code(valid_code), - "Valid status code not detected" - ) - - def test_validator_with_invalid_int_code(self): - invalid_int_code = 648 - self.assertFalse( - utils.is_valid_code(invalid_int_code), - "Invalid status code not detected" - ) - - def test_validator_with_invalid_str_code(self): - invalid_str_code = '404' - self.assertFalse( - utils.is_valid_code(invalid_str_code), - "Invalid status code not detected" - ) diff --git a/wsme/types.py b/wsme/types.py deleted file mode 100644 index 0a5fc02..0000000 --- a/wsme/types.py +++ /dev/null @@ -1,840 +0,0 @@ -import base64 -import datetime -import decimal -import inspect -import logging -import netaddr -import re -import six -import sys -import uuid -import weakref - -from wsme import exc - -log = logging.getLogger(__name__) - -#: The 'str' (python 2) or 'bytes' (python 3) type. -#: Its use should be restricted to -#: pure ascii strings as the protocols will generally not be -#: be able to send non-unicode strings. -#: To transmit binary strings, use the :class:`binary` type -bytes = six.binary_type - -#: Unicode string. -text = six.text_type - - -class ArrayType(object): - def __init__(self, item_type): - if iscomplex(item_type): - self._item_type = weakref.ref(item_type) - else: - self._item_type = item_type - - def __hash__(self): - return hash(self.item_type) - - def __eq__(self, other): - return isinstance(other, ArrayType) \ - and self.item_type == other.item_type - - def sample(self): - return [getattr(self.item_type, 'sample', self.item_type)()] - - @property - def item_type(self): - if isinstance(self._item_type, weakref.ref): - return self._item_type() - else: - return self._item_type - - def validate(self, value): - if value is None: - return - if not isinstance(value, list): - raise ValueError("Wrong type. Expected '[%s]', got '%s'" % ( - self.item_type, type(value) - )) - return [ - validate_value(self.item_type, item) - for item in value - ] - - -class DictType(object): - def __init__(self, key_type, value_type): - if key_type not in pod_types: - raise ValueError("Dictionaries key can only be a pod type") - self.key_type = key_type - if iscomplex(value_type): - self._value_type = weakref.ref(value_type) - else: - self._value_type = value_type - - def __hash__(self): - return hash((self.key_type, self.value_type)) - - def sample(self): - key = getattr(self.key_type, 'sample', self.key_type)() - value = getattr(self.value_type, 'sample', self.value_type)() - return {key: value} - - @property - def value_type(self): - if isinstance(self._value_type, weakref.ref): - return self._value_type() - else: - return self._value_type - - def validate(self, value): - if not isinstance(value, dict): - raise ValueError("Wrong type. Expected '{%s: %s}', got '%s'" % ( - self.key_type, self.value_type, type(value) - )) - return dict(( - ( - validate_value(self.key_type, key), - validate_value(self.value_type, v) - ) for key, v in value.items() - )) - - -class UserType(object): - basetype = None - name = None - - def validate(self, value): - return value - - def tobasetype(self, value): - return value - - def frombasetype(self, value): - return value - - -def isusertype(class_): - return isinstance(class_, UserType) - - -class BinaryType(UserType): - """ - A user type that use base64 strings to carry binary data. - """ - basetype = bytes - name = 'binary' - - def tobasetype(self, value): - if value is None: - return None - return base64.encodestring(value) - - def frombasetype(self, value): - if value is None: - return None - return base64.decodestring(value) - - -#: The binary almost-native type -binary = BinaryType() - - -class IntegerType(UserType): - """ - A simple integer type. Can validate a value range. - - :param minimum: Possible minimum value - :param maximum: Possible maximum value - - Example:: - - Price = IntegerType(minimum=1) - - """ - basetype = int - name = "integer" - - def __init__(self, minimum=None, maximum=None): - self.minimum = minimum - self.maximum = maximum - - @staticmethod - def frombasetype(value): - return int(value) if value is not None else None - - def validate(self, value): - if self.minimum is not None and value < self.minimum: - error = 'Value should be greater or equal to %s' % self.minimum - raise ValueError(error) - - if self.maximum is not None and value > self.maximum: - error = 'Value should be lower or equal to %s' % self.maximum - raise ValueError(error) - - return value - - -class StringType(UserType): - """ - A simple string type. Can validate a length and a pattern. - - :param min_length: Possible minimum length - :param max_length: Possible maximum length - :param pattern: Possible string pattern - - Example:: - - Name = StringType(min_length=1, pattern='^[a-zA-Z ]*$') - - """ - basetype = six.string_types - name = "string" - - def __init__(self, min_length=None, max_length=None, pattern=None): - self.min_length = min_length - self.max_length = max_length - if isinstance(pattern, six.string_types): - self.pattern = re.compile(pattern) - else: - self.pattern = pattern - - def validate(self, value): - if not isinstance(value, self.basetype): - error = 'Value should be string' - raise ValueError(error) - - if self.min_length is not None and len(value) < self.min_length: - error = 'Value should have a minimum character requirement of %s' \ - % self.min_length - raise ValueError(error) - - if self.max_length is not None and len(value) > self.max_length: - error = 'Value should have a maximum character requirement of %s' \ - % self.max_length - raise ValueError(error) - - if self.pattern is not None and not self.pattern.search(value): - error = 'Value should match the pattern %s' % self.pattern.pattern - raise ValueError(error) - - return value - - -class IPv4AddressType(UserType): - """ - A simple IPv4 type. - """ - basetype = six.string_types - name = "ipv4address" - - @staticmethod - def validate(value): - try: - netaddr.IPAddress(value, version=4, flags=netaddr.INET_PTON) - except netaddr.AddrFormatError: - error = 'Value should be IPv4 format' - raise ValueError(error) - else: - return value - - -class IPv6AddressType(UserType): - """ - A simple IPv6 type. - - This type represents IPv6 addresses in the short format. - """ - basetype = six.string_types - name = "ipv6address" - - @staticmethod - def validate(value): - try: - netaddr.IPAddress(value, version=6, flags=netaddr.INET_PTON) - except netaddr.AddrFormatError: - error = 'Value should be IPv6 format' - raise ValueError(error) - else: - return value - - -class UuidType(UserType): - """ - A simple UUID type. - - This type allows not only UUID having dashes but also UUID not - having dashes. For example, '6a0a707c-45ef-4758-b533-e55adddba8ce' - and '6a0a707c45ef4758b533e55adddba8ce' are distinguished as valid. - """ - basetype = six.string_types - name = "uuid" - - @staticmethod - def validate(value): - try: - return six.text_type((uuid.UUID(value))) - except (TypeError, ValueError, AttributeError): - error = 'Value should be UUID format' - raise ValueError(error) - - -class Enum(UserType): - """ - A simple enumeration type. Can be based on any non-complex type. - - :param basetype: The actual data type - :param values: A set of possible values - - If nullable, 'None' should be added the values set. - - Example:: - - Gender = Enum(str, 'male', 'female') - Specie = Enum(str, 'cat', 'dog') - - """ - def __init__(self, basetype, *values, **kw): - self.basetype = basetype - self.values = set(values) - name = kw.pop('name', None) - if name is None: - name = "Enum(%s)" % ', '.join((six.text_type(v) for v in values)) - self.name = name - - def validate(self, value): - if value not in self.values: - raise ValueError("Value should be one of: %s" % - ', '.join(map(six.text_type, self.values))) - return value - - def tobasetype(self, value): - return value - - def frombasetype(self, value): - return value - - -class UnsetType(object): - if sys.version < '3': - def __nonzero__(self): - return False - else: - def __bool__(self): - return False - - def __repr__(self): - return 'Unset' - - -Unset = UnsetType() - -#: A special type that corresponds to the host framework request object. -#: It can only be used in the function parameters, and if so the request object -#: of the host framework will be passed to the function. -HostRequest = object() - - -pod_types = six.integer_types + ( - bytes, text, float, bool) -dt_types = (datetime.date, datetime.time, datetime.datetime) -extra_types = (binary, decimal.Decimal) -native_types = pod_types + dt_types + extra_types -# The types for which we allow promotion to certain numbers. -_promotable_types = six.integer_types + (text, bytes) - - -def iscomplex(datatype): - return inspect.isclass(datatype) \ - and '_wsme_attributes' in datatype.__dict__ - - -def isarray(datatype): - return isinstance(datatype, ArrayType) - - -def isdict(datatype): - return isinstance(datatype, DictType) - - -def validate_value(datatype, value): - if value in (Unset, None): - return value - - # Try to promote the data type to one of our complex types. - if isinstance(datatype, list): - datatype = ArrayType(datatype[0]) - elif isinstance(datatype, dict): - datatype = DictType(*list(datatype.items())[0]) - - # If the datatype has its own validator, use that. - if hasattr(datatype, 'validate'): - return datatype.validate(value) - - # Do type promotion/conversion and data validation for builtin - # types. - v_type = type(value) - if datatype in six.integer_types: - if v_type in _promotable_types: - try: - # Try to turn the value into an int - value = datatype(value) - except ValueError: - # An error is raised at the end of the function - # when the types don't match. - pass - elif datatype is float and v_type in _promotable_types: - try: - value = float(value) - except ValueError: - # An error is raised at the end of the function - # when the types don't match. - pass - elif datatype is text and isinstance(value, bytes): - value = value.decode() - elif datatype is bytes and isinstance(value, text): - value = value.encode() - - if not isinstance(value, datatype): - raise ValueError( - "Wrong type. Expected '%s', got '%s'" % ( - datatype, v_type - )) - return value - - -class wsproperty(property): - """ - A specialised :class:`property` to define typed-property on complex types. - Example:: - - class MyComplexType(wsme.types.Base): - def get_aint(self): - return self._aint - - def set_aint(self, value): - assert avalue < 10 # Dummy input validation - self._aint = value - - aint = wsproperty(int, get_aint, set_aint, mandatory=True) - """ - def __init__(self, datatype, fget, fset=None, - mandatory=False, doc=None, name=None): - property.__init__(self, fget, fset) - #: The property name in the parent python class - self.key = None - #: The attribute name on the public of the api. - #: Defaults to :attr:`key` - self.name = name - #: property data type - self.datatype = datatype - #: True if the property is mandatory - self.mandatory = mandatory - - -class wsattr(object): - """ - Complex type attribute definition. - - Example:: - - class MyComplexType(wsme.types.Base): - optionalvalue = int - mandatoryvalue = wsattr(int, mandatory=True) - named_value = wsattr(int, name='named.value') - - After inspection, the non-wsattr attributes will be replaced, and - the above class will be equivalent to:: - - class MyComplexType(wsme.types.Base): - optionalvalue = wsattr(int) - mandatoryvalue = wsattr(int, mandatory=True) - - """ - def __init__(self, datatype, mandatory=False, name=None, default=Unset, - readonly=False): - #: The attribute name in the parent python class. - #: Set by :func:`inspect_class` - self.key = None # will be set by class inspection - #: The attribute name on the public of the api. - #: Defaults to :attr:`key` - self.name = name - self._datatype = (datatype,) - #: True if the attribute is mandatory - self.mandatory = mandatory - #: Default value. The attribute will return this instead - #: of :data:`Unset` if no value has been set. - self.default = default - #: If True value cannot be set from json/xml input data - self.readonly = readonly - - self.complextype = None - - def _get_dataholder(self, instance): - dataholder = getattr(instance, '_wsme_dataholder', None) - if dataholder is None: - dataholder = instance._wsme_DataHolderClass() - instance._wsme_dataholder = dataholder - return dataholder - - def __get__(self, instance, owner): - if instance is None: - return self - return getattr( - self._get_dataholder(instance), - self.key, - self.default - ) - - def __set__(self, instance, value): - try: - value = validate_value(self.datatype, value) - except (ValueError, TypeError) as e: - raise exc.InvalidInput(self.name, value, six.text_type(e)) - dataholder = self._get_dataholder(instance) - if value is Unset: - if hasattr(dataholder, self.key): - delattr(dataholder, self.key) - else: - setattr(dataholder, self.key, value) - - def __delete__(self, instance): - self.__set__(instance, Unset) - - def _get_datatype(self): - if isinstance(self._datatype, tuple): - self._datatype = \ - self.complextype().__registry__.resolve_type(self._datatype[0]) - if isinstance(self._datatype, weakref.ref): - return self._datatype() - if isinstance(self._datatype, list): - return [ - item() if isinstance(item, weakref.ref) else item - for item in self._datatype - ] - return self._datatype - - def _set_datatype(self, datatype): - self._datatype = datatype - - #: attribute data type. Can be either an actual type, - #: or a type name, in which case the actual type will be - #: determined when needed (generally just before scanning the api). - datatype = property(_get_datatype, _set_datatype) - - -def iswsattr(attr): - if inspect.isfunction(attr) or inspect.ismethod(attr): - return False - if isinstance(attr, property) and not isinstance(attr, wsproperty): - return False - return True - - -def sort_attributes(class_, attributes): - """Sort a class attributes list. - - 3 mechanisms are attempted : - - #. Look for a _wsme_attr_order attribute on the class_. This allow - to define an arbitrary order of the attributes (useful for - generated types). - - #. Access the object source code to find the declaration order. - - #. Sort by alphabetically""" - - if not len(attributes): - return - - attrs = dict((a.key, a) for a in attributes) - - if hasattr(class_, '_wsme_attr_order'): - names_order = class_._wsme_attr_order - else: - names = attrs.keys() - names_order = [] - try: - lines = [] - for cls in inspect.getmro(class_): - if cls is object: - continue - lines[len(lines):] = inspect.getsourcelines(cls)[0] - for line in lines: - line = line.strip().replace(" ", "") - if '=' in line: - aname = line[:line.index('=')] - if aname in names and aname not in names_order: - names_order.append(aname) - if len(names_order) < len(names): - names_order.extend(( - name for name in names if name not in names_order)) - assert len(names_order) == len(names) - except (TypeError, IOError): - names_order = list(names) - names_order.sort() - - attributes[:] = [attrs[name] for name in names_order] - - -def inspect_class(class_): - """Extract a list of (name, wsattr|wsproperty) for the given class_""" - attributes = [] - for name, attr in inspect.getmembers(class_, iswsattr): - if name.startswith('_'): - continue - if inspect.isroutine(attr): - continue - - if isinstance(attr, (wsattr, wsproperty)): - attrdef = attr - else: - if attr not in native_types and ( - inspect.isclass(attr) or - isinstance(attr, (list, dict))): - register_type(attr) - attrdef = getattr(class_, '__wsattrclass__', wsattr)(attr) - - attrdef.key = name - if attrdef.name is None: - attrdef.name = name - attrdef.complextype = weakref.ref(class_) - attributes.append(attrdef) - setattr(class_, name, attrdef) - - sort_attributes(class_, attributes) - return attributes - - -def list_attributes(class_): - """ - Returns a list of a complex type attributes. - """ - if not iscomplex(class_): - raise TypeError("%s is not a registered type") - return class_._wsme_attributes - - -def make_dataholder(class_): - # the slots are computed outside the class scope to avoid - # 'attr' to pullute the class namespace, which leads to weird - # things if one of the slots is named 'attr'. - slots = [attr.key for attr in class_._wsme_attributes] - - class DataHolder(object): - __slots__ = slots - - DataHolder.__name__ = class_.__name__ + 'DataHolder' - return DataHolder - - -class Registry(object): - def __init__(self): - self._complex_types = [] - self.array_types = set() - self.dict_types = set() - - @property - def complex_types(self): - return [t() for t in self._complex_types if t()] - - def register(self, class_): - """ - Make sure a type is registered. - - It is automatically called by :class:`expose() <wsme.expose>` - and :class:`validate() <wsme.validate>`. - Unless you want to control when the class inspection is done there - is no need to call it. - """ - if class_ is None or \ - class_ in native_types or \ - isusertype(class_) or iscomplex(class_) or \ - isarray(class_) or isdict(class_): - return class_ - - if isinstance(class_, list): - if len(class_) != 1: - raise ValueError("Cannot register type %s" % repr(class_)) - dt = ArrayType(class_[0]) - self.register(dt.item_type) - self.array_types.add(dt) - return dt - - if isinstance(class_, dict): - if len(class_) != 1: - raise ValueError("Cannot register type %s" % repr(class_)) - dt = DictType(*list(class_.items())[0]) - self.register(dt.value_type) - self.dict_types.add(dt) - return dt - - class_._wsme_attributes = None - class_._wsme_attributes = inspect_class(class_) - class_._wsme_DataHolderClass = make_dataholder(class_) - - class_.__registry__ = self - self._complex_types.append(weakref.ref(class_)) - return class_ - - def reregister(self, class_): - """Register a type which may already have been registered. - """ - self._unregister(class_) - return self.register(class_) - - def _unregister(self, class_): - """Remove a previously registered type. - """ - # Clear the existing attribute reference so it is rebuilt if - # the class is registered again later. - if hasattr(class_, '_wsme_attributes'): - del class_._wsme_attributes - # FIXME(dhellmann): This method does not recurse through the - # types like register() does. Should it? - if isinstance(class_, list): - at = ArrayType(class_[0]) - try: - self.array_types.remove(at) - except KeyError: - pass - elif isinstance(class_, dict): - key_type, value_type = list(class_.items())[0] - self.dict_types = set( - dt for dt in self.dict_types - if (dt.key_type, dt.value_type) != (key_type, value_type) - ) - # We can't use remove() here because the items in - # _complex_types are weakref objects pointing to the classes, - # so we can't compare with them directly. - self._complex_types = [ - ct for ct in self._complex_types - if ct() is not class_ - ] - - def lookup(self, typename): - log.debug('Lookup %s' % typename) - modname = None - if '.' in typename: - modname, typename = typename.rsplit('.', 1) - for ct in self._complex_types: - ct = ct() - if ct is not None and typename == ct.__name__ and ( - modname is None or modname == ct.__module__): - return ct - - def resolve_type(self, type_): - if isinstance(type_, six.string_types): - return self.lookup(type_) - if isinstance(type_, list): - type_ = ArrayType(type_[0]) - if isinstance(type_, dict): - type_ = DictType(list(type_.keys())[0], list(type_.values())[0]) - if isinstance(type_, ArrayType): - type_ = ArrayType(self.resolve_type(type_.item_type)) - self.array_types.add(type_) - elif isinstance(type_, DictType): - type_ = DictType( - type_.key_type, - self.resolve_type(type_.value_type) - ) - self.dict_types.add(type_) - else: - type_ = self.register(type_) - return type_ - - -# Default type registry -registry = Registry() - - -def register_type(class_): - return registry.register(class_) - - -class BaseMeta(type): - def __new__(cls, name, bases, dct): - if bases and bases[0] is not object and '__registry__' not in dct: - dct['__registry__'] = registry - return type.__new__(cls, name, bases, dct) - - def __init__(cls, name, bases, dct): - if bases and bases[0] is not object and cls.__registry__: - cls.__registry__.register(cls) - - -class Base(six.with_metaclass(BaseMeta)): - """Base type for complex types""" - def __init__(self, **kw): - for key, value in kw.items(): - if hasattr(self, key): - setattr(self, key, value) - - -class File(Base): - """A complex type that represents a file. - - In the particular case of protocol accepting form encoded data as - input, File can be loaded from a form file field. - """ - #: The file name - filename = wsattr(text) - - #: Mime type of the content - contenttype = wsattr(text) - - def _get_content(self): - if self._content is None and self._file: - self._content = self._file.read() - return self._content - - def _set_content(self, value): - self._content = value - self._file = None - - #: File content - content = wsproperty(binary, _get_content, _set_content) - - def __init__(self, filename=None, file=None, content=None, - contenttype=None, fieldstorage=None): - self.filename = filename - self.contenttype = contenttype - self._file = file - self._content = content - - if fieldstorage is not None: - if fieldstorage.file: - self._file = fieldstorage.file - self.filename = fieldstorage.filename - self.contenttype = text(fieldstorage.type) - else: - self._content = fieldstorage.value - - @property - def file(self): - if self._file is None and self._content: - self._file = six.BytesIO(self._content) - return self._file - - -class DynamicBase(Base): - """Base type for complex types for which all attributes are not - defined when the class is constructed. - - This class is meant to be used as a base for types that have - properties added after the main class is created, such as by - loading plugins. - - """ - - @classmethod - def add_attributes(cls, **attrs): - """Add more attributes - - The arguments should be valid Python attribute names - associated with a type for the new attribute. - - """ - for n, t in attrs.items(): - setattr(cls, n, t) - cls.__registry__.reregister(cls) diff --git a/wsme/utils.py b/wsme/utils.py deleted file mode 100644 index e52b0ef..0000000 --- a/wsme/utils.py +++ /dev/null @@ -1,118 +0,0 @@ -import decimal -import datetime -import pytz -import re -from six.moves import builtins, http_client - -try: - import dateutil.parser -except ImportError: - dateutil = None # noqa - -date_re = r'(?P<year>-?\d{4,})-(?P<month>\d{2})-(?P<day>\d{2})' -time_re = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})' + \ - r'(\.(?P<sec_frac>\d+))?' -tz_re = r'((?P<tz_sign>[+-])(?P<tz_hour>\d{2}):(?P<tz_min>\d{2}))' + \ - r'|(?P<tz_z>Z)' - -datetime_re = re.compile( - '%sT%s(%s)?' % (date_re, time_re, tz_re)) -date_re = re.compile(date_re) -time_re = re.compile('%s(%s)?' % (time_re, tz_re)) - - -if hasattr(builtins, '_'): - _ = builtins._ -else: - def _(s): - return s - - -def parse_isodate(value): - m = date_re.match(value) - if m is None: - raise ValueError("'%s' is not a legal date value" % (value)) - try: - return datetime.date( - int(m.group('year')), - int(m.group('month')), - int(m.group('day'))) - except ValueError: - raise ValueError("'%s' is a out-of-range date" % (value)) - - -def parse_isotime(value): - m = time_re.match(value) - if m is None: - raise ValueError("'%s' is not a legal time value" % (value)) - try: - ms = 0 - if m.group('sec_frac') is not None: - f = decimal.Decimal('0.' + m.group('sec_frac')) - f = str(f.quantize(decimal.Decimal('0.000001'))) - ms = int(f[2:]) - tz = _parse_tzparts(m.groupdict()) - return datetime.time( - int(m.group('hour')), - int(m.group('min')), - int(m.group('sec')), - ms, - tz) - except ValueError: - raise ValueError("'%s' is a out-of-range time" % (value)) - - -def parse_isodatetime(value): - if dateutil: - return dateutil.parser.parse(value) - m = datetime_re.match(value) - if m is None: - raise ValueError("'%s' is not a legal datetime value" % (value)) - try: - ms = 0 - if m.group('sec_frac') is not None: - f = decimal.Decimal('0.' + m.group('sec_frac')) - f = f.quantize(decimal.Decimal('0.000001')) - ms = int(str(f)[2:]) - tz = _parse_tzparts(m.groupdict()) - return datetime.datetime( - int(m.group('year')), - int(m.group('month')), - int(m.group('day')), - int(m.group('hour')), - int(m.group('min')), - int(m.group('sec')), - ms, - tz) - except ValueError: - raise ValueError("'%s' is a out-of-range datetime" % (value)) - - -def _parse_tzparts(parts): - if 'tz_z' in parts and parts['tz_z'] == 'Z': - return pytz.UTC - if 'tz_min' not in parts or not parts['tz_min']: - return None - - tz_minute_offset = (int(parts['tz_hour']) * 60 + int(parts['tz_min'])) - tz_multiplier = -1 if parts['tz_sign'] == '-' else 1 - - return pytz.FixedOffset(tz_multiplier * tz_minute_offset) - - -def is_valid_code(code_value): - """ - This function checks if incoming value in http response codes range. - """ - return code_value in http_client.responses - - -def is_client_error(code): - """ Checks client error code (RFC 2616).""" - return 400 <= code < 500 - - -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict # noqa diff --git a/wsmeext/__init__.py b/wsmeext/__init__.py deleted file mode 100644 index ece379c..0000000 --- a/wsmeext/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -import pkg_resources -pkg_resources.declare_namespace(__name__) diff --git a/wsmeext/cornice.py b/wsmeext/cornice.py deleted file mode 100644 index 6c81386..0000000 --- a/wsmeext/cornice.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -WSME for cornice - - -Activate it:: - - config.include('wsme.cornice') - - -And use it:: - - @hello.get() - @wsexpose(Message, wsme.types.text) - def get_hello(who=u'World'): - return Message(text='Hello %s' % who) -""" -from __future__ import absolute_import - -import inspect -import sys - -import wsme -from wsme.rest import json as restjson -from wsme.rest import xml as restxml -import wsme.runtime -import wsme.api -import functools - -from wsme.rest.args import ( - args_from_args, args_from_params, args_from_body, combine_args -) - - -class WSMEJsonRenderer(object): - def __init__(self, info): - pass - - def __call__(self, data, context): - response = context['request'].response - response.content_type = 'application/json' - if 'faultcode' in data: - if 'orig_code' in data: - response.status_code = data['orig_code'] - elif data['faultcode'] == 'Client': - response.status_code = 400 - else: - response.status_code = 500 - return restjson.encode_error(None, data) - obj = data['result'] - if isinstance(obj, wsme.api.Response): - response.status_code = obj.status_code - if obj.error: - return restjson.encode_error(None, obj.error) - obj = obj.obj - return restjson.encode_result(obj, data['datatype']) - - -class WSMEXmlRenderer(object): - def __init__(self, info): - pass - - def __call__(self, data, context): - response = context['request'].response - if 'faultcode' in data: - if data['faultcode'] == 'Client': - response.status_code = 400 - else: - response.status_code = 500 - return restxml.encode_error(None, data) - response.content_type = 'text/xml' - return restxml.encode_result(data['result'], data['datatype']) - - -def get_outputformat(request): - df = None - if 'Accept' in request.headers: - if 'application/json' in request.headers['Accept']: - df = 'json' - elif 'text/xml' in request.headers['Accept']: - df = 'xml' - if df is None and 'Content-Type' in request.headers: - if 'application/json' in request.headers['Content-Type']: - df = 'json' - elif 'text/xml' in request.headers['Content-Type']: - df = 'xml' - return df if df else 'json' - - -def signature(*args, **kwargs): - sig = wsme.signature(*args, **kwargs) - - def decorate(f): - args = inspect.getargspec(f)[0] - with_self = args[0] == 'self' if args else False - f = sig(f) - funcdef = wsme.api.FunctionDefinition.get(f) - funcdef.resolve_types(wsme.types.registry) - - @functools.wraps(f) - def callfunction(*args): - if with_self: - if len(args) == 1: - self = args[0] - request = self.request - elif len(args) == 2: - self, request = args - else: - raise ValueError("Cannot do anything with these arguments") - else: - request = args[0] - request.override_renderer = 'wsme' + get_outputformat(request) - try: - args, kwargs = combine_args(funcdef, ( - args_from_args(funcdef, (), request.matchdict), - args_from_params(funcdef, request.params), - args_from_body(funcdef, request.body, request.content_type) - )) - wsme.runtime.check_arguments(funcdef, args, kwargs) - if funcdef.pass_request: - kwargs[funcdef.pass_request] = request - if with_self: - args.insert(0, self) - - result = f(*args, **kwargs) - return { - 'datatype': funcdef.return_type, - 'result': result - } - except Exception: - 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') diff --git a/wsmeext/extdirect/__init__.py b/wsmeext/extdirect/__init__.py deleted file mode 100644 index ff14bd5..0000000 --- a/wsmeext/extdirect/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from wsmeext.extdirect.protocol import ExtDirectProtocol # noqa diff --git a/wsmeext/extdirect/datastore.py b/wsmeext/extdirect/datastore.py deleted file mode 100644 index 287e3a7..0000000 --- a/wsmeext/extdirect/datastore.py +++ /dev/null @@ -1,121 +0,0 @@ -import wsme -import wsme.types - -try: - import simplejson as json -except ImportError: - import json - - -class ReadResultBase(wsme.types.Base): - total = int - success = bool - message = wsme.types.text - - -def make_readresult(datatype): - ReadResult = type( - datatype.__name__ + 'ReadResult', - (ReadResultBase,), { - 'data': [datatype] - } - ) - return ReadResult - - -class DataStoreControllerMeta(type): - def __init__(cls, name, bases, dct): - if cls.__datatype__ is None: - return - if getattr(cls, '__readresulttype__', None) is None: - cls.__readresulttype__ = make_readresult(cls.__datatype__) - - cls.create = wsme.expose( - cls.__readresulttype__, - extdirect_params_notation='positional')(cls.create) - cls.create = wsme.validate(cls.__datatype__)(cls.create) - - cls.read = wsme.expose( - cls.__readresulttype__, - extdirect_params_notation='named')(cls.read) - cls.read = wsme.validate(str, str, int, int, int)(cls.read) - - cls.update = wsme.expose( - cls.__readresulttype__, - extdirect_params_notation='positional')(cls.update) - cls.update = wsme.validate(cls.__datatype__)(cls.update) - - cls.destroy = wsme.expose( - cls.__readresulttype__, - extdirect_params_notation='positional')(cls.destroy) - cls.destroy = wsme.validate(cls.__idtype__)(cls.destroy) - - -class DataStoreControllerMixin(object): - __datatype__ = None - __idtype__ = int - - __readresulttype__ = None - - def create(self, obj): - pass - - def read(self, query=None, sort=None, page=None, start=None, limit=None): - pass - - def update(self, obj): - pass - - def destroy(self, obj_id): - pass - - def model(self): - tpl = """ -Ext.define('%(appns)s.model.%(classname)s', { - extend: 'Ext.data.Model', - fields: %(fields)s, - - proxy: { - type: 'direct', - api: { - create: %(appns)s.%(controllerns)s.create, - read: %(appns)s.%(controllerns)s.read, - update: %(appns)s.%(controllerns)s.update, - destroy: %(appns)s.%(controllerns)s.destroy - }, - reader: { - root: 'data' - } - } -}); - """ - fields = [ - attr.name for attr in self.__datatype__._wsme_attributes - ] - d = { - 'appns': 'Demo', - 'controllerns': 'stores.' + self.__datatype__.__name__.lower(), - 'classname': self.__datatype__.__name__, - 'fields': json.dumps(fields) - } - return tpl % d - - def store(self): - tpl = """ -Ext.define('%(appns)s.store.%(classname)s', { - extend: 'Ext.data.Store', - model: '%(appns)s.model.%(classname)s' -}); -""" - d = { - 'appns': 'Demo', - 'classname': self.__datatype__.__name__, - } - - return tpl % d - - -DataStoreController = DataStoreControllerMeta( - 'DataStoreController', - (DataStoreControllerMixin,), {} -) diff --git a/wsmeext/extdirect/protocol.py b/wsmeext/extdirect/protocol.py deleted file mode 100644 index 23793b4..0000000 --- a/wsmeext/extdirect/protocol.py +++ /dev/null @@ -1,450 +0,0 @@ -import datetime -import decimal - -from simplegeneric import generic - -from wsme.exc import ClientSideError -from wsme.protocol import CallContext, Protocol, expose -from wsme.utils import parse_isodate, parse_isodatetime, parse_isotime -from wsme.rest.args import from_params -from wsme.types import iscomplex, isusertype, list_attributes, Unset -import wsme.types - -try: - import simplejson as json -except ImportError: - import json # noqa - -from six import u - - -class APIDefinitionGenerator(object): - tpl = """\ -Ext.ns("%(rootns)s"); - -if (!%(rootns)s.wsroot) { - %(rootns)s.wsroot = "%(webpath)s. -} - -%(descriptors)s - -Ext.syncRequire(['Ext.direct.*'], function() { - %(providers)s -}); -""" - descriptor_tpl = """\ -Ext.ns("%(fullns)s"); - -%(fullns)s.Descriptor = { - "url": %(rootns)s.wsroot + "extdirect/router/%(ns)s", - "namespace": "%(fullns)s", - "type": "remoting", - "actions": %(actions)s - "enableBuffer": true -}; -""" - provider_tpl = """\ - Ext.direct.Manager.addProvider(%(fullns)s.Descriptor); -""" - - def __init__(self): - pass - - def render(self, rootns, webpath, namespaces, fullns): - descriptors = u('') - for ns in sorted(namespaces): - descriptors += self.descriptor_tpl % { - 'ns': ns, - 'rootns': rootns, - 'fullns': fullns(ns), - 'actions': '\n'.join(( - ' ' * 4 + line - for line - in json.dumps(namespaces[ns], indent=4).split('\n') - )) - } - - providers = u('') - for ns in sorted(namespaces): - providers += self.provider_tpl % { - 'fullns': fullns(ns) - } - - r = self.tpl % { - 'rootns': rootns, - 'webpath': webpath, - 'descriptors': descriptors, - 'providers': providers, - } - return r - - -@generic -def fromjson(datatype, value): - if value is None: - return None - if iscomplex(datatype): - newvalue = datatype() - for attrdef in list_attributes(datatype): - if attrdef.name in value: - setattr(newvalue, attrdef.key, - fromjson(attrdef.datatype, value[attrdef.name])) - value = newvalue - elif isusertype(datatype): - value = datatype.frombasetype(fromjson(datatype.basetype, value)) - return value - - -@generic -def tojson(datatype, value): - if value is None: - return value - if iscomplex(datatype): - d = {} - for attrdef in list_attributes(datatype): - attrvalue = getattr(value, attrdef.key) - if attrvalue is not Unset: - d[attrdef.name] = tojson(attrdef.datatype, attrvalue) - value = d - elif isusertype(datatype): - value = tojson(datatype.basetype, datatype.tobasetype(value)) - return value - - -@fromjson.when_type(wsme.types.ArrayType) -def array_fromjson(datatype, value): - return [fromjson(datatype.item_type, item) for item in value] - - -@tojson.when_type(wsme.types.ArrayType) -def array_tojson(datatype, value): - if value is None: - return value - return [tojson(datatype.item_type, item) for item in value] - - -@fromjson.when_type(wsme.types.DictType) -def dict_fromjson(datatype, value): - if value is None: - return value - return dict(( - (fromjson(datatype.key_type, key), - fromjson(datatype.value_type, value)) - for key, value in value.items() - )) - - -@tojson.when_type(wsme.types.DictType) -def dict_tojson(datatype, value): - if value is None: - return value - return dict(( - (tojson(datatype.key_type, key), - tojson(datatype.value_type, value)) - for key, value in value.items() - )) - - -@tojson.when_object(wsme.types.bytes) -def bytes_tojson(datatype, value): - if value is None: - return value - return value.decode('ascii') - - -# raw strings -@fromjson.when_object(wsme.types.bytes) -def bytes_fromjson(datatype, value): - if value is not None: - value = value.encode('ascii') - return value - - -# unicode strings - -@fromjson.when_object(wsme.types.text) -def text_fromjson(datatype, value): - if isinstance(value, wsme.types.bytes): - return value.decode('utf-8') - return value - - -# datetime.time - -@fromjson.when_object(datetime.time) -def time_fromjson(datatype, value): - if value is None or value == '': - return None - return parse_isotime(value) - - -@tojson.when_object(datetime.time) -def time_tojson(datatype, value): - if value is None: - return value - return value.isoformat() - - -# datetime.date - -@fromjson.when_object(datetime.date) -def date_fromjson(datatype, value): - if value is None or value == '': - return None - return parse_isodate(value) - - -@tojson.when_object(datetime.date) -def date_tojson(datatype, value): - if value is None: - return value - return value.isoformat() - - -# datetime.datetime - -@fromjson.when_object(datetime.datetime) -def datetime_fromjson(datatype, value): - if value is None or value == '': - return None - return parse_isodatetime(value) - - -@tojson.when_object(datetime.datetime) -def datetime_tojson(datatype, value): - if value is None: - return value - return value.isoformat() - - -# decimal.Decimal - -@fromjson.when_object(decimal.Decimal) -def decimal_fromjson(datatype, value): - if value is None: - return value - return decimal.Decimal(value) - - -@tojson.when_object(decimal.Decimal) -def decimal_tojson(datatype, value): - if value is None: - return value - return str(value) - - -class ExtCallContext(CallContext): - def __init__(self, request, namespace, calldata): - super(ExtCallContext, self).__init__(request) - self.namespace = namespace - - self.tid = calldata['tid'] - self.action = calldata['action'] - self.method = calldata['method'] - self.params = calldata['data'] - - -class FormExtCallContext(CallContext): - def __init__(self, request, namespace): - super(FormExtCallContext, self).__init__(request) - self.namespace = namespace - - self.tid = request.params['extTID'] - self.action = request.params['extAction'] - self.method = request.params['extMethod'] - self.params = [] - - -class ExtDirectProtocol(Protocol): - """ - ExtDirect protocol. - - For more detail on the protocol, see - http://www.sencha.com/products/extjs/extdirect. - - .. autoattribute:: name - .. autoattribute:: content_types - """ - name = 'extdirect' - displayname = 'ExtDirect' - content_types = ['application/json', 'text/javascript'] - - def __init__(self, namespace='', params_notation='named', nsfolder=None): - self.namespace = namespace - self.appns, self.apins = namespace.rsplit('.', 2) \ - if '.' in namespace else (namespace, '') - self.default_params_notation = params_notation - self.appnsfolder = nsfolder - - @property - def api_alias(self): - if self.appnsfolder: - alias = '/%s/%s.js' % ( - self.appnsfolder, - self.apins.replace('.', '/')) - return alias - - def accept(self, req): - path = req.path - assert path.startswith(self.root._webpath) - path = path[len(self.root._webpath):] - - return ( - path == self.api_alias or - path == "/extdirect/api" or - path.startswith("/extdirect/router") - ) - - def iter_calls(self, req): - path = req.path - - assert path.startswith(self.root._webpath) - path = path[len(self.root._webpath):].strip() - - assert path.startswith('/extdirect/router'), path - path = path[17:].strip('/') - - if path: - namespace = path.split('.') - else: - namespace = [] - - if 'extType' in req.params: - req.wsme_extdirect_batchcall = False - yield FormExtCallContext(req, namespace) - else: - data = json.loads(req.body.decode('utf8')) - req.wsme_extdirect_batchcall = isinstance(data, list) - if not req.wsme_extdirect_batchcall: - data = [data] - req.callcount = len(data) - - for call in data: - yield ExtCallContext(req, namespace, call) - - def extract_path(self, context): - path = list(context.namespace) - - if context.action: - path.append(context.action) - - path.append(context.method) - - return path - - def read_std_arguments(self, context): - funcdef = context.funcdef - notation = funcdef.extra_options.get('extdirect_params_notation', - self.default_params_notation) - args = context.params - if notation == 'positional': - kw = dict( - (argdef.name, fromjson(argdef.datatype, arg)) - for argdef, arg in zip(funcdef.arguments, args) - ) - elif notation == 'named': - if len(args) == 0: - args = [{}] - elif len(args) > 1: - raise ClientSideError( - "Named arguments: takes a single object argument") - args = args[0] - kw = dict( - (argdef.name, fromjson(argdef.datatype, args[argdef.name])) - for argdef in funcdef.arguments if argdef.name in args - ) - else: - raise ValueError("Invalid notation: %s" % notation) - return kw - - def read_form_arguments(self, context): - kw = {} - for argdef in context.funcdef.arguments: - value = from_params(argdef.datatype, context.request.params, - argdef.name, set()) - if value is not Unset: - kw[argdef.name] = value - return kw - - def read_arguments(self, context): - if isinstance(context, ExtCallContext): - kwargs = self.read_std_arguments(context) - elif isinstance(context, FormExtCallContext): - kwargs = self.read_form_arguments(context) - wsme.runtime.check_arguments(context.funcdef, (), kwargs) - return kwargs - - def encode_result(self, context, result): - return json.dumps({ - 'type': 'rpc', - 'tid': context.tid, - 'action': context.action, - 'method': context.method, - 'result': tojson(context.funcdef.return_type, result) - }) - - def encode_error(self, context, infos): - return json.dumps({ - 'type': 'exception', - 'tid': context.tid, - 'action': context.action, - 'method': context.method, - 'message': '%(faultcode)s: %(faultstring)s' % infos, - 'where': infos['debuginfo']}) - - def prepare_response_body(self, request, results): - r = ",\n".join(results) - if request.wsme_extdirect_batchcall: - return "[\n%s\n]" % r - else: - return r - - def get_response_status(self, request): - return 200 - - def get_response_contenttype(self, request): - return "text/javascript" - - def fullns(self, ns): - return ns and '%s.%s' % (self.namespace, ns) or self.namespace - - @expose('/extdirect/api', "text/javascript") - @expose('${api_alias}', "text/javascript") - def api(self): - namespaces = {} - for path, funcdef in self.root.getapi(): - if len(path) > 1: - namespace = '.'.join(path[:-2]) - action = path[-2] - else: - namespace = '' - action = '' - if namespace not in namespaces: - namespaces[namespace] = {} - if action not in namespaces[namespace]: - namespaces[namespace][action] = [] - notation = funcdef.extra_options.get('extdirect_params_notation', - self.default_params_notation) - method = { - 'name': funcdef.name} - - if funcdef.extra_options.get('extdirect_formhandler', False): - method['formHandler'] = True - method['len'] = 1 if notation == 'named' \ - else len(funcdef.arguments) - namespaces[namespace][action].append(method) - webpath = self.root._webpath - if webpath and not webpath.endswith('/'): - webpath += '/' - return APIDefinitionGenerator().render( - namespaces=namespaces, - webpath=webpath, - rootns=self.namespace, - fullns=self.fullns, - ) - - def encode_sample_value(self, datatype, value, format=False): - r = tojson(datatype, value) - content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0, - sort_keys=format) - return ('javascript', content) diff --git a/wsmeext/extdirect/sadatastore.py b/wsmeext/extdirect/sadatastore.py deleted file mode 100644 index 44d79cb..0000000 --- a/wsmeext/extdirect/sadatastore.py +++ /dev/null @@ -1,19 +0,0 @@ -from wsmeext.extdirect import datastore - - -class SADataStoreController(datastore.DataStoreController): - __dbsession__ = None - __datatype__ = None - - def read(self, query=None, sort=None, page=None, start=None, limit=None): - q = self.__dbsession__.query(self.__datatype__.__saclass__) - total = q.count() - if start is not None and limit is not None: - q = q.slice(start, limit) - return self.__readresulttype__( - data=[ - self.__datatype__(o) for o in q - ], - success=True, - total=total - ) diff --git a/wsmeext/flask.py b/wsmeext/flask.py deleted file mode 100644 index 4d9e446..0000000 --- a/wsmeext/flask.py +++ /dev/null @@ -1,108 +0,0 @@ -from __future__ import absolute_import - -import functools -import logging -import sys -import inspect - -import wsme -import wsme.api -import wsme.rest.json -import wsme.rest.xml -import wsme.rest.args -from wsme.utils import is_valid_code - -import flask - -log = logging.getLogger(__name__) - - -TYPES = { - 'application/json': wsme.rest.json, - 'application/xml': wsme.rest.xml, - 'text/xml': wsme.rest.xml -} - - -def get_dataformat(): - if 'Accept' in flask.request.headers: - for t in TYPES: - if t in flask.request.headers['Accept']: - return TYPES[t] - - # Look for the wanted data format in the request. - req_dataformat = getattr(flask.request, 'response_type', None) - if req_dataformat in TYPES: - return TYPES[req_dataformat] - - log.info('''Could not determine what format is wanted by the - caller, falling back to json''') - return wsme.rest.json - - -def signature(*args, **kw): - sig = wsme.signature(*args, **kw) - - def decorator(f): - args = inspect.getargspec(f)[0] - ismethod = args and args[0] == 'self' - sig(f) - funcdef = wsme.api.FunctionDefinition.get(f) - funcdef.resolve_types(wsme.types.registry) - - @functools.wraps(f) - def wrapper(*args, **kwargs): - if ismethod: - self, args = args[0], args[1:] - args, kwargs = wsme.rest.args.get_args( - funcdef, args, kwargs, - flask.request.args, flask.request.form, - flask.request.data, - flask.request.mimetype - ) - - if funcdef.pass_request: - kwargs[funcdef.pass_request] = flask.request - - dataformat = get_dataformat() - - try: - if ismethod: - args = [self] + list(args) - result = f(*args, **kwargs) - - # NOTE: Support setting of status_code with default 20 - status_code = funcdef.status_code - if isinstance(result, wsme.api.Response): - status_code = result.status_code - result = result.obj - - res = flask.make_response( - dataformat.encode_result( - result, - funcdef.return_type - ) - ) - res.mimetype = dataformat.content_type - res.status_code = status_code - except Exception: - try: - exception_info = sys.exc_info() or None - 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 orig_code and is_valid_code(orig_code): - res.status_code = orig_code - elif data['faultcode'].lower() == 'client': - res.status_code = 400 - else: - res.status_code = 500 - return res - - wrapper.wsme_func = f - return wrapper - return decorator diff --git a/wsmeext/pecan.py b/wsmeext/pecan.py deleted file mode 100644 index 9d63dde..0000000 --- a/wsmeext/pecan.py +++ /dev/null @@ -1,142 +0,0 @@ -from __future__ import absolute_import - -import functools -import inspect -import sys - -import wsme -import wsme.rest.args -import wsme.rest.json -import wsme.rest.xml - -import pecan - -from wsme.utils import is_valid_code - - -class JSonRenderer(object): - @staticmethod - def __init__(path, extra_vars): - pass - - @staticmethod - def render(template_path, namespace): - if 'faultcode' in namespace: - return wsme.rest.json.encode_error(None, namespace) - return wsme.rest.json.encode_result( - namespace['result'], - namespace['datatype'] - ) - - -class XMLRenderer(object): - @staticmethod - def __init__(path, extra_vars): - pass - - @staticmethod - def render(template_path, namespace): - if 'faultcode' in namespace: - return wsme.rest.xml.encode_error(None, namespace) - return wsme.rest.xml.encode_result( - namespace['result'], - namespace['datatype'] - ) - - -pecan.templating._builtin_renderers['wsmejson'] = JSonRenderer -pecan.templating._builtin_renderers['wsmexml'] = XMLRenderer - -pecan_json_decorate = pecan.expose( - template='wsmejson:', - content_type='application/json', - generic=False) -pecan_xml_decorate = pecan.expose( - template='wsmexml:', - content_type='application/xml', - generic=False -) -pecan_text_xml_decorate = pecan.expose( - template='wsmexml:', - content_type='text/xml', - generic=False -) - - -def wsexpose(*args, **kwargs): - sig = wsme.signature(*args, **kwargs) - - def decorate(f): - sig(f) - funcdef = wsme.api.FunctionDefinition.get(f) - funcdef.resolve_types(wsme.types.registry) - - @functools.wraps(f) - def callfunction(self, *args, **kwargs): - return_type = funcdef.return_type - - try: - args, kwargs = wsme.rest.args.get_args( - funcdef, args, kwargs, pecan.request.params, None, - pecan.request.body, pecan.request.content_type - ) - if funcdef.pass_request: - kwargs[funcdef.pass_request] = pecan.request - result = f(self, *args, **kwargs) - - # NOTE: Support setting of status_code with default 201 - pecan.response.status = funcdef.status_code - if isinstance(result, wsme.api.Response): - pecan.response.status = result.status_code - - # NOTE(lucasagomes): If the return code is 204 - # (No Response) we have to make sure that we are not - # returning anything in the body response and the - # content-length is 0 - if result.status_code == 204: - return_type = None - elif not isinstance(result.return_type, - wsme.types.UnsetType): - return_type = result.return_type - - result = result.obj - - except Exception: - 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 diff --git a/wsmeext/soap/__init__.py b/wsmeext/soap/__init__.py deleted file mode 100644 index 7a237ba..0000000 --- a/wsmeext/soap/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import absolute_import - -from wsmeext.soap.protocol import SoapProtocol - -__all__ = ['SoapProtocol'] diff --git a/wsmeext/soap/protocol.py b/wsmeext/soap/protocol.py deleted file mode 100644 index 0d87af8..0000000 --- a/wsmeext/soap/protocol.py +++ /dev/null @@ -1,478 +0,0 @@ -""" -A SOAP implementation for wsme. -Parts of the code were taken from the tgwebservices soap implmentation. -""" -from __future__ import absolute_import - -import pkg_resources -import datetime -import decimal -import base64 -import logging - -import six - -from wsmeext.soap.simplegeneric import generic -from wsmeext.soap.wsdl import WSDLGenerator - -try: - from lxml import etree as ET - use_lxml = True -except ImportError: - from xml.etree import cElementTree as ET # noqa - use_lxml = False - -from wsme.protocol import CallContext, Protocol, expose - -import wsme.types -import wsme.runtime - -from wsme import exc -from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime - -log = logging.getLogger(__name__) - -xsd_ns = 'http://www.w3.org/2001/XMLSchema' -xsi_ns = 'http://www.w3.org/2001/XMLSchema-instance' -soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/' - -if not use_lxml: - ET.register_namespace('soap', soapenv_ns) - -type_qn = '{%s}type' % xsi_ns -nil_qn = '{%s}nil' % xsi_ns - -Envelope_qn = '{%s}Envelope' % soapenv_ns -Body_qn = '{%s}Body' % soapenv_ns -Fault_qn = '{%s}Fault' % soapenv_ns -faultcode_qn = '{%s}faultcode' % soapenv_ns -faultstring_qn = '{%s}faultstring' % soapenv_ns -detail_qn = '{%s}detail' % soapenv_ns - - -type_registry = { - wsme.types.bytes: 'xs:string', - wsme.types.text: 'xs:string', - int: 'xs:int', - float: "xs:float", - bool: "xs:boolean", - datetime.datetime: "xs:dateTime", - datetime.date: "xs:date", - datetime.time: "xs:time", - decimal.Decimal: "xs:decimal", - wsme.types.binary: "xs:base64Binary", -} - -if not six.PY3: - type_registry[long] = "xs:long" # noqa - -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" # noqa - - -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', six.text_type(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 diff --git a/wsmeext/soap/simplegeneric.py b/wsmeext/soap/simplegeneric.py deleted file mode 100644 index 97c169b..0000000 --- a/wsmeext/soap/simplegeneric.py +++ /dev/null @@ -1,107 +0,0 @@ -import inspect - -__all__ = ["generic"] -try: - from types import ClassType, InstanceType - classtypes = type, ClassType -except ImportError: - classtypes = type - InstanceType = None - - -def generic(func, argpos=None): - """Create a simple generic function""" - - if argpos is None: - if hasattr(func, 'argpos'): - argpos = func.argpos - else: - argnames = inspect.getargspec(func)[0] - if argnames and argnames[0] == 'self': - argpos = 1 - else: - argpos = 0 - - _sentinel = object() - - def _by_class(*args, **kw): - cls = args[argpos].__class__ - for t in type(cls.__name__, (cls, object), {}).__mro__: - f = _gbt(t, _sentinel) - if f is not _sentinel: - return f(*args, **kw) - else: - return func(*args, **kw) - - _by_type = {object: func, InstanceType: _by_class} - _gbt = _by_type.get - - def when_type(*types): - """Decorator to add a method that will be called for the given types""" - for t in types: - if not isinstance(t, classtypes): - raise TypeError( - "%r is not a type or class" % (t,) - ) - - def decorate(f): - for t in types: - if _by_type.setdefault(t, f) is not f: - raise TypeError( - "%r already has method for type %r" % (func, t) - ) - return f - return decorate - - _by_object = {} - _gbo = _by_object.get - - def when_object(*obs): - """Decorator to add a method to be called for the given object(s)""" - def decorate(f): - for o in obs: - if _by_object.setdefault(id(o), (o, f))[1] is not f: - raise TypeError( - "%r already has method for object %r" % (func, o) - ) - return f - return decorate - - def dispatch(*args, **kw): - f = _gbo(id(args[argpos]), _sentinel) - if f is _sentinel: - for t in type(args[argpos]).__mro__: - f = _gbt(t, _sentinel) - if f is not _sentinel: - return f(*args, **kw) - else: - return func(*args, **kw) - else: - return f[1](*args, **kw) - - dispatch.__name__ = func.__name__ - dispatch.__dict__ = func.__dict__.copy() - dispatch.__doc__ = func.__doc__ - dispatch.__module__ = func.__module__ - - dispatch.when_type = when_type - dispatch.when_object = when_object - dispatch.default = func - dispatch.has_object = lambda o: id(o) in _by_object - dispatch.has_type = lambda t: t in _by_type - dispatch.argpos = argpos - return dispatch - - -def test_suite(): - import doctest - return doctest.DocFileSuite( - 'README.txt', - optionflags=doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE, - ) - - -if __name__ == '__main__': - import unittest - r = unittest.TextTestRunner() - r.run(test_suite()) diff --git a/wsmeext/soap/wsdl.py b/wsmeext/soap/wsdl.py deleted file mode 100644 index b60aff4..0000000 --- a/wsmeext/soap/wsdl.py +++ /dev/null @@ -1,297 +0,0 @@ -import six -import wsme.types - -try: - from lxml import etree as ET - use_lxml = True -except ImportError: - from xml.etree import cElementTree as ET # noqa - use_lxml = False - - -def xml_tostring(el, pretty_print=False): - if use_lxml: - return ET.tostring(el, pretty_print=pretty_print) - return ET.tostring(el) - - -class NS(object): - def __init__(self, url): - self.url = url - - def __call__(self, name): - return self.qn(name) - - def __str__(self): - return self.url - - def qn(self, name): - return '{%s}%s' % (self.url, name) - - -wsdl_ns = NS("http://schemas.xmlsoap.org/wsdl/") -soap_ns = NS("http://schemas.xmlsoap.org/wsdl/soap/") -xs_ns = NS("http://www.w3.org/2001/XMLSchema") -soapenc_ns = NS("http://schemas.xmlsoap.org/soap/encoding/") - - -class WSDLGenerator(object): - def __init__( - self, - tns, - types_ns, - soapenc, - service_name, - complex_types, - funclist, - arrays, - baseURL, - soap_array, - soap_type, - soap_fname): - - self.tns = NS(tns) - self.types_ns = NS(types_ns) - self.soapenc = soapenc - self.service_name = service_name - self.complex_types = complex_types - self.funclist = funclist - self.arrays = arrays - self.baseURL = baseURL or '' - self.soap_array = soap_array - self.soap_fname = soap_fname - self.soap_type = soap_type - - def gen_complex_type(self, cls): - complexType = ET.Element(xs_ns('complexType')) - complexType.set('name', cls.__name__) - sequence = ET.SubElement(complexType, xs_ns('sequence')) - for attrdef in wsme.types.list_attributes(cls): - soap_type = self.soap_type(attrdef.datatype, str(self.types_ns)) - if soap_type is None: - continue - element = ET.SubElement(sequence, xs_ns('element')) - element.set('name', attrdef.name) - element.set('type', soap_type) - element.set('minOccurs', '1' if attrdef.mandatory else '0') - element.set('maxOccurs', '1') - return complexType - - def gen_array(self, array): - complexType = ET.Element(xs_ns('complexType')) - complexType.set('name', self.soap_array(array, False)) - ET.SubElement( - ET.SubElement(complexType, xs_ns('sequence')), - xs_ns('element'), - name='item', - maxOccurs='unbounded', - nillable='true', - type=self.soap_type(array.item_type, self.types_ns) - ) - return complexType - - def gen_function_types(self, path, funcdef): - args_el = ET.Element( - xs_ns('element'), - name=self.soap_fname(path, funcdef) - ) - - sequence = ET.SubElement( - ET.SubElement(args_el, xs_ns('complexType')), - xs_ns('sequence') - ) - - for farg in funcdef.arguments: - t = self.soap_type(farg.datatype, True) - if t is None: - continue - element = ET.SubElement( - sequence, xs_ns('element'), - name=farg.name, - type=self.soap_type(farg.datatype, True) - ) - if not farg.mandatory: - element.set('minOccurs', '0') - - response_el = ET.Element( - xs_ns('element'), - name=self.soap_fname(path, funcdef) + 'Response' - ) - element = ET.SubElement( - ET.SubElement( - ET.SubElement( - response_el, - xs_ns('complexType') - ), - xs_ns('sequence') - ), - xs_ns('element'), - name='result' - ) - return_soap_type = self.soap_type(funcdef.return_type, True) - if return_soap_type is not None: - element.set('type', return_soap_type) - - return args_el, response_el - - def gen_types(self): - types = ET.Element(wsdl_ns('types')) - schema = ET.SubElement(types, xs_ns('schema')) - schema.set('elementFormDefault', 'qualified') - schema.set('targetNamespace', str(self.types_ns)) - for cls in self.complex_types: - schema.append(self.gen_complex_type(cls)) - for array in self.arrays: - schema.append(self.gen_array(array)) - for path, funcdef in self.funclist: - schema.extend(self.gen_function_types(path, funcdef)) - return types - - def gen_functions(self): - messages = [] - - binding = ET.Element( - wsdl_ns('binding'), - name='%s_Binding' % self.service_name, - type='tns:%s_PortType' % self.service_name - ) - ET.SubElement( - binding, - soap_ns('binding'), - style='document', - transport='http://schemas.xmlsoap.org/soap/http' - ) - - portType = ET.Element( - wsdl_ns('portType'), - name='%s_PortType' % self.service_name - ) - - for path, funcdef in self.funclist: - soap_fname = self.soap_fname(path, funcdef) - - # message - req_message = ET.Element( - wsdl_ns('message'), - name=soap_fname + 'Request', - xmlns=str(self.types_ns) - ) - ET.SubElement( - req_message, - wsdl_ns('part'), - name='parameters', - element='types:%s' % soap_fname - ) - messages.append(req_message) - - res_message = ET.Element( - wsdl_ns('message'), - name=soap_fname + 'Response', - xmlns=str(self.types_ns) - ) - ET.SubElement( - res_message, - wsdl_ns('part'), - name='parameters', - element='types:%sResponse' % soap_fname - ) - messages.append(res_message) - - # portType/operation - operation = ET.SubElement( - portType, - wsdl_ns('operation'), - name=soap_fname - ) - if funcdef.doc: - ET.SubElement( - operation, - wsdl_ns('documentation') - ).text = funcdef.doc - ET.SubElement( - operation, wsdl_ns('input'), - message='tns:%sRequest' % soap_fname - ) - ET.SubElement( - operation, wsdl_ns('output'), - message='tns:%sResponse' % soap_fname - ) - - # binding/operation - operation = ET.SubElement( - binding, - wsdl_ns('operation'), - name=soap_fname - ) - ET.SubElement( - operation, - soap_ns('operation'), - soapAction=soap_fname - ) - ET.SubElement( - ET.SubElement( - operation, - wsdl_ns('input') - ), - soap_ns('body'), - use='literal' - ) - ET.SubElement( - ET.SubElement( - operation, - wsdl_ns('output') - ), - soap_ns('body'), - use='literal' - ) - - return messages + [portType, binding] - - def gen_service(self): - service = ET.Element(wsdl_ns('service'), name=self.service_name) - ET.SubElement( - service, - wsdl_ns('documentation') - ).text = six.u('WSDL File for %s') % self.service_name - ET.SubElement( - ET.SubElement( - service, - wsdl_ns('port'), - binding='tns:%s_Binding' % self.service_name, - name='%s_PortType' % self.service_name - ), - soap_ns('address'), - location=self.baseURL - ) - - return service - - def gen_definitions(self): - attrib = { - 'name': self.service_name, - 'targetNamespace': str(self.tns) - } - if use_lxml: - definitions = ET.Element( - wsdl_ns('definitions'), - attrib=attrib, - nsmap={ - 'xs': str(xs_ns), - 'soap': str(soap_ns), - 'types': str(self.types_ns), - 'tns': str(self.tns) - } - ) - else: - definitions = ET.Element(wsdl_ns('definitions'), **attrib) - definitions.set('xmlns:types', str(self.types_ns)) - definitions.set('xmlns:tns', str(self.tns)) - - definitions.set('name', self.service_name) - definitions.append(self.gen_types()) - definitions.extend(self.gen_functions()) - definitions.append(self.gen_service()) - return definitions - - def generate(self, format=False): - return xml_tostring(self.gen_definitions(), pretty_print=format) diff --git a/wsmeext/sphinxext.py b/wsmeext/sphinxext.py deleted file mode 100644 index 7c45a0f..0000000 --- a/wsmeext/sphinxext.py +++ /dev/null @@ -1,600 +0,0 @@ -import inspect -import re -import sys - -import six - -from sphinx import addnodes -from sphinx.ext import autodoc -from sphinx.domains.python import PyClasslike, PyClassmember -from sphinx.domains import Domain, ObjType -from sphinx.directives import ObjectDescription -from sphinx.util.docfields import Field -from sphinx.util.nodes import make_refnode - -from sphinx.roles import XRefRole -from sphinx.locale import l_, _ - -from docutils.parsers.rst import Directive -from docutils.parsers.rst import directives - -import wsme -import wsme.types -import wsme.rest.json -import wsme.rest.xml - -field_re = re.compile(r':(?P<field>\w+)(\s+(?P<name>\w+))?:') - - -def datatypename(datatype): - if isinstance(datatype, wsme.types.UserType): - return datatype.name - if isinstance(datatype, wsme.types.DictType): - return 'dict(%s: %s)' % (datatypename(datatype.key_type), - datatypename(datatype.value_type)) - if isinstance(datatype, wsme.types.ArrayType): - return 'list(%s)' % datatypename(datatype.item_type) - return datatype.__name__ - - -def make_sample_object(datatype): - if datatype is wsme.types.bytes: - return six.b('samplestring') - if datatype is wsme.types.text: - return u'sample unicode' - if datatype is int: - return 5 - sample_obj = getattr(datatype, 'sample', datatype)() - return sample_obj - - -def get_protocols(names): - names = list(names) - protocols = [] - if 'rest' in names: - names.remove('rest') - protocols.extend('restjson', 'restxml') - if 'restjson' in names: - names.remove('restjson') - protocols.append(('Json', wsme.rest.json)) - if 'restxml' in names: - names.remove('restxml') - protocols.append(('XML', wsme.rest.xml)) - for name in names: - p = wsme.protocol.getprotocol(name) - protocols.append((p.displayname or p.name, p)) - return protocols - - -class SampleType(object): - """A Sample Type""" - - #: A Int - aint = int - - def __init__(self, aint=None): - if aint: - self.aint = aint - - @classmethod - def sample(cls): - return cls(10) - - -class SampleService(wsme.WSRoot): - @wsme.expose(SampleType) - @wsme.validate(SampleType, int, str) - def change_aint(data, aint, dummy='useless'): - """ - :param aint: The new value - - :return: The data object with its aint field value changed. - """ - data.aint = aint - return data - - -def getroot(env, force=False): - root = env.temp_data.get('wsme:root') - if not force and root: - return root - rootpath = env.temp_data.get('wsme:rootpath', env.app.config.wsme_root) - - if rootpath is None: - return None - - modname, classname = rootpath.rsplit('.', 1) - __import__(modname) - module = sys.modules[modname] - root = getattr(module, classname) - env.temp_data['wsme:root'] = root - return root - - -def scan_services(service, path=[]): - has_functions = False - for name in dir(service): - if name.startswith('_'): - continue - a = getattr(service, name) - if inspect.ismethod(a): - if hasattr(a, '_wsme_definition'): - has_functions = True - if inspect.isclass(a): - continue - if len(path) > wsme.rest.APIPATH_MAXLEN: - raise ValueError("Path is too long: " + str(path)) - for value in scan_services(a, path + [name]): - yield value - if has_functions: - yield service, path - - -def find_service_path(env, service): - root = getroot(env) - if service == root: - return [] - for s, path in scan_services(root): - if s == service: - return path - return None - - -class TypeDirective(PyClasslike): - def get_index_text(self, modname, name_cls): - return _('%s (webservice type)') % name_cls[0] - - def add_target_and_index(self, name_cls, sig, signode): - ret = super(TypeDirective, self).add_target_and_index( - name_cls, sig, signode - ) - name = name_cls[0] - types = self.env.domaindata['wsme']['types'] - if name in types: - self.state_machine.reporter.warning( - 'duplicate type description of %s ' % name) - types[name] = self.env.docname - return ret - - -class AttributeDirective(PyClassmember): - doc_field_types = [ - Field('datatype', label=l_('Type'), has_arg=False, - names=('type', 'datatype')) - ] - - -def check_samples_slot(value): - """Validate the samples_slot option to the TypeDocumenter. - - Valid positions are 'before-docstring' and - 'after-docstring'. Using the explicit 'none' disables sample - output. The default is after-docstring. - """ - if not value: - return 'after-docstring' - val = directives.choice( - value, - ('none', # do not include - 'before-docstring', # show samples then docstring - 'after-docstring', # show docstring then samples - )) - return val - - -class TypeDocumenter(autodoc.ClassDocumenter): - objtype = 'type' - directivetype = 'type' - domain = 'wsme' - - required_arguments = 1 - default_samples_slot = 'after-docstring' - - option_spec = dict( - autodoc.ClassDocumenter.option_spec, - **{'protocols': lambda l: [v.strip() for v in l.split(',')], - 'samples-slot': check_samples_slot, - }) - - @staticmethod - def can_document_member(member, membername, isattr, parent): - # we don't want to be automaticaly used - # TODO check if the member is registered an an exposed type - return False - - def format_name(self): - return self.object.__name__ - - def format_signature(self): - return u'' - - def add_directive_header(self, sig): - super(TypeDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - result_len = len(self.directive.result) - for index, item in zip(reversed(range(result_len)), - reversed(self.directive.result)): - if ':module:' in item: - self.directive.result.pop(index) - - def import_object(self): - if super(TypeDocumenter, self).import_object(): - wsme.types.register_type(self.object) - return True - else: - return False - - def add_content(self, more_content, no_docstring=False): - # Check where to include the samples - samples_slot = self.options.samples_slot or self.default_samples_slot - - def add_docstring(): - super(TypeDocumenter, self).add_content( - more_content, no_docstring) - - def add_samples(): - protocols = get_protocols( - self.options.protocols or self.env.app.config.wsme_protocols - ) - content = [] - if protocols: - sample_obj = make_sample_object(self.object) - content.extend([ - l_(u'Data samples:'), - u'', - u'.. cssclass:: toggle', - u'' - ]) - for name, protocol in protocols: - language, sample = protocol.encode_sample_value( - self.object, sample_obj, format=True) - content.extend([ - name, - u' .. code-block:: ' + language, - u'', - ]) - content.extend( - u' ' * 8 + line - for line in six.text_type(sample).split('\n')) - for line in content: - self.add_line(line, u'<wsmeext.sphinxext') - - self.add_line(u'', '<wsmeext.sphinxext>') - - if samples_slot == 'after-docstring': - add_docstring() - add_samples() - elif samples_slot == 'before-docstring': - add_samples() - add_docstring() - else: - add_docstring() - - -class AttributeDocumenter(autodoc.AttributeDocumenter): - datatype = None - domain = 'wsme' - - @staticmethod - def can_document_member(member, membername, isattr, parent): - return isinstance(parent, TypeDocumenter) - - def import_object(self): - success = super(AttributeDocumenter, self).import_object() - if success: - self.datatype = self.object.datatype - return success - - def add_content(self, more_content, no_docstring=False): - self.add_line( - u':type: %s' % datatypename(self.datatype), - '<wsmeext.sphinxext>' - ) - self.add_line(u'', '<wsmeext.sphinxext>') - super(AttributeDocumenter, self).add_content( - more_content, no_docstring) - - def add_directive_header(self, sig): - super(AttributeDocumenter, self).add_directive_header(sig) - - -class RootDirective(Directive): - """ - This directive is to tell what class is the Webservice root - """ - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = { - 'webpath': directives.unchanged - } - - def run(self): - env = self.state.document.settings.env - rootpath = self.arguments[0].strip() - env.temp_data['wsme:rootpath'] = rootpath - if 'wsme:root' in env.temp_data: - del env.temp_data['wsme:root'] - if 'webpath' in self.options: - env.temp_data['wsme:webpath'] = self.options['webpath'] - return [] - - -class ServiceDirective(ObjectDescription): - name = 'service' - - optional_arguments = 1 - - def handle_signature(self, sig, signode): - path = sig.split('/') - - namespace = '/'.join(path[:-1]) - if namespace and not namespace.endswith('/'): - namespace += '/' - - servicename = path[-1] - - if not namespace and not servicename: - servicename = '/' - - signode += addnodes.desc_annotation('service ', 'service ') - - if namespace: - signode += addnodes.desc_addname(namespace, namespace) - - signode += addnodes.desc_name(servicename, servicename) - - return sig - - -class ServiceDocumenter(autodoc.ClassDocumenter): - domain = 'wsme' - objtype = 'service' - directivetype = 'service' - - def add_directive_header(self, sig): - super(ServiceDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - result_len = len(self.directive.result) - for index, item in zip(reversed(range(result_len)), - reversed(self.directive.result)): - if ':module:' in item: - self.directive.result.pop(index) - - 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 - result_len = len(self.directive.result) - for index, item in zip(reversed(range(result_len)), - reversed(self.directive.result)): - if ':module:' in item: - self.directive.result.pop(index) - - -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') diff --git a/wsmeext/sqlalchemy/__init__.py b/wsmeext/sqlalchemy/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/wsmeext/sqlalchemy/__init__.py +++ /dev/null diff --git a/wsmeext/sqlalchemy/controllers.py b/wsmeext/sqlalchemy/controllers.py deleted file mode 100644 index 504a46a..0000000 --- a/wsmeext/sqlalchemy/controllers.py +++ /dev/null @@ -1,97 +0,0 @@ -from wsme.rest import expose, validate -import wsme.types - -from wsmeext.sqlalchemy.types import SQLAlchemyRegistry - - -class CRUDControllerMeta(type): - def __init__(cls, name, bases, dct): - if cls.__saclass__ is not None: - if cls.__registry__ is None: - cls.__registry__ = wsme.types.registry - if cls.__wstype__ is None: - cls.__wstype__ = cls.__registry__.resolve_type( - SQLAlchemyRegistry.get( - cls.__registry__).getdatatype(cls.__saclass__)) - - cls.create = expose( - cls.__wstype__, - method='PUT', - wrap=True - )(cls.create) - cls.create = validate(cls.__wstype__)(cls.create) - - cls.read = expose( - cls.__wstype__, - method='GET', - wrap=True - )(cls.read) - cls.read = validate(cls.__wstype__)(cls.read) - - cls.update = expose( - cls.__wstype__, - method='POST', - wrap=True - )(cls.update) - cls.update = validate(cls.__wstype__)(cls.update) - - cls.delete = expose( - method='DELETE', - wrap=True - )(cls.delete) - cls.delete = validate(cls.__wstype__)(cls.delete) - - super(CRUDControllerMeta, cls).__init__(name, bases, dct) - - -class CRUDControllerBase(object): - __registry__ = None - __saclass__ = None - __wstype__ = None - __dbsession__ = None - - def _create_one(self, data): - obj = self.__saclass__() - data.to_instance(obj) - self.__dbsession__.add(obj) - return obj - - def _get_one(self, ref): - q = self.__dbsession__.query(self.__saclass__) - q = q.filter(ref.get_ref_criterion()) - return q.one() - - def _update_one(self, data): - obj = self._get_one(data) - if obj is None: - raise ValueError("No match for data=%s" % data) - data.to_instance(obj) - return obj - - def _delete(self, ref): - obj = self._get_one(ref) - self.__dbsession__.delete(obj) - - def create(self, data): - obj = self._create_one(data) - self.__dbsession__.flush() - return self.__wstype__(obj) - - def read(self, ref): - obj = self._get_one(ref) - return self.__wstype__(obj) - - def update(self, data): - obj = self._update_one(data) - self.__dbsession__.flush() - return self.__wstype__(obj) - - def delete(self, ref): - self._delete(ref) - self.__dbsession__.flush() - return None - - -CRUDController = CRUDControllerMeta( - 'CRUDController', (CRUDControllerBase,), {} -) diff --git a/wsmeext/sqlalchemy/types.py b/wsmeext/sqlalchemy/types.py deleted file mode 100644 index 414bf52..0000000 --- a/wsmeext/sqlalchemy/types.py +++ /dev/null @@ -1,200 +0,0 @@ -import datetime -import decimal -import logging - -import six - -from sqlalchemy.orm import class_mapper -from sqlalchemy.orm.properties import ColumnProperty, RelationProperty - -import sqlalchemy.types - -import wsme.types - -log = logging.getLogger(__name__) - - -class SQLAlchemyRegistry(object): - @classmethod - def get(cls, registry): - if not hasattr(registry, 'sqlalchemy'): - registry.sqlalchemy = cls() - return registry.sqlalchemy - - def __init__(self): - self.types = {} - self.satypeclasses = { - sqlalchemy.types.Integer: int, - sqlalchemy.types.Boolean: bool, - sqlalchemy.types.Float: float, - sqlalchemy.types.Numeric: decimal.Decimal, - sqlalchemy.types.Date: datetime.date, - sqlalchemy.types.Time: datetime.time, - sqlalchemy.types.DateTime: datetime.datetime, - sqlalchemy.types.String: wsme.types.text, - sqlalchemy.types.Unicode: wsme.types.text, - } - - def getdatatype(self, sadatatype): - if sadatatype.__class__ in self.satypeclasses: - return self.satypeclasses[sadatatype.__class__] - elif sadatatype in self.types: - return self.types[sadatatype] - else: - return sadatatype.__name__ - - -def register_saclass(registry, saclass, typename=None): - """Associate a webservice type name to a SQLAlchemy mapped class. - The default typename if the saclass name itself. - """ - if typename is None: - typename = saclass.__name__ - - SQLAlchemyRegistry.get(registry).types[saclass] = typename - - -class wsattr(wsme.types.wsattr): - def __init__(self, datatype, saproperty=None, **kw): - super(wsattr, self).__init__(datatype, **kw) - self.saname = saproperty.key - self.saproperty = saproperty - self.isrelation = isinstance(saproperty, RelationProperty) - - -def make_wsattr(registry, saproperty): - datatype = None - if isinstance(saproperty, ColumnProperty): - if len(saproperty.columns) > 1: - log.warning("Cannot handle multi-column ColumnProperty") - return None - datatype = SQLAlchemyRegistry.get(registry).getdatatype( - saproperty.columns[0].type) - elif isinstance(saproperty, RelationProperty): - other_saclass = saproperty.mapper.class_ - datatype = SQLAlchemyRegistry.get(registry).getdatatype(other_saclass) - if saproperty.uselist: - datatype = [datatype] - else: - log.warning("Don't know how to handle %s attributes" % - saproperty.__class__) - - if datatype: - return wsattr(datatype, saproperty) - - -class BaseMeta(wsme.types.BaseMeta): - def __new__(cls, name, bases, dct): - if '__registry__' not in dct: - dct['__registry__'] = wsme.types.registry - return type.__new__(cls, name, bases, dct) - - def __init__(cls, name, bases, dct): - saclass = getattr(cls, '__saclass__', None) - if saclass: - mapper = class_mapper(saclass) - cls._pkey_attrs = [] - cls._ref_attrs = [] - for prop in mapper.iterate_properties: - key = prop.key - if hasattr(cls, key): - continue - if key.startswith('_'): - continue - attr = make_wsattr(cls.__registry__, prop) - if attr is not None: - setattr(cls, key, attr) - - if attr and isinstance(prop, ColumnProperty) and \ - prop.columns[0] in mapper.primary_key: - cls._pkey_attrs.append(attr) - cls._ref_attrs.append(attr) - - register_saclass(cls.__registry__, cls.__saclass__, cls.__name__) - super(BaseMeta, cls).__init__(name, bases, dct) - - -class Base(six.with_metaclass(BaseMeta, wsme.types.Base)): - def __init__(self, instance=None, keyonly=False, attrs=None, eagerload=[]): - if instance: - self.from_instance(instance, keyonly, attrs, eagerload) - - def from_instance(self, instance, keyonly=False, attrs=None, eagerload=[]): - if keyonly: - attrs = self._pkey_attrs + self._ref_attrs - for attr in self._wsme_attributes: - if not isinstance(attr, wsattr): - continue - if attrs and not attr.isrelation and attr.name not in attrs: - continue - if attr.isrelation and attr.name not in eagerload: - continue - value = getattr(instance, attr.saname) - if attr.isrelation: - attr_keyonly = attr.name not in eagerload - attr_attrs = None - attr_eagerload = [] - if not attr_keyonly: - attr_attrs = [ - aname[len(attr.name) + 1:] - for aname in attrs - if aname.startswith(attr.name + '.') - ] - attr_eagerload = [ - aname[len(attr.name) + 1:] - for aname in eagerload - if aname.startswith(attr.name + '.') - ] - if attr.saproperty.uselist: - value = [ - attr.datatype.item_type( - o, - keyonly=attr_keyonly, - attrs=attr_attrs, - eagerload=attr_eagerload - ) - for o in value - ] - else: - value = attr.datatype( - value, - keyonly=attr_keyonly, - attrs=attr_attrs, - eagerload=attr_eagerload - ) - attr.__set__(self, value) - - def to_instance(self, instance): - for attr in self._wsme_attributes: - if isinstance(attr, wsattr): - value = attr.__get__(self, self.__class__) - if value is not wsme.types.Unset: - setattr(instance, attr.saname, value) - - def get_ref_criterion(self): - """Returns a criterion that match a database object - having the pkey/ref attribute values of this webservice object""" - criterions = [] - for attr in self._pkey_attrs + self._ref_attrs: - value = attr.__get__(self, self.__class__) - if value is not wsme.types.Unset: - criterions.append(attr.saproperty == value) - - -def generate_types(*classes, **kw): - registry = kw.pop('registry', wsme.types.registry) - prefix = kw.pop('prefix', '') - postfix = kw.pop('postfix', '') - makename = kw.pop('makename', lambda s: prefix + s + postfix) - - newtypes = {} - for c in classes: - if isinstance(c, list): - newtypes.update(generate_types(c)) - else: - name = makename(c.__name__) - newtypes[name] = BaseMeta(name, (Base, ), { - '__saclass__': c, - '__registry__': registry - }) - return newtypes diff --git a/wsmeext/tests/__init__.py b/wsmeext/tests/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/wsmeext/tests/__init__.py +++ /dev/null diff --git a/wsmeext/tests/test_extdirect.py b/wsmeext/tests/test_extdirect.py deleted file mode 100644 index 4c5bea8..0000000 --- a/wsmeext/tests/test_extdirect.py +++ /dev/null @@ -1,243 +0,0 @@ -import base64 -import datetime -import decimal - -try: - import simplejson as json -except ImportError: - import json # noqa - -import wsme.tests.protocol -from wsme.utils import parse_isodatetime, parse_isodate, parse_isotime -from wsme.types import isarray, isdict, isusertype - -import six - -if six.PY3: - from urllib.parse import urlencode -else: - from urllib import urlencode # noqa - - -def encode_arg(value): - if isinstance(value, tuple): - value, datatype = value - else: - datatype = type(value) - - if isinstance(datatype, list): - value = [encode_arg((item, datatype[0])) for item in value] - elif isinstance(datatype, dict): - key_type, value_type = list(datatype.items())[0] - value = dict(( - (encode_arg((key, key_type)), - encode_arg((value, value_type))) - for key, value in value.items() - )) - elif datatype in (datetime.date, datetime.time, datetime.datetime): - value = value.isoformat() - elif datatype == wsme.types.binary: - value = base64.encodestring(value).decode('ascii') - elif datatype == wsme.types.bytes: - value = value.decode('ascii') - elif datatype == decimal.Decimal: - value = str(value) - return value - - -def decode_result(value, datatype): - if value is None: - return None - if datatype == wsme.types.binary: - value = base64.decodestring(value.encode('ascii')) - return value - if isusertype(datatype): - datatype = datatype.basetype - if isinstance(datatype, list): - value = [decode_result(item, datatype[0]) for item in value] - elif isarray(datatype): - value = [decode_result(item, datatype.item_type) for item in value] - elif isinstance(datatype, dict): - key_type, value_type = list(datatype.items())[0] - value = dict(( - (decode_result(key, key_type), - decode_result(value, value_type)) - for key, value in value.items() - )) - elif isdict(datatype): - key_type, value_type = datatype.key_type, datatype.value_type - value = dict(( - (decode_result(key, key_type), - decode_result(value, value_type)) - for key, value in value.items() - )) - elif datatype == datetime.time: - value = parse_isotime(value) - elif datatype == datetime.date: - value = parse_isodate(value) - elif datatype == datetime.datetime: - value = parse_isodatetime(value) - elif hasattr(datatype, '_wsme_attributes'): - for attr in datatype._wsme_attributes: - if attr.key not in value: - continue - value[attr.key] = decode_result(value[attr.key], attr.datatype) - elif datatype == decimal.Decimal: - value = decimal.Decimal(value) - elif datatype == wsme.types.bytes: - value = value.encode('ascii') - elif datatype is not None and type(value) != datatype: - value = datatype(value) - return value - - -class TestExtDirectProtocol(wsme.tests.protocol.ProtocolTestCase): - protocol = 'extdirect' - protocol_options = { - 'namespace': 'MyNS.api', - 'nsfolder': 'app' - } - - def call(self, fname, _rt=None, _no_result_decode=False, _accept=None, - **kw): - path = fname.split('/') - try: - func, funcdef, args = self.root._lookup_function(path) - arguments = funcdef.arguments - except Exception: - 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 - } - }] - } diff --git a/wsmeext/tests/test_soap.py b/wsmeext/tests/test_soap.py deleted file mode 100644 index bc17696..0000000 --- a/wsmeext/tests/test_soap.py +++ /dev/null @@ -1,423 +0,0 @@ -import decimal -import datetime -import base64 - -import six - -import wsme.tests.protocol - -try: - import xml.etree.ElementTree as et -except ImportError: - 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 KeyError: - pass - - def clear(self, id): - self.d = {} - - -sudscache = SudsCache() - -tns = "http://foo.bar.baz/soap/" -typenamespace = "http://foo.bar.baz/types/" - -soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/' -xsi_ns = 'http://www.w3.org/2001/XMLSchema-instance' -body_qn = '{%s}Body' % soapenv_ns -fault_qn = '{%s}Fault' % soapenv_ns -faultcode_qn = '{%s}faultcode' % soapenv_ns -faultstring_qn = '{%s}faultstring' % soapenv_ns -faultdetail_qn = '{%s}detail' % soapenv_ns -type_qn = '{%s}type' % xsi_ns -nil_qn = '{%s}nil' % xsi_ns - - -def build_soap_message(method, params=""): - message = """<?xml version="1.0"?> -<soap:Envelope -xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" -xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" -soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> - - <soap:Body xmlns="%(typenamespace)s"> - <%(method)s> - %(params)s - </%(method)s> - </soap:Body> - -</soap:Envelope> -""" % dict(method=method, params=params, typenamespace=typenamespace) - return message - - -python_types = { - int: ('xs:int', str), - float: ('xs:float', str), - bool: ('xs:boolean', str), - wsme.types.bytes: ( - 'xs:string', - lambda x: x.decode('ascii') if isinstance(x, wsme.types.bytes) else x - ), - wsme.types.text: ('xs:string', wsme.types.text), - wsme.types.binary: ( - 'xs:base64Binary', - lambda x: base64.encodestring(x).decode('ascii') - ), - decimal.Decimal: ('xs:decimal', str), - datetime.date: ('xs:date', datetime.date.isoformat), - datetime.time: ('xs:time', datetime.time.isoformat), - datetime.datetime: ('xs:dateTime', datetime.datetime.isoformat), -} - -array_types = { - wsme.types.bytes: "String_Array", - wsme.types.text: "String_Array", - int: "Int_Array", - float: "Float_Array", - bool: "Boolean_Array", - datetime.datetime: "dateTime_Array" -} - -if not six.PY3: - array_types[long] = "Long_Array" # noqa - - -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, # noqa - '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. diff --git a/wsmeext/tests/test_sqlalchemy_controllers.py b/wsmeext/tests/test_sqlalchemy_controllers.py deleted file mode 100644 index 1956788..0000000 --- a/wsmeext/tests/test_sqlalchemy_controllers.py +++ /dev/null @@ -1,223 +0,0 @@ -import 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 diff --git a/wsmeext/tests/test_sqlalchemy_types.py b/wsmeext/tests/test_sqlalchemy_types.py deleted file mode 100644 index 8512015..0000000 --- a/wsmeext/tests/test_sqlalchemy_types.py +++ /dev/null @@ -1,72 +0,0 @@ -import 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'] diff --git a/wsmeext/tg1.py b/wsmeext/tg1.py deleted file mode 100644 index b978166..0000000 --- a/wsmeext/tg1.py +++ /dev/null @@ -1,173 +0,0 @@ -try: - 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 Exception: - 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) diff --git a/wsmeext/tg11.py b/wsmeext/tg11.py deleted file mode 100644 index 80ec50c..0000000 --- a/wsmeext/tg11.py +++ /dev/null @@ -1,40 +0,0 @@ -from 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 diff --git a/wsmeext/tg15.py b/wsmeext/tg15.py deleted file mode 100644 index 4b8a9b1..0000000 --- a/wsmeext/tg15.py +++ /dev/null @@ -1,20 +0,0 @@ -import 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 |