From e9c6edfe510f4ed407f8d2d84b4b931a382b48b3 Mon Sep 17 00:00:00 2001 From: Jim Rollenhagen Date: Thu, 26 Sep 2019 09:43:27 -0400 Subject: Retire github mirror, repo moved to opendev --- .gitignore | 29 - .gitreview | 4 - .hgtags | 14 - .zuul.yaml | 5 - LICENSE | 19 - README.md | 3 + README.rst | 112 ---- doc/Makefile | 134 ----- doc/_static/toggle.css | 10 - doc/_static/toggle.js | 9 - doc/_static/wsme.css | 27 - doc/api.rst | 45 -- doc/changes.rst | 439 -------------- doc/conf.py | 259 --------- doc/document.rst | 190 ------ doc/functions.rst | 204 ------- doc/gettingstarted.rst | 15 - doc/index.rst | 27 - doc/integrate.rst | 342 ----------- doc/make.bat | 155 ----- doc/protocols.rst | 366 ------------ doc/requirements.txt | 3 - doc/todo.rst | 31 - doc/types.rst | 246 -------- examples/demo/client.py | 32 - examples/demo/demo.py | 104 ---- examples/demo/setup.cfg | 2 - examples/demo/setup.py | 9 - examples/demo/sporeclient.py | 18 - requirements-py3.txt | 5 - requirements.txt | 5 - setup.cfg | 54 -- setup.py | 6 - tests/pecantest/setup.cfg | 6 - tests/pecantest/setup.py | 22 - tests/pecantest/test/__init__.py | 0 tests/pecantest/test/app.py | 15 - tests/pecantest/test/controllers/__init__.py | 0 tests/pecantest/test/controllers/root.py | 21 - tests/pecantest/test/controllers/ws.py | 150 ----- tests/pecantest/test/model/__init__.py | 2 - tests/pecantest/test/tests/__init__.py | 22 - tests/pecantest/test/tests/config.py | 24 - tests/pecantest/test/tests/test_ws.py | 247 -------- tests/rest/test_args.py | 20 - tests/sphinxexample/conf.py | 232 -------- tests/sphinxexample/document.rst | 43 -- tests/sphinxexample/index.rst | 3 - tests/test_cornice.py | 183 ------ tests/test_flask.py | 216 ------- tests/test_sphinxext.py | 51 -- tests/test_tg1.py | 196 ------- tests/test_tg15.py | 177 ------ tox-tmpl.ini | 141 ----- tox.ini | 406 ------------- toxgen.py | 144 ----- wsme/__init__.py | 10 - wsme/api.py | 237 -------- wsme/exc.py | 92 --- wsme/protocol.py | 147 ----- wsme/rest/__init__.py | 78 --- wsme/rest/args.py | 310 ---------- wsme/rest/json.py | 328 ----------- wsme/rest/protocol.py | 133 ----- wsme/rest/xml.py | 298 ---------- wsme/root.py | 372 ------------ wsme/runtime.py | 9 - wsme/spore.py | 64 -- wsme/tests/__init__.py | 0 wsme/tests/protocol.py | 720 ----------------------- wsme/tests/test_api.py | 419 ------------- wsme/tests/test_exc.py | 40 -- wsme/tests/test_protocols.py | 70 --- wsme/tests/test_protocols_commons.py | 159 ----- wsme/tests/test_restjson.py | 779 ------------------------- wsme/tests/test_restxml.py | 211 ------- wsme/tests/test_root.py | 122 ---- wsme/tests/test_spore.py | 51 -- wsme/tests/test_types.py | 667 --------------------- wsme/tests/test_utils.py | 97 ---- wsme/types.py | 840 --------------------------- wsme/utils.py | 118 ---- wsmeext/__init__.py | 2 - wsmeext/cornice.py | 168 ------ wsmeext/extdirect/__init__.py | 1 - wsmeext/extdirect/datastore.py | 121 ---- wsmeext/extdirect/protocol.py | 450 -------------- wsmeext/extdirect/sadatastore.py | 19 - wsmeext/flask.py | 108 ---- wsmeext/pecan.py | 142 ----- wsmeext/soap/__init__.py | 5 - wsmeext/soap/protocol.py | 478 --------------- wsmeext/soap/simplegeneric.py | 107 ---- wsmeext/soap/wsdl.py | 297 ---------- wsmeext/sphinxext.py | 600 ------------------- wsmeext/sqlalchemy/__init__.py | 0 wsmeext/sqlalchemy/controllers.py | 97 ---- wsmeext/sqlalchemy/types.py | 200 ------- wsmeext/tests/__init__.py | 0 wsmeext/tests/test_extdirect.py | 243 -------- wsmeext/tests/test_soap.py | 423 -------------- wsmeext/tests/test_sqlalchemy_controllers.py | 223 ------- wsmeext/tests/test_sqlalchemy_types.py | 72 --- wsmeext/tg1.py | 173 ------ wsmeext/tg11.py | 40 -- wsmeext/tg15.py | 20 - 106 files changed, 3 insertions(+), 15601 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitreview delete mode 100644 .hgtags delete mode 100644 .zuul.yaml delete mode 100644 LICENSE create mode 100644 README.md delete mode 100644 README.rst delete mode 100644 doc/Makefile delete mode 100644 doc/_static/toggle.css delete mode 100644 doc/_static/toggle.js delete mode 100644 doc/_static/wsme.css delete mode 100644 doc/api.rst delete mode 100644 doc/changes.rst delete mode 100644 doc/conf.py delete mode 100644 doc/document.rst delete mode 100644 doc/functions.rst delete mode 100644 doc/gettingstarted.rst delete mode 100644 doc/index.rst delete mode 100644 doc/integrate.rst delete mode 100644 doc/make.bat delete mode 100644 doc/protocols.rst delete mode 100644 doc/requirements.txt delete mode 100644 doc/todo.rst delete mode 100644 doc/types.rst delete mode 100644 examples/demo/client.py delete mode 100644 examples/demo/demo.py delete mode 100644 examples/demo/setup.cfg delete mode 100644 examples/demo/setup.py delete mode 100644 examples/demo/sporeclient.py delete mode 100644 requirements-py3.txt delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 tests/pecantest/setup.cfg delete mode 100644 tests/pecantest/setup.py delete mode 100644 tests/pecantest/test/__init__.py delete mode 100644 tests/pecantest/test/app.py delete mode 100644 tests/pecantest/test/controllers/__init__.py delete mode 100644 tests/pecantest/test/controllers/root.py delete mode 100644 tests/pecantest/test/controllers/ws.py delete mode 100644 tests/pecantest/test/model/__init__.py delete mode 100644 tests/pecantest/test/tests/__init__.py delete mode 100644 tests/pecantest/test/tests/config.py delete mode 100644 tests/pecantest/test/tests/test_ws.py delete mode 100644 tests/rest/test_args.py delete mode 100644 tests/sphinxexample/conf.py delete mode 100644 tests/sphinxexample/document.rst delete mode 100644 tests/sphinxexample/index.rst delete mode 100644 tests/test_cornice.py delete mode 100644 tests/test_flask.py delete mode 100644 tests/test_sphinxext.py delete mode 100644 tests/test_tg1.py delete mode 100644 tests/test_tg15.py delete mode 100644 tox-tmpl.ini delete mode 100644 tox.ini delete mode 100644 toxgen.py delete mode 100644 wsme/__init__.py delete mode 100644 wsme/api.py delete mode 100644 wsme/exc.py delete mode 100644 wsme/protocol.py delete mode 100644 wsme/rest/__init__.py delete mode 100644 wsme/rest/args.py delete mode 100644 wsme/rest/json.py delete mode 100644 wsme/rest/protocol.py delete mode 100644 wsme/rest/xml.py delete mode 100644 wsme/root.py delete mode 100644 wsme/runtime.py delete mode 100644 wsme/spore.py delete mode 100644 wsme/tests/__init__.py delete mode 100644 wsme/tests/protocol.py delete mode 100644 wsme/tests/test_api.py delete mode 100644 wsme/tests/test_exc.py delete mode 100644 wsme/tests/test_protocols.py delete mode 100644 wsme/tests/test_protocols_commons.py delete mode 100644 wsme/tests/test_restjson.py delete mode 100644 wsme/tests/test_restxml.py delete mode 100644 wsme/tests/test_root.py delete mode 100644 wsme/tests/test_spore.py delete mode 100644 wsme/tests/test_types.py delete mode 100644 wsme/tests/test_utils.py delete mode 100644 wsme/types.py delete mode 100644 wsme/utils.py delete mode 100644 wsmeext/__init__.py delete mode 100644 wsmeext/cornice.py delete mode 100644 wsmeext/extdirect/__init__.py delete mode 100644 wsmeext/extdirect/datastore.py delete mode 100644 wsmeext/extdirect/protocol.py delete mode 100644 wsmeext/extdirect/sadatastore.py delete mode 100644 wsmeext/flask.py delete mode 100644 wsmeext/pecan.py delete mode 100644 wsmeext/soap/__init__.py delete mode 100644 wsmeext/soap/protocol.py delete mode 100644 wsmeext/soap/simplegeneric.py delete mode 100644 wsmeext/soap/wsdl.py delete mode 100644 wsmeext/sphinxext.py delete mode 100644 wsmeext/sqlalchemy/__init__.py delete mode 100644 wsmeext/sqlalchemy/controllers.py delete mode 100644 wsmeext/sqlalchemy/types.py delete mode 100644 wsmeext/tests/__init__.py delete mode 100644 wsmeext/tests/test_extdirect.py delete mode 100644 wsmeext/tests/test_soap.py delete mode 100644 wsmeext/tests/test_sqlalchemy_controllers.py delete mode 100644 wsmeext/tests/test_sqlalchemy_types.py delete mode 100644 wsmeext/tg1.py delete mode 100644 wsmeext/tg11.py delete mode 100644 wsmeext/tg15.py 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:///ws/hello.json?who=you`` - - ``"Hello you !"`` - - * - ``http:///ws/hello.xml`` - - ``Hello World !`` - - * - ``http:///ws/api.wsdl`` - - A WSDL description for any SOAP client. - - -Main features -~~~~~~~~~~~~~ - -- Very simple API. -- Supports user-defined simple and complex types. -- Multi-protocol : REST+Json, REST+XML, SOAP, ExtDirect and more to come. -- Extensible : easy to add more protocols or more base types. -- Framework independence : adapters are provided to easily integrate - your API in any web framework, for example a wsgi container, - Pecan_, TurboGears_, Flask_, cornice_... -- Very few runtime dependencies: webob, simplegeneric. Optionnaly lxml and - simplejson if you need better performances. -- Integration in `Sphinx`_ for making clean documentation with - ``wsmeext.sphinxext``. - -.. _Pecan: http://pecanpy.org/ -.. _TurboGears: http://www.turbogears.org/ -.. _Flask: http://flask.pocoo.org/ -.. _cornice: http://pypi.python.org/pypi/cornice - -Install -~~~~~~~ - -:: - - pip install WSME - -or, if you do not have pip on your system or virtualenv - -:: - - easy_install WSME - -Changes -~~~~~~~ - -- Read the `Changelog`_ - -Getting Help -~~~~~~~~~~~~ - -- Read the `WSME Documentation`_. -- Questions about WSME should go to the `python-wsme mailinglist`_. - -Contribute -~~~~~~~~~~ - -* Documentation: http://packages.python.org/WSME/ -* Source: http://git.openstack.org/cgit/openstack/wsme -* Bugs: https://bugs.launchpad.net/wsme/+bugs -* Code review: https://review.openstack.org/#/q/project:openstack/wsme,n,z - -.. _Changelog: http://packages.python.org/WSME/changes.html -.. _python-wsme mailinglist: http://groups.google.com/group/python-wsme -.. _WSME Documentation: http://packages.python.org/WSME/ -.. _WSME issue tracker: https://bugs.launchpad.net/wsme/+bugs -.. _Sphinx: http://sphinx.pocoo.org/ 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 ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -ziphtml: html - rm -f $(BUILDDIR)/wsme-documentation.zip - cd $(BUILDDIR)/html && zip -r ../wsme-documentation.zip . - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/WebServicesMadeEasy" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WebServicesMadeEasy" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." 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 -# " v documentation". -html_title = "WSME %s" % release - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = "WSME" - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'WebServicesMadeEasydoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -latex_paper_size = 'a4' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation', - u'Christophe de Vienne', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -latex_preamble = ''' -\usepackage[T2A]{fontenc} -\usepackage[utf8]{inputenc} -''' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation', - [u'Christophe de Vienne'], 1) -] - - -autodoc_member_order = 'bysource' - -wsme_protocols = [ - 'restjson', 'restxml', 'soap', 'extdirect' -] - -intersphinx_mapping = { - 'python': ('http://docs.python.org/', None), - 'six': ('http://packages.python.org/six/', None), -} - - -def setup(app): - # confval directive taken from the sphinx doc - app.add_object_type('confval', 'confval', - objname='configuration value', - indextemplate='pair: %s; configuration value') 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:: - - Define the service root controller for this documentation source file. - To set it globally, see :confval:`wsme_root`. - - A ``webpath`` option allows override of :confval:`wsme_webpath`. - - Example: - - .. code-block:: rst - - .. wsme:root:: myapp.controllers.MyWSRoot - :webpath: /api - -.. rst:directive:: .. service:: name/space/ServiceName - - Declare a service. - -.. rst:directive:: .. type:: MyComplexType - - Equivalent to the :rst:dir:`py:class` directive to document a complex type - -.. rst:directive:: .. attribute:: aname - - Equivalent to the :rst:dir:`py:attribute` directive to document a complex type - attribute. It takes an additional ``:type:`` field. - -Example -~~~~~~~ - -.. list-table:: - :header-rows: 1 - - * - Source - - Result - - * - .. code-block:: rst - - .. wsme:root:: wsmeext.sphinxext.SampleService - :webpath: /api - - .. wsme:type:: MyType - - .. wsme:attribute:: test - - :type: int - - .. wsme:service:: name/space/SampleService - - .. wsme:function:: doit - - - .. wsme:root:: wsmeext.sphinxext.SampleService - :webpath: /api - - .. wsme:type:: MyType - - .. wsme:attribute:: test - - :type: int - - .. wsme:service:: name/space/SampleService - - .. wsme:function:: getType - - Returns a :wsme:type:`MyType ` - - -Autodoc directives -~~~~~~~~~~~~~~~~~~ - -Theses directives scan your code to generate the documentation from the -docstrings and your API types and controllers. - -.. rst:directive:: .. autotype:: myapp.MyType - - Generate the myapp.MyType documentation. - -.. rst:directive:: .. autoattribute:: myapp.MyType.aname - - Generate the myapp.MyType.aname documentation. - -.. rst:directive:: .. autoservice:: myapp.MyService - - Generate the myapp.MyService documentation. - -.. rst:directive:: .. autofunction:: myapp.MyService.myfunction - - Generate the myapp.MyService.myfunction documentation. - -Full Example ------------- - -Python source -~~~~~~~~~~~~~ - -.. literalinclude:: ../wsmeext/sphinxext.py - :lines: 69-96 - :language: python - -Documentation source -~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: rst - - .. default-domain:: wsmeext - - .. type:: int - - An integer - - .. autotype:: wsmeext.sphinxext.SampleType - :members: - - .. autoservice:: wsmeext.sphinxext.SampleService - :members: - -Result -~~~~~~ - -.. default-domain:: wsmeext - -.. type:: int - - An integer - -.. autotype:: wsmeext.sphinxext.SampleType - :members: - -.. autoservice:: wsmeext.sphinxext.SampleService - :members: - - -.. _Sphinx: http://sphinx.pocoo.org/ 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 `_ - instead of the expose decorator from Pecan. - -Configuration -~~~~~~~~~~~~~ - -WSME can be configured through the application configation, by adding a 'wsme' -configuration entry in ``config.py``: - -.. code-block:: python - - wsme = { - 'debug': True - } - -Valid configuration variables are : - -- ``'debug'``: Whether or not to include exception tracebacks in the returned - server-side errors. - -Example -~~~~~~~ - -The `example `_ from the Pecan documentation becomes: - -.. code-block:: python - - from wsmeext.pecan import wsexpose - - class BooksController(RestController): - @wsexpose(Book, int, int) - def get(self, author_id, id): - # .. - - @wsexpose(Book, int, int, body=Book) - def put(self, author_id, id, book): - # .. - - class AuthorsController(RestController): - books = BooksController() - -.. _Pecan: http://pecanpy.org/ - -.. _adapter-tg1: - -Turbogears 1.x --------------- - -The TG adapters have an api very similar to TGWebServices. Migrating from it -should be straightforward (a little howto migrate would not hurt though, and it -will be written as soon as possible). - -:mod:`wsmeext.tg11` -- TG 1.1 adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.tg11 - -.. function:: wsexpose(return_type, \*arg_types, \*\*options) - - See @\ :func:`signature` for parameters documentation. - - Can be used on any function of a controller - instead of the expose decorator from TG. - -.. function:: wsvalidate(\*arg_types) - - Set the argument types of an exposed function. This decorator is provided - so that WSME is an almost drop-in replacement for TGWebServices. If - starting from scratch you can use \ :func:`wsexpose` only - -.. function:: adapt(wsroot) - - Returns a TG1 controller instance that publish a :class:`wsme.WSRoot`. - It can then be mounted on a TG1 controller. - - Because the adapt function modifies the cherrypy filters of the controller - the 'webpath' of the WSRoot instance must be consistent with the path it - will be mounted on. - -:mod:`wsmeext.tg15` -- TG 1.5 adapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. module:: wsmeext.tg15 - -This adapter has the exact same api as :mod:`wsmeext.tg11`. - -Example -~~~~~~~ - -In a freshly quickstarted tg1 application (let's say, wsmedemo), you can add -REST-ish functions anywhere in your controller tree. Here directly on the root, -in controllers.py: - -.. code-block:: python - - # ... - - # For tg 1.5, import from wsmeext.tg15 instead : - from wsmeext.tg11 import wsexpose, WSRoot - - class Root(controllers.RootController): - # Having a WSRoot on /ws is only required to enable additional - # protocols. For REST-only services, it can be ignored. - ws = adapt( - WSRoot(webpath='/ws', protocols=['soap']) - ) - - @wsexpose(int, int, int) - def multiply(self, a, b): - return a * b - -.. _TurboGears: http://www.turbogears.org/ - -Other frameworks ----------------- - -Bottle -~~~~~~ - -No adapter is provided yet but it should not be hard to write one, by taking -example on the cornice adapter. - -This example only show how to mount a WSRoot inside a bottle application. - -.. code-block:: python - - import bottle - import wsme - - class MyRoot(wsme.WSRoot): - @wsme.expose(unicode) - def helloworld(self): - return u"Hello World !" - - root = MyRoot(webpath='/ws', protocols=['restjson']) - - bottle.mount('/ws', root.wsgiapp()) - bottle.run() - -Pyramid -~~~~~~~ - -The recommended way of using WSME inside Pyramid is to use -:ref:`adapter-cornice`. 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 ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\WebServicesMadeEasy.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\WebServicesMadeEasy.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end 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 | -| | | -| | a string | -+---------------+----------------------------------------+ -| ``unicode`` | .. code-block:: xml | -| | | -| | a string | -+---------------+----------------------------------------+ -| ``int`` | .. code-block:: xml | -| | | -| | 5 | -+---------------+----------------------------------------+ -| ``float`` | .. code-block:: xml | -| | | -| | 3.14 | -+---------------+----------------------------------------+ -| ``bool`` | .. code-block:: xml | -| | | -| | true | -+---------------+----------------------------------------+ -| ``Decimal`` | .. code-block:: xml | -| | | -| | 5.46 | -+---------------+----------------------------------------+ -| ``date`` | .. code-block:: xml | -| | | -| | 2010-04-27 | -+---------------+----------------------------------------+ -| ``time`` | .. code-block:: xml | -| | | -| | 12:54:18 | -+---------------+----------------------------------------+ -| ``datetime`` | .. code-block:: xml | -| | | -| | 2010-04-27T12:54:18 | -+---------------+----------------------------------------+ -| Arrays | .. code-block:: xml | -| | | -| | | -| | Dinausaurs | -| | Rachel | -| | | -+---------------+----------------------------------------+ -| None | .. code-block:: xml | -| | | -| | | -+---------------+----------------------------------------+ -| Complex types | .. code-block:: xml | -| | | -| | | -| | 1 | -| | Ross | -| | | -+---------------+----------------------------------------+ - -Return -~~~~~~ - -A xml tree with a top 'result' element. - -.. code-block:: xml - - - 1 - Ross - Geller - - -Errors -~~~~~~ - -A xml tree with a top 'error' element, having 'faultcode', 'faultstring' -and 'debuginfo' subelements: - -.. code-block:: xml - - - Client - id is missing - - -SOAP ----- - -:name: ``'soap'`` - -Implements the SOAP protocol. - -A wsdl definition of the webservice is available at the 'api.wsdl' subpath. -(``/ws/api.wsdl`` in our example). - -The protocol is selected if the request matches one of the following condition: - -- The Content-Type is 'application/soap+xml' -- A header 'Soapaction' is present - -Options -~~~~~~~ - -:tns: Type namespace - -ExtDirect ---------- - -:name: ``extdirect`` - -Implements the `Ext Direct`_ protocol. - -The provider definition is made available at the ``/extdirect/api.js`` subpath. - -The router url is ``/extdirect/router[/subnamespace]``. - -Options -~~~~~~~ - -:namespace: Base namespace of the api. Used for the provider definition. -:params_notation: Default notation for function call parameters. Can be - 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 ` -- for transporting binary data as - base64 strings. -- :class:`Enum ` -- enforce that the values belongs to a - pre-defined list of values. - -These types are good examples of how to define user types. Have -a look at their source code! - -Here is a little example that combines :class:`binary ` -and :class:`Enum `:: - - ImageKind = Enum(str, 'jpeg', 'gif') - - class Image(object): - name = unicode - kind = ImageKind - data = binary - -.. data:: wsme.types.binary - - The :class:`wsme.types.BinaryType` instance to use when you need to - transfer base64 encoded data. - -.. autoclass:: wsme.types.BinaryType - -.. autoclass:: wsme.types.Enum - - -Complex types -------------- - -Complex types are structured types. They are defined as simple Python classes -and will be mapped to adequate structured types in the various protocols. - -A base class for structured types is provided, :class:`wsme.types.Base`, -but is not mandatory. The only thing it adds is a default constructor. - -The attributes that are set at the class level will be used by WSME to discover -the structure. These attributes can be: - - - A datatype -- Any native, user or complex type. - - A :class:`wsattr ` -- This allows you to add more information about - the attribute, for example if it is mandatory. - - A :class:`wsproperty ` -- A special typed property. Works - like standard ``property`` with additional properties like - :class:`wsattr `. - -Attributes having a leading '_' in their name will be ignored, as well as the -attributes that are not in the above list. This means the type can have methods, -they will not get in the way. - -Example -~~~~~~~ - -:: - - Gender = wsme.types.Enum(str, 'male', 'female') - Title = wsme.types.Enum(str, 'M', 'Mrs') - - class Person(wsme.types.Base): - lastname = wsme.types.wsattr(unicode, mandatory=True) - firstname = wsme.types.wsattr(unicode, mandatory=True) - - age = int - gender = Gender - title = Title - - hobbies = [unicode] - -Rules -~~~~~ - -A few things you should know about complex types: - - - The class must have a default constructor -- - Since instances of the type will be created by the protocols when - used as input types, they must be instantiable without any argument. - - - Complex types are registered automatically - (and thus inspected) as soon a they are used in expose or validate, - even if they are nested in another complex type. - - If for some reason you need to control when type is inspected, you - can use :func:`wsme.types.register_type`. - - - The datatype attributes will be replaced. - - When using the 'short' way of defining attributes, ie setting a - simple data type, they will be replaced by a wsattr instance. - - So, when you write:: - - class Person(object): - name = unicode - - After type registration the class will actually be equivalent to:: - - class Person(object): - name = wsattr(unicode) - - You can still access the datatype by accessing the attribute on the - class, along with the other wsattr properties:: - - class Person(object): - name = unicode - - register_type(Person) - - assert Person.name.datatype is unicode - assert Person.name.key == "name" - assert Person.name.mandatory is False - - - The default value of instance attributes is - :data:`Unset `. - - :: - - class Person(object): - name = wsattr(unicode) - - p = Person() - assert p.name is Unset - - This allows the protocol to make a clear distinction between null values - that will be transmitted, and unset values that will not be transmitted. - - For input values, it allows the code to know if the values were, or were not, - sent by the caller. - - - When 2 complex types refer to each other, their names can be - used as datatypes to avoid adding attributes afterwards: - - :: - - class A(object): - b = wsattr('B') - - class B(object): - a = wsattr(A) - - -Predefined Types -~~~~~~~~~~~~~~~~ - -.. default-domain:: wsme - -- .. autotype:: wsme.types.File - :members: - 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 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 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 '1' in body - assert 'aname' in body - - def test_post_body_parameter_validation(self): - res = self.app.post( - '/authors', '{"firstname": "Robert"}', - headers={"Content-Type": "application/json"}, - expect_errors=True - ) - self.assertEqual(res.status_int, 400) - a = json.loads(res.body.decode('utf-8')) - self.assertEqual(a['faultcode'], 'Client') - self.assertEqual(a['faultstring'], "I don't like this author!") - - def test_post_body_parameter(self): - res = self.app.post( - '/authors', '{"firstname": "test"}', - headers={"Content-Type": "application/json"} - ) - assert res.status_int == 201 - a = json.loads(res.body.decode('utf-8')) - assert a['id'] == 10 - assert a['firstname'] == 'test' - - def test_put_parameter_validate(self): - res = self.app.put( - '/authors/foobar', '{"firstname": "test"}', - headers={"Content-Type": "application/json"}, - expect_errors=True - ) - self.assertEqual(res.status_int, 400) - a = json.loads(res.body.decode('utf-8')) - self.assertEqual( - a['faultstring'], - "Invalid input for field/attribute author_id. " - "Value: 'foobar'. unable to convert to int. Error: invalid " - "literal for int() with base 10: 'foobar'") - - def test_clientsideerror(self): - expected_status_code = 400 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/999.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Client' - - res = self.app.get( - '/authors/999.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert 'Client' in res.body.decode('utf-8') - - def test_custom_clientside_error(self): - expected_status_code = 404 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/998.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Client' - - res = self.app.get( - '/authors/998.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert 'Client' in res.body.decode('utf-8') - - def test_custom_non_http_clientside_error(self): - expected_status_code = 500 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/997.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Server' - - res = self.app.get( - '/authors/997.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert 'Server' in res.body.decode('utf-8') - - def test_clientsideerror_status_code(self): - expected_status_code = 403 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/996.json', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Client' - - res = self.app.get( - '/authors/996.xml', - expect_errors=True - ) - self.assertEqual(res.status, expected_status) - assert 'Client' in res.body.decode('utf-8') - - def test_non_default_response(self): - expected_status_code = 401 - expected_status = http_response_messages[expected_status_code] - res = self.app.get( - '/authors/911.json', - expect_errors=True - ) - self.assertEqual(res.status_int, expected_status_code) - self.assertEqual(res.status, expected_status) - - def test_non_default_response_return_type(self): - res = self.app.get( - '/authors/913', - ) - self.assertEqual(res.status_int, 200) - self.assertEqual(res.body, b'"foo"') - self.assertEqual(res.content_length, 5) - - def test_non_default_response_return_type_no_content(self): - res = self.app.get( - '/authors/912', - ) - self.assertEqual(res.status_int, 204) - self.assertEqual(res.body, b'') - self.assertEqual(res.content_length, 0) - - def test_serversideerror(self): - expected_status_code = 500 - expected_status = http_response_messages[expected_status_code] - res = self.app.get('/divide_by_zero.json', expect_errors=True) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Server' - assert a['debuginfo'] is None - - def test_serversideerror_with_debug(self): - expected_status_code = 500 - expected_status = http_response_messages[expected_status_code] - pecan.set_config({'wsme': {'debug': True}}) - res = self.app.get('/divide_by_zero.json', expect_errors=True) - self.assertEqual(res.status, expected_status) - a = json.loads(res.body.decode('utf-8')) - assert a['faultcode'] == 'Server' - assert a['debuginfo'].startswith('Traceback (most recent call last):') - - def test_json_only(self): - res = self.app.get('/authors/json_only.json') - assert res.status_int == 200 - body = json.loads(res.body.decode('utf-8')) - assert len(body) == 1 - assert body[0]['firstname'] == u"aname" - assert body[0]['books'] == [] - assert body[0]['id'] == 1 - res = self.app.get('/authors/json_only.xml', expect_errors=True) - - def test_xml_only(self): - res = self.app.get('/authors/xml_only.xml') - assert res.status_int == 200 - assert '1' in res.body.decode('utf-8') - assert 'aname' in res.body.decode('utf-8') - assert '' in res.body.decode('utf-8') - res = self.app.get('/authors/xml_only.json', expect_errors=True) - - def test_body_parameter(self): - res = self.app.put( - '/authors/1/books/2.json', - '{"name": "Alice au pays des merveilles"}', - headers={"Content-Type": "application/json"} - ) - book = json.loads(res.body.decode('utf-8')) - assert book['id'] == 2 - assert book['author']['id'] == 1 - - def test_no_content_type_if_no_return_type(self): - if six.PY3: - self.skipTest( - "This test does not work in Python 3 until https://review.openstack.org/#/c/48439/ is merged") - res = self.app.delete('/authors/4') - assert "Content-Type" not in res.headers, res.headers['Content-Type'] 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 -# " v documentation". -html_title = "WSME %s" % release - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'WebServicesMadeEasydoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation', - u'Christophe de Vienne', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation', - [u'Christophe de Vienne'], 1) -] - - -autodoc_member_order = 'bysource' - -wsme_protocols = [ - 'restjson', 'restxml' -] 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 ` - - -.. 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'1first' - ) - - def test_post_json_data(self): - data = json.dumps({"name": "new"}) - resp = self.app.post( - '/users', data, - headers={"Content-Type": "application/json"} - ) - self.assertEqual( - resp.body, - b'{"id": 2, "name": "new"}' - ) - - def test_post_xml_data(self): - data = 'new' - resp = self.app.post( - '/users', data, - headers={"Content-Type": "text/xml"} - ) - self.assertEqual( - resp.body, - b'2new' - ) - - def test_pass_request(self): - resp = self.app.get('/needrequest') - assert resp.json is True - - def test_resource_collection_get(self): - resp = self.app.get('/author') - assert len(resp.json) == 3 - assert resp.json[0]['name'] == 'Author 1' - assert resp.json[1]['name'] == 'Author 2' - assert resp.json[2]['name'] == 'Author 3' - - def test_resource_get(self): - resp = self.app.get('/author/5') - assert resp.json['name'] == 'Author 5' - - def test_resource_post(self): - resp = self.app.post( - '/author/5', - json.dumps({"name": "Author 5"}), - headers={"Content-Type": "application/json"} - ) - assert resp.json['authorId'] == 5 - assert resp.json['name'] == 'Author 5' - - def test_server_error(self): - resp = self.app.get('/divide?a=1&b=0', expect_errors=True) - self.assertEqual(resp.json['faultcode'], 'Server') - self.assertEqual(resp.status_code, 500) - - def test_client_error(self): - resp = self.app.get( - '/divide?a=1&c=0', - headers={'Accept': 'application/json'}, - expect_errors=True - ) - self.assertEqual(resp.json['faultcode'], 'Client') - self.assertEqual(resp.status_code, 400) - - def test_runtime_error(self): - resp = self.app.get( - '/secret', - headers={'Accept': 'application/json'}, - expect_errors=True - ) - self.assertEqual(resp.json['faultcode'], 'Client') - self.assertEqual(resp.status_code, 401) 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/') -@signature(Model, text) -def get_model(name): - return Model(name=name) - - -@test_app.route('/models//secret') -@signature(Model, text) -def model_secret(name): - abort(403) - - -@test_app.route('/models//custom-error') -@signature(Model, text) -def model_custom_error(name): - class CustomError(Exception): - code = 412 - raise CustomError("FOO!") - - -@test_app.route('/models', methods=['POST']) -@signature(Model, body=Model) -def post_model(body): - return Model(name=body.name) - - -@test_app.route('/status_sig') -@signature(int, status_code=201) -def get_status_sig(): - return 1 - - -@test_app.route('/status_response') -@signature(int) -def get_status_response(): - return Response(1, status_code=201) - - -class RestFullApi(restful.Resource): - @signature(Model) - def get(self): - return Model(id=1, name=u"Gérard") - - @signature(int, body=Model) - def post(self, model): - return model.id - -api.add_resource(RestFullApi, '/restful') - - -class FlaskrTestCase(unittest.TestCase): - - def setUp(self): - test_app.config['TESTING'] = True - self.app = test_app.test_client() - - def tearDown(self): - pass - - def test_multiply(self): - r = self.app.get('/multiply?a=2&b=5') - assert r.data == b'10', r.data - - def test_get_model(self): - resp = self.app.get('/models/test') - assert resp.status_code == 200 - - def test_list_models(self): - resp = self.app.get('/models') - assert resp.status_code == 200 - - def test_array_parameter(self): - resp = self.app.get('/models?q.op=%3D&q.attr=name&q.value=second') - assert resp.status_code == 200 - self.assertEqual( - resp.data, b'[{"name": "second"}]' - ) - - def test_post_model(self): - resp = self.app.post('/models', data={"body.name": "test"}) - assert resp.status_code == 200 - resp = self.app.post( - '/models', - data=json.dumps({"name": "test"}), - content_type="application/json" - ) - assert resp.status_code == 200 - - def test_get_status_sig(self): - resp = self.app.get('/status_sig') - assert resp.status_code == 201 - - def test_get_status_response(self): - resp = self.app.get('/status_response') - assert resp.status_code == 201 - - def test_custom_clientside_error(self): - r = self.app.get( - '/models/test/secret', - headers={'Accept': 'application/json'} - ) - assert r.status_code == 403, r.status_code - assert '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"Client" - b"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"") - - # 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'Client' - b'FOO!' - b'') - - def test_serversideerror(self): - r = self.app.get('/divide_by_zero') - assert r.status_code == 500 - data = json.loads(r.data) - self.assertEqual(data['debuginfo'], None) - self.assertEqual(data['faultcode'], 'Server') - self.assertIn('by zero', data['faultstring']) - - def test_restful_get(self): - r = self.app.get('/restful', headers={'Accept': 'application/json'}) - self.assertEqual(r.status_code, 200) - - data = json.loads(r.data) - - self.assertEqual(data['id'], 1) - self.assertEqual(data['name'], u"Gérard") - - def test_restful_post(self): - r = self.app.post( - '/restful', - data=json.dumps({'id': 5, 'name': u'Huguette'}), - headers={ - 'Accept': 'application/json', - 'Content-Type': 'application/json'}) - self.assertEqual(r.status_code, 200) - - data = json.loads(r.data) - - self.assertEqual(data, 5) - -if __name__ == '__main__': - test_app.run() 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 == "50" - - def test_custom_clientside_error(self): - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'application/json'}, - expect_errors=True - ) - assert response.status_int == 400 - assert simplejson.loads(response.body) == { - "debuginfo": None, - "faultcode": "Server", - "faultstring": "(400, 'Cannot divide by zero!')" - } - - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'text/xml'}, - expect_errors=True - ) - assert response.status_int == 400 - assert response.body == ("Server" - "(400, 'Cannot divide by zero!')" - "") - - def test_soap_wsdl(self): - ts = test_soap.TestSOAP('test_wsdl') - ts.app = self.app - ts.ws_path = '/ws/' - ts.run() - #wsdl = self.app.get('/ws/api.wsdl').body - #print wsdl - #assert 'multiply' in wsdl - - def test_soap_call(self): - ts = test_soap.TestSOAP('test_wsdl') - ts.app = self.app - ts.ws_path = '/ws/' - - print ts.ws_path - assert ts.call('multiply', a=5, b=10, _rt=int) == 50 - - def test_scan_api_loops(self): - class MyRoot(object): - pass - - MyRoot.loop = MyRoot() - - root = MyRoot() - - api = list(wsmeext.tg1._scan_api(root)) - print(api) - - self.assertEqual(len(api), 0) - - def test_scan_api_maxlen(self): - class ARoot(object): - pass - - def make_subcontrollers(n): - c = type('Controller%s' % n, (object,), {}) - return c - - c = ARoot - for n in 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 == "50" - - def test_custom_clientside_error(self): - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'application/json'}, - expect_errors=True - ) - assert response.status_int == 400 - assert simplejson.loads(response.body) == { - "debuginfo": None, - "faultcode": "Client", - "faultstring": "(400, 'Cannot divide by zero!')" - } - - response = self.app.post( - "/divide", - simplejson.dumps({'a': 5, 'b': 0}), - {'Content-Type': 'application/json', 'Accept': 'text/xml'}, - expect_errors=True - ) - assert response.status_int == 400 - assert response.body == ("Client" - "(400, 'Cannot divide by zero!')" - "") - - def test_soap_wsdl(self): - wsdl = self.app.get('/ws/api.wsdl').body - print wsdl - assert 'multiply' in wsdl - - def test_soap_call(self): - ts = test_soap.TestSOAP('test_wsdl') - ts.app = self.app - ts.ws_path = '/ws/' - - print ts.ws_path - assert ts.call('multiply', a=5, b=10, _rt=int) == 50 - - def test_scan_api_loops(self): - class MyRoot(object): - pass - - MyRoot.loop = MyRoot() - - root = MyRoot() - - api = list(wsmeext.tg1._scan_api(root)) - print(api) - - self.assertEqual(len(api), 0) - - def test_scan_api_maxlen(self): - class ARoot(object): - pass - - def make_subcontrollers(n): - c = type('Controller%s' % n, (object,), {}) - return c - - c = ARoot - for n in 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[^\.])' % 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\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[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[0-2][0-9]):(?P[0-5][0-9]):(?P[0-6][0-9])') - - -def xml_indent(elem, level=0): - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - for e in elem: - xml_indent(e, level + 1) - if not e.tail or not e.tail.strip(): - e.tail = i - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - -@generic -def toxml(datatype, key, value): - """ - A generic converter from python to xml elements. - - If a non-complex user specific type is to be used in the api, - a specific toxml should be added:: - - from wsme.protocol.restxml import toxml - - myspecialtype = object() - - @toxml.when_object(myspecialtype) - def myspecialtype_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = str(value) - return el - """ - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - if wsme.types.isusertype(datatype): - return toxml(datatype.basetype, - key, datatype.tobasetype(value)) - elif wsme.types.iscomplex(datatype): - for attrdef in datatype._wsme_attributes: - attrvalue = getattr(value, attrdef.key) - if attrvalue is not wsme.types.Unset: - el.append(toxml(attrdef.datatype, attrdef.name, - attrvalue)) - else: - el.text = six.text_type(value) - return el - - -@generic -def fromxml(datatype, element): - """ - A generic converter from xml elements to python datatype. - - If a non-complex user specific type is to be used in the api, - a specific fromxml should be added:: - - from wsme.protocol.restxml import fromxml - - class MySpecialType(object): - pass - - @fromxml.when_object(MySpecialType) - def myspecialtype_fromxml(datatype, element): - if element.get('nil', False): - return None - return MySpecialType(element.text) - """ - if element.get('nil', False): - return None - if wsme.types.isusertype(datatype): - return datatype.frombasetype(fromxml(datatype.basetype, element)) - if wsme.types.iscomplex(datatype): - obj = datatype() - for attrdef in wsme.types.list_attributes(datatype): - sub = element.find(attrdef.name) - if sub is not None: - val_fromxml = fromxml(attrdef.datatype, sub) - if getattr(attrdef, 'readonly', False): - raise InvalidInput(attrdef.name, val_fromxml, - "Cannot set read only field.") - setattr(obj, attrdef.key, val_fromxml) - elif attrdef.mandatory: - raise InvalidInput(attrdef.name, None, - "Mandatory field missing.") - return wsme.types.validate_value(datatype, obj) - if datatype is wsme.types.bytes: - return element.text.encode('ascii') - return datatype(element.text) - - -@toxml.when_type(wsme.types.ArrayType) -def array_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - for item in value: - el.append(toxml(datatype.item_type, 'item', item)) - return el - - -@toxml.when_type(wsme.types.DictType) -def dict_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - for item in value.items(): - key = toxml(datatype.key_type, 'key', item[0]) - value = toxml(datatype.value_type, 'value', item[1]) - node = et.Element('item') - node.append(key) - node.append(value) - el.append(node) - return el - - -@toxml.when_object(wsme.types.bytes) -def bytes_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value.decode('ascii') - return el - - -@toxml.when_object(bool) -def bool_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value and 'true' or 'false' - return el - - -@toxml.when_object(datetime.date) -def date_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value.isoformat() - return el - - -@toxml.when_object(datetime.datetime) -def datetime_toxml(datatype, key, value): - el = et.Element(key) - if value is None: - el.set('nil', 'true') - else: - el.text = value.isoformat() - return el - - -@fromxml.when_type(wsme.types.ArrayType) -def array_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return [ - fromxml(datatype.item_type, item) - for item in element.findall('item') - ] - - -@fromxml.when_object(bool) -def bool_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return element.text.lower() != 'false' - - -@fromxml.when_type(wsme.types.DictType) -def dict_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return dict(( - (fromxml(datatype.key_type, item.find('key')), - fromxml(datatype.value_type, item.find('value'))) - for item in element.findall('item'))) - - -@fromxml.when_object(wsme.types.text) -def unicode_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.types.text(element.text) if element.text else six.u('') - - -@fromxml.when_object(datetime.date) -def date_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.utils.parse_isodate(element.text) - - -@fromxml.when_object(datetime.time) -def time_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.utils.parse_isotime(element.text) - - -@fromxml.when_object(datetime.datetime) -def datetime_fromxml(datatype, element): - if element.get('nil') == 'true': - return None - return wsme.utils.parse_isodatetime(element.text) - - -def parse(s, datatypes, bodyarg): - if hasattr(s, 'read'): - tree = et.parse(s) - else: - tree = et.fromstring(s) - if bodyarg: - name = list(datatypes.keys())[0] - return {name: fromxml(datatypes[name], tree)} - else: - kw = {} - extra_args = [] - for sub in tree: - if sub.tag not in datatypes: - extra_args.append(sub.tag) - kw[sub.tag] = fromxml(datatypes[sub.tag], sub) - if extra_args: - raise UnknownArgument(', '.join(extra_args)) - return kw - - -def encode_result(value, datatype, **options): - return et.tostring(toxml( - datatype, options.get('nested_result_attrname', 'result'), value - )) - - -def encode_error(context, errordetail): - el = et.Element('error') - et.SubElement(el, 'faultcode').text = errordetail['faultcode'] - et.SubElement(el, 'faultstring').text = errordetail['faultstring'] - if 'debuginfo' in errordetail: - et.SubElement(el, 'debuginfo').text = errordetail['debuginfo'] - return et.tostring(el) - - -def encode_sample_value(datatype, value, format=False): - r = toxml(datatype, 'value', value) - if format: - xml_indent(r) - content = et.tostring(r) - return ('xml', content) - - -def encode_sample_params(params, format=False): - node = et.Element('parameters') - for name, datatype, value in params: - node.append(toxml(datatype, name, value)) - if format: - xml_indent(node) - content = et.tostring(node) - return ('xml', content) - - -def encode_sample_result(datatype, value, format=False): - r = toxml(datatype, 'result', value) - if format: - xml_indent(r) - content = et.tostring(r) - return ('xml', content) 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(""" - - - - - -%(content)s - - -""") - - -def default_prepare_response_body(request, results): - r = None - sep = None - for value in results: - if sep is None: - if isinstance(value, six.text_type): - sep = u('\n') - r = u('') - else: - sep = b('\n') - r = b('') - else: - r += sep - r += value - return r - - -class DummyTransaction: - def commit(self): - pass - - def abort(self): - pass - - -class WSRoot(object): - """ - Root controller for webservices. - - :param protocols: A list of protocols to enable (see :meth:`addprotocol`) - :param webpath: The web path where the webservice is published. - - :type transaction: A `transaction - `_-like - object or ``True``. - :param transaction: If specified, a transaction will be created and - handled on a per-call base. - - This option *can* be enabled along with `repoze.tm2 - `_ - (it will only make it void). - - If ``True``, the default :mod:`transaction` - module will be imported and used. - - """ - __registry__ = wsme.types.registry - - def __init__(self, protocols=[], webpath='', transaction=None, - scan_api=scan_api): - self._debug = True - self._webpath = webpath - self.protocols = [] - self._scan_api = scan_api - - self._transaction = transaction - if self._transaction is True: - import transaction - self._transaction = transaction - - for protocol in protocols: - self.addprotocol(protocol) - - self._api = None - - def wsgiapp(self): - """Returns a wsgi application""" - from webob.dec import wsgify - return wsgify(self._handle_request) - - def begin(self): - if self._transaction: - return self._transaction.begin() - else: - return DummyTransaction() - - def addprotocol(self, protocol, **options): - """ - Enable a new protocol on the controller. - - :param protocol: A registered protocol name or an instance - of a protocol. - """ - if isinstance(protocol, str): - protocol = getprotocol(protocol, **options) - self.protocols.append(protocol) - protocol.root = weakref.proxy(self) - - def getapi(self): - """ - Returns the api description. - - :rtype: list of (path, :class:`FunctionDefinition`) - """ - if self._api is None: - self._api = [ - (path, f, f._wsme_definition, args) - for path, f, args in self._scan_api(self) - ] - for path, f, fdef, args in self._api: - fdef.resolve_types(self.__registry__) - return [ - (path, fdef) - for path, f, fdef, args in self._api - ] - - def _get_protocol(self, name): - for protocol in self.protocols: - if protocol.name == name: - return protocol - - def _select_protocol(self, request): - log.debug("Selecting a protocol for the following request :\n" - "headers: %s\nbody: %s", request.headers.items(), - request.content_length and ( - request.content_length > 512 and - request.body[:512] or - request.body) or '') - protocol = None - error = ClientSideError(status_code=406) - path = str(request.path) - assert path.startswith(self._webpath) - path = path[len(self._webpath) + 1:] - if 'wsmeproto' in request.params: - return self._get_protocol(request.params['wsmeproto']) - else: - - for p in self.protocols: - try: - if p.accept(request): - protocol = p - break - except ClientSideError as e: - error = e - # If we could not select a protocol, we raise the last exception - # that we got, or the default one. - if not protocol: - raise error - return protocol - - def _do_call(self, protocol, context): - request = context.request - request.calls.append(context) - try: - if context.path is None: - context.path = protocol.extract_path(context) - - if context.path is None: - raise ClientSideError(u( - 'The %s protocol was unable to extract a function ' - 'path from the request') % protocol.name) - - context.func, context.funcdef, args = \ - self._lookup_function(context.path) - kw = protocol.read_arguments(context) - args = list(args) - - txn = self.begin() - try: - result = context.func(*args, **kw) - txn.commit() - except 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('
%s
') % - 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 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(""" - 5 - test -""") - - def test_encode_sample_params(self): - lang, content = wsme.rest.xml.encode_sample_params( - [('a', int, 2)], True) - assert lang == 'xml', lang - assert content == b('\n 2\n'), content - - def test_encode_sample_result(self): - lang, content = wsme.rest.xml.encode_sample_result(int, 2, True) - assert lang == 'xml', lang - assert content == b('2'), content - - def test_nil_fromxml(self): - for dt in ( - str, [int], {int: str}, bool, - datetime.date, datetime.time, datetime.datetime): - e = et.Element('value', nil='true') - assert fromxml(dt, e) is None - - def test_nil_toxml(self): - for dt in ( - wsme.types.bytes, - [int], {int: str}, bool, - datetime.date, datetime.time, datetime.datetime): - x = et.tostring(toxml(dt, 'value', None)) - assert x == b(''), x - - def test_unset_attrs(self): - class AType(object): - someattr = wsme.types.bytes - - wsme.types.register_type(AType) - - x = et.tostring(toxml(AType, 'value', AType())) - assert x == b(''), x 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() ` - and :class:`validate() `. - Unless you want to control when the class inspection is done there - is no need to call it. - """ - if class_ is None or \ - class_ in native_types or \ - isusertype(class_) or iscomplex(class_) or \ - isarray(class_) or isdict(class_): - return class_ - - if isinstance(class_, list): - if len(class_) != 1: - raise ValueError("Cannot register type %s" % repr(class_)) - dt = ArrayType(class_[0]) - self.register(dt.item_type) - self.array_types.add(dt) - return dt - - if isinstance(class_, dict): - if len(class_) != 1: - raise ValueError("Cannot register type %s" % repr(class_)) - dt = DictType(*list(class_.items())[0]) - self.register(dt.value_type) - self.dict_types.add(dt) - return dt - - class_._wsme_attributes = None - class_._wsme_attributes = inspect_class(class_) - class_._wsme_DataHolderClass = make_dataholder(class_) - - class_.__registry__ = self - self._complex_types.append(weakref.ref(class_)) - return class_ - - def reregister(self, class_): - """Register a type which may already have been registered. - """ - self._unregister(class_) - return self.register(class_) - - def _unregister(self, class_): - """Remove a previously registered type. - """ - # Clear the existing attribute reference so it is rebuilt if - # the class is registered again later. - if hasattr(class_, '_wsme_attributes'): - del class_._wsme_attributes - # FIXME(dhellmann): This method does not recurse through the - # types like register() does. Should it? - if isinstance(class_, list): - at = ArrayType(class_[0]) - try: - self.array_types.remove(at) - except KeyError: - pass - elif isinstance(class_, dict): - key_type, value_type = list(class_.items())[0] - self.dict_types = set( - dt for dt in self.dict_types - if (dt.key_type, dt.value_type) != (key_type, value_type) - ) - # We can't use remove() here because the items in - # _complex_types are weakref objects pointing to the classes, - # so we can't compare with them directly. - self._complex_types = [ - ct for ct in self._complex_types - if ct() is not class_ - ] - - def lookup(self, typename): - log.debug('Lookup %s' % typename) - modname = None - if '.' in typename: - modname, typename = typename.rsplit('.', 1) - for ct in self._complex_types: - ct = ct() - if ct is not None and typename == ct.__name__ and ( - modname is None or modname == ct.__module__): - return ct - - def resolve_type(self, type_): - if isinstance(type_, six.string_types): - return self.lookup(type_) - if isinstance(type_, list): - type_ = ArrayType(type_[0]) - if isinstance(type_, dict): - type_ = DictType(list(type_.keys())[0], list(type_.values())[0]) - if isinstance(type_, ArrayType): - type_ = ArrayType(self.resolve_type(type_.item_type)) - self.array_types.add(type_) - elif isinstance(type_, DictType): - type_ = DictType( - type_.key_type, - self.resolve_type(type_.value_type) - ) - self.dict_types.add(type_) - else: - type_ = self.register(type_) - return type_ - - -# Default type registry -registry = Registry() - - -def register_type(class_): - return registry.register(class_) - - -class BaseMeta(type): - def __new__(cls, name, bases, dct): - if bases and bases[0] is not object and '__registry__' not in dct: - dct['__registry__'] = registry - return type.__new__(cls, name, bases, dct) - - def __init__(cls, name, bases, dct): - if bases and bases[0] is not object and cls.__registry__: - cls.__registry__.register(cls) - - -class Base(six.with_metaclass(BaseMeta)): - """Base type for complex types""" - def __init__(self, **kw): - for key, value in kw.items(): - if hasattr(self, key): - setattr(self, key, value) - - -class File(Base): - """A complex type that represents a file. - - In the particular case of protocol accepting form encoded data as - input, File can be loaded from a form file field. - """ - #: The file name - filename = wsattr(text) - - #: Mime type of the content - contenttype = wsattr(text) - - def _get_content(self): - if self._content is None and self._file: - self._content = self._file.read() - return self._content - - def _set_content(self, value): - self._content = value - self._file = None - - #: File content - content = wsproperty(binary, _get_content, _set_content) - - def __init__(self, filename=None, file=None, content=None, - contenttype=None, fieldstorage=None): - self.filename = filename - self.contenttype = contenttype - self._file = file - self._content = content - - if fieldstorage is not None: - if fieldstorage.file: - self._file = fieldstorage.file - self.filename = fieldstorage.filename - self.contenttype = text(fieldstorage.type) - else: - self._content = fieldstorage.value - - @property - def file(self): - if self._file is None and self._content: - self._file = six.BytesIO(self._content) - return self._file - - -class DynamicBase(Base): - """Base type for complex types for which all attributes are not - defined when the class is constructed. - - This class is meant to be used as a base for types that have - properties added after the main class is created, such as by - loading plugins. - - """ - - @classmethod - def add_attributes(cls, **attrs): - """Add more attributes - - The arguments should be valid Python attribute names - associated with a type for the new attribute. - - """ - for n, t in attrs.items(): - setattr(cls, n, t) - cls.__registry__.reregister(cls) 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-?\d{4,})-(?P\d{2})-(?P\d{2})' -time_re = r'(?P\d{2}):(?P\d{2}):(?P\d{2})' + \ - r'(\.(?P\d+))?' -tz_re = r'((?P[+-])(?P\d{2}):(?P\d{2}))' + \ - r'|(?PZ)' - -datetime_re = re.compile( - '%sT%s(%s)?' % (date_re, time_re, tz_re)) -date_re = re.compile(date_re) -time_re = re.compile('%s(%s)?' % (time_re, tz_re)) - - -if hasattr(builtins, '_'): - _ = builtins._ -else: - def _(s): - return s - - -def parse_isodate(value): - m = date_re.match(value) - if m is None: - raise ValueError("'%s' is not a legal date value" % (value)) - try: - return datetime.date( - int(m.group('year')), - int(m.group('month')), - int(m.group('day'))) - except ValueError: - raise ValueError("'%s' is a out-of-range date" % (value)) - - -def parse_isotime(value): - m = time_re.match(value) - if m is None: - raise ValueError("'%s' is not a legal time value" % (value)) - try: - ms = 0 - if m.group('sec_frac') is not None: - f = decimal.Decimal('0.' + m.group('sec_frac')) - f = str(f.quantize(decimal.Decimal('0.000001'))) - ms = int(f[2:]) - tz = _parse_tzparts(m.groupdict()) - return datetime.time( - int(m.group('hour')), - int(m.group('min')), - int(m.group('sec')), - ms, - tz) - except ValueError: - raise ValueError("'%s' is a out-of-range time" % (value)) - - -def parse_isodatetime(value): - if dateutil: - return dateutil.parser.parse(value) - m = datetime_re.match(value) - if m is None: - raise ValueError("'%s' is not a legal datetime value" % (value)) - try: - ms = 0 - if m.group('sec_frac') is not None: - f = decimal.Decimal('0.' + m.group('sec_frac')) - f = f.quantize(decimal.Decimal('0.000001')) - ms = int(str(f)[2:]) - tz = _parse_tzparts(m.groupdict()) - return datetime.datetime( - int(m.group('year')), - int(m.group('month')), - int(m.group('day')), - int(m.group('hour')), - int(m.group('min')), - int(m.group('sec')), - ms, - tz) - except ValueError: - raise ValueError("'%s' is a out-of-range datetime" % (value)) - - -def _parse_tzparts(parts): - if 'tz_z' in parts and parts['tz_z'] == 'Z': - return pytz.UTC - if 'tz_min' not in parts or not parts['tz_min']: - return None - - tz_minute_offset = (int(parts['tz_hour']) * 60 + int(parts['tz_min'])) - tz_multiplier = -1 if parts['tz_sign'] == '-' else 1 - - return pytz.FixedOffset(tz_multiplier * tz_minute_offset) - - -def is_valid_code(code_value): - """ - This function checks if incoming value in http response codes range. - """ - return code_value in http_client.responses - - -def is_client_error(code): - """ Checks client error code (RFC 2616).""" - return 400 <= code < 500 - - -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict # noqa 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\w+)(\s+(?P\w+))?:') - - -def datatypename(datatype): - if isinstance(datatype, wsme.types.UserType): - return datatype.name - if isinstance(datatype, wsme.types.DictType): - return 'dict(%s: %s)' % (datatypename(datatype.key_type), - datatypename(datatype.value_type)) - if isinstance(datatype, wsme.types.ArrayType): - return 'list(%s)' % datatypename(datatype.item_type) - return datatype.__name__ - - -def make_sample_object(datatype): - if datatype is wsme.types.bytes: - return six.b('samplestring') - if datatype is wsme.types.text: - return u'sample unicode' - if datatype is int: - return 5 - sample_obj = getattr(datatype, 'sample', datatype)() - return sample_obj - - -def get_protocols(names): - names = list(names) - protocols = [] - if 'rest' in names: - names.remove('rest') - protocols.extend('restjson', 'restxml') - if 'restjson' in names: - names.remove('restjson') - protocols.append(('Json', wsme.rest.json)) - if 'restxml' in names: - names.remove('restxml') - protocols.append(('XML', wsme.rest.xml)) - for name in names: - p = wsme.protocol.getprotocol(name) - protocols.append((p.displayname or p.name, p)) - return protocols - - -class SampleType(object): - """A Sample Type""" - - #: A Int - aint = int - - def __init__(self, aint=None): - if aint: - self.aint = aint - - @classmethod - def sample(cls): - return cls(10) - - -class SampleService(wsme.WSRoot): - @wsme.expose(SampleType) - @wsme.validate(SampleType, int, str) - def change_aint(data, aint, dummy='useless'): - """ - :param aint: The new value - - :return: The data object with its aint field value changed. - """ - data.aint = aint - return data - - -def getroot(env, force=False): - root = env.temp_data.get('wsme:root') - if not force and root: - return root - rootpath = env.temp_data.get('wsme:rootpath', env.app.config.wsme_root) - - if rootpath is None: - return None - - modname, classname = rootpath.rsplit('.', 1) - __import__(modname) - module = sys.modules[modname] - root = getattr(module, classname) - env.temp_data['wsme:root'] = root - return root - - -def scan_services(service, path=[]): - has_functions = False - for name in dir(service): - if name.startswith('_'): - continue - a = getattr(service, name) - if inspect.ismethod(a): - if hasattr(a, '_wsme_definition'): - has_functions = True - if inspect.isclass(a): - continue - if len(path) > wsme.rest.APIPATH_MAXLEN: - raise ValueError("Path is too long: " + str(path)) - for value in scan_services(a, path + [name]): - yield value - if has_functions: - yield service, path - - -def find_service_path(env, service): - root = getroot(env) - if service == root: - return [] - for s, path in scan_services(root): - if s == service: - return path - return None - - -class TypeDirective(PyClasslike): - def get_index_text(self, modname, name_cls): - return _('%s (webservice type)') % name_cls[0] - - def add_target_and_index(self, name_cls, sig, signode): - ret = super(TypeDirective, self).add_target_and_index( - name_cls, sig, signode - ) - name = name_cls[0] - types = self.env.domaindata['wsme']['types'] - if name in types: - self.state_machine.reporter.warning( - 'duplicate type description of %s ' % name) - types[name] = self.env.docname - return ret - - -class AttributeDirective(PyClassmember): - doc_field_types = [ - Field('datatype', label=l_('Type'), has_arg=False, - names=('type', 'datatype')) - ] - - -def check_samples_slot(value): - """Validate the samples_slot option to the TypeDocumenter. - - Valid positions are 'before-docstring' and - 'after-docstring'. Using the explicit 'none' disables sample - output. The default is after-docstring. - """ - if not value: - return 'after-docstring' - val = directives.choice( - value, - ('none', # do not include - 'before-docstring', # show samples then docstring - 'after-docstring', # show docstring then samples - )) - return val - - -class TypeDocumenter(autodoc.ClassDocumenter): - objtype = 'type' - directivetype = 'type' - domain = 'wsme' - - required_arguments = 1 - default_samples_slot = 'after-docstring' - - option_spec = dict( - autodoc.ClassDocumenter.option_spec, - **{'protocols': lambda l: [v.strip() for v in l.split(',')], - 'samples-slot': check_samples_slot, - }) - - @staticmethod - def can_document_member(member, membername, isattr, parent): - # we don't want to be automaticaly used - # TODO check if the member is registered an an exposed type - return False - - def format_name(self): - return self.object.__name__ - - def format_signature(self): - return u'' - - def add_directive_header(self, sig): - super(TypeDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - 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'') - - if samples_slot == 'after-docstring': - add_docstring() - add_samples() - elif samples_slot == 'before-docstring': - add_samples() - add_docstring() - else: - add_docstring() - - -class AttributeDocumenter(autodoc.AttributeDocumenter): - datatype = None - domain = 'wsme' - - @staticmethod - def can_document_member(member, membername, isattr, parent): - return isinstance(parent, TypeDocumenter) - - def import_object(self): - success = super(AttributeDocumenter, self).import_object() - if success: - self.datatype = self.object.datatype - return success - - def add_content(self, more_content, no_docstring=False): - self.add_line( - u':type: %s' % datatypename(self.datatype), - '' - ) - self.add_line(u'', '') - super(AttributeDocumenter, self).add_content( - more_content, no_docstring) - - def add_directive_header(self, sig): - super(AttributeDocumenter, self).add_directive_header(sig) - - -class RootDirective(Directive): - """ - This directive is to tell what class is the Webservice root - """ - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = { - 'webpath': directives.unchanged - } - - def run(self): - env = self.state.document.settings.env - rootpath = self.arguments[0].strip() - env.temp_data['wsme:rootpath'] = rootpath - if 'wsme:root' in env.temp_data: - del env.temp_data['wsme:root'] - if 'webpath' in self.options: - env.temp_data['wsme:webpath'] = self.options['webpath'] - return [] - - -class ServiceDirective(ObjectDescription): - name = 'service' - - optional_arguments = 1 - - def handle_signature(self, sig, signode): - path = sig.split('/') - - namespace = '/'.join(path[:-1]) - if namespace and not namespace.endswith('/'): - namespace += '/' - - servicename = path[-1] - - if not namespace and not servicename: - servicename = '/' - - signode += addnodes.desc_annotation('service ', 'service ') - - if namespace: - signode += addnodes.desc_addname(namespace, namespace) - - signode += addnodes.desc_name(servicename, servicename) - - return sig - - -class ServiceDocumenter(autodoc.ClassDocumenter): - domain = 'wsme' - objtype = 'service' - directivetype = 'service' - - def add_directive_header(self, sig): - super(ServiceDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - 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 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 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 = """ - - - - <%(method)s> - %(params)s - - - - -""" % dict(method=method, params=params, typenamespace=typenamespace) - return message - - -python_types = { - int: ('xs:int', str), - float: ('xs:float', str), - bool: ('xs:boolean', str), - wsme.types.bytes: ( - 'xs:string', - lambda x: x.decode('ascii') if isinstance(x, wsme.types.bytes) else x - ), - wsme.types.text: ('xs:string', wsme.types.text), - wsme.types.binary: ( - 'xs:base64Binary', - lambda x: base64.encodestring(x).decode('ascii') - ), - decimal.Decimal: ('xs:decimal', str), - datetime.date: ('xs:date', datetime.date.isoformat), - datetime.time: ('xs:time', datetime.time.isoformat), - datetime.datetime: ('xs:dateTime', datetime.datetime.isoformat), -} - -array_types = { - wsme.types.bytes: "String_Array", - wsme.types.text: "String_Array", - int: "Int_Array", - float: "Float_Array", - bool: "Boolean_Array", - datetime.datetime: "dateTime_Array" -} - -if not six.PY3: - array_types[long] = "Long_Array" # 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 -- cgit v1.2.1