Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Ib3976cdfab839242694ce603451ef38407650721
This commit is contained in:
		
							
								
								
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| [gerrit] |  | ||||||
| host=review.openstack.org |  | ||||||
| port=29418 |  | ||||||
| project=openstack/wsme.git |  | ||||||
							
								
								
									
										14
									
								
								.hgtags
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								.hgtags
									
									
									
									
									
								
							| @@ -1,14 +0,0 @@ | |||||||
| 2bd203a084dcc785257b35e7231b2021722f60de 0.1.0a1 |  | ||||||
| 0eae00db9384d52cc4a82c09ab207d631ecb82e4 0.1.0a2 |  | ||||||
| 86466da44f44a97b379c7b8e94c371526be0eb9f 0.1.0a3 |  | ||||||
| b38c56a2b9130d8fade7be22c8ac66a45fa77a6e 0.1.0a4 |  | ||||||
| b0019e486c807bafe412ebaa6eb9bd9ab656c81c 0.1.0 |  | ||||||
| c17de432c1857cfa059816d0db332bcdabea0c82 0.1.1 |  | ||||||
| cfb5efc624f55710c987c7795501f3dd44a01078 0.2.0 |  | ||||||
| ebe2c6f228ad4a365fbda9418f3e113d542390f0 0.3b1 |  | ||||||
| d5eab01bf49192df2e0f24c78ca4936073e45b19 0.3b2 |  | ||||||
| 603c8586b076f5cf9b70b6cd82578dba7226e0c7 0.3 |  | ||||||
| 5ad01afed8779bb5a384802a2ec7d6ed0186c7d5 0.4b1 |  | ||||||
| f06e004ca8e4013bf94df0cdade23b01742b0ec0 0.4 |  | ||||||
| 359199eb4e0999b5920eadfa40038013cd360df6 0.5b1 |  | ||||||
| d3e5eee0b150048762169ff20ee25b43aa0369fa 0.5b2 |  | ||||||
							
								
								
									
										19
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,19 +0,0 @@ | |||||||
| The MIT License (MIT) |  | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
|  |  | ||||||
| The above copyright notice and this permission notice shall be included in all |  | ||||||
| copies or substantial portions of the Software. |  | ||||||
|  |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
| SOFTWARE. |  | ||||||
							
								
								
									
										14
									
								
								README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | This project is no longer maintained. | ||||||
|  |  | ||||||
|  | The contents of this repository are still available in the Git | ||||||
|  | source code management system. To see the contents of this | ||||||
|  | repository before it reached its end of life, please check out the | ||||||
|  | previous commit with "git checkout HEAD^1". | ||||||
|  |  | ||||||
|  | For ongoing work on maintaining OpenStack packages in the Debian | ||||||
|  | distribution, please see the Debian OpenStack packaging team at | ||||||
|  | https://wiki.debian.org/OpenStack/. | ||||||
|  |  | ||||||
|  | For any further questions, please email | ||||||
|  | openstack-dev@lists.openstack.org or join #openstack-dev on | ||||||
|  | Freenode. | ||||||
							
								
								
									
										112
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								README.rst
									
									
									
									
									
								
							| @@ -1,112 +0,0 @@ | |||||||
| Web Services Made Easy |  | ||||||
| ====================== |  | ||||||
|  |  | ||||||
| Introduction |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| Web Services Made Easy (WSME) simplifies the writing of REST web services |  | ||||||
| by providing simple yet powerful typing, removing the need to directly |  | ||||||
| manipulate the request and the response objects. |  | ||||||
|  |  | ||||||
| WSME can work standalone or on top of your favorite Python web |  | ||||||
| (micro)framework, so you can use both your preferred way of routing your REST |  | ||||||
| requests and most of the features of WSME that rely on the typing system like: |  | ||||||
|  |  | ||||||
| -   Alternate protocols, including those supporting batch-calls |  | ||||||
| -   Easy documentation through a Sphinx_ extension |  | ||||||
|  |  | ||||||
| WSME is originally a rewrite of TGWebServices |  | ||||||
| with a focus on extensibility, framework-independance and better type handling. |  | ||||||
|  |  | ||||||
| How Easy ? |  | ||||||
| ~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Here is a standalone wsgi example:: |  | ||||||
|      |  | ||||||
|     from wsme import WSRoot, expose |  | ||||||
|  |  | ||||||
|     class MyService(WSRoot): |  | ||||||
|         @expose(unicode, unicode)  # First parameter is the return type, |  | ||||||
|                                    # then the function argument types |  | ||||||
|         def hello(self, who=u'World'): |  | ||||||
|             return u"Hello {0} !".format(who) |  | ||||||
|  |  | ||||||
|     ws = MyService(protocols=['restjson', 'restxml', 'soap']) |  | ||||||
|     application = ws.wsgiapp() |  | ||||||
|  |  | ||||||
| With this published at the ``/ws`` path of your application, you can access |  | ||||||
| your hello function in various protocols: |  | ||||||
|  |  | ||||||
| .. list-table:: |  | ||||||
|     :header-rows: 1 |  | ||||||
|  |  | ||||||
|     * - URL |  | ||||||
|       - Returns |  | ||||||
|      |  | ||||||
|     * - ``http://<server>/ws/hello.json?who=you`` |  | ||||||
|       - ``"Hello you !"`` |  | ||||||
|  |  | ||||||
|     * - ``http://<server>/ws/hello.xml`` |  | ||||||
|       - ``<result>Hello World !</result>`` |  | ||||||
|  |  | ||||||
|     * - ``http://<server>/ws/api.wsdl`` |  | ||||||
|       - A WSDL description for any SOAP client. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Main features |  | ||||||
| ~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| -   Very simple API. |  | ||||||
| -   Supports user-defined simple and complex types. |  | ||||||
| -   Multi-protocol : REST+Json, REST+XML, SOAP, ExtDirect and more to come. |  | ||||||
| -   Extensible : easy to add more protocols or more base types. |  | ||||||
| -   Framework independence : adapters are provided to easily integrate |  | ||||||
|     your API in any web framework, for example a wsgi container, |  | ||||||
|     Pecan_, TurboGears_, Flask_, cornice_... |  | ||||||
| -   Very few runtime dependencies: webob, simplegeneric. Optionnaly lxml and |  | ||||||
|     simplejson if you need better performances. |  | ||||||
| -   Integration in `Sphinx`_ for making clean documentation with |  | ||||||
|     ``wsmeext.sphinxext``. |  | ||||||
|  |  | ||||||
| .. _Pecan: http://pecanpy.org/ |  | ||||||
| .. _TurboGears: http://www.turbogears.org/ |  | ||||||
| .. _Flask: http://flask.pocoo.org/ |  | ||||||
| .. _cornice: http://pypi.python.org/pypi/cornice |  | ||||||
|  |  | ||||||
| Install |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| :: |  | ||||||
|  |  | ||||||
|     pip install WSME |  | ||||||
|  |  | ||||||
| or, if you do not have pip on your system or virtualenv |  | ||||||
|  |  | ||||||
| :: |  | ||||||
|  |  | ||||||
|     easy_install WSME |  | ||||||
|  |  | ||||||
| Changes |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| -   Read the `Changelog`_ |  | ||||||
|  |  | ||||||
| Getting Help |  | ||||||
| ~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| -   Read the `WSME Documentation`_. |  | ||||||
| -   Questions about WSME should go to the `python-wsme mailinglist`_. |  | ||||||
|  |  | ||||||
| Contribute |  | ||||||
| ~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| * Documentation: http://packages.python.org/WSME/ |  | ||||||
| * Source: http://git.openstack.org/cgit/openstack/wsme |  | ||||||
| * Bugs: https://bugs.launchpad.net/wsme/+bugs |  | ||||||
| * Code review: https://review.openstack.org/#/q/project:openstack/wsme,n,z |  | ||||||
|  |  | ||||||
| .. _Changelog: http://packages.python.org/WSME/changes.html |  | ||||||
| .. _python-wsme mailinglist: http://groups.google.com/group/python-wsme |  | ||||||
| .. _WSME Documentation: http://packages.python.org/WSME/ |  | ||||||
| .. _WSME issue tracker: https://bugs.launchpad.net/wsme/+bugs |  | ||||||
| .. _Sphinx: http://sphinx.pocoo.org/ |  | ||||||
							
								
								
									
										134
									
								
								doc/Makefile
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								doc/Makefile
									
									
									
									
									
								
							| @@ -1,134 +0,0 @@ | |||||||
| # Makefile for Sphinx documentation |  | ||||||
| # |  | ||||||
|  |  | ||||||
| # You can set these variables from the command line. |  | ||||||
| SPHINXOPTS    = |  | ||||||
| SPHINXBUILD   = sphinx-build |  | ||||||
| PAPER         = |  | ||||||
| BUILDDIR      = _build |  | ||||||
|  |  | ||||||
| # Internal variables. |  | ||||||
| PAPEROPT_a4     = -D latex_paper_size=a4 |  | ||||||
| PAPEROPT_letter = -D latex_paper_size=letter |  | ||||||
| ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . |  | ||||||
|  |  | ||||||
| .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest |  | ||||||
|  |  | ||||||
| help: |  | ||||||
| 	@echo "Please use \`make <target>' where <target> is one of" |  | ||||||
| 	@echo "  html       to make standalone HTML files" |  | ||||||
| 	@echo "  dirhtml    to make HTML files named index.html in directories" |  | ||||||
| 	@echo "  singlehtml to make a single large HTML file" |  | ||||||
| 	@echo "  pickle     to make pickle files" |  | ||||||
| 	@echo "  json       to make JSON files" |  | ||||||
| 	@echo "  htmlhelp   to make HTML files and a HTML help project" |  | ||||||
| 	@echo "  qthelp     to make HTML files and a qthelp project" |  | ||||||
| 	@echo "  devhelp    to make HTML files and a Devhelp project" |  | ||||||
| 	@echo "  epub       to make an epub" |  | ||||||
| 	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter" |  | ||||||
| 	@echo "  latexpdf   to make LaTeX files and run them through pdflatex" |  | ||||||
| 	@echo "  text       to make text files" |  | ||||||
| 	@echo "  man        to make manual pages" |  | ||||||
| 	@echo "  changes    to make an overview of all changed/added/deprecated items" |  | ||||||
| 	@echo "  linkcheck  to check all external links for integrity" |  | ||||||
| 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)" |  | ||||||
|  |  | ||||||
| clean: |  | ||||||
| 	-rm -rf $(BUILDDIR)/* |  | ||||||
|  |  | ||||||
| html: |  | ||||||
| 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." |  | ||||||
|  |  | ||||||
| ziphtml: html |  | ||||||
| 	rm -f $(BUILDDIR)/wsme-documentation.zip |  | ||||||
| 	cd $(BUILDDIR)/html && zip -r ../wsme-documentation.zip . |  | ||||||
|  |  | ||||||
| dirhtml: |  | ||||||
| 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." |  | ||||||
|  |  | ||||||
| singlehtml: |  | ||||||
| 	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." |  | ||||||
|  |  | ||||||
| pickle: |  | ||||||
| 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished; now you can process the pickle files." |  | ||||||
|  |  | ||||||
| json: |  | ||||||
| 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished; now you can process the JSON files." |  | ||||||
|  |  | ||||||
| htmlhelp: |  | ||||||
| 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished; now you can run HTML Help Workshop with the" \ |  | ||||||
| 	      ".hhp project file in $(BUILDDIR)/htmlhelp." |  | ||||||
|  |  | ||||||
| qthelp: |  | ||||||
| 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \ |  | ||||||
| 	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:" |  | ||||||
| 	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhcp" |  | ||||||
| 	@echo "To view the help file:" |  | ||||||
| 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WebServicesMadeEasy.qhc" |  | ||||||
|  |  | ||||||
| devhelp: |  | ||||||
| 	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished." |  | ||||||
| 	@echo "To view the help file:" |  | ||||||
| 	@echo "# mkdir -p $$HOME/.local/share/devhelp/WebServicesMadeEasy" |  | ||||||
| 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WebServicesMadeEasy" |  | ||||||
| 	@echo "# devhelp" |  | ||||||
|  |  | ||||||
| epub: |  | ||||||
| 	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The epub file is in $(BUILDDIR)/epub." |  | ||||||
|  |  | ||||||
| latex: |  | ||||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." |  | ||||||
| 	@echo "Run \`make' in that directory to run these through (pdf)latex" \ |  | ||||||
| 	      "(use \`make latexpdf' here to do that automatically)." |  | ||||||
|  |  | ||||||
| latexpdf: |  | ||||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |  | ||||||
| 	@echo "Running LaTeX files through pdflatex..." |  | ||||||
| 	make -C $(BUILDDIR)/latex all-pdf |  | ||||||
| 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." |  | ||||||
|  |  | ||||||
| text: |  | ||||||
| 	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The text files are in $(BUILDDIR)/text." |  | ||||||
|  |  | ||||||
| man: |  | ||||||
| 	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The manual pages are in $(BUILDDIR)/man." |  | ||||||
|  |  | ||||||
| changes: |  | ||||||
| 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes |  | ||||||
| 	@echo |  | ||||||
| 	@echo "The overview file is in $(BUILDDIR)/changes." |  | ||||||
|  |  | ||||||
| linkcheck: |  | ||||||
| 	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Link check complete; look for any errors in the above output " \ |  | ||||||
| 	      "or in $(BUILDDIR)/linkcheck/output.txt." |  | ||||||
|  |  | ||||||
| doctest: |  | ||||||
| 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest |  | ||||||
| 	@echo "Testing of doctests in the sources finished, look at the " \ |  | ||||||
| 	      "results in $(BUILDDIR)/doctest/output.txt." |  | ||||||
							
								
								
									
										10
									
								
								doc/_static/toggle.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								doc/_static/toggle.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +0,0 @@ | |||||||
| dl.toggle dt { |  | ||||||
|     background-color: #eeffcc; |  | ||||||
|     border: 1px solid #ac9; |  | ||||||
|     display: inline; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| dl.toggle dd { |  | ||||||
|     display: none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								doc/_static/toggle.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								doc/_static/toggle.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,9 +0,0 @@ | |||||||
| /*global $,document*/ |  | ||||||
| $(document).ready(function () { |  | ||||||
|     "use strict"; |  | ||||||
|     $("dl.toggle > dt").click( |  | ||||||
|         function (event) { |  | ||||||
|             $(this).next().toggle(250); |  | ||||||
|         } |  | ||||||
|     ); |  | ||||||
| }); |  | ||||||
							
								
								
									
										27
									
								
								doc/_static/wsme.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								doc/_static/wsme.css
									
									
									
									
										vendored
									
									
								
							| @@ -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; |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								doc/api.rst
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								doc/api.rst
									
									
									
									
									
								
							| @@ -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: |  | ||||||
|  |  | ||||||
							
								
								
									
										433
									
								
								doc/changes.rst
									
									
									
									
									
								
							
							
						
						
									
										433
									
								
								doc/changes.rst
									
									
									
									
									
								
							| @@ -1,433 +0,0 @@ | |||||||
| Changes |  | ||||||
| ======= |  | ||||||
|  |  | ||||||
| 0.8.0 (2015-08-25) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| Changes that may break your app: |  | ||||||
|  |  | ||||||
| * Returns 400 if unexpected attributes are added to complex types (#1277571). |  | ||||||
|  |  | ||||||
| Other changes: |  | ||||||
|  |  | ||||||
| * Returns 415 when Content-Type is invalid (#1419110) |  | ||||||
| * Returns 400 if a complex input type is not a json object (#1423634) |  | ||||||
| * Fix error reports with ArrayType and DictType invalid inputs (#1428185, #1428628) |  | ||||||
| * Update README |  | ||||||
|  |  | ||||||
| 0.7.0 (2015-05-13) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| * Ensure UserType objects are converted to basetype |  | ||||||
| * Convert built-in types when passed as strings |  | ||||||
| * Multiple protocol accept or content-type matching |  | ||||||
| * Raise an InvalidInput if you get a ValueError from JSON data |  | ||||||
| * Remove unsupported python versions from setup.cfg |  | ||||||
| * Clean up setup.py and add requirements.txt |  | ||||||
| * Add full MIT license |  | ||||||
| * Fix i18n when formatting exception |  | ||||||
| * Cleanup up logging |  | ||||||
| * Make it possible to use the Response to return a non-default return type |  | ||||||
| * several fixes for SOAP protocol |  | ||||||
|  |  | ||||||
| 0.6.4 (2014-11-20) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| - Include tests in the source distribution |  | ||||||
|  |  | ||||||
| 0.6.3 (2014-11-19) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| - Disable universal wheels |  | ||||||
|  |  | ||||||
| 0.6.2 (2014-11-18) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| * Flask adapter complex types now supports flask.ext.restful |  | ||||||
| * Allow disabling complex types auto-register |  | ||||||
| * Documentation edits |  | ||||||
| * Various documentation build fixes |  | ||||||
| * Fix passing Dict and Array based UserType as params |  | ||||||
|  |  | ||||||
| 0.6.1 (2014-05-02) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| * Fix error: variable 'kw' referenced before assignment |  | ||||||
| * Fix default handling for zero values |  | ||||||
| * Fixing spelling mistakes |  | ||||||
| * A proper check of UuidType |  | ||||||
| * pecan: cleanup, use global vars and staticmethod |  | ||||||
| * args_from_args() to work with an instance of UserType |  | ||||||
|  |  | ||||||
| 0.6 (2014-02-06) |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| * Add 'readonly' parameter to wsattr |  | ||||||
| * Fix typos in documents and comments |  | ||||||
| * Support dynamic types |  | ||||||
| * Support building wheels (PEP-427) |  | ||||||
| * Fix a typo in the types documentation |  | ||||||
| * Add IntegerType and some classes for validation |  | ||||||
| * Use assertRaises() for negative tests |  | ||||||
| * Remove the duplicated error message from Enum |  | ||||||
| * Drop description from 403 flask test case |  | ||||||
| * Fix SyntaxWarning under Python 3 |  | ||||||
|  |  | ||||||
| 0.5b6 (2013-10-16) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *  Add improved support for HTTP response codes in cornice apps. |  | ||||||
|  |  | ||||||
| *  Handle mandatory attributes |  | ||||||
|  |  | ||||||
| *  Fix error code returned when None is used in an Enum |  | ||||||
|  |  | ||||||
| *  Handle list and dict for body type in REST protocol |  | ||||||
|  |  | ||||||
| *  Fix Sphinx for Python 3 |  | ||||||
|  |  | ||||||
| *  Add custom error code to ClientSideError |  | ||||||
|  |  | ||||||
| *  Return a ClientSideError if unable to convert data |  | ||||||
|  |  | ||||||
| *  Validate body when using Pecan |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 0.5b5 (2013-09-16) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| More packaging fixes. |  | ||||||
|  |  | ||||||
| 0.5b4 (2013-09-11) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| Fixes some release-related files for the stackforge release process. |  | ||||||
| No user-facing bug fixes or features over what 0.5b3 provides. |  | ||||||
|  |  | ||||||
| 0.5b3 (2013-09-04) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| The project moved to stackforge. Mind the new URLs for the repository, bug |  | ||||||
| report etc (see the documentation). |  | ||||||
|  |  | ||||||
| *   Allow non-default status code return with the pecan adapter |  | ||||||
|     (Angus Salked). |  | ||||||
|  |  | ||||||
| *   Fix returning objects with object attributes set to None on rest-json |  | ||||||
|     & ExtDirect. |  | ||||||
|  |  | ||||||
| *   Allow error details to be set on the Response object (experimental !). |  | ||||||
|  |  | ||||||
| *   Fix: Content-Type header is not set anymore when the return type is None |  | ||||||
|     on the pecan adapter. |  | ||||||
|  |  | ||||||
| *   Support unicode message in ClientSideError (Mehdi Abaakouk). |  | ||||||
|  |  | ||||||
| *   Use pbr instead of d2to1 (Julien Danjou). |  | ||||||
|  |  | ||||||
| *   Python 3.3 support (Julien Danjou). |  | ||||||
|  |  | ||||||
| *   Pecan adapter: returned status can now be set on exceptions (Vitaly |  | ||||||
|     Kostenko). |  | ||||||
|  |  | ||||||
| *   TG adapters: returned status can be set on exceptions (Ryan |  | ||||||
|     Petrello). |  | ||||||
|  |  | ||||||
| *   six >= 1.4.0 support (Julien Danjou). |  | ||||||
|  |  | ||||||
| *   Require ordereddict from pypi for python < 2.6 (Ryan Petrello). |  | ||||||
|  |  | ||||||
| *   Make the code PEP8 compliant (Ryan Petrello). |  | ||||||
|  |  | ||||||
| 0.5b2 (2013-04-18) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Changed the way datas of complex types are stored. In previous versions, an |  | ||||||
|     attribute was added to the type for each attribute, its name being the |  | ||||||
|     attribute name prefixed with '_'. |  | ||||||
|  |  | ||||||
|     Starting with this version, a single attribute _wsme_dataholder is added to |  | ||||||
|     the instance. |  | ||||||
|  |  | ||||||
|     The motivation behind this change is to avoid adding too many attributes to |  | ||||||
|     the object. |  | ||||||
|  |  | ||||||
| *   Add a special type 'HostRequest' that allow a function to ask for the host |  | ||||||
|     framework request object in its arguments. |  | ||||||
|  |  | ||||||
| *   Pecan adapter: Debug mode (which returns the exception tracebacks to the |  | ||||||
|     client) can be enabled by the pecan application configuration. |  | ||||||
|  |  | ||||||
| *   New adapter: wsmeext.flask, for the Flask_ framework. |  | ||||||
|  |  | ||||||
| .. _Flask: http://flask.pocoo.org/ |  | ||||||
|  |  | ||||||
| *   Fix: the cornice adapter was not usable. |  | ||||||
|  |  | ||||||
| *   Fix: Submodules of wsmeext were missing in the packages. |  | ||||||
|  |  | ||||||
| *   Fix: The demo app was still depending on the WSME-Soap package (which has |  | ||||||
|     been merged into WSME in 0.5b1). |  | ||||||
|  |  | ||||||
| *   Fix: A function with only on 'body' parameter would fail when being called. |  | ||||||
|  |  | ||||||
| *   Fix: Missing arguments were poorly reported by the frameworks adapters. |  | ||||||
|  |  | ||||||
| 0.5b1 (2013-01-30) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Introduce a new kind of adapters that rely on the framework routing. |  | ||||||
|     Adapters are provided for Pecan, TurboGears and cornice. |  | ||||||
|  |  | ||||||
| *   Reorganised the rest protocol implementation to ease the implementation of |  | ||||||
|     adapters that rely only on the host framework routing system. |  | ||||||
|  |  | ||||||
| *   The default rest ``@expose`` decorator does not wrap the decorated function |  | ||||||
|     anymore. If needed to expose a same function several times, a parameter |  | ||||||
|     ``multiple_expose=True`` has been introduced. |  | ||||||
|  |  | ||||||
| *   Remove the wsme.release module |  | ||||||
|  |  | ||||||
| *   Fix == operator on ArrayType |  | ||||||
|  |  | ||||||
| *   Adapted the wsme.sphinxext module to work with the function exposed by the |  | ||||||
|     ``wsme.pecan`` adapter. |  | ||||||
|     |  | ||||||
| *   Allow promotion of ``int`` to ``float`` on float attributes (Doug Hellman) |  | ||||||
|  |  | ||||||
| *   Add a ``samples_slot`` option to the ``.. autotype`` directive to |  | ||||||
|     choose where the data samples whould be inserted (Doug Hellman). |  | ||||||
|  |  | ||||||
| *   Add ``sample()`` to ArrayType and DictType (Doug Hellman). |  | ||||||
|  |  | ||||||
| *   New syntax for object arrays as GET parameters, without brackets. Ex: |  | ||||||
|     ``?o.f1=a&o.f1=b&o.f2=c&o.f2=d`` is an array of two objects: |  | ||||||
|     [{'f1': 'a', 'f2': 'c']}, {'f1': 'b', 'f2': 'd']}. |  | ||||||
|  |  | ||||||
| *   @signature (and its @wsexpose frontends) has a new parameter: |  | ||||||
|     ``ignore_extra_args``. |  | ||||||
|  |  | ||||||
| *   Fix boolean as input type support in the soap implementation (Craig |  | ||||||
|     McDaniel). |  | ||||||
|  |  | ||||||
| *   Fix empty/nil strings distinction in soap (Craig McDaniel). |  | ||||||
|  |  | ||||||
| *   Improved unittests code coverage. |  | ||||||
|  |  | ||||||
| *   Ported the soap implementation to python 3. |  | ||||||
|  |  | ||||||
| *   Moved non-core features (adapters, sphinx extension) to the ``wsmeext`` module. |  | ||||||
|  |  | ||||||
| *   Change the GET parameter name for passing the request body as a parameter |  | ||||||
|     is now from 'body' to '__body__' |  | ||||||
|  |  | ||||||
| *   The soap, extdirect and sqlalchemy packages have been merged into the main |  | ||||||
|     package. |  | ||||||
|  |  | ||||||
| *   Changed the documentation theme to "Cloud". |  | ||||||
|  |  | ||||||
| 0.4 (2012-10-15) |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| *   Automatically converts unicode strings to/from ascii bytes. |  | ||||||
|  |  | ||||||
| *   Use d2to1 to simplify setup.py. |  | ||||||
|  |  | ||||||
| *   Implements the SPORE specification. |  | ||||||
|  |  | ||||||
| *   Fixed a few things in the documentation |  | ||||||
|  |  | ||||||
| 0.4b1 (2012-09-14) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Now supports Python 3.2 |  | ||||||
|  |  | ||||||
| *   String types handling is clearer. |  | ||||||
|  |  | ||||||
| *   New :class:`wsme.types.File` type. |  | ||||||
|  |  | ||||||
| *   Supports cross-referenced types. |  | ||||||
|  |  | ||||||
| *   Various bugfixes. |  | ||||||
|  |  | ||||||
| *   Tests code coverage is now over 95%. |  | ||||||
|  |  | ||||||
| *   RESTful protocol can now use the http method. |  | ||||||
|  |  | ||||||
| *   UserTypes can now be given a name that will be used in the |  | ||||||
|     documentation. |  | ||||||
|  |  | ||||||
| *   Complex types can inherit :class:`wsme.types.Base`. They will |  | ||||||
|     have a default constructor and be registered automatically. |  | ||||||
|  |  | ||||||
| *   Removed the wsme.wsgi.adapt function if favor of |  | ||||||
|     :meth:`wsme.WSRoot.wsgiapp` |  | ||||||
|  |  | ||||||
| Extensions |  | ||||||
| ~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| wsme-soap |  | ||||||
|     *   Function names now starts with a lowercase letter. |  | ||||||
|  |  | ||||||
|     *   Fixed issues with arrays (issue #3). |  | ||||||
|  |  | ||||||
|     *   Fixed empty array handling. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| wsme-sqlalchemy |  | ||||||
|     This new extension makes it easy to create webservices on top |  | ||||||
|     of a SQLAlchemy set of mapped classes. |  | ||||||
|  |  | ||||||
| wsme-extdirect |  | ||||||
|     *   Implements server-side DataStore |  | ||||||
|         (:class:`wsmeext.extdirect.datastore.DataStoreController`). |  | ||||||
|  |  | ||||||
|     *   Add Store and Model javascript definition auto-generation |  | ||||||
|  |  | ||||||
|     *   Add Store server-side based on SQLAlchemy mapped classes |  | ||||||
|         (:class:`wsmeext.extdirect.sadatastore.SADataStoreController`). |  | ||||||
|  |  | ||||||
| 0.3 (2012-04-20) |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| *   Initial Sphinx integration. |  | ||||||
|  |  | ||||||
| 0.3b2 (2012-03-29) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Fixed issues with the TG1 adapter. |  | ||||||
|  |  | ||||||
| *   Now handle dict and UserType types as GET/POST params. |  | ||||||
|  |  | ||||||
| *   Better handling of application/x-www-form-urlencoded encoded POSTs |  | ||||||
|     in rest protocols. |  | ||||||
|  |  | ||||||
| *   :class:`wsattr` now takes a 'default' parameter that will be returned |  | ||||||
|     instead of 'Unset' if no value has been set. |  | ||||||
|  |  | ||||||
| 0.3b1 (2012-01-19) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Per-call database transaction handling. |  | ||||||
|  |  | ||||||
| *   :class:`Unset` is now imported in the wsme module |  | ||||||
|  |  | ||||||
| *   Attributes of complex types can now have a different name in |  | ||||||
|     the public api and in the implementation. |  | ||||||
|  |  | ||||||
| *   Complex arguments can now be sent as GET/POST params in the rest |  | ||||||
|     protocols. |  | ||||||
|  |  | ||||||
| *   The restjson protocol do not nest the results in an object anymore. |  | ||||||
|  |  | ||||||
| *   Improved the documentation |  | ||||||
|  |  | ||||||
| *   Fix array attributes validation. |  | ||||||
|  |  | ||||||
| *   Fix date|time parsing errors. |  | ||||||
|  |  | ||||||
| *   Fix Unset values validation. |  | ||||||
|  |  | ||||||
| *   Fix registering of complex types inheriting form already |  | ||||||
|     registered complex types. |  | ||||||
|  |  | ||||||
| *   Fix user types, str and None values encoding/decoding. |  | ||||||
|  |  | ||||||
| 0.2.0 (2011-10-29) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Added batch-calls abilities. |  | ||||||
|  |  | ||||||
| *   Introduce a :class:`UnsetType` and a :data:`Unset` constant |  | ||||||
|     so that non-mandatory attributes can remain unset (which is |  | ||||||
|     different from null). |  | ||||||
|  |  | ||||||
| *   Fix: If a complex type was only used as an input type, it was |  | ||||||
|     not registered. |  | ||||||
|  |  | ||||||
| *   Add support for user types. |  | ||||||
|  |  | ||||||
| *   Add an Enum type (which is a user type). |  | ||||||
|  |  | ||||||
| *   The 'binary' type is now a user type. |  | ||||||
|  |  | ||||||
| *   Complex types: |  | ||||||
|  |  | ||||||
|     -   Fix inspection of complex types with inheritance. |  | ||||||
|  |  | ||||||
|     -   Fix inspection of self-referencing complex types. |  | ||||||
|  |  | ||||||
|     -   wsattr is now a python Descriptor, which makes it possible |  | ||||||
|         to retrieve the attribute definition on a class while |  | ||||||
|         manipulating values on the instance. |  | ||||||
|      |  | ||||||
|     -   Add strong type validation on assignment (made possible by |  | ||||||
|         the use of Descriptors). |  | ||||||
|  |  | ||||||
| *   ExtDirect: |  | ||||||
|  |  | ||||||
|     -   Implements batch calls |  | ||||||
|  |  | ||||||
|     -   Fix None values conversion |  | ||||||
|  |  | ||||||
|     -   Fix transaction result : 'action' and 'method' were missing. |  | ||||||
|  |  | ||||||
| 0.1.1 (2011-10-20) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Changed the internal API by introducing a CallContext object. |  | ||||||
|     It makes it easier to implement some protocols that have |  | ||||||
|     a transaction or call id that has to be returned. It will also |  | ||||||
|     make it possible to implement batch-calls in a later version. |  | ||||||
|  |  | ||||||
| *   More test coverage. |  | ||||||
|  |  | ||||||
| *   Fix a problem with array attribute types not being registered. |  | ||||||
|  |  | ||||||
| *   Fix the mandatory / default detection on function arguments. |  | ||||||
|  |  | ||||||
| *   Fix issues with the SOAP protocol implementation which should now |  | ||||||
|     work properly with a suds client. |  | ||||||
|  |  | ||||||
| *   Fix issues with the ExtDirect protocol implementation. |  | ||||||
|  |  | ||||||
| 0.1.0 (2011-10-14) |  | ||||||
| ------------------ |  | ||||||
|  |  | ||||||
| *   Protocol insertion order now influence the protocol selection |  | ||||||
|  |  | ||||||
| *   Move the soap protocol implementation in a separate lib, |  | ||||||
|     WSME-Soap |  | ||||||
|  |  | ||||||
| *   Introduce a new protocol ExtDirect in the WSME-ExtDirect lib. |  | ||||||
|  |  | ||||||
| 0.1.0a4 (2011-10-12) |  | ||||||
| -------------------- |  | ||||||
|  |  | ||||||
| *   Change the way framework adapters works. Now the adapter modules |  | ||||||
|     have a simple adapt function that adapt a :class:`wsme.WSRoot` |  | ||||||
|     instance. This way a same root can be integrated in several |  | ||||||
|     framework. |  | ||||||
|  |  | ||||||
| *   Protocol lookup now use entry points in the group ``[wsme.protocols]``. |  | ||||||
|  |  | ||||||
| 0.1.0a3 (2011-10-11) |  | ||||||
| -------------------- |  | ||||||
|  |  | ||||||
| *   Add specialised WSRoot classes for easy integration as a |  | ||||||
|     WSGI Application (:class:`wsme.wsgi.WSRoot`) or a |  | ||||||
|     TurboGears 1.x controller (:class:`wsme.tg1.WSRoot`). |  | ||||||
|  |  | ||||||
| *   Improve the documentation. |  | ||||||
|  |  | ||||||
| *   More unit tests and code-coverage. |  | ||||||
|  |  | ||||||
| 0.1.0a2 (2011-10-07) |  | ||||||
| -------------------- |  | ||||||
|  |  | ||||||
| *   Added support for arrays in all the protocols |  | ||||||
|  |  | ||||||
| 0.1.0a1 (2011-10-04) |  | ||||||
| -------------------- |  | ||||||
|  |  | ||||||
| Initial public release. |  | ||||||
							
								
								
									
										259
									
								
								doc/conf.py
									
									
									
									
									
								
							
							
						
						
									
										259
									
								
								doc/conf.py
									
									
									
									
									
								
							| @@ -1,259 +0,0 @@ | |||||||
| # -*- coding: utf-8 -*- |  | ||||||
| # |  | ||||||
| # Web Services Made Easy documentation build configuration file, created by |  | ||||||
| # sphinx-quickstart on Sun Oct  2 20:27:45 2011. |  | ||||||
| # |  | ||||||
| # This file is execfile()d with the current directory set to its containing dir. |  | ||||||
| # |  | ||||||
| # Note that not all possible configuration values are present in this |  | ||||||
| # autogenerated file. |  | ||||||
| # |  | ||||||
| # All configuration values have a default; values that are commented out |  | ||||||
| # serve to show the default. |  | ||||||
|  |  | ||||||
| import sys, os |  | ||||||
|  |  | ||||||
| # If extensions (or modules to document with autodoc) are in another directory, |  | ||||||
| # add these directories to sys.path here. If the directory is relative to the |  | ||||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. |  | ||||||
| sys.path.insert(0, os.path.abspath('..')) |  | ||||||
|  |  | ||||||
| # -- General configuration ----------------------------------------------------- |  | ||||||
|  |  | ||||||
| # If your documentation needs a minimal Sphinx version, state it here. |  | ||||||
| #needs_sphinx = '1.0' |  | ||||||
|  |  | ||||||
| # Add any Sphinx extension module names here, as strings. They can be extensions |  | ||||||
| # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |  | ||||||
| extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'wsmeext.sphinxext', |  | ||||||
|     'sphinx.ext.intersphinx'] |  | ||||||
|  |  | ||||||
| # Add any paths that contain templates here, relative to this directory. |  | ||||||
| templates_path = ['_templates'] |  | ||||||
|  |  | ||||||
| # The suffix of source filenames. |  | ||||||
| source_suffix = '.rst' |  | ||||||
|  |  | ||||||
| # The encoding of source files. |  | ||||||
| #source_encoding = 'utf-8-sig' |  | ||||||
|  |  | ||||||
| # The master toctree document. |  | ||||||
| master_doc = 'index' |  | ||||||
|  |  | ||||||
| # General information about the project. |  | ||||||
| project = u'Web Services Made Easy' |  | ||||||
| copyright = u'2011, Christophe de Vienne' |  | ||||||
|  |  | ||||||
| # The version info for the project you're documenting, acts as replacement for |  | ||||||
| # |version| and |release|, also used in various other places throughout the |  | ||||||
| # built documents. |  | ||||||
| # |  | ||||||
| import pkg_resources |  | ||||||
| wsmedist = pkg_resources.require('WSME')[0] |  | ||||||
| version = wsmedist.version |  | ||||||
|  |  | ||||||
| # The short X.Y version. |  | ||||||
| version = '.'.join(version.split('.')[:2]) |  | ||||||
| # The full version, including alpha/beta/rc tags. |  | ||||||
| release = version |  | ||||||
|  |  | ||||||
| # The language for content autogenerated by Sphinx. Refer to documentation |  | ||||||
| # for a list of supported languages. |  | ||||||
| #language = None |  | ||||||
|  |  | ||||||
| # There are two options for replacing |today|: either, you set today to some |  | ||||||
| # non-false value, then it is used: |  | ||||||
| #today = '' |  | ||||||
| # Else, today_fmt is used as the format for a strftime call. |  | ||||||
| #today_fmt = '%B %d, %Y' |  | ||||||
|  |  | ||||||
| # List of patterns, relative to source directory, that match files and |  | ||||||
| # directories to ignore when looking for source files. |  | ||||||
| exclude_patterns = ['_build'] |  | ||||||
|  |  | ||||||
| # The reST default role (used for this markup: `text`) to use for all documents. |  | ||||||
| #default_role = None |  | ||||||
|  |  | ||||||
| # If true, '()' will be appended to :func: etc. cross-reference text. |  | ||||||
| #add_function_parentheses = True |  | ||||||
|  |  | ||||||
| # If true, the current module name will be prepended to all description |  | ||||||
| # unit titles (such as .. function::). |  | ||||||
| #add_module_names = True |  | ||||||
|  |  | ||||||
| # If true, sectionauthor and moduleauthor directives will be shown in the |  | ||||||
| # output. They are ignored by default. |  | ||||||
| #show_authors = False |  | ||||||
|  |  | ||||||
| # The name of the Pygments (syntax highlighting) style to use. |  | ||||||
| pygments_style = 'sphinx' |  | ||||||
|  |  | ||||||
| # A list of ignored prefixes for module index sorting. |  | ||||||
| #modindex_common_prefix = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for HTML output --------------------------------------------------- |  | ||||||
|  |  | ||||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for |  | ||||||
| # a list of builtin themes. |  | ||||||
| #html_theme = 'agogo' |  | ||||||
| #html_theme_options = { |  | ||||||
| #    "pagewidth": "60em", |  | ||||||
| #    "documentwidth": "40em", |  | ||||||
| #} |  | ||||||
|  |  | ||||||
| #html_style = 'wsme.css' |  | ||||||
|  |  | ||||||
| import cloud_sptheme as csp |  | ||||||
|  |  | ||||||
| html_theme = 'cloud' |  | ||||||
| html_theme_path = [csp.get_theme_dir()] |  | ||||||
|  |  | ||||||
| html_theme_options = { |  | ||||||
|     "roottarget": "index", |  | ||||||
|     "googleanalytics_id": "UA-8510502-6" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| # Theme options are theme-specific and customize the look and feel of a theme |  | ||||||
| # further.  For a list of options available for each theme, see the |  | ||||||
| # documentation. |  | ||||||
| #html_theme_options = {} |  | ||||||
|  |  | ||||||
| # Add any paths that contain custom themes here, relative to this directory. |  | ||||||
| #html_theme_path = [] |  | ||||||
|  |  | ||||||
| # The name for this set of Sphinx documents.  If None, it defaults to |  | ||||||
| # "<project> v<release> documentation". |  | ||||||
| html_title = "WSME %s" % release |  | ||||||
|  |  | ||||||
| # A shorter title for the navigation bar.  Default is the same as html_title. |  | ||||||
| #html_short_title = "WSME" |  | ||||||
|  |  | ||||||
| # The name of an image file (relative to this directory) to place at the top |  | ||||||
| # of the sidebar. |  | ||||||
| #html_logo = None |  | ||||||
|  |  | ||||||
| # The name of an image file (within the static path) to use as favicon of the |  | ||||||
| # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 |  | ||||||
| # pixels large. |  | ||||||
| #html_favicon = None |  | ||||||
|  |  | ||||||
| # Add any paths that contain custom static files (such as style sheets) here, |  | ||||||
| # relative to this directory. They are copied after the builtin static files, |  | ||||||
| # so a file named "default.css" will overwrite the builtin "default.css". |  | ||||||
| html_static_path = ['_static'] |  | ||||||
|  |  | ||||||
| # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |  | ||||||
| # using the given strftime format. |  | ||||||
| #html_last_updated_fmt = '%b %d, %Y' |  | ||||||
|  |  | ||||||
| # If true, SmartyPants will be used to convert quotes and dashes to |  | ||||||
| # typographically correct entities. |  | ||||||
| #html_use_smartypants = True |  | ||||||
|  |  | ||||||
| # Custom sidebar templates, maps document names to template names. |  | ||||||
| #html_sidebars = {} |  | ||||||
|  |  | ||||||
| # Additional templates that should be rendered to pages, maps page names to |  | ||||||
| # template names. |  | ||||||
| #html_additional_pages = {} |  | ||||||
|  |  | ||||||
| # If false, no module index is generated. |  | ||||||
| #html_domain_indices = True |  | ||||||
|  |  | ||||||
| # If false, no index is generated. |  | ||||||
| #html_use_index = True |  | ||||||
|  |  | ||||||
| # If true, the index is split into individual pages for each letter. |  | ||||||
| #html_split_index = False |  | ||||||
|  |  | ||||||
| # If true, links to the reST sources are added to the pages. |  | ||||||
| #html_show_sourcelink = True |  | ||||||
|  |  | ||||||
| # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. |  | ||||||
| #html_show_sphinx = True |  | ||||||
|  |  | ||||||
| # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. |  | ||||||
| #html_show_copyright = True |  | ||||||
|  |  | ||||||
| # If true, an OpenSearch description file will be output, and all pages will |  | ||||||
| # contain a <link> tag referring to it.  The value of this option must be the |  | ||||||
| # base URL from which the finished HTML is served. |  | ||||||
| #html_use_opensearch = '' |  | ||||||
|  |  | ||||||
| # This is the file name suffix for HTML files (e.g. ".xhtml"). |  | ||||||
| #html_file_suffix = None |  | ||||||
|  |  | ||||||
| # Output file base name for HTML help builder. |  | ||||||
| htmlhelp_basename = 'WebServicesMadeEasydoc' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for LaTeX output -------------------------------------------------- |  | ||||||
|  |  | ||||||
| # The paper size ('letter' or 'a4'). |  | ||||||
| latex_paper_size = 'a4' |  | ||||||
|  |  | ||||||
| # The font size ('10pt', '11pt' or '12pt'). |  | ||||||
| #latex_font_size = '10pt' |  | ||||||
|  |  | ||||||
| # Grouping the document tree into LaTeX files. List of tuples |  | ||||||
| # (source start file, target name, title, author, documentclass [howto/manual]). |  | ||||||
| latex_documents = [ |  | ||||||
|   ('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation', |  | ||||||
|    u'Christophe de Vienne', 'manual'), |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| # The name of an image file (relative to this directory) to place at the top of |  | ||||||
| # the title page. |  | ||||||
| #latex_logo = None |  | ||||||
|  |  | ||||||
| # For "manual" documents, if this is true, then toplevel headings are parts, |  | ||||||
| # not chapters. |  | ||||||
| #latex_use_parts = False |  | ||||||
|  |  | ||||||
| # If true, show page references after internal links. |  | ||||||
| #latex_show_pagerefs = False |  | ||||||
|  |  | ||||||
| # If true, show URL addresses after external links. |  | ||||||
| #latex_show_urls = False |  | ||||||
|  |  | ||||||
| # Additional stuff for the LaTeX preamble. |  | ||||||
| latex_preamble = ''' |  | ||||||
| \usepackage[T2A]{fontenc} |  | ||||||
| \usepackage[utf8]{inputenc} |  | ||||||
| ''' |  | ||||||
|  |  | ||||||
| # Documents to append as an appendix to all manuals. |  | ||||||
| #latex_appendices = [] |  | ||||||
|  |  | ||||||
| # If false, no module index is generated. |  | ||||||
| #latex_domain_indices = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for manual page output -------------------------------------------- |  | ||||||
|  |  | ||||||
| # One entry per manual page. List of tuples |  | ||||||
| # (source start file, name, description, authors, manual section). |  | ||||||
| man_pages = [ |  | ||||||
|     ('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation', |  | ||||||
|      [u'Christophe de Vienne'], 1) |  | ||||||
| ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| autodoc_member_order = 'bysource' |  | ||||||
|  |  | ||||||
| wsme_protocols = [ |  | ||||||
|     'restjson', 'restxml', 'soap', 'extdirect' |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| intersphinx_mapping = { |  | ||||||
|     'python': ('http://docs.python.org/', None), |  | ||||||
|     'six': ('http://packages.python.org/six/', None), |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup(app): |  | ||||||
|     # confval directive taken from the sphinx doc |  | ||||||
|     app.add_object_type('confval', 'confval', |  | ||||||
|                         objname='configuration value', |  | ||||||
|                         indextemplate='pair: %s; configuration value') |  | ||||||
							
								
								
									
										190
									
								
								doc/document.rst
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								doc/document.rst
									
									
									
									
									
								
							| @@ -1,190 +0,0 @@ | |||||||
| Document your API |  | ||||||
| ================= |  | ||||||
|  |  | ||||||
| Web services without a proper documentation are usually useless. |  | ||||||
|  |  | ||||||
| To make it easy to document your own API, WSME provides a Sphinx_ extension. |  | ||||||
|  |  | ||||||
| Install the extension |  | ||||||
| --------------------- |  | ||||||
|  |  | ||||||
| Here we consider that you already quick-started a sphinx project. |  | ||||||
|  |  | ||||||
| #.  In your ``conf.py`` file, add ``'ext'`` to your extensions, |  | ||||||
|     and optionally set the enabled protocols. |  | ||||||
|  |  | ||||||
|     .. code-block:: python |  | ||||||
|  |  | ||||||
|         extensions = ['ext'] |  | ||||||
|  |  | ||||||
|         wsme_protocols = ['restjson', 'restxml', 'extdirect'] |  | ||||||
|  |  | ||||||
| #.  Copy :download:`toggle.js <_static/toggle.js>` |  | ||||||
|     and :download:`toggle.css <_static/toggle.css>` |  | ||||||
|     in your _static directory. |  | ||||||
|  |  | ||||||
| The ``wsme`` domain |  | ||||||
| ------------------- |  | ||||||
|  |  | ||||||
| The extension will add a new Sphinx domain providing a few directives. |  | ||||||
|  |  | ||||||
| Config values |  | ||||||
| ~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. confval:: wsme_protocols |  | ||||||
|  |  | ||||||
|     A list of strings that are WSME protocol names. If provided by an |  | ||||||
|     additional package (for example WSME-Soap or WSME-ExtDirect), that package must |  | ||||||
|     be installed. |  | ||||||
|  |  | ||||||
|     The types and services generated documentation will include code samples |  | ||||||
|     for each of these protocols. |  | ||||||
|  |  | ||||||
| .. confval:: wsme_root |  | ||||||
|  |  | ||||||
|     A string that is the full name of the service root controller. |  | ||||||
|     It will be used |  | ||||||
|     to determinate the relative path of the other controllers when they |  | ||||||
|     are autodocumented, and calculate the complete webpath of the other |  | ||||||
|     controllers. |  | ||||||
|  |  | ||||||
| .. confval:: wsme_webpath |  | ||||||
|  |  | ||||||
|     A string that is the webpath where the :confval:`wsme_root` is mounted. |  | ||||||
|  |  | ||||||
| Directives |  | ||||||
| ~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. root:: <WSRoot full path> |  | ||||||
|  |  | ||||||
|     Define the service root controller for this documentation source file. |  | ||||||
|     To set it globally, see :confval:`wsme_root`. |  | ||||||
|  |  | ||||||
|     A ``webpath`` option allows override of :confval:`wsme_webpath`. |  | ||||||
|      |  | ||||||
|     Example: |  | ||||||
|  |  | ||||||
|     .. code-block:: rst |  | ||||||
|  |  | ||||||
|         .. wsme:root:: myapp.controllers.MyWSRoot |  | ||||||
|             :webpath: /api |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. service:: name/space/ServiceName |  | ||||||
|  |  | ||||||
|     Declare a service. |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. type:: MyComplexType |  | ||||||
|  |  | ||||||
|     Equivalent to the :rst:dir:`py:class` directive to document a complex type |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. attribute:: aname |  | ||||||
|  |  | ||||||
|     Equivalent to the :rst:dir:`py:attribute` directive to document a complex type |  | ||||||
|     attribute. It takes an additional ``:type:`` field. |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| .. list-table:: |  | ||||||
|     :header-rows: 1 |  | ||||||
|  |  | ||||||
|     * - Source |  | ||||||
|       - Result |  | ||||||
|  |  | ||||||
|     * - .. code-block:: rst |  | ||||||
|  |  | ||||||
|             .. wsme:root:: wsmeext.sphinxext.SampleService |  | ||||||
|                 :webpath: /api |  | ||||||
|  |  | ||||||
|             .. wsme:type:: MyType |  | ||||||
|  |  | ||||||
|                 .. wsme:attribute:: test |  | ||||||
|  |  | ||||||
|                     :type: int |  | ||||||
|  |  | ||||||
|             .. wsme:service:: name/space/SampleService |  | ||||||
|                  |  | ||||||
|                 .. wsme:function:: doit |  | ||||||
|                      |  | ||||||
|       - .. wsme:root:: wsmeext.sphinxext.SampleService |  | ||||||
|             :webpath: /api |  | ||||||
|  |  | ||||||
|         .. wsme:type:: MyType |  | ||||||
|  |  | ||||||
|             .. wsme:attribute:: test |  | ||||||
|  |  | ||||||
|                 :type: int |  | ||||||
|  |  | ||||||
|         .. wsme:service:: name/space/SampleService |  | ||||||
|              |  | ||||||
|             .. wsme:function:: getType |  | ||||||
|                  |  | ||||||
|                 Returns a :wsme:type:`MyType <MyType>` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Autodoc directives |  | ||||||
| ~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Theses directives scan your code to generate the documentation from the |  | ||||||
| docstrings and your API types and controllers. |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. autotype:: myapp.MyType |  | ||||||
|  |  | ||||||
|     Generate the myapp.MyType documentation. |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. autoattribute:: myapp.MyType.aname |  | ||||||
|  |  | ||||||
|     Generate the myapp.MyType.aname documentation. |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. autoservice:: myapp.MyService |  | ||||||
|  |  | ||||||
|     Generate the myapp.MyService documentation. |  | ||||||
|  |  | ||||||
| .. rst:directive:: .. autofunction:: myapp.MyService.myfunction |  | ||||||
|  |  | ||||||
|     Generate the myapp.MyService.myfunction documentation. |  | ||||||
|  |  | ||||||
| Full Example |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| Python source |  | ||||||
| ~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. literalinclude:: ../wsmeext/sphinxext.py |  | ||||||
|     :lines: 69-96 |  | ||||||
|     :language: python |  | ||||||
|  |  | ||||||
| Documentation source |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. code-block:: rst |  | ||||||
|  |  | ||||||
|     .. default-domain:: wsmeext |  | ||||||
|  |  | ||||||
|     .. type:: int |  | ||||||
|  |  | ||||||
|         An integer |  | ||||||
|  |  | ||||||
|     .. autotype:: wsmeext.sphinxext.SampleType |  | ||||||
|         :members: |  | ||||||
|  |  | ||||||
|     .. autoservice:: wsmeext.sphinxext.SampleService |  | ||||||
|         :members: |  | ||||||
|  |  | ||||||
| Result |  | ||||||
| ~~~~~~ |  | ||||||
|  |  | ||||||
| .. default-domain:: wsmeext |  | ||||||
|  |  | ||||||
| .. type:: int |  | ||||||
|  |  | ||||||
|     An integer |  | ||||||
|  |  | ||||||
| .. autotype:: wsmeext.sphinxext.SampleType |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autoservice:: wsmeext.sphinxext.SampleService |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _Sphinx: http://sphinx.pocoo.org/ |  | ||||||
| @@ -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. |  | ||||||
| @@ -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 |  | ||||||
|  |  | ||||||
| @@ -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` |  | ||||||
|  |  | ||||||
| @@ -1,342 +0,0 @@ | |||||||
| Integrating with a Framework |  | ||||||
| ============================ |  | ||||||
|  |  | ||||||
| General considerations |  | ||||||
| ---------------------- |  | ||||||
|  |  | ||||||
| Using WSME within another framework providing its own REST capabilities is |  | ||||||
| generally done by using a specific decorator to declare the function signature, |  | ||||||
| in addition to the framework's own way of declaring exposed functions. |  | ||||||
|  |  | ||||||
| This decorator can have two different names depending on the adapter. |  | ||||||
|  |  | ||||||
| ``@wsexpose`` |  | ||||||
|     This decorator will declare the function signature *and* |  | ||||||
|     take care of calling the adequate decorators of the framework. |  | ||||||
|  |  | ||||||
|     Generally this decorator is provided for frameworks that use |  | ||||||
|     object-dispatch controllers, such as :ref:`adapter-pecan` and |  | ||||||
|     :ref:`adapter-tg1`.  |  | ||||||
|  |  | ||||||
| ``@signature`` |  | ||||||
|     This decorator only sets the function signature and returns a function |  | ||||||
|     that can be used by the host framework as a REST request target. |  | ||||||
|  |  | ||||||
|     Generally this decorator is provided for frameworks that expect functions |  | ||||||
|     taking a request object as a single parameter and returning a response |  | ||||||
|     object. This is the case for :ref:`adapter-cornice` and |  | ||||||
|     :ref:`adapter-flask`. |  | ||||||
|  |  | ||||||
| If you want to enable additional protocols, you will need to |  | ||||||
| mount a :class:`WSRoot` instance somewhere in the application, generally |  | ||||||
| ``/ws``. This subpath will then handle the additional protocols. In a future |  | ||||||
| version, a WSGI middleware will probably play this role. |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     Not all the adapters are at the same level of maturity. |  | ||||||
|  |  | ||||||
| WSGI Application |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| The :func:`wsme.WSRoot.wsgiapp` function of WSRoot returns a WSGI |  | ||||||
| application. |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| The following example assumes the REST protocol will be entirely handled by |  | ||||||
| WSME, which is the case if you write a WSME standalone application. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     from wsme import WSRoot, expose |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     class MyRoot(WSRoot): |  | ||||||
|         @expose(unicode) |  | ||||||
|         def helloworld(self): |  | ||||||
|             return u"Hello World !" |  | ||||||
|  |  | ||||||
|     root = MyRoot(protocols=['restjson']) |  | ||||||
|     application = root.wsgiapp() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _adapter-cornice: |  | ||||||
|  |  | ||||||
| Cornice |  | ||||||
| ------- |  | ||||||
|  |  | ||||||
| .. _cornice: http://cornice.readthedocs.org/en/latest/ |  | ||||||
|  |  | ||||||
|     *"* Cornice_ *provides helpers to build & document REST-ish Web Services with |  | ||||||
|     Pyramid, with decent default behaviors. It takes care of following the HTTP |  | ||||||
|     specification in an automated way where possible."* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| :mod:`wsmeext.cornice` -- Cornice adapter |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. module:: wsmeext.cornice |  | ||||||
|  |  | ||||||
| .. function:: signature |  | ||||||
|      |  | ||||||
|     Declare the parameters of a function and returns a function suitable for |  | ||||||
|     cornice (ie that takes a request and returns a response). |  | ||||||
|  |  | ||||||
| Configuration |  | ||||||
| ~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| To use WSME with Cornice you have to add a configuration option to your Pyramid application. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     from pyramid.config import Configurator |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def make_app(): |  | ||||||
|         config = Configurator() |  | ||||||
|         config.include("cornice") |  | ||||||
|         config.include("wsmeext.cornice")  # This includes WSME cornice support |  | ||||||
|         # ... |  | ||||||
|         return config.make_wsgi_app() |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     from cornice import Service |  | ||||||
|     from wsmeext.cornice import signature |  | ||||||
|     import wsme.types |  | ||||||
|  |  | ||||||
|     hello = Service(name='hello', path='/', description="Simplest app") |  | ||||||
|  |  | ||||||
|     class Info(wsme.types.Base): |  | ||||||
|         message = wsme.types.text |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @hello.get() |  | ||||||
|     @signature(Info) |  | ||||||
|     def get_info(): |  | ||||||
|         """Returns Hello in JSON or XML.""" |  | ||||||
|         return Info(message='Hello World') |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @hello.post() |  | ||||||
|     @signature(None, Info) |  | ||||||
|     def set_info(info): |  | ||||||
|         print("Got a message: %s" % info.message) |  | ||||||
|      |  | ||||||
|  |  | ||||||
| .. _adapter-flask: |  | ||||||
|  |  | ||||||
| Flask |  | ||||||
| ----- |  | ||||||
|  |  | ||||||
|     *"Flask is a microframework for Python based on Werkzeug, Jinja 2 and good |  | ||||||
|     intentions. And before you ask: It's BSD licensed! "* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. warning:: |  | ||||||
|  |  | ||||||
|     Flask support is limited to function signature handling. It does not |  | ||||||
|     support additional protocols. This is a temporary limitation, if you have |  | ||||||
|     needs on that matter please tell us at python-wsme@googlegroups.com. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| :mod:`wsmeext.flask` -- Flask adapter |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. module:: wsmeext.flask |  | ||||||
|  |  | ||||||
| .. function:: signature(return_type, \*arg_types, \*\*options) |  | ||||||
|  |  | ||||||
|     See @\ :func:`signature` for parameters documentation. |  | ||||||
|  |  | ||||||
|     Can be used on a function before routing it with flask. |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     from wsmeext.flask import signature |  | ||||||
|  |  | ||||||
|     @app.route('/multiply') |  | ||||||
|     @signature(int, int, int) |  | ||||||
|     def multiply(a, b): |  | ||||||
|         return a * b |  | ||||||
|  |  | ||||||
| .. _adapter-pecan: |  | ||||||
|  |  | ||||||
| Pecan |  | ||||||
| ----- |  | ||||||
|  |  | ||||||
|     *"*\ Pecan_ *was created to fill a void in the Python web-framework world – |  | ||||||
|     a very lightweight framework that provides object-dispatch style routing. |  | ||||||
|     Pecan does not aim to be a "full stack" framework, and therefore includes |  | ||||||
|     no out of the box support for things like sessions or databases. Pecan |  | ||||||
|     instead focuses on HTTP itself."* |  | ||||||
|  |  | ||||||
| .. warning:: |  | ||||||
|  |  | ||||||
|     A pecan application is not able to mount another WSGI application on a |  | ||||||
|     subpath. For that reason, additional protocols are not supported for now, |  | ||||||
|     until WSME provides a middleware that can do the same as a mounted |  | ||||||
|     WSRoot. |  | ||||||
|  |  | ||||||
| :mod:`wsmeext.pecan` -- Pecan adapter |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. module:: wsmeext.pecan |  | ||||||
|  |  | ||||||
| .. function:: wsexpose(return_type, \*arg_types, \*\*options) |  | ||||||
|  |  | ||||||
|     See @\ :func:`signature` for parameters documentation. |  | ||||||
|  |  | ||||||
|     Can be used on any function of a pecan |  | ||||||
|     `RestController <http://pecan.readthedocs.org/en/latest/rest.html>`_ |  | ||||||
|     instead of the expose decorator from Pecan. |  | ||||||
|  |  | ||||||
| Configuration |  | ||||||
| ~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| WSME can be configured through the application configation, by adding a 'wsme' |  | ||||||
| configuration entry in ``config.py``: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     wsme = { |  | ||||||
|         'debug': True |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| Valid configuration variables are : |  | ||||||
|  |  | ||||||
| -   ``'debug'``: Whether or not to include exception tracebacks in the returned |  | ||||||
|     server-side errors. |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| The `example <http://pecan.readthedocs.org/en/latest/rest.html#nesting-restcontroller>`_ from the Pecan documentation becomes: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     from wsmeext.pecan import wsexpose |  | ||||||
|          |  | ||||||
|     class BooksController(RestController): |  | ||||||
|         @wsexpose(Book, int, int) |  | ||||||
|         def get(self, author_id, id): |  | ||||||
|             # .. |  | ||||||
|  |  | ||||||
|         @wsexpose(Book, int, int, body=Book) |  | ||||||
|         def put(self, author_id, id, book): |  | ||||||
|             # .. |  | ||||||
|  |  | ||||||
|     class AuthorsController(RestController): |  | ||||||
|             books = BooksController() |  | ||||||
|  |  | ||||||
| .. _Pecan: http://pecanpy.org/ |  | ||||||
|  |  | ||||||
| .. _adapter-tg1: |  | ||||||
|  |  | ||||||
| Turbogears 1.x |  | ||||||
| -------------- |  | ||||||
|  |  | ||||||
| The TG adapters have an api very similar to TGWebServices. Migrating from it |  | ||||||
| should be straightforward (a little howto migrate would not hurt though, and it |  | ||||||
| will be written as soon as possible). |  | ||||||
|  |  | ||||||
| :mod:`wsmeext.tg11` -- TG 1.1 adapter |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. module:: wsmeext.tg11 |  | ||||||
|  |  | ||||||
| .. function:: wsexpose(return_type, \*arg_types, \*\*options) |  | ||||||
|  |  | ||||||
|     See @\ :func:`signature` for parameters documentation. |  | ||||||
|  |  | ||||||
|     Can be used on any function of a controller |  | ||||||
|     instead of the expose decorator from TG. |  | ||||||
|  |  | ||||||
| .. function:: wsvalidate(\*arg_types) |  | ||||||
|  |  | ||||||
|     Set the argument types of an exposed function. This decorator is provided |  | ||||||
|     so that WSME is an almost drop-in replacement for TGWebServices. If |  | ||||||
|     starting from scratch you can use \ :func:`wsexpose` only |  | ||||||
|  |  | ||||||
| .. function:: adapt(wsroot) |  | ||||||
|  |  | ||||||
|     Returns a TG1 controller instance that publish a :class:`wsme.WSRoot`. |  | ||||||
|     It can then be mounted on a TG1 controller. |  | ||||||
|  |  | ||||||
|     Because the adapt function modifies the cherrypy filters of the controller |  | ||||||
|     the 'webpath' of the WSRoot instance must be consistent with the path it |  | ||||||
|     will be mounted on. |  | ||||||
|  |  | ||||||
| :mod:`wsmeext.tg15` -- TG 1.5 adapter |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. module:: wsmeext.tg15 |  | ||||||
|  |  | ||||||
| This adapter has the exact same api as :mod:`wsmeext.tg11`. |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| In a freshly quickstarted tg1 application (let's say, wsmedemo), you can add |  | ||||||
| REST-ish functions anywhere in your controller tree. Here directly on the root, |  | ||||||
| in controllers.py: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     # ... |  | ||||||
|  |  | ||||||
|     # For tg 1.5, import from wsmeext.tg15 instead : |  | ||||||
|     from wsmeext.tg11 import wsexpose, WSRoot |  | ||||||
|  |  | ||||||
|     class Root(controllers.RootController): |  | ||||||
|         # Having a WSRoot on /ws is only required to enable additional |  | ||||||
|         # protocols. For REST-only services, it can be ignored. |  | ||||||
|         ws = adapt( |  | ||||||
|             WSRoot(webpath='/ws', protocols=['soap']) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         @wsexpose(int, int, int) |  | ||||||
|         def multiply(self, a, b): |  | ||||||
|             return a * b |  | ||||||
|  |  | ||||||
| .. _TurboGears: http://www.turbogears.org/ |  | ||||||
|  |  | ||||||
| Other frameworks |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| Bottle |  | ||||||
| ~~~~~~ |  | ||||||
|  |  | ||||||
| No adapter is provided yet but it should not be hard to write one, by taking |  | ||||||
| example on the cornice adapter. |  | ||||||
|  |  | ||||||
| This example only show how to mount a WSRoot inside a bottle application. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     import bottle |  | ||||||
|     import wsme |  | ||||||
|  |  | ||||||
|     class MyRoot(wsme.WSRoot): |  | ||||||
|         @wsme.expose(unicode) |  | ||||||
|         def helloworld(self): |  | ||||||
|             return u"Hello World !" |  | ||||||
|  |  | ||||||
|     root = MyRoot(webpath='/ws', protocols=['restjson']) |  | ||||||
|  |  | ||||||
|     bottle.mount('/ws', root.wsgiapp()) |  | ||||||
|     bottle.run() |  | ||||||
|  |  | ||||||
| Pyramid |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| The recommended way of using WSME inside Pyramid is to use |  | ||||||
| :ref:`adapter-cornice`. |  | ||||||
							
								
								
									
										155
									
								
								doc/make.bat
									
									
									
									
									
								
							
							
						
						
									
										155
									
								
								doc/make.bat
									
									
									
									
									
								
							| @@ -1,155 +0,0 @@ | |||||||
| @ECHO OFF |  | ||||||
|  |  | ||||||
| REM Command file for Sphinx documentation |  | ||||||
|  |  | ||||||
| if "%SPHINXBUILD%" == "" ( |  | ||||||
| 	set SPHINXBUILD=sphinx-build |  | ||||||
| ) |  | ||||||
| set BUILDDIR=_build |  | ||||||
| set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . |  | ||||||
| if NOT "%PAPER%" == "" ( |  | ||||||
| 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "" goto help |  | ||||||
|  |  | ||||||
| if "%1" == "help" ( |  | ||||||
| 	:help |  | ||||||
| 	echo.Please use `make ^<target^>` where ^<target^> is one of |  | ||||||
| 	echo.  html       to make standalone HTML files |  | ||||||
| 	echo.  dirhtml    to make HTML files named index.html in directories |  | ||||||
| 	echo.  singlehtml to make a single large HTML file |  | ||||||
| 	echo.  pickle     to make pickle files |  | ||||||
| 	echo.  json       to make JSON files |  | ||||||
| 	echo.  htmlhelp   to make HTML files and a HTML help project |  | ||||||
| 	echo.  qthelp     to make HTML files and a qthelp project |  | ||||||
| 	echo.  devhelp    to make HTML files and a Devhelp project |  | ||||||
| 	echo.  epub       to make an epub |  | ||||||
| 	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter |  | ||||||
| 	echo.  text       to make text files |  | ||||||
| 	echo.  man        to make manual pages |  | ||||||
| 	echo.  changes    to make an overview over all changed/added/deprecated items |  | ||||||
| 	echo.  linkcheck  to check all external links for integrity |  | ||||||
| 	echo.  doctest    to run all doctests embedded in the documentation if enabled |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "clean" ( |  | ||||||
| 	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i |  | ||||||
| 	del /q /s %BUILDDIR%\* |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "html" ( |  | ||||||
| 	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/html. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "dirhtml" ( |  | ||||||
| 	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "singlehtml" ( |  | ||||||
| 	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "pickle" ( |  | ||||||
| 	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished; now you can process the pickle files. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "json" ( |  | ||||||
| 	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished; now you can process the JSON files. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "htmlhelp" ( |  | ||||||
| 	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished; now you can run HTML Help Workshop with the ^ |  | ||||||
| .hhp project file in %BUILDDIR%/htmlhelp. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "qthelp" ( |  | ||||||
| 	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished; now you can run "qcollectiongenerator" with the ^ |  | ||||||
| .qhcp project file in %BUILDDIR%/qthelp, like this: |  | ||||||
| 	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\WebServicesMadeEasy.qhcp |  | ||||||
| 	echo.To view the help file: |  | ||||||
| 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\WebServicesMadeEasy.ghc |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "devhelp" ( |  | ||||||
| 	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "epub" ( |  | ||||||
| 	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. The epub file is in %BUILDDIR%/epub. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "latex" ( |  | ||||||
| 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "text" ( |  | ||||||
| 	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. The text files are in %BUILDDIR%/text. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "man" ( |  | ||||||
| 	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man |  | ||||||
| 	echo. |  | ||||||
| 	echo.Build finished. The manual pages are in %BUILDDIR%/man. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "changes" ( |  | ||||||
| 	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes |  | ||||||
| 	echo. |  | ||||||
| 	echo.The overview file is in %BUILDDIR%/changes. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "linkcheck" ( |  | ||||||
| 	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck |  | ||||||
| 	echo. |  | ||||||
| 	echo.Link check complete; look for any errors in the above output ^ |  | ||||||
| or in %BUILDDIR%/linkcheck/output.txt. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if "%1" == "doctest" ( |  | ||||||
| 	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest |  | ||||||
| 	echo. |  | ||||||
| 	echo.Testing of doctests in the sources finished, look at the ^ |  | ||||||
| results in %BUILDDIR%/doctest/output.txt. |  | ||||||
| 	goto end |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| :end |  | ||||||
| @@ -1,366 +0,0 @@ | |||||||
| Protocols |  | ||||||
| ========= |  | ||||||
|  |  | ||||||
| In this document the same webservice example will be used to |  | ||||||
| illustrate the different protocols. Its source code is in the |  | ||||||
| last chapter (:ref:`protocols-the-example`). |  | ||||||
|  |  | ||||||
| REST |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     This chapter applies for all adapters, not just the native REST |  | ||||||
|     implementation. |  | ||||||
|  |  | ||||||
| The two REST protocols share common characterics. |  | ||||||
|  |  | ||||||
| Each function corresponds to distinct webpath that starts with the |  | ||||||
| root webpath, followed by the controllers names if any, and finally |  | ||||||
| the function name. |  | ||||||
|  |  | ||||||
| The example's exposed functions will be mapped to the following paths: |  | ||||||
|  |  | ||||||
| -   ``/ws/persons/create`` |  | ||||||
| -   ``/ws/persons/get`` |  | ||||||
| -   ``/ws/persons/list`` |  | ||||||
| -   ``/ws/persons/update`` |  | ||||||
| -   ``/ws/persons/destroy`` |  | ||||||
|  |  | ||||||
| In addition to this trivial function mapping, a `method` option can |  | ||||||
| be given to the `expose` decorator. In such a case, the function |  | ||||||
| name can be omitted by the caller, and the dispatch will look at the |  | ||||||
| HTTP method used in the request to select the correct function. |  | ||||||
|  |  | ||||||
| The function parameters can be transmitted in two ways (if using |  | ||||||
| the HTTP method to select the function, one way or the other |  | ||||||
| may be usable) : |  | ||||||
|  |  | ||||||
| #.  As a GET query string or POST form parameters. |  | ||||||
|  |  | ||||||
|     Simple types are straight forward : |  | ||||||
|  |  | ||||||
|     ``/ws/person/get?id=5`` |  | ||||||
|  |  | ||||||
|     Complex types can be transmitted this way: |  | ||||||
|  |  | ||||||
|     ``/ws/person/update?p.id=1&p.name=Ross&p.hobbies[0]=Dinausaurs&p.hobbies[1]=Rachel`` |  | ||||||
|  |  | ||||||
| #.  In a Json or XML encoded POST body (see below) |  | ||||||
|  |  | ||||||
| The result will be returned Json or XML encoded (see below). |  | ||||||
|  |  | ||||||
| In case of error, a 400 or 500 status code is returned, and the |  | ||||||
| response body contains details about the error (see below). |  | ||||||
|  |  | ||||||
| REST+Json |  | ||||||
| --------- |  | ||||||
|  |  | ||||||
| :name: ``'restjson'`` |  | ||||||
|  |  | ||||||
| Implements a REST+Json protocol. |  | ||||||
|  |  | ||||||
| This protocol is selected if: |  | ||||||
|  |  | ||||||
| -   The request content-type is either 'text/javascript' or 'application/json' |  | ||||||
| -   The request 'Accept' header contains 'text/javascript' or 'application/json' |  | ||||||
| -   A trailing '.json' is added to the path |  | ||||||
| -   A 'wsmeproto=restjson' is added in the query string |  | ||||||
|  |  | ||||||
| Options |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| :nest_result: Nest the encoded result in a result param of an object. |  | ||||||
|               For example, a result of ``2`` would be ``{'result': 2}`` |  | ||||||
|  |  | ||||||
| Types |  | ||||||
| ~~~~~ |  | ||||||
|  |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | Type          | Json type                     | |  | ||||||
| +===============+===============================+ |  | ||||||
| | ``str``       | String                        | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``unicode``   | String                        | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``int``       | Number                        | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``float``     | Number                        | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``bool``      | Boolean                       | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``Decimal``   | String                        | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``date``      | String (YYYY-MM-DD)           | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``time``      | String (hh:mm:ss)             | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | ``datetime``  | String (YYYY-MM-DDThh:mm:ss)  | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | Arrays        | Array                         | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | None          | null                          | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
| | Complex types | Object                        | |  | ||||||
| +---------------+-------------------------------+ |  | ||||||
|  |  | ||||||
| Return |  | ||||||
| ~~~~~~ |  | ||||||
|  |  | ||||||
| The Json encoded result when the response code is 200, or a Json object |  | ||||||
| with error properties ('faulcode', 'faultstring' and 'debuginfo' if |  | ||||||
| available) on error. |  | ||||||
|  |  | ||||||
| For example, the '/ws/person/get' result looks like: |  | ||||||
|  |  | ||||||
| .. code-block:: javascript |  | ||||||
|  |  | ||||||
|     { |  | ||||||
|         'id': 2 |  | ||||||
|         'fistname': 'Monica', |  | ||||||
|         'lastname': 'Geller', |  | ||||||
|         'age': 28, |  | ||||||
|         'hobbies': [ |  | ||||||
|             'Food', |  | ||||||
|             'Cleaning' |  | ||||||
|         ] |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| And in case of error: |  | ||||||
|  |  | ||||||
| .. code-block:: javascript |  | ||||||
|      |  | ||||||
|     { |  | ||||||
|         'faultcode': 'Client', |  | ||||||
|         'faultstring': 'id is missing' |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| REST+XML |  | ||||||
| -------- |  | ||||||
|  |  | ||||||
| :name: ``'restxml'`` |  | ||||||
|  |  | ||||||
| This protocol is selected if |  | ||||||
|  |  | ||||||
| -   The request content-type is 'text/xml' |  | ||||||
| -   The request 'Accept' header contains 'text/xml' |  | ||||||
| -   A trailing '.xml' is added to the path |  | ||||||
| -   A 'wsmeproto=restxml' is added in the query string |  | ||||||
|  |  | ||||||
| Types |  | ||||||
| ~~~~~ |  | ||||||
|  |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | Type          | XML example                            | |  | ||||||
| +===============+========================================+ |  | ||||||
| | ``str``       | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>a string</value>            | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``unicode``   | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>a string</value>            | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``int``       | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>5</value>                   | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``float``     | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>3.14</value>                | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``bool``      | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>true</value>                | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``Decimal``   | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>5.46</value>                | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``date``      | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>2010-04-27</value>          | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``time``      | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>12:54:18</value>            | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | ``datetime``  | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>2010-04-27T12:54:18</value> | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | Arrays        | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>                            | |  | ||||||
| |               |         <item>Dinausaurs<item>         | |  | ||||||
| |               |         <item>Rachel<item>             | |  | ||||||
| |               |     </value>                           | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | None          | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value nil="true"/>                | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
| | Complex types | .. code-block:: xml                    | |  | ||||||
| |               |                                        | |  | ||||||
| |               |     <value>                            | |  | ||||||
| |               |         <id>1</id>                     | |  | ||||||
| |               |         <fistname>Ross</fistname>      | |  | ||||||
| |               |     </value>                           | |  | ||||||
| +---------------+----------------------------------------+ |  | ||||||
|  |  | ||||||
| Return |  | ||||||
| ~~~~~~ |  | ||||||
|  |  | ||||||
| A xml tree with a top 'result' element. |  | ||||||
|  |  | ||||||
| .. code-block:: xml |  | ||||||
|  |  | ||||||
|     <result> |  | ||||||
|         <id>1</id> |  | ||||||
|         <firstname>Ross</firstname> |  | ||||||
|         <lastname>Geller</lastname> |  | ||||||
|     </result> |  | ||||||
|  |  | ||||||
| Errors |  | ||||||
| ~~~~~~ |  | ||||||
|  |  | ||||||
| A xml tree with a top 'error' element, having 'faultcode', 'faultstring' |  | ||||||
| and 'debuginfo' subelements: |  | ||||||
|  |  | ||||||
| .. code-block:: xml |  | ||||||
|  |  | ||||||
|     <error> |  | ||||||
|         <faultcode>Client</faultcode> |  | ||||||
|         <faultstring>id is missing</faultstring> |  | ||||||
|     </error> |  | ||||||
|  |  | ||||||
| SOAP |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| :name: ``'soap'`` |  | ||||||
|  |  | ||||||
| Implements the SOAP protocol. |  | ||||||
|  |  | ||||||
| A wsdl definition of the webservice is available at the 'api.wsdl' subpath. |  | ||||||
| (``/ws/api.wsdl`` in our example). |  | ||||||
|  |  | ||||||
| The protocol is selected if the request matches one of the following condition: |  | ||||||
|  |  | ||||||
| -   The Content-Type is 'application/soap+xml' |  | ||||||
| -   A header 'Soapaction' is present |  | ||||||
|  |  | ||||||
| Options |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| :tns: Type namespace |  | ||||||
|  |  | ||||||
| ExtDirect |  | ||||||
| --------- |  | ||||||
|  |  | ||||||
| :name: ``extdirect`` |  | ||||||
|  |  | ||||||
| Implements the `Ext Direct`_ protocol. |  | ||||||
|  |  | ||||||
| The provider definition is made available at the ``/extdirect/api.js`` subpath. |  | ||||||
|  |  | ||||||
| The router url is ``/extdirect/router[/subnamespace]``. |  | ||||||
|  |  | ||||||
| Options |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| :namespace: Base namespace of the api. Used for the provider definition. |  | ||||||
| :params_notation: Default notation for function call parameters. Can be |  | ||||||
|     overriden for individual functions by adding the |  | ||||||
|     ``extdirect_params_notation`` extra option to @expose. |  | ||||||
|  |  | ||||||
|     The possible notations are : |  | ||||||
|  |  | ||||||
|     -   ``'named'``  -- The function will take only one object parameter |  | ||||||
|         in which each property will be one of the parameters. |  | ||||||
|     -   ``'positional'`` -- The function will take as many parameters as |  | ||||||
|         the function has, and their position will determine which parameter |  | ||||||
|         they are. |  | ||||||
|  |  | ||||||
| expose extra options |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| :extdirect_params_notation: Override the params_notation for a particular |  | ||||||
|     function. |  | ||||||
|  |  | ||||||
| .. _Ext Direct: http://www.sencha.com/products/extjs/extdirect |  | ||||||
|  |  | ||||||
| .. _protocols-the-example: |  | ||||||
|  |  | ||||||
| The example |  | ||||||
| ----------- |  | ||||||
|  |  | ||||||
| In this document the same webservice example will be used to |  | ||||||
| illustrate the different protocols: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     class Person(object): |  | ||||||
|         id = int |  | ||||||
|         lastname = unicode |  | ||||||
|         firstname = unicode |  | ||||||
|         age = int |  | ||||||
|  |  | ||||||
|         hobbies = [unicode] |  | ||||||
|  |  | ||||||
|         def __init__(self, id=None, lastname=None, firstname=None, age=None, |  | ||||||
|                     hobbies=None): |  | ||||||
|             if id: |  | ||||||
|                 self.id = id |  | ||||||
|             if lastname: |  | ||||||
|                 self.lastname = lastname |  | ||||||
|             if firstname: |  | ||||||
|                 self.firstname = firstname |  | ||||||
|             if age: |  | ||||||
|                 self.age = age |  | ||||||
|             if hobbies: |  | ||||||
|                 self.hobbies = hobbies |  | ||||||
|  |  | ||||||
|     persons = { |  | ||||||
|         1: Person(1, "Geller", "Ross", 30, ["Dinosaurs", "Rachel"]), |  | ||||||
|         2: Person(2, "Geller", "Monica", 28, ["Food", "Cleaning"]) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     class PersonController(object): |  | ||||||
|         @expose(Person) |  | ||||||
|         @validate(int) |  | ||||||
|         def get(self, id): |  | ||||||
|             return persons[id] |  | ||||||
|  |  | ||||||
|         @expose([Person]) |  | ||||||
|         def list(self): |  | ||||||
|             return persons.values() |  | ||||||
|  |  | ||||||
|         @expose(Person) |  | ||||||
|         @validate(Person) |  | ||||||
|         def update(self, p): |  | ||||||
|             if p.id is Unset: |  | ||||||
|                 raise ClientSideError("id is missing") |  | ||||||
|             persons[p.id] = p |  | ||||||
|             return p |  | ||||||
|  |  | ||||||
|         @expose(Person) |  | ||||||
|         @validate(Person) |  | ||||||
|         def create(self, p): |  | ||||||
|             if p.id is not Unset: |  | ||||||
|                 raise ClientSideError("I don't want an id") |  | ||||||
|             p.id = max(persons.keys()) + 1 |  | ||||||
|             persons[p.id] = p |  | ||||||
|             return p |  | ||||||
|  |  | ||||||
|         @expose() |  | ||||||
|         @validate(int) |  | ||||||
|         def destroy(self, id): |  | ||||||
|             if id not in persons: |  | ||||||
|                 raise ClientSideError("Unknown ID") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     class WS(WSRoot): |  | ||||||
|         person = PersonController() |  | ||||||
|  |  | ||||||
|     root = WS(webpath='ws') |  | ||||||
|  |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| sphinx |  | ||||||
| cloud_sptheme |  | ||||||
| -r ../requirements.txt |  | ||||||
							
								
								
									
										31
									
								
								doc/todo.rst
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								doc/todo.rst
									
									
									
									
									
								
							| @@ -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). |  | ||||||
							
								
								
									
										246
									
								
								doc/types.rst
									
									
									
									
									
								
							
							
						
						
									
										246
									
								
								doc/types.rst
									
									
									
									
									
								
							| @@ -1,246 +0,0 @@ | |||||||
| Types |  | ||||||
| ===== |  | ||||||
|  |  | ||||||
| Three kinds of data types can be used as input or output by WSME. |  | ||||||
|  |  | ||||||
| Native types |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| The native types are a fixed set of standard Python types that |  | ||||||
| different protocols map to their own basic types. |  | ||||||
|  |  | ||||||
| The native types are : |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: bytes |  | ||||||
|      |  | ||||||
|             A pure-ascii string (:py:class:`wsme.types.bytes` which is |  | ||||||
|             :py:class:`str` in Python 2 and :py:class:`bytes` in Python 3). |  | ||||||
|              |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: text |  | ||||||
|  |  | ||||||
|             A unicode string (:py:class:`wsme.types.text` which is |  | ||||||
|             :py:class:`unicode` in Python 2 and :py:class:`str` in Python 3). |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: int |  | ||||||
|      |  | ||||||
|             An integer (:py:class:`int`) |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: float |  | ||||||
|      |  | ||||||
|             A float (:py:class:`float`) |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: bool |  | ||||||
|      |  | ||||||
|             A boolean (:py:class:`bool`) |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: Decimal |  | ||||||
|      |  | ||||||
|             A fixed-width decimal (:py:class:`decimal.Decimal`) |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: date |  | ||||||
|              |  | ||||||
|             A date (:py:class:`datetime.date`) |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: datetime |  | ||||||
|  |  | ||||||
|             A date and time (:py:class:`datetime.datetime`) |  | ||||||
|  |  | ||||||
|     -   .. wsme:type:: time |  | ||||||
|      |  | ||||||
|             A time (:py:class:`datetime.time`) |  | ||||||
|  |  | ||||||
|     -   Arrays -- This is a special case. When stating a list |  | ||||||
|         datatype, always state its content type as the unique element |  | ||||||
|         of a list. Example:: |  | ||||||
|  |  | ||||||
|             class SomeWebService(object): |  | ||||||
|                 @expose([str]) |  | ||||||
|                 def getlist(self): |  | ||||||
|                     return ['a', 'b', 'c'] |  | ||||||
|  |  | ||||||
|     -   Dictionaries -- Statically typed mappings are allowed. When exposing |  | ||||||
|         a dictionary datatype, you can specify the key and value types, |  | ||||||
|         with a restriction on the key value that must be a 'pod' type. |  | ||||||
|         Example:: |  | ||||||
|  |  | ||||||
|             class SomeType(object): |  | ||||||
|                 amap = {str: SomeOthertype} |  | ||||||
|  |  | ||||||
| There are other types that are supported out of the box.  See the |  | ||||||
| :ref:`pre-defined-user-types`. |  | ||||||
|  |  | ||||||
| User types |  | ||||||
| ---------- |  | ||||||
|  |  | ||||||
| User types allow you to define new, almost-native types. |  | ||||||
|  |  | ||||||
| The idea is that you may have Python data that should be transported as base |  | ||||||
| types by the different protocols, but needs conversion to/from these base types, |  | ||||||
| or needs to validate data integrity. |  | ||||||
|  |  | ||||||
| To define a user type, you just have to inherit from |  | ||||||
| :class:`wsme.types.UserType` and instantiate your new class. This instance |  | ||||||
| will be your new type and can be used as @\ :class:`wsme.expose` or |  | ||||||
| @\ :class:`wsme.validate` parameters. |  | ||||||
|  |  | ||||||
| Note that protocols can choose to specifically handle a user type or |  | ||||||
| a base class of user types. This is case with the two pre-defined |  | ||||||
| user types, :class:`wsme.types.Enum` and :data:`wsme.types.binary`. |  | ||||||
|  |  | ||||||
| .. _pre-defined-user-types: |  | ||||||
|  |  | ||||||
| Pre-defined user types |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| WSME provides some pre-defined user types: |  | ||||||
|  |  | ||||||
| -   :class:`binary <wsme.types.binary>` -- for transporting binary data as |  | ||||||
|     base64 strings. |  | ||||||
| -   :class:`Enum <wsme.types.Enum>` -- enforce that the values belongs to a |  | ||||||
|     pre-defined list of values. |  | ||||||
|  |  | ||||||
| These types are good examples of how to define user types. Have |  | ||||||
| a look at their source code! |  | ||||||
|  |  | ||||||
| Here is a little example that combines :class:`binary <wsme.types.binary>` |  | ||||||
| and :class:`Enum <wsme.types.Enum>`:: |  | ||||||
|      |  | ||||||
|     ImageKind = Enum(str, 'jpeg', 'gif') |  | ||||||
|  |  | ||||||
|     class Image(object): |  | ||||||
|         name = unicode |  | ||||||
|         kind = ImageKind |  | ||||||
|         data = binary |  | ||||||
|  |  | ||||||
| .. data:: wsme.types.binary |  | ||||||
|  |  | ||||||
|     The :class:`wsme.types.BinaryType` instance to use when you need to |  | ||||||
|     transfer base64 encoded data. |  | ||||||
|  |  | ||||||
| .. autoclass:: wsme.types.BinaryType |  | ||||||
|  |  | ||||||
| .. autoclass:: wsme.types.Enum |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Complex types |  | ||||||
| ------------- |  | ||||||
|  |  | ||||||
| Complex types are structured types. They are defined as simple Python classes |  | ||||||
| and will be mapped to adequate structured types in the various protocols. |  | ||||||
|  |  | ||||||
| A base class for structured types is provided, :class:`wsme.types.Base`, |  | ||||||
| but is not mandatory. The only thing it adds is a default constructor. |  | ||||||
|  |  | ||||||
| The attributes that are set at the class level will be used by WSME to discover |  | ||||||
| the structure. These attributes can be: |  | ||||||
|  |  | ||||||
|     -   A datatype -- Any native, user or complex type. |  | ||||||
|     -   A :class:`wsattr <wsme.wsattr>` -- This allows you to add more information about |  | ||||||
|         the attribute, for example if it is mandatory. |  | ||||||
|     -   A :class:`wsproperty <wsme.wsproperty>` -- A special typed property. Works |  | ||||||
|         like standard ``property`` with additional properties like |  | ||||||
|         :class:`wsattr <wsme.wsattr>`. |  | ||||||
|  |  | ||||||
| Attributes having a leading '_' in their name will be ignored, as well as the |  | ||||||
| attributes that are not in the above list.  This means the type can have methods, |  | ||||||
| they will not get in the way. |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| :: |  | ||||||
|  |  | ||||||
|     Gender = wsme.types.Enum(str, 'male', 'female') |  | ||||||
|     Title = wsme.types.Enum(str, 'M', 'Mrs') |  | ||||||
|  |  | ||||||
|     class Person(wsme.types.Base): |  | ||||||
|         lastname = wsme.types.wsattr(unicode, mandatory=True) |  | ||||||
|         firstname = wsme.types.wsattr(unicode, mandatory=True) |  | ||||||
|  |  | ||||||
|         age = int |  | ||||||
|         gender = Gender |  | ||||||
|         title = Title |  | ||||||
|  |  | ||||||
|         hobbies = [unicode] |  | ||||||
|  |  | ||||||
| Rules |  | ||||||
| ~~~~~ |  | ||||||
|  |  | ||||||
| A few things you should know about complex types: |  | ||||||
|  |  | ||||||
|     -   The class must have a default constructor -- |  | ||||||
|         Since instances of the type will be created by the protocols when |  | ||||||
|         used as input types, they must be instantiable without any argument. |  | ||||||
|  |  | ||||||
|     -   Complex types are registered automatically  |  | ||||||
|         (and thus inspected) as soon a they are used in expose or validate, |  | ||||||
|         even if they are nested in another complex type. |  | ||||||
|  |  | ||||||
|         If for some reason you need to control when type is inspected, you |  | ||||||
|         can use :func:`wsme.types.register_type`. |  | ||||||
|  |  | ||||||
|     -   The datatype attributes will be replaced. |  | ||||||
|  |  | ||||||
|         When using the 'short' way of defining attributes, ie setting a  |  | ||||||
|         simple data type, they will be replaced by a wsattr instance. |  | ||||||
|  |  | ||||||
|         So, when you write:: |  | ||||||
|  |  | ||||||
|             class Person(object): |  | ||||||
|                 name = unicode |  | ||||||
|  |  | ||||||
|         After type registration the class will actually be equivalent to:: |  | ||||||
|  |  | ||||||
|             class Person(object): |  | ||||||
|                 name = wsattr(unicode) |  | ||||||
|  |  | ||||||
|         You can still access the datatype by accessing the attribute on the |  | ||||||
|         class, along with the other wsattr properties:: |  | ||||||
|  |  | ||||||
|             class Person(object): |  | ||||||
|                 name = unicode |  | ||||||
|  |  | ||||||
|             register_type(Person) |  | ||||||
|  |  | ||||||
|             assert Person.name.datatype is unicode |  | ||||||
|             assert Person.name.key == "name" |  | ||||||
|             assert Person.name.mandatory is False |  | ||||||
|  |  | ||||||
|     -   The default value of instance attributes is |  | ||||||
|         :data:`Unset <wsme.Unset>`. |  | ||||||
|  |  | ||||||
|         :: |  | ||||||
|  |  | ||||||
|             class Person(object): |  | ||||||
|                 name = wsattr(unicode) |  | ||||||
|  |  | ||||||
|             p = Person() |  | ||||||
|             assert p.name is Unset |  | ||||||
|  |  | ||||||
|         This allows the protocol to make a clear distinction between null values |  | ||||||
|         that will be transmitted, and unset values that will not be transmitted. |  | ||||||
|  |  | ||||||
|         For input values, it allows the code to know if the values were, or were not, |  | ||||||
|         sent by the caller. |  | ||||||
|  |  | ||||||
|     -   When 2 complex types refer to each other, their names can be |  | ||||||
|         used as datatypes to avoid adding attributes afterwards: |  | ||||||
|  |  | ||||||
|         :: |  | ||||||
|  |  | ||||||
|             class A(object): |  | ||||||
|                 b = wsattr('B') |  | ||||||
|  |  | ||||||
|             class B(object): |  | ||||||
|                 a = wsattr(A) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Predefined Types |  | ||||||
| ~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. default-domain:: wsme |  | ||||||
|  |  | ||||||
| -   .. autotype:: wsme.types.File |  | ||||||
|         :members: |  | ||||||
|  |  | ||||||
| @@ -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) |  | ||||||
|  |  | ||||||
| @@ -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() |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| [easy_install] |  | ||||||
| find_links = http://www.owlfish.com/software/wsgiutils/download.html |  | ||||||
| @@ -1,9 +0,0 @@ | |||||||
| from setuptools import setup |  | ||||||
|  |  | ||||||
| setup(name='demo', |  | ||||||
|     install_requires=[ |  | ||||||
|         'WSME', |  | ||||||
|         'Bottle', |  | ||||||
|         'Pygments', |  | ||||||
|     ], |  | ||||||
|     package=['demo']) |  | ||||||
| @@ -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 |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| six>=1.9.0 |  | ||||||
| WebOb>=1.2.3 |  | ||||||
| simplegeneric |  | ||||||
| pytz |  | ||||||
| netaddr>=0.7.12 |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| six>=1.9.0 |  | ||||||
| WebOb>=1.2.3 |  | ||||||
| simplegeneric |  | ||||||
| pytz |  | ||||||
| netaddr>=0.7.12 |  | ||||||
							
								
								
									
										51
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								setup.cfg
									
									
									
									
									
								
							| @@ -1,51 +0,0 @@ | |||||||
| [metadata] |  | ||||||
| name = WSME |  | ||||||
|  |  | ||||||
| author = Christophe de Vienne |  | ||||||
| author-email = python-wsme@googlegroups.com |  | ||||||
|  |  | ||||||
| summary = Simplify the writing of REST APIs, and extend them with additional protocols. |  | ||||||
|  |  | ||||||
| description-file = README.rst |  | ||||||
|  |  | ||||||
| url = http://git.openstack.org/cgit/openstack/wsme |  | ||||||
|  |  | ||||||
| license = MIT |  | ||||||
|  |  | ||||||
| classifier = |  | ||||||
|         Development Status :: 3 - Alpha |  | ||||||
|         Operating System :: OS Independent |  | ||||||
|         Programming Language :: Python |  | ||||||
|         Programming Language :: Python :: 2.7 |  | ||||||
|         Programming Language :: Python :: 3.4 |  | ||||||
|         Programming Language :: Python :: Implementation :: CPython |  | ||||||
|         Programming Language :: Python :: Implementation :: PyPy |  | ||||||
|         License :: OSI Approved :: MIT License |  | ||||||
|         Topic :: Internet :: WWW/HTTP :: WSGI |  | ||||||
|         Topic :: Software Development :: Libraries :: Python Modules |  | ||||||
|  |  | ||||||
| [entry_points] |  | ||||||
| wsme.protocols = |  | ||||||
|     rest = wsme.rest.protocol:RestProtocol |  | ||||||
|     restjson = wsme.rest.protocol:RestProtocol |  | ||||||
|     restxml = wsme.rest.protocol:RestProtocol |  | ||||||
|     soap = wsmeext.soap:SoapProtocol |  | ||||||
|     extdirect = wsmeext.extdirect:ExtDirectProtocol |  | ||||||
|  |  | ||||||
| [files] |  | ||||||
| packages = |  | ||||||
|     wsme |  | ||||||
|     wsmeext |  | ||||||
|  |  | ||||||
| namespace_packages = |  | ||||||
|     wsmeext |  | ||||||
|  |  | ||||||
| extra_files = |  | ||||||
|     setup.py |  | ||||||
|     README.rst |  | ||||||
|     tests |  | ||||||
|  |  | ||||||
| [wheel] |  | ||||||
| # WSME has different requirements depending on the version of Python |  | ||||||
| # being used, so we cannot build universal wheels. |  | ||||||
| universal = 0 |  | ||||||
							
								
								
									
										6
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,6 +0,0 @@ | |||||||
| import setuptools |  | ||||||
|  |  | ||||||
| setuptools.setup( |  | ||||||
|     setup_requires=['pbr'], |  | ||||||
|     pbr=True |  | ||||||
| ) |  | ||||||
| @@ -1,6 +0,0 @@ | |||||||
| [nosetests] |  | ||||||
| match=^test |  | ||||||
| where=test |  | ||||||
| nocapture=1 |  | ||||||
| cover-package=test |  | ||||||
| cover-erase=1 |  | ||||||
| @@ -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']) |  | ||||||
| ) |  | ||||||
| @@ -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) |  | ||||||
|     ) |  | ||||||
| @@ -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 |  | ||||||
| @@ -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=[])] |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| def init_model(): |  | ||||||
|     pass |  | ||||||
| @@ -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) |  | ||||||
| @@ -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 |  | ||||||
| @@ -1,247 +0,0 @@ | |||||||
| from six.moves import http_client |  | ||||||
| from test.tests import FunctionalTest |  | ||||||
| import json |  | ||||||
| import pecan |  | ||||||
| import six |  | ||||||
|  |  | ||||||
|  |  | ||||||
| used_status_codes = [400, 401, 403, 404, 500] |  | ||||||
| http_response_messages = {} |  | ||||||
| for code in used_status_codes: |  | ||||||
|     http_response_messages[code] = '%s %s' % (code, http_client.responses[code]) |  | ||||||
|  |  | ||||||
| class TestWS(FunctionalTest): |  | ||||||
|  |  | ||||||
|     def test_get_all(self): |  | ||||||
|         self.app.get('/authors') |  | ||||||
|  |  | ||||||
|     def test_optional_array_param(self): |  | ||||||
|         r = self.app.get('/authors?q=a&q=b') |  | ||||||
|         l = json.loads(r.body.decode('utf-8')) |  | ||||||
|         assert len(l) == 2 |  | ||||||
|         assert l[0]['firstname'] == 'a' |  | ||||||
|         assert l[1]['firstname'] == 'b' |  | ||||||
|  |  | ||||||
|     def test_optional_indexed_array_param(self): |  | ||||||
|         r = self.app.get('/authors?q[0]=a&q[1]=b') |  | ||||||
|         l = json.loads(r.body.decode('utf-8')) |  | ||||||
|         assert len(l) == 2 |  | ||||||
|         assert l[0]['firstname'] == 'a' |  | ||||||
|         assert l[1]['firstname'] == 'b' |  | ||||||
|  |  | ||||||
|     def test_options_object_array_param(self): |  | ||||||
|         r = self.app.get('/authors?r.value=a&r.value=b') |  | ||||||
|         l = json.loads(r.body.decode('utf-8')) |  | ||||||
|         assert len(l) == 2 |  | ||||||
|         assert l[0]['firstname'] == 'a' |  | ||||||
|         assert l[1]['firstname'] == 'b' |  | ||||||
|  |  | ||||||
|     def test_options_indexed_object_array_param(self): |  | ||||||
|         r = self.app.get('/authors?r[0].value=a&r[1].value=b') |  | ||||||
|         l = json.loads(r.body.decode('utf-8')) |  | ||||||
|         assert len(l) == 2 |  | ||||||
|         assert l[0]['firstname'] == 'a' |  | ||||||
|         assert l[1]['firstname'] == 'b' |  | ||||||
|  |  | ||||||
|     def test_get_author(self): |  | ||||||
|         a = self.app.get( |  | ||||||
|             '/authors/1.json', |  | ||||||
|         ) |  | ||||||
|         a = json.loads(a.body.decode('utf-8')) |  | ||||||
|  |  | ||||||
|         assert a['id'] == 1 |  | ||||||
|         assert a['firstname'] == 'aname' |  | ||||||
|  |  | ||||||
|         a = self.app.get( |  | ||||||
|             '/authors/1.xml', |  | ||||||
|         ) |  | ||||||
|         body = a.body.decode('utf-8') |  | ||||||
|         assert '<id>1</id>' in body |  | ||||||
|         assert '<firstname>aname</firstname>' in body |  | ||||||
|  |  | ||||||
|     def test_post_body_parameter_validation(self): |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/authors', '{"firstname": "Robert"}', |  | ||||||
|             headers={"Content-Type": "application/json"}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         self.assertEqual(a['faultcode'], 'Client') |  | ||||||
|         self.assertEqual(a['faultstring'], "I don't like this author!") |  | ||||||
|  |  | ||||||
|     def test_post_body_parameter(self): |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/authors', '{"firstname": "test"}', |  | ||||||
|             headers={"Content-Type": "application/json"} |  | ||||||
|         ) |  | ||||||
|         assert res.status_int == 201 |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['id'] == 10 |  | ||||||
|         assert a['firstname'] == 'test' |  | ||||||
|  |  | ||||||
|     def test_put_parameter_validate(self): |  | ||||||
|         res = self.app.put( |  | ||||||
|             '/authors/foobar', '{"firstname": "test"}', |  | ||||||
|             headers={"Content-Type": "application/json"}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         self.assertEqual( |  | ||||||
|             a['faultstring'], |  | ||||||
|             "Invalid input for field/attribute author_id. " |  | ||||||
|             "Value: 'foobar'. unable to convert to int. Error: invalid " |  | ||||||
|             "literal for int() with base 10: 'foobar'") |  | ||||||
|  |  | ||||||
|     def test_clientsideerror(self): |  | ||||||
|         expected_status_code = 400 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/999.json', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['faultcode'] == 'Client' |  | ||||||
|  |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/999.xml', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8') |  | ||||||
|  |  | ||||||
|     def test_custom_clientside_error(self): |  | ||||||
|         expected_status_code = 404 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/998.json', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['faultcode'] == 'Client' |  | ||||||
|  |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/998.xml', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8') |  | ||||||
|  |  | ||||||
|     def test_custom_non_http_clientside_error(self): |  | ||||||
|         expected_status_code = 500 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/997.json', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['faultcode'] == 'Server' |  | ||||||
|  |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/997.xml', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8') |  | ||||||
|  |  | ||||||
|     def test_clientsideerror_status_code(self): |  | ||||||
|         expected_status_code = 403 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/996.json', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['faultcode'] == 'Client' |  | ||||||
|  |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/996.xml', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8') |  | ||||||
|  |  | ||||||
|     def test_non_default_response(self): |  | ||||||
|         expected_status_code = 401 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/911.json', |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status_int, expected_status_code) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|  |  | ||||||
|     def test_non_default_response_return_type(self): |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/913', |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status_int, 200) |  | ||||||
|         self.assertEqual(res.body, b'"foo"') |  | ||||||
|         self.assertEqual(res.content_length, 5) |  | ||||||
|  |  | ||||||
|     def test_non_default_response_return_type_no_content(self): |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/authors/912', |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(res.status_int, 204) |  | ||||||
|         self.assertEqual(res.body, b'') |  | ||||||
|         self.assertEqual(res.content_length, 0) |  | ||||||
|  |  | ||||||
|     def test_serversideerror(self): |  | ||||||
|         expected_status_code = 500 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         res = self.app.get('/divide_by_zero.json', expect_errors=True) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['faultcode'] == 'Server' |  | ||||||
|         assert a['debuginfo'] is None |  | ||||||
|  |  | ||||||
|     def test_serversideerror_with_debug(self): |  | ||||||
|         expected_status_code = 500 |  | ||||||
|         expected_status = http_response_messages[expected_status_code] |  | ||||||
|         pecan.set_config({'wsme': {'debug': True}}) |  | ||||||
|         res = self.app.get('/divide_by_zero.json', expect_errors=True) |  | ||||||
|         self.assertEqual(res.status, expected_status) |  | ||||||
|         a = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert a['faultcode'] == 'Server' |  | ||||||
|         assert a['debuginfo'].startswith('Traceback (most recent call last):') |  | ||||||
|  |  | ||||||
|     def test_json_only(self): |  | ||||||
|         res = self.app.get('/authors/json_only.json') |  | ||||||
|         assert res.status_int == 200 |  | ||||||
|         body = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert len(body) == 1 |  | ||||||
|         assert body[0]['firstname'] == u"aname" |  | ||||||
|         assert body[0]['books'] == [] |  | ||||||
|         assert body[0]['id'] == 1 |  | ||||||
|         res = self.app.get('/authors/json_only.xml', expect_errors=True) |  | ||||||
|  |  | ||||||
|     def test_xml_only(self): |  | ||||||
|         res = self.app.get('/authors/xml_only.xml') |  | ||||||
|         assert res.status_int == 200 |  | ||||||
|         assert '<id>1</id>' in res.body.decode('utf-8') |  | ||||||
|         assert '<firstname>aname</firstname>' in res.body.decode('utf-8') |  | ||||||
|         assert '<books />' in res.body.decode('utf-8') |  | ||||||
|         res = self.app.get('/authors/xml_only.json', expect_errors=True) |  | ||||||
|  |  | ||||||
|     def test_body_parameter(self): |  | ||||||
|         res = self.app.put( |  | ||||||
|             '/authors/1/books/2.json', |  | ||||||
|             '{"name": "Alice au pays des merveilles"}', |  | ||||||
|             headers={"Content-Type": "application/json"} |  | ||||||
|         ) |  | ||||||
|         book = json.loads(res.body.decode('utf-8')) |  | ||||||
|         assert book['id'] == 2 |  | ||||||
|         assert book['author']['id'] == 1 |  | ||||||
|  |  | ||||||
|     def test_no_content_type_if_no_return_type(self): |  | ||||||
|         if six.PY3: |  | ||||||
|             self.skipTest( |  | ||||||
|                 "This test does not work in Python 3 until https://review.openstack.org/#/c/48439/ is merged") |  | ||||||
|         res = self.app.delete('/authors/4') |  | ||||||
|         assert "Content-Type" not in res.headers, res.headers['Content-Type'] |  | ||||||
| @@ -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, ((), {})) |  | ||||||
| @@ -1,232 +0,0 @@ | |||||||
| # -*- coding: utf-8 -*- |  | ||||||
| # |  | ||||||
| # Web Services Made Easy documentation build configuration file, created by |  | ||||||
| # sphinx-quickstart on Sun Oct  2 20:27:45 2011. |  | ||||||
| # |  | ||||||
| # This file is execfile()d with the current directory set to its containing dir. |  | ||||||
| # |  | ||||||
| # Note that not all possible configuration values are present in this |  | ||||||
| # autogenerated file. |  | ||||||
| # |  | ||||||
| # All configuration values have a default; values that are commented out |  | ||||||
| # serve to show the default. |  | ||||||
|  |  | ||||||
| import sys, os |  | ||||||
|  |  | ||||||
| # If extensions (or modules to document with autodoc) are in another directory, |  | ||||||
| # add these directories to sys.path here. If the directory is relative to the |  | ||||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. |  | ||||||
| sys.path.insert(0, os.path.abspath('..')) |  | ||||||
|  |  | ||||||
| # -- General configuration ----------------------------------------------------- |  | ||||||
|  |  | ||||||
| # If your documentation needs a minimal Sphinx version, state it here. |  | ||||||
| #needs_sphinx = '1.0' |  | ||||||
|  |  | ||||||
| # Add any Sphinx extension module names here, as strings. They can be extensions |  | ||||||
| # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |  | ||||||
| extensions = ['sphinx.ext.autodoc', 'wsmeext.sphinxext'] |  | ||||||
|  |  | ||||||
| # Add any paths that contain templates here, relative to this directory. |  | ||||||
| templates_path = ['_templates'] |  | ||||||
|  |  | ||||||
| # The suffix of source filenames. |  | ||||||
| source_suffix = '.rst' |  | ||||||
|  |  | ||||||
| # The encoding of source files. |  | ||||||
| #source_encoding = 'utf-8-sig' |  | ||||||
|  |  | ||||||
| # The master toctree document. |  | ||||||
| master_doc = 'index' |  | ||||||
|  |  | ||||||
| # General information about the project. |  | ||||||
| project = u'wsmeext.sphinxext Test' |  | ||||||
| copyright = u'2011, Christophe de Vienne' |  | ||||||
|  |  | ||||||
| # The version info for the project you're documenting, acts as replacement for |  | ||||||
| # |version| and |release|, also used in various other places throughout the |  | ||||||
| # built documents. |  | ||||||
| # |  | ||||||
| import pkg_resources |  | ||||||
| dist = pkg_resources.require('WSME')[0] |  | ||||||
|  |  | ||||||
| # The short X.Y version. |  | ||||||
| version = '.'.join(dist.version[:2]) |  | ||||||
| # The full version, including alpha/beta/rc tags. |  | ||||||
| release = dist.version |  | ||||||
|  |  | ||||||
| # The language for content autogenerated by Sphinx. Refer to documentation |  | ||||||
| # for a list of supported languages. |  | ||||||
| #language = None |  | ||||||
|  |  | ||||||
| # There are two options for replacing |today|: either, you set today to some |  | ||||||
| # non-false value, then it is used: |  | ||||||
| #today = '' |  | ||||||
| # Else, today_fmt is used as the format for a strftime call. |  | ||||||
| #today_fmt = '%B %d, %Y' |  | ||||||
|  |  | ||||||
| # List of patterns, relative to source directory, that match files and |  | ||||||
| # directories to ignore when looking for source files. |  | ||||||
| exclude_patterns = ['_build'] |  | ||||||
|  |  | ||||||
| # The reST default role (used for this markup: `text`) to use for all documents. |  | ||||||
| #default_role = None |  | ||||||
|  |  | ||||||
| # If true, '()' will be appended to :func: etc. cross-reference text. |  | ||||||
| #add_function_parentheses = True |  | ||||||
|  |  | ||||||
| # If true, the current module name will be prepended to all description |  | ||||||
| # unit titles (such as .. function::). |  | ||||||
| #add_module_names = True |  | ||||||
|  |  | ||||||
| # If true, sectionauthor and moduleauthor directives will be shown in the |  | ||||||
| # output. They are ignored by default. |  | ||||||
| #show_authors = False |  | ||||||
|  |  | ||||||
| # The name of the Pygments (syntax highlighting) style to use. |  | ||||||
| pygments_style = 'sphinx' |  | ||||||
|  |  | ||||||
| # A list of ignored prefixes for module index sorting. |  | ||||||
| #modindex_common_prefix = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for HTML output --------------------------------------------------- |  | ||||||
|  |  | ||||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for |  | ||||||
| # a list of builtin themes. |  | ||||||
| html_theme = 'agogo' |  | ||||||
| html_theme_options = { |  | ||||||
|     "pagewidth": "60em", |  | ||||||
|     "documentwidth": "40em", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| html_style = 'wsme.css' |  | ||||||
|  |  | ||||||
| # Theme options are theme-specific and customize the look and feel of a theme |  | ||||||
| # further.  For a list of options available for each theme, see the |  | ||||||
| # documentation. |  | ||||||
| #html_theme_options = {} |  | ||||||
|  |  | ||||||
| # Add any paths that contain custom themes here, relative to this directory. |  | ||||||
| #html_theme_path = [] |  | ||||||
|  |  | ||||||
| # The name for this set of Sphinx documents.  If None, it defaults to |  | ||||||
| # "<project> v<release> documentation". |  | ||||||
| html_title = "WSME %s" % release |  | ||||||
|  |  | ||||||
| # A shorter title for the navigation bar.  Default is the same as html_title. |  | ||||||
| #html_short_title = None |  | ||||||
|  |  | ||||||
| # The name of an image file (relative to this directory) to place at the top |  | ||||||
| # of the sidebar. |  | ||||||
| #html_logo = None |  | ||||||
|  |  | ||||||
| # The name of an image file (within the static path) to use as favicon of the |  | ||||||
| # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 |  | ||||||
| # pixels large. |  | ||||||
| #html_favicon = None |  | ||||||
|  |  | ||||||
| # Add any paths that contain custom static files (such as style sheets) here, |  | ||||||
| # relative to this directory. They are copied after the builtin static files, |  | ||||||
| # so a file named "default.css" will overwrite the builtin "default.css". |  | ||||||
| #html_static_path = ['_static'] |  | ||||||
|  |  | ||||||
| # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |  | ||||||
| # using the given strftime format. |  | ||||||
| #html_last_updated_fmt = '%b %d, %Y' |  | ||||||
|  |  | ||||||
| # If true, SmartyPants will be used to convert quotes and dashes to |  | ||||||
| # typographically correct entities. |  | ||||||
| #html_use_smartypants = True |  | ||||||
|  |  | ||||||
| # Custom sidebar templates, maps document names to template names. |  | ||||||
| #html_sidebars = {} |  | ||||||
|  |  | ||||||
| # Additional templates that should be rendered to pages, maps page names to |  | ||||||
| # template names. |  | ||||||
| #html_additional_pages = {} |  | ||||||
|  |  | ||||||
| # If false, no module index is generated. |  | ||||||
| #html_domain_indices = True |  | ||||||
|  |  | ||||||
| # If false, no index is generated. |  | ||||||
| #html_use_index = True |  | ||||||
|  |  | ||||||
| # If true, the index is split into individual pages for each letter. |  | ||||||
| #html_split_index = False |  | ||||||
|  |  | ||||||
| # If true, links to the reST sources are added to the pages. |  | ||||||
| #html_show_sourcelink = True |  | ||||||
|  |  | ||||||
| # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. |  | ||||||
| #html_show_sphinx = True |  | ||||||
|  |  | ||||||
| # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. |  | ||||||
| #html_show_copyright = True |  | ||||||
|  |  | ||||||
| # If true, an OpenSearch description file will be output, and all pages will |  | ||||||
| # contain a <link> tag referring to it.  The value of this option must be the |  | ||||||
| # base URL from which the finished HTML is served. |  | ||||||
| #html_use_opensearch = '' |  | ||||||
|  |  | ||||||
| # This is the file name suffix for HTML files (e.g. ".xhtml"). |  | ||||||
| #html_file_suffix = None |  | ||||||
|  |  | ||||||
| # Output file base name for HTML help builder. |  | ||||||
| htmlhelp_basename = 'WebServicesMadeEasydoc' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for LaTeX output -------------------------------------------------- |  | ||||||
|  |  | ||||||
| # The paper size ('letter' or 'a4'). |  | ||||||
| #latex_paper_size = 'letter' |  | ||||||
|  |  | ||||||
| # The font size ('10pt', '11pt' or '12pt'). |  | ||||||
| #latex_font_size = '10pt' |  | ||||||
|  |  | ||||||
| # Grouping the document tree into LaTeX files. List of tuples |  | ||||||
| # (source start file, target name, title, author, documentclass [howto/manual]). |  | ||||||
| latex_documents = [ |  | ||||||
|   ('index', 'WebServicesMadeEasy.tex', u'Web Services Made Easy Documentation', |  | ||||||
|    u'Christophe de Vienne', 'manual'), |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| # The name of an image file (relative to this directory) to place at the top of |  | ||||||
| # the title page. |  | ||||||
| #latex_logo = None |  | ||||||
|  |  | ||||||
| # For "manual" documents, if this is true, then toplevel headings are parts, |  | ||||||
| # not chapters. |  | ||||||
| #latex_use_parts = False |  | ||||||
|  |  | ||||||
| # If true, show page references after internal links. |  | ||||||
| #latex_show_pagerefs = False |  | ||||||
|  |  | ||||||
| # If true, show URL addresses after external links. |  | ||||||
| #latex_show_urls = False |  | ||||||
|  |  | ||||||
| # Additional stuff for the LaTeX preamble. |  | ||||||
| #latex_preamble = '' |  | ||||||
|  |  | ||||||
| # Documents to append as an appendix to all manuals. |  | ||||||
| #latex_appendices = [] |  | ||||||
|  |  | ||||||
| # If false, no module index is generated. |  | ||||||
| #latex_domain_indices = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for manual page output -------------------------------------------- |  | ||||||
|  |  | ||||||
| # One entry per manual page. List of tuples |  | ||||||
| # (source start file, name, description, authors, manual section). |  | ||||||
| man_pages = [ |  | ||||||
|     ('index', 'webservicesmadeeasy', u'Web Services Made Easy Documentation', |  | ||||||
|      [u'Christophe de Vienne'], 1) |  | ||||||
| ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| autodoc_member_order = 'bysource' |  | ||||||
|  |  | ||||||
| wsme_protocols = [ |  | ||||||
|     'restjson', 'restxml' |  | ||||||
| ] |  | ||||||
| @@ -1,43 +0,0 @@ | |||||||
| API Documentation test |  | ||||||
| ====================== |  | ||||||
|  |  | ||||||
| Example |  | ||||||
| ~~~~~~~ |  | ||||||
|  |  | ||||||
| .. wsme:root:: wsmeext.sphinxext.SampleService |  | ||||||
|     :webpath: /api |  | ||||||
|  |  | ||||||
| .. wsme:type:: MyType |  | ||||||
|  |  | ||||||
|     .. wsme:attribute:: test |  | ||||||
|  |  | ||||||
|         :type: int |  | ||||||
|  |  | ||||||
| .. wsme:service:: name/space/SampleService |  | ||||||
|      |  | ||||||
|     .. wsme:function:: getType |  | ||||||
|          |  | ||||||
|         Returns a :wsme:type:`MyType <MyType>` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. default-domain:: wsme |  | ||||||
|  |  | ||||||
| .. type:: int |  | ||||||
|  |  | ||||||
|     An integer |  | ||||||
|  |  | ||||||
| .. autotype:: wsmeext.sphinxext.SampleType |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autoservice:: wsmeext.sphinxext.SampleService |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. autotype:: test_sphinxext.ASampleType |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autotype:: wsme.types.bytes |  | ||||||
|  |  | ||||||
| .. autotype:: wsme.types.text |  | ||||||
|  |  | ||||||
| .. _Sphinx: http://sphinx.pocoo.org/ |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| .. toctree:: |  | ||||||
|  |  | ||||||
|     document |  | ||||||
| @@ -1,183 +0,0 @@ | |||||||
| import unittest |  | ||||||
| import json |  | ||||||
|  |  | ||||||
| import webtest |  | ||||||
|  |  | ||||||
| from cornice import Service |  | ||||||
| from cornice import resource |  | ||||||
| from pyramid.config import Configurator |  | ||||||
| from pyramid.httpexceptions import HTTPUnauthorized |  | ||||||
|  |  | ||||||
| from wsme.types import text, Base, HostRequest |  | ||||||
| from wsmeext.cornice import signature |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class User(Base): |  | ||||||
|     id = int |  | ||||||
|     name = text |  | ||||||
|  |  | ||||||
| users = Service(name='users', path='/users') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @users.get() |  | ||||||
| @signature([User]) |  | ||||||
| def users_get(): |  | ||||||
|     return [User(id=1, name='first')] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @users.post() |  | ||||||
| @signature(User, body=User) |  | ||||||
| def users_create(data): |  | ||||||
|     data.id = 2 |  | ||||||
|     return data |  | ||||||
|  |  | ||||||
|  |  | ||||||
| secret = Service(name='secrets', path='/secret') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @secret.get() |  | ||||||
| @signature() |  | ||||||
| def secret_get(): |  | ||||||
|     raise HTTPUnauthorized() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| divide = Service(name='divide', path='/divide') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @divide.get() |  | ||||||
| @signature(int, int, int) |  | ||||||
| def do_divide(a, b): |  | ||||||
|     return a / b |  | ||||||
|  |  | ||||||
| needrequest = Service(name='needrequest', path='/needrequest') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @needrequest.get() |  | ||||||
| @signature(bool, HostRequest) |  | ||||||
| def needrequest_get(request): |  | ||||||
|     assert request.path == '/needrequest', request.path |  | ||||||
|     return True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Author(Base): |  | ||||||
|     authorId = int |  | ||||||
|     name = text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @resource.resource(collection_path='/author', path='/author/{authorId}') |  | ||||||
| class AuthorResource(object): |  | ||||||
|     def __init__(self, request): |  | ||||||
|         self.request = request |  | ||||||
|  |  | ||||||
|     @signature(Author, int) |  | ||||||
|     def get(self, authorId): |  | ||||||
|         return Author(authorId=authorId, name="Author %s" % authorId) |  | ||||||
|  |  | ||||||
|     @signature(Author, int, body=Author) |  | ||||||
|     def post(self, authorId, data): |  | ||||||
|         data.authorId = authorId |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|     @signature([Author], text) |  | ||||||
|     def collection_get(self, where=None): |  | ||||||
|         return [ |  | ||||||
|             Author(authorId=1, name="Author 1"), |  | ||||||
|             Author(authorId=2, name="Author 2"), |  | ||||||
|             Author(authorId=3, name="Author 3") |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def make_app(): |  | ||||||
|     config = Configurator() |  | ||||||
|     config.include("cornice") |  | ||||||
|     config.include("wsmeext.cornice") |  | ||||||
|     config.scan("test_cornice") |  | ||||||
|     return config.make_wsgi_app() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WSMECorniceTestCase(unittest.TestCase): |  | ||||||
|     def setUp(self): |  | ||||||
|         self.app = webtest.TestApp(make_app()) |  | ||||||
|  |  | ||||||
|     def test_get_json_list(self): |  | ||||||
|         resp = self.app.get('/users') |  | ||||||
|         self.assertEqual( |  | ||||||
|             resp.body, |  | ||||||
|             b'[{"id": 1, "name": "first"}]' |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def test_get_xml_list(self): |  | ||||||
|         resp = self.app.get('/users', headers={"Accept": "text/xml"}) |  | ||||||
|         self.assertEqual( |  | ||||||
|             resp.body, |  | ||||||
|            b'<result><item><id>1</id><name>first</name></item></result>' |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def test_post_json_data(self): |  | ||||||
|         data = json.dumps({"name": "new"}) |  | ||||||
|         resp = self.app.post( |  | ||||||
|             '/users', data, |  | ||||||
|             headers={"Content-Type": "application/json"} |  | ||||||
|         ) |  | ||||||
|         self.assertEqual( |  | ||||||
|             resp.body, |  | ||||||
|             b'{"id": 2, "name": "new"}' |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def test_post_xml_data(self): |  | ||||||
|         data = '<data><name>new</name></data>' |  | ||||||
|         resp = self.app.post( |  | ||||||
|             '/users', data, |  | ||||||
|             headers={"Content-Type": "text/xml"} |  | ||||||
|         ) |  | ||||||
|         self.assertEqual( |  | ||||||
|             resp.body, |  | ||||||
|             b'<result><id>2</id><name>new</name></result>' |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def test_pass_request(self): |  | ||||||
|         resp = self.app.get('/needrequest') |  | ||||||
|         assert resp.json is True |  | ||||||
|  |  | ||||||
|     def test_resource_collection_get(self): |  | ||||||
|         resp = self.app.get('/author') |  | ||||||
|         assert len(resp.json) == 3 |  | ||||||
|         assert resp.json[0]['name'] == 'Author 1' |  | ||||||
|         assert resp.json[1]['name'] == 'Author 2' |  | ||||||
|         assert resp.json[2]['name'] == 'Author 3' |  | ||||||
|  |  | ||||||
|     def test_resource_get(self): |  | ||||||
|         resp = self.app.get('/author/5') |  | ||||||
|         assert resp.json['name'] == 'Author 5' |  | ||||||
|  |  | ||||||
|     def test_resource_post(self): |  | ||||||
|         resp = self.app.post( |  | ||||||
|             '/author/5', |  | ||||||
|             json.dumps({"name": "Author 5"}), |  | ||||||
|             headers={"Content-Type": "application/json"} |  | ||||||
|         ) |  | ||||||
|         assert resp.json['authorId'] == 5 |  | ||||||
|         assert resp.json['name'] == 'Author 5' |  | ||||||
|  |  | ||||||
|     def test_server_error(self): |  | ||||||
|         resp = self.app.get('/divide?a=1&b=0', expect_errors=True) |  | ||||||
|         self.assertEqual(resp.json['faultcode'], 'Server') |  | ||||||
|         self.assertEqual(resp.status_code, 500) |  | ||||||
|  |  | ||||||
|     def test_client_error(self): |  | ||||||
|         resp = self.app.get( |  | ||||||
|             '/divide?a=1&c=0', |  | ||||||
|             headers={'Accept': 'application/json'}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(resp.json['faultcode'], 'Client') |  | ||||||
|         self.assertEqual(resp.status_code, 400) |  | ||||||
|  |  | ||||||
|     def test_runtime_error(self): |  | ||||||
|         resp = self.app.get( |  | ||||||
|             '/secret', |  | ||||||
|             headers={'Accept': 'application/json'}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(resp.json['faultcode'], 'Client') |  | ||||||
|         self.assertEqual(resp.status_code, 401) |  | ||||||
| @@ -1,208 +0,0 @@ | |||||||
| # encoding=utf8 |  | ||||||
| import unittest |  | ||||||
| from flask import Flask, json, abort |  | ||||||
| from flask.ext import restful |  | ||||||
|  |  | ||||||
| from wsmeext.flask import signature |  | ||||||
| from wsme.api import Response |  | ||||||
| from wsme.types import Base, text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Model(Base): |  | ||||||
|     id = int |  | ||||||
|     name = text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Criterion(Base): |  | ||||||
|     op = text |  | ||||||
|     attr = text |  | ||||||
|     value = text |  | ||||||
|  |  | ||||||
| test_app = Flask(__name__) |  | ||||||
| api = restful.Api(test_app) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/multiply') |  | ||||||
| @signature(int, int, int) |  | ||||||
| def multiply(a, b): |  | ||||||
|     return a * b |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/divide_by_zero') |  | ||||||
| @signature(None) |  | ||||||
| def divide_by_zero(): |  | ||||||
|     return 1 / 0 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/models') |  | ||||||
| @signature([Model], [Criterion]) |  | ||||||
| def list_models(q=None): |  | ||||||
|     if q: |  | ||||||
|         name = q[0].value |  | ||||||
|     else: |  | ||||||
|         name = 'first' |  | ||||||
|     return [Model(name=name)] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/models/<name>') |  | ||||||
| @signature(Model, text) |  | ||||||
| def get_model(name): |  | ||||||
|     return Model(name=name) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/models/<name>/secret') |  | ||||||
| @signature(Model, text) |  | ||||||
| def model_secret(name): |  | ||||||
|     abort(403) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/models/<name>/custom-error') |  | ||||||
| @signature(Model, text) |  | ||||||
| def model_custom_error(name): |  | ||||||
|     class CustomError(Exception): |  | ||||||
|         code = 412 |  | ||||||
|     raise CustomError("FOO!") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/models', methods=['POST']) |  | ||||||
| @signature(Model, body=Model) |  | ||||||
| def post_model(body): |  | ||||||
|     return Model(name=body.name) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/status_sig') |  | ||||||
| @signature(int, status_code=201) |  | ||||||
| def get_status_sig(): |  | ||||||
|     return 1 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @test_app.route('/status_response') |  | ||||||
| @signature(int) |  | ||||||
| def get_status_response(): |  | ||||||
|     return Response(1, status_code=201) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RestFullApi(restful.Resource): |  | ||||||
|     @signature(Model) |  | ||||||
|     def get(self): |  | ||||||
|         return Model(id=1, name=u"Gérard") |  | ||||||
|  |  | ||||||
|     @signature(int, body=Model) |  | ||||||
|     def post(self, model): |  | ||||||
|         return model.id |  | ||||||
|  |  | ||||||
| api.add_resource(RestFullApi, '/restful') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlaskrTestCase(unittest.TestCase): |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         test_app.config['TESTING'] = True |  | ||||||
|         self.app = test_app.test_client() |  | ||||||
|  |  | ||||||
|     def tearDown(self): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def test_multiply(self): |  | ||||||
|         r = self.app.get('/multiply?a=2&b=5') |  | ||||||
|         assert r.data == b'10', r.data |  | ||||||
|  |  | ||||||
|     def test_get_model(self): |  | ||||||
|         resp = self.app.get('/models/test') |  | ||||||
|         assert resp.status_code == 200 |  | ||||||
|  |  | ||||||
|     def test_list_models(self): |  | ||||||
|         resp = self.app.get('/models') |  | ||||||
|         assert resp.status_code == 200 |  | ||||||
|  |  | ||||||
|     def test_array_parameter(self): |  | ||||||
|         resp = self.app.get('/models?q.op=%3D&q.attr=name&q.value=second') |  | ||||||
|         assert resp.status_code == 200 |  | ||||||
|         self.assertEqual( |  | ||||||
|             resp.data, b'[{"name": "second"}]' |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def test_post_model(self): |  | ||||||
|         resp = self.app.post('/models', data={"body.name": "test"}) |  | ||||||
|         assert resp.status_code == 200 |  | ||||||
|         resp = self.app.post( |  | ||||||
|             '/models', |  | ||||||
|             data=json.dumps({"name": "test"}), |  | ||||||
|             content_type="application/json" |  | ||||||
|         ) |  | ||||||
|         assert resp.status_code == 200 |  | ||||||
|  |  | ||||||
|     def test_get_status_sig(self): |  | ||||||
|         resp = self.app.get('/status_sig') |  | ||||||
|         assert resp.status_code == 201 |  | ||||||
|  |  | ||||||
|     def test_get_status_response(self): |  | ||||||
|         resp = self.app.get('/status_response') |  | ||||||
|         assert resp.status_code == 201 |  | ||||||
|  |  | ||||||
|     def test_custom_clientside_error(self): |  | ||||||
|         r = self.app.get( |  | ||||||
|             '/models/test/secret', |  | ||||||
|             headers={'Accept': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         assert r.status_code == 403, r.status_code |  | ||||||
|         assert json.loads(r.data)['faultstring'] == '403: Forbidden' |  | ||||||
|  |  | ||||||
|         r = self.app.get( |  | ||||||
|             '/models/test/secret', |  | ||||||
|             headers={'Accept': 'application/xml'} |  | ||||||
|         ) |  | ||||||
|         assert r.status_code == 403, r.status_code |  | ||||||
|         assert r.data == (b'<error><faultcode>Client</faultcode>' |  | ||||||
|                           b'<faultstring>403: Forbidden</faultstring>' |  | ||||||
|                           b'<debuginfo /></error>') |  | ||||||
|  |  | ||||||
|     def test_custom_non_http_clientside_error(self): |  | ||||||
|         r = self.app.get( |  | ||||||
|             '/models/test/custom-error', |  | ||||||
|             headers={'Accept': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         assert r.status_code == 412, r.status_code |  | ||||||
|         assert json.loads(r.data)['faultstring'] == 'FOO!' |  | ||||||
|  |  | ||||||
|         r = self.app.get( |  | ||||||
|             '/models/test/custom-error', |  | ||||||
|             headers={'Accept': 'application/xml'} |  | ||||||
|         ) |  | ||||||
|         assert r.status_code == 412, r.status_code |  | ||||||
|         assert r.data == (b'<error><faultcode>Client</faultcode>' |  | ||||||
|                           b'<faultstring>FOO!</faultstring>' |  | ||||||
|                           b'<debuginfo /></error>') |  | ||||||
|  |  | ||||||
|     def test_serversideerror(self): |  | ||||||
|         r = self.app.get('/divide_by_zero') |  | ||||||
|         assert r.status_code == 500 |  | ||||||
|         data = json.loads(r.data) |  | ||||||
|         self.assertEqual(data['debuginfo'], None) |  | ||||||
|         self.assertEqual(data['faultcode'], 'Server') |  | ||||||
|         self.assertIn('by zero', data['faultstring']) |  | ||||||
|  |  | ||||||
|     def test_restful_get(self): |  | ||||||
|         r = self.app.get('/restful', headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertEqual(r.status_code, 200) |  | ||||||
|  |  | ||||||
|         data = json.loads(r.data) |  | ||||||
|  |  | ||||||
|         self.assertEqual(data['id'], 1) |  | ||||||
|         self.assertEqual(data['name'], u"Gérard") |  | ||||||
|  |  | ||||||
|     def test_restful_post(self): |  | ||||||
|         r = self.app.post( |  | ||||||
|             '/restful', |  | ||||||
|             data=json.dumps({'id': 5, 'name': u'Huguette'}), |  | ||||||
|             headers={ |  | ||||||
|                 'Accept': 'application/json', |  | ||||||
|                 'Content-Type': 'application/json'}) |  | ||||||
|         self.assertEqual(r.status_code, 200) |  | ||||||
|  |  | ||||||
|         data = json.loads(r.data) |  | ||||||
|  |  | ||||||
|         self.assertEqual(data, 5) |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     test_app.run() |  | ||||||
| @@ -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)') |  | ||||||
| @@ -1,196 +0,0 @@ | |||||||
| import wsmeext.tg11 |  | ||||||
| from wsme import WSRoot |  | ||||||
| from wsmeext.tg11 import wsexpose, wsvalidate |  | ||||||
| import wsmeext.tg1 |  | ||||||
|  |  | ||||||
| from turbogears.controllers import RootController |  | ||||||
| import cherrypy |  | ||||||
|  |  | ||||||
| import unittest |  | ||||||
|  |  | ||||||
| import simplejson |  | ||||||
|  |  | ||||||
|  |  | ||||||
| from wsmeext.tests import test_soap |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WSController(WSRoot): |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Subcontroller(object): |  | ||||||
|     @wsexpose(int, int, int) |  | ||||||
|     def add(self, a, b): |  | ||||||
|         return a + b |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Root(RootController): |  | ||||||
|     class UselessSubClass: |  | ||||||
|         # This class is here only to make sure wsmeext.tg1.scan_api |  | ||||||
|         # does its job properly |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     ws = WSController(webpath='/ws') |  | ||||||
|     ws.addprotocol( |  | ||||||
|         'soap', |  | ||||||
|         tns=test_soap.tns, |  | ||||||
|         typenamespace=test_soap.typenamespace, |  | ||||||
|         baseURL='/ws/' |  | ||||||
|     ) |  | ||||||
|     ws = wsmeext.tg11.adapt(ws) |  | ||||||
|  |  | ||||||
|     @wsexpose(int) |  | ||||||
|     @wsvalidate(int, int) |  | ||||||
|     def multiply(self, a, b): |  | ||||||
|         return a * b |  | ||||||
|  |  | ||||||
|     @wsexpose(int) |  | ||||||
|     @wsvalidate(int, int) |  | ||||||
|     def divide(self, a, b): |  | ||||||
|         if b == 0: |  | ||||||
|             raise cherrypy.HTTPError(400, 'Cannot divide by zero!') |  | ||||||
|         return a / b |  | ||||||
|  |  | ||||||
|     sub = Subcontroller() |  | ||||||
|  |  | ||||||
| from turbogears import testutil, config, startup |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestController(unittest.TestCase): |  | ||||||
|     root = Root |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         "Tests the output of the index method" |  | ||||||
|         self.app = testutil.make_app(self.root) |  | ||||||
|         testutil.start_server() |  | ||||||
|  |  | ||||||
|     def tearDown(self): |  | ||||||
|         # implementation copied from turbogears.testutil.stop_server. |  | ||||||
|         # The only change is that cherrypy.root is set to None |  | ||||||
|         # AFTER stopTurbogears has been called so that wsmeext.tg11 |  | ||||||
|         # can correctly uninstall its filter. |  | ||||||
|         if config.get("cp_started"): |  | ||||||
|             cherrypy.server.stop() |  | ||||||
|             config.update({"cp_started": False}) |  | ||||||
|  |  | ||||||
|         if config.get("server_started"): |  | ||||||
|             startup.stopTurboGears() |  | ||||||
|             config.update({"server_started": False}) |  | ||||||
|  |  | ||||||
|     def test_restcall(self): |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 50 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/sub/add", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 15 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 50 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'text/javascript'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 50 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json', |  | ||||||
|              'Accept': 'text/xml'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert response.body == "<result>50</result>" |  | ||||||
|  |  | ||||||
|     def test_custom_clientside_error(self): |  | ||||||
|         response = self.app.post( |  | ||||||
|             "/divide", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 0}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'application/json'}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         assert response.status_int == 400 |  | ||||||
|         assert simplejson.loads(response.body) == { |  | ||||||
|             "debuginfo": None, |  | ||||||
|             "faultcode": "Server", |  | ||||||
|             "faultstring": "(400, 'Cannot divide by zero!')" |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         response = self.app.post( |  | ||||||
|             "/divide", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 0}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'text/xml'}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         assert response.status_int == 400 |  | ||||||
|         assert response.body == ("<error><faultcode>Server</faultcode>" |  | ||||||
|                                  "<faultstring>(400, 'Cannot divide by zero!')" |  | ||||||
|                                  "</faultstring><debuginfo /></error>") |  | ||||||
|  |  | ||||||
|     def test_soap_wsdl(self): |  | ||||||
|         ts = test_soap.TestSOAP('test_wsdl') |  | ||||||
|         ts.app = self.app |  | ||||||
|         ts.ws_path = '/ws/' |  | ||||||
|         ts.run() |  | ||||||
|         #wsdl = self.app.get('/ws/api.wsdl').body |  | ||||||
|         #print wsdl |  | ||||||
|         #assert 'multiply' in wsdl |  | ||||||
|  |  | ||||||
|     def test_soap_call(self): |  | ||||||
|         ts = test_soap.TestSOAP('test_wsdl') |  | ||||||
|         ts.app = self.app |  | ||||||
|         ts.ws_path = '/ws/' |  | ||||||
|  |  | ||||||
|         print ts.ws_path |  | ||||||
|         assert ts.call('multiply', a=5, b=10, _rt=int) == 50 |  | ||||||
|  |  | ||||||
|     def test_scan_api_loops(self): |  | ||||||
|         class MyRoot(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         MyRoot.loop = MyRoot() |  | ||||||
|  |  | ||||||
|         root = MyRoot() |  | ||||||
|  |  | ||||||
|         api = list(wsmeext.tg1._scan_api(root)) |  | ||||||
|         print(api) |  | ||||||
|  |  | ||||||
|         self.assertEqual(len(api), 0) |  | ||||||
|  |  | ||||||
|     def test_scan_api_maxlen(self): |  | ||||||
|         class ARoot(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         def make_subcontrollers(n): |  | ||||||
|             c = type('Controller%s' % n, (object,), {}) |  | ||||||
|             return c |  | ||||||
|  |  | ||||||
|         c = ARoot |  | ||||||
|         for n in xrange(55): |  | ||||||
|             subc = make_subcontrollers(n) |  | ||||||
|             c.sub = subc() |  | ||||||
|             c = subc |  | ||||||
|         root = ARoot() |  | ||||||
|         self.assertRaises(ValueError, list, wsmeext.tg1._scan_api(root)) |  | ||||||
|  |  | ||||||
|     def test_templates_content_type(self): |  | ||||||
|         self.assertEqual( |  | ||||||
|             "application/json", |  | ||||||
|             wsmeext.tg1.AutoJSONTemplate().get_content_type('dummy') |  | ||||||
|         ) |  | ||||||
|         self.assertEqual( |  | ||||||
|             "text/xml", |  | ||||||
|             wsmeext.tg1.AutoXMLTemplate().get_content_type('dummy') |  | ||||||
|         ) |  | ||||||
| @@ -1,177 +0,0 @@ | |||||||
| import wsmeext.tg15 |  | ||||||
| from wsme import WSRoot |  | ||||||
|  |  | ||||||
| from turbogears.controllers import RootController |  | ||||||
| import cherrypy |  | ||||||
|  |  | ||||||
| from wsmeext.tests import test_soap |  | ||||||
|  |  | ||||||
| import simplejson |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Subcontroller(object): |  | ||||||
|     @wsmeext.tg15.wsexpose(int, int, int) |  | ||||||
|     def add(self, a, b): |  | ||||||
|         return a + b |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Root(RootController): |  | ||||||
|     class UselessSubClass: |  | ||||||
|         # This class is here only to make sure wsmeext.tg1.scan_api |  | ||||||
|         # does its job properly |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     sub = Subcontroller() |  | ||||||
|  |  | ||||||
|     ws = WSRoot(webpath='/ws') |  | ||||||
|     ws.addprotocol('soap', |  | ||||||
|         tns=test_soap.tns, |  | ||||||
|         typenamespace=test_soap.typenamespace, |  | ||||||
|         baseURL='/ws/' |  | ||||||
|     ) |  | ||||||
|     ws = wsmeext.tg15.adapt(ws) |  | ||||||
|  |  | ||||||
|     @wsmeext.tg15.wsexpose(int) |  | ||||||
|     @wsmeext.tg15.wsvalidate(int, int) |  | ||||||
|     def multiply(self, a, b): |  | ||||||
|         return a * b |  | ||||||
|  |  | ||||||
|     @wsmeext.tg15.wsexpose(int) |  | ||||||
|     @wsmeext.tg15.wsvalidate(int, int) |  | ||||||
|     def divide(self, a, b): |  | ||||||
|         if b == 0: |  | ||||||
|             raise cherrypy.HTTPError(400, 'Cannot divide by zero!') |  | ||||||
|         return a / b |  | ||||||
|  |  | ||||||
|  |  | ||||||
| from turbogears import testutil |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestController(testutil.TGTest): |  | ||||||
|     root = Root |  | ||||||
|  |  | ||||||
| #    def setUp(self): |  | ||||||
| #        "Tests the output of the index method" |  | ||||||
| #        self.app = testutil.make_app(self.root) |  | ||||||
| #        #print cherrypy.root |  | ||||||
| #        testutil.start_server() |  | ||||||
|  |  | ||||||
| #    def tearDown(self): |  | ||||||
| #        # implementation copied from turbogears.testutil.stop_server. |  | ||||||
| #        # The only change is that cherrypy.root is set to None |  | ||||||
| #        # AFTER stopTurbogears has been called so that wsmeext.tg15 |  | ||||||
| #        # can correctly uninstall its filter. |  | ||||||
| #        if config.get("cp_started"): |  | ||||||
| #            cherrypy.server.stop() |  | ||||||
| #            config.update({"cp_started": False}) |  | ||||||
| # |  | ||||||
| #        if config.get("server_started"): |  | ||||||
| #            startup.stopTurboGears() |  | ||||||
| #            config.update({"server_started": False}) |  | ||||||
|  |  | ||||||
|     def test_restcall(self): |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 50 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'application/json'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 50 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'text/javascript'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert simplejson.loads(response.body) == 50 |  | ||||||
|  |  | ||||||
|         response = self.app.post("/multiply", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 10}), |  | ||||||
|             {'Content-Type': 'application/json', |  | ||||||
|              'Accept': 'text/xml'} |  | ||||||
|         ) |  | ||||||
|         print response |  | ||||||
|         assert response.body == "<result>50</result>" |  | ||||||
|  |  | ||||||
|     def test_custom_clientside_error(self): |  | ||||||
|         response = self.app.post( |  | ||||||
|             "/divide", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 0}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'application/json'}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         assert response.status_int == 400 |  | ||||||
|         assert simplejson.loads(response.body) == { |  | ||||||
|             "debuginfo": None, |  | ||||||
|             "faultcode": "Client", |  | ||||||
|             "faultstring": "(400, 'Cannot divide by zero!')" |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         response = self.app.post( |  | ||||||
|             "/divide", |  | ||||||
|             simplejson.dumps({'a': 5, 'b': 0}), |  | ||||||
|             {'Content-Type': 'application/json', 'Accept': 'text/xml'}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         assert response.status_int == 400 |  | ||||||
|         assert response.body == ("<error><faultcode>Client</faultcode>" |  | ||||||
|                                  "<faultstring>(400, 'Cannot divide by zero!')" |  | ||||||
|                                  "</faultstring><debuginfo /></error>") |  | ||||||
|  |  | ||||||
|     def test_soap_wsdl(self): |  | ||||||
|         wsdl = self.app.get('/ws/api.wsdl').body |  | ||||||
|         print wsdl |  | ||||||
|         assert 'multiply' in wsdl |  | ||||||
|  |  | ||||||
|     def test_soap_call(self): |  | ||||||
|         ts = test_soap.TestSOAP('test_wsdl') |  | ||||||
|         ts.app = self.app |  | ||||||
|         ts.ws_path = '/ws/' |  | ||||||
|  |  | ||||||
|         print ts.ws_path |  | ||||||
|         assert ts.call('multiply', a=5, b=10, _rt=int) == 50 |  | ||||||
|  |  | ||||||
|     def test_scan_api_loops(self): |  | ||||||
|         class MyRoot(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         MyRoot.loop = MyRoot() |  | ||||||
|  |  | ||||||
|         root = MyRoot() |  | ||||||
|  |  | ||||||
|         api = list(wsmeext.tg1._scan_api(root)) |  | ||||||
|         print(api) |  | ||||||
|  |  | ||||||
|         self.assertEqual(len(api), 0) |  | ||||||
|  |  | ||||||
|     def test_scan_api_maxlen(self): |  | ||||||
|         class ARoot(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         def make_subcontrollers(n): |  | ||||||
|             c = type('Controller%s' % n, (object,), {}) |  | ||||||
|             return c |  | ||||||
|  |  | ||||||
|         c = ARoot |  | ||||||
|         for n in xrange(55): |  | ||||||
|             subc = make_subcontrollers(n) |  | ||||||
|             c.sub = subc() |  | ||||||
|             c = subc |  | ||||||
|         root = ARoot() |  | ||||||
|         self.assertRaises(ValueError, list, wsmeext.tg1._scan_api(root)) |  | ||||||
|  |  | ||||||
|     def test_templates_content_type(self): |  | ||||||
|         self.assertEqual( |  | ||||||
|             "application/json", |  | ||||||
|             wsmeext.tg1.AutoJSONTemplate().get_content_type('dummy') |  | ||||||
|         ) |  | ||||||
|         self.assertEqual( |  | ||||||
|             "text/xml", |  | ||||||
|             wsmeext.tg1.AutoXMLTemplate().get_content_type('dummy') |  | ||||||
|         ) |  | ||||||
							
								
								
									
										143
									
								
								tox-tmpl.ini
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								tox-tmpl.ini
									
									
									
									
									
								
							| @@ -1,143 +0,0 @@ | |||||||
| # content of: tox.ini , put in same dir as setup.py |  | ||||||
| [tox] |  | ||||||
| envlist = py27,py27-nolxml,pypy,tg11,tg15,cornice,cornice-py3,coverage,py35,py35-nolxml,pecan-dev27,pecan-dev35,pep8 |  | ||||||
|  |  | ||||||
| [common] |  | ||||||
| testtools= |  | ||||||
|     nose |  | ||||||
|     coverage < 3.99 |  | ||||||
|     pbr |  | ||||||
|     webtest |  | ||||||
| basedeps= |  | ||||||
|     transaction |  | ||||||
|     pecan |  | ||||||
|     cloud_sptheme |  | ||||||
|     Sphinx < 1.2.99 |  | ||||||
|     Flask |  | ||||||
|     flask-restful |  | ||||||
|  |  | ||||||
| [axes] |  | ||||||
| python=py27,py35,pypy |  | ||||||
| sqlalchemy=sa5,sa6,sa7* |  | ||||||
| lxml=lxml*,nolxml |  | ||||||
| json=json*,simplejson |  | ||||||
|  |  | ||||||
| [axis:python] |  | ||||||
| deps = |  | ||||||
|     {[common]testtools} |  | ||||||
|     {[common]basedeps} |  | ||||||
|     suds-jurko |  | ||||||
|  |  | ||||||
| commands= |  | ||||||
|     {envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
|     {envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
|     {envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
|  |  | ||||||
| [axis:python:py27] |  | ||||||
| basepython=python2.7 |  | ||||||
|  |  | ||||||
| [axis:python:py35] |  | ||||||
| basepython=python3.5 |  | ||||||
|  |  | ||||||
| [axis:sqlalchemy:sa5] |  | ||||||
| deps= |  | ||||||
|     SQLAlchemy<=0.5.99 |  | ||||||
|  |  | ||||||
| [axis:sqlalchemy:sa6] |  | ||||||
| deps= |  | ||||||
|     SQLAlchemy<=0.6.99 |  | ||||||
|  |  | ||||||
| [axis:sqlalchemy:sa7] |  | ||||||
| deps= |  | ||||||
|     SQLAlchemy<=0.7.99 |  | ||||||
|  |  | ||||||
| [axis:json:simplejson] |  | ||||||
| deps= |  | ||||||
|     simplejson |  | ||||||
|  |  | ||||||
| [axis:lxml:lxml] |  | ||||||
| deps= |  | ||||||
|     lxml |  | ||||||
|  |  | ||||||
| [testenv] |  | ||||||
| setenv= |  | ||||||
|     COVERAGE_FILE=.coverage.{envname} |  | ||||||
|  |  | ||||||
| [testenv:cornice] |  | ||||||
| basepython=python2.7 |  | ||||||
| usedevelop=True |  | ||||||
| deps= |  | ||||||
|     pbr |  | ||||||
|     nose |  | ||||||
|     webtest |  | ||||||
|     coverage < 3.99 |  | ||||||
|     cornice |  | ||||||
| commands= |  | ||||||
|     {envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs} |  | ||||||
|     {envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py |  | ||||||
|  |  | ||||||
| [testenv:cornice-py3] |  | ||||||
| basepython = python3.5 |  | ||||||
| usedevelop = {[testenv:cornice]usedevelop} |  | ||||||
| deps = {[testenv:cornice]deps} |  | ||||||
| # disable hash randomization |  | ||||||
| setenv = |  | ||||||
|         PYTHONHASHSEED=0 |  | ||||||
| commands = |  | ||||||
|     {envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs} |  | ||||||
|     {envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py |  | ||||||
|  |  | ||||||
| [testenv:pecan-dev-base] |  | ||||||
| deps= |  | ||||||
|     {[common]testtools} |  | ||||||
|     transaction |  | ||||||
|     suds-jurko |  | ||||||
|     https://github.com/stackforge/pecan/zipball/master |  | ||||||
|  |  | ||||||
| [testenv:pecan-dev27] |  | ||||||
| basepython=python2.7 |  | ||||||
| deps={[testenv:pecan-dev-base]deps} |  | ||||||
| commands= |  | ||||||
|     {envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs} |  | ||||||
|  |  | ||||||
| [testenv:pecan-dev35] |  | ||||||
| basepython=python3.5 |  | ||||||
| deps={[testenv:pecan-dev-base]deps} |  | ||||||
| commands= |  | ||||||
|     {envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs} |  | ||||||
|  |  | ||||||
| [testenv:coverage] |  | ||||||
| basepython=python |  | ||||||
| deps= |  | ||||||
|     coverage < 3.99 |  | ||||||
| setenv= |  | ||||||
|     COVERAGE_FILE=.coverage |  | ||||||
| commands= |  | ||||||
|     {envbindir}/coverage erase |  | ||||||
|     {envbindir}/coverage combine |  | ||||||
|     {envbindir}/coverage xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
|     {envbindir}/coverage report --show-missing wsme/*.py wsme/protocols/*.py wsmeext/*.py |  | ||||||
|  |  | ||||||
| [testenv:doc] |  | ||||||
| deps= |  | ||||||
|     cloud_sptheme |  | ||||||
|     Sphinx < 1.2.99 |  | ||||||
|  |  | ||||||
| changedir= |  | ||||||
|     doc |  | ||||||
|  |  | ||||||
| commands= |  | ||||||
|     make clean ziphtml |  | ||||||
|  |  | ||||||
| [testenv:pep8] |  | ||||||
| deps = flake8 |  | ||||||
| commands = flake8 wsme wsmeext setup.py |  | ||||||
|  |  | ||||||
| # Generic environment for running commands like packaging |  | ||||||
| [testenv:venv] |  | ||||||
| commands = {posargs} |  | ||||||
| usedevelop=True |  | ||||||
| deps = |  | ||||||
|     pbr |  | ||||||
|     oslo.config |  | ||||||
|     oslotest |  | ||||||
							
								
								
									
										932
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										932
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -1,932 +0,0 @@ | |||||||
| [tox] |  | ||||||
| envlist = py27,py27-nolxml,pypy,tg11,tg15,cornice,cornice-py3,coverage,py35,py35-nolxml,pecan-dev27,pecan-dev35,pep8 |  | ||||||
|  |  | ||||||
| [common] |  | ||||||
| testtools =  |  | ||||||
| 	nose |  | ||||||
| 	coverage < 3.99 |  | ||||||
| 	pbr |  | ||||||
| 	webtest |  | ||||||
| basedeps =  |  | ||||||
| 	transaction |  | ||||||
| 	pecan |  | ||||||
| 	cloud_sptheme |  | ||||||
| 	Sphinx < 1.2.99 |  | ||||||
| 	Flask |  | ||||||
| 	flask-restful |  | ||||||
|  |  | ||||||
| [testenv] |  | ||||||
| setenv =  |  | ||||||
| 	COVERAGE_FILE=.coverage.{envname} |  | ||||||
|  |  | ||||||
| [testenv:cornice] |  | ||||||
| basepython = python2.7 |  | ||||||
| usedevelop = True |  | ||||||
| deps =  |  | ||||||
| 	pbr |  | ||||||
| 	nose |  | ||||||
| 	webtest |  | ||||||
| 	coverage < 3.99 |  | ||||||
| 	cornice |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py |  | ||||||
|  |  | ||||||
| [testenv:cornice-py3] |  | ||||||
| basepython = python3.5 |  | ||||||
| usedevelop = {[testenv:cornice]usedevelop} |  | ||||||
| deps = {[testenv:cornice]deps} |  | ||||||
| setenv =  |  | ||||||
| 	PYTHONHASHSEED=0 |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/nosetests tests/test_cornice.py --with-xunit --xunit-file nosetests-{envname}.xml --verbose --with-coverage --cover-package wsmeext {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsmeext/cornice.py |  | ||||||
|  |  | ||||||
| [testenv:pecan-dev-base] |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	transaction |  | ||||||
| 	suds-jurko |  | ||||||
| 	https://github.com/stackforge/pecan/zipball/master |  | ||||||
|  |  | ||||||
| [testenv:pecan-dev27] |  | ||||||
| basepython = python2.7 |  | ||||||
| deps = {[testenv:pecan-dev-base]deps} |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs} |  | ||||||
|  |  | ||||||
| [testenv:pecan-dev35] |  | ||||||
| basepython = python3.5 |  | ||||||
| deps = {[testenv:pecan-dev-base]deps} |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/nosetests tests/pecantest --with-xunit --xunit-file nosetests-{envname}.xml --verbose {posargs} |  | ||||||
|  |  | ||||||
| [testenv:coverage] |  | ||||||
| basepython = python |  | ||||||
| deps =  |  | ||||||
| 	coverage < 3.99 |  | ||||||
| setenv =  |  | ||||||
| 	COVERAGE_FILE=.coverage |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage erase |  | ||||||
| 	{envbindir}/coverage combine |  | ||||||
| 	{envbindir}/coverage xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/protocols/*.py wsmeext/*.py |  | ||||||
|  |  | ||||||
| [testenv:doc] |  | ||||||
| deps =  |  | ||||||
| 	cloud_sptheme |  | ||||||
| 	Sphinx < 1.2.99 |  | ||||||
| changedir =  |  | ||||||
| 	doc |  | ||||||
| commands =  |  | ||||||
| 	make clean ziphtml |  | ||||||
|  |  | ||||||
| [testenv:pep8] |  | ||||||
| deps = flake8 |  | ||||||
| commands = flake8 wsme wsmeext setup.py |  | ||||||
|  |  | ||||||
| [testenv:venv] |  | ||||||
| commands = {posargs} |  | ||||||
| usedevelop = True |  | ||||||
| deps =  |  | ||||||
| 	pbr |  | ||||||
| 	oslo.config |  | ||||||
| 	oslotest |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa5-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa6-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa7-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa7-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa7-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-sa7-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py27-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python2.7 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa5-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa6-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa7-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa7-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa7-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-sa7-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:py35-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	simplejson |  | ||||||
| basepython = python3.5 |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa5-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.5.99 |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa6-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.6.99 |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa7-lxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
|  |  | ||||||
| [testenv:pypy] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa7-lxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	lxml |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa7-nolxml-json] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
|  |  | ||||||
| [testenv:pypy-nolxml] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
|  |  | ||||||
| [testenv:pypy-sa7-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
| [testenv:pypy-nolxml-simplejson] |  | ||||||
| commands =  |  | ||||||
| 	{envbindir}/coverage run {envbindir}/nosetests --nologcapture --with-xunit --xunit-file nosetests-{envname}.xml wsme/tests wsmeext/tests tests/pecantest tests/test_sphinxext.py tests/test_flask.py --verbose {posargs} |  | ||||||
| 	{envbindir}/coverage xml -o coverage-{envname}.xml wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| 	{envbindir}/coverage report --show-missing wsme/*.py wsme/rest/*.py wsmeext/*.py |  | ||||||
| deps =  |  | ||||||
| 	{[common]testtools} |  | ||||||
| 	{[common]basedeps} |  | ||||||
| 	suds-jurko |  | ||||||
| 	SQLAlchemy<=0.7.99 |  | ||||||
| 	simplejson |  | ||||||
|  |  | ||||||
							
								
								
									
										144
									
								
								toxgen.py
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								toxgen.py
									
									
									
									
									
								
							| @@ -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() |  | ||||||
| @@ -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' |  | ||||||
| ] |  | ||||||
							
								
								
									
										237
									
								
								wsme/api.py
									
									
									
									
									
								
							
							
						
						
									
										237
									
								
								wsme/api.py
									
									
									
									
									
								
							| @@ -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 |  | ||||||
							
								
								
									
										92
									
								
								wsme/exc.py
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								wsme/exc.py
									
									
									
									
									
								
							| @@ -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) |  | ||||||
							
								
								
									
										147
									
								
								wsme/protocol.py
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								wsme/protocol.py
									
									
									
									
									
								
							| @@ -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 |  | ||||||
| @@ -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 |  | ||||||
| @@ -1,310 +0,0 @@ | |||||||
| import cgi |  | ||||||
| import datetime |  | ||||||
| import re |  | ||||||
|  |  | ||||||
| from simplegeneric import generic |  | ||||||
|  |  | ||||||
| from wsme.exc import ClientSideError, UnknownArgument, InvalidInput |  | ||||||
|  |  | ||||||
| from wsme.types import iscomplex, list_attributes, Unset |  | ||||||
| from wsme.types import UserType, ArrayType, DictType, File |  | ||||||
| from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime |  | ||||||
| import wsme.runtime |  | ||||||
|  |  | ||||||
| from six import moves |  | ||||||
|  |  | ||||||
| ARRAY_MAX_SIZE = 1000 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @generic |  | ||||||
| def from_param(datatype, value): |  | ||||||
|     return datatype(value) if value is not None else None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_param.when_object(datetime.date) |  | ||||||
| def date_from_param(datatype, value): |  | ||||||
|     return parse_isodate(value) if value else None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_param.when_object(datetime.time) |  | ||||||
| def time_from_param(datatype, value): |  | ||||||
|     return parse_isotime(value) if value else None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_param.when_object(datetime.datetime) |  | ||||||
| def datetime_from_param(datatype, value): |  | ||||||
|     return parse_isodatetime(value) if value else None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_param.when_object(File) |  | ||||||
| def filetype_from_param(datatype, value): |  | ||||||
|     if isinstance(value, cgi.FieldStorage): |  | ||||||
|         return File(fieldstorage=value) |  | ||||||
|     return File(content=value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_param.when_type(UserType) |  | ||||||
| def usertype_from_param(datatype, value): |  | ||||||
|     return datatype.frombasetype( |  | ||||||
|         from_param(datatype.basetype, value)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_param.when_type(ArrayType) |  | ||||||
| def array_from_param(datatype, value): |  | ||||||
|     if value is None: |  | ||||||
|         return value |  | ||||||
|     return [ |  | ||||||
|         from_param(datatype.item_type, item) |  | ||||||
|         for item in value |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @generic |  | ||||||
| def from_params(datatype, params, path, hit_paths): |  | ||||||
|     if iscomplex(datatype) and datatype is not File: |  | ||||||
|         objfound = False |  | ||||||
|         for key in params: |  | ||||||
|             if key.startswith(path + '.'): |  | ||||||
|                 objfound = True |  | ||||||
|                 break |  | ||||||
|         if objfound: |  | ||||||
|             r = datatype() |  | ||||||
|             for attrdef in list_attributes(datatype): |  | ||||||
|                 value = from_params( |  | ||||||
|                     attrdef.datatype, |  | ||||||
|                     params, '%s.%s' % (path, attrdef.key), hit_paths |  | ||||||
|                 ) |  | ||||||
|                 if value is not Unset: |  | ||||||
|                     setattr(r, attrdef.key, value) |  | ||||||
|             return r |  | ||||||
|     else: |  | ||||||
|         if path in params: |  | ||||||
|             hit_paths.add(path) |  | ||||||
|             return from_param(datatype, params[path]) |  | ||||||
|     return Unset |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_params.when_type(ArrayType) |  | ||||||
| def array_from_params(datatype, params, path, hit_paths): |  | ||||||
|     if hasattr(params, 'getall'): |  | ||||||
|         # webob multidict |  | ||||||
|         def getall(params, path): |  | ||||||
|             return params.getall(path) |  | ||||||
|     elif hasattr(params, 'getlist'): |  | ||||||
|         # werkzeug multidict |  | ||||||
|         def getall(params, path):  # noqa |  | ||||||
|             return params.getlist(path) |  | ||||||
|     if path in params: |  | ||||||
|         hit_paths.add(path) |  | ||||||
|         return [ |  | ||||||
|             from_param(datatype.item_type, value) |  | ||||||
|             for value in getall(params, path)] |  | ||||||
|  |  | ||||||
|     if iscomplex(datatype.item_type): |  | ||||||
|         attributes = set() |  | ||||||
|         r = re.compile('^%s\.(?P<attrname>[^\.])' % re.escape(path)) |  | ||||||
|         for p in params.keys(): |  | ||||||
|             m = r.match(p) |  | ||||||
|             if m: |  | ||||||
|                 attributes.add(m.group('attrname')) |  | ||||||
|         if attributes: |  | ||||||
|             value = [] |  | ||||||
|             for attrdef in list_attributes(datatype.item_type): |  | ||||||
|                 attrpath = '%s.%s' % (path, attrdef.key) |  | ||||||
|                 hit_paths.add(attrpath) |  | ||||||
|                 attrvalues = getall(params, attrpath) |  | ||||||
|                 if len(value) < len(attrvalues): |  | ||||||
|                     value[-1:] = [ |  | ||||||
|                         datatype.item_type() |  | ||||||
|                         for i in moves.range(len(attrvalues) - len(value)) |  | ||||||
|                     ] |  | ||||||
|                 for i, attrvalue in enumerate(attrvalues): |  | ||||||
|                     setattr( |  | ||||||
|                         value[i], |  | ||||||
|                         attrdef.key, |  | ||||||
|                         from_param(attrdef.datatype, attrvalue) |  | ||||||
|                     ) |  | ||||||
|             return value |  | ||||||
|  |  | ||||||
|     indexes = set() |  | ||||||
|     r = re.compile('^%s\[(?P<index>\d+)\]' % re.escape(path)) |  | ||||||
|  |  | ||||||
|     for p in params.keys(): |  | ||||||
|         m = r.match(p) |  | ||||||
|         if m: |  | ||||||
|             indexes.add(int(m.group('index'))) |  | ||||||
|  |  | ||||||
|     if not indexes: |  | ||||||
|         return Unset |  | ||||||
|  |  | ||||||
|     indexes = list(indexes) |  | ||||||
|     indexes.sort() |  | ||||||
|  |  | ||||||
|     return [from_params(datatype.item_type, params, |  | ||||||
|                         '%s[%s]' % (path, index), hit_paths) |  | ||||||
|             for index in indexes] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_params.when_type(DictType) |  | ||||||
| def dict_from_params(datatype, params, path, hit_paths): |  | ||||||
|  |  | ||||||
|     keys = set() |  | ||||||
|     r = re.compile('^%s\[(?P<key>[a-zA-Z0-9_\.]+)\]' % re.escape(path)) |  | ||||||
|  |  | ||||||
|     for p in params.keys(): |  | ||||||
|         m = r.match(p) |  | ||||||
|         if m: |  | ||||||
|             keys.add(from_param(datatype.key_type, m.group('key'))) |  | ||||||
|  |  | ||||||
|     if not keys: |  | ||||||
|         return Unset |  | ||||||
|  |  | ||||||
|     return dict(( |  | ||||||
|         (key, from_params(datatype.value_type, |  | ||||||
|                           params, '%s[%s]' % (path, key), hit_paths)) |  | ||||||
|         for key in keys)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @from_params.when_type(UserType) |  | ||||||
| def usertype_from_params(datatype, params, path, hit_paths): |  | ||||||
|     value = from_params(datatype.basetype, params, path, hit_paths) |  | ||||||
|     if value is not Unset: |  | ||||||
|         return datatype.frombasetype(value) |  | ||||||
|     return Unset |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def args_from_args(funcdef, args, kwargs): |  | ||||||
|     newargs = [] |  | ||||||
|     for argdef, arg in zip(funcdef.arguments[:len(args)], args): |  | ||||||
|         try: |  | ||||||
|             newargs.append(from_param(argdef.datatype, arg)) |  | ||||||
|         except Exception as e: |  | ||||||
|             if isinstance(argdef.datatype, UserType): |  | ||||||
|                 datatype_name = argdef.datatype.name |  | ||||||
|             elif isinstance(argdef.datatype, type): |  | ||||||
|                 datatype_name = argdef.datatype.__name__ |  | ||||||
|             else: |  | ||||||
|                 datatype_name = argdef.datatype.__class__.__name__ |  | ||||||
|             raise InvalidInput( |  | ||||||
|                 argdef.name, |  | ||||||
|                 arg, |  | ||||||
|                 "unable to convert to %(datatype)s. Error: %(error)s" % { |  | ||||||
|                     'datatype': datatype_name, 'error': e}) |  | ||||||
|     newkwargs = {} |  | ||||||
|     for argname, value in kwargs.items(): |  | ||||||
|         newkwargs[argname] = from_param( |  | ||||||
|             funcdef.get_arg(argname).datatype, value |  | ||||||
|         ) |  | ||||||
|     return newargs, newkwargs |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def args_from_params(funcdef, params): |  | ||||||
|     kw = {} |  | ||||||
|     hit_paths = set() |  | ||||||
|     for argdef in funcdef.arguments: |  | ||||||
|         value = from_params( |  | ||||||
|             argdef.datatype, params, argdef.name, hit_paths) |  | ||||||
|         if value is not Unset: |  | ||||||
|             kw[argdef.name] = value |  | ||||||
|     paths = set(params.keys()) |  | ||||||
|     unknown_paths = paths - hit_paths |  | ||||||
|     if '__body__' in unknown_paths: |  | ||||||
|         unknown_paths.remove('__body__') |  | ||||||
|     if not funcdef.ignore_extra_args and unknown_paths: |  | ||||||
|         raise UnknownArgument(', '.join(unknown_paths)) |  | ||||||
|     return [], kw |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def args_from_body(funcdef, body, mimetype): |  | ||||||
|     from wsme.rest import json as restjson |  | ||||||
|     from wsme.rest import xml as restxml |  | ||||||
|  |  | ||||||
|     if funcdef.body_type is not None: |  | ||||||
|         datatypes = {funcdef.arguments[-1].name: funcdef.body_type} |  | ||||||
|     else: |  | ||||||
|         datatypes = dict(((a.name, a.datatype) for a in funcdef.arguments)) |  | ||||||
|  |  | ||||||
|     if not body: |  | ||||||
|         return (), {} |  | ||||||
|     if mimetype == "application/x-www-form-urlencoded": |  | ||||||
|         # the parameters should have been parsed in params |  | ||||||
|         return (), {} |  | ||||||
|     elif mimetype in restjson.accept_content_types: |  | ||||||
|         dataformat = restjson |  | ||||||
|     elif mimetype in restxml.accept_content_types: |  | ||||||
|         dataformat = restxml |  | ||||||
|     else: |  | ||||||
|         raise ClientSideError("Unknown mimetype: %s" % mimetype, |  | ||||||
|                               status_code=415) |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         kw = dataformat.parse( |  | ||||||
|             body, datatypes, bodyarg=funcdef.body_type is not None |  | ||||||
|         ) |  | ||||||
|     except UnknownArgument: |  | ||||||
|         if not funcdef.ignore_extra_args: |  | ||||||
|             raise |  | ||||||
|         kw = {} |  | ||||||
|  |  | ||||||
|     return (), kw |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def combine_args(funcdef, akw, allow_override=False): |  | ||||||
|     newargs, newkwargs = [], {} |  | ||||||
|     for args, kwargs in akw: |  | ||||||
|         for i, arg in enumerate(args): |  | ||||||
|             n = funcdef.arguments[i].name |  | ||||||
|             if not allow_override and n in newkwargs: |  | ||||||
|                 raise ClientSideError( |  | ||||||
|                     "Parameter %s was given several times" % n) |  | ||||||
|             newkwargs[n] = arg |  | ||||||
|         for name, value in kwargs.items(): |  | ||||||
|             n = str(name) |  | ||||||
|             if not allow_override and n in newkwargs: |  | ||||||
|                 raise ClientSideError( |  | ||||||
|                     "Parameter %s was given several times" % n) |  | ||||||
|             newkwargs[n] = value |  | ||||||
|     return newargs, newkwargs |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_args(funcdef, args, kwargs, params, form, body, mimetype): |  | ||||||
|     """Combine arguments from : |  | ||||||
|     * the host framework args and kwargs |  | ||||||
|     * the request params |  | ||||||
|     * the request body |  | ||||||
|  |  | ||||||
|     Note that the host framework args and kwargs can be overridden |  | ||||||
|     by arguments from params of body |  | ||||||
|     """ |  | ||||||
|     # get the body from params if not given directly |  | ||||||
|     if not body and '__body__' in params: |  | ||||||
|         body = params['__body__'] |  | ||||||
|  |  | ||||||
|     # extract args from the host args and kwargs |  | ||||||
|     from_args = args_from_args(funcdef, args, kwargs) |  | ||||||
|  |  | ||||||
|     # extract args from the request parameters |  | ||||||
|     from_params = args_from_params(funcdef, params) |  | ||||||
|  |  | ||||||
|     # extract args from the form parameters |  | ||||||
|     if form: |  | ||||||
|         from_form_params = args_from_params(funcdef, form) |  | ||||||
|     else: |  | ||||||
|         from_form_params = (), {} |  | ||||||
|  |  | ||||||
|     # extract args from the request body |  | ||||||
|     from_body = args_from_body(funcdef, body, mimetype) |  | ||||||
|  |  | ||||||
|     # combine params and body arguments |  | ||||||
|     from_params_and_body = combine_args( |  | ||||||
|         funcdef, |  | ||||||
|         (from_params, from_form_params, from_body) |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     args, kwargs = combine_args( |  | ||||||
|         funcdef, |  | ||||||
|         (from_args, from_params_and_body), |  | ||||||
|         allow_override=True |  | ||||||
|     ) |  | ||||||
|     wsme.runtime.check_arguments(funcdef, args, kwargs) |  | ||||||
|     return args, kwargs |  | ||||||
| @@ -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) |  | ||||||
| @@ -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 |  | ||||||
							
								
								
									
										298
									
								
								wsme/rest/xml.py
									
									
									
									
									
								
							
							
						
						
									
										298
									
								
								wsme/rest/xml.py
									
									
									
									
									
								
							| @@ -1,298 +0,0 @@ | |||||||
| from __future__ import absolute_import |  | ||||||
|  |  | ||||||
| import datetime |  | ||||||
|  |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| import xml.etree.ElementTree as et |  | ||||||
|  |  | ||||||
| from simplegeneric import generic |  | ||||||
|  |  | ||||||
| import wsme.types |  | ||||||
| from wsme.exc import UnknownArgument, InvalidInput |  | ||||||
|  |  | ||||||
| import re |  | ||||||
|  |  | ||||||
| content_type = 'text/xml' |  | ||||||
| accept_content_types = [ |  | ||||||
|     content_type, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| time_re = re.compile(r'(?P<h>[0-2][0-9]):(?P<m>[0-5][0-9]):(?P<s>[0-6][0-9])') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def xml_indent(elem, level=0): |  | ||||||
|     i = "\n" + level * "  " |  | ||||||
|     if len(elem): |  | ||||||
|         if not elem.text or not elem.text.strip(): |  | ||||||
|             elem.text = i + "  " |  | ||||||
|         for e in elem: |  | ||||||
|             xml_indent(e, level + 1) |  | ||||||
|         if not e.tail or not e.tail.strip(): |  | ||||||
|             e.tail = i |  | ||||||
|     if level and (not elem.tail or not elem.tail.strip()): |  | ||||||
|         elem.tail = i |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @generic |  | ||||||
| def toxml(datatype, key, value): |  | ||||||
|     """ |  | ||||||
|     A generic converter from python to xml elements. |  | ||||||
|  |  | ||||||
|     If a non-complex user specific type is to be used in the api, |  | ||||||
|     a specific toxml should be added:: |  | ||||||
|  |  | ||||||
|         from wsme.protocol.restxml import toxml |  | ||||||
|  |  | ||||||
|         myspecialtype = object() |  | ||||||
|  |  | ||||||
|         @toxml.when_object(myspecialtype) |  | ||||||
|         def myspecialtype_toxml(datatype, key, value): |  | ||||||
|             el = et.Element(key) |  | ||||||
|             if value is None: |  | ||||||
|                 el.set('nil', 'true') |  | ||||||
|             else: |  | ||||||
|                 el.text = str(value) |  | ||||||
|             return el |  | ||||||
|     """ |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         if wsme.types.isusertype(datatype): |  | ||||||
|             return toxml(datatype.basetype, |  | ||||||
|                          key, datatype.tobasetype(value)) |  | ||||||
|         elif wsme.types.iscomplex(datatype): |  | ||||||
|             for attrdef in datatype._wsme_attributes: |  | ||||||
|                 attrvalue = getattr(value, attrdef.key) |  | ||||||
|                 if attrvalue is not wsme.types.Unset: |  | ||||||
|                     el.append(toxml(attrdef.datatype, attrdef.name, |  | ||||||
|                                     attrvalue)) |  | ||||||
|         else: |  | ||||||
|             el.text = six.text_type(value) |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @generic |  | ||||||
| def fromxml(datatype, element): |  | ||||||
|     """ |  | ||||||
|     A generic converter from xml elements to python datatype. |  | ||||||
|  |  | ||||||
|     If a non-complex user specific type is to be used in the api, |  | ||||||
|     a specific fromxml should be added:: |  | ||||||
|  |  | ||||||
|         from wsme.protocol.restxml import fromxml |  | ||||||
|  |  | ||||||
|         class MySpecialType(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         @fromxml.when_object(MySpecialType) |  | ||||||
|         def myspecialtype_fromxml(datatype, element): |  | ||||||
|             if element.get('nil', False): |  | ||||||
|                 return None |  | ||||||
|             return MySpecialType(element.text) |  | ||||||
|     """ |  | ||||||
|     if element.get('nil', False): |  | ||||||
|         return None |  | ||||||
|     if wsme.types.isusertype(datatype): |  | ||||||
|         return datatype.frombasetype(fromxml(datatype.basetype, element)) |  | ||||||
|     if wsme.types.iscomplex(datatype): |  | ||||||
|         obj = datatype() |  | ||||||
|         for attrdef in wsme.types.list_attributes(datatype): |  | ||||||
|             sub = element.find(attrdef.name) |  | ||||||
|             if sub is not None: |  | ||||||
|                 val_fromxml = fromxml(attrdef.datatype, sub) |  | ||||||
|                 if getattr(attrdef, 'readonly', False): |  | ||||||
|                     raise InvalidInput(attrdef.name, val_fromxml, |  | ||||||
|                                        "Cannot set read only field.") |  | ||||||
|                 setattr(obj, attrdef.key, val_fromxml) |  | ||||||
|             elif attrdef.mandatory: |  | ||||||
|                 raise InvalidInput(attrdef.name, None, |  | ||||||
|                                    "Mandatory field missing.") |  | ||||||
|         return wsme.types.validate_value(datatype, obj) |  | ||||||
|     if datatype is wsme.types.bytes: |  | ||||||
|         return element.text.encode('ascii') |  | ||||||
|     return datatype(element.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @toxml.when_type(wsme.types.ArrayType) |  | ||||||
| def array_toxml(datatype, key, value): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         for item in value: |  | ||||||
|             el.append(toxml(datatype.item_type, 'item', item)) |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @toxml.when_type(wsme.types.DictType) |  | ||||||
| def dict_toxml(datatype, key, value): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         for item in value.items(): |  | ||||||
|             key = toxml(datatype.key_type, 'key', item[0]) |  | ||||||
|             value = toxml(datatype.value_type, 'value', item[1]) |  | ||||||
|             node = et.Element('item') |  | ||||||
|             node.append(key) |  | ||||||
|             node.append(value) |  | ||||||
|             el.append(node) |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @toxml.when_object(wsme.types.bytes) |  | ||||||
| def bytes_toxml(datatype, key, value): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         el.text = value.decode('ascii') |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @toxml.when_object(bool) |  | ||||||
| def bool_toxml(datatype, key, value): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         el.text = value and 'true' or 'false' |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @toxml.when_object(datetime.date) |  | ||||||
| def date_toxml(datatype, key, value): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         el.text = value.isoformat() |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @toxml.when_object(datetime.datetime) |  | ||||||
| def datetime_toxml(datatype, key, value): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     else: |  | ||||||
|         el.text = value.isoformat() |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_type(wsme.types.ArrayType) |  | ||||||
| def array_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return [ |  | ||||||
|         fromxml(datatype.item_type, item) |  | ||||||
|         for item in element.findall('item') |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_object(bool) |  | ||||||
| def bool_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return element.text.lower() != 'false' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_type(wsme.types.DictType) |  | ||||||
| def dict_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return dict(( |  | ||||||
|         (fromxml(datatype.key_type, item.find('key')), |  | ||||||
|             fromxml(datatype.value_type, item.find('value'))) |  | ||||||
|         for item in element.findall('item'))) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_object(wsme.types.text) |  | ||||||
| def unicode_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return wsme.types.text(element.text) if element.text else six.u('') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_object(datetime.date) |  | ||||||
| def date_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return wsme.utils.parse_isodate(element.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_object(datetime.time) |  | ||||||
| def time_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return wsme.utils.parse_isotime(element.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromxml.when_object(datetime.datetime) |  | ||||||
| def datetime_fromxml(datatype, element): |  | ||||||
|     if element.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     return wsme.utils.parse_isodatetime(element.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse(s, datatypes, bodyarg): |  | ||||||
|     if hasattr(s, 'read'): |  | ||||||
|         tree = et.parse(s) |  | ||||||
|     else: |  | ||||||
|         tree = et.fromstring(s) |  | ||||||
|     if bodyarg: |  | ||||||
|         name = list(datatypes.keys())[0] |  | ||||||
|         return {name: fromxml(datatypes[name], tree)} |  | ||||||
|     else: |  | ||||||
|         kw = {} |  | ||||||
|         extra_args = [] |  | ||||||
|         for sub in tree: |  | ||||||
|             if sub.tag not in datatypes: |  | ||||||
|                 extra_args.append(sub.tag) |  | ||||||
|             kw[sub.tag] = fromxml(datatypes[sub.tag], sub) |  | ||||||
|         if extra_args: |  | ||||||
|             raise UnknownArgument(', '.join(extra_args)) |  | ||||||
|         return kw |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_result(value, datatype, **options): |  | ||||||
|     return et.tostring(toxml( |  | ||||||
|         datatype, options.get('nested_result_attrname', 'result'), value |  | ||||||
|     )) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_error(context, errordetail): |  | ||||||
|     el = et.Element('error') |  | ||||||
|     et.SubElement(el, 'faultcode').text = errordetail['faultcode'] |  | ||||||
|     et.SubElement(el, 'faultstring').text = errordetail['faultstring'] |  | ||||||
|     if 'debuginfo' in errordetail: |  | ||||||
|         et.SubElement(el, 'debuginfo').text = errordetail['debuginfo'] |  | ||||||
|     return et.tostring(el) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_sample_value(datatype, value, format=False): |  | ||||||
|     r = toxml(datatype, 'value', value) |  | ||||||
|     if format: |  | ||||||
|         xml_indent(r) |  | ||||||
|     content = et.tostring(r) |  | ||||||
|     return ('xml', content) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_sample_params(params, format=False): |  | ||||||
|     node = et.Element('parameters') |  | ||||||
|     for name, datatype, value in params: |  | ||||||
|         node.append(toxml(datatype, name, value)) |  | ||||||
|     if format: |  | ||||||
|         xml_indent(node) |  | ||||||
|     content = et.tostring(node) |  | ||||||
|     return ('xml', content) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_sample_result(datatype, value, format=False): |  | ||||||
|     r = toxml(datatype, 'result', value) |  | ||||||
|     if format: |  | ||||||
|         xml_indent(r) |  | ||||||
|     content = et.tostring(r) |  | ||||||
|     return ('xml', content) |  | ||||||
							
								
								
									
										372
									
								
								wsme/root.py
									
									
									
									
									
								
							
							
						
						
									
										372
									
								
								wsme/root.py
									
									
									
									
									
								
							| @@ -1,372 +0,0 @@ | |||||||
| import logging |  | ||||||
| import sys |  | ||||||
| import weakref |  | ||||||
|  |  | ||||||
| from six import u, b |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| import webob |  | ||||||
|  |  | ||||||
| from wsme.exc import ClientSideError, UnknownFunction |  | ||||||
| from wsme.protocol import getprotocol |  | ||||||
| from wsme.rest import scan_api |  | ||||||
| from wsme import spore |  | ||||||
| import wsme.api |  | ||||||
| import wsme.types |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
| html_body = u(""" |  | ||||||
| <html> |  | ||||||
| <head> |  | ||||||
|   <style type='text/css'> |  | ||||||
|     %(css)s |  | ||||||
|   </style> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
| %(content)s |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
| """) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def default_prepare_response_body(request, results): |  | ||||||
|     r = None |  | ||||||
|     sep = None |  | ||||||
|     for value in results: |  | ||||||
|         if sep is None: |  | ||||||
|             if isinstance(value, six.text_type): |  | ||||||
|                 sep = u('\n') |  | ||||||
|                 r = u('') |  | ||||||
|             else: |  | ||||||
|                 sep = b('\n') |  | ||||||
|                 r = b('') |  | ||||||
|         else: |  | ||||||
|             r += sep |  | ||||||
|         r += value |  | ||||||
|     return r |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DummyTransaction: |  | ||||||
|     def commit(self): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def abort(self): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WSRoot(object): |  | ||||||
|     """ |  | ||||||
|     Root controller for webservices. |  | ||||||
|  |  | ||||||
|     :param protocols: A list of protocols to enable (see :meth:`addprotocol`) |  | ||||||
|     :param webpath: The web path where the webservice is published. |  | ||||||
|  |  | ||||||
|     :type  transaction: A `transaction |  | ||||||
|                         <http://pypi.python.org/pypi/transaction>`_-like |  | ||||||
|                         object or ``True``. |  | ||||||
|     :param transaction: If specified, a transaction will be created and |  | ||||||
|                         handled on a per-call base. |  | ||||||
|  |  | ||||||
|                         This option *can* be enabled along with `repoze.tm2 |  | ||||||
|                         <http://pypi.python.org/pypi/repoze.tm2>`_ |  | ||||||
|                         (it will only make it void). |  | ||||||
|  |  | ||||||
|                         If ``True``, the default :mod:`transaction` |  | ||||||
|                         module will be imported and used. |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     __registry__ = wsme.types.registry |  | ||||||
|  |  | ||||||
|     def __init__(self, protocols=[], webpath='', transaction=None, |  | ||||||
|                  scan_api=scan_api): |  | ||||||
|         self._debug = True |  | ||||||
|         self._webpath = webpath |  | ||||||
|         self.protocols = [] |  | ||||||
|         self._scan_api = scan_api |  | ||||||
|  |  | ||||||
|         self._transaction = transaction |  | ||||||
|         if self._transaction is True: |  | ||||||
|             import transaction |  | ||||||
|             self._transaction = transaction |  | ||||||
|  |  | ||||||
|         for protocol in protocols: |  | ||||||
|             self.addprotocol(protocol) |  | ||||||
|  |  | ||||||
|         self._api = None |  | ||||||
|  |  | ||||||
|     def wsgiapp(self): |  | ||||||
|         """Returns a wsgi application""" |  | ||||||
|         from webob.dec import wsgify |  | ||||||
|         return wsgify(self._handle_request) |  | ||||||
|  |  | ||||||
|     def begin(self): |  | ||||||
|         if self._transaction: |  | ||||||
|             return self._transaction.begin() |  | ||||||
|         else: |  | ||||||
|             return DummyTransaction() |  | ||||||
|  |  | ||||||
|     def addprotocol(self, protocol, **options): |  | ||||||
|         """ |  | ||||||
|         Enable a new protocol on the controller. |  | ||||||
|  |  | ||||||
|         :param protocol: A registered protocol name or an instance |  | ||||||
|                          of a protocol. |  | ||||||
|         """ |  | ||||||
|         if isinstance(protocol, str): |  | ||||||
|             protocol = getprotocol(protocol, **options) |  | ||||||
|         self.protocols.append(protocol) |  | ||||||
|         protocol.root = weakref.proxy(self) |  | ||||||
|  |  | ||||||
|     def getapi(self): |  | ||||||
|         """ |  | ||||||
|         Returns the api description. |  | ||||||
|  |  | ||||||
|         :rtype: list of (path, :class:`FunctionDefinition`) |  | ||||||
|         """ |  | ||||||
|         if self._api is None: |  | ||||||
|             self._api = [ |  | ||||||
|                 (path, f, f._wsme_definition, args) |  | ||||||
|                 for path, f, args in self._scan_api(self) |  | ||||||
|             ] |  | ||||||
|             for path, f, fdef, args in self._api: |  | ||||||
|                 fdef.resolve_types(self.__registry__) |  | ||||||
|         return [ |  | ||||||
|             (path, fdef) |  | ||||||
|             for path, f, fdef, args in self._api |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|     def _get_protocol(self, name): |  | ||||||
|         for protocol in self.protocols: |  | ||||||
|             if protocol.name == name: |  | ||||||
|                 return protocol |  | ||||||
|  |  | ||||||
|     def _select_protocol(self, request): |  | ||||||
|         log.debug("Selecting a protocol for the following request :\n" |  | ||||||
|                   "headers: %s\nbody: %s", request.headers.items(), |  | ||||||
|                   request.content_length and ( |  | ||||||
|                       request.content_length > 512 and |  | ||||||
|                       request.body[:512] or |  | ||||||
|                       request.body) or '') |  | ||||||
|         protocol = None |  | ||||||
|         error = ClientSideError(status_code=406) |  | ||||||
|         path = str(request.path) |  | ||||||
|         assert path.startswith(self._webpath) |  | ||||||
|         path = path[len(self._webpath) + 1:] |  | ||||||
|         if 'wsmeproto' in request.params: |  | ||||||
|             return self._get_protocol(request.params['wsmeproto']) |  | ||||||
|         else: |  | ||||||
|  |  | ||||||
|             for p in self.protocols: |  | ||||||
|                 try: |  | ||||||
|                     if p.accept(request): |  | ||||||
|                         protocol = p |  | ||||||
|                         break |  | ||||||
|                 except ClientSideError as e: |  | ||||||
|                     error = e |  | ||||||
|             # If we could not select a protocol, we raise the last exception |  | ||||||
|             # that we got, or the default one. |  | ||||||
|             if not protocol: |  | ||||||
|                 raise error |  | ||||||
|         return protocol |  | ||||||
|  |  | ||||||
|     def _do_call(self, protocol, context): |  | ||||||
|         request = context.request |  | ||||||
|         request.calls.append(context) |  | ||||||
|         try: |  | ||||||
|             if context.path is None: |  | ||||||
|                 context.path = protocol.extract_path(context) |  | ||||||
|  |  | ||||||
|             if context.path is None: |  | ||||||
|                 raise ClientSideError(u( |  | ||||||
|                     'The %s protocol was unable to extract a function ' |  | ||||||
|                     'path from the request') % protocol.name) |  | ||||||
|  |  | ||||||
|             context.func, context.funcdef, args = \ |  | ||||||
|                 self._lookup_function(context.path) |  | ||||||
|             kw = protocol.read_arguments(context) |  | ||||||
|             args = list(args) |  | ||||||
|  |  | ||||||
|             txn = self.begin() |  | ||||||
|             try: |  | ||||||
|                 result = context.func(*args, **kw) |  | ||||||
|                 txn.commit() |  | ||||||
|             except: |  | ||||||
|                 txn.abort() |  | ||||||
|                 raise |  | ||||||
|  |  | ||||||
|             else: |  | ||||||
|                 # TODO make sure result type == a._wsme_definition.return_type |  | ||||||
|                 return protocol.encode_result(context, result) |  | ||||||
|  |  | ||||||
|         except Exception as e: |  | ||||||
|             infos = wsme.api.format_exception(sys.exc_info(), self._debug) |  | ||||||
|             if isinstance(e, ClientSideError): |  | ||||||
|                 request.client_errorcount += 1 |  | ||||||
|                 request.client_last_status_code = e.code |  | ||||||
|             else: |  | ||||||
|                 request.server_errorcount += 1 |  | ||||||
|             return protocol.encode_error(context, infos) |  | ||||||
|  |  | ||||||
|     def find_route(self, path): |  | ||||||
|         for p in self.protocols: |  | ||||||
|             for routepath, func in p.iter_routes(): |  | ||||||
|                 if path.startswith(routepath): |  | ||||||
|                     return routepath, func |  | ||||||
|         return None, None |  | ||||||
|  |  | ||||||
|     def _handle_request(self, request): |  | ||||||
|         res = webob.Response() |  | ||||||
|         res_content_type = None |  | ||||||
|  |  | ||||||
|         path = request.path |  | ||||||
|         if path.startswith(self._webpath): |  | ||||||
|             path = path[len(self._webpath):] |  | ||||||
|         routepath, func = self.find_route(path) |  | ||||||
|         if routepath: |  | ||||||
|             content = func() |  | ||||||
|             if isinstance(content, six.text_type): |  | ||||||
|                 res.text = content |  | ||||||
|             elif isinstance(content, six.binary_type): |  | ||||||
|                 res.body = content |  | ||||||
|             res.content_type = func._cfg['content-type'] |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         if request.path == self._webpath + '/api.spore': |  | ||||||
|             res.body = spore.getdesc(self, request.host_url) |  | ||||||
|             res.content_type = 'application/json' |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             msg = None |  | ||||||
|             error_status = 500 |  | ||||||
|             protocol = self._select_protocol(request) |  | ||||||
|         except ClientSideError as e: |  | ||||||
|             error_status = e.code |  | ||||||
|             msg = e.faultstring |  | ||||||
|             protocol = None |  | ||||||
|         except Exception as e: |  | ||||||
|             msg = ("Unexpected error while selecting protocol: %s" % str(e)) |  | ||||||
|             log.exception(msg) |  | ||||||
|             protocol = None |  | ||||||
|             error_status = 500 |  | ||||||
|  |  | ||||||
|         if protocol is None: |  | ||||||
|             if not msg: |  | ||||||
|                 msg = ("None of the following protocols can handle this " |  | ||||||
|                        "request : %s" % ','.join(( |  | ||||||
|                            p.name for p in self.protocols))) |  | ||||||
|             res.status = error_status |  | ||||||
|             res.content_type = 'text/plain' |  | ||||||
|             try: |  | ||||||
|                 res.text = u(msg) |  | ||||||
|             except TypeError: |  | ||||||
|                 res.text = msg |  | ||||||
|             log.error(msg) |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         request.calls = [] |  | ||||||
|         request.client_errorcount = 0 |  | ||||||
|         request.client_last_status_code = None |  | ||||||
|         request.server_errorcount = 0 |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|  |  | ||||||
|             context = None |  | ||||||
|  |  | ||||||
|             if hasattr(protocol, 'prepare_response_body'): |  | ||||||
|                 prepare_response_body = protocol.prepare_response_body |  | ||||||
|             else: |  | ||||||
|                 prepare_response_body = default_prepare_response_body |  | ||||||
|  |  | ||||||
|             body = prepare_response_body(request, ( |  | ||||||
|                 self._do_call(protocol, context) |  | ||||||
|                 for context in protocol.iter_calls(request))) |  | ||||||
|  |  | ||||||
|             if isinstance(body, six.text_type): |  | ||||||
|                 res.text = body |  | ||||||
|             else: |  | ||||||
|                 res.body = body |  | ||||||
|  |  | ||||||
|             if len(request.calls) == 1: |  | ||||||
|                 if hasattr(protocol, 'get_response_status'): |  | ||||||
|                     res.status = protocol.get_response_status(request) |  | ||||||
|                 else: |  | ||||||
|                     if request.client_errorcount == 1: |  | ||||||
|                         res.status = request.client_last_status_code |  | ||||||
|                     elif request.client_errorcount: |  | ||||||
|                         res.status = 400 |  | ||||||
|                     elif request.server_errorcount: |  | ||||||
|                         res.status = 500 |  | ||||||
|                     else: |  | ||||||
|                         res.status = 200 |  | ||||||
|             else: |  | ||||||
|                 res.status = protocol.get_response_status(request) |  | ||||||
|                 res_content_type = protocol.get_response_contenttype(request) |  | ||||||
|         except ClientSideError as e: |  | ||||||
|             request.server_errorcount += 1 |  | ||||||
|             res.status = e.code |  | ||||||
|             res.text = e.faultstring |  | ||||||
|         except Exception: |  | ||||||
|             infos = wsme.api.format_exception(sys.exc_info(), self._debug) |  | ||||||
|             request.server_errorcount += 1 |  | ||||||
|             res.text = protocol.encode_error(context, infos) |  | ||||||
|             res.status = 500 |  | ||||||
|  |  | ||||||
|         if res_content_type is None: |  | ||||||
|             # Attempt to correctly guess what content-type we should return. |  | ||||||
|             ctypes = [ct for ct in protocol.content_types if ct] |  | ||||||
|             if ctypes: |  | ||||||
|                 res_content_type = request.accept.best_match(ctypes) |  | ||||||
|  |  | ||||||
|         # If not we will attempt to convert the body to an accepted |  | ||||||
|         # output format. |  | ||||||
|         if res_content_type is None: |  | ||||||
|             if "text/html" in request.accept: |  | ||||||
|                 res.text = self._html_format(res.body, protocol.content_types) |  | ||||||
|                 res_content_type = "text/html" |  | ||||||
|  |  | ||||||
|         # TODO should we consider the encoding asked by |  | ||||||
|         # the web browser ? |  | ||||||
|         res.headers['Content-Type'] = "%s; charset=UTF-8" % res_content_type |  | ||||||
|  |  | ||||||
|         return res |  | ||||||
|  |  | ||||||
|     def _lookup_function(self, path): |  | ||||||
|         if not self._api: |  | ||||||
|             self.getapi() |  | ||||||
|  |  | ||||||
|         for fpath, f, fdef, args in self._api: |  | ||||||
|             if path == fpath: |  | ||||||
|                 return f, fdef, args |  | ||||||
|         raise UnknownFunction('/'.join(path)) |  | ||||||
|  |  | ||||||
|     def _html_format(self, content, content_types): |  | ||||||
|         try: |  | ||||||
|             from pygments import highlight |  | ||||||
|             from pygments.lexers import get_lexer_for_mimetype |  | ||||||
|             from pygments.formatters import HtmlFormatter |  | ||||||
|  |  | ||||||
|             lexer = None |  | ||||||
|             for ct in content_types: |  | ||||||
|                 try: |  | ||||||
|                     lexer = get_lexer_for_mimetype(ct) |  | ||||||
|                     break |  | ||||||
|                 except: |  | ||||||
|                     pass |  | ||||||
|  |  | ||||||
|             if lexer is None: |  | ||||||
|                 raise ValueError("No lexer found") |  | ||||||
|             formatter = HtmlFormatter() |  | ||||||
|             return html_body % dict( |  | ||||||
|                 css=formatter.get_style_defs(), |  | ||||||
|                 content=highlight(content, lexer, formatter).encode('utf8')) |  | ||||||
|         except Exception as e: |  | ||||||
|             log.warning( |  | ||||||
|                 "Could not pygment the content because of the following " |  | ||||||
|                 "error :\n%s" % e) |  | ||||||
|             return html_body % dict( |  | ||||||
|                 css='', |  | ||||||
|                 content=u('<pre>%s</pre>') % |  | ||||||
|                     content.replace(b('>'), b('>')) |  | ||||||
|                            .replace(b('<'), b('<'))) |  | ||||||
| @@ -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) |  | ||||||
| @@ -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) |  | ||||||
| @@ -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) |  | ||||||
| @@ -1,419 +0,0 @@ | |||||||
| # encoding=utf8 |  | ||||||
|  |  | ||||||
| from six import b |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import unittest2 as unittest |  | ||||||
| except ImportError: |  | ||||||
|     import unittest |  | ||||||
| import webtest |  | ||||||
|  |  | ||||||
| from wsme import WSRoot, expose, validate |  | ||||||
| from wsme.rest import scan_api |  | ||||||
| from wsme import types |  | ||||||
| from wsme import exc |  | ||||||
| import wsme.api as wsme_api |  | ||||||
| import wsme.types |  | ||||||
|  |  | ||||||
| from wsme.tests.test_protocols import DummyProtocol |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestController(unittest.TestCase): |  | ||||||
|     def test_expose(self): |  | ||||||
|         class MyWS(WSRoot): |  | ||||||
|             @expose(int) |  | ||||||
|             def getint(self): |  | ||||||
|                 return 1 |  | ||||||
|  |  | ||||||
|         assert MyWS.getint._wsme_definition.return_type == int |  | ||||||
|  |  | ||||||
|     def test_validate(self): |  | ||||||
|         class ComplexType(object): |  | ||||||
|             attr = int |  | ||||||
|  |  | ||||||
|         class MyWS(object): |  | ||||||
|             @expose(int) |  | ||||||
|             @validate(int, int, int) |  | ||||||
|             def add(self, a, b, c=0): |  | ||||||
|                 return a + b + c |  | ||||||
|  |  | ||||||
|             @expose(bool) |  | ||||||
|             @validate(ComplexType) |  | ||||||
|             def setcplx(self, obj): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         MyWS.add._wsme_definition.resolve_types(wsme.types.registry) |  | ||||||
|         MyWS.setcplx._wsme_definition.resolve_types(wsme.types.registry) |  | ||||||
|         args = MyWS.add._wsme_definition.arguments |  | ||||||
|  |  | ||||||
|         assert args[0].name == 'a' |  | ||||||
|         assert args[0].datatype == int |  | ||||||
|         assert args[0].mandatory |  | ||||||
|         assert args[0].default is None |  | ||||||
|  |  | ||||||
|         assert args[1].name == 'b' |  | ||||||
|         assert args[1].datatype == int |  | ||||||
|         assert args[1].mandatory |  | ||||||
|         assert args[1].default is None |  | ||||||
|  |  | ||||||
|         assert args[2].name == 'c' |  | ||||||
|         assert args[2].datatype == int |  | ||||||
|         assert not args[2].mandatory |  | ||||||
|         assert args[2].default == 0 |  | ||||||
|  |  | ||||||
|         assert types.iscomplex(ComplexType) |  | ||||||
|  |  | ||||||
|     def test_validate_enum_with_none(self): |  | ||||||
|         class Version(object): |  | ||||||
|             number = types.Enum(str, 'v1', 'v2', None) |  | ||||||
|  |  | ||||||
|         class MyWS(WSRoot): |  | ||||||
|             @expose(str) |  | ||||||
|             @validate(Version) |  | ||||||
|             def setcplx(self, version): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         r = MyWS(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/setcplx', params={'version': {'number': 'arf'}}, |  | ||||||
|                             expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertTrue( |  | ||||||
|             res.json_body['faultstring'].startswith( |  | ||||||
|                 "Invalid input for field/attribute number. Value: 'arf'. \ |  | ||||||
| Value should be one of:")) |  | ||||||
|         self.assertIn('v1', res.json_body['faultstring']) |  | ||||||
|         self.assertIn('v2', res.json_body['faultstring']) |  | ||||||
|         self.assertIn('None', res.json_body['faultstring']) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_validate_enum_with_wrong_type(self): |  | ||||||
|         class Version(object): |  | ||||||
|             number = types.Enum(str, 'v1', 'v2', None) |  | ||||||
|  |  | ||||||
|         class MyWS(WSRoot): |  | ||||||
|             @expose(str) |  | ||||||
|             @validate(Version) |  | ||||||
|             def setcplx(self, version): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         r = MyWS(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/setcplx', params={'version': {'number': 1}}, |  | ||||||
|                             expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertTrue( |  | ||||||
|             res.json_body['faultstring'].startswith( |  | ||||||
|                 "Invalid input for field/attribute number. Value: '1'. \ |  | ||||||
| Value should be one of:")) |  | ||||||
|         self.assertIn('v1', res.json_body['faultstring']) |  | ||||||
|         self.assertIn('v2', res.json_body['faultstring']) |  | ||||||
|         self.assertIn('None', res.json_body['faultstring']) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_scan_api(self): |  | ||||||
|         class NS(object): |  | ||||||
|             @expose(int) |  | ||||||
|             @validate(int, int) |  | ||||||
|             def multiply(self, a, b): |  | ||||||
|                 return a * b |  | ||||||
|  |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             ns = NS() |  | ||||||
|  |  | ||||||
|         r = MyRoot() |  | ||||||
|  |  | ||||||
|         api = list(scan_api(r)) |  | ||||||
|         assert len(api) == 1 |  | ||||||
|         path, fd, args = api[0] |  | ||||||
|         assert path == ['ns', 'multiply'] |  | ||||||
|         assert fd._wsme_definition.name == 'multiply' |  | ||||||
|         assert args == [] |  | ||||||
|  |  | ||||||
|     def test_scan_subclass(self): |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             class SubClass(object): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         r = MyRoot() |  | ||||||
|         api = list(scan_api(r)) |  | ||||||
|  |  | ||||||
|         assert len(api) == 0 |  | ||||||
|  |  | ||||||
|     def test_scan_api_too_deep(self): |  | ||||||
|         class Loop(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         l = Loop() |  | ||||||
|         for i in range(0, 21): |  | ||||||
|             nl = Loop() |  | ||||||
|             nl.l = l |  | ||||||
|             l = nl |  | ||||||
|  |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             loop = l |  | ||||||
|  |  | ||||||
|         r = MyRoot() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             list(scan_api(r)) |  | ||||||
|             assert False, "ValueError not raised" |  | ||||||
|         except ValueError as e: |  | ||||||
|             assert str(e).startswith("Path is too long") |  | ||||||
|  |  | ||||||
|     def test_handle_request(self): |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             @expose() |  | ||||||
|             def touch(self): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         p = DummyProtocol() |  | ||||||
|         r = MyRoot(protocols=[p]) |  | ||||||
|  |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|  |  | ||||||
|         res = app.get('/') |  | ||||||
|  |  | ||||||
|         assert p.lastreq.path == '/' |  | ||||||
|         assert p.hits == 1 |  | ||||||
|  |  | ||||||
|         res = app.get('/touch?wsmeproto=dummy') |  | ||||||
|  |  | ||||||
|         assert p.lastreq.path == '/touch' |  | ||||||
|         assert p.hits == 2 |  | ||||||
|  |  | ||||||
|         class NoPathProto(DummyProtocol): |  | ||||||
|             def extract_path(self, request): |  | ||||||
|                 return None |  | ||||||
|  |  | ||||||
|         p = NoPathProto() |  | ||||||
|         r = MyRoot(protocols=[p]) |  | ||||||
|  |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|  |  | ||||||
|         res = app.get('/', expect_errors=True) |  | ||||||
|         print(res.status, res.body) |  | ||||||
|         assert res.status_int == 400 |  | ||||||
|  |  | ||||||
|     def test_no_available_protocol(self): |  | ||||||
|         r = WSRoot() |  | ||||||
|  |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|  |  | ||||||
|         res = app.get('/', expect_errors=True) |  | ||||||
|         print(res.status_int) |  | ||||||
|         assert res.status_int == 406 |  | ||||||
|         print(res.body) |  | ||||||
|         assert res.body.find( |  | ||||||
|             b("None of the following protocols can handle this request")) != -1 |  | ||||||
|  |  | ||||||
|     def test_return_content_type_guess(self): |  | ||||||
|         class DummierProto(DummyProtocol): |  | ||||||
|             content_types = ['text/xml', 'text/plain'] |  | ||||||
|  |  | ||||||
|         r = WSRoot([DummierProto()]) |  | ||||||
|  |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|  |  | ||||||
|         res = app.get('/', expect_errors=True, headers={ |  | ||||||
|             'Accept': 'text/xml,q=0.8'}) |  | ||||||
|         assert res.status_int == 400 |  | ||||||
|         assert res.content_type == 'text/xml', res.content_type |  | ||||||
|  |  | ||||||
|         res = app.get('/', expect_errors=True, headers={ |  | ||||||
|             'Accept': 'text/plain'}) |  | ||||||
|         assert res.status_int == 400 |  | ||||||
|         assert res.content_type == 'text/plain', res.content_type |  | ||||||
|  |  | ||||||
|     def test_double_expose(self): |  | ||||||
|         try: |  | ||||||
|             class MyRoot(WSRoot): |  | ||||||
|                 @expose() |  | ||||||
|                 @expose() |  | ||||||
|                 def atest(self): |  | ||||||
|                     pass |  | ||||||
|             assert False, "A ValueError should have been raised" |  | ||||||
|         except ValueError: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def test_multiple_expose(self): |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             def multiply(self, a, b): |  | ||||||
|                 return a * b |  | ||||||
|  |  | ||||||
|             mul_int = expose(int, int, int, wrap=True)(multiply) |  | ||||||
|  |  | ||||||
|             mul_float = expose( |  | ||||||
|                 float, float, float, |  | ||||||
|                 wrap=True)(multiply) |  | ||||||
|  |  | ||||||
|             mul_string = expose( |  | ||||||
|                 wsme.types.text, wsme.types.text, int, |  | ||||||
|                 wrap=True)(multiply) |  | ||||||
|  |  | ||||||
|         r = MyRoot(['restjson']) |  | ||||||
|  |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|  |  | ||||||
|         res = app.get('/mul_int?a=2&b=5', headers={ |  | ||||||
|             'Accept': 'application/json' |  | ||||||
|         }) |  | ||||||
|  |  | ||||||
|         self.assertEqual(res.body, b('10')) |  | ||||||
|  |  | ||||||
|         res = app.get('/mul_float?a=1.2&b=2.9', headers={ |  | ||||||
|             'Accept': 'application/json' |  | ||||||
|         }) |  | ||||||
|  |  | ||||||
|         self.assertEqual(res.body, b('3.48')) |  | ||||||
|  |  | ||||||
|         res = app.get('/mul_string?a=hello&b=2', headers={ |  | ||||||
|             'Accept': 'application/json' |  | ||||||
|         }) |  | ||||||
|  |  | ||||||
|         self.assertEqual(res.body, b('"hellohello"')) |  | ||||||
|  |  | ||||||
|     def test_wsattr_mandatory(self): |  | ||||||
|         class ComplexType(object): |  | ||||||
|             attr = wsme.types.wsattr(int, mandatory=True) |  | ||||||
|  |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             @expose(int, body=ComplexType) |  | ||||||
|             @validate(ComplexType) |  | ||||||
|             def clx(self, a): |  | ||||||
|                 return a.attr |  | ||||||
|  |  | ||||||
|         r = MyRoot(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/clx', params={}, expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_wsattr_readonly(self): |  | ||||||
|         class ComplexType(object): |  | ||||||
|             attr = wsme.types.wsattr(int, readonly=True) |  | ||||||
|  |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             @expose(int, body=ComplexType) |  | ||||||
|             @validate(ComplexType) |  | ||||||
|             def clx(self, a): |  | ||||||
|                 return a.attr |  | ||||||
|  |  | ||||||
|         r = MyRoot(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/clx', params={'attr': 1005}, expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertIn('Cannot set read only field.', |  | ||||||
|                       res.json_body['faultstring']) |  | ||||||
|         self.assertIn('1005', res.json_body['faultstring']) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_wsattr_default(self): |  | ||||||
|         class ComplexType(object): |  | ||||||
|             attr = wsme.types.wsattr(wsme.types.Enum(str, 'or', 'and'), |  | ||||||
|                                      default='and') |  | ||||||
|  |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             @expose(int) |  | ||||||
|             @validate(ComplexType) |  | ||||||
|             def clx(self, a): |  | ||||||
|                 return a.attr |  | ||||||
|  |  | ||||||
|         r = MyRoot(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/clx', params={}, expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_wsproperty_mandatory(self): |  | ||||||
|         class ComplexType(object): |  | ||||||
|             def foo(self): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|             attr = wsme.types.wsproperty(int, foo, foo, mandatory=True) |  | ||||||
|  |  | ||||||
|         class MyRoot(WSRoot): |  | ||||||
|             @expose(int, body=ComplexType) |  | ||||||
|             @validate(ComplexType) |  | ||||||
|             def clx(self, a): |  | ||||||
|                 return a.attr |  | ||||||
|  |  | ||||||
|         r = MyRoot(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/clx', params={}, expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_validate_enum_mandatory(self): |  | ||||||
|         class Version(object): |  | ||||||
|             number = wsme.types.wsattr(wsme.types.Enum(str, 'v1', 'v2'), |  | ||||||
|                                        mandatory=True) |  | ||||||
|  |  | ||||||
|         class MyWS(WSRoot): |  | ||||||
|             @expose(str) |  | ||||||
|             @validate(Version) |  | ||||||
|             def setcplx(self, version): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         r = MyWS(['restjson']) |  | ||||||
|         app = webtest.TestApp(r.wsgiapp()) |  | ||||||
|         res = app.post_json('/setcplx', params={'version': {}}, |  | ||||||
|                             expect_errors=True, |  | ||||||
|                             headers={'Accept': 'application/json'}) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestFunctionDefinition(unittest.TestCase): |  | ||||||
|  |  | ||||||
|     def test_get_arg(self): |  | ||||||
|         def myfunc(self): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         fd = wsme_api.FunctionDefinition(wsme_api.FunctionDefinition) |  | ||||||
|         fd.arguments.append(wsme_api.FunctionArgument('a', int, True, None)) |  | ||||||
|  |  | ||||||
|         assert fd.get_arg('a').datatype is int |  | ||||||
|         assert fd.get_arg('b') is None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestFormatException(unittest.TestCase): |  | ||||||
|  |  | ||||||
|     def _test_format_exception(self, exception, debug=False): |  | ||||||
|         fake_exc_info = (None, exception, None) |  | ||||||
|         return wsme_api.format_exception(fake_exc_info, debug=debug) |  | ||||||
|  |  | ||||||
|     def test_format_client_exception(self): |  | ||||||
|         faultstring = 'boom' |  | ||||||
|         ret = self._test_format_exception(exc.ClientSideError(faultstring)) |  | ||||||
|         self.assertIsNone(ret['debuginfo']) |  | ||||||
|         self.assertEqual('Client', ret['faultcode']) |  | ||||||
|         self.assertEqual(faultstring, ret['faultstring']) |  | ||||||
|  |  | ||||||
|     def test_format_client_exception_unicode(self): |  | ||||||
|         faultstring = u'\xc3\xa3o' |  | ||||||
|         ret = self._test_format_exception(exc.ClientSideError(faultstring)) |  | ||||||
|         self.assertIsNone(ret['debuginfo']) |  | ||||||
|         self.assertEqual('Client', ret['faultcode']) |  | ||||||
|         self.assertEqual(faultstring, ret['faultstring']) |  | ||||||
|  |  | ||||||
|     def test_format_server_exception(self): |  | ||||||
|         faultstring = 'boom' |  | ||||||
|         ret = self._test_format_exception(Exception(faultstring)) |  | ||||||
|         self.assertIsNone(ret['debuginfo']) |  | ||||||
|         self.assertEqual('Server', ret['faultcode']) |  | ||||||
|         self.assertEqual(faultstring, ret['faultstring']) |  | ||||||
|  |  | ||||||
|     def test_format_server_exception_unicode(self): |  | ||||||
|         faultstring = u'\xc3\xa3o' |  | ||||||
|         ret = self._test_format_exception(Exception(faultstring)) |  | ||||||
|         self.assertIsNone(ret['debuginfo']) |  | ||||||
|         self.assertEqual('Server', ret['faultcode']) |  | ||||||
|         self.assertEqual(faultstring, ret['faultstring']) |  | ||||||
|  |  | ||||||
|     def test_format_server_exception_debug(self): |  | ||||||
|         faultstring = 'boom' |  | ||||||
|         ret = self._test_format_exception(Exception(faultstring), debug=True) |  | ||||||
|         # assert debuginfo is populated |  | ||||||
|         self.assertIsNotNone(ret['debuginfo']) |  | ||||||
|         self.assertEqual('Server', ret['faultcode']) |  | ||||||
|         self.assertEqual(faultstring, ret['faultstring']) |  | ||||||
| @@ -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 |  | ||||||
| @@ -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') |  | ||||||
| @@ -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)) |  | ||||||
| @@ -1,779 +0,0 @@ | |||||||
| import base64 |  | ||||||
| import datetime |  | ||||||
| import decimal |  | ||||||
|  |  | ||||||
| import wsme.tests.protocol |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import simplejson as json |  | ||||||
| except: |  | ||||||
|     import json  # noqa |  | ||||||
|  |  | ||||||
| from wsme.rest.json import fromjson, tojson, parse |  | ||||||
| from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate |  | ||||||
| from wsme.types import isarray, isdict, isusertype, register_type |  | ||||||
| from wsme.types import UserType, ArrayType, DictType |  | ||||||
| from wsme.rest import expose, validate |  | ||||||
| from wsme.exc import ClientSideError, InvalidInput |  | ||||||
|  |  | ||||||
|  |  | ||||||
| import six |  | ||||||
| from six import b, u |  | ||||||
|  |  | ||||||
| if six.PY3: |  | ||||||
|     from urllib.parse import urlencode |  | ||||||
| else: |  | ||||||
|     from urllib import urlencode  # noqa |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def prepare_value(value, datatype): |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         return [prepare_value(item, datatype[0]) for item in value] |  | ||||||
|     if isinstance(datatype, dict): |  | ||||||
|         key_type, value_type = list(datatype.items())[0] |  | ||||||
|         return dict(( |  | ||||||
|             (prepare_value(item[0], key_type), |  | ||||||
|                 prepare_value(item[1], value_type)) |  | ||||||
|             for item in value.items() |  | ||||||
|         )) |  | ||||||
|     if datatype in (datetime.date, datetime.time, datetime.datetime): |  | ||||||
|         return value.isoformat() |  | ||||||
|     if datatype == decimal.Decimal: |  | ||||||
|         return str(value) |  | ||||||
|     if datatype == wsme.types.binary: |  | ||||||
|         return base64.encodestring(value).decode('ascii') |  | ||||||
|     if datatype == wsme.types.bytes: |  | ||||||
|         return value.decode('ascii') |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def prepare_result(value, datatype): |  | ||||||
|     print(value, datatype) |  | ||||||
|     if value is None: |  | ||||||
|         return None |  | ||||||
|     if datatype == wsme.types.binary: |  | ||||||
|         return base64.decodestring(value.encode('ascii')) |  | ||||||
|     if isusertype(datatype): |  | ||||||
|         datatype = datatype.basetype |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         return [prepare_result(item, datatype[0]) for item in value] |  | ||||||
|     if isarray(datatype): |  | ||||||
|         return [prepare_result(item, datatype.item_type) for item in value] |  | ||||||
|     if isinstance(datatype, dict): |  | ||||||
|         return dict(( |  | ||||||
|             (prepare_result(item[0], list(datatype.keys())[0]), |  | ||||||
|                 prepare_result(item[1], list(datatype.values())[0])) |  | ||||||
|             for item in value.items() |  | ||||||
|         )) |  | ||||||
|     if isdict(datatype): |  | ||||||
|         return dict(( |  | ||||||
|             (prepare_result(item[0], datatype.key_type), |  | ||||||
|                 prepare_result(item[1], datatype.value_type)) |  | ||||||
|             for item in value.items() |  | ||||||
|         )) |  | ||||||
|     if datatype == datetime.date: |  | ||||||
|         return parse_isodate(value) |  | ||||||
|     if datatype == datetime.time: |  | ||||||
|         return parse_isotime(value) |  | ||||||
|     if datatype == datetime.datetime: |  | ||||||
|         return parse_isodatetime(value) |  | ||||||
|     if hasattr(datatype, '_wsme_attributes'): |  | ||||||
|         for attr in datatype._wsme_attributes: |  | ||||||
|             if attr.key not in value: |  | ||||||
|                 continue |  | ||||||
|             value[attr.key] = prepare_result(value[attr.key], attr.datatype) |  | ||||||
|         return value |  | ||||||
|     if datatype == wsme.types.bytes: |  | ||||||
|         return value.encode('ascii') |  | ||||||
|     if type(value) != datatype: |  | ||||||
|         print(type(value), datatype) |  | ||||||
|         return datatype(value) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CustomInt(UserType): |  | ||||||
|     basetype = int |  | ||||||
|     name = "custom integer" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Obj(wsme.types.Base): |  | ||||||
|     id = int |  | ||||||
|     name = wsme.types.text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NestedObj(wsme.types.Base): |  | ||||||
|     o = Obj |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CRUDResult(object): |  | ||||||
|     data = Obj |  | ||||||
|     message = wsme.types.text |  | ||||||
|  |  | ||||||
|     def __init__(self, data=wsme.types.Unset, message=wsme.types.Unset): |  | ||||||
|         self.data = data |  | ||||||
|         self.message = message |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MiniCrud(object): |  | ||||||
|     @expose(CRUDResult, method='PUT') |  | ||||||
|     @validate(Obj) |  | ||||||
|     def create(self, data): |  | ||||||
|         print(repr(data)) |  | ||||||
|         return CRUDResult(data, u('create')) |  | ||||||
|  |  | ||||||
|     @expose(CRUDResult, method='GET', ignore_extra_args=True) |  | ||||||
|     @validate(Obj) |  | ||||||
|     def read(self, ref): |  | ||||||
|         print(repr(ref)) |  | ||||||
|         if ref.id == 1: |  | ||||||
|             ref.name = u('test') |  | ||||||
|         return CRUDResult(ref, u('read')) |  | ||||||
|  |  | ||||||
|     @expose(CRUDResult, method='POST') |  | ||||||
|     @validate(Obj) |  | ||||||
|     def update(self, data): |  | ||||||
|         print(repr(data)) |  | ||||||
|         return CRUDResult(data, u('update')) |  | ||||||
|  |  | ||||||
|     @expose(CRUDResult, wsme.types.text, body=Obj) |  | ||||||
|     def update_with_body(self, msg, data): |  | ||||||
|         print(repr(data)) |  | ||||||
|         return CRUDResult(data, msg) |  | ||||||
|  |  | ||||||
|     @expose(CRUDResult, method='DELETE') |  | ||||||
|     @validate(Obj) |  | ||||||
|     def delete(self, ref): |  | ||||||
|         print(repr(ref)) |  | ||||||
|         if ref.id == 1: |  | ||||||
|             ref.name = u('test') |  | ||||||
|         return CRUDResult(ref, u('delete')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| wsme.tests.protocol.WSTestRoot.crud = MiniCrud() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase): |  | ||||||
|     protocol = 'restjson' |  | ||||||
|  |  | ||||||
|     def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False, |  | ||||||
|              body=None, **kw): |  | ||||||
|         if body: |  | ||||||
|             if isinstance(body, tuple): |  | ||||||
|                 body, datatype = body |  | ||||||
|             else: |  | ||||||
|                 datatype = type(body) |  | ||||||
|             body = prepare_value(body, datatype) |  | ||||||
|             content = json.dumps(body) |  | ||||||
|         else: |  | ||||||
|             for key in kw: |  | ||||||
|                 if isinstance(kw[key], tuple): |  | ||||||
|                     value, datatype = kw[key] |  | ||||||
|                 else: |  | ||||||
|                     value = kw[key] |  | ||||||
|                     datatype = type(value) |  | ||||||
|                 kw[key] = prepare_value(value, datatype) |  | ||||||
|             content = json.dumps(kw) |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'application/json', |  | ||||||
|         } |  | ||||||
|         if _accept is not None: |  | ||||||
|             headers["Accept"] = _accept |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/' + fpath, |  | ||||||
|             content, |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=True) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|  |  | ||||||
|         if _no_result_decode: |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         r = json.loads(res.text) |  | ||||||
|         if res.status_int == 200: |  | ||||||
|             if _rt and r: |  | ||||||
|                 r = prepare_result(r, _rt) |  | ||||||
|             return r |  | ||||||
|         else: |  | ||||||
|             raise wsme.tests.protocol.CallException( |  | ||||||
|                 r['faultcode'], |  | ||||||
|                 r['faultstring'], |  | ||||||
|                 r.get('debuginfo') |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         return json.loads(res.text) |  | ||||||
|  |  | ||||||
|     def test_fromjson(self): |  | ||||||
|         assert fromjson(str, None) is None |  | ||||||
|  |  | ||||||
|     def test_keyargs(self): |  | ||||||
|         r = self.app.get('/argtypes/setint.json?value=2') |  | ||||||
|         print(r) |  | ||||||
|         assert json.loads(r.text) == 2 |  | ||||||
|  |  | ||||||
|         nestedarray = 'value[0].inner.aint=54&value[1].inner.aint=55' |  | ||||||
|         r = self.app.get('/argtypes/setnestedarray.json?' + nestedarray) |  | ||||||
|         print(r) |  | ||||||
|         assert json.loads(r.text) == [ |  | ||||||
|             {'inner': {'aint': 54}}, |  | ||||||
|             {'inner': {'aint': 55}}] |  | ||||||
|  |  | ||||||
|     def test_form_urlencoded_args(self): |  | ||||||
|         params = { |  | ||||||
|             'value[0].inner.aint': 54, |  | ||||||
|             'value[1].inner.aint': 55 |  | ||||||
|         } |  | ||||||
|         body = urlencode(params) |  | ||||||
|         r = self.app.post( |  | ||||||
|             '/argtypes/setnestedarray.json', |  | ||||||
|             body, |  | ||||||
|             headers={'Content-Type': 'application/x-www-form-urlencoded'} |  | ||||||
|         ) |  | ||||||
|         print(r) |  | ||||||
|  |  | ||||||
|         assert json.loads(r.text) == [ |  | ||||||
|             {'inner': {'aint': 54}}, |  | ||||||
|             {'inner': {'aint': 55}}] |  | ||||||
|  |  | ||||||
|     def test_body_and_params(self): |  | ||||||
|         r = self.app.post('/argtypes/setint.json?value=2', '{"value": 2}', |  | ||||||
|                           headers={"Content-Type": "application/json"}, |  | ||||||
|                           expect_errors=True) |  | ||||||
|         print(r) |  | ||||||
|         assert r.status_int == 400 |  | ||||||
|         assert json.loads(r.text)['faultstring'] == \ |  | ||||||
|             "Parameter value was given several times" |  | ||||||
|  |  | ||||||
|     def test_inline_body(self): |  | ||||||
|         params = urlencode({'__body__': '{"value": 4}'}) |  | ||||||
|         r = self.app.get('/argtypes/setint.json?' + params) |  | ||||||
|         print(r) |  | ||||||
|         assert json.loads(r.text) == 4 |  | ||||||
|  |  | ||||||
|     def test_empty_body(self): |  | ||||||
|         params = urlencode({'__body__': ''}) |  | ||||||
|         r = self.app.get('/returntypes/getint.json?' + params) |  | ||||||
|         print(r) |  | ||||||
|         assert json.loads(r.text) == 2 |  | ||||||
|  |  | ||||||
|     def test_invalid_content_type_body(self): |  | ||||||
|         r = self.app.post('/argtypes/setint.json', '{"value": 2}', |  | ||||||
|                           headers={"Content-Type": "application/invalid"}, |  | ||||||
|                           expect_errors=True) |  | ||||||
|         print(r) |  | ||||||
|         assert r.status_int == 415 |  | ||||||
|         assert json.loads(r.text)['faultstring'] == \ |  | ||||||
|             "Unknown mimetype: application/invalid" |  | ||||||
|  |  | ||||||
|     def test_invalid_json_body(self): |  | ||||||
|         r = self.app.post('/argtypes/setint.json', '{"value": 2', |  | ||||||
|                           headers={"Content-Type": "application/json"}, |  | ||||||
|                           expect_errors=True) |  | ||||||
|         print(r) |  | ||||||
|         assert r.status_int == 400 |  | ||||||
|         assert json.loads(r.text)['faultstring'] == \ |  | ||||||
|             "Request is not in valid JSON format" |  | ||||||
|  |  | ||||||
|     def test_unknown_arg(self): |  | ||||||
|         r = self.app.post('/returntypes/getint.json', '{"a": 2}', |  | ||||||
|                           headers={"Content-Type": "application/json"}, |  | ||||||
|                           expect_errors=True) |  | ||||||
|         print(r) |  | ||||||
|         assert r.status_int == 400 |  | ||||||
|         assert json.loads(r.text)['faultstring'].startswith( |  | ||||||
|             "Unknown argument:" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         r = self.app.get('/returntypes/getint.json?a=2', expect_errors=True) |  | ||||||
|         print(r) |  | ||||||
|         assert r.status_int == 400 |  | ||||||
|         assert json.loads(r.text)['faultstring'].startswith( |  | ||||||
|             "Unknown argument:" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def test_set_custom_object(self): |  | ||||||
|         r = self.app.post( |  | ||||||
|             '/argtypes/setcustomobject', |  | ||||||
|             '{"value": {"aint": 2, "name": "test"}}', |  | ||||||
|             headers={"Content-Type": "application/json"} |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(r.status_int, 200) |  | ||||||
|         self.assertEqual(r.json, {'aint': 2, 'name': 'test'}) |  | ||||||
|  |  | ||||||
|     def test_set_extended_int(self): |  | ||||||
|         r = self.app.post( |  | ||||||
|             '/argtypes/setextendedint', |  | ||||||
|             '{"value": 3}', |  | ||||||
|             headers={"Content-Type": "application/json"} |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(r.status_int, 200) |  | ||||||
|         self.assertEqual(r.json, 3) |  | ||||||
|  |  | ||||||
|     def test_unset_attrs(self): |  | ||||||
|         class AType(object): |  | ||||||
|             attr = int |  | ||||||
|  |  | ||||||
|         wsme.types.register_type(AType) |  | ||||||
|  |  | ||||||
|         j = tojson(AType, AType()) |  | ||||||
|         assert j == {} |  | ||||||
|  |  | ||||||
|     def test_array_tojson(self): |  | ||||||
|         assert tojson([int], None) is None |  | ||||||
|         assert tojson([int], []) == [] |  | ||||||
|         assert tojson([str], ['1', '4']) == ['1', '4'] |  | ||||||
|  |  | ||||||
|     def test_dict_tojson(self): |  | ||||||
|         assert tojson({int: str}, None) is None |  | ||||||
|         assert tojson({int: str}, {5: '5'}) == {5: '5'} |  | ||||||
|  |  | ||||||
|     def test_None_tojson(self): |  | ||||||
|         for dt in (datetime.date, datetime.time, datetime.datetime, |  | ||||||
|                    decimal.Decimal): |  | ||||||
|             assert tojson(dt, None) is None |  | ||||||
|  |  | ||||||
|     def test_None_fromjson(self): |  | ||||||
|         for dt in (str, int, datetime.date, datetime.time, datetime.datetime, |  | ||||||
|                    decimal.Decimal, [int], {int: int}): |  | ||||||
|             assert fromjson(dt, None) is None |  | ||||||
|  |  | ||||||
|     def test_parse_valid_date(self): |  | ||||||
|         j = parse('{"a": "2011-01-01"}', {'a': datetime.date}, False) |  | ||||||
|         assert isinstance(j['a'], datetime.date) |  | ||||||
|  |  | ||||||
|     def test_invalid_root_dict_fromjson(self): |  | ||||||
|         try: |  | ||||||
|             parse('["invalid"]', {'a': ArrayType(str)}, False) |  | ||||||
|             assert False |  | ||||||
|         except Exception as e: |  | ||||||
|             assert isinstance(e, ClientSideError) |  | ||||||
|             assert e.msg == "Request must be a JSON dict" |  | ||||||
|  |  | ||||||
|     def test_invalid_list_fromjson(self): |  | ||||||
|         jlist = "invalid" |  | ||||||
|         try: |  | ||||||
|             parse('{"a": "%s"}' % jlist, {'a': ArrayType(str)}, False) |  | ||||||
|             assert False |  | ||||||
|         except Exception as e: |  | ||||||
|             assert isinstance(e, InvalidInput) |  | ||||||
|             assert e.fieldname == 'a' |  | ||||||
|             assert e.value == jlist |  | ||||||
|             assert e.msg == "Value not a valid list: %s" % jlist |  | ||||||
|  |  | ||||||
|     def test_invalid_dict_fromjson(self): |  | ||||||
|         jdict = "invalid" |  | ||||||
|         try: |  | ||||||
|             parse('{"a": "%s"}' % jdict, {'a': DictType(str, str)}, False) |  | ||||||
|             assert False |  | ||||||
|         except Exception as e: |  | ||||||
|             assert isinstance(e, InvalidInput) |  | ||||||
|             assert e.fieldname == 'a' |  | ||||||
|             assert e.value == jdict |  | ||||||
|             assert e.msg == "Value not a valid dict: %s" % jdict |  | ||||||
|  |  | ||||||
|     def test_invalid_date_fromjson(self): |  | ||||||
|         jdate = "2015-01-invalid" |  | ||||||
|         try: |  | ||||||
|             parse('{"a": "%s"}' % jdate, {'a': datetime.date}, False) |  | ||||||
|             assert False |  | ||||||
|         except Exception as e: |  | ||||||
|             assert isinstance(e, InvalidInput) |  | ||||||
|             assert e.fieldname == 'a' |  | ||||||
|             assert e.value == jdate |  | ||||||
|             assert e.msg == "'%s' is not a legal date value" % jdate |  | ||||||
|  |  | ||||||
|     def test_parse_valid_date_bodyarg(self): |  | ||||||
|         j = parse('"2011-01-01"', {'a': datetime.date}, True) |  | ||||||
|         assert isinstance(j['a'], datetime.date) |  | ||||||
|  |  | ||||||
|     def test_invalid_date_fromjson_bodyarg(self): |  | ||||||
|         jdate = "2015-01-invalid" |  | ||||||
|         try: |  | ||||||
|             parse('"%s"' % jdate, {'a': datetime.date}, True) |  | ||||||
|             assert False |  | ||||||
|         except Exception as e: |  | ||||||
|             assert isinstance(e, InvalidInput) |  | ||||||
|             assert e.fieldname == 'a' |  | ||||||
|             assert e.value == jdate |  | ||||||
|             assert e.msg == "'%s' is not a legal date value" % jdate |  | ||||||
|  |  | ||||||
|     def test_valid_str_to_builtin_fromjson(self): |  | ||||||
|         types = six.integer_types + (bool, float) |  | ||||||
|         value = '2' |  | ||||||
|         for t in types: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '%s' if ba else '{"a": %s}' |  | ||||||
|                 i = parse(jd % value, {'a': t}, ba) |  | ||||||
|                 self.assertEqual( |  | ||||||
|                     i, {'a': t(value)}, |  | ||||||
|                     "Parsed value does not correspond for %s: " |  | ||||||
|                     "%s != {'a': %s}" % ( |  | ||||||
|                         t, repr(i), repr(t(value)) |  | ||||||
|                     ) |  | ||||||
|                 ) |  | ||||||
|                 self.assertIsInstance(i['a'], t) |  | ||||||
|  |  | ||||||
|     def test_valid_int_fromjson(self): |  | ||||||
|         value = 2 |  | ||||||
|         for ba in True, False: |  | ||||||
|             jd = '%d' if ba else '{"a": %d}' |  | ||||||
|             i = parse(jd % value, {'a': int}, ba) |  | ||||||
|             self.assertEqual(i, {'a': 2}) |  | ||||||
|             self.assertIsInstance(i['a'], int) |  | ||||||
|  |  | ||||||
|     def test_valid_num_to_float_fromjson(self): |  | ||||||
|         values = 2, 2.3 |  | ||||||
|         for v in values: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '%f' if ba else '{"a": %f}' |  | ||||||
|                 i = parse(jd % v, {'a': float}, ba) |  | ||||||
|                 self.assertEqual(i, {'a': float(v)}) |  | ||||||
|                 self.assertIsInstance(i['a'], float) |  | ||||||
|  |  | ||||||
|     def test_invalid_str_to_buitin_fromjson(self): |  | ||||||
|         types = six.integer_types + (float, bool) |  | ||||||
|         value = '2a' |  | ||||||
|         for t in types: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '"%s"' if ba else '{"a": "%s"}' |  | ||||||
|                 try: |  | ||||||
|                     parse(jd % value, {'a': t}, ba) |  | ||||||
|                     assert False, ( |  | ||||||
|                         "Value '%s' should not parse correctly for %s." % |  | ||||||
|                         (value, t) |  | ||||||
|                     ) |  | ||||||
|                 except ClientSideError as e: |  | ||||||
|                     self.assertIsInstance(e, InvalidInput) |  | ||||||
|                     self.assertEqual(e.fieldname, 'a') |  | ||||||
|                     self.assertEqual(e.value, value) |  | ||||||
|  |  | ||||||
|     def test_ambiguous_to_bool(self): |  | ||||||
|         amb_values = ('', 'randomstring', '2', '-32', 'not true') |  | ||||||
|         for value in amb_values: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '"%s"' if ba else '{"a": "%s"}' |  | ||||||
|                 try: |  | ||||||
|                     parse(jd % value, {'a': bool}, ba) |  | ||||||
|                     assert False, ( |  | ||||||
|                         "Value '%s' should not parse correctly for %s." % |  | ||||||
|                         (value, bool) |  | ||||||
|                     ) |  | ||||||
|                 except ClientSideError as e: |  | ||||||
|                     self.assertIsInstance(e, InvalidInput) |  | ||||||
|                     self.assertEqual(e.fieldname, 'a') |  | ||||||
|                     self.assertEqual(e.value, value) |  | ||||||
|  |  | ||||||
|     def test_true_strings_to_bool(self): |  | ||||||
|         true_values = ('true', 't', 'yes', 'y', 'on', '1') |  | ||||||
|         for value in true_values: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '"%s"' if ba else '{"a": "%s"}' |  | ||||||
|                 i = parse(jd % value, {'a': bool}, ba) |  | ||||||
|                 self.assertIsInstance(i['a'], bool) |  | ||||||
|                 self.assertTrue(i['a']) |  | ||||||
|  |  | ||||||
|     def test_false_strings_to_bool(self): |  | ||||||
|         false_values = ('false', 'f', 'no', 'n', 'off', '0') |  | ||||||
|         for value in false_values: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '"%s"' if ba else '{"a": "%s"}' |  | ||||||
|                 i = parse(jd % value, {'a': bool}, ba) |  | ||||||
|                 self.assertIsInstance(i['a'], bool) |  | ||||||
|                 self.assertFalse(i['a']) |  | ||||||
|  |  | ||||||
|     def test_true_ints_to_bool(self): |  | ||||||
|         true_values = (1, 5, -3) |  | ||||||
|         for value in true_values: |  | ||||||
|             for ba in True, False: |  | ||||||
|                 jd = '%d' if ba else '{"a": %d}' |  | ||||||
|                 i = parse(jd % value, {'a': bool}, ba) |  | ||||||
|                 self.assertIsInstance(i['a'], bool) |  | ||||||
|                 self.assertTrue(i['a']) |  | ||||||
|  |  | ||||||
|     def test_false_ints_to_bool(self): |  | ||||||
|         value = 0 |  | ||||||
|         for ba in True, False: |  | ||||||
|             jd = '%d' if ba else '{"a": %d}' |  | ||||||
|             i = parse(jd % value, {'a': bool}, ba) |  | ||||||
|             self.assertIsInstance(i['a'], bool) |  | ||||||
|             self.assertFalse(i['a']) |  | ||||||
|  |  | ||||||
|     def test_valid_simple_custom_type_fromjson(self): |  | ||||||
|         value = 2 |  | ||||||
|         for ba in True, False: |  | ||||||
|             jd = '"%d"' if ba else '{"a": "%d"}' |  | ||||||
|             i = parse(jd % value, {'a': CustomInt()}, ba) |  | ||||||
|             self.assertEqual(i, {'a': 2}) |  | ||||||
|             self.assertIsInstance(i['a'], int) |  | ||||||
|  |  | ||||||
|     def test_invalid_simple_custom_type_fromjson(self): |  | ||||||
|         value = '2b' |  | ||||||
|         for ba in True, False: |  | ||||||
|             jd = '"%s"' if ba else '{"a": "%s"}' |  | ||||||
|             try: |  | ||||||
|                 i = parse(jd % value, {'a': CustomInt()}, ba) |  | ||||||
|                 self.assertEqual(i, {'a': 2}) |  | ||||||
|             except ClientSideError as e: |  | ||||||
|                 self.assertIsInstance(e, InvalidInput) |  | ||||||
|                 self.assertEqual(e.fieldname, 'a') |  | ||||||
|                 self.assertEqual(e.value, value) |  | ||||||
|                 self.assertEqual( |  | ||||||
|                     e.msg, |  | ||||||
|                     "invalid literal for int() with base 10: '%s'" % value |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|     def test_parse_unexpected_attribute(self): |  | ||||||
|         o = { |  | ||||||
|             "id": "1", |  | ||||||
|             "name": "test", |  | ||||||
|             "other": "unknown", |  | ||||||
|             "other2": "still unknown", |  | ||||||
|         } |  | ||||||
|         for ba in True, False: |  | ||||||
|             jd = o if ba else {"o": o} |  | ||||||
|             try: |  | ||||||
|                 parse(json.dumps(jd), {'o': Obj}, ba) |  | ||||||
|                 raise AssertionError("Object should not parse correcty.") |  | ||||||
|             except wsme.exc.UnknownAttribute as e: |  | ||||||
|                 self.assertEqual(e.attributes, set(['other', 'other2'])) |  | ||||||
|  |  | ||||||
|     def test_parse_unexpected_nested_attribute(self): |  | ||||||
|         no = { |  | ||||||
|             "o": { |  | ||||||
|                 "id": "1", |  | ||||||
|                 "name": "test", |  | ||||||
|                 "other": "unknown", |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|         for ba in False, True: |  | ||||||
|             jd = no if ba else {"no": no} |  | ||||||
|             try: |  | ||||||
|                 parse(json.dumps(jd), {'no': NestedObj}, ba) |  | ||||||
|             except wsme.exc.UnknownAttribute as e: |  | ||||||
|                 self.assertEqual(e.attributes, set(['other'])) |  | ||||||
|                 self.assertEqual(e.fieldname, "no.o") |  | ||||||
|  |  | ||||||
|     def test_nest_result(self): |  | ||||||
|         self.root.protocols[0].nest_result = True |  | ||||||
|         r = self.app.get('/returntypes/getint.json') |  | ||||||
|         print(r) |  | ||||||
|         assert json.loads(r.text) == {"result": 2} |  | ||||||
|  |  | ||||||
|     def test_encode_sample_value(self): |  | ||||||
|         class MyType(object): |  | ||||||
|             aint = int |  | ||||||
|             astr = str |  | ||||||
|  |  | ||||||
|         register_type(MyType) |  | ||||||
|  |  | ||||||
|         v = MyType() |  | ||||||
|         v.aint = 4 |  | ||||||
|         v.astr = 's' |  | ||||||
|  |  | ||||||
|         r = wsme.rest.json.encode_sample_value(MyType, v, True) |  | ||||||
|         print(r) |  | ||||||
|         assert r[0] == ('javascript') |  | ||||||
|         assert r[1] == json.dumps({'aint': 4, 'astr': 's'}, ensure_ascii=False, |  | ||||||
|                                   indent=4, sort_keys=True) |  | ||||||
|  |  | ||||||
|     def test_bytes_tojson(self): |  | ||||||
|         assert tojson(wsme.types.bytes, None) is None |  | ||||||
|         assert tojson(wsme.types.bytes, b('ascii')) == u('ascii') |  | ||||||
|  |  | ||||||
|     def test_encode_sample_params(self): |  | ||||||
|         r = wsme.rest.json.encode_sample_params( |  | ||||||
|             [('a', int, 2)], True |  | ||||||
|         ) |  | ||||||
|         assert r[0] == 'javascript', r[0] |  | ||||||
|         assert r[1] == '''{ |  | ||||||
|     "a": 2 |  | ||||||
| }''', r[1] |  | ||||||
|  |  | ||||||
|     def test_encode_sample_result(self): |  | ||||||
|         r = wsme.rest.json.encode_sample_result( |  | ||||||
|             int, 2, True |  | ||||||
|         ) |  | ||||||
|         assert r[0] == 'javascript', r[0] |  | ||||||
|         assert r[1] == '''2''' |  | ||||||
|  |  | ||||||
|     def test_PUT(self): |  | ||||||
|         data = {"id": 1, "name": u("test")} |  | ||||||
|         content = json.dumps(dict(data=data)) |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'application/json', |  | ||||||
|         } |  | ||||||
|         res = self.app.put( |  | ||||||
|             '/crud', |  | ||||||
|             content, |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|         assert result['message'] == "create" |  | ||||||
|  |  | ||||||
|     def test_GET(self): |  | ||||||
|         headers = { |  | ||||||
|             'Accept': 'application/json', |  | ||||||
|         } |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/crud?ref.id=1', |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|  |  | ||||||
|     def test_GET_complex_accept(self): |  | ||||||
|         headers = { |  | ||||||
|             'Accept': 'text/html,application/xml;q=0.9,*/*;q=0.8' |  | ||||||
|         } |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/crud?ref.id=1', |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|  |  | ||||||
|     def test_GET_complex_choose_xml(self): |  | ||||||
|         headers = { |  | ||||||
|             'Accept': 'text/html,text/xml;q=0.9,*/*;q=0.8' |  | ||||||
|         } |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/crud?ref.id=1', |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         assert res.content_type == 'text/xml' |  | ||||||
|  |  | ||||||
|     def test_GET_complex_accept_no_match(self): |  | ||||||
|         headers = { |  | ||||||
|             'Accept': 'text/html,application/xml;q=0.9' |  | ||||||
|         } |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/crud?ref.id=1', |  | ||||||
|             headers=headers, |  | ||||||
|             status=406) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         assert res.body == b("Unacceptable Accept type: " |  | ||||||
|                              "text/html, application/xml;q=0.9 not in " |  | ||||||
|                              "['application/json', 'text/javascript', " |  | ||||||
|                              "'application/javascript', 'text/xml']") |  | ||||||
|  |  | ||||||
|     def test_GET_bad_simple_accept(self): |  | ||||||
|         headers = { |  | ||||||
|             'Accept': 'text/plain', |  | ||||||
|         } |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/crud?ref.id=1', |  | ||||||
|             headers=headers, |  | ||||||
|             status=406) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         assert res.body == b("Unacceptable Accept type: text/plain not in " |  | ||||||
|                              "['application/json', 'text/javascript', " |  | ||||||
|                              "'application/javascript', 'text/xml']") |  | ||||||
|  |  | ||||||
|     def test_POST(self): |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'application/json', |  | ||||||
|         } |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/crud', |  | ||||||
|             json.dumps(dict(data=dict(id=1, name=u('test')))), |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|         assert result['message'] == "update" |  | ||||||
|  |  | ||||||
|     def test_POST_bad_content_type(self): |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'text/plain', |  | ||||||
|         } |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/crud', |  | ||||||
|             json.dumps(dict(data=dict(id=1, name=u('test')))), |  | ||||||
|             headers=headers, |  | ||||||
|             status=415) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         assert res.body == b("Unacceptable Content-Type: text/plain not in " |  | ||||||
|                              "['application/json', 'text/javascript', " |  | ||||||
|                              "'application/javascript', 'text/xml']") |  | ||||||
|  |  | ||||||
|     def test_DELETE(self): |  | ||||||
|         res = self.app.delete( |  | ||||||
|             '/crud.json?ref.id=1', |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|         assert result['message'] == "delete" |  | ||||||
|  |  | ||||||
|     def test_extra_arguments(self): |  | ||||||
|         headers = { |  | ||||||
|             'Accept': 'application/json', |  | ||||||
|         } |  | ||||||
|         res = self.app.get( |  | ||||||
|             '/crud?ref.id=1&extraarg=foo', |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|         assert result['message'] == "read" |  | ||||||
|  |  | ||||||
|     def test_unexpected_extra_arg(self): |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'application/json', |  | ||||||
|         } |  | ||||||
|         data = {"id": 1, "name": "test"} |  | ||||||
|         content = json.dumps({"data": data, "other": "unexpected"}) |  | ||||||
|         res = self.app.put( |  | ||||||
|             '/crud', |  | ||||||
|             content, |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=True) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_unexpected_extra_attribute(self): |  | ||||||
|         """Expect a failure if we send an unexpected object attribute.""" |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'application/json', |  | ||||||
|         } |  | ||||||
|         data = {"id": 1, "name": "test", "other": "unexpected"} |  | ||||||
|         content = json.dumps({"data": data}) |  | ||||||
|         res = self.app.put( |  | ||||||
|             '/crud', |  | ||||||
|             content, |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=True) |  | ||||||
|         self.assertEqual(res.status_int, 400) |  | ||||||
|  |  | ||||||
|     def test_body_arg(self): |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'application/json', |  | ||||||
|         } |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/crud/update_with_body?msg=hello', |  | ||||||
|             json.dumps(dict(id=1, name=u('test'))), |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=False) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|         result = json.loads(res.text) |  | ||||||
|         print(result) |  | ||||||
|         assert result['data']['id'] == 1 |  | ||||||
|         assert result['data']['name'] == u("test") |  | ||||||
|         assert result['message'] == "hello" |  | ||||||
| @@ -1,211 +0,0 @@ | |||||||
| import decimal |  | ||||||
| import datetime |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| from six import u, b |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| import wsme.tests.protocol |  | ||||||
| from wsme.utils import parse_isodatetime, parse_isodate, parse_isotime |  | ||||||
| from wsme.types import isarray, isdict, isusertype, register_type |  | ||||||
|  |  | ||||||
| from wsme.rest.xml import fromxml, toxml |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import xml.etree.ElementTree as et |  | ||||||
| except: |  | ||||||
|     import cElementTree as et  # noqa |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def dumpxml(key, obj, datatype=None): |  | ||||||
|     el = et.Element(key) |  | ||||||
|     if isinstance(obj, tuple): |  | ||||||
|         obj, datatype = obj |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         for item in obj: |  | ||||||
|             el.append(dumpxml('item', item, datatype[0])) |  | ||||||
|     elif isinstance(datatype, dict): |  | ||||||
|         key_type, value_type = list(datatype.items())[0] |  | ||||||
|         for item in obj.items(): |  | ||||||
|             node = et.SubElement(el, 'item') |  | ||||||
|             node.append(dumpxml('key', item[0], key_type)) |  | ||||||
|             node.append(dumpxml('value', item[1], value_type)) |  | ||||||
|     elif datatype == wsme.types.binary: |  | ||||||
|         el.text = base64.encodestring(obj).decode('ascii') |  | ||||||
|     elif isinstance(obj, wsme.types.bytes): |  | ||||||
|         el.text = obj.decode('ascii') |  | ||||||
|     elif isinstance(obj, wsme.types.text): |  | ||||||
|         el.text = obj |  | ||||||
|     elif type(obj) in (int, float, bool, decimal.Decimal): |  | ||||||
|         el.text = six.text_type(obj) |  | ||||||
|     elif type(obj) in (datetime.date, datetime.time, datetime.datetime): |  | ||||||
|         el.text = obj.isoformat() |  | ||||||
|     elif isinstance(obj, type(None)): |  | ||||||
|         el.set('nil', 'true') |  | ||||||
|     elif hasattr(datatype, '_wsme_attributes'): |  | ||||||
|         for attr in datatype._wsme_attributes: |  | ||||||
|             name = attr.name |  | ||||||
|             if name not in obj: |  | ||||||
|                 continue |  | ||||||
|             o = obj[name] |  | ||||||
|             el.append(dumpxml(name, o, attr.datatype)) |  | ||||||
|     elif type(obj) == dict: |  | ||||||
|         for name, value in obj.items(): |  | ||||||
|             el.append(dumpxml(name, value)) |  | ||||||
|     print(obj, datatype, et.tostring(el)) |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def loadxml(el, datatype): |  | ||||||
|     print(el, datatype, len(el)) |  | ||||||
|     if el.get('nil') == 'true': |  | ||||||
|         return None |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         return [loadxml(item, datatype[0]) for item in el.findall('item')] |  | ||||||
|     elif isarray(datatype): |  | ||||||
|         return [ |  | ||||||
|             loadxml(item, datatype.item_type) for item in el.findall('item') |  | ||||||
|         ] |  | ||||||
|     elif isinstance(datatype, dict): |  | ||||||
|         key_type, value_type = list(datatype.items())[0] |  | ||||||
|         return dict(( |  | ||||||
|             (loadxml(item.find('key'), key_type), |  | ||||||
|                 loadxml(item.find('value'), value_type)) |  | ||||||
|             for item in el.findall('item') |  | ||||||
|         )) |  | ||||||
|     elif isdict(datatype): |  | ||||||
|         return dict(( |  | ||||||
|             (loadxml(item.find('key'), datatype.key_type), |  | ||||||
|                 loadxml(item.find('value'), datatype.value_type)) |  | ||||||
|             for item in el.findall('item') |  | ||||||
|         )) |  | ||||||
|     elif isdict(datatype): |  | ||||||
|         return dict(( |  | ||||||
|             (loadxml(item.find('key'), datatype.key_type), |  | ||||||
|                 loadxml(item.find('value'), datatype.value_type)) |  | ||||||
|             for item in el.findall('item') |  | ||||||
|         )) |  | ||||||
|     elif len(el): |  | ||||||
|         d = {} |  | ||||||
|         for attr in datatype._wsme_attributes: |  | ||||||
|             name = attr.name |  | ||||||
|             child = el.find(name) |  | ||||||
|             print(name, attr, child) |  | ||||||
|             if child is not None: |  | ||||||
|                 d[name] = loadxml(child, attr.datatype) |  | ||||||
|         print(d) |  | ||||||
|         return d |  | ||||||
|     else: |  | ||||||
|         if datatype == wsme.types.binary: |  | ||||||
|             return base64.decodestring(el.text.encode('ascii')) |  | ||||||
|         if isusertype(datatype): |  | ||||||
|             datatype = datatype.basetype |  | ||||||
|         if datatype == datetime.date: |  | ||||||
|             return parse_isodate(el.text) |  | ||||||
|         if datatype == datetime.time: |  | ||||||
|             return parse_isotime(el.text) |  | ||||||
|         if datatype == datetime.datetime: |  | ||||||
|             return parse_isodatetime(el.text) |  | ||||||
|         if datatype == wsme.types.text: |  | ||||||
|             return datatype(el.text if el.text else u('')) |  | ||||||
|         if datatype == bool: |  | ||||||
|             return el.text.lower() != 'false' |  | ||||||
|         if datatype is None: |  | ||||||
|             return el.text |  | ||||||
|         if datatype is wsme.types.bytes: |  | ||||||
|             return el.text.encode('ascii') |  | ||||||
|         return datatype(el.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase): |  | ||||||
|     protocol = 'restxml' |  | ||||||
|  |  | ||||||
|     def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False, |  | ||||||
|              body=None, **kw): |  | ||||||
|         if body: |  | ||||||
|             el = dumpxml('body', body) |  | ||||||
|         else: |  | ||||||
|             el = dumpxml('parameters', kw) |  | ||||||
|         content = et.tostring(el) |  | ||||||
|         headers = { |  | ||||||
|             'Content-Type': 'text/xml', |  | ||||||
|         } |  | ||||||
|         if _accept is not None: |  | ||||||
|             headers['Accept'] = _accept |  | ||||||
|         res = self.app.post( |  | ||||||
|             '/' + fpath, |  | ||||||
|             content, |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=True) |  | ||||||
|         print("Received:", res.body) |  | ||||||
|  |  | ||||||
|         if _no_result_decode: |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         el = et.fromstring(res.body) |  | ||||||
|         if el.tag == 'error': |  | ||||||
|             raise wsme.tests.protocol.CallException( |  | ||||||
|                 el.find('faultcode').text, |  | ||||||
|                 el.find('faultstring').text, |  | ||||||
|                 el.find('debuginfo') is not None and |  | ||||||
|                 el.find('debuginfo').text or None |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         else: |  | ||||||
|             return loadxml(et.fromstring(res.body), _rt) |  | ||||||
|  |  | ||||||
|     def test_encode_sample_value(self): |  | ||||||
|         class MyType(object): |  | ||||||
|             aint = int |  | ||||||
|             atext = wsme.types.text |  | ||||||
|  |  | ||||||
|         register_type(MyType) |  | ||||||
|  |  | ||||||
|         value = MyType() |  | ||||||
|         value.aint = 5 |  | ||||||
|         value.atext = u('test') |  | ||||||
|  |  | ||||||
|         language, sample = wsme.rest.xml.encode_sample_value( |  | ||||||
|             MyType, value, True) |  | ||||||
|         print(language, sample) |  | ||||||
|  |  | ||||||
|         assert language == 'xml' |  | ||||||
|         assert sample == b("""<value> |  | ||||||
|   <aint>5</aint> |  | ||||||
|   <atext>test</atext> |  | ||||||
| </value>""") |  | ||||||
|  |  | ||||||
|     def test_encode_sample_params(self): |  | ||||||
|         lang, content = wsme.rest.xml.encode_sample_params( |  | ||||||
|             [('a', int, 2)], True) |  | ||||||
|         assert lang == 'xml', lang |  | ||||||
|         assert content == b('<parameters>\n  <a>2</a>\n</parameters>'), content |  | ||||||
|  |  | ||||||
|     def test_encode_sample_result(self): |  | ||||||
|         lang, content = wsme.rest.xml.encode_sample_result(int, 2, True) |  | ||||||
|         assert lang == 'xml', lang |  | ||||||
|         assert content == b('<result>2</result>'), content |  | ||||||
|  |  | ||||||
|     def test_nil_fromxml(self): |  | ||||||
|         for dt in ( |  | ||||||
|                 str, [int], {int: str}, bool, |  | ||||||
|                 datetime.date, datetime.time, datetime.datetime): |  | ||||||
|             e = et.Element('value', nil='true') |  | ||||||
|             assert fromxml(dt, e) is None |  | ||||||
|  |  | ||||||
|     def test_nil_toxml(self): |  | ||||||
|         for dt in ( |  | ||||||
|                 wsme.types.bytes, |  | ||||||
|                 [int], {int: str}, bool, |  | ||||||
|                 datetime.date, datetime.time, datetime.datetime): |  | ||||||
|             x = et.tostring(toxml(dt, 'value', None)) |  | ||||||
|             assert x == b('<value nil="true" />'), x |  | ||||||
|  |  | ||||||
|     def test_unset_attrs(self): |  | ||||||
|         class AType(object): |  | ||||||
|             someattr = wsme.types.bytes |  | ||||||
|  |  | ||||||
|         wsme.types.register_type(AType) |  | ||||||
|  |  | ||||||
|         x = et.tostring(toxml(AType, 'value', AType())) |  | ||||||
|         assert x == b('<value />'), x |  | ||||||
| @@ -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" |  | ||||||
| @@ -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'] |  | ||||||
| @@ -1,667 +0,0 @@ | |||||||
| import re |  | ||||||
| try: |  | ||||||
|     import unittest2 as unittest |  | ||||||
| except ImportError: |  | ||||||
|     import unittest |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| from wsme import exc |  | ||||||
| from wsme import types |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def gen_class(): |  | ||||||
|     d = {} |  | ||||||
|     exec('''class tmp(object): pass''', d) |  | ||||||
|     return d['tmp'] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestTypes(unittest.TestCase): |  | ||||||
|     def setUp(self): |  | ||||||
|         types.registry = types.Registry() |  | ||||||
|  |  | ||||||
|     def test_default_usertype(self): |  | ||||||
|         class MyType(types.UserType): |  | ||||||
|             basetype = str |  | ||||||
|  |  | ||||||
|         My = MyType() |  | ||||||
|  |  | ||||||
|         assert My.validate('a') == 'a' |  | ||||||
|         assert My.tobasetype('a') == 'a' |  | ||||||
|         assert My.frombasetype('a') == 'a' |  | ||||||
|  |  | ||||||
|     def test_unset(self): |  | ||||||
|         u = types.Unset |  | ||||||
|  |  | ||||||
|         assert not u |  | ||||||
|  |  | ||||||
|     def test_flat_type(self): |  | ||||||
|         class Flat(object): |  | ||||||
|             aint = int |  | ||||||
|             abytes = six.binary_type |  | ||||||
|             atext = six.text_type |  | ||||||
|             afloat = float |  | ||||||
|  |  | ||||||
|         types.register_type(Flat) |  | ||||||
|  |  | ||||||
|         assert len(Flat._wsme_attributes) == 4 |  | ||||||
|         attrs = Flat._wsme_attributes |  | ||||||
|         print(attrs) |  | ||||||
|  |  | ||||||
|         assert attrs[0].key == 'aint' |  | ||||||
|         assert attrs[0].name == 'aint' |  | ||||||
|         assert isinstance(attrs[0], types.wsattr) |  | ||||||
|         assert attrs[0].datatype == int |  | ||||||
|         assert attrs[0].mandatory is False |  | ||||||
|         assert attrs[1].key == 'abytes' |  | ||||||
|         assert attrs[1].name == 'abytes' |  | ||||||
|         assert attrs[2].key == 'atext' |  | ||||||
|         assert attrs[2].name == 'atext' |  | ||||||
|         assert attrs[3].key == 'afloat' |  | ||||||
|         assert attrs[3].name == 'afloat' |  | ||||||
|  |  | ||||||
|     def test_private_attr(self): |  | ||||||
|         class WithPrivateAttrs(object): |  | ||||||
|             _private = 12 |  | ||||||
|  |  | ||||||
|         types.register_type(WithPrivateAttrs) |  | ||||||
|  |  | ||||||
|         assert len(WithPrivateAttrs._wsme_attributes) == 0 |  | ||||||
|  |  | ||||||
|     def test_attribute_order(self): |  | ||||||
|         class ForcedOrder(object): |  | ||||||
|             _wsme_attr_order = ('a2', 'a1', 'a3') |  | ||||||
|             a1 = int |  | ||||||
|             a2 = int |  | ||||||
|             a3 = int |  | ||||||
|  |  | ||||||
|         types.register_type(ForcedOrder) |  | ||||||
|  |  | ||||||
|         print(ForcedOrder._wsme_attributes) |  | ||||||
|         assert ForcedOrder._wsme_attributes[0].key == 'a2' |  | ||||||
|         assert ForcedOrder._wsme_attributes[1].key == 'a1' |  | ||||||
|         assert ForcedOrder._wsme_attributes[2].key == 'a3' |  | ||||||
|  |  | ||||||
|         c = gen_class() |  | ||||||
|         print(c) |  | ||||||
|         types.register_type(c) |  | ||||||
|         del c._wsme_attributes |  | ||||||
|  |  | ||||||
|         c.a2 = int |  | ||||||
|         c.a1 = int |  | ||||||
|         c.a3 = int |  | ||||||
|  |  | ||||||
|         types.register_type(c) |  | ||||||
|  |  | ||||||
|         assert c._wsme_attributes[0].key == 'a1', c._wsme_attributes[0].key |  | ||||||
|         assert c._wsme_attributes[1].key == 'a2' |  | ||||||
|         assert c._wsme_attributes[2].key == 'a3' |  | ||||||
|  |  | ||||||
|     def test_wsproperty(self): |  | ||||||
|         class WithWSProp(object): |  | ||||||
|             def __init__(self): |  | ||||||
|                 self._aint = 0 |  | ||||||
|  |  | ||||||
|             def get_aint(self): |  | ||||||
|                 return self._aint |  | ||||||
|  |  | ||||||
|             def set_aint(self, value): |  | ||||||
|                 self._aint = value |  | ||||||
|  |  | ||||||
|             aint = types.wsproperty(int, get_aint, set_aint, mandatory=True) |  | ||||||
|  |  | ||||||
|         types.register_type(WithWSProp) |  | ||||||
|  |  | ||||||
|         print(WithWSProp._wsme_attributes) |  | ||||||
|         assert len(WithWSProp._wsme_attributes) == 1 |  | ||||||
|         a = WithWSProp._wsme_attributes[0] |  | ||||||
|         assert a.key == 'aint' |  | ||||||
|         assert a.datatype == int |  | ||||||
|         assert a.mandatory |  | ||||||
|  |  | ||||||
|         o = WithWSProp() |  | ||||||
|         o.aint = 12 |  | ||||||
|  |  | ||||||
|         assert o.aint == 12 |  | ||||||
|  |  | ||||||
|     def test_nested(self): |  | ||||||
|         class Inner(object): |  | ||||||
|             aint = int |  | ||||||
|  |  | ||||||
|         class Outer(object): |  | ||||||
|             inner = Inner |  | ||||||
|  |  | ||||||
|         types.register_type(Outer) |  | ||||||
|  |  | ||||||
|         assert hasattr(Inner, '_wsme_attributes') |  | ||||||
|         assert len(Inner._wsme_attributes) == 1 |  | ||||||
|  |  | ||||||
|     def test_inspect_with_inheritance(self): |  | ||||||
|         class Parent(object): |  | ||||||
|             parent_attribute = int |  | ||||||
|  |  | ||||||
|         class Child(Parent): |  | ||||||
|             child_attribute = int |  | ||||||
|  |  | ||||||
|         types.register_type(Parent) |  | ||||||
|         types.register_type(Child) |  | ||||||
|  |  | ||||||
|         assert len(Child._wsme_attributes) == 2 |  | ||||||
|  |  | ||||||
|     def test_selfreftype(self): |  | ||||||
|         class SelfRefType(object): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         SelfRefType.parent = SelfRefType |  | ||||||
|  |  | ||||||
|         types.register_type(SelfRefType) |  | ||||||
|  |  | ||||||
|     def test_inspect_with_property(self): |  | ||||||
|         class AType(object): |  | ||||||
|             @property |  | ||||||
|             def test(self): |  | ||||||
|                 return 'test' |  | ||||||
|  |  | ||||||
|         types.register_type(AType) |  | ||||||
|  |  | ||||||
|         assert len(AType._wsme_attributes) == 0 |  | ||||||
|         assert AType().test == 'test' |  | ||||||
|  |  | ||||||
|     def test_enum(self): |  | ||||||
|         aenum = types.Enum(str, 'v1', 'v2') |  | ||||||
|         assert aenum.basetype is str |  | ||||||
|  |  | ||||||
|         class AType(object): |  | ||||||
|             a = aenum |  | ||||||
|  |  | ||||||
|         types.register_type(AType) |  | ||||||
|  |  | ||||||
|         assert AType.a.datatype is aenum |  | ||||||
|  |  | ||||||
|         obj = AType() |  | ||||||
|         obj.a = 'v1' |  | ||||||
|         assert obj.a == 'v1', repr(obj.a) |  | ||||||
|  |  | ||||||
|         self.assertRaisesRegexp(exc.InvalidInput, |  | ||||||
|                                 "Invalid input for field/attribute a. \ |  | ||||||
| Value: 'v3'. Value should be one of: v., v.", |  | ||||||
|                                 setattr, |  | ||||||
|                                 obj, |  | ||||||
|                                 'a', |  | ||||||
|                                 'v3') |  | ||||||
|  |  | ||||||
|     def test_attribute_validation(self): |  | ||||||
|         class AType(object): |  | ||||||
|             alist = [int] |  | ||||||
|             aint = int |  | ||||||
|  |  | ||||||
|         types.register_type(AType) |  | ||||||
|  |  | ||||||
|         obj = AType() |  | ||||||
|  |  | ||||||
|         obj.alist = [1, 2, 3] |  | ||||||
|         assert obj.alist == [1, 2, 3] |  | ||||||
|         obj.aint = 5 |  | ||||||
|         assert obj.aint == 5 |  | ||||||
|  |  | ||||||
|         self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', 12) |  | ||||||
|         self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', [2, 'a']) |  | ||||||
|  |  | ||||||
|     def test_attribute_validation_minimum(self): |  | ||||||
|         class ATypeInt(object): |  | ||||||
|             attr = types.IntegerType(minimum=1, maximum=5) |  | ||||||
|  |  | ||||||
|         types.register_type(ATypeInt) |  | ||||||
|  |  | ||||||
|         obj = ATypeInt() |  | ||||||
|         obj.attr = 2 |  | ||||||
|  |  | ||||||
|         # comparison between 'zero' value and intger minimum (1) raises a |  | ||||||
|         # TypeError which must be wrapped into an InvalidInput exception |  | ||||||
|         self.assertRaises(exc.InvalidInput, setattr, obj, 'attr', 'zero') |  | ||||||
|  |  | ||||||
|     def test_text_attribute_conversion(self): |  | ||||||
|         class SType(object): |  | ||||||
|             atext = types.text |  | ||||||
|             abytes = types.bytes |  | ||||||
|  |  | ||||||
|         types.register_type(SType) |  | ||||||
|  |  | ||||||
|         obj = SType() |  | ||||||
|  |  | ||||||
|         obj.atext = six.b('somebytes') |  | ||||||
|         assert obj.atext == six.u('somebytes') |  | ||||||
|         assert isinstance(obj.atext, types.text) |  | ||||||
|  |  | ||||||
|         obj.abytes = six.u('sometext') |  | ||||||
|         assert obj.abytes == six.b('sometext') |  | ||||||
|         assert isinstance(obj.abytes, types.bytes) |  | ||||||
|  |  | ||||||
|     def test_named_attribute(self): |  | ||||||
|         class ABCDType(object): |  | ||||||
|             a_list = types.wsattr([int], name='a.list') |  | ||||||
|             astr = str |  | ||||||
|  |  | ||||||
|         types.register_type(ABCDType) |  | ||||||
|  |  | ||||||
|         assert len(ABCDType._wsme_attributes) == 2 |  | ||||||
|         attrs = ABCDType._wsme_attributes |  | ||||||
|  |  | ||||||
|         assert attrs[0].key == 'a_list', attrs[0].key |  | ||||||
|         assert attrs[0].name == 'a.list', attrs[0].name |  | ||||||
|         assert attrs[1].key == 'astr', attrs[1].key |  | ||||||
|         assert attrs[1].name == 'astr', attrs[1].name |  | ||||||
|  |  | ||||||
|     def test_wsattr_del(self): |  | ||||||
|         class MyType(object): |  | ||||||
|             a = types.wsattr(int) |  | ||||||
|  |  | ||||||
|         types.register_type(MyType) |  | ||||||
|  |  | ||||||
|         value = MyType() |  | ||||||
|  |  | ||||||
|         value.a = 5 |  | ||||||
|         assert value.a == 5 |  | ||||||
|         del value.a |  | ||||||
|         assert value.a is types.Unset |  | ||||||
|  |  | ||||||
|     def test_validate_dict(self): |  | ||||||
|         assert types.validate_value({int: str}, {1: '1', 5: '5'}) |  | ||||||
|  |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, |  | ||||||
|                           {int: str}, []) |  | ||||||
|  |  | ||||||
|         assert types.validate_value({int: str}, {'1': '1', 5: '5'}) |  | ||||||
|  |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, |  | ||||||
|                           {int: str}, {1: 1, 5: '5'}) |  | ||||||
|  |  | ||||||
|     def test_validate_list_valid(self): |  | ||||||
|         assert types.validate_value([int], [1, 2]) |  | ||||||
|         assert types.validate_value([int], ['5']) |  | ||||||
|  |  | ||||||
|     def test_validate_list_empty(self): |  | ||||||
|         assert types.validate_value([int], []) == [] |  | ||||||
|  |  | ||||||
|     def test_validate_list_none(self): |  | ||||||
|         v = types.ArrayType(int) |  | ||||||
|         assert v.validate(None) is None |  | ||||||
|  |  | ||||||
|     def test_validate_list_invalid_member(self): |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, [int], |  | ||||||
|                           ['not-a-number']) |  | ||||||
|  |  | ||||||
|     def test_validate_list_invalid_type(self): |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, [int], 1) |  | ||||||
|  |  | ||||||
|     def test_validate_float(self): |  | ||||||
|         self.assertEqual(types.validate_value(float, 1), 1.0) |  | ||||||
|         self.assertEqual(types.validate_value(float, '1'), 1.0) |  | ||||||
|         self.assertEqual(types.validate_value(float, 1.1), 1.1) |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, float, []) |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, float, |  | ||||||
|                           'not-a-float') |  | ||||||
|  |  | ||||||
|     def test_validate_int(self): |  | ||||||
|         self.assertEqual(types.validate_value(int, 1), 1) |  | ||||||
|         self.assertEqual(types.validate_value(int, '1'), 1) |  | ||||||
|         self.assertEqual(types.validate_value(int, six.u('1')), 1) |  | ||||||
|         self.assertRaises(ValueError, types.validate_value, int, 1.1) |  | ||||||
|  |  | ||||||
|     def test_validate_integer_type(self): |  | ||||||
|         v = types.IntegerType(minimum=1, maximum=10) |  | ||||||
|         v.validate(1) |  | ||||||
|         v.validate(5) |  | ||||||
|         v.validate(10) |  | ||||||
|         self.assertRaises(ValueError, v.validate, 0) |  | ||||||
|         self.assertRaises(ValueError, v.validate, 11) |  | ||||||
|  |  | ||||||
|     def test_validate_string_type(self): |  | ||||||
|         v = types.StringType(min_length=1, max_length=10, |  | ||||||
|                              pattern='^[a-zA-Z0-9]*$') |  | ||||||
|         v.validate('1') |  | ||||||
|         v.validate('12345') |  | ||||||
|         v.validate('1234567890') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '12345678901') |  | ||||||
|  |  | ||||||
|         # Test a pattern validation |  | ||||||
|         v.validate('a') |  | ||||||
|         v.validate('A') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '_') |  | ||||||
|  |  | ||||||
|     def test_validate_string_type_precompile(self): |  | ||||||
|         precompile = re.compile('^[a-zA-Z0-9]*$') |  | ||||||
|         v = types.StringType(min_length=1, max_length=10, |  | ||||||
|                              pattern=precompile) |  | ||||||
|  |  | ||||||
|         # Test a pattern validation |  | ||||||
|         v.validate('a') |  | ||||||
|         v.validate('A') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '_') |  | ||||||
|  |  | ||||||
|     def test_validate_string_type_pattern_exception_message(self): |  | ||||||
|         regex = '^[a-zA-Z0-9]*$' |  | ||||||
|         v = types.StringType(pattern=regex) |  | ||||||
|         try: |  | ||||||
|             v.validate('_') |  | ||||||
|             self.assertFail() |  | ||||||
|         except ValueError as e: |  | ||||||
|             self.assertIn(regex, str(e)) |  | ||||||
|  |  | ||||||
|     def test_validate_ipv4_address_type(self): |  | ||||||
|         v = types.IPv4AddressType() |  | ||||||
|         self.assertEqual(v.validate('127.0.0.1'), '127.0.0.1') |  | ||||||
|         self.assertEqual(v.validate('192.168.0.1'), '192.168.0.1') |  | ||||||
|         self.assertEqual(v.validate(u'8.8.1.1'), u'8.8.1.1') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '') |  | ||||||
|         self.assertRaises(ValueError, v.validate, 'foo') |  | ||||||
|         self.assertRaises(ValueError, v.validate, |  | ||||||
|                           '2001:0db8:bd05:01d2:288a:1fc0:0001:10ee') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '1.2.3') |  | ||||||
|  |  | ||||||
|     def test_validate_ipv6_address_type(self): |  | ||||||
|         v = types.IPv6AddressType() |  | ||||||
|         self.assertEqual(v.validate('0:0:0:0:0:0:0:1'), |  | ||||||
|                          '0:0:0:0:0:0:0:1') |  | ||||||
|         self.assertEqual(v.validate(u'0:0:0:0:0:0:0:1'), u'0:0:0:0:0:0:0:1') |  | ||||||
|         self.assertEqual(v.validate('2001:0db8:bd05:01d2:288a:1fc0:0001:10ee'), |  | ||||||
|                          '2001:0db8:bd05:01d2:288a:1fc0:0001:10ee') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '') |  | ||||||
|         self.assertRaises(ValueError, v.validate, 'foo') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '192.168.0.1') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '0:0:0:0:0:0:1') |  | ||||||
|  |  | ||||||
|     def test_validate_uuid_type(self): |  | ||||||
|         v = types.UuidType() |  | ||||||
|         self.assertEqual(v.validate('6a0a707c-45ef-4758-b533-e55adddba8ce'), |  | ||||||
|                          '6a0a707c-45ef-4758-b533-e55adddba8ce') |  | ||||||
|         self.assertEqual(v.validate('6a0a707c45ef4758b533e55adddba8ce'), |  | ||||||
|                          '6a0a707c-45ef-4758-b533-e55adddba8ce') |  | ||||||
|         self.assertRaises(ValueError, v.validate, '') |  | ||||||
|         self.assertRaises(ValueError, v.validate, 'foo') |  | ||||||
|         self.assertRaises(ValueError, v.validate, |  | ||||||
|                           '6a0a707c-45ef-4758-b533-e55adddba8ce-a') |  | ||||||
|  |  | ||||||
|     def test_register_invalid_array(self): |  | ||||||
|         self.assertRaises(ValueError, types.register_type, []) |  | ||||||
|         self.assertRaises(ValueError, types.register_type, [int, str]) |  | ||||||
|         self.assertRaises(AttributeError, types.register_type, [1]) |  | ||||||
|  |  | ||||||
|     def test_register_invalid_dict(self): |  | ||||||
|         self.assertRaises(ValueError, types.register_type, {}) |  | ||||||
|         self.assertRaises(ValueError, types.register_type, |  | ||||||
|                           {int: str, str: int}) |  | ||||||
|         self.assertRaises(ValueError, types.register_type, |  | ||||||
|                           {types.Unset: str}) |  | ||||||
|  |  | ||||||
|     def test_list_attribute_no_auto_register(self): |  | ||||||
|         class MyType(object): |  | ||||||
|             aint = int |  | ||||||
|  |  | ||||||
|         assert not hasattr(MyType, '_wsme_attributes') |  | ||||||
|  |  | ||||||
|         self.assertRaises(TypeError, types.list_attributes, MyType) |  | ||||||
|  |  | ||||||
|         assert not hasattr(MyType, '_wsme_attributes') |  | ||||||
|  |  | ||||||
|     def test_list_of_complextypes(self): |  | ||||||
|         class A(object): |  | ||||||
|             bs = types.wsattr(['B']) |  | ||||||
|  |  | ||||||
|         class B(object): |  | ||||||
|             i = int |  | ||||||
|  |  | ||||||
|         types.register_type(A) |  | ||||||
|         types.register_type(B) |  | ||||||
|  |  | ||||||
|         assert A.bs.datatype.item_type is B |  | ||||||
|  |  | ||||||
|     def test_cross_referenced_types(self): |  | ||||||
|         class A(object): |  | ||||||
|             b = types.wsattr('B') |  | ||||||
|  |  | ||||||
|         class B(object): |  | ||||||
|             a = A |  | ||||||
|  |  | ||||||
|         types.register_type(A) |  | ||||||
|         types.register_type(B) |  | ||||||
|  |  | ||||||
|         assert A.b.datatype is B |  | ||||||
|  |  | ||||||
|     def test_base(self): |  | ||||||
|         class B1(types.Base): |  | ||||||
|             b2 = types.wsattr('B2') |  | ||||||
|  |  | ||||||
|         class B2(types.Base): |  | ||||||
|             b2 = types.wsattr('B2') |  | ||||||
|  |  | ||||||
|         assert B1.b2.datatype is B2, repr(B1.b2.datatype) |  | ||||||
|         assert B2.b2.datatype is B2 |  | ||||||
|  |  | ||||||
|     def test_base_init(self): |  | ||||||
|         class C1(types.Base): |  | ||||||
|             s = six.text_type |  | ||||||
|  |  | ||||||
|         c = C1(s=six.u('test')) |  | ||||||
|         assert c.s == six.u('test') |  | ||||||
|  |  | ||||||
|     def test_array_eq(self): |  | ||||||
|         l = [types.ArrayType(str)] |  | ||||||
|         assert types.ArrayType(str) in l |  | ||||||
|  |  | ||||||
|     def test_array_sample(self): |  | ||||||
|         s = types.ArrayType(str).sample() |  | ||||||
|         assert isinstance(s, list) |  | ||||||
|         assert s |  | ||||||
|         assert s[0] == '' |  | ||||||
|  |  | ||||||
|     def test_dict_sample(self): |  | ||||||
|         s = types.DictType(str, str).sample() |  | ||||||
|         assert isinstance(s, dict) |  | ||||||
|         assert s |  | ||||||
|         assert s == {'': ''} |  | ||||||
|  |  | ||||||
|     def test_binary_to_base(self): |  | ||||||
|         import base64 |  | ||||||
|         assert types.binary.tobasetype(None) is None |  | ||||||
|         expected = base64.encodestring(six.b('abcdef')) |  | ||||||
|         assert types.binary.tobasetype(six.b('abcdef')) == expected |  | ||||||
|  |  | ||||||
|     def test_binary_from_base(self): |  | ||||||
|         import base64 |  | ||||||
|         assert types.binary.frombasetype(None) is None |  | ||||||
|         encoded = base64.encodestring(six.b('abcdef')) |  | ||||||
|         assert types.binary.frombasetype(encoded) == six.b('abcdef') |  | ||||||
|  |  | ||||||
|     def test_wsattr_weakref_datatype(self): |  | ||||||
|         # If the datatype inside the wsattr ends up a weakref, it |  | ||||||
|         # should be converted to the real type when accessed again by |  | ||||||
|         # the property getter. |  | ||||||
|         import weakref |  | ||||||
|         a = types.wsattr(int) |  | ||||||
|         a.datatype = weakref.ref(int) |  | ||||||
|         assert a.datatype is int |  | ||||||
|  |  | ||||||
|     def test_wsattr_list_datatype(self): |  | ||||||
|         # If the datatype inside the wsattr ends up a list of weakrefs |  | ||||||
|         # to types, it should be converted to the real types when |  | ||||||
|         # accessed again by the property getter. |  | ||||||
|         import weakref |  | ||||||
|         a = types.wsattr(int) |  | ||||||
|         a.datatype = [weakref.ref(int)] |  | ||||||
|         assert isinstance(a.datatype, list) |  | ||||||
|         assert a.datatype[0] is int |  | ||||||
|  |  | ||||||
|     def test_file_get_content_by_reading(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'abcdef' |  | ||||||
|         f = types.File(file=buffer()) |  | ||||||
|         assert f.content == 'abcdef' |  | ||||||
|  |  | ||||||
|     def test_file_content_overrides_file(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'from-file' |  | ||||||
|         f = types.File(content='from-content', file=buffer()) |  | ||||||
|         assert f.content == 'from-content' |  | ||||||
|  |  | ||||||
|     def test_file_setting_content_discards_file(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'from-file' |  | ||||||
|         f = types.File(file=buffer()) |  | ||||||
|         f.content = 'from-content' |  | ||||||
|         assert f.content == 'from-content' |  | ||||||
|  |  | ||||||
|     def test_file_field_storage(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'from-file' |  | ||||||
|  |  | ||||||
|         class fieldstorage: |  | ||||||
|             filename = 'static.json' |  | ||||||
|             file = buffer() |  | ||||||
|             type = 'application/json' |  | ||||||
|         f = types.File(fieldstorage=fieldstorage) |  | ||||||
|         assert f.content == 'from-file' |  | ||||||
|  |  | ||||||
|     def test_file_field_storage_value(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'from-file' |  | ||||||
|  |  | ||||||
|         class fieldstorage: |  | ||||||
|             filename = 'static.json' |  | ||||||
|             file = None |  | ||||||
|             type = 'application/json' |  | ||||||
|             value = 'from-value' |  | ||||||
|         f = types.File(fieldstorage=fieldstorage) |  | ||||||
|         assert f.content == 'from-value' |  | ||||||
|  |  | ||||||
|     def test_file_property_file(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'from-file' |  | ||||||
|         buf = buffer() |  | ||||||
|         f = types.File(file=buf) |  | ||||||
|         assert f.file is buf |  | ||||||
|  |  | ||||||
|     def test_file_property_content(self): |  | ||||||
|         class buffer: |  | ||||||
|             def read(self): |  | ||||||
|                 return 'from-file' |  | ||||||
|         f = types.File(content=six.b('from-content')) |  | ||||||
|         assert f.file.read() == six.b('from-content') |  | ||||||
|  |  | ||||||
|     def test_unregister(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         v = types.registry.lookup('TempType') |  | ||||||
|         self.assertIs(v, TempType) |  | ||||||
|         types.registry._unregister(TempType) |  | ||||||
|         after = types.registry.lookup('TempType') |  | ||||||
|         self.assertIs(after, None) |  | ||||||
|  |  | ||||||
|     def test_unregister_twice(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         v = types.registry.lookup('TempType') |  | ||||||
|         self.assertIs(v, TempType) |  | ||||||
|         types.registry._unregister(TempType) |  | ||||||
|         # Second call should not raise an exception |  | ||||||
|         types.registry._unregister(TempType) |  | ||||||
|         after = types.registry.lookup('TempType') |  | ||||||
|         self.assertIs(after, None) |  | ||||||
|  |  | ||||||
|     def test_unregister_array_type(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         t = [TempType] |  | ||||||
|         types.registry.register(t) |  | ||||||
|         self.assertNotEqual(types.registry.array_types, set()) |  | ||||||
|         types.registry._unregister(t) |  | ||||||
|         self.assertEqual(types.registry.array_types, set()) |  | ||||||
|  |  | ||||||
|     def test_unregister_array_type_twice(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         t = [TempType] |  | ||||||
|         types.registry.register(t) |  | ||||||
|         self.assertNotEqual(types.registry.array_types, set()) |  | ||||||
|         types.registry._unregister(t) |  | ||||||
|         # Second call should not raise an exception |  | ||||||
|         types.registry._unregister(t) |  | ||||||
|         self.assertEqual(types.registry.array_types, set()) |  | ||||||
|  |  | ||||||
|     def test_unregister_dict_type(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         t = {str: TempType} |  | ||||||
|         types.registry.register(t) |  | ||||||
|         self.assertNotEqual(types.registry.dict_types, set()) |  | ||||||
|         types.registry._unregister(t) |  | ||||||
|         self.assertEqual(types.registry.dict_types, set()) |  | ||||||
|  |  | ||||||
|     def test_unregister_dict_type_twice(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         t = {str: TempType} |  | ||||||
|         types.registry.register(t) |  | ||||||
|         self.assertNotEqual(types.registry.dict_types, set()) |  | ||||||
|         types.registry._unregister(t) |  | ||||||
|         # Second call should not raise an exception |  | ||||||
|         types.registry._unregister(t) |  | ||||||
|         self.assertEqual(types.registry.dict_types, set()) |  | ||||||
|  |  | ||||||
|     def test_reregister(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         v = types.registry.lookup('TempType') |  | ||||||
|         self.assertIs(v, TempType) |  | ||||||
|         types.registry.reregister(TempType) |  | ||||||
|         after = types.registry.lookup('TempType') |  | ||||||
|         self.assertIs(after, TempType) |  | ||||||
|  |  | ||||||
|     def test_reregister_and_add_attr(self): |  | ||||||
|         class TempType(object): |  | ||||||
|             pass |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         attrs = types.list_attributes(TempType) |  | ||||||
|         self.assertEqual(attrs, []) |  | ||||||
|         TempType.one = str |  | ||||||
|         types.registry.reregister(TempType) |  | ||||||
|         after = types.list_attributes(TempType) |  | ||||||
|         self.assertNotEqual(after, []) |  | ||||||
|  |  | ||||||
|     def test_dynamicbase_add_attributes(self): |  | ||||||
|         class TempType(types.DynamicBase): |  | ||||||
|             pass |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         attrs = types.list_attributes(TempType) |  | ||||||
|         self.assertEqual(attrs, []) |  | ||||||
|         TempType.add_attributes(one=str) |  | ||||||
|         after = types.list_attributes(TempType) |  | ||||||
|         self.assertEqual(len(after), 1) |  | ||||||
|  |  | ||||||
|     def test_dynamicbase_add_attributes_second(self): |  | ||||||
|         class TempType(types.DynamicBase): |  | ||||||
|             pass |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         attrs = types.list_attributes(TempType) |  | ||||||
|         self.assertEqual(attrs, []) |  | ||||||
|         TempType.add_attributes(one=str) |  | ||||||
|         TempType.add_attributes(two=int) |  | ||||||
|         after = types.list_attributes(TempType) |  | ||||||
|         self.assertEqual(len(after), 2) |  | ||||||
|  |  | ||||||
|     def test_non_registered_complex_type(self): |  | ||||||
|         class TempType(types.Base): |  | ||||||
|             __registry__ = None |  | ||||||
|  |  | ||||||
|         self.assertFalse(types.iscomplex(TempType)) |  | ||||||
|         types.registry.register(TempType) |  | ||||||
|         self.assertTrue(types.iscomplex(TempType)) |  | ||||||
| @@ -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" |  | ||||||
|         ) |  | ||||||
							
								
								
									
										840
									
								
								wsme/types.py
									
									
									
									
									
								
							
							
						
						
									
										840
									
								
								wsme/types.py
									
									
									
									
									
								
							| @@ -1,840 +0,0 @@ | |||||||
| import base64 |  | ||||||
| import datetime |  | ||||||
| import decimal |  | ||||||
| import inspect |  | ||||||
| import logging |  | ||||||
| import netaddr |  | ||||||
| import re |  | ||||||
| import six |  | ||||||
| import sys |  | ||||||
| import uuid |  | ||||||
| import weakref |  | ||||||
|  |  | ||||||
| from wsme import exc |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
| #: The 'str' (python 2) or 'bytes' (python 3) type. |  | ||||||
| #: Its use should be restricted to |  | ||||||
| #: pure ascii strings as the protocols will generally not be |  | ||||||
| #: be able to send non-unicode strings. |  | ||||||
| #: To transmit binary strings, use the :class:`binary` type |  | ||||||
| bytes = six.binary_type |  | ||||||
|  |  | ||||||
| #: Unicode string. |  | ||||||
| text = six.text_type |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArrayType(object): |  | ||||||
|     def __init__(self, item_type): |  | ||||||
|         if iscomplex(item_type): |  | ||||||
|             self._item_type = weakref.ref(item_type) |  | ||||||
|         else: |  | ||||||
|             self._item_type = item_type |  | ||||||
|  |  | ||||||
|     def __hash__(self): |  | ||||||
|         return hash(self.item_type) |  | ||||||
|  |  | ||||||
|     def __eq__(self, other): |  | ||||||
|         return isinstance(other, ArrayType) \ |  | ||||||
|             and self.item_type == other.item_type |  | ||||||
|  |  | ||||||
|     def sample(self): |  | ||||||
|         return [getattr(self.item_type, 'sample', self.item_type)()] |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def item_type(self): |  | ||||||
|         if isinstance(self._item_type, weakref.ref): |  | ||||||
|             return self._item_type() |  | ||||||
|         else: |  | ||||||
|             return self._item_type |  | ||||||
|  |  | ||||||
|     def validate(self, value): |  | ||||||
|         if value is None: |  | ||||||
|             return |  | ||||||
|         if not isinstance(value, list): |  | ||||||
|             raise ValueError("Wrong type. Expected '[%s]', got '%s'" % ( |  | ||||||
|                 self.item_type, type(value) |  | ||||||
|             )) |  | ||||||
|         return [ |  | ||||||
|             validate_value(self.item_type, item) |  | ||||||
|             for item in value |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DictType(object): |  | ||||||
|     def __init__(self, key_type, value_type): |  | ||||||
|         if key_type not in pod_types: |  | ||||||
|             raise ValueError("Dictionaries key can only be a pod type") |  | ||||||
|         self.key_type = key_type |  | ||||||
|         if iscomplex(value_type): |  | ||||||
|             self._value_type = weakref.ref(value_type) |  | ||||||
|         else: |  | ||||||
|             self._value_type = value_type |  | ||||||
|  |  | ||||||
|     def __hash__(self): |  | ||||||
|         return hash((self.key_type, self.value_type)) |  | ||||||
|  |  | ||||||
|     def sample(self): |  | ||||||
|         key = getattr(self.key_type, 'sample', self.key_type)() |  | ||||||
|         value = getattr(self.value_type, 'sample', self.value_type)() |  | ||||||
|         return {key: value} |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def value_type(self): |  | ||||||
|         if isinstance(self._value_type, weakref.ref): |  | ||||||
|             return self._value_type() |  | ||||||
|         else: |  | ||||||
|             return self._value_type |  | ||||||
|  |  | ||||||
|     def validate(self, value): |  | ||||||
|         if not isinstance(value, dict): |  | ||||||
|             raise ValueError("Wrong type. Expected '{%s: %s}', got '%s'" % ( |  | ||||||
|                 self.key_type, self.value_type, type(value) |  | ||||||
|             )) |  | ||||||
|         return dict(( |  | ||||||
|             ( |  | ||||||
|                 validate_value(self.key_type, key), |  | ||||||
|                 validate_value(self.value_type, v) |  | ||||||
|             ) for key, v in value.items() |  | ||||||
|         )) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserType(object): |  | ||||||
|     basetype = None |  | ||||||
|     name = None |  | ||||||
|  |  | ||||||
|     def validate(self, value): |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|     def tobasetype(self, value): |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|     def frombasetype(self, value): |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def isusertype(class_): |  | ||||||
|     return isinstance(class_, UserType) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BinaryType(UserType): |  | ||||||
|     """ |  | ||||||
|     A user type that use base64 strings to carry binary data. |  | ||||||
|     """ |  | ||||||
|     basetype = bytes |  | ||||||
|     name = 'binary' |  | ||||||
|  |  | ||||||
|     def tobasetype(self, value): |  | ||||||
|         if value is None: |  | ||||||
|             return None |  | ||||||
|         return base64.encodestring(value) |  | ||||||
|  |  | ||||||
|     def frombasetype(self, value): |  | ||||||
|         if value is None: |  | ||||||
|             return None |  | ||||||
|         return base64.decodestring(value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #: The binary almost-native type |  | ||||||
| binary = BinaryType() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IntegerType(UserType): |  | ||||||
|     """ |  | ||||||
|     A simple integer type. Can validate a value range. |  | ||||||
|  |  | ||||||
|     :param minimum: Possible minimum value |  | ||||||
|     :param maximum: Possible maximum value |  | ||||||
|  |  | ||||||
|     Example:: |  | ||||||
|  |  | ||||||
|         Price = IntegerType(minimum=1) |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     basetype = int |  | ||||||
|     name = "integer" |  | ||||||
|  |  | ||||||
|     def __init__(self, minimum=None, maximum=None): |  | ||||||
|         self.minimum = minimum |  | ||||||
|         self.maximum = maximum |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def frombasetype(value): |  | ||||||
|         return int(value) if value is not None else None |  | ||||||
|  |  | ||||||
|     def validate(self, value): |  | ||||||
|         if self.minimum is not None and value < self.minimum: |  | ||||||
|             error = 'Value should be greater or equal to %s' % self.minimum |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|         if self.maximum is not None and value > self.maximum: |  | ||||||
|             error = 'Value should be lower or equal to %s' % self.maximum |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StringType(UserType): |  | ||||||
|     """ |  | ||||||
|     A simple string type. Can validate a length and a pattern. |  | ||||||
|  |  | ||||||
|     :param min_length: Possible minimum length |  | ||||||
|     :param max_length: Possible maximum length |  | ||||||
|     :param pattern: Possible string pattern |  | ||||||
|  |  | ||||||
|     Example:: |  | ||||||
|  |  | ||||||
|         Name = StringType(min_length=1, pattern='^[a-zA-Z ]*$') |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     basetype = six.string_types |  | ||||||
|     name = "string" |  | ||||||
|  |  | ||||||
|     def __init__(self, min_length=None, max_length=None, pattern=None): |  | ||||||
|         self.min_length = min_length |  | ||||||
|         self.max_length = max_length |  | ||||||
|         if isinstance(pattern, six.string_types): |  | ||||||
|             self.pattern = re.compile(pattern) |  | ||||||
|         else: |  | ||||||
|             self.pattern = pattern |  | ||||||
|  |  | ||||||
|     def validate(self, value): |  | ||||||
|         if not isinstance(value, self.basetype): |  | ||||||
|             error = 'Value should be string' |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|         if self.min_length is not None and len(value) < self.min_length: |  | ||||||
|             error = 'Value should have a minimum character requirement of %s' \ |  | ||||||
|                     % self.min_length |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|         if self.max_length is not None and len(value) > self.max_length: |  | ||||||
|             error = 'Value should have a maximum character requirement of %s' \ |  | ||||||
|                     % self.max_length |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|         if self.pattern is not None and not self.pattern.search(value): |  | ||||||
|             error = 'Value should match the pattern %s' % self.pattern.pattern |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IPv4AddressType(UserType): |  | ||||||
|     """ |  | ||||||
|     A simple IPv4 type. |  | ||||||
|     """ |  | ||||||
|     basetype = six.string_types |  | ||||||
|     name = "ipv4address" |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def validate(value): |  | ||||||
|         try: |  | ||||||
|             netaddr.IPAddress(value, version=4, flags=netaddr.INET_PTON) |  | ||||||
|         except netaddr.AddrFormatError: |  | ||||||
|             error = 'Value should be IPv4 format' |  | ||||||
|             raise ValueError(error) |  | ||||||
|         else: |  | ||||||
|             return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IPv6AddressType(UserType): |  | ||||||
|     """ |  | ||||||
|     A simple IPv6 type. |  | ||||||
|  |  | ||||||
|     This type represents IPv6 addresses in the short format. |  | ||||||
|     """ |  | ||||||
|     basetype = six.string_types |  | ||||||
|     name = "ipv6address" |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def validate(value): |  | ||||||
|         try: |  | ||||||
|             netaddr.IPAddress(value, version=6, flags=netaddr.INET_PTON) |  | ||||||
|         except netaddr.AddrFormatError: |  | ||||||
|             error = 'Value should be IPv6 format' |  | ||||||
|             raise ValueError(error) |  | ||||||
|         else: |  | ||||||
|             return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UuidType(UserType): |  | ||||||
|     """ |  | ||||||
|     A simple UUID type. |  | ||||||
|  |  | ||||||
|     This type allows not only UUID having dashes but also UUID not |  | ||||||
|     having dashes. For example, '6a0a707c-45ef-4758-b533-e55adddba8ce' |  | ||||||
|     and '6a0a707c45ef4758b533e55adddba8ce' are distinguished as valid. |  | ||||||
|     """ |  | ||||||
|     basetype = six.string_types |  | ||||||
|     name = "uuid" |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def validate(value): |  | ||||||
|         try: |  | ||||||
|             return six.text_type((uuid.UUID(value))) |  | ||||||
|         except (TypeError, ValueError, AttributeError): |  | ||||||
|             error = 'Value should be UUID format' |  | ||||||
|             raise ValueError(error) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Enum(UserType): |  | ||||||
|     """ |  | ||||||
|     A simple enumeration type. Can be based on any non-complex type. |  | ||||||
|  |  | ||||||
|     :param basetype: The actual data type |  | ||||||
|     :param values: A set of possible values |  | ||||||
|  |  | ||||||
|     If nullable, 'None' should be added the values set. |  | ||||||
|  |  | ||||||
|     Example:: |  | ||||||
|  |  | ||||||
|         Gender = Enum(str, 'male', 'female') |  | ||||||
|         Specie = Enum(str, 'cat', 'dog') |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     def __init__(self, basetype, *values, **kw): |  | ||||||
|         self.basetype = basetype |  | ||||||
|         self.values = set(values) |  | ||||||
|         name = kw.pop('name', None) |  | ||||||
|         if name is None: |  | ||||||
|             name = "Enum(%s)" % ', '.join((six.text_type(v) for v in values)) |  | ||||||
|         self.name = name |  | ||||||
|  |  | ||||||
|     def validate(self, value): |  | ||||||
|         if value not in self.values: |  | ||||||
|             raise ValueError("Value should be one of: %s" % |  | ||||||
|                              ', '.join(map(six.text_type, self.values))) |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|     def tobasetype(self, value): |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|     def frombasetype(self, value): |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UnsetType(object): |  | ||||||
|     if sys.version < '3': |  | ||||||
|         def __nonzero__(self): |  | ||||||
|             return False |  | ||||||
|     else: |  | ||||||
|         def __bool__(self): |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|     def __repr__(self): |  | ||||||
|         return 'Unset' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Unset = UnsetType() |  | ||||||
|  |  | ||||||
| #: A special type that corresponds to the host framework request object. |  | ||||||
| #: It can only be used in the function parameters, and if so the request object |  | ||||||
| #: of the host framework will be passed to the function. |  | ||||||
| HostRequest = object() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| pod_types = six.integer_types + ( |  | ||||||
|     bytes, text, float, bool) |  | ||||||
| dt_types = (datetime.date, datetime.time, datetime.datetime) |  | ||||||
| extra_types = (binary, decimal.Decimal) |  | ||||||
| native_types = pod_types + dt_types + extra_types |  | ||||||
| # The types for which we allow promotion to certain numbers. |  | ||||||
| _promotable_types = six.integer_types + (text, bytes) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def iscomplex(datatype): |  | ||||||
|     return inspect.isclass(datatype) \ |  | ||||||
|         and '_wsme_attributes' in datatype.__dict__ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def isarray(datatype): |  | ||||||
|     return isinstance(datatype, ArrayType) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def isdict(datatype): |  | ||||||
|     return isinstance(datatype, DictType) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_value(datatype, value): |  | ||||||
|     if value in (Unset, None): |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|     # Try to promote the data type to one of our complex types. |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         datatype = ArrayType(datatype[0]) |  | ||||||
|     elif isinstance(datatype, dict): |  | ||||||
|         datatype = DictType(*list(datatype.items())[0]) |  | ||||||
|  |  | ||||||
|     # If the datatype has its own validator, use that. |  | ||||||
|     if hasattr(datatype, 'validate'): |  | ||||||
|         return datatype.validate(value) |  | ||||||
|  |  | ||||||
|     # Do type promotion/conversion and data validation for builtin |  | ||||||
|     # types. |  | ||||||
|     v_type = type(value) |  | ||||||
|     if datatype in six.integer_types: |  | ||||||
|         if v_type in _promotable_types: |  | ||||||
|             try: |  | ||||||
|                 # Try to turn the value into an int |  | ||||||
|                 value = datatype(value) |  | ||||||
|             except ValueError: |  | ||||||
|                 # An error is raised at the end of the function |  | ||||||
|                 # when the types don't match. |  | ||||||
|                 pass |  | ||||||
|     elif datatype is float and v_type in _promotable_types: |  | ||||||
|         try: |  | ||||||
|             value = float(value) |  | ||||||
|         except ValueError: |  | ||||||
|             # An error is raised at the end of the function |  | ||||||
|             # when the types don't match. |  | ||||||
|             pass |  | ||||||
|     elif datatype is text and isinstance(value, bytes): |  | ||||||
|         value = value.decode() |  | ||||||
|     elif datatype is bytes and isinstance(value, text): |  | ||||||
|         value = value.encode() |  | ||||||
|  |  | ||||||
|     if not isinstance(value, datatype): |  | ||||||
|         raise ValueError( |  | ||||||
|             "Wrong type. Expected '%s', got '%s'" % ( |  | ||||||
|                 datatype, v_type |  | ||||||
|             )) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class wsproperty(property): |  | ||||||
|     """ |  | ||||||
|     A specialised :class:`property` to define typed-property on complex types. |  | ||||||
|     Example:: |  | ||||||
|  |  | ||||||
|         class MyComplexType(wsme.types.Base): |  | ||||||
|             def get_aint(self): |  | ||||||
|                 return self._aint |  | ||||||
|  |  | ||||||
|             def set_aint(self, value): |  | ||||||
|                 assert avalue < 10  # Dummy input validation |  | ||||||
|                 self._aint = value |  | ||||||
|  |  | ||||||
|             aint = wsproperty(int, get_aint, set_aint, mandatory=True) |  | ||||||
|     """ |  | ||||||
|     def __init__(self, datatype, fget, fset=None, |  | ||||||
|                  mandatory=False, doc=None, name=None): |  | ||||||
|         property.__init__(self, fget, fset) |  | ||||||
|         #: The property name in the parent python class |  | ||||||
|         self.key = None |  | ||||||
|         #: The attribute name on the public of the api. |  | ||||||
|         #: Defaults to :attr:`key` |  | ||||||
|         self.name = name |  | ||||||
|         #: property data type |  | ||||||
|         self.datatype = datatype |  | ||||||
|         #: True if the property is mandatory |  | ||||||
|         self.mandatory = mandatory |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class wsattr(object): |  | ||||||
|     """ |  | ||||||
|     Complex type attribute definition. |  | ||||||
|  |  | ||||||
|     Example:: |  | ||||||
|  |  | ||||||
|         class MyComplexType(wsme.types.Base): |  | ||||||
|             optionalvalue = int |  | ||||||
|             mandatoryvalue = wsattr(int, mandatory=True) |  | ||||||
|             named_value = wsattr(int, name='named.value') |  | ||||||
|  |  | ||||||
|     After inspection, the non-wsattr attributes will be replaced, and |  | ||||||
|     the above class will be equivalent to:: |  | ||||||
|  |  | ||||||
|         class MyComplexType(wsme.types.Base): |  | ||||||
|             optionalvalue = wsattr(int) |  | ||||||
|             mandatoryvalue = wsattr(int, mandatory=True) |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     def __init__(self, datatype, mandatory=False, name=None, default=Unset, |  | ||||||
|                  readonly=False): |  | ||||||
|         #: The attribute name in the parent python class. |  | ||||||
|         #: Set by :func:`inspect_class` |  | ||||||
|         self.key = None  # will be set by class inspection |  | ||||||
|         #: The attribute name on the public of the api. |  | ||||||
|         #: Defaults to :attr:`key` |  | ||||||
|         self.name = name |  | ||||||
|         self._datatype = (datatype,) |  | ||||||
|         #: True if the attribute is mandatory |  | ||||||
|         self.mandatory = mandatory |  | ||||||
|         #: Default value. The attribute will return this instead |  | ||||||
|         #: of :data:`Unset` if no value has been set. |  | ||||||
|         self.default = default |  | ||||||
|         #: If True value cannot be set from json/xml input data |  | ||||||
|         self.readonly = readonly |  | ||||||
|  |  | ||||||
|         self.complextype = None |  | ||||||
|  |  | ||||||
|     def _get_dataholder(self, instance): |  | ||||||
|         dataholder = getattr(instance, '_wsme_dataholder', None) |  | ||||||
|         if dataholder is None: |  | ||||||
|             dataholder = instance._wsme_DataHolderClass() |  | ||||||
|             instance._wsme_dataholder = dataholder |  | ||||||
|         return dataholder |  | ||||||
|  |  | ||||||
|     def __get__(self, instance, owner): |  | ||||||
|         if instance is None: |  | ||||||
|             return self |  | ||||||
|         return getattr( |  | ||||||
|             self._get_dataholder(instance), |  | ||||||
|             self.key, |  | ||||||
|             self.default |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def __set__(self, instance, value): |  | ||||||
|         try: |  | ||||||
|             value = validate_value(self.datatype, value) |  | ||||||
|         except (ValueError, TypeError) as e: |  | ||||||
|             raise exc.InvalidInput(self.name, value, six.text_type(e)) |  | ||||||
|         dataholder = self._get_dataholder(instance) |  | ||||||
|         if value is Unset: |  | ||||||
|             if hasattr(dataholder, self.key): |  | ||||||
|                 delattr(dataholder, self.key) |  | ||||||
|         else: |  | ||||||
|             setattr(dataholder, self.key, value) |  | ||||||
|  |  | ||||||
|     def __delete__(self, instance): |  | ||||||
|         self.__set__(instance, Unset) |  | ||||||
|  |  | ||||||
|     def _get_datatype(self): |  | ||||||
|         if isinstance(self._datatype, tuple): |  | ||||||
|             self._datatype = \ |  | ||||||
|                 self.complextype().__registry__.resolve_type(self._datatype[0]) |  | ||||||
|         if isinstance(self._datatype, weakref.ref): |  | ||||||
|             return self._datatype() |  | ||||||
|         if isinstance(self._datatype, list): |  | ||||||
|             return [ |  | ||||||
|                 item() if isinstance(item, weakref.ref) else item |  | ||||||
|                 for item in self._datatype |  | ||||||
|             ] |  | ||||||
|         return self._datatype |  | ||||||
|  |  | ||||||
|     def _set_datatype(self, datatype): |  | ||||||
|         self._datatype = datatype |  | ||||||
|  |  | ||||||
|     #: attribute data type. Can be either an actual type, |  | ||||||
|     #: or a type name, in which case the actual type will be |  | ||||||
|     #: determined when needed (generally just before scanning the api). |  | ||||||
|     datatype = property(_get_datatype, _set_datatype) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def iswsattr(attr): |  | ||||||
|     if inspect.isfunction(attr) or inspect.ismethod(attr): |  | ||||||
|         return False |  | ||||||
|     if isinstance(attr, property) and not isinstance(attr, wsproperty): |  | ||||||
|         return False |  | ||||||
|     return True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def sort_attributes(class_, attributes): |  | ||||||
|     """Sort a class attributes list. |  | ||||||
|  |  | ||||||
|     3 mechanisms are attempted : |  | ||||||
|  |  | ||||||
|     #.  Look for a _wsme_attr_order attribute on the class_. This allow |  | ||||||
|         to define an arbitrary order of the attributes (useful for |  | ||||||
|         generated types). |  | ||||||
|  |  | ||||||
|     #.  Access the object source code to find the declaration order. |  | ||||||
|  |  | ||||||
|     #.  Sort by alphabetically""" |  | ||||||
|  |  | ||||||
|     if not len(attributes): |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     attrs = dict((a.key, a) for a in attributes) |  | ||||||
|  |  | ||||||
|     if hasattr(class_, '_wsme_attr_order'): |  | ||||||
|         names_order = class_._wsme_attr_order |  | ||||||
|     else: |  | ||||||
|         names = attrs.keys() |  | ||||||
|         names_order = [] |  | ||||||
|         try: |  | ||||||
|             lines = [] |  | ||||||
|             for cls in inspect.getmro(class_): |  | ||||||
|                 if cls is object: |  | ||||||
|                     continue |  | ||||||
|                 lines[len(lines):] = inspect.getsourcelines(cls)[0] |  | ||||||
|             for line in lines: |  | ||||||
|                 line = line.strip().replace(" ", "") |  | ||||||
|                 if '=' in line: |  | ||||||
|                     aname = line[:line.index('=')] |  | ||||||
|                     if aname in names and aname not in names_order: |  | ||||||
|                         names_order.append(aname) |  | ||||||
|             if len(names_order) < len(names): |  | ||||||
|                 names_order.extend(( |  | ||||||
|                     name for name in names if name not in names_order)) |  | ||||||
|             assert len(names_order) == len(names) |  | ||||||
|         except (TypeError, IOError): |  | ||||||
|             names_order = list(names) |  | ||||||
|             names_order.sort() |  | ||||||
|  |  | ||||||
|     attributes[:] = [attrs[name] for name in names_order] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def inspect_class(class_): |  | ||||||
|     """Extract a list of (name, wsattr|wsproperty) for the given class_""" |  | ||||||
|     attributes = [] |  | ||||||
|     for name, attr in inspect.getmembers(class_, iswsattr): |  | ||||||
|         if name.startswith('_'): |  | ||||||
|             continue |  | ||||||
|         if inspect.isroutine(attr): |  | ||||||
|             continue |  | ||||||
|  |  | ||||||
|         if isinstance(attr, (wsattr, wsproperty)): |  | ||||||
|             attrdef = attr |  | ||||||
|         else: |  | ||||||
|             if attr not in native_types and ( |  | ||||||
|                     inspect.isclass(attr) or |  | ||||||
|                     isinstance(attr, (list, dict))): |  | ||||||
|                 register_type(attr) |  | ||||||
|             attrdef = getattr(class_, '__wsattrclass__', wsattr)(attr) |  | ||||||
|  |  | ||||||
|         attrdef.key = name |  | ||||||
|         if attrdef.name is None: |  | ||||||
|             attrdef.name = name |  | ||||||
|         attrdef.complextype = weakref.ref(class_) |  | ||||||
|         attributes.append(attrdef) |  | ||||||
|         setattr(class_, name, attrdef) |  | ||||||
|  |  | ||||||
|     sort_attributes(class_, attributes) |  | ||||||
|     return attributes |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def list_attributes(class_): |  | ||||||
|     """ |  | ||||||
|     Returns a list of a complex type attributes. |  | ||||||
|     """ |  | ||||||
|     if not iscomplex(class_): |  | ||||||
|         raise TypeError("%s is not a registered type") |  | ||||||
|     return class_._wsme_attributes |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def make_dataholder(class_): |  | ||||||
|     # the slots are computed outside the class scope to avoid |  | ||||||
|     # 'attr' to pullute the class namespace, which leads to weird |  | ||||||
|     # things if one of the slots is named 'attr'. |  | ||||||
|     slots = [attr.key for attr in class_._wsme_attributes] |  | ||||||
|  |  | ||||||
|     class DataHolder(object): |  | ||||||
|         __slots__ = slots |  | ||||||
|  |  | ||||||
|     DataHolder.__name__ = class_.__name__ + 'DataHolder' |  | ||||||
|     return DataHolder |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Registry(object): |  | ||||||
|     def __init__(self): |  | ||||||
|         self._complex_types = [] |  | ||||||
|         self.array_types = set() |  | ||||||
|         self.dict_types = set() |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def complex_types(self): |  | ||||||
|         return [t() for t in self._complex_types if t()] |  | ||||||
|  |  | ||||||
|     def register(self, class_): |  | ||||||
|         """ |  | ||||||
|         Make sure a type is registered. |  | ||||||
|  |  | ||||||
|         It is automatically called by :class:`expose() <wsme.expose>` |  | ||||||
|         and :class:`validate() <wsme.validate>`. |  | ||||||
|         Unless you want to control when the class inspection is done there |  | ||||||
|         is no need to call it. |  | ||||||
|         """ |  | ||||||
|         if class_ is None or \ |  | ||||||
|                 class_ in native_types or \ |  | ||||||
|                 isusertype(class_) or iscomplex(class_) or \ |  | ||||||
|                 isarray(class_) or isdict(class_): |  | ||||||
|             return class_ |  | ||||||
|  |  | ||||||
|         if isinstance(class_, list): |  | ||||||
|             if len(class_) != 1: |  | ||||||
|                 raise ValueError("Cannot register type %s" % repr(class_)) |  | ||||||
|             dt = ArrayType(class_[0]) |  | ||||||
|             self.register(dt.item_type) |  | ||||||
|             self.array_types.add(dt) |  | ||||||
|             return dt |  | ||||||
|  |  | ||||||
|         if isinstance(class_, dict): |  | ||||||
|             if len(class_) != 1: |  | ||||||
|                 raise ValueError("Cannot register type %s" % repr(class_)) |  | ||||||
|             dt = DictType(*list(class_.items())[0]) |  | ||||||
|             self.register(dt.value_type) |  | ||||||
|             self.dict_types.add(dt) |  | ||||||
|             return dt |  | ||||||
|  |  | ||||||
|         class_._wsme_attributes = None |  | ||||||
|         class_._wsme_attributes = inspect_class(class_) |  | ||||||
|         class_._wsme_DataHolderClass = make_dataholder(class_) |  | ||||||
|  |  | ||||||
|         class_.__registry__ = self |  | ||||||
|         self._complex_types.append(weakref.ref(class_)) |  | ||||||
|         return class_ |  | ||||||
|  |  | ||||||
|     def reregister(self, class_): |  | ||||||
|         """Register a type which may already have been registered. |  | ||||||
|         """ |  | ||||||
|         self._unregister(class_) |  | ||||||
|         return self.register(class_) |  | ||||||
|  |  | ||||||
|     def _unregister(self, class_): |  | ||||||
|         """Remove a previously registered type. |  | ||||||
|         """ |  | ||||||
|         # Clear the existing attribute reference so it is rebuilt if |  | ||||||
|         # the class is registered again later. |  | ||||||
|         if hasattr(class_, '_wsme_attributes'): |  | ||||||
|             del class_._wsme_attributes |  | ||||||
|         # FIXME(dhellmann): This method does not recurse through the |  | ||||||
|         # types like register() does. Should it? |  | ||||||
|         if isinstance(class_, list): |  | ||||||
|             at = ArrayType(class_[0]) |  | ||||||
|             try: |  | ||||||
|                 self.array_types.remove(at) |  | ||||||
|             except KeyError: |  | ||||||
|                 pass |  | ||||||
|         elif isinstance(class_, dict): |  | ||||||
|             key_type, value_type = list(class_.items())[0] |  | ||||||
|             self.dict_types = set( |  | ||||||
|                 dt for dt in self.dict_types |  | ||||||
|                 if (dt.key_type, dt.value_type) != (key_type, value_type) |  | ||||||
|             ) |  | ||||||
|         # We can't use remove() here because the items in |  | ||||||
|         # _complex_types are weakref objects pointing to the classes, |  | ||||||
|         # so we can't compare with them directly. |  | ||||||
|         self._complex_types = [ |  | ||||||
|             ct for ct in self._complex_types |  | ||||||
|             if ct() is not class_ |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|     def lookup(self, typename): |  | ||||||
|         log.debug('Lookup %s' % typename) |  | ||||||
|         modname = None |  | ||||||
|         if '.' in typename: |  | ||||||
|             modname, typename = typename.rsplit('.', 1) |  | ||||||
|         for ct in self._complex_types: |  | ||||||
|             ct = ct() |  | ||||||
|             if ct is not None and typename == ct.__name__ and ( |  | ||||||
|                     modname is None or modname == ct.__module__): |  | ||||||
|                 return ct |  | ||||||
|  |  | ||||||
|     def resolve_type(self, type_): |  | ||||||
|         if isinstance(type_, six.string_types): |  | ||||||
|             return self.lookup(type_) |  | ||||||
|         if isinstance(type_, list): |  | ||||||
|             type_ = ArrayType(type_[0]) |  | ||||||
|         if isinstance(type_, dict): |  | ||||||
|             type_ = DictType(list(type_.keys())[0], list(type_.values())[0]) |  | ||||||
|         if isinstance(type_, ArrayType): |  | ||||||
|             type_ = ArrayType(self.resolve_type(type_.item_type)) |  | ||||||
|             self.array_types.add(type_) |  | ||||||
|         elif isinstance(type_, DictType): |  | ||||||
|             type_ = DictType( |  | ||||||
|                 type_.key_type, |  | ||||||
|                 self.resolve_type(type_.value_type) |  | ||||||
|             ) |  | ||||||
|             self.dict_types.add(type_) |  | ||||||
|         else: |  | ||||||
|             type_ = self.register(type_) |  | ||||||
|         return type_ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Default type registry |  | ||||||
| registry = Registry() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def register_type(class_): |  | ||||||
|     return registry.register(class_) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseMeta(type): |  | ||||||
|     def __new__(cls, name, bases, dct): |  | ||||||
|         if bases and bases[0] is not object and '__registry__' not in dct: |  | ||||||
|             dct['__registry__'] = registry |  | ||||||
|         return type.__new__(cls, name, bases, dct) |  | ||||||
|  |  | ||||||
|     def __init__(cls, name, bases, dct): |  | ||||||
|         if bases and bases[0] is not object and cls.__registry__: |  | ||||||
|             cls.__registry__.register(cls) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Base(six.with_metaclass(BaseMeta)): |  | ||||||
|     """Base type for complex types""" |  | ||||||
|     def __init__(self, **kw): |  | ||||||
|         for key, value in kw.items(): |  | ||||||
|             if hasattr(self, key): |  | ||||||
|                 setattr(self, key, value) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class File(Base): |  | ||||||
|     """A complex type that represents a file. |  | ||||||
|  |  | ||||||
|     In the particular case of protocol accepting form encoded data as |  | ||||||
|     input, File can be loaded from a form file field. |  | ||||||
|     """ |  | ||||||
|     #: The file name |  | ||||||
|     filename = wsattr(text) |  | ||||||
|  |  | ||||||
|     #: Mime type of the content |  | ||||||
|     contenttype = wsattr(text) |  | ||||||
|  |  | ||||||
|     def _get_content(self): |  | ||||||
|         if self._content is None and self._file: |  | ||||||
|             self._content = self._file.read() |  | ||||||
|         return self._content |  | ||||||
|  |  | ||||||
|     def _set_content(self, value): |  | ||||||
|         self._content = value |  | ||||||
|         self._file = None |  | ||||||
|  |  | ||||||
|     #: File content |  | ||||||
|     content = wsproperty(binary, _get_content, _set_content) |  | ||||||
|  |  | ||||||
|     def __init__(self, filename=None, file=None, content=None, |  | ||||||
|                  contenttype=None, fieldstorage=None): |  | ||||||
|         self.filename = filename |  | ||||||
|         self.contenttype = contenttype |  | ||||||
|         self._file = file |  | ||||||
|         self._content = content |  | ||||||
|  |  | ||||||
|         if fieldstorage is not None: |  | ||||||
|             if fieldstorage.file: |  | ||||||
|                 self._file = fieldstorage.file |  | ||||||
|                 self.filename = fieldstorage.filename |  | ||||||
|                 self.contenttype = text(fieldstorage.type) |  | ||||||
|             else: |  | ||||||
|                 self._content = fieldstorage.value |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def file(self): |  | ||||||
|         if self._file is None and self._content: |  | ||||||
|             self._file = six.BytesIO(self._content) |  | ||||||
|         return self._file |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DynamicBase(Base): |  | ||||||
|     """Base type for complex types for which all attributes are not |  | ||||||
|     defined when the class is constructed. |  | ||||||
|  |  | ||||||
|     This class is meant to be used as a base for types that have |  | ||||||
|     properties added after the main class is created, such as by |  | ||||||
|     loading plugins. |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def add_attributes(cls, **attrs): |  | ||||||
|         """Add more attributes |  | ||||||
|  |  | ||||||
|         The arguments should be valid Python attribute names |  | ||||||
|         associated with a type for the new attribute. |  | ||||||
|  |  | ||||||
|         """ |  | ||||||
|         for n, t in attrs.items(): |  | ||||||
|             setattr(cls, n, t) |  | ||||||
|         cls.__registry__.reregister(cls) |  | ||||||
							
								
								
									
										118
									
								
								wsme/utils.py
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								wsme/utils.py
									
									
									
									
									
								
							| @@ -1,118 +0,0 @@ | |||||||
| import decimal |  | ||||||
| import datetime |  | ||||||
| import pytz |  | ||||||
| import re |  | ||||||
| from six.moves import builtins, http_client |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import dateutil.parser |  | ||||||
| except: |  | ||||||
|     dateutil = None  # noqa |  | ||||||
|  |  | ||||||
| date_re = r'(?P<year>-?\d{4,})-(?P<month>\d{2})-(?P<day>\d{2})' |  | ||||||
| time_re = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})' + \ |  | ||||||
|           r'(\.(?P<sec_frac>\d+))?' |  | ||||||
| tz_re = r'((?P<tz_sign>[+-])(?P<tz_hour>\d{2}):(?P<tz_min>\d{2}))' + \ |  | ||||||
|         r'|(?P<tz_z>Z)' |  | ||||||
|  |  | ||||||
| datetime_re = re.compile( |  | ||||||
|     '%sT%s(%s)?' % (date_re, time_re, tz_re)) |  | ||||||
| date_re = re.compile(date_re) |  | ||||||
| time_re = re.compile('%s(%s)?' % (time_re, tz_re)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if hasattr(builtins, '_'): |  | ||||||
|     _ = builtins._ |  | ||||||
| else: |  | ||||||
|     def _(s): |  | ||||||
|         return s |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_isodate(value): |  | ||||||
|     m = date_re.match(value) |  | ||||||
|     if m is None: |  | ||||||
|         raise ValueError("'%s' is not a legal date value" % (value)) |  | ||||||
|     try: |  | ||||||
|         return datetime.date( |  | ||||||
|             int(m.group('year')), |  | ||||||
|             int(m.group('month')), |  | ||||||
|             int(m.group('day'))) |  | ||||||
|     except ValueError: |  | ||||||
|         raise ValueError("'%s' is a out-of-range date" % (value)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_isotime(value): |  | ||||||
|     m = time_re.match(value) |  | ||||||
|     if m is None: |  | ||||||
|         raise ValueError("'%s' is not a legal time value" % (value)) |  | ||||||
|     try: |  | ||||||
|         ms = 0 |  | ||||||
|         if m.group('sec_frac') is not None: |  | ||||||
|             f = decimal.Decimal('0.' + m.group('sec_frac')) |  | ||||||
|             f = str(f.quantize(decimal.Decimal('0.000001'))) |  | ||||||
|             ms = int(f[2:]) |  | ||||||
|         tz = _parse_tzparts(m.groupdict()) |  | ||||||
|         return datetime.time( |  | ||||||
|             int(m.group('hour')), |  | ||||||
|             int(m.group('min')), |  | ||||||
|             int(m.group('sec')), |  | ||||||
|             ms, |  | ||||||
|             tz) |  | ||||||
|     except ValueError: |  | ||||||
|         raise ValueError("'%s' is a out-of-range time" % (value)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_isodatetime(value): |  | ||||||
|     if dateutil: |  | ||||||
|         return dateutil.parser.parse(value) |  | ||||||
|     m = datetime_re.match(value) |  | ||||||
|     if m is None: |  | ||||||
|         raise ValueError("'%s' is not a legal datetime value" % (value)) |  | ||||||
|     try: |  | ||||||
|         ms = 0 |  | ||||||
|         if m.group('sec_frac') is not None: |  | ||||||
|             f = decimal.Decimal('0.' + m.group('sec_frac')) |  | ||||||
|             f = f.quantize(decimal.Decimal('0.000001')) |  | ||||||
|             ms = int(str(f)[2:]) |  | ||||||
|         tz = _parse_tzparts(m.groupdict()) |  | ||||||
|         return datetime.datetime( |  | ||||||
|             int(m.group('year')), |  | ||||||
|             int(m.group('month')), |  | ||||||
|             int(m.group('day')), |  | ||||||
|             int(m.group('hour')), |  | ||||||
|             int(m.group('min')), |  | ||||||
|             int(m.group('sec')), |  | ||||||
|             ms, |  | ||||||
|             tz) |  | ||||||
|     except ValueError: |  | ||||||
|         raise ValueError("'%s' is a out-of-range datetime" % (value)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _parse_tzparts(parts): |  | ||||||
|     if 'tz_z' in parts and parts['tz_z'] == 'Z': |  | ||||||
|         return pytz.UTC |  | ||||||
|     if 'tz_min' not in parts or not parts['tz_min']: |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     tz_minute_offset = (int(parts['tz_hour']) * 60 + int(parts['tz_min'])) |  | ||||||
|     tz_multiplier = -1 if parts['tz_sign'] == '-' else 1 |  | ||||||
|  |  | ||||||
|     return pytz.FixedOffset(tz_multiplier * tz_minute_offset) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_valid_code(code_value): |  | ||||||
|     """ |  | ||||||
|     This function checks if incoming value in http response codes range. |  | ||||||
|     """ |  | ||||||
|     return code_value in http_client.responses |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_client_error(code): |  | ||||||
|     """ Checks client error code (RFC 2616).""" |  | ||||||
|     return 400 <= code < 500 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     from collections import OrderedDict |  | ||||||
| except ImportError: |  | ||||||
|     from ordereddict import OrderedDict  # noqa |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| import pkg_resources |  | ||||||
| pkg_resources.declare_namespace(__name__) |  | ||||||
| @@ -1,168 +0,0 @@ | |||||||
| """ |  | ||||||
| WSME for cornice |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Activate it:: |  | ||||||
|  |  | ||||||
|     config.include('wsme.cornice') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| And use it:: |  | ||||||
|  |  | ||||||
|     @hello.get() |  | ||||||
|     @wsexpose(Message, wsme.types.text) |  | ||||||
|     def get_hello(who=u'World'): |  | ||||||
|         return Message(text='Hello %s' % who) |  | ||||||
| """ |  | ||||||
| from __future__ import absolute_import |  | ||||||
|  |  | ||||||
| import inspect |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| import wsme |  | ||||||
| from wsme.rest import json as restjson |  | ||||||
| from wsme.rest import xml as restxml |  | ||||||
| import wsme.runtime |  | ||||||
| import wsme.api |  | ||||||
| import functools |  | ||||||
|  |  | ||||||
| from wsme.rest.args import ( |  | ||||||
|     args_from_args, args_from_params, args_from_body, combine_args |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WSMEJsonRenderer(object): |  | ||||||
|     def __init__(self, info): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def __call__(self, data, context): |  | ||||||
|         response = context['request'].response |  | ||||||
|         response.content_type = 'application/json' |  | ||||||
|         if 'faultcode' in data: |  | ||||||
|             if 'orig_code' in data: |  | ||||||
|                 response.status_code = data['orig_code'] |  | ||||||
|             elif data['faultcode'] == 'Client': |  | ||||||
|                 response.status_code = 400 |  | ||||||
|             else: |  | ||||||
|                 response.status_code = 500 |  | ||||||
|             return restjson.encode_error(None, data) |  | ||||||
|         obj = data['result'] |  | ||||||
|         if isinstance(obj, wsme.api.Response): |  | ||||||
|             response.status_code = obj.status_code |  | ||||||
|             if obj.error: |  | ||||||
|                 return restjson.encode_error(None, obj.error) |  | ||||||
|             obj = obj.obj |  | ||||||
|         return restjson.encode_result(obj, data['datatype']) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WSMEXmlRenderer(object): |  | ||||||
|     def __init__(self, info): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def __call__(self, data, context): |  | ||||||
|         response = context['request'].response |  | ||||||
|         if 'faultcode' in data: |  | ||||||
|             if data['faultcode'] == 'Client': |  | ||||||
|                 response.status_code = 400 |  | ||||||
|             else: |  | ||||||
|                 response.status_code = 500 |  | ||||||
|             return restxml.encode_error(None, data) |  | ||||||
|         response.content_type = 'text/xml' |  | ||||||
|         return restxml.encode_result(data['result'], data['datatype']) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_outputformat(request): |  | ||||||
|     df = None |  | ||||||
|     if 'Accept' in request.headers: |  | ||||||
|         if 'application/json' in request.headers['Accept']: |  | ||||||
|             df = 'json' |  | ||||||
|         elif 'text/xml' in request.headers['Accept']: |  | ||||||
|             df = 'xml' |  | ||||||
|     if df is None and 'Content-Type' in request.headers: |  | ||||||
|         if 'application/json' in request.headers['Content-Type']: |  | ||||||
|             df = 'json' |  | ||||||
|         elif 'text/xml' in request.headers['Content-Type']: |  | ||||||
|             df = 'xml' |  | ||||||
|     return df if df else 'json' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def signature(*args, **kwargs): |  | ||||||
|     sig = wsme.signature(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def decorate(f): |  | ||||||
|         args = inspect.getargspec(f)[0] |  | ||||||
|         with_self = args[0] == 'self' if args else False |  | ||||||
|         f = sig(f) |  | ||||||
|         funcdef = wsme.api.FunctionDefinition.get(f) |  | ||||||
|         funcdef.resolve_types(wsme.types.registry) |  | ||||||
|  |  | ||||||
|         @functools.wraps(f) |  | ||||||
|         def callfunction(*args): |  | ||||||
|             if with_self: |  | ||||||
|                 if len(args) == 1: |  | ||||||
|                     self = args[0] |  | ||||||
|                     request = self.request |  | ||||||
|                 elif len(args) == 2: |  | ||||||
|                     self, request = args |  | ||||||
|                 else: |  | ||||||
|                     raise ValueError("Cannot do anything with these arguments") |  | ||||||
|             else: |  | ||||||
|                 request = args[0] |  | ||||||
|             request.override_renderer = 'wsme' + get_outputformat(request) |  | ||||||
|             try: |  | ||||||
|                 args, kwargs = combine_args(funcdef, ( |  | ||||||
|                     args_from_args(funcdef, (), request.matchdict), |  | ||||||
|                     args_from_params(funcdef, request.params), |  | ||||||
|                     args_from_body(funcdef, request.body, request.content_type) |  | ||||||
|                 )) |  | ||||||
|                 wsme.runtime.check_arguments(funcdef, args, kwargs) |  | ||||||
|                 if funcdef.pass_request: |  | ||||||
|                     kwargs[funcdef.pass_request] = request |  | ||||||
|                 if with_self: |  | ||||||
|                     args.insert(0, self) |  | ||||||
|  |  | ||||||
|                 result = f(*args, **kwargs) |  | ||||||
|                 return { |  | ||||||
|                     'datatype': funcdef.return_type, |  | ||||||
|                     'result': result |  | ||||||
|                 } |  | ||||||
|             except: |  | ||||||
|                 try: |  | ||||||
|                     exception_info = sys.exc_info() |  | ||||||
|                     orig_exception = exception_info[1] |  | ||||||
|                     orig_code = getattr(orig_exception, 'code', None) |  | ||||||
|                     data = wsme.api.format_exception(exception_info) |  | ||||||
|                     if orig_code is not None: |  | ||||||
|                         data['orig_code'] = orig_code |  | ||||||
|                     return data |  | ||||||
|                 finally: |  | ||||||
|                     del exception_info |  | ||||||
|  |  | ||||||
|         callfunction.wsme_func = f |  | ||||||
|         return callfunction |  | ||||||
|     return decorate |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def scan_api(root=None): |  | ||||||
|     from cornice.service import get_services |  | ||||||
|     for service in get_services(): |  | ||||||
|         for method, func, options in service.definitions: |  | ||||||
|             wsme_func = getattr(func, 'wsme_func') |  | ||||||
|             basepath = service.path.split('/') |  | ||||||
|             if basepath and not basepath[0]: |  | ||||||
|                 del basepath[0] |  | ||||||
|             if wsme_func: |  | ||||||
|                 yield ( |  | ||||||
|                     basepath + [method.lower()], |  | ||||||
|                     wsme_func._wsme_definition |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def includeme(config): |  | ||||||
|     import pyramid.wsgi |  | ||||||
|     wsroot = wsme.WSRoot(scan_api=scan_api, webpath='/ws') |  | ||||||
|     wsroot.addprotocol('extdirect') |  | ||||||
|     config.add_renderer('wsmejson', WSMEJsonRenderer) |  | ||||||
|     config.add_renderer('wsmexml', WSMEXmlRenderer) |  | ||||||
|     config.add_route('wsme', '/ws/*path') |  | ||||||
|     config.add_view(pyramid.wsgi.wsgiapp(wsroot.wsgiapp()), route_name='wsme') |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| from wsmeext.extdirect.protocol import ExtDirectProtocol  # noqa |  | ||||||
| @@ -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,), {} |  | ||||||
| ) |  | ||||||
| @@ -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) |  | ||||||
| @@ -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 |  | ||||||
|         ) |  | ||||||
							
								
								
									
										108
									
								
								wsmeext/flask.py
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								wsmeext/flask.py
									
									
									
									
									
								
							| @@ -1,108 +0,0 @@ | |||||||
| from __future__ import absolute_import |  | ||||||
|  |  | ||||||
| import functools |  | ||||||
| import logging |  | ||||||
| import sys |  | ||||||
| import inspect |  | ||||||
|  |  | ||||||
| import wsme |  | ||||||
| import wsme.api |  | ||||||
| import wsme.rest.json |  | ||||||
| import wsme.rest.xml |  | ||||||
| import wsme.rest.args |  | ||||||
| from wsme.utils import is_valid_code |  | ||||||
|  |  | ||||||
| import flask |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| TYPES = { |  | ||||||
|     'application/json': wsme.rest.json, |  | ||||||
|     'application/xml': wsme.rest.xml, |  | ||||||
|     'text/xml': wsme.rest.xml |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_dataformat(): |  | ||||||
|     if 'Accept' in flask.request.headers: |  | ||||||
|         for t in TYPES: |  | ||||||
|             if t in flask.request.headers['Accept']: |  | ||||||
|                 return TYPES[t] |  | ||||||
|  |  | ||||||
|     # Look for the wanted data format in the request. |  | ||||||
|     req_dataformat = getattr(flask.request, 'response_type', None) |  | ||||||
|     if req_dataformat in TYPES: |  | ||||||
|         return TYPES[req_dataformat] |  | ||||||
|  |  | ||||||
|     log.info('''Could not determine what format is wanted by the |  | ||||||
|              caller, falling back to json''') |  | ||||||
|     return wsme.rest.json |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def signature(*args, **kw): |  | ||||||
|     sig = wsme.signature(*args, **kw) |  | ||||||
|  |  | ||||||
|     def decorator(f): |  | ||||||
|         args = inspect.getargspec(f)[0] |  | ||||||
|         ismethod = args and args[0] == 'self' |  | ||||||
|         sig(f) |  | ||||||
|         funcdef = wsme.api.FunctionDefinition.get(f) |  | ||||||
|         funcdef.resolve_types(wsme.types.registry) |  | ||||||
|  |  | ||||||
|         @functools.wraps(f) |  | ||||||
|         def wrapper(*args, **kwargs): |  | ||||||
|             if ismethod: |  | ||||||
|                 self, args = args[0], args[1:] |  | ||||||
|             args, kwargs = wsme.rest.args.get_args( |  | ||||||
|                 funcdef, args, kwargs, |  | ||||||
|                 flask.request.args, flask.request.form, |  | ||||||
|                 flask.request.data, |  | ||||||
|                 flask.request.mimetype |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|             if funcdef.pass_request: |  | ||||||
|                 kwargs[funcdef.pass_request] = flask.request |  | ||||||
|  |  | ||||||
|             dataformat = get_dataformat() |  | ||||||
|  |  | ||||||
|             try: |  | ||||||
|                 if ismethod: |  | ||||||
|                     args = [self] + list(args) |  | ||||||
|                 result = f(*args, **kwargs) |  | ||||||
|  |  | ||||||
|                 # NOTE: Support setting of status_code with default 20 |  | ||||||
|                 status_code = funcdef.status_code |  | ||||||
|                 if isinstance(result, wsme.api.Response): |  | ||||||
|                     status_code = result.status_code |  | ||||||
|                     result = result.obj |  | ||||||
|  |  | ||||||
|                 res = flask.make_response( |  | ||||||
|                     dataformat.encode_result( |  | ||||||
|                         result, |  | ||||||
|                         funcdef.return_type |  | ||||||
|                     ) |  | ||||||
|                 ) |  | ||||||
|                 res.mimetype = dataformat.content_type |  | ||||||
|                 res.status_code = status_code |  | ||||||
|             except: |  | ||||||
|                 try: |  | ||||||
|                     exception_info = sys.exc_info() |  | ||||||
|                     orig_exception = exception_info[1] |  | ||||||
|                     orig_code = getattr(orig_exception, 'code', None) |  | ||||||
|                     data = wsme.api.format_exception(exception_info) |  | ||||||
|                 finally: |  | ||||||
|                     del exception_info |  | ||||||
|  |  | ||||||
|                 res = flask.make_response(dataformat.encode_error(None, data)) |  | ||||||
|                 if data['faultcode'] == 'client': |  | ||||||
|                     res.status_code = 400 |  | ||||||
|                 elif orig_code and is_valid_code(orig_code): |  | ||||||
|                     res.status_code = orig_code |  | ||||||
|                 else: |  | ||||||
|                     res.status_code = 500 |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         wrapper.wsme_func = f |  | ||||||
|         return wrapper |  | ||||||
|     return decorator |  | ||||||
							
								
								
									
										142
									
								
								wsmeext/pecan.py
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								wsmeext/pecan.py
									
									
									
									
									
								
							| @@ -1,142 +0,0 @@ | |||||||
| from __future__ import absolute_import |  | ||||||
|  |  | ||||||
| import functools |  | ||||||
| import inspect |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| import wsme |  | ||||||
| import wsme.rest.args |  | ||||||
| import wsme.rest.json |  | ||||||
| import wsme.rest.xml |  | ||||||
|  |  | ||||||
| import pecan |  | ||||||
|  |  | ||||||
| from wsme.utils import is_valid_code |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class JSonRenderer(object): |  | ||||||
|     @staticmethod |  | ||||||
|     def __init__(path, extra_vars): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def render(template_path, namespace): |  | ||||||
|         if 'faultcode' in namespace: |  | ||||||
|             return wsme.rest.json.encode_error(None, namespace) |  | ||||||
|         return wsme.rest.json.encode_result( |  | ||||||
|             namespace['result'], |  | ||||||
|             namespace['datatype'] |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class XMLRenderer(object): |  | ||||||
|     @staticmethod |  | ||||||
|     def __init__(path, extra_vars): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def render(template_path, namespace): |  | ||||||
|         if 'faultcode' in namespace: |  | ||||||
|             return wsme.rest.xml.encode_error(None, namespace) |  | ||||||
|         return wsme.rest.xml.encode_result( |  | ||||||
|             namespace['result'], |  | ||||||
|             namespace['datatype'] |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| pecan.templating._builtin_renderers['wsmejson'] = JSonRenderer |  | ||||||
| pecan.templating._builtin_renderers['wsmexml'] = XMLRenderer |  | ||||||
|  |  | ||||||
| pecan_json_decorate = pecan.expose( |  | ||||||
|     template='wsmejson:', |  | ||||||
|     content_type='application/json', |  | ||||||
|     generic=False) |  | ||||||
| pecan_xml_decorate = pecan.expose( |  | ||||||
|     template='wsmexml:', |  | ||||||
|     content_type='application/xml', |  | ||||||
|     generic=False |  | ||||||
| ) |  | ||||||
| pecan_text_xml_decorate = pecan.expose( |  | ||||||
|     template='wsmexml:', |  | ||||||
|     content_type='text/xml', |  | ||||||
|     generic=False |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def wsexpose(*args, **kwargs): |  | ||||||
|     sig = wsme.signature(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def decorate(f): |  | ||||||
|         sig(f) |  | ||||||
|         funcdef = wsme.api.FunctionDefinition.get(f) |  | ||||||
|         funcdef.resolve_types(wsme.types.registry) |  | ||||||
|  |  | ||||||
|         @functools.wraps(f) |  | ||||||
|         def callfunction(self, *args, **kwargs): |  | ||||||
|             return_type = funcdef.return_type |  | ||||||
|  |  | ||||||
|             try: |  | ||||||
|                 args, kwargs = wsme.rest.args.get_args( |  | ||||||
|                     funcdef, args, kwargs, pecan.request.params, None, |  | ||||||
|                     pecan.request.body, pecan.request.content_type |  | ||||||
|                 ) |  | ||||||
|                 if funcdef.pass_request: |  | ||||||
|                     kwargs[funcdef.pass_request] = pecan.request |  | ||||||
|                 result = f(self, *args, **kwargs) |  | ||||||
|  |  | ||||||
|                 # NOTE: Support setting of status_code with default 201 |  | ||||||
|                 pecan.response.status = funcdef.status_code |  | ||||||
|                 if isinstance(result, wsme.api.Response): |  | ||||||
|                     pecan.response.status = result.status_code |  | ||||||
|  |  | ||||||
|                     # NOTE(lucasagomes): If the return code is 204 |  | ||||||
|                     # (No Response) we have to make sure that we are not |  | ||||||
|                     # returning anything in the body response and the |  | ||||||
|                     # content-length is 0 |  | ||||||
|                     if result.status_code == 204: |  | ||||||
|                         return_type = None |  | ||||||
|                     elif not isinstance(result.return_type, |  | ||||||
|                                         wsme.types.UnsetType): |  | ||||||
|                         return_type = result.return_type |  | ||||||
|  |  | ||||||
|                     result = result.obj |  | ||||||
|  |  | ||||||
|             except: |  | ||||||
|                 try: |  | ||||||
|                     exception_info = sys.exc_info() |  | ||||||
|                     orig_exception = exception_info[1] |  | ||||||
|                     orig_code = getattr(orig_exception, 'code', None) |  | ||||||
|                     data = wsme.api.format_exception( |  | ||||||
|                         exception_info, |  | ||||||
|                         pecan.conf.get('wsme', {}).get('debug', False) |  | ||||||
|                     ) |  | ||||||
|                 finally: |  | ||||||
|                     del exception_info |  | ||||||
|  |  | ||||||
|                 if orig_code and is_valid_code(orig_code): |  | ||||||
|                     pecan.response.status = orig_code |  | ||||||
|                 else: |  | ||||||
|                     pecan.response.status = 500 |  | ||||||
|  |  | ||||||
|                 return data |  | ||||||
|  |  | ||||||
|             if return_type is None: |  | ||||||
|                 pecan.request.pecan['content_type'] = None |  | ||||||
|                 pecan.response.content_type = None |  | ||||||
|                 return '' |  | ||||||
|  |  | ||||||
|             return dict( |  | ||||||
|                 datatype=return_type, |  | ||||||
|                 result=result |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         if 'xml' in funcdef.rest_content_types: |  | ||||||
|             pecan_xml_decorate(callfunction) |  | ||||||
|             pecan_text_xml_decorate(callfunction) |  | ||||||
|         if 'json' in funcdef.rest_content_types: |  | ||||||
|             pecan_json_decorate(callfunction) |  | ||||||
|         pecan.util._cfg(callfunction)['argspec'] = inspect.getargspec(f) |  | ||||||
|         callfunction._wsme_definition = funcdef |  | ||||||
|         return callfunction |  | ||||||
|  |  | ||||||
|     return decorate |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| from __future__ import absolute_import |  | ||||||
|  |  | ||||||
| from wsmeext.soap.protocol import SoapProtocol |  | ||||||
|  |  | ||||||
| __all__ = ['SoapProtocol'] |  | ||||||
| @@ -1,478 +0,0 @@ | |||||||
| """ |  | ||||||
| A SOAP implementation for wsme. |  | ||||||
| Parts of the code were taken from the tgwebservices soap implmentation. |  | ||||||
| """ |  | ||||||
| from __future__ import absolute_import |  | ||||||
|  |  | ||||||
| import pkg_resources |  | ||||||
| import datetime |  | ||||||
| import decimal |  | ||||||
| import base64 |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| from wsmeext.soap.simplegeneric import generic |  | ||||||
| from wsmeext.soap.wsdl import WSDLGenerator |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     from lxml import etree as ET |  | ||||||
|     use_lxml = True |  | ||||||
| except ImportError: |  | ||||||
|     from xml.etree import cElementTree as ET  # noqa |  | ||||||
|     use_lxml = False |  | ||||||
|  |  | ||||||
| from wsme.protocol import CallContext, Protocol, expose |  | ||||||
|  |  | ||||||
| import wsme.types |  | ||||||
| import wsme.runtime |  | ||||||
|  |  | ||||||
| from wsme import exc |  | ||||||
| from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
| xsd_ns = 'http://www.w3.org/2001/XMLSchema' |  | ||||||
| xsi_ns = 'http://www.w3.org/2001/XMLSchema-instance' |  | ||||||
| soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/' |  | ||||||
|  |  | ||||||
| if not use_lxml: |  | ||||||
|     ET.register_namespace('soap', soapenv_ns) |  | ||||||
|  |  | ||||||
| type_qn = '{%s}type' % xsi_ns |  | ||||||
| nil_qn = '{%s}nil' % xsi_ns |  | ||||||
|  |  | ||||||
| Envelope_qn = '{%s}Envelope' % soapenv_ns |  | ||||||
| Body_qn = '{%s}Body' % soapenv_ns |  | ||||||
| Fault_qn = '{%s}Fault' % soapenv_ns |  | ||||||
| faultcode_qn = '{%s}faultcode' % soapenv_ns |  | ||||||
| faultstring_qn = '{%s}faultstring' % soapenv_ns |  | ||||||
| detail_qn = '{%s}detail' % soapenv_ns |  | ||||||
|  |  | ||||||
|  |  | ||||||
| type_registry = { |  | ||||||
|     wsme.types.bytes: 'xs:string', |  | ||||||
|     wsme.types.text: 'xs:string', |  | ||||||
|     int: 'xs:int', |  | ||||||
|     float: "xs:float", |  | ||||||
|     bool: "xs:boolean", |  | ||||||
|     datetime.datetime: "xs:dateTime", |  | ||||||
|     datetime.date: "xs:date", |  | ||||||
|     datetime.time: "xs:time", |  | ||||||
|     decimal.Decimal: "xs:decimal", |  | ||||||
|     wsme.types.binary: "xs:base64Binary", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if not six.PY3: |  | ||||||
|     type_registry[long] = "xs:long" |  | ||||||
|  |  | ||||||
| array_registry = { |  | ||||||
|     wsme.types.text: "String_Array", |  | ||||||
|     wsme.types.bytes: "String_Array", |  | ||||||
|     int: "Int_Array", |  | ||||||
|     float: "Float_Array", |  | ||||||
|     bool: "Boolean_Array", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if not six.PY3: |  | ||||||
|     array_registry[long] = "Long_Array" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def soap_array(datatype, ns): |  | ||||||
|     if datatype.item_type in array_registry: |  | ||||||
|         name = array_registry[datatype.item_type] |  | ||||||
|     else: |  | ||||||
|         name = soap_type(datatype.item_type, False) + '_Array' |  | ||||||
|     if ns: |  | ||||||
|         name = 'types:' + name |  | ||||||
|     return name |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def soap_type(datatype, ns): |  | ||||||
|     name = None |  | ||||||
|     if wsme.types.isarray(datatype): |  | ||||||
|         return soap_array(datatype, ns) |  | ||||||
|     if wsme.types.isdict(datatype): |  | ||||||
|         return None |  | ||||||
|     if datatype in type_registry: |  | ||||||
|         stype = type_registry[datatype] |  | ||||||
|         if not ns: |  | ||||||
|             stype = stype[3:] |  | ||||||
|         return stype |  | ||||||
|     if wsme.types.iscomplex(datatype): |  | ||||||
|         name = datatype.__name__ |  | ||||||
|         if name and ns: |  | ||||||
|             name = 'types:' + name |  | ||||||
|         return name |  | ||||||
|     if wsme.types.isusertype(datatype): |  | ||||||
|         return soap_type(datatype.basetype, ns) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def soap_fname(path, funcdef): |  | ||||||
|     return "".join([path[0]] + [i.capitalize() for i in path[1:]]) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SoapEncoder(object): |  | ||||||
|     def __init__(self, types_ns): |  | ||||||
|         self.types_ns = types_ns |  | ||||||
|  |  | ||||||
|     def make_soap_element(self, datatype, tag, value, xsitype=None): |  | ||||||
|         el = ET.Element(tag) |  | ||||||
|         if value is None: |  | ||||||
|             el.set(nil_qn, 'true') |  | ||||||
|         elif xsitype is not None: |  | ||||||
|             el.set(type_qn, xsitype) |  | ||||||
|             el.text = value |  | ||||||
|         elif wsme.types.isusertype(datatype): |  | ||||||
|             return self.tosoap(datatype.basetype, tag, |  | ||||||
|                                datatype.tobasetype(value)) |  | ||||||
|         elif wsme.types.iscomplex(datatype): |  | ||||||
|             el.set(type_qn, 'types:%s' % (datatype.__name__)) |  | ||||||
|             for attrdef in wsme.types.list_attributes(datatype): |  | ||||||
|                 attrvalue = getattr(value, attrdef.key) |  | ||||||
|                 if attrvalue is not wsme.types.Unset: |  | ||||||
|                     el.append(self.tosoap( |  | ||||||
|                         attrdef.datatype, |  | ||||||
|                         '{%s}%s' % (self.types_ns, attrdef.name), |  | ||||||
|                         attrvalue |  | ||||||
|                     )) |  | ||||||
|         else: |  | ||||||
|             el.set(type_qn, type_registry.get(datatype)) |  | ||||||
|             if not isinstance(value, wsme.types.text): |  | ||||||
|                 value = wsme.types.text(value) |  | ||||||
|             el.text = value |  | ||||||
|         return el |  | ||||||
|  |  | ||||||
|     @generic |  | ||||||
|     def tosoap(self, datatype, tag, value): |  | ||||||
|         """Converts a value into xml Element objects for inclusion in the SOAP |  | ||||||
|         response output (after adding the type to the type_registry). |  | ||||||
|  |  | ||||||
|         If a non-complex user specific type is to be used in the api, |  | ||||||
|         a specific toxml should be added:: |  | ||||||
|  |  | ||||||
|             from wsme.protocol.soap import tosoap, make_soap_element, \ |  | ||||||
|                 type_registry |  | ||||||
|  |  | ||||||
|             class MySpecialType(object): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|             type_registry[MySpecialType] = 'xs:MySpecialType' |  | ||||||
|  |  | ||||||
|             @tosoap.when_object(MySpecialType) |  | ||||||
|             def myspecialtype_tosoap(datatype, tag, value): |  | ||||||
|                 return make_soap_element(datatype, tag, str(value)) |  | ||||||
|         """ |  | ||||||
|         return self.make_soap_element(datatype, tag, value) |  | ||||||
|  |  | ||||||
|     @tosoap.when_type(wsme.types.ArrayType) |  | ||||||
|     def array_tosoap(self, datatype, tag, value): |  | ||||||
|         el = ET.Element(tag) |  | ||||||
|         el.set(type_qn, soap_array(datatype, self.types_ns)) |  | ||||||
|         if value is None: |  | ||||||
|             el.set(nil_qn, 'true') |  | ||||||
|         elif len(value) == 0: |  | ||||||
|             el.append(ET.Element('item')) |  | ||||||
|         else: |  | ||||||
|             for item in value: |  | ||||||
|                 el.append(self.tosoap(datatype.item_type, 'item', item)) |  | ||||||
|         return el |  | ||||||
|  |  | ||||||
|     @tosoap.when_object(bool) |  | ||||||
|     def bool_tosoap(self, datatype, tag, value): |  | ||||||
|         return self.make_soap_element( |  | ||||||
|             datatype, |  | ||||||
|             tag, |  | ||||||
|             'true' if value is True else 'false' if value is False else None |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @tosoap.when_object(wsme.types.bytes) |  | ||||||
|     def bytes_tosoap(self, datatype, tag, value): |  | ||||||
|         log.debug('(bytes_tosoap, %s, %s, %s, %s)', datatype, |  | ||||||
|                   tag, value, type(value)) |  | ||||||
|         if isinstance(value, wsme.types.bytes): |  | ||||||
|             value = value.decode('ascii') |  | ||||||
|         return self.make_soap_element(datatype, tag, value) |  | ||||||
|  |  | ||||||
|     @tosoap.when_object(datetime.datetime) |  | ||||||
|     def datetime_tosoap(self, datatype, tag, value): |  | ||||||
|         return self.make_soap_element( |  | ||||||
|             datatype, |  | ||||||
|             tag, |  | ||||||
|             value is not None and value.isoformat() or None |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @tosoap.when_object(wsme.types.binary) |  | ||||||
|     def binary_tosoap(self, datatype, tag, value): |  | ||||||
|         log.debug("(%s, %s, %s)", datatype, tag, value) |  | ||||||
|         value = base64.encodestring(value) if value is not None else None |  | ||||||
|         if six.PY3: |  | ||||||
|             value = value.decode('ascii') |  | ||||||
|         return self.make_soap_element( |  | ||||||
|             datatype.basetype, tag, value, 'xs:base64Binary' |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @tosoap.when_object(None) |  | ||||||
|     def None_tosoap(self, datatype, tag, value): |  | ||||||
|         return self.make_soap_element(datatype, tag, None) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @generic |  | ||||||
| def fromsoap(datatype, el, ns): |  | ||||||
|     """ |  | ||||||
|     A generic converter from soap elements to python datatype. |  | ||||||
|  |  | ||||||
|     If a non-complex user specific type is to be used in the api, |  | ||||||
|     a specific fromsoap should be added. |  | ||||||
|     """ |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if datatype in type_registry: |  | ||||||
|         value = datatype(el.text) |  | ||||||
|     elif wsme.types.isusertype(datatype): |  | ||||||
|         value = datatype.frombasetype( |  | ||||||
|             fromsoap(datatype.basetype, el, ns)) |  | ||||||
|     else: |  | ||||||
|         value = datatype() |  | ||||||
|         for attr in wsme.types.list_attributes(datatype): |  | ||||||
|             child = el.find('{%s}%s' % (ns['type'], attr.name)) |  | ||||||
|             if child is not None: |  | ||||||
|                 setattr(value, attr.key, fromsoap(attr.datatype, child, ns)) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_type(wsme.types.ArrayType) |  | ||||||
| def array_fromsoap(datatype, el, ns): |  | ||||||
|     if len(el) == 1: |  | ||||||
|         if datatype.item_type \ |  | ||||||
|                 not in wsme.types.pod_types + wsme.types.dt_types \ |  | ||||||
|                 and len(el[0]) == 0: |  | ||||||
|             return [] |  | ||||||
|     return [fromsoap(datatype.item_type, child, ns) for child in el] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(wsme.types.bytes) |  | ||||||
| def bytes_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:string'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return el.text.encode('ascii') if el.text else six.b('') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(wsme.types.text) |  | ||||||
| def text_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:string'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return datatype(el.text if el.text else '') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(bool) |  | ||||||
| def bool_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:boolean'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return el.text.lower() != 'false' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(datetime.date) |  | ||||||
| def date_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:date'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return parse_isodate(el.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(datetime.time) |  | ||||||
| def time_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:time'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return parse_isotime(el.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(datetime.datetime) |  | ||||||
| def datetime_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:dateTime'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return parse_isodatetime(el.text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @fromsoap.when_object(wsme.types.binary) |  | ||||||
| def binary_fromsoap(datatype, el, ns): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     if el.get(type_qn) not in (None, 'xs:base64Binary'): |  | ||||||
|         raise exc.InvalidInput(el.tag, ET.tostring(el)) |  | ||||||
|     return base64.decodestring(el.text.encode('ascii')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SoapProtocol(Protocol): |  | ||||||
|     """ |  | ||||||
|     SOAP protocol. |  | ||||||
|  |  | ||||||
|     .. autoattribute:: name |  | ||||||
|     .. autoattribute:: content_types |  | ||||||
|     """ |  | ||||||
|     name = 'soap' |  | ||||||
|     displayname = 'SOAP' |  | ||||||
|     content_types = ['application/soap+xml'] |  | ||||||
|  |  | ||||||
|     ns = { |  | ||||||
|         "soap": "http://www.w3.org/2001/12/soap-envelope", |  | ||||||
|         "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", |  | ||||||
|         "soapenc": "http://schemas.xmlsoap.org/soap/encoding/", |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     def __init__(self, tns=None, typenamespace=None, baseURL=None, |  | ||||||
|                  servicename='MyApp'): |  | ||||||
|         self.tns = tns |  | ||||||
|         self.typenamespace = typenamespace |  | ||||||
|         self.servicename = servicename |  | ||||||
|         self.baseURL = baseURL |  | ||||||
|         self._name_mapping = {} |  | ||||||
|  |  | ||||||
|         self.encoder = SoapEncoder(typenamespace) |  | ||||||
|  |  | ||||||
|     def get_name_mapping(self, service=None): |  | ||||||
|         if service not in self._name_mapping: |  | ||||||
|             self._name_mapping[service] = dict( |  | ||||||
|                 (soap_fname(path, f), path) |  | ||||||
|                 for path, f in self.root.getapi() |  | ||||||
|                 if service is None or (path and path[0] == service) |  | ||||||
|             ) |  | ||||||
|         return self._name_mapping[service] |  | ||||||
|  |  | ||||||
|     def accept(self, req): |  | ||||||
|         for ct in self.content_types: |  | ||||||
|             if req.headers['Content-Type'].startswith(ct): |  | ||||||
|                 return True |  | ||||||
|         if req.headers.get("Soapaction"): |  | ||||||
|             return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def iter_calls(self, request): |  | ||||||
|         yield CallContext(request) |  | ||||||
|  |  | ||||||
|     def extract_path(self, context): |  | ||||||
|         request = context.request |  | ||||||
|         el = ET.fromstring(request.body) |  | ||||||
|         body = el.find('{%(soapenv)s}Body' % self.ns) |  | ||||||
|         # Extract the service name from the tns |  | ||||||
|         message = list(body)[0] |  | ||||||
|         fname = message.tag |  | ||||||
|         if fname.startswith('{%s}' % self.typenamespace): |  | ||||||
|             fname = fname[len(self.typenamespace) + 2:] |  | ||||||
|             mapping = self.get_name_mapping() |  | ||||||
|             if fname not in mapping: |  | ||||||
|                 raise exc.UnknownFunction(fname) |  | ||||||
|             path = mapping[fname] |  | ||||||
|             context.soap_message = message |  | ||||||
|             return path |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     def read_arguments(self, context): |  | ||||||
|         kw = {} |  | ||||||
|         if not hasattr(context, 'soap_message'): |  | ||||||
|             return kw |  | ||||||
|         msg = context.soap_message |  | ||||||
|         for param in msg: |  | ||||||
|             # FIX for python2.6 (only for lxml) |  | ||||||
|             if use_lxml and isinstance(param, ET._Comment): |  | ||||||
|                 continue |  | ||||||
|             name = param.tag[len(self.typenamespace) + 2:] |  | ||||||
|             arg = context.funcdef.get_arg(name) |  | ||||||
|             value = fromsoap(arg.datatype, param, { |  | ||||||
|                 'type': self.typenamespace, |  | ||||||
|             }) |  | ||||||
|             kw[name] = value |  | ||||||
|         wsme.runtime.check_arguments(context.funcdef, (), kw) |  | ||||||
|         return kw |  | ||||||
|  |  | ||||||
|     def soap_response(self, path, funcdef, result): |  | ||||||
|         r = ET.Element('{%s}%sResponse' % ( |  | ||||||
|             self.typenamespace, soap_fname(path, funcdef) |  | ||||||
|         )) |  | ||||||
|         log.debug('(soap_response, %s, %s)', funcdef.return_type, result) |  | ||||||
|         r.append(self.encoder.tosoap( |  | ||||||
|             funcdef.return_type, '{%s}result' % self.typenamespace, result |  | ||||||
|         )) |  | ||||||
|         return r |  | ||||||
|  |  | ||||||
|     def encode_result(self, context, result): |  | ||||||
|         log.debug('(encode_result, %s)', result) |  | ||||||
|         if use_lxml: |  | ||||||
|             envelope = ET.Element( |  | ||||||
|                 Envelope_qn, |  | ||||||
|                 nsmap={'xs': xsd_ns, 'types': self.typenamespace} |  | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             envelope = ET.Element(Envelope_qn, { |  | ||||||
|                 'xmlns:xs': xsd_ns, |  | ||||||
|                 'xmlns:types': self.typenamespace |  | ||||||
|             }) |  | ||||||
|         body = ET.SubElement(envelope, Body_qn) |  | ||||||
|         body.append(self.soap_response(context.path, context.funcdef, result)) |  | ||||||
|         s = ET.tostring(envelope) |  | ||||||
|         return s |  | ||||||
|  |  | ||||||
|     def get_template(self, name): |  | ||||||
|         return pkg_resources.resource_string( |  | ||||||
|             __name__, '%s.html' % name) |  | ||||||
|  |  | ||||||
|     def encode_error(self, context, infos): |  | ||||||
|         envelope = ET.Element(Envelope_qn) |  | ||||||
|         body = ET.SubElement(envelope, Body_qn) |  | ||||||
|         fault = ET.SubElement(body, Fault_qn) |  | ||||||
|         ET.SubElement(fault, faultcode_qn).text = infos['faultcode'] |  | ||||||
|         ET.SubElement(fault, faultstring_qn).text = infos['faultstring'] |  | ||||||
|         if 'debuginfo' in infos: |  | ||||||
|             ET.SubElement(fault, detail_qn).text = infos['debuginfo'] |  | ||||||
|         s = ET.tostring(envelope) |  | ||||||
|         return s |  | ||||||
|  |  | ||||||
|     @expose('/api.wsdl', 'text/xml') |  | ||||||
|     def api_wsdl(self, service=None): |  | ||||||
|         if service is None: |  | ||||||
|             servicename = self.servicename |  | ||||||
|         else: |  | ||||||
|             servicename = self.servicename + service.capitalize() |  | ||||||
|         return WSDLGenerator( |  | ||||||
|             tns=self.tns, |  | ||||||
|             types_ns=self.typenamespace, |  | ||||||
|             soapenc=self.ns['soapenc'], |  | ||||||
|             service_name=servicename, |  | ||||||
|             complex_types=self.root.__registry__.complex_types, |  | ||||||
|             funclist=self.root.getapi(), |  | ||||||
|             arrays=self.root.__registry__.array_types, |  | ||||||
|             baseURL=self.baseURL, |  | ||||||
|             soap_array=soap_array, |  | ||||||
|             soap_type=soap_type, |  | ||||||
|             soap_fname=soap_fname, |  | ||||||
|         ).generate(True) |  | ||||||
|  |  | ||||||
|     def encode_sample_value(self, datatype, value, format=False): |  | ||||||
|         r = self.encoder.make_soap_element(datatype, 'value', value) |  | ||||||
|         if format: |  | ||||||
|             xml_indent(r) |  | ||||||
|         return ('xml', unicode(r)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def xml_indent(elem, level=0): |  | ||||||
|     i = "\n" + level * "  " |  | ||||||
|     if len(elem): |  | ||||||
|         if not elem.text or not elem.text.strip(): |  | ||||||
|             elem.text = i + "  " |  | ||||||
|         for e in elem: |  | ||||||
|             xml_indent(e, level + 1) |  | ||||||
|         if not e.tail or not e.tail.strip(): |  | ||||||
|             e.tail = i |  | ||||||
|     if level and (not elem.tail or not elem.tail.strip()): |  | ||||||
|         elem.tail = i |  | ||||||
| @@ -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()) |  | ||||||
| @@ -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) |  | ||||||
| @@ -1,591 +0,0 @@ | |||||||
| import inspect |  | ||||||
| import re |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| from sphinx import addnodes |  | ||||||
| from sphinx.ext import autodoc |  | ||||||
| from sphinx.domains.python import PyClasslike, PyClassmember |  | ||||||
| from sphinx.domains import Domain, ObjType |  | ||||||
| from sphinx.directives import ObjectDescription |  | ||||||
| from sphinx.util.docfields import Field |  | ||||||
| from sphinx.util.nodes import make_refnode |  | ||||||
|  |  | ||||||
| from sphinx.roles import XRefRole |  | ||||||
| from sphinx.locale import l_, _ |  | ||||||
|  |  | ||||||
| from docutils.parsers.rst import Directive |  | ||||||
| from docutils.parsers.rst import directives |  | ||||||
|  |  | ||||||
| import wsme |  | ||||||
| import wsme.types |  | ||||||
| import wsme.rest.json |  | ||||||
| import wsme.rest.xml |  | ||||||
|  |  | ||||||
| field_re = re.compile(r':(?P<field>\w+)(\s+(?P<name>\w+))?:') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def datatypename(datatype): |  | ||||||
|     if isinstance(datatype, wsme.types.UserType): |  | ||||||
|         return datatype.name |  | ||||||
|     if isinstance(datatype, wsme.types.DictType): |  | ||||||
|         return 'dict(%s: %s)' % (datatypename(datatype.key_type), |  | ||||||
|                                  datatypename(datatype.value_type)) |  | ||||||
|     if isinstance(datatype, wsme.types.ArrayType): |  | ||||||
|         return 'list(%s)' % datatypename(datatype.item_type) |  | ||||||
|     return datatype.__name__ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def make_sample_object(datatype): |  | ||||||
|     if datatype is wsme.types.bytes: |  | ||||||
|         return six.b('samplestring') |  | ||||||
|     if datatype is wsme.types.text: |  | ||||||
|         return u'sample unicode' |  | ||||||
|     if datatype is int: |  | ||||||
|         return 5 |  | ||||||
|     sample_obj = getattr(datatype, 'sample', datatype)() |  | ||||||
|     return sample_obj |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_protocols(names): |  | ||||||
|     names = list(names) |  | ||||||
|     protocols = [] |  | ||||||
|     if 'rest' in names: |  | ||||||
|         names.remove('rest') |  | ||||||
|         protocols.extend('restjson', 'restxml') |  | ||||||
|     if 'restjson' in names: |  | ||||||
|         names.remove('restjson') |  | ||||||
|         protocols.append(('Json', wsme.rest.json)) |  | ||||||
|     if 'restxml' in names: |  | ||||||
|         names.remove('restxml') |  | ||||||
|         protocols.append(('XML', wsme.rest.xml)) |  | ||||||
|     for name in names: |  | ||||||
|         p = wsme.protocol.getprotocol(name) |  | ||||||
|         protocols.append((p.displayname or p.name, p)) |  | ||||||
|     return protocols |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SampleType(object): |  | ||||||
|     """A Sample Type""" |  | ||||||
|  |  | ||||||
|     #: A Int |  | ||||||
|     aint = int |  | ||||||
|  |  | ||||||
|     def __init__(self, aint=None): |  | ||||||
|         if aint: |  | ||||||
|             self.aint = aint |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def sample(cls): |  | ||||||
|         return cls(10) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SampleService(wsme.WSRoot): |  | ||||||
|     @wsme.expose(SampleType) |  | ||||||
|     @wsme.validate(SampleType, int, str) |  | ||||||
|     def change_aint(data, aint, dummy='useless'): |  | ||||||
|         """ |  | ||||||
|         :param aint: The new value |  | ||||||
|  |  | ||||||
|         :return: The data object with its aint field value changed. |  | ||||||
|         """ |  | ||||||
|         data.aint = aint |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def getroot(env, force=False): |  | ||||||
|     root = env.temp_data.get('wsme:root') |  | ||||||
|     if not force and root: |  | ||||||
|         return root |  | ||||||
|     rootpath = env.temp_data.get('wsme:rootpath', env.app.config.wsme_root) |  | ||||||
|  |  | ||||||
|     if rootpath is None: |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     modname, classname = rootpath.rsplit('.', 1) |  | ||||||
|     __import__(modname) |  | ||||||
|     module = sys.modules[modname] |  | ||||||
|     root = getattr(module, classname) |  | ||||||
|     env.temp_data['wsme:root'] = root |  | ||||||
|     return root |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def scan_services(service, path=[]): |  | ||||||
|     has_functions = False |  | ||||||
|     for name in dir(service): |  | ||||||
|         if name.startswith('_'): |  | ||||||
|             continue |  | ||||||
|         a = getattr(service, name) |  | ||||||
|         if inspect.ismethod(a): |  | ||||||
|             if hasattr(a, '_wsme_definition'): |  | ||||||
|                 has_functions = True |  | ||||||
|         if inspect.isclass(a): |  | ||||||
|             continue |  | ||||||
|         if len(path) > wsme.rest.APIPATH_MAXLEN: |  | ||||||
|             raise ValueError("Path is too long: " + str(path)) |  | ||||||
|         for value in scan_services(a, path + [name]): |  | ||||||
|             yield value |  | ||||||
|     if has_functions: |  | ||||||
|         yield service, path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def find_service_path(env, service): |  | ||||||
|     root = getroot(env) |  | ||||||
|     if service == root: |  | ||||||
|         return [] |  | ||||||
|     for s, path in scan_services(root): |  | ||||||
|         if s == service: |  | ||||||
|             return path |  | ||||||
|     return None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TypeDirective(PyClasslike): |  | ||||||
|     def get_index_text(self, modname, name_cls): |  | ||||||
|         return _('%s (webservice type)') % name_cls[0] |  | ||||||
|  |  | ||||||
|     def add_target_and_index(self, name_cls, sig, signode): |  | ||||||
|         ret = super(TypeDirective, self).add_target_and_index( |  | ||||||
|             name_cls, sig, signode |  | ||||||
|         ) |  | ||||||
|         name = name_cls[0] |  | ||||||
|         types = self.env.domaindata['wsme']['types'] |  | ||||||
|         if name in types: |  | ||||||
|             self.state_machine.reporter.warning( |  | ||||||
|                 'duplicate type description of %s ' % name) |  | ||||||
|         types[name] = self.env.docname |  | ||||||
|         return ret |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AttributeDirective(PyClassmember): |  | ||||||
|     doc_field_types = [ |  | ||||||
|         Field('datatype', label=l_('Type'), has_arg=False, |  | ||||||
|               names=('type', 'datatype')) |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def check_samples_slot(value): |  | ||||||
|     """Validate the samples_slot option to the TypeDocumenter. |  | ||||||
|  |  | ||||||
|     Valid positions are 'before-docstring' and |  | ||||||
|     'after-docstring'. Using the explicit 'none' disables sample |  | ||||||
|     output. The default is after-docstring. |  | ||||||
|     """ |  | ||||||
|     if not value: |  | ||||||
|         return 'after-docstring' |  | ||||||
|     val = directives.choice( |  | ||||||
|         value, |  | ||||||
|         ('none',              # do not include |  | ||||||
|          'before-docstring',  # show samples then docstring |  | ||||||
|          'after-docstring',   # show docstring then samples |  | ||||||
|          )) |  | ||||||
|     return val |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TypeDocumenter(autodoc.ClassDocumenter): |  | ||||||
|     objtype = 'type' |  | ||||||
|     directivetype = 'type' |  | ||||||
|     domain = 'wsme' |  | ||||||
|  |  | ||||||
|     required_arguments = 1 |  | ||||||
|     default_samples_slot = 'after-docstring' |  | ||||||
|  |  | ||||||
|     option_spec = dict( |  | ||||||
|         autodoc.ClassDocumenter.option_spec, |  | ||||||
|         **{'protocols': lambda l: [v.strip() for v in l.split(',')], |  | ||||||
|            'samples-slot': check_samples_slot, |  | ||||||
|            }) |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def can_document_member(member, membername, isattr, parent): |  | ||||||
|         # we don't want to be automaticaly used |  | ||||||
|         # TODO check if the member is registered an an exposed type |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def format_name(self): |  | ||||||
|         return self.object.__name__ |  | ||||||
|  |  | ||||||
|     def format_signature(self): |  | ||||||
|         return u'' |  | ||||||
|  |  | ||||||
|     def add_directive_header(self, sig): |  | ||||||
|         super(TypeDocumenter, self).add_directive_header(sig) |  | ||||||
|         # remove the :module: option that was added by ClassDocumenter |  | ||||||
|         if ':module:' in self.directive.result[-1]: |  | ||||||
|             self.directive.result.pop() |  | ||||||
|  |  | ||||||
|     def import_object(self): |  | ||||||
|         if super(TypeDocumenter, self).import_object(): |  | ||||||
|             wsme.types.register_type(self.object) |  | ||||||
|             return True |  | ||||||
|         else: |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|     def add_content(self, more_content, no_docstring=False): |  | ||||||
|         # Check where to include the samples |  | ||||||
|         samples_slot = self.options.samples_slot or self.default_samples_slot |  | ||||||
|  |  | ||||||
|         def add_docstring(): |  | ||||||
|             super(TypeDocumenter, self).add_content( |  | ||||||
|                 more_content, no_docstring) |  | ||||||
|  |  | ||||||
|         def add_samples(): |  | ||||||
|             protocols = get_protocols( |  | ||||||
|                 self.options.protocols or self.env.app.config.wsme_protocols |  | ||||||
|             ) |  | ||||||
|             content = [] |  | ||||||
|             if protocols: |  | ||||||
|                 sample_obj = make_sample_object(self.object) |  | ||||||
|                 content.extend([ |  | ||||||
|                     l_(u'Data samples:'), |  | ||||||
|                     u'', |  | ||||||
|                     u'.. cssclass:: toggle', |  | ||||||
|                     u'' |  | ||||||
|                 ]) |  | ||||||
|                 for name, protocol in protocols: |  | ||||||
|                     language, sample = protocol.encode_sample_value( |  | ||||||
|                         self.object, sample_obj, format=True) |  | ||||||
|                     content.extend([ |  | ||||||
|                         name, |  | ||||||
|                         u'    .. code-block:: ' + language, |  | ||||||
|                         u'', |  | ||||||
|                     ]) |  | ||||||
|                     content.extend( |  | ||||||
|                         u' ' * 8 + line |  | ||||||
|                         for line in six.text_type(sample).split('\n')) |  | ||||||
|             for line in content: |  | ||||||
|                 self.add_line(line, u'<wsmeext.sphinxext') |  | ||||||
|  |  | ||||||
|             self.add_line(u'', '<wsmeext.sphinxext>') |  | ||||||
|  |  | ||||||
|         if samples_slot == 'after-docstring': |  | ||||||
|             add_docstring() |  | ||||||
|             add_samples() |  | ||||||
|         elif samples_slot == 'before-docstring': |  | ||||||
|             add_samples() |  | ||||||
|             add_docstring() |  | ||||||
|         else: |  | ||||||
|             add_docstring() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AttributeDocumenter(autodoc.AttributeDocumenter): |  | ||||||
|     datatype = None |  | ||||||
|     domain = 'wsme' |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def can_document_member(member, membername, isattr, parent): |  | ||||||
|         return isinstance(parent, TypeDocumenter) |  | ||||||
|  |  | ||||||
|     def import_object(self): |  | ||||||
|         success = super(AttributeDocumenter, self).import_object() |  | ||||||
|         if success: |  | ||||||
|             self.datatype = self.object.datatype |  | ||||||
|         return success |  | ||||||
|  |  | ||||||
|     def add_content(self, more_content, no_docstring=False): |  | ||||||
|         self.add_line( |  | ||||||
|             u':type: %s' % datatypename(self.datatype), |  | ||||||
|             '<wsmeext.sphinxext>' |  | ||||||
|         ) |  | ||||||
|         self.add_line(u'', '<wsmeext.sphinxext>') |  | ||||||
|         super(AttributeDocumenter, self).add_content( |  | ||||||
|             more_content, no_docstring) |  | ||||||
|  |  | ||||||
|     def add_directive_header(self, sig): |  | ||||||
|         super(AttributeDocumenter, self).add_directive_header(sig) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RootDirective(Directive): |  | ||||||
|     """ |  | ||||||
|     This directive is to tell what class is the Webservice root |  | ||||||
|     """ |  | ||||||
|     has_content = False |  | ||||||
|     required_arguments = 1 |  | ||||||
|     optional_arguments = 0 |  | ||||||
|     final_argument_whitespace = False |  | ||||||
|     option_spec = { |  | ||||||
|         'webpath': directives.unchanged |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     def run(self): |  | ||||||
|         env = self.state.document.settings.env |  | ||||||
|         rootpath = self.arguments[0].strip() |  | ||||||
|         env.temp_data['wsme:rootpath'] = rootpath |  | ||||||
|         if 'wsme:root' in env.temp_data: |  | ||||||
|             del env.temp_data['wsme:root'] |  | ||||||
|         if 'webpath' in self.options: |  | ||||||
|             env.temp_data['wsme:webpath'] = self.options['webpath'] |  | ||||||
|         return [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ServiceDirective(ObjectDescription): |  | ||||||
|     name = 'service' |  | ||||||
|  |  | ||||||
|     optional_arguments = 1 |  | ||||||
|  |  | ||||||
|     def handle_signature(self, sig, signode): |  | ||||||
|         path = sig.split('/') |  | ||||||
|  |  | ||||||
|         namespace = '/'.join(path[:-1]) |  | ||||||
|         if namespace and not namespace.endswith('/'): |  | ||||||
|             namespace += '/' |  | ||||||
|  |  | ||||||
|         servicename = path[-1] |  | ||||||
|  |  | ||||||
|         if not namespace and not servicename: |  | ||||||
|             servicename = '/' |  | ||||||
|  |  | ||||||
|         signode += addnodes.desc_annotation('service ', 'service ') |  | ||||||
|  |  | ||||||
|         if namespace: |  | ||||||
|             signode += addnodes.desc_addname(namespace, namespace) |  | ||||||
|  |  | ||||||
|         signode += addnodes.desc_name(servicename, servicename) |  | ||||||
|  |  | ||||||
|         return sig |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ServiceDocumenter(autodoc.ClassDocumenter): |  | ||||||
|     domain = 'wsme' |  | ||||||
|     objtype = 'service' |  | ||||||
|     directivetype = 'service' |  | ||||||
|  |  | ||||||
|     def add_directive_header(self, sig): |  | ||||||
|         super(ServiceDocumenter, self).add_directive_header(sig) |  | ||||||
|         # remove the :module: option that was added by ClassDocumenter |  | ||||||
|         if ':module:' in self.directive.result[-1]: |  | ||||||
|             self.directive.result.pop() |  | ||||||
|  |  | ||||||
|     def format_signature(self): |  | ||||||
|         return u'' |  | ||||||
|  |  | ||||||
|     def format_name(self): |  | ||||||
|         path = find_service_path(self.env, self.object) |  | ||||||
|         if path is None: |  | ||||||
|             return |  | ||||||
|         return '/' + '/'.join(path) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FunctionDirective(PyClassmember): |  | ||||||
|     name = 'function' |  | ||||||
|     objtype = 'function' |  | ||||||
|  |  | ||||||
|     def get_signature_prefix(self, sig): |  | ||||||
|         return 'function ' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def document_function(funcdef, docstrings=None, protocols=['restjson']): |  | ||||||
|     """A helper function to complete a function documentation with return and |  | ||||||
|     parameter types""" |  | ||||||
|     # If the function doesn't have a docstring, add an empty list |  | ||||||
|     # so the default behaviors below work correctly. |  | ||||||
|     if not docstrings: |  | ||||||
|         docstrings = [[]] |  | ||||||
|     found_params = set() |  | ||||||
|  |  | ||||||
|     for si, docstring in enumerate(docstrings): |  | ||||||
|         for i, line in enumerate(docstring): |  | ||||||
|             m = field_re.match(line) |  | ||||||
|             if m and m.group('field') == 'param': |  | ||||||
|                 found_params.add(m.group('name')) |  | ||||||
|  |  | ||||||
|     next_param_pos = (0, 0) |  | ||||||
|  |  | ||||||
|     for arg in funcdef.arguments: |  | ||||||
|         content = [ |  | ||||||
|             u':type  %s: :wsme:type:`%s`' % ( |  | ||||||
|                 arg.name, datatypename(arg.datatype)) |  | ||||||
|         ] |  | ||||||
|         if arg.name not in found_params: |  | ||||||
|             content.insert(0, u':param %s: ' % (arg.name)) |  | ||||||
|             pos = next_param_pos |  | ||||||
|         else: |  | ||||||
|             for si, docstring in enumerate(docstrings): |  | ||||||
|                 for i, line in enumerate(docstring): |  | ||||||
|                     m = field_re.match(line) |  | ||||||
|                     if m and m.group('field') == 'param' \ |  | ||||||
|                             and m.group('name') == arg.name: |  | ||||||
|                         pos = (si, i + 1) |  | ||||||
|                         break |  | ||||||
|         docstring = docstrings[pos[0]] |  | ||||||
|         docstring[pos[1]:pos[1]] = content |  | ||||||
|         next_param_pos = (pos[0], pos[1] + len(content)) |  | ||||||
|  |  | ||||||
|     if funcdef.return_type: |  | ||||||
|         content = [ |  | ||||||
|             u':rtype: %s' % datatypename(funcdef.return_type) |  | ||||||
|         ] |  | ||||||
|         pos = None |  | ||||||
|         for si, docstring in enumerate(docstrings): |  | ||||||
|             for i, line in enumerate(docstring): |  | ||||||
|                 m = field_re.match(line) |  | ||||||
|                 if m and m.group('field') == 'return': |  | ||||||
|                     pos = (si, i + 1) |  | ||||||
|                     break |  | ||||||
|         else: |  | ||||||
|             pos = next_param_pos |  | ||||||
|         docstring = docstrings[pos[0]] |  | ||||||
|         docstring[pos[1]:pos[1]] = content |  | ||||||
|  |  | ||||||
|     codesamples = [] |  | ||||||
|  |  | ||||||
|     if protocols: |  | ||||||
|         params = [] |  | ||||||
|         for arg in funcdef.arguments: |  | ||||||
|             params.append(( |  | ||||||
|                 arg.name, |  | ||||||
|                 arg.datatype, |  | ||||||
|                 make_sample_object(arg.datatype) |  | ||||||
|             )) |  | ||||||
|         codesamples.extend([ |  | ||||||
|             u':%s:' % l_(u'Parameters samples'), |  | ||||||
|             u'    .. cssclass:: toggle', |  | ||||||
|             u'' |  | ||||||
|         ]) |  | ||||||
|         for name, protocol in protocols: |  | ||||||
|             language, sample = protocol.encode_sample_params( |  | ||||||
|                 params, format=True) |  | ||||||
|             codesamples.extend([ |  | ||||||
|                 u' ' * 4 + name, |  | ||||||
|                 u'        .. code-block:: ' + language, |  | ||||||
|                 u'', |  | ||||||
|             ]) |  | ||||||
|             codesamples.extend(( |  | ||||||
|                 u' ' * 12 + line |  | ||||||
|                 for line in six.text_type(sample).split('\n') |  | ||||||
|             )) |  | ||||||
|  |  | ||||||
|         if funcdef.return_type: |  | ||||||
|             codesamples.extend([ |  | ||||||
|                 u':%s:' % l_(u'Return samples'), |  | ||||||
|                 u'    .. cssclass:: toggle', |  | ||||||
|                 u'' |  | ||||||
|             ]) |  | ||||||
|             sample_obj = make_sample_object(funcdef.return_type) |  | ||||||
|             for name, protocol in protocols: |  | ||||||
|                 language, sample = protocol.encode_sample_result( |  | ||||||
|                     funcdef.return_type, sample_obj, format=True) |  | ||||||
|                 codesamples.extend([ |  | ||||||
|                     u' ' * 4 + name, |  | ||||||
|                     u'        .. code-block:: ' + language, |  | ||||||
|                     u'', |  | ||||||
|                 ]) |  | ||||||
|                 codesamples.extend(( |  | ||||||
|                     u' ' * 12 + line |  | ||||||
|                     for line in six.text_type(sample).split('\n') |  | ||||||
|                 )) |  | ||||||
|  |  | ||||||
|     docstrings[0:0] = [codesamples] |  | ||||||
|     return docstrings |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FunctionDocumenter(autodoc.MethodDocumenter): |  | ||||||
|     domain = 'wsme' |  | ||||||
|     directivetype = 'function' |  | ||||||
|     objtype = 'function' |  | ||||||
|     priority = 1 |  | ||||||
|  |  | ||||||
|     option_spec = { |  | ||||||
|         'path': directives.unchanged, |  | ||||||
|         'method': directives.unchanged |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def can_document_member(member, membername, isattr, parent): |  | ||||||
|         return (isinstance(parent, ServiceDocumenter) and |  | ||||||
|                 wsme.api.iswsmefunction(member)) |  | ||||||
|  |  | ||||||
|     def import_object(self): |  | ||||||
|         ret = super(FunctionDocumenter, self).import_object() |  | ||||||
|         self.directivetype = 'function' |  | ||||||
|         self.wsme_fd = wsme.api.FunctionDefinition.get(self.object) |  | ||||||
|         self.retann = datatypename(self.wsme_fd.return_type) |  | ||||||
|         return ret |  | ||||||
|  |  | ||||||
|     def format_args(self): |  | ||||||
|         args = [arg.name for arg in self.wsme_fd.arguments] |  | ||||||
|         defaults = [ |  | ||||||
|             arg.default |  | ||||||
|             for arg in self.wsme_fd.arguments if not arg.mandatory |  | ||||||
|         ] |  | ||||||
|         return inspect.formatargspec(args, defaults=defaults) |  | ||||||
|  |  | ||||||
|     def get_doc(self, encoding=None): |  | ||||||
|         """Inject the type and param fields into the docstrings so that the |  | ||||||
|         user can add its own param fields to document the parameters""" |  | ||||||
|         docstrings = super(FunctionDocumenter, self).get_doc(encoding) |  | ||||||
|  |  | ||||||
|         protocols = get_protocols( |  | ||||||
|             self.options.protocols or self.env.app.config.wsme_protocols |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         return document_function( |  | ||||||
|             self.wsme_fd, docstrings, protocols |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def add_content(self, more_content, no_docstring=False): |  | ||||||
|         super(FunctionDocumenter, self).add_content(more_content, no_docstring) |  | ||||||
|  |  | ||||||
|     def format_name(self): |  | ||||||
|         return self.wsme_fd.name |  | ||||||
|  |  | ||||||
|     def add_directive_header(self, sig): |  | ||||||
|         super(FunctionDocumenter, self).add_directive_header(sig) |  | ||||||
|         # remove the :module: option that was added by ClassDocumenter |  | ||||||
|         if ':module:' in self.directive.result[-1]: |  | ||||||
|             self.directive.result.pop() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WSMEDomain(Domain): |  | ||||||
|     name = 'wsme' |  | ||||||
|     label = 'WSME' |  | ||||||
|  |  | ||||||
|     object_types = { |  | ||||||
|         'type': ObjType(l_('type'), 'type', 'obj'), |  | ||||||
|         'service': ObjType(l_('service'), 'service', 'obj') |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     directives = { |  | ||||||
|         'type': TypeDirective, |  | ||||||
|         'attribute':  AttributeDirective, |  | ||||||
|         'service': ServiceDirective, |  | ||||||
|         'root': RootDirective, |  | ||||||
|         'function': FunctionDirective, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     roles = { |  | ||||||
|         'type': XRefRole() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     initial_data = { |  | ||||||
|         'types': {},  # fullname -> docname |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     def clear_doc(self, docname): |  | ||||||
|         keys = list(self.data['types'].keys()) |  | ||||||
|         for key in keys: |  | ||||||
|             value = self.data['types'][key] |  | ||||||
|             if value == docname: |  | ||||||
|                 del self.data['types'][key] |  | ||||||
|  |  | ||||||
|     def resolve_xref(self, env, fromdocname, builder, |  | ||||||
|                      type, target, node, contnode): |  | ||||||
|         if target not in self.data['types']: |  | ||||||
|             return None |  | ||||||
|         todocname = self.data['types'][target] |  | ||||||
|         return make_refnode( |  | ||||||
|             builder, fromdocname, todocname, target, contnode, target) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup(app): |  | ||||||
|     app.add_domain(WSMEDomain) |  | ||||||
|     app.add_autodocumenter(TypeDocumenter) |  | ||||||
|     app.add_autodocumenter(AttributeDocumenter) |  | ||||||
|     app.add_autodocumenter(ServiceDocumenter) |  | ||||||
|     app.add_autodocumenter(FunctionDocumenter) |  | ||||||
|  |  | ||||||
|     app.add_config_value('wsme_root', None, 'env') |  | ||||||
|     app.add_config_value('wsme_webpath', '/', 'env') |  | ||||||
|     app.add_config_value('wsme_protocols', ['restjson', 'restxml'], 'env') |  | ||||||
|     app.add_javascript('toggle.js') |  | ||||||
|     app.add_stylesheet('toggle.css') |  | ||||||
| @@ -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,), {} |  | ||||||
| ) |  | ||||||
| @@ -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 |  | ||||||
| @@ -1,243 +0,0 @@ | |||||||
| import base64 |  | ||||||
| import datetime |  | ||||||
| import decimal |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import simplejson as json |  | ||||||
| except ImportError: |  | ||||||
|     import json  # noqa |  | ||||||
|  |  | ||||||
| import wsme.tests.protocol |  | ||||||
| from wsme.utils import parse_isodatetime, parse_isodate, parse_isotime |  | ||||||
| from wsme.types import isarray, isdict, isusertype |  | ||||||
|  |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| if six.PY3: |  | ||||||
|     from urllib.parse import urlencode |  | ||||||
| else: |  | ||||||
|     from urllib import urlencode  # noqa |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_arg(value): |  | ||||||
|     if isinstance(value, tuple): |  | ||||||
|         value, datatype = value |  | ||||||
|     else: |  | ||||||
|         datatype = type(value) |  | ||||||
|  |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         value = [encode_arg((item, datatype[0])) for item in value] |  | ||||||
|     elif isinstance(datatype, dict): |  | ||||||
|         key_type, value_type = list(datatype.items())[0] |  | ||||||
|         value = dict(( |  | ||||||
|             (encode_arg((key, key_type)), |  | ||||||
|                 encode_arg((value, value_type))) |  | ||||||
|             for key, value in value.items() |  | ||||||
|         )) |  | ||||||
|     elif datatype in (datetime.date, datetime.time, datetime.datetime): |  | ||||||
|         value = value.isoformat() |  | ||||||
|     elif datatype == wsme.types.binary: |  | ||||||
|         value = base64.encodestring(value).decode('ascii') |  | ||||||
|     elif datatype == wsme.types.bytes: |  | ||||||
|         value = value.decode('ascii') |  | ||||||
|     elif datatype == decimal.Decimal: |  | ||||||
|         value = str(value) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def decode_result(value, datatype): |  | ||||||
|     if value is None: |  | ||||||
|         return None |  | ||||||
|     if datatype == wsme.types.binary: |  | ||||||
|         value = base64.decodestring(value.encode('ascii')) |  | ||||||
|         return value |  | ||||||
|     if isusertype(datatype): |  | ||||||
|         datatype = datatype.basetype |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         value = [decode_result(item, datatype[0]) for item in value] |  | ||||||
|     elif isarray(datatype): |  | ||||||
|         value = [decode_result(item, datatype.item_type) for item in value] |  | ||||||
|     elif isinstance(datatype, dict): |  | ||||||
|         key_type, value_type = list(datatype.items())[0] |  | ||||||
|         value = dict(( |  | ||||||
|             (decode_result(key, key_type), |  | ||||||
|                 decode_result(value, value_type)) |  | ||||||
|             for key, value in value.items() |  | ||||||
|         )) |  | ||||||
|     elif isdict(datatype): |  | ||||||
|         key_type, value_type = datatype.key_type, datatype.value_type |  | ||||||
|         value = dict(( |  | ||||||
|             (decode_result(key, key_type), |  | ||||||
|                 decode_result(value, value_type)) |  | ||||||
|             for key, value in value.items() |  | ||||||
|         )) |  | ||||||
|     elif datatype == datetime.time: |  | ||||||
|         value = parse_isotime(value) |  | ||||||
|     elif datatype == datetime.date: |  | ||||||
|         value = parse_isodate(value) |  | ||||||
|     elif datatype == datetime.datetime: |  | ||||||
|         value = parse_isodatetime(value) |  | ||||||
|     elif hasattr(datatype, '_wsme_attributes'): |  | ||||||
|         for attr in datatype._wsme_attributes: |  | ||||||
|             if attr.key not in value: |  | ||||||
|                 continue |  | ||||||
|             value[attr.key] = decode_result(value[attr.key], attr.datatype) |  | ||||||
|     elif datatype == decimal.Decimal: |  | ||||||
|         value = decimal.Decimal(value) |  | ||||||
|     elif datatype == wsme.types.bytes: |  | ||||||
|         value = value.encode('ascii') |  | ||||||
|     elif datatype is not None and type(value) != datatype: |  | ||||||
|         value = datatype(value) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestExtDirectProtocol(wsme.tests.protocol.ProtocolTestCase): |  | ||||||
|     protocol = 'extdirect' |  | ||||||
|     protocol_options = { |  | ||||||
|         'namespace': 'MyNS.api', |  | ||||||
|         'nsfolder': 'app' |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     def call(self, fname, _rt=None, _no_result_decode=False, _accept=None, |  | ||||||
|              **kw): |  | ||||||
|         path = fname.split('/') |  | ||||||
|         try: |  | ||||||
|             func, funcdef, args = self.root._lookup_function(path) |  | ||||||
|             arguments = funcdef.arguments |  | ||||||
|         except: |  | ||||||
|             arguments = [] |  | ||||||
|         if len(path) == 1: |  | ||||||
|             ns, action, fname = '', '', path[0] |  | ||||||
|         elif len(path) == 2: |  | ||||||
|             ns, action, fname = '', path[0], path[1] |  | ||||||
|         else: |  | ||||||
|             ns, action, fname = '.'.join(path[:-2]), path[-2], path[-1] |  | ||||||
|         print(kw) |  | ||||||
|  |  | ||||||
|         args = [ |  | ||||||
|             dict( |  | ||||||
|                 (arg.name, encode_arg(kw[arg.name])) |  | ||||||
|                 for arg in arguments if arg.name in kw |  | ||||||
|             ) |  | ||||||
|         ] |  | ||||||
|         print("args =", args) |  | ||||||
|         data = json.dumps({ |  | ||||||
|             'type': 'rpc', |  | ||||||
|             'tid': 0, |  | ||||||
|             'action': action, |  | ||||||
|             'method': fname, |  | ||||||
|             'data': args, |  | ||||||
|         }) |  | ||||||
|         print(data) |  | ||||||
|         headers = {'Content-Type': 'application/json'} |  | ||||||
|         if _accept: |  | ||||||
|             headers['Accept'] = _accept |  | ||||||
|         res = self.app.post('/extdirect/router/%s' % ns, data, headers=headers, |  | ||||||
|                             expect_errors=True) |  | ||||||
|  |  | ||||||
|         print(res.body) |  | ||||||
|  |  | ||||||
|         if _no_result_decode: |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         data = json.loads(res.text) |  | ||||||
|         if data['type'] == 'rpc': |  | ||||||
|             r = data['result'] |  | ||||||
|             return decode_result(r, _rt) |  | ||||||
|         elif data['type'] == 'exception': |  | ||||||
|             faultcode, faultstring = data['message'].split(': ', 1) |  | ||||||
|             debuginfo = data.get('where') |  | ||||||
|             raise wsme.tests.protocol.CallException( |  | ||||||
|                 faultcode, faultstring, debuginfo) |  | ||||||
|  |  | ||||||
|     def test_api_alias(self): |  | ||||||
|         assert self.root._get_protocol('extdirect').api_alias == '/app/api.js' |  | ||||||
|  |  | ||||||
|     def test_get_api(self): |  | ||||||
|         res = self.app.get('/app/api.js') |  | ||||||
|         print(res.body) |  | ||||||
|         assert res.body |  | ||||||
|  |  | ||||||
|     def test_positional(self): |  | ||||||
|         self.root._get_protocol('extdirect').default_params_notation = \ |  | ||||||
|             'positional' |  | ||||||
|  |  | ||||||
|         data = json.dumps({ |  | ||||||
|             'type': 'rpc', |  | ||||||
|             'tid': 0, |  | ||||||
|             'action': 'misc', |  | ||||||
|             'method': 'multiply', |  | ||||||
|             'data': [2, 5], |  | ||||||
|         }) |  | ||||||
|         headers = {'Content-Type': 'application/json'} |  | ||||||
|         res = self.app.post('/extdirect/router', data, headers=headers) |  | ||||||
|  |  | ||||||
|         print(res.body) |  | ||||||
|  |  | ||||||
|         data = json.loads(res.text) |  | ||||||
|         assert data['type'] == 'rpc' |  | ||||||
|         r = data['result'] |  | ||||||
|         assert r == 10 |  | ||||||
|  |  | ||||||
|     def test_batchcall(self): |  | ||||||
|         data = json.dumps([{ |  | ||||||
|             'type': 'rpc', |  | ||||||
|             'tid': 1, |  | ||||||
|             'action': 'argtypes', |  | ||||||
|             'method': 'setdate', |  | ||||||
|             'data': [{'value': '2011-04-06'}], |  | ||||||
|         }, { |  | ||||||
|             'type': 'rpc', |  | ||||||
|             'tid': 2, |  | ||||||
|             'action': 'returntypes', |  | ||||||
|             'method': 'getbytes', |  | ||||||
|             'data': [] |  | ||||||
|         }]) |  | ||||||
|         print(data) |  | ||||||
|         headers = {'Content-Type': 'application/json'} |  | ||||||
|         res = self.app.post('/extdirect/router', data, headers=headers) |  | ||||||
|  |  | ||||||
|         print(res.body) |  | ||||||
|  |  | ||||||
|         rdata = json.loads(res.text) |  | ||||||
|  |  | ||||||
|         assert len(rdata) == 2 |  | ||||||
|  |  | ||||||
|         assert rdata[0]['tid'] == 1 |  | ||||||
|         assert rdata[0]['result'] == '2011-04-06' |  | ||||||
|         assert rdata[1]['tid'] == 2 |  | ||||||
|         assert rdata[1]['result'] == 'astring' |  | ||||||
|  |  | ||||||
|     def test_form_call(self): |  | ||||||
|         params = { |  | ||||||
|             'value[0].inner.aint': 54, |  | ||||||
|             'value[1].inner.aint': 55, |  | ||||||
|             'extType': 'rpc', |  | ||||||
|             'extTID': 1, |  | ||||||
|             'extAction': 'argtypes', |  | ||||||
|             'extMethod': 'setnestedarray', |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         body = urlencode(params) |  | ||||||
|         r = self.app.post( |  | ||||||
|             '/extdirect/router', |  | ||||||
|             body, |  | ||||||
|             headers={'Content-Type': 'application/x-www-form-urlencoded'} |  | ||||||
|         ) |  | ||||||
|         print(r) |  | ||||||
|  |  | ||||||
|         assert json.loads(r.text) == { |  | ||||||
|             "tid": "1", |  | ||||||
|             "action": "argtypes", |  | ||||||
|             "type": "rpc", |  | ||||||
|             "method": "setnestedarray", |  | ||||||
|             "result": [{ |  | ||||||
|                 "inner": { |  | ||||||
|                     "aint": 54 |  | ||||||
|                 } |  | ||||||
|             }, { |  | ||||||
|                 "inner": { |  | ||||||
|                     "aint": 55 |  | ||||||
|                 } |  | ||||||
|             }] |  | ||||||
|         } |  | ||||||
| @@ -1,423 +0,0 @@ | |||||||
| import decimal |  | ||||||
| import datetime |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| import six |  | ||||||
|  |  | ||||||
| import wsme.tests.protocol |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import xml.etree.ElementTree as et |  | ||||||
| except: |  | ||||||
|     import cElementTree as et  # noqa |  | ||||||
|  |  | ||||||
| import suds.cache |  | ||||||
| import suds.client |  | ||||||
| import suds.transport |  | ||||||
|  |  | ||||||
| import wsme.utils |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class XDecimal(suds.xsd.sxbuiltin.XBuiltin): |  | ||||||
|     def translate(self, value, topython=True): |  | ||||||
|         if topython: |  | ||||||
|             if isinstance(value, six.string_types) and len(value): |  | ||||||
|                 return decimal.Decimal(value) |  | ||||||
|         else: |  | ||||||
|             if isinstance(value, (decimal.Decimal, int, float)): |  | ||||||
|                 return str(value) |  | ||||||
|             return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| suds.xsd.sxbuiltin.Factory.tags['decimal'] = XDecimal |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WebtestSudsTransport(suds.transport.Transport): |  | ||||||
|     def __init__(self, app): |  | ||||||
|         suds.transport.Transport.__init__(self) |  | ||||||
|         self.app = app |  | ||||||
|  |  | ||||||
|     def open(self, request): |  | ||||||
|         res = self.app.get(request.url, headers=request.headers) |  | ||||||
|         return six.BytesIO(res.body) |  | ||||||
|  |  | ||||||
|     def send(self, request): |  | ||||||
|         res = self.app.post( |  | ||||||
|             request.url, |  | ||||||
|             request.message, |  | ||||||
|             headers=dict(( |  | ||||||
|                 (key, str(value)) for key, value in request.headers.items() |  | ||||||
|             )), |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         return suds.transport.Reply( |  | ||||||
|             res.status_int, |  | ||||||
|             dict(res.headers), |  | ||||||
|             res.body |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SudsCache(suds.cache.Cache): |  | ||||||
|     def __init__(self): |  | ||||||
|         self.d = {} |  | ||||||
|  |  | ||||||
|     def get(self, id): |  | ||||||
|         return self.d.get(id) |  | ||||||
|  |  | ||||||
|     def getf(self, id): |  | ||||||
|         b = self.get(id) |  | ||||||
|         if b is not None: |  | ||||||
|             return six.StringIO(self.get(id)) |  | ||||||
|  |  | ||||||
|     def put(self, id, bfr): |  | ||||||
|         self.d[id] = bfr |  | ||||||
|  |  | ||||||
|     def putf(self, id, fp): |  | ||||||
|         self.put(id, fp.read()) |  | ||||||
|  |  | ||||||
|     def purge(self, id): |  | ||||||
|         try: |  | ||||||
|             del self.d[id] |  | ||||||
|         except: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def clear(self, id): |  | ||||||
|         self.d = {} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sudscache = SudsCache() |  | ||||||
|  |  | ||||||
| tns = "http://foo.bar.baz/soap/" |  | ||||||
| typenamespace = "http://foo.bar.baz/types/" |  | ||||||
|  |  | ||||||
| soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/' |  | ||||||
| xsi_ns = 'http://www.w3.org/2001/XMLSchema-instance' |  | ||||||
| body_qn = '{%s}Body' % soapenv_ns |  | ||||||
| fault_qn = '{%s}Fault' % soapenv_ns |  | ||||||
| faultcode_qn = '{%s}faultcode' % soapenv_ns |  | ||||||
| faultstring_qn = '{%s}faultstring' % soapenv_ns |  | ||||||
| faultdetail_qn = '{%s}detail' % soapenv_ns |  | ||||||
| type_qn = '{%s}type' % xsi_ns |  | ||||||
| nil_qn = '{%s}nil' % xsi_ns |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def build_soap_message(method, params=""): |  | ||||||
|     message = """<?xml version="1.0"?> |  | ||||||
| <soap:Envelope |  | ||||||
| xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" |  | ||||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |  | ||||||
| soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> |  | ||||||
|  |  | ||||||
|   <soap:Body xmlns="%(typenamespace)s"> |  | ||||||
|     <%(method)s> |  | ||||||
|         %(params)s |  | ||||||
|     </%(method)s> |  | ||||||
|   </soap:Body> |  | ||||||
|  |  | ||||||
| </soap:Envelope> |  | ||||||
| """ % dict(method=method, params=params, typenamespace=typenamespace) |  | ||||||
|     return message |  | ||||||
|  |  | ||||||
|  |  | ||||||
| python_types = { |  | ||||||
|     int: ('xs:int', str), |  | ||||||
|     float: ('xs:float', str), |  | ||||||
|     bool: ('xs:boolean', str), |  | ||||||
|     wsme.types.bytes: ( |  | ||||||
|         'xs:string', |  | ||||||
|         lambda x: x.decode('ascii') if isinstance(x, wsme.types.bytes) else x |  | ||||||
|     ), |  | ||||||
|     wsme.types.text: ('xs:string', wsme.types.text), |  | ||||||
|     wsme.types.binary: ( |  | ||||||
|         'xs:base64Binary', |  | ||||||
|         lambda x: base64.encodestring(x).decode('ascii') |  | ||||||
|     ), |  | ||||||
|     decimal.Decimal: ('xs:decimal', str), |  | ||||||
|     datetime.date: ('xs:date', datetime.date.isoformat), |  | ||||||
|     datetime.time: ('xs:time', datetime.time.isoformat), |  | ||||||
|     datetime.datetime: ('xs:dateTime', datetime.datetime.isoformat), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| array_types = { |  | ||||||
|     wsme.types.bytes: "String_Array", |  | ||||||
|     wsme.types.text: "String_Array", |  | ||||||
|     int: "Int_Array", |  | ||||||
|     float: "Float_Array", |  | ||||||
|     bool: "Boolean_Array", |  | ||||||
|     datetime.datetime: "dateTime_Array" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if not six.PY3: |  | ||||||
|     array_types[long] = "Long_Array" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def tosoap(tag, value): |  | ||||||
|     el = et.Element(tag) |  | ||||||
|     if isinstance(value, tuple): |  | ||||||
|         value, datatype = value |  | ||||||
|     else: |  | ||||||
|         datatype = type(value) |  | ||||||
|     if value is None: |  | ||||||
|         el.set('xsi:nil', 'true') |  | ||||||
|         return el |  | ||||||
|     if datatype in python_types: |  | ||||||
|         stype, conv = python_types[datatype] |  | ||||||
|         el.text = conv(value) |  | ||||||
|         el.set('xsi:type', stype) |  | ||||||
|     el.text = str(value) |  | ||||||
|     return el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def tosuds(client, value): |  | ||||||
|     if value is None: |  | ||||||
|         return None |  | ||||||
|     if isinstance(value, tuple): |  | ||||||
|         value, datatype = value |  | ||||||
|     else: |  | ||||||
|         datatype = type(value) |  | ||||||
|     if value is None: |  | ||||||
|         return None |  | ||||||
|     if isinstance(datatype, list): |  | ||||||
|         if datatype[0] in array_types: |  | ||||||
|             tname = array_types[datatype[0]] |  | ||||||
|         else: |  | ||||||
|             tname = datatype[0].__name__ + '_Array' |  | ||||||
|         o = client.factory.create('types:' + tname) |  | ||||||
|         o.item = [tosuds(client, (item, datatype[0])) for item in value] |  | ||||||
|         return o |  | ||||||
|     elif datatype in python_types: |  | ||||||
|         return python_types[datatype][1](value) |  | ||||||
|     else: |  | ||||||
|         o = client.factory.create('types:' + datatype.__name__) |  | ||||||
|  |  | ||||||
|         for attr in datatype._wsme_attributes: |  | ||||||
|             if attr.name in value: |  | ||||||
|                 setattr( |  | ||||||
|                     o, attr.name, |  | ||||||
|                     tosuds(client, (value[attr.name], attr.datatype)) |  | ||||||
|                 ) |  | ||||||
|         return o |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def read_bool(value): |  | ||||||
|     return value == 'true' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| soap_types = { |  | ||||||
|     'xs:string': wsme.types.text, |  | ||||||
|     'xs:int': int, |  | ||||||
|     'xs:long': int if six.PY3 else long, |  | ||||||
|     'xs:float': float, |  | ||||||
|     'xs:decimal': decimal.Decimal, |  | ||||||
|     'xs:boolean': read_bool, |  | ||||||
|     'xs:date': wsme.utils.parse_isodate, |  | ||||||
|     'xs:time': wsme.utils.parse_isotime, |  | ||||||
|     'xs:dateTime': wsme.utils.parse_isodatetime, |  | ||||||
|     'xs:base64Binary': base64.decodestring, |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def fromsoap(el): |  | ||||||
|     if el.get(nil_qn) == 'true': |  | ||||||
|         return None |  | ||||||
|     t = el.get(type_qn) |  | ||||||
|     if t == 'xs:string': |  | ||||||
|         return wsme.types.text(el.text if el.text else '') |  | ||||||
|     if t in soap_types: |  | ||||||
|         return soap_types[t](el.text) |  | ||||||
|     elif t and t.endswith('_Array'): |  | ||||||
|         return [fromsoap(i) for i in el] |  | ||||||
|     else: |  | ||||||
|         d = {} |  | ||||||
|         for child in el: |  | ||||||
|             name = child.tag |  | ||||||
|             assert name.startswith('{%s}' % typenamespace), name |  | ||||||
|             name = name[len(typenamespace) + 2:] |  | ||||||
|             d[name] = fromsoap(child) |  | ||||||
|         return d |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def tobytes(value): |  | ||||||
|     if isinstance(value, wsme.types.text): |  | ||||||
|         value = value.encode() |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def tobin(value): |  | ||||||
|     value = base64.decodestring(value.encode()) |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fromsuds_types = { |  | ||||||
|     wsme.types.binary: tobin, |  | ||||||
|     wsme.types.bytes: tobytes, |  | ||||||
|     decimal.Decimal: decimal.Decimal, |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def fromsuds(dt, value): |  | ||||||
|     if value is None: |  | ||||||
|         return None |  | ||||||
|     if isinstance(dt, list): |  | ||||||
|         return [fromsuds(dt[0], item) for item in value.item] |  | ||||||
|     if wsme.types.isarray(dt): |  | ||||||
|         return [fromsuds(dt.item_type, item) for item in value.item] |  | ||||||
|     if wsme.types.isusertype(dt) and dt not in fromsuds_types: |  | ||||||
|         dt = dt.basetype |  | ||||||
|     if dt in fromsuds_types: |  | ||||||
|         print(dt, value) |  | ||||||
|         value = fromsuds_types[dt](value) |  | ||||||
|         print(value) |  | ||||||
|         return value |  | ||||||
|     if wsme.types.iscomplex(dt): |  | ||||||
|         d = {} |  | ||||||
|         for attrdef in dt._wsme_attributes: |  | ||||||
|             if not hasattr(value, attrdef.name): |  | ||||||
|                 continue |  | ||||||
|             d[attrdef.name] = fromsuds( |  | ||||||
|                 attrdef.datatype, getattr(value, attrdef.name) |  | ||||||
|             ) |  | ||||||
|         return d |  | ||||||
|     return value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestSOAP(wsme.tests.protocol.ProtocolTestCase): |  | ||||||
|     protocol = 'soap' |  | ||||||
|     protocol_options = dict(tns=tns, typenamespace=typenamespace) |  | ||||||
|     ws_path = '/' |  | ||||||
|     _sudsclient = None |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         wsme.tests.protocol.ProtocolTestCase.setUp(self) |  | ||||||
|  |  | ||||||
|     def test_simple_call(self): |  | ||||||
|         message = build_soap_message('touch') |  | ||||||
|         print(message) |  | ||||||
|         res = self.app.post( |  | ||||||
|             self.ws_path, |  | ||||||
|             message, |  | ||||||
|             headers={"Content-Type": "application/soap+xml; charset=utf-8"}, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         print(res.body) |  | ||||||
|         assert res.status.startswith('200') |  | ||||||
|  |  | ||||||
|     def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False, |  | ||||||
|              **kw): |  | ||||||
|  |  | ||||||
|         if _no_result_decode or _accept or self._testMethodName in ( |  | ||||||
|             'test_missing_argument', 'test_invalid_path', 'test_settext_empty', |  | ||||||
|             'test_settext_none' |  | ||||||
|         ): |  | ||||||
|             return self.raw_call(fpath, _rt, _accept, _no_result_decode, **kw) |  | ||||||
|  |  | ||||||
|         path = fpath.strip('/').split('/') |  | ||||||
|         methodname = ''.join([path[0]] + [i.capitalize() for i in path[1:]]) |  | ||||||
|  |  | ||||||
|         m = getattr(self.sudsclient.service, methodname) |  | ||||||
|         kw = dict(( |  | ||||||
|             (key, tosuds(self.sudsclient, value)) for key, value in kw.items() |  | ||||||
|         )) |  | ||||||
|         print(kw) |  | ||||||
|         try: |  | ||||||
|             return fromsuds(_rt, m(**kw)) |  | ||||||
|         except suds.WebFault as exc: |  | ||||||
|             raise wsme.tests.protocol.CallException( |  | ||||||
|                 exc.fault.faultcode, |  | ||||||
|                 exc.fault.faultstring, |  | ||||||
|                 getattr(exc.fault, 'detail', None) or None |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     def raw_call(self, fpath, _rt=None, _accept=None, _no_result_decode=False, |  | ||||||
|                  **kw): |  | ||||||
|         path = fpath.strip('/').split('/') |  | ||||||
|         methodname = ''.join([path[0]] + [i.capitalize() for i in path[1:]]) |  | ||||||
|         # get the actual definition so we can build the adequate request |  | ||||||
|         if kw: |  | ||||||
|             el = et.Element('parameters') |  | ||||||
|             for key, value in kw.items(): |  | ||||||
|                 el.append(tosoap(key, value)) |  | ||||||
|  |  | ||||||
|             params = six.b("\n").join(et.tostring(el) for el in el) |  | ||||||
|         else: |  | ||||||
|             params = "" |  | ||||||
|         methodname = ''.join([path[0]] + [i.capitalize() for i in path[1:]]) |  | ||||||
|         message = build_soap_message(methodname, params) |  | ||||||
|         print(message) |  | ||||||
|         headers = {"Content-Type": "application/soap+xml; charset=utf-8"} |  | ||||||
|         if _accept is not None: |  | ||||||
|             headers['Accept'] = _accept |  | ||||||
|         res = self.app.post( |  | ||||||
|             self.ws_path, |  | ||||||
|             message, |  | ||||||
|             headers=headers, |  | ||||||
|             expect_errors=True |  | ||||||
|         ) |  | ||||||
|         print("Status: ", res.status, "Received:", res.body) |  | ||||||
|  |  | ||||||
|         if _no_result_decode: |  | ||||||
|             return res |  | ||||||
|  |  | ||||||
|         el = et.fromstring(res.body) |  | ||||||
|         body = el.find(body_qn) |  | ||||||
|         print(body) |  | ||||||
|  |  | ||||||
|         if res.status_int == 200: |  | ||||||
|             response_tag = '{%s}%sResponse' % (typenamespace, methodname) |  | ||||||
|             r = body.find(response_tag) |  | ||||||
|             result = r.find('{%s}result' % typenamespace) |  | ||||||
|             print("Result element: ", result) |  | ||||||
|             return fromsoap(result) |  | ||||||
|         elif res.status_int == 400: |  | ||||||
|             fault = body.find(fault_qn) |  | ||||||
|             raise wsme.tests.protocol.CallException( |  | ||||||
|                 fault.find(faultcode_qn).text, |  | ||||||
|                 fault.find(faultstring_qn).text, |  | ||||||
|                 "") |  | ||||||
|  |  | ||||||
|         elif res.status_int == 500: |  | ||||||
|             fault = body.find(fault_qn) |  | ||||||
|             raise wsme.tests.protocol.CallException( |  | ||||||
|                 fault.find(faultcode_qn).text, |  | ||||||
|                 fault.find(faultstring_qn).text, |  | ||||||
|                 fault.find(faultdetail_qn) is not None and |  | ||||||
|                 fault.find(faultdetail_qn).text or None) |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def sudsclient(self): |  | ||||||
|         if self._sudsclient is None: |  | ||||||
|             self._sudsclient = suds.client.Client( |  | ||||||
|                 self.ws_path + 'api.wsdl', |  | ||||||
|                 transport=WebtestSudsTransport(self.app), |  | ||||||
|                 cache=sudscache |  | ||||||
|             ) |  | ||||||
|         return self._sudsclient |  | ||||||
|  |  | ||||||
|     def test_wsdl(self): |  | ||||||
|         c = self.sudsclient |  | ||||||
|         assert c.wsdl.tns[1] == tns, c.wsdl.tns |  | ||||||
|  |  | ||||||
|         sd = c.sd[0] |  | ||||||
|  |  | ||||||
|         assert len(sd.ports) == 1 |  | ||||||
|         port, methods = sd.ports[0] |  | ||||||
|         self.assertEqual(len(methods), 51) |  | ||||||
|  |  | ||||||
|         methods = dict(methods) |  | ||||||
|  |  | ||||||
|         assert 'returntypesGettext' in methods |  | ||||||
|         print(methods) |  | ||||||
|  |  | ||||||
|         assert methods['argtypesSettime'][0][0] == 'value' |  | ||||||
|  |  | ||||||
|     def test_return_nesteddict(self): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def test_setnesteddict(self): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def test_return_objectdictattribute(self): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def test_setnested_nullobj(self): |  | ||||||
|         pass  # TODO write a soap adapted version of this test. |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Tony Breeds
					Tony Breeds