pax_global_header00006660000000000000000000000064131135306710014512gustar00rootroot0000000000000052 comment=fc34689395edc079bd242553d8d17e00c565b366 flask-restful-0.3.6/000077500000000000000000000000001311353067100143025ustar00rootroot00000000000000flask-restful-0.3.6/.coveragerc000066400000000000000000000000661311353067100164250ustar00rootroot00000000000000[run] branch = true omit = */venv/* */tests/* flask-restful-0.3.6/.gitignore000066400000000000000000000005731311353067100162770ustar00rootroot00000000000000*.py[cod] env # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 .cache # Installer logs pip-log.txt # Unit test / coverage reports .coverage htmlcov/ .tox nosetests.xml #Translations *.mo #Mr Developer .mr.developer.cfg cover #Docs docs/_build #Pycharm .idea #Eclipse/Aptana .project .pydevproject flask-restful-0.3.6/.travis.yml000066400000000000000000000003171311353067100164140ustar00rootroot00000000000000language: python python: - 3.5 - 3.4 - 3.3 - 2.7 - 2.6 install: pip install coveralls script: make ci after_success: coveralls notifications: email: on_success: never on_failure: change flask-restful-0.3.6/AUTHORS.md000066400000000000000000000037101311353067100157520ustar00rootroot00000000000000Authors ======= A huge thanks to all of our contributors: - Adam Chainz - Alec Nikolas Reiter - Alex Gaynor - Alex M - Alex Morken - Andrew Dunham - Andriy Yurchuk - Anil Kulkarni - Antonio Dourado - Antonio Herraiz - Ares Ou - Artur Rodrigues - Axel Haustant - Belousow Makc - Benjamin Dopplinger - Bennett, Bryan - Bohan Zhang - Bryan Bennett - Bulat Bochkariov - Cameron Brandon White - Catherine Devlin - Dan Quirk - Daniele Esposti - Dario Bertini - David Arnold - David Baumgold - David Boucha - David Crawford - Dimitris Theodorou - Doug Black - Evan Dale Aromin - Eyal Levin - Francesco Della Vedova - Frank Stratton - Garret Raziel - Gary Belvin - Gilles Dartiguelongue - Giorgio Salluzzo - Guillaume BINET - Heston Liebowitz - Hu WQ - Jacob Magnusson - James Booth - James Ogura - James Turk - Jeff Widman - Joakim Ekberg - Johannes - Jordan Yelloz - Josh Friend - Joshua C. Randall - Joshua Randall - José Fernández Ramos - Juan Rossi - JuneHyeon Bae - Kamil Gałuszka - Kevin Burke - Kevin Deldycke - Kevin Funk - Kyle Conroy - Lance Ingle - Lars Holm Nielsen - Luiz Armesto - Malthe Borch - Marek Hlobil - Matt Wright - Max Mautner - Max Peterson - Maxim - Michael Hwang - Michael Newman - Miguel Grinberg - Mihai Tomescu - Neil Halelamien - Nicolas Harraudeau - Pavel Tyslyatsky - Petrus J.v.Rensburg - Philippe Ndiaye - Piotr Husiatyński - Prasanna Swaminathan - Robert Warner - Rod Cloutier - Ryan Horn - Rémi Alvergnat - Sam Kimbrel - Samarth Shah - Sami Jaktholm - Sander Sink - Sasha Baranov - Saul Diez-Guerra - Sergey Romanov - Shreyans Sheth - Steven Leggett - Sven-Hendrik Haase - Usman Ehtesham Gul - Victor Neo - Vlad Frolov - Vladimir Pal - WooParadog - Yaniv Aknin - akash - bret barker - hachichaud - jbouzekri - jobou - johnrichter - justanr - k-funk - kelvinhammond - kenjones - kieran gorman - kumy - lyschoening - mailto1587 - mniebla - mozillazg - muchosalsa - nachinius - nixdata - papaeye - pingz - saml - siavashg - silasray - soasme - ueg1990 - y-p flask-restful-0.3.6/CHANGES.md000066400000000000000000000330221311353067100156740ustar00rootroot00000000000000Flask-RESTful Changelog ======================= Here you can see the full list of changes between each Flask-RESTful release. Version 0.3.6 ------------- Released May 31, 2017 - `Argument.help` now supports unicode strings ([#564](https://github.com/flask-restful/flask-restful/pull/564)) - Flags can now be passed to `inputs.regex` ([#621](https://github.com/flask-restful/flask-restful/pull/621)) - Fix behavior of `action='append'` in conjunction with `location='json'` ([#645](https://github.com/flask-restful/flask-restful/pull/645)) - `method_decorators` can be a `dict` to apply decorator behavior for only specific HTTP methods ([#532](https://github.com/flask-restful/flask-restful/pull/532)) - JSON keys are no longer sorted by default in debug mode in python3 ([#680](https://github.com/flask-restful/flask-restful/pull/680)) - Various small fixes and updates to documentation Version 0.3.5 ------------- Released December 9, 2015 - Add `nullable` option to request parser to allow/disallow null values for arguments ([#538](https://github.com/flask-restful/flask-restful/pull/538)) - Use Flask's exception log method in `handle_error(e)` method instead of directly logging the exception notice. ([#496](https://github.com/flask-restful/flask-restful/pull/496)) - `Argument.help` now allows more flexible message formatting using the `{error_msg}` string interpolation token. ([#518](https://github.com/flask-restful/flask-restful/pull/518)) - Prevent representation from being chosen at random when `Accept: */*` ([#524](https://github.com/flask-restful/flask-restful/pull/524)) - Headers from `HTTPException`s are now returned in the response instead of being discarded ([#523](https://github.com/flask-restful/flask-restful/pull/523)) - Marshalling now checks for a `__marshallable__` method first before defaulting back to `__getitem__` ([](https://github.com/flask-restful/flask-restful/issues/324)) - Flask 1.0 compatability fixes ([#506](https://github.com/flask-restful/flask-restful/pull/506)) Version 0.3.4 ------------- Released July 20, 2015 - Fixed issue where `abort()` and `raise Exception` were not equivalent ([#205](https://github.com/flask-restful/flask-restful/issues/205)) - Fixed `RequestParser` settings not being copied properly ([#483](https://github.com/flask-restful/flask-restful/pull/483)) - Add ability to configure json serializer settings from application config ([#458](https://github.com/flask-restful/flask-restful/pull/458)) - Project metadata, tests, and examples are now included in source distributions ([#475](https://github.com/flask-restful/flask-restful/issues/475)) - Various documentation improvements Version 0.3.3 ------------- Released May 22, 2015 - Disable [challenge on 401](https://github.com/flask-restful/flask-restful/commit/fe53f49bdc28dd83ee3acbeb0a313b411411e377) by default (**THIS IS A BREAKING CHANGE**, albeit a very small one with behavior that probably no one depended upon. You can easily change this back to the old way). - Doc fixes ([#404](https://github.com/flask-restful/flask-restful/pull/404), [#406](https://github.com/flask-restful/flask-restful/pull/406), [#436](https://github.com/flask-restful/flask-restful/pull/436), misc. other commits) - Fix truncation of microseconds in iso8601 datetime output ([#368](https://github.com/flask-restful/flask-restful/pull/405)) - `null` arguments from JSON no longer cast to string ([#390](https://github.com/flask-restful/flask-restful/pull/390)) - Made list fields work with classes ([#409](https://github.com/flask-restful/flask-restful/pull/409)) - Fix `url_for()` when used with Blueprints ([#410](https://github.com/flask-restful/flask-restful/pull/410)) - Add CORS "Access-Control-Expose-Headers" support ([#412](https://github.com/flask-restful/flask-restful/pull/412)) - Fix class references in RequestParser ([#414](https://github.com/flask-restful/flask-restful/pull/414)) - Allow any callables to be used as lazy attributes ([#417](https://github.com/flask-restful/flask-restful/pull/417)) - Fix references to `flask.ext.*` ([#420](https://github.com/flask-restful/flask-restful/issues/420)) - Trim support with fixes ([#428](https://github.com/flask-restful/flask-restful/pull/428)) - Added ability to pass-in parameters into Resource constructors ([#444](https://github.com/flask-restful/flask-restful/pull/444)) - Fix custom type docs on "Intermediate usage" and docstring ([#434](https://github.com/flask-restful/flask-restful/pull/434)) - Fixed problem with `RequestParser.copy` ([#435](https://github.com/flask-restful/flask-restful/pull/435)) - Feature/error bundling ([#431](https://github.com/flask-restful/flask-restful/pull/431)) - Explicitly check the class type for `propagate_exceptions` ([#445](https://github.com/flask-restful/flask-restful/pull/445)) - Remove min. year limit 1900 in `inputs.date` ([#446](https://github.com/flask-restful/flask-restful/pull/446)) Version 0.3.2 ------------- Released February 25, 2015 - Doc fixes ([#344](https://github.com/flask-restful/flask-restful/pull/344), [#378](https://github.com/flask-restful/flask-restful/issues/378), [#402](https://github.com/flask-restful/flask-restful/pull/402)) - Microseconds no longer truncated in ISO8601 format datetime inputs ([#381](https://github.com/flask-restful/flask-restful/pull/381)) - Datetime inputs now preserve timezone instead of forcing conversion to UTC ([#381](https://github.com/flask-restful/flask-restful/pull/381)) - Fixes content negotiation to respect q-values ([#245](https://github.com/flask-restful/flask-restful/pull/245)) - Fix `fields.URL` when used with Blueprints ([#379](https://github.com/flask-restful/flask-restful/pull/379)) - Fix `BadRequest` raised with empty body and `application/json` content type ([#366](https://github.com/flask-restful/flask-restful/pull/366)) - Improved argument validation error messages ([#386](https://github.com/flask-restful/flask-restful/pull/386)) - Allow custom validation for `FileStorage` type arguments ([#388](https://github.com/flask-restful/flask-restful/pull/388)) - Allow lambdas to be specified for field attributes ([#309](https://github.com/flask-restful/flask-restful/pull/309)) - Added regex input validator ([#374](https://github.com/flask-restful/flask-restful/pull/374)) Version 0.3.1 ------------- Released December 13, 2014 - Adds `strict` option to `parse_args()` ([#358](https://github.com/flask-restful/flask-restful/pull/358)) - Adds an option to envelop marshaled objects ([#349](https://github.com/flask-restful/flask-restful/pull/349)) - Fixes initialization of `Api.blueprint` attribute ([#263](https://github.com/flask-restful/flask-restful/pull/263)) - Makes `Api.error_router` fall back to Flask handlers ([#296](https://github.com/flask-restful/flask-restful/pull/296)/[#356](https://github.com/flask-restful/flask-restful/pull/356)) - Makes docs more viewable on mobile devices ([#347](https://github.com/flask-restful/flask-restful/issues/347)) - Wheel distribution is now universal ([#363](https://github.com/flask-restful/flask-restful/issues/363)) Version 0.3.0 -------------- Released November 22, 2014 - Adds `@api.resource` decorator ([#311](https://github.com/flask-restful/flask-restful/pull/311)) - Adds custom error handling ([#225](https://github.com/flask-restful/flask-restful/pull/225)) - Adds `RequestParser` inheritance ([#249](https://github.com/flask-restful/flask-restful/pull/249)) - Adds 1/0 as valid values for `inputs.boolean` ([#341](https://github.com/flask-restful/flask-restful/pull/341)) - Improved `datetime` serialization and deserialization ([#345](https://github.com/flask-restful/flask-restful/pull/345)) - `init_app` now follows Flask extension guidelines ([#130](https://github.com/flask-restful/flask-restful/pull/130)) - `types` module renamed to `inputs` ([#243](https://github.com/flask-restful/flask-restful/pull/243)) - Fixes `inputs.boolean` inability to parse values from JSON ([#314](https://github.com/flask-restful/flask-restful/pull/314)) - Fixes `RequestParser` inability to use arguments from multiple sources at once ([#261](https://github.com/flask-restful/flask-restful/pull/261)) - Fixes missing `Allow` header when HTTP 405 is returned ([#294](https://github.com/flask-restful/flask-restful/pull/294)) - Doc fixes and updates. Version 0.2.12 -------------- Released March 4, 2014 - Fixed a bug in error handling code. - Don't install tests by default. - Doc fixes and updates. Version 0.2.11 -------------- Released January 17, 2014 - Fixes the List field when marshalling a list of dictionaries. ([#165](https://github.com/twilio/flask-restful/issues/165)) - Adds Boolean and Price types to fields.\_\_all\_\_ ([#180](https://github.com/twilio/flask-restful/issues/180)) - Adds support for serializing a set object with a List field. ([#175](https://github.com/twilio/flask-restful/pull/175)) - Fixes support for using callables as reqparser type arguments ([#167](https://github.com/twilio/flask-restful/pull/167)) - Add configuration variable to control smart-errors behavior on 404 responses. ([#181](https://github.com/twilio/flask-restful/issues/181)) - Fixes bug preventing use of Flask redirects. ([#162](https://github.com/twilio/flask-restful/pull/162)) - Documentation fixes ([#173](https://github.com/twilio/flask-restful/pull/173)) - Fixes bug swallowing tracebacks in handle_error. ([#166](https://github.com/twilio/flask-restful/pull/166)) Version 0.2.10 -------------- Released December 17, 2013 - Removes twilio-specific type checks present in version 0.2.9. - Correctly bump version number in setup.py. Version 0.2.9 ------------- Released December 17, 2013. - Adds new `positive` and `iso8601interval` types. - Typo fix. - Updating the test infrastructure to use common Twilio conventions and testing styles. Version 0.2.8 ------------- Released November 22, 2013 - Add 'absolute' and 'scheme' to fields.Url Version 0.2.6 ------------- Released November 18, 2013 - blueprint support - CORS support - allow custom unauthorized response - when failing to marshal custom indexable objects, its attributes are checked - better error messages Version 0.2.5 ------------- Released Aug 6, 2013 - add callable location - allow field type Fixed to take an attribute argument - added url_for() wrapper as Api.url_for(resource) Version 0.2.4 ------------- Released Aug 5, 2013 - Python 3.3 support. - You can now marshal nested fields. - Small fixes in docs. Version 0.2.2 ------------- Released on May 5, 2013 - JSON will be pretty-printed if you're running your app in debug mode. - pycrypto is now an optional dependency. Version 0.2.1 ------------- Released on April 9, 2013 - Use the default Flask-RESTful error handler, instead of the default Flask error handler, to handle 405 Not Allowed errors on requests to Api endpoints. Version 0.2.0 ------------- Released on April 9, 2013 - Flask-RESTful will no longer clobber your app's error handler; it will only handle errors that occur while handling Flask-RESTful routes. The breaking change is that 404 errors will default to using the Flask text/html error handler. Override this behavior by passing `catch_all_404s=True` to the `Api` constructor. (via [@yaniv-aknin]( /yaniv-aknin )) - Arguments can now take `location` as a tuple, in case you want to specify that an argument could be passed in multiple places. (via [@mindflayer](/mindflayer)) - Fixes a problem where passing an empty post body to a resource that expected a json argument would throw a 500. - Creation of the `Api` and initialization of the Flask `app` are no longer bundled together. (via [@andrew-d](/andrew-d)) - `marshal_with` now works with responses that are tuples. (via [@noise](/noise)) - `types.url` will no longer throw a ascii decoding ValueError if you pass it Unicode characters Version 0.1.7 ------------- Released on March 24, 2013 The first released version of 0.1.6 contained a problem with the tar.gz uploaded to PyPI. 0.1.7 contains the same changes as 0.1.6 but ensures the version you download from PyPI does not contain problems (if for example, you cached the old, broken version of 0.1.6). Version 0.1.6 ------------- Released on February 27th, 2013 - Update the documentation with a fuller example (https://github.com/twilio/flask-restful/pull/37) - Update the test runner to use setuptools (https://github.com/twilio/flask-restful/pull/46) - Don't set exception data if we have no data to set (https://github.com/twilio/flask-restful/pull/49) - action='append' in the RequestParser always returns a list. (https://github.com/twilio/flask-restful/pull/41) Version 0.1.5 ------------- Released on Jan 9th, 2013 - Fix error handler for exceptions that do not have a message Version 0.1.4 ------------- Released on Jan 8th, 2013 - Crypto support for paging - Added paging helper for resources - Stricter arg parse - Flask view arguments are no longer implicitly parsed by RequestParser - Fixed incorrectly formatted err message Version 0.1.3 ------------- Released on Jan 8th, 2013 - Smart 404 error in case of slight mistakes in the URL - Scheme error message - Attribute/key accessible namespace for reqparse - Add Namespace with dual attribute/item access - Added the original requested URI in the error - Better message if user passes URL w/ no scheme - Allow chaining of add_argument calls - Fixed bug 21 : Endpoint name clash on different views - Fixed string formatting for python 2.6 - Fixed dictionary comprehensions for python 2.6 - Fixed r'' for python 2.6 Version 0.1.2 ------------- Released on Nov 19th, 2012 - Fixed a bug in fields.Fixed when formatting a value of 0 Version 0.1.1 ------------- Released on Nov 19th, 2012 - Added the Fixed field Version 0.1 ------------- First public release flask-restful-0.3.6/CONTRIBUTING.md000066400000000000000000000007361311353067100165410ustar00rootroot00000000000000Contributing to Flask-RESTful ============================= We welcome features and bug fixes to Flask-RESTful! Fork this project and issue [pull requests](https://github.com/flask-restful/flask-restful/compare). New code must come with a set of unit tests (that pass!) before the pull request is merged. Reporting Bugs -------------- Bugs can be filed as [Issues on github.](https://github.com/flask-restful/flask-restful/issues/new) Thanks from the Flask-RESTful team! flask-restful-0.3.6/LICENSE000066400000000000000000000027151311353067100153140ustar00rootroot00000000000000Copyright (c) 2013, Twilio, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the Twilio, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. flask-restful-0.3.6/MANIFEST.in000066400000000000000000000005301311353067100160360ustar00rootroot00000000000000include Makefile CHANGES LICENSE AUTHORS.md recursive-include tests * recursive-include examples * recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo recursive-exclude tests *.pyc recursive-exclude tests *.pyo recursive-exclude examples *.pyc recursive-exclude examples *.pyo prune docs/_build prune docs/_themesflask-restful-0.3.6/Makefile000066400000000000000000000112641311353067100157460ustar00rootroot00000000000000# Python settings ifndef TRAVIS ifndef PYTHON_MAJOR PYTHON_MAJOR := 2 endif ifndef PYTHON_MINOR PYTHON_MINOR := 7 endif ENV := env/py$(PYTHON_MAJOR)$(PYTHON_MINOR) else # Use the virtualenv provided by Travis ENV = $(VIRTUAL_ENV) endif # Project settings PROJECT := Flask-RESTful PACKAGE := flask_restful SOURCES := Makefile setup.py $(shell find $(PACKAGE) -name '*.py') EGG_INFO := $(subst -,_,$(PROJECT)).egg-info # System paths PLATFORM := $(shell python -c 'import sys; print(sys.platform)') ifneq ($(findstring win32, $(PLATFORM)), ) SYS_PYTHON_DIR := C:\\Python$(PYTHON_MAJOR)$(PYTHON_MINOR) SYS_PYTHON := $(SYS_PYTHON_DIR)\\python.exe SYS_VIRTUALENV := $(SYS_PYTHON_DIR)\\Scripts\\virtualenv.exe else SYS_PYTHON := python$(PYTHON_MAJOR) ifdef PYTHON_MINOR SYS_PYTHON := $(SYS_PYTHON).$(PYTHON_MINOR) endif SYS_VIRTUALENV := virtualenv endif # virtualenv paths ifneq ($(findstring win32, $(PLATFORM)), ) BIN := $(ENV)/Scripts OPEN := cmd /c start else BIN := $(ENV)/bin ifneq ($(findstring cygwin, $(PLATFORM)), ) OPEN := cygstart else OPEN := open endif endif # virtualenv executables PYTHON := $(BIN)/python PIP := $(BIN)/pip FLAKE8 := $(BIN)/flake8 PEP8RADIUS := $(BIN)/pep8radius PEP257 := $(BIN)/pep257 NOSE := $(BIN)/nosetests COVERAGE := $(BIN)/coverage ACTIVATE := $(BIN)/activate # Remove if you don't want pip to cache downloads PIP_CACHE_DIR := .cache PIP_CACHE := --download-cache $(PIP_CACHE_DIR) # Flags for PHONY targets DEPENDS_TEST := $(ENV)/.depends-test DEPENDS_DEV := $(ENV)/.depends-dev DEPENDS_DOC := $(ENV)/.depends-doc # Main Targets ############################################################### .PHONY: all all: test # Targets to run on Travis .PHONY: ci ci: test # Development Installation ################################################### .PHONY: env env: $(PIP) $(PIP): $(SYS_VIRTUALENV) --python $(SYS_PYTHON) $(ENV) $(PIP) install wheel .PHONY: depends depends: .depends-test .depends-dev .depends-doc .PHONY: .depends-test .depends-test: env Makefile $(DEPENDS_TEST) $(DEPENDS_TEST): Makefile tests/requirements.txt $(PIP) install -e .[paging] $(PIP) install -r tests/requirements.txt touch $(DEPENDS_TEST) # flag to indicate dependencies are installed .PHONY: .depends-dev .depends-dev: env Makefile $(DEPENDS_DEV) $(DEPENDS_DEV): Makefile $(PIP) install $(PIP_CACHE) flake8 pep257 pep8radius touch $(DEPENDS_DEV) # flag to indicate dependencies are installed .PHONY: .depends-doc .depends-doc: env Makefile setup.py $(DEPENDS_DOC) $(DEPENDS_DOC): Makefile setup.py $(PIP) install -e .[docs] touch $(DEPENDS_DOC) # flag to indicate dependencies are installed # Documentation ############################################################## .PHONY: doc doc: .depends-doc . $(ACTIVATE); cd docs; $(MAKE) html .PHONY: read read: doc $(OPEN) docs/_build/html/index.html # Static Analysis ############################################################ .PHONY: check check: flake8 pep257 PEP8_IGNORED := E501 .PHONY: flake8 flake8: .depends-dev $(FLAKE8) $(PACKAGE) tests --ignore=$(PEP8_IGNORED) .PHONY: pep257 pep257: .depends-dev $(PEP257) $(PACKAGE) .PHONY: fix fix: .depends-dev $(PEP8RADIUS) --docformatter --in-place # Testing #################################################################### .PHONY: test test: .depends-test .clean-test $(NOSE) tests --with-coverage --cover-package=$(PACKAGE) test-all: test-py26 test-py27 test-py33 test-py34 test-py35 test-py26: PYTHON_MAJOR=2 PYTHON_MINOR=6 $(MAKE) test test-py27: PYTHON_MAJOR=2 PYTHON_MINOR=7 $(MAKE) test test-py33: PYTHON_MAJOR=3 PYTHON_MINOR=3 $(MAKE) test test-py34: PYTHON_MAJOR=3 PYTHON_MINOR=4 $(MAKE) test test-py35: PYTHON_MAJOR=3 PYTHON_MINOR=5 $(MAKE) test .PHONY: htmlcov htmlcov: test $(COVERAGE) html $(OPEN) htmlcov/index.html # Cleanup #################################################################### .PHONY: clean clean: .clean-test find $(PACKAGE) -name '*.pyc' -delete find $(PACKAGE) -name '__pycache__' -delete rm -rf dist build rm -rf docs/_build rm -rf $(EGG_INFO) .PHONY: clean-all clean-all: clean rm -rf $(PIP_CACHE_DIR) rm -rf $(ENV) .PHONY: .clean-test .clean-test: rm -rf .coverage htmlcov # Release #################################################################### .PHONY: authors authors: echo "Authors\n=======\n\nA huge thanks to all of our contributors:\n\n" > AUTHORS.md git log --raw | grep "^Author: " | cut -d ' ' -f2- | cut -d '<' -f1 | sed 's/^/- /' | sort | uniq >> AUTHORS.md .PHONY: register register: doc $(PYTHON) setup.py register .PHONY: dist dist: doc test $(PYTHON) setup.py sdist $(PYTHON) setup.py bdist_wheel .PHONY: upload upload: doc register $(PYTHON) setup.py sdist upload $(PYTHON) setup.py bdist_wheel upload flask-restful-0.3.6/README.md000066400000000000000000000013051311353067100155600ustar00rootroot00000000000000# Flask-RESTful [![Build Status](https://travis-ci.org/flask-restful/flask-restful.svg?branch=master)](http://travis-ci.org/flask-restful/flask-restful) [![Coverage Status](http://img.shields.io/coveralls/flask-restful/flask-restful/master.svg)](https://coveralls.io/r/flask-restful/flask-restful) [![PyPI Version](http://img.shields.io/pypi/v/Flask-RESTful.svg)](https://pypi.python.org/pypi/Flask-RESTful) [![PyPI Downloads](http://img.shields.io/pypi/dm/Flask-RESTful.svg)](https://pypi.python.org/pypi/Flask-RESTful) Flask-RESTful provides the building blocks for creating a great REST API. ## User Guide You'll find the user guide and all documentation [here](https://flask-restful.readthedocs.io/) flask-restful-0.3.6/docs/000077500000000000000000000000001311353067100152325ustar00rootroot00000000000000flask-restful-0.3.6/docs/Makefile000066400000000000000000000127301311353067100166750ustar00rootroot00000000000000# 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) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @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." 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/Flask-RESTful.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-RESTful.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/Flask-RESTful" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-RESTful" @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." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 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." flask-restful-0.3.6/docs/_static/000077500000000000000000000000001311353067100166605ustar00rootroot00000000000000flask-restful-0.3.6/docs/_static/flask-restful-small.png000066400000000000000000000506611311353067100232660ustar00rootroot00000000000000PNG  IHDRq*$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYsiie2diTXtXML:com.adobe.xmp Adobe ImageReady z@IDATx܎eBm4D4{o5H&]* !Y%-i~5R]yoKd ,(UTi-\}ʒ yrՈ׆P@0mip a%d `h mMHk kBX 4a(8xp ؆}l'Kd ^gҘi |ցa(B8ө R"q9O@Y.a*,?V?iAԲl@ńzI%HYbZVѬ}ZH)d3Zzӓ#'\i7%"ςC[zgtg1,',V[t.d(6lѝG<50 gkj$ٚѮ@zEƥ,HeV#Lކ=LXd|~%OU",n-zUmEi!iܹL_Ԅy*L7_KyXwʼ5ؖ)V/K臮/Y;ƺ-;Ch}Ӧ\c p&>_c.,couV ɵFZ9+!)3MfYԩ|߀ZjVm˷K5WZ-q4gj%ciՅ=`kps"/ '܌$sqë́b^3?Ji]Ӿt  kÇhM!c 5PWHsMs"z#@fjM(,B0D Qx_V :(_(4@IR+>$aA+KZ{^,};iG/ !2,kqwhz~^^6:'oƠ8_ OZ04Ac}^#E_xh=UO]qH\KP^YMp }d}6`+;0kugPwz`h4bBV'NW2Ily y(zme}hoZWP`wǞ9 z5_NAiF.,q4VG:Vشu}w\ >j?<#0A !5Z%C9t['| Jz<=?Y=Yl n)'g7hfM4V *Xi#*'.Jsuleo H} MSS D[QEaԆ֠x1|ۡ2y4 ڷQnBx= b\8|{ o0B!ld+ӡ] gDḶ6W4vCj+yq٧ZЕ^o4ܿ'i67&IEcV+Ƴh>6Fl`oP4ЇTQPZ pu.Mi.A#fh(k:d/ ,vƛ6%V`q6hώ[𺨙hAh^r?AVؓ)tޝW!"л"@B/xv2ԟJDa+@hJ_ހ-7|˰H8]a0(yZC-Uc[2 70ľ}f[vPއ.0Y_")a}lXonN;z5ЈҠ4n644Gy+&W |U4]aeq sp/ ѐgגUe]&Pɼzs\7:RIiO @i}q9!8Se&/^hpȜиˎeSKjlX7QP+WG%a}hQ[6WoU 6_-! LTU#= ujl'í'Q@EDQ`8cυm0zt6Lm8ކRo%GC|PX.HXMTO$%)xGun3AO< 0\siw`H{r#گBAA)t64i+c't(L]ڶ-H^:Gq^, ,elnbEXs4ׄt&bԬ>s`m)iI~y1$K)2FA9Fóy ޽gs7qUjra\ 6`#z%.WzaN|7 ;^; !d,S'"y޻]Škx5Q!X_ xWX_v?&dTLփ#=/{䯦ofp/go, GC=pl ׻Š[=c;ԧ`/tvpHx?9},؇ - a9pѴ__nL\qa0 ~3<+)Zj,lV>Z;`kh^PqӕwW<zz@R/ o*cEhBŧKջ_?9hx c1L#tXr7ُнJ) c{kt ٹ0` 0T2B< iAzTgTʻ$)GBP hs$z}|/ŗu_J?D zF8׽@[멳 -YT(,Tts;1V ˱zPELT7%vos6rƫ n@Sҏ"hI}<g/JJg([dac!P_1ݯgt0`54 21/Hp&I|Y7sioOn]C=IKxY;MijkR?}x±2 ;p){+)Lic!o FHՀ&^W4&^#~O,]RW6$B-P,v=cMZ3Wf~0n~EkY|;k t+^'U;ppΪf^v~01OxH3O5a |+g۶MMm)[k=3ԂB6 jUYXJp)Xo:x OE5vxE;gs}_ԧ҉hš)@~ G _ba@{UGQf:R-Uw* 5%݉bm1G7ekty ߾b {<1hq9P0gx5f~s_U#У;J'p\߫DK8o_:~0pcI_ۇ9lݍ/ q~0g ~v&q`Bw$ |3 cq{ Wj;[]TqGZ'*2sQ&^sU&d^<3*#LI\E$c{`<؀| onG|Rϧl-U_8< ( /=h!2dܶ0s?g`}= 5RE GnM+!0D< Mлo^C.l桇> =t{a/"Կ٥80&:-8^:ր!|P*Vh7 AU|@^0 wK+ X+h끲tQ-2v*x8ZJ= CŲ5`XF'h<ѾqNBup*p,x@B]Ork#cvX%lבquЏk-MYg<:4(_I~^8u |E;:Уod/2>E "CMM/ S/08z)Ӷ*9bٍqp(tҠ8Ǣ7hL@Ï~Gn5pszAOT7S< 0(᦯~FY(Џ)K+&Y>CF=%ԅKlvuڸEw|.h1eG~Zn@F\Ќ9cݪ_JΤ q_iT1F翤>ú>>oM?3" I\.4cA+^ V@Cp1>p< lk@q.;uSw8 v ';nҚ{Bx#=^Lz/64m08F빁C~rky_X.{XrAsȾMKK/TG >>4[gyH<.F|\Ln A/$5H'a> -2nrÝzФii!_/&e3ġLS\Ӱ[{Lp<녞Rp xms,2<]39i¡0eyd&ư|wQ12>s1 c]X-ԇ%э uFPueAϩZ{zyNЋ 2.JpCׇO njjb y$>54x߂4,;@劣w\AMp+<@ e3'@W B cpn:eG+Q4oI1R;z pMvؔ k/hö4qDۀr h 5u7] 7_u=D@x,:XK Q}Ex)_eժK :ycɳz\޽gkEXz8F{\͵r-{Idu'œMOzOB7@Ͼ2N >ւ'>dcMs ^%Nw^4p_*9t/c ݏRb|AB4f t%۟mg,0Ci?æfm 5, G Fp  NM}db8H 鐍?FM'oˋv}8/Fz-HKݺ⡻#]cyT_}U=/ GQf~6fY8-i 3\4*׆XX=aиb>\nyD4jID:Qi`w dޕp9pan4Y^GnO&mIѯ=@Г 4B7dȏx1ƙ_^qֆnB-ܖ>!Jw5 =l'mO_l _0dwN ek?;p{fj7@}t*$T,~-TN] uR8pc4OE|ރvi5`г>>^p7` ʴ̉.0.\=HpO]i%p,a]_SY<=j8V`헨{7@L{Vx)qb 46QBٙ0>+F)3N9| #uֈQR@wme O۠۔w^+e:XuR00t9\9.ŻeGw'A\o }p74 q#Hya? ~Ћi4AT~DYiaSasazO8>cpFS%, :hTǵkԴ}f`Ի l!лWv"$N-<~$Tt[QЅ. ӻ> n!=wroVX6a[>.`z݅}W4g;]Ϡx2/~>/Qj(CrLMnoGi,-S)z+=}+ fsy&'t}c]ˑ0,>SG`fBo߆ށ=L,?ҡ|, 8QU`7G?Kq z]t\uOP2t0"!Ԃ;i\~8'A;V2BEC~ e⼆p6hxֱl|qN< Ǭhhk~)nwM A,?')O`}qkx Iw+wrq egzZiDž'+"C7F{`t@yɷh !Q,ͿI)|k¾?.6豏n{ٻv.?t.܍ZxL듭snhNTŚ.tžPaF iN1z觃S'"kIl Rķ%x|k4\kF9n] g kzPG}3x2~~X .<>7 ,ɇӞ#b说G*4/QP7+}G@p 1A.b!&{x>h"9c "*n<4L0&c އc=6c 4xh3 yL*A?8mhׇlnV+Ba;霼xpK46hi|np&wʛ#4(Fkn΄xvMI}x@h?j\m~jS|S[6 aՔ7|X'P=<dp%XeHkaꇥ>=LÝS1} NmmS5t'zI`^g)GhGۘ=ϩ|Ѐ}WWo =p1nOB^CqBehn0 {S%41{ =[(Dԟ;rξ92(ι\w'<_=i:i3ՉK 0H_ $$Y܃S3Bى3uJ`al 42U+hxPV?Bl~cnpXE'w~&j؝`i瘦?F9zalYf'"E|eǵ=S7؄蓅o^w")z̹JCgFcv_J }gTv x>0\Ɵw>B۱h|{@- p埡)譠?fy ! @'|M8G\+C2< yz^Q ZD=p>yz]aUh5^skz479zzdސu=m5Mc2=s\ܕ=h\+Wv:,@]\cS @yK Nl :!۶oWsv5}z7zΡ /̳,Y"zt!4@&.:<nפ{^1$2dJSк=;T(֥}؟^L\ ׅ8l| X&4 ^Ga04\vˁ宆AϢlGa?p }Ah۷Pa?d,HvpڀՍTЇEЧO#̄25-H0$d +$1hֻ>p$m|B ٱ^t!Qe ߀ :G0A+zoCVpL'Ûώ7b q?Yo4WپYf&Ig Q(O'Ž:z8c"!4 #!x4쇠k! GXhsN:墮.l,C4^2/ʼM ࡹ.#X}a M}%uhKnJ} ;W<(q|-SWгoR'v1jB,FcQIP/C m 5^.#)ң$_΀cS)$ g9nuR+k<7ڈGhWd礏 <n^A?_?=`%,'PNr00-p!=nPJݲ#-qº!nMq濳f@NTwQSYy!/{lHzjЅ1RWv^JC-x~C#*mFo]`\:Gu_!4*#]ox x_U\+ 3< /Úם}ݵoN,򧌷JH:=zEkKx0aqhQaJڋaXt&϶WyBnhcXB5?&Ԉ50>2ص6!=^ l'w;4 23 Pxlg/0 wC5q:(S&8tL(纘w*[uU'a4b"B== c5 *6pX (-`+h"> b,40;ւ@ô+`Kh ޹Op#v5rQШz"ԇi ڊ&^ѩܝ+.p : |tkxk uu롏Ew ys2/á("a_p\c~CLlqX5:PBgC=^º).vݨlSLY3-QVϥHizSWf%^oIeÅy<8KP xFBnt⦆ ׃ ĖdmotPC+ ;5\zU1fU@Up=zyn xAh9xa=|]SzXJ1dט) 5&Phݨ2#hbMGa55a\4fw3==nWǢhAuШwOK_jvCA F,G)X}]`A< 5*T2Wi2x0o=bx<P-e~Mhq`gY , ㍉bNbFA4au2K] T84A#ѐ,oѶ 8Fn mSOY~RNNqM=1E|rxj(5b0Xw$Թ h!hkuF X\7fVZViKqHyZ9[nI!D? ZCȟ[He .4u'n4 \6_7)|FP81^ ch0_5dpohXmǠOx8P x~'`&hՠ!iEK̥iG2W;l| BȜނh,fF<FhĴS[HB{ĭkWëOu9tċA pFkqooҧЃt6tH}ipwr gSZ  >{L7A+G)nuLˣ_=_ݼ#ekXwTn: >))zkPtӟ6<ȼyjh{0 :Ú`衝X\C[)Yr Nx,N B.q]ṶC?vK/ kC<6"C Sܟ9f,`7ȏ')tjD 5880s4~=أ#pVkԟ2^ac_7oA1O 3T[@hge%Oc.{xEڃ_dj6Gu0 ;ny-R~³I Pǐ|/$:B o(׃wIXB4\p<Ӄk`DJ`tF `[h#3?ho ˌ*k`\:EP2;K/,Ox?~:@<21BX ~ ٷ/)kw8*_W?dAg7(tC49И΃6iV㈋7sz4^Q6*M̓:i{Q6 Ax@'g;@pֹOLf= ,f&t'7^!{NX7LJk>G|H} l<v}J4Jqus>~qA|qb]1>62(EҠ :Ya\ڲ5 y5LmTOn)4ap&PlI_ZagM!n9 +%({ )muU@cX'/%7ùA  v,?/xC=sh :`!6텇e4XSf$zw^ό%ʇH]}"k<L[Pg9O7x؏>OOs!}Оyٟt5Z g9܈ڰ*˒ns-E|ѻhzRk'[|7 Oz:FA.,K9>fݘXIkE)o9y3Pn"Gt7BG^wz 5[4C= p߂#p?syZkE2J{%ykV vAɬHS]?poNjUxvN%~%tmɷk$r/<BЁl>zM+z2=u|:ʼnŦfȌ[P+p2 Ǡ|?87рE D/ApC0$u =w07ݍtNn*à|OðgϠ+p7s?4}L4|.Ӳ3yqN$tCWG EAEkM9C9dw--*,<J8\'rͫRʴ8l_q WC߆QA)4S~55 «sYԚH*✵Na]Sp; =`$Bc4ʹ9Ƭhs;L7U@#Wܪ0|lkX; +p^aJ*l肾~ ؎ sվxU"?Ҽ8eiАg !-b~ϋqxa 5Ҍ#kCDZ2d&BB?[hkqxr#Ͳm@c!i q$t͗CDx6 >~t(() 1tƢ>H7 3B  `8jF nXl x K~ińrF\Ec,&ˀsS:u 㧃k V Iv%4Mqޙit :`jE1zYҳیrAyx40ʢ{HaOe0:M *r]0AJD#)=ToPxT E1w{\j^l# vx bΠ5Z7' hǢZo7<r' I7oCvm>-.| mSiݜß9@ ϥ3 碣)8\={]Ou #)BsL׹gK'WW+ɔn@ bcB*{R&Q;6GpIe@GSa d<hxv aX&6E߈H3MM4sh'_wnzB1.я^OsJ߀3gjUB7D³eFܻv^Z}(@bSRBqca3i 3M7n dwDMtDo߂{$)5H )p&W@ ǢhdS sY.w(mpxp՘p om4|OOzo-h<7Ŀ(Qnqgkz߂+a TL{0$z˲m ՠn*5_` NAo}G]w77g0$?9yJڲ]~s.qO?P=߆, }9(1VvohEqp|g&{el\ Hbl-o! s Nc}iA#U/|DZB4X>qv& 8P+{Fw<Yxm`}mqPpq~ F/`\O^OJ0N?"*5X6$ǽy 4S$aSx, @5axˁ؎bFp  Ш sowMJ7$T%jtM`c+hXUB.e.`F!aaY=w/(}92 au^ 3Oz98v(^xw]\Iw,5uUwz? ][MHs/&o ^($(8ւ=ayP '8@kf.jyN"JTQm[/??qlAY8@CP-tX*ٕ`M B46q38M1t{`m\ 1/1&Vl׾?7 g~)o q`w8c*{zǸR~No;O>8m )C_>G_**ZwœCe}2nN.Ti xzzkOՏ)OAo>¼Gd#G#@SLHP)nK8W%uݡƓA;\AubB$Mw<ְdxKtt+mo~(z;bB^CڀSLvQ6AϮHsnyS׋coqj.`x2oR5z?ѠԆe FmN[ˢ+qτ<'(һhi84A;::7X䏹"M\\>\մBߎ<*ʨ'gBze>e BZ"-p8*Ԅ^pS NAc4mY#x`Ģ['Gh,Nnv Ds7|{!pzCF z@ A{(##8rw` 'Pf/\9eaI4)8Ci8TY9^| HxGByt %珖 9atvQBrR e0|[^ ?e4nY-9NµVq`WsOOB/+zᰒNQ\lsy!! :v¢29Lׁ:I'iqu{4@3?> kS,y-SOL(]hLi ңw^ư(IkSp ޝt| )-ʺOځ{q?lG}IفY`߀6du5Q X; WNiJYiK\%)xp.JwԱW3IENDB`flask-restful-0.3.6/docs/_static/flask-restful.png000066400000000000000000000427111311353067100221550ustar00rootroot00000000000000PNG  IHDRxA tEXtSoftwareAdobe ImageReadyqe<$iTXtXML:com.adobe.xmp  oB;IDATx] |! HCP DDD͂h@BB*VB"Z_@Q WQy[PA@yC.{3&yMvgܙ{nBQQ@ QGN@ !@  @  @@ !@  @  @@ !@  @  @@ !@ j rkI$ZZκ5K6CtUTTBN@ r +AݮL:v2DlgvnKuv Ouv@ FB{| .RH U_떦[Gݶ6E6Cuaqn;mn;.$!U͋i A{݆6Hہ?!@ &)Ս(uC޺('D)(> HCbIZD@q)'^n3G`C@ mI^GWݎak4WcIZT)zChrvn?Q(Wv[7$>m}o@ wr-ʙoznp Ɂt;D*GS 8?IR0^nc}x붻B(@ ca,m+=MUNz[*xl '7vrj 7UN}$xGϕH_]U24b4p AA%fnku+>x?Ec2JW0wꖮi |¬@ Prr$|O&!@xanһFn14nRK.@#ƚpA Aڐh,wʉc"ۥpHWv7 $eVT$^},"$ @ Q ٿrH?(ݺ; H[:ۍ)Gi/vrWF2A@ 'ПA@9~(_,xns𮊼BID~u a m"CI0nӕ@^@PQŕ>TB~n*ba]i >(gc@( ]@WS 3&6;RN]F) 5ujб`"bufW[dar H0rA~G0I9UXakSliL&T*/9Z/n eW~@ Ԅ,cY9I_ӳ7/|xL9SˇI(gjb_s F$' $?*'̀ojk;+'@  )8*V{DF4M4˿ HL@d|MRş%%aQ*Jh@"H.A@ A(u0upUn4iAg&q, >~甓\hO떣 Y"C9ړ*<{$P76lEcr}8T%1{$''OM[t?~v u>?? 㔣GzxOk;Ii^׊ 4U  +,B_% b`mC@H+gM74fvB#VJzB|Q*9L|\/^U&B!&`nD Ir.#{h LǛ MLƭ `eSNm=;Ǫ =8z=S=-5kCX/^ojffH>de˖v ^9W>FC2x9Q^+Ys=U 2;dz.B"гCo%XuZ#Dp)C!D™)4c: v恡fXgI>p !Q@ҐDb3 8תNr,!(J/XիWkזOM-fM J%#H6-[fΜ@"-2nj8k,U#9Oh (i>K Ir#hwy%GirU?1B< ^ϣr[ mAZ~#di1{K+}=s$C>*$)~rB'Vf|R+I5n8?{drH$#)+)܊z=kd+'fRn 0o/V=\URHm,A kk8.MY4dȐׯ_y*1%6N8iqsl=Nabky`ŊWl߾=؇mØ]o}z\x8Wӗ/_LcLKK)*_zj=w*339/\I|N08|z@D*r z~gJ֍%AQ:;J*bkKXg??rF@w#@0=odP곱ua#+*Ls _k$;4P[ |>6QՀz9IA]*TY+gF@ҪAnlr _MB/!ǃd(VqP7iɒ%aкvFYІN&nRǏW^NC,Hɓ'tn- aٳgCӧ Èj07pÀvu+WP~*=lAp-BsIR6i̘1 zbDG.h\u|W 5Ib\yvRϏM|" c,P?8<* ;.k_(b*⦻H]d4!E^`ߦ.E7 \}^DqC99x>QdТBxAe?uD=R',c]vUz12t+ho4{E:rP΅60H{EyAzJѭpԩEi#Sp;]hQp^xL s=%9RǐmVFdoM.}/%%%>G?Es-dGu<%M>L0HĬH fszҎkAk SÛO#=nz9'[kxݺ 5=n{ %P_Ͼ`I1|d3e!iN6 u/C X^Y$BQAy QGi)|$*dBy! (2Y w,[SEkY'G 6`bZD޽nÇW]6<ľ2i}K.-!X5<'!|"_5gΜT$Rk_h#?åP pРAFx\&d.%Ca5 p-wHFg'.ʭH`?-B_ר,kl"Bf5$x/Srxi׹ݨjl)o4FS|#n|Cj$ ၆T0!9H|eiQWHAP1-KnZBx$# } w"СC?πwnȍHw V}0ϴW_}un#*WuBGiiiz_u\0!!!k:t0%ÑdA1B_zx9ǑPORxc~q}Jq&GQ#PZ"@47jSihk]T8q/BwsW1bAJrc{DZݥ㾟ǁsy NU *!y\j8yencm_f HɁmc}wO|)9@v;fw\~ґ$Q*r;DXAhHowOU!YD$*b fDay*͉L ?B! %SPKS7 vHXysy Я %1F.r<76HDcz>Uc T@N$0I@,%u$ wյG*=%em~WBX1^Gt}*$)閉N-jq"(cpk[lyonnnTDIh}dлFz I C-0TuF\AB TPnj T十5H/+ ?Tޝ!34vORϯADo#X.h'SNdB +^8FEǿuwq&ʙ.>G64@Cߊb){i(/ $izU$I /Y\^SE%V~?/pYc?Q@~T(b  6G935&vϱHA[gIN3S+ig*ZfIz)T.` dv$)*~eX Bò,>B#FVt*Z š "B`HU3{?\YpmE6S̽#+XI6OV2Iq=C4sǓv^-$4GPt}}h3U^w]x]~Q=sK# EP}e4fEqՌ׍4C ܀$> $xQ ^c> ‡|0q?JJ뛬1o`#r/p!r%[ӵ<fTi8D\p79bj:! rj<S}]~EZ*n'rR u*(fs]+B*E U+S*d+~ٳGu!M{Q3 ++ >o 4D$-# A9`>hX5) rl0[+B}Л{jMA@B>oSFb`'> ?C9Ay<f "и~E,۝^*,۬04*cQ*nk+n FO*"|ly1> iu^նa!)vq1`j* {rŢfQQBEj4*cU6Kw5qPJ惴TGcΰ `Z7dsEa@X!77jn HBÃYe󂩐X[\jzcJhso}N4k;E΄ а۲4hfTQI@,>ALd?n=@6.B~H.҈Կ<[ ,w!@q+(ϓ 4URAVwh|]AE7 [V0b{pAaFpvEF؏|yUv,'ph HV 4$lᆘ5E AP yߜ^nzӋV*b 6HЈB1h羀$s̎Fcٞo`0T^#A 1  L W$6s魯#YN H UsZ :<s*POC߀m!'.*n.O2 䗐\I7CG4|or HtD%UHoB# ҀIH4ⶶor|M}l;] 0c@Rcbԏ*TLʬp=z $5 C!nTo>}MJCwjȊBʌ4;TkV68Qnz ~wcY#)otENsUFC2t{9LQ90z MF&` Y6 eC<0ޏݗ n >C *> +ibHz\%I>I%HDT#П|=a?W@2Z@dW*r<0؞0Anw>!ŭ|𶈅g R/ Q`TAUf{(H ?r>Xs=rH\r-ʂ^̼4T:G5nIxrW `"ѣG͟?$%KpάJPQ/}'z/qLﻥE 4[@6?P0oh$5f.ҐHpBȩI|(8=FsPdSH@r-TLbj4h481X3׆ks.rcY,)[YDuFn޼y%|@ yPM&X!駟~B톧srr_4+8}$Rz葯["T@c8`"ŌptwhvtDN&9bIy:(Rŧ3"!tJ@D'؏;=8YN0<_W$9PSdxS2 7BUN$*.U^cVԅ9JLS-D MǵEUnQ6@ &TPPrVP\Hiyyyϧ`5@ GJNAE7Q8f"'=U9 |MC}GSJA[z'',ٶrRB!!T3*@a,Ŭ8ʏf1'8c_yE$58y# MÍB 9ZV3Vr? p Tj Q8,6AA)/a@xx zIM{L\pFCUR>a&538R *@Hb1"YB`[$Fmsht*Tyz_[)T|~v;zح$ SP?#5F$LrY?6%I&޾8  OB,kכ N_6m*(((44}bvGn cMV@BH+ l,nz͠?F|Ec9yA^o4j^Ry>B#5 |ٮ+|$:΢qEn !2TH7*4P$^|Jsy)UAŃ#?-Bsr2$mS cy?Ytd[ kݺ2K!$`Z… ;a\IJ2{wrܭq˴߬[ *DKSc{n3i$R \M6N81XőJAq6]h̙bP5 Crh2x˰EZۓg\]33gAUFfU!B҈x@-(K"40yaU rĸf0o(YbUh V&pUh}rUj{.U'EWŒ\vm#=0p@Łl7I3fB5Ap+(`ط~Ν;?dj:b,1hGF|*Uʵ/CHʲ( sp/"{Iҥ|ym/FG^DϏh֓iӦx\ 1$XdHy/l _k}ALc<P%@$`҇H NrTFA[ b$H$?CO 238HF6҈w'a7MI@ *T`! 1,HbIO0]$U%$#1do/R,VRL.ùHJc'qz?b#?G<_m~qmc u '_5a0BeABUNrgmYEbG3ד#p2rc,۱JAU4Ax Nz$WQ@NG4'iA58B >\Bk>`_8#W`3۟ 3;*6?H"9 5@WPs.{ϛ92EҲGӢEN4kG*fG"Q2gad~nMvIS}Ud~""o;.}5k4ի]~!##b(_t{%Kf/ C@?xm:uS5AЄ+AA˻~ʔ)7Κ5]>}p,T PH H bQr?VҐ!Cz__nF8.Ka1a*k; 87޽5xf V?+gh _-j)4>d"Pv'#IʛH5,= u /N5Tv8=$;8=x$%nFϯg߈^/PŧJ 8$ =I&F\ D&<d  R\hʯUb B$YЛ*ZY"k6d`tq Z?Qÿf@©TLd;($4̿4ސ^"9yd<* x. 7OX)r=4B_Hv~;PPy/J%7@V@7El'Xn.T1;e$1=  HXQ3*H <$G~; $'] mlLID> -}%T@z*S=MX .ƾ}9 Uʑ@  Čy-AERY=2,wR??"@ɊHI򐦜c-5>**Tà56$Jx~]$@x BylHL"Ԍ1C{.Иco8[*R?hUʙi|]ră$6A%Y>^:Շ3h0U(*tC4 ip/:pEz=uwݙd$K%>o1B{EaǟOqE\ $#H!3y^:r ;$ u /PƎxy. ?,po @Bj<4HkIޥDBOncv>.$ ef%Wޙ7#HgbN4|/ HDHh$Gs- ɑ ̔!٢BaglOT`Kbr9qsNߠ %"gmUn {r ҼUۻlG CQytFR*0xCi $bZ8H㾕 9$z2WֈߢCeDpؠj 40 9Hf4'HBIד s~~>w8Ja}b&'Mo }^$-`U_rϵT'Z?@#lAuTlV|A 'ېIϥЀư>|MEL94hCE08f:"N"qV41xcmEQ;*3[$(^F"Ê0tE'h`c <x4 UC 0,tU}%jBS@qKIrCeANG$/Go\~bf@ j0A!CzՈ˦ 1C46{H&#wkkzd?0ʩؘDTݭBU$")qH.eK1U$15.Q 01 1ӟILHG_èDPr@2PJ~bwFŝz0#|PbDUr͞$M[$@"0-s3{=Zj,)H g1]QQOTEB0Wr;D  XlJC&8  )j%= $ n,(flaFlH(*ԏdLz 1ݨ&4RC ]Mo_ǏZW*2BI/ױDe"cM5YDÍHy|֊<TեZaO?L/A$&N~(&$8!_]ۉl}ޔ,Lϖ^`;͟9$ TN?O,$а+$/$!Q1y$ wL@CiDH<ؐY4/(dOл @@Heнvw:j Ώ!8@Rp 6̦#@ !Ee1!ɈhC!T4h1KDF4Pv,l} ?G{4/,쵬>~$$( NkCc^A"|-T r$ PlwȃӔ|eBCL< SVM8ίI@ Ay)=$`slĹϦވ_T |?4h&}ߢ!C~@7zײߒ`$?!!B{cns5}+Lxi^: -(qorf4rl)A<l!~GߘEWؑC^@b", k*)(TTzZ}I.|5 Ar}e_uHfV,Om% r6w{Z,{ѧ$ K~w) }78ه $xvۛF F6 J>@NXR5*TRٍ6(CU؇F*%HpՅkE|7"^a9RiRPDrֱϦjWUEu&O"A|RP<Q"Q@@Pugy# `w*߁9i?@B O4/;ӫ+H0kh\gҀH#m*&8>:QP<.$;@d8OљwLU4f_Hnp('r3Ūr\+uHpަbbI[*C|&9@ !ҵaD1\oZՄS iT-zŪ?6kI>:pǸr[3TAVSlKb>$SI@X ~<E5e M, PG>#$j  t{f*('[/ A!/$>HcIH vf$!Kû^/VϠZ$ѻ.T Ec^*FD!l)Ũ*q4QFYpHDDB}3Pa:$?puy^q\$=)dHp@ A8mhϧ#w#hxfd7 yBߋvf$98njzAbout

Flask-RESTful provides an extension to Flask for building REST APIs. Flask-RESTful was initially developed as an internal project at Twilio, built to power their public and internal APIs.

Useful Links

flask-restful-0.3.6/docs/_templates/sidebarlogo.html000066400000000000000000000002661311353067100225530ustar00rootroot00000000000000 flask-restful-0.3.6/docs/_themes/000077500000000000000000000000001311353067100166565ustar00rootroot00000000000000flask-restful-0.3.6/docs/_themes/.gitignore000066400000000000000000000000261311353067100206440ustar00rootroot00000000000000*.pyc *.pyo .DS_Store flask-restful-0.3.6/docs/_themes/LICENSE000066400000000000000000000033751311353067100176730ustar00rootroot00000000000000Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. flask-restful-0.3.6/docs/_themes/README000066400000000000000000000021051311353067100175340ustar00rootroot00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge flask-restful-0.3.6/docs/_themes/flask/000077500000000000000000000000001311353067100177565ustar00rootroot00000000000000flask-restful-0.3.6/docs/_themes/flask/layout.html000066400000000000000000000012651311353067100221650ustar00rootroot00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {%- block footer %} {% if pagename == 'index' %}
{% endif %} {%- endblock %} flask-restful-0.3.6/docs/_themes/flask/relations.html000066400000000000000000000011161311353067100226430ustar00rootroot00000000000000

Related Topics

flask-restful-0.3.6/docs/_themes/flask/static/000077500000000000000000000000001311353067100212455ustar00rootroot00000000000000flask-restful-0.3.6/docs/_themes/flask/static/flasky.css_t000066400000000000000000000215761311353067100236060ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a, div.sphinxsidebar ul { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; } flask-restful-0.3.6/docs/_themes/flask/theme.conf000066400000000000000000000002611311353067100217260ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = 'sleepycat.png' index_logo_height = 143px touch_icon = flask-restful-0.3.6/docs/_themes/flask_small/000077500000000000000000000000001311353067100211465ustar00rootroot00000000000000flask-restful-0.3.6/docs/_themes/flask_small/layout.html000066400000000000000000000012531311353067100233520ustar00rootroot00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} flask-restful-0.3.6/docs/_themes/flask_small/static/000077500000000000000000000000001311353067100224355ustar00rootroot00000000000000flask-restful-0.3.6/docs/_themes/flask_small/static/flasky.css_t000066400000000000000000000161341311353067100247700ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a, div.sphinxsidebar ul { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; } flask-restful-0.3.6/docs/_themes/flask_small/theme.conf000066400000000000000000000002701311353067100231160ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px github_fork = '' flask-restful-0.3.6/docs/_themes/flask_theme_support.py000066400000000000000000000114131311353067100233060ustar00rootroot00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: #Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } flask-restful-0.3.6/docs/api.rst000066400000000000000000000011031311353067100165300ustar00rootroot00000000000000.. _api: API Docs ======== .. module:: flask_restful .. autofunction:: marshal .. autofunction:: marshal_with .. autofunction:: marshal_with_field .. autofunction:: abort Api --- .. autoclass:: Api :members: .. automethod:: unauthorized .. autoclass:: Resource :members: ReqParse -------- .. module:: reqparse .. autoclass:: RequestParser :members: .. autoclass:: Argument :members: .. automethod:: __init__ Fields ------ .. automodule:: fields :members: :undoc-members: Inputs ------ .. automodule:: inputs :members: :undoc-members: flask-restful-0.3.6/docs/conf.py000066400000000000000000000211651311353067100165360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Flask-RESTful documentation build configuration file, created by # sphinx-quickstart on Fri Sep 28 15:16:53 2012. # # 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. from datetime import date import os import sys # 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('.')) sys.path.append(os.path.abspath('_themes')) sys.path.append(os.path.abspath('../flask_restful')) # -- 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.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'Flask-RESTful' copyright = u'{}, Kevin Burke, Kyle Conroy, Ryan Horn, Frank Stratton, Guillaume Binet'.format( date.today().year) # 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. from flask_restful.__version__ import __version__ vparts = __version__.split('.') # The short X.Y version. version = '.'.join(vparts[:2]) # The full version, including alpha/beta/rc tags. release = '.'.join(vparts) # 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_path = ['_themes'] html_theme = 'flask' # 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 = { 'index_logo': 'flask-restful.png' } # Add any paths that contain custom themes here, relative to this directory. # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # 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 = { 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] } # 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 module index is generated. html_use_modindex = False # 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 = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Flask-RESTfuldoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Flask-RESTful.tex', u'Flask-RESTful Documentation', u'Kyle Conroy, Ryan Horn, Frank Stratton', '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 # 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', 'flask-restful', u'Flask-RESTful Documentation', [u'Kyle Conroy, Ryan Horn, Frank Stratton'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Flask-RESTful', u'Flask-RESTful Documentation', u'Kyle Conroy, Ryan Horn, Frank Stratton', 'Flask-RESTful', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' intersphinx_mapping = { 'flask': ('http://flask.pocoo.org/docs/', None), 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None), 'python': ('https://docs.python.org/2/', None), 'python3': ('https://docs.python.org/3/', None), 'six': ('http://pythonhosted.org/six/', None), } flask-restful-0.3.6/docs/contents.rst.inc000066400000000000000000000011651311353067100203740ustar00rootroot00000000000000User's Guide ------------ This part of the documentation will show you how to get started in using Flask-RESTful with Flask. .. toctree:: :maxdepth: 2 installation quickstart reqparse fields extending intermediate-usage API Reference ------------- If you are looking for information on a specific function, class or method, this part of the documentation is for you. .. toctree:: :maxdepth: 2 api Additional Notes ---------------- See Flask's license_ for legal information governing this project. .. toctree:: :maxdepth: 2 testing .. _license: http://flask.pocoo.org/docs/license/ flask-restful-0.3.6/docs/extending.rst000066400000000000000000000223731311353067100177600ustar00rootroot00000000000000.. _extending: Extending Flask-RESTful ======================= .. currentmodule:: flask_restful We realize that everyone has different needs in a REST framework. Flask-RESTful tries to be as flexible as possible, but sometimes you might find that the builtin functionality is not enough to meet your needs. Flask-RESTful has a few different extension points that can help in that case. Content Negotiation ------------------- Out of the box, Flask-RESTful is only configured to support JSON. We made this decision to give API maintainers full control of over API format support; so a year down the road you don’t have to support people using the CSV representation of your API you didn’t even know existed. To add additional mediatypes to your API, you’ll need to declare your supported representations on the :class:`~Api` object. :: app = Flask(__name__) api = Api(app) @api.representation('application/json') def output_json(data, code, headers=None): resp = make_response(json.dumps(data), code) resp.headers.extend(headers or {}) return resp These representation functions must return a Flask :class:`~flask.Response` object. .. Note :: Flask-RESTful uses the :mod:`json` module from the Python standard library instead of :mod:`flask.json` because the Flask JSON serializer includes serializtion capabilities which are not in the JSON spec. If your application needs these customizations, you can replace the default JSON representation with one using the Flask JSON module as described above. It is possible to configure how the default Flask-RESTful JSON representation will format JSON by providing a ``RESTFUL_JSON`` attribute on the application configuration. This setting is a dictionary with keys that correspond to the keyword arguments of :py:func:`json.dumps`. :: class MyConfig(object): RESTFUL_JSON = {'separators': (', ', ': '), 'indent': 2, 'cls': MyCustomEncoder} .. Note :: If the application is running in debug mode (``app.debug = True``) and either ``sort_keys`` or ``indent`` are not declared in the ``RESTFUL_JSON`` configuration setting, Flask-RESTful will provide defaults of ``True`` and ``4`` respectively. Custom Fields & Inputs ---------------------- One of the most common additions to Flask-RESTful is to define custom types or fields based on your own data types. Fields ~~~~~~ Custom output fields let you perform your own output formatting without having to modify your internal objects directly. All you have to do is subclass :class:`~fields.Raw` and implement the :meth:`~fields.Raw.format` method:: class AllCapsString(fields.Raw): def format(self, value): return value.upper() # example usage fields = { 'name': fields.String, 'all_caps_name': AllCapsString(attribute=name), } Inputs ~~~~~~ For parsing arguments, you might want to perform custom validation. Creating your own input types lets you extend request parsing with ease. :: def odd_number(value): if value % 2 == 0: raise ValueError("Value is not odd") return value The request parser will also give you access to the name of the argument for cases where you want to reference the name in the error message. :: def odd_number(value, name): if value % 2 == 0: raise ValueError("The parameter '{}' is not odd. You gave us the value: {}".format(name, value)) return value You can also convert public parameter values to internal representations: :: # maps the strings to their internal integer representation # 'init' => 0 # 'in-progress' => 1 # 'completed' => 2 def task_status(value): statuses = [u"init", u"in-progress", u"completed"] return statuses.index(value) Then you can use these custom input types in your :class:`~reqparse.RequestParser`: :: parser = reqparse.RequestParser() parser.add_argument('OddNumber', type=odd_number) parser.add_argument('Status', type=task_status) args = parser.parse_args() Response Formats ---------------- To support other representations (xml, csv, html), you can use the :meth:`~Api.representation` decorator. You need to have a reference to your API. :: api = Api(app) @api.representation('text/csv') def output_csv(data, code, headers=None): pass # implement csv output! These output functions take three parameters, ``data``, ``code``, and ``headers`` ``data`` is the object you return from your resource method, code is the HTTP status code that it expects, and headers are any HTTP headers to set in the response. Your output function should return a :class:`flask.Response` object. :: def output_json(data, code, headers=None): """Makes a Flask response with a JSON encoded body""" resp = make_response(json.dumps(data), code) resp.headers.extend(headers or {}) return resp Another way to accomplish this is to subclass the :class:`~Api` class and provide your own output functions. :: class Api(restful.Api): def __init__(self, *args, **kwargs): super(Api, self).__init__(*args, **kwargs) self.representations = { 'application/xml': output_xml, 'text/html': output_html, 'text/csv': output_csv, 'application/json': output_json, } Resource Method Decorators -------------------------- There is a property on the :class:`~flask_restful.Resource` class called ``method_decorators``. You can subclass the Resource and add your own decorators that will be added to all ``method`` functions in resource. For instance, if you want to build custom authentication into every request. :: def authenticate(func): @wraps(func) def wrapper(*args, **kwargs): if not getattr(func, 'authenticated', True): return func(*args, **kwargs) acct = basic_authentication() # custom account lookup function if acct: return func(*args, **kwargs) restful.abort(401) return wrapper class Resource(restful.Resource): method_decorators = [authenticate] # applies to all inherited resources Alternatively, you can specify a dictionary of iterables that map to HTTP methods and the decorators will only apply to matching requests. .. code-block:: python def cache(f): @wraps(f) def cacher(*args, **kwargs): # caching stuff return cacher class MyResource(restful.Resource): method_decorators = {'get': [cache]} def get(self, *args, **kwargs): return something_interesting(*args, **kwargs) def post(self, *args, **kwargs): return create_something(*args, **kwargs) In this case, the caching decorator would only apply to the `GET` request and not the `POST` request. Since Flask-RESTful Resources are actually Flask view objects, you can also use standard `flask view decorators `_. Custom Error Handlers --------------------- Error handling is a tricky problem. Your Flask application may be wearing multiple hats, yet you want to handle all Flask-RESTful errors with the correct content type and error syntax as your 200-level requests. Flask-RESTful will call the :meth:`~flask_restful.Api.handle_error` function on any 400 or 500 error that happens on a Flask-RESTful route, and leave other routes alone. You may want your app to return an error message with the correct media type on 404 Not Found errors; in which case, use the `catch_all_404s` parameter of the :class:`~flask_restful.Api` constructor. :: app = Flask(__name__) api = flask_restful.Api(app, catch_all_404s=True) Then Flask-RESTful will handle 404s in addition to errors on its own routes. Sometimes you want to do something special when an error occurs - log to a file, send an email, etc. Use the :meth:`~flask.got_request_exception` method to attach custom error handlers to an exception. :: def log_exception(sender, exception, **extra): """ Log an exception to our logging framework """ sender.logger.debug('Got exception during processing: %s', exception) from flask import got_request_exception got_request_exception.connect(log_exception, app) Define Custom Error Messages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You may want to return a specific message and/or status code when certain errors are encountered during a request. You can tell Flask-RESTful how you want to handle each error/exception so you won't have to fill your API code with try/except blocks. :: errors = { 'UserAlreadyExistsError': { 'message': "A user with that username already exists.", 'status': 409, }, 'ResourceDoesNotExist': { 'message': "A resource with that ID no longer exists.", 'status': 410, 'extra': "Any extra information you want.", }, } Including the `'status'` key will set the Response's status code. If not specified it will default to 500. Once your ``errors`` dictionary is defined, simply pass it to the :class:`~flask_restful.Api` constructor. :: app = Flask(__name__) api = flask_restful.Api(app, errors=errors) flask-restful-0.3.6/docs/fields.rst000066400000000000000000000223111311353067100172310ustar00rootroot00000000000000.. _fields: Output Fields =============== .. currentmodule:: flask_restful Flask-RESTful provides an easy way to control what data you actually render in your response. With the :mod:`fields` module, you can use whatever objects (ORM models/custom classes/etc.) you want in your resource. :mod:`fields` also lets you format and filter the response so you don't have to worry about exposing internal data structures. It's also very clear when looking at your code what data will be rendered and how it will be formatted. Basic Usage ----------- You can define a dict or OrderedDict of fields whose keys are names of attributes or keys on the object to render, and whose values are a class that will format & return the value for that field. This example has three fields: two are :class:`~fields.String` and one is a :class:`~fields.DateTime`, formatted as an RFC 822 date string (ISO 8601 is supported as well) :: from flask_restful import Resource, fields, marshal_with resource_fields = { 'name': fields.String, 'address': fields.String, 'date_updated': fields.DateTime(dt_format='rfc822'), } class Todo(Resource): @marshal_with(resource_fields, envelope='resource') def get(self, **kwargs): return db_get_todo() # Some function that queries the db This example assumes that you have a custom database object (``todo``) that has attributes ``name``, ``address``, and ``date_updated``. Any additional attributes on the object are considered private and won't be rendered in the output. An optional ``envelope`` keyword argument is specified to wrap the resulting output. The decorator :class:`marshal_with` is what actually takes your data object and applies the field filtering. The marshalling can work on single objects, dicts, or lists of objects. .. note :: :class:`marshal_with` is a convenience decorator, that is functionally equivalent to :: class Todo(Resource): def get(self, **kwargs): return marshal(db_get_todo(), resource_fields), 200 This explicit expression can be used to return HTTP status codes other than 200 along with a successful response (see :func:`abort` for errors). Renaming Attributes ------------------- Often times your public facing field name is different from your internal field name. To configure this mapping, use the ``attribute`` keyword argument. :: fields = { 'name': fields.String(attribute='private_name'), 'address': fields.String, } A lambda (or any callable) can also be specified as the ``attribute`` :: fields = { 'name': fields.String(attribute=lambda x: x._private_name), 'address': fields.String, } Nested properties can also be accessed with ``attribute`` :: fields = { 'name': fields.String(attribute='people_list.0.person_dictionary.name'), 'address': fields.String, } Default Values -------------- If for some reason your data object doesn't have an attribute in your fields list, you can specify a default value to return instead of ``None``. :: fields = { 'name': fields.String(default='Anonymous User'), 'address': fields.String, } Custom Fields & Multiple Values ------------------------------- Sometimes you have your own custom formatting needs. You can subclass the :class:`fields.Raw` class and implement the format function. This is especially useful when an attribute stores multiple pieces of information. e.g. a bit-field whose individual bits represent distinct values. You can use fields to multiplex a single attribute to multiple output values. This example assumes that bit 1 in the ``flags`` attribute signifies a "Normal" or "Urgent" item, and bit 2 signifies "Read" or "Unread". These items might be easy to store in a bitfield, but for a human readable output it's nice to convert them to seperate string fields. :: class UrgentItem(fields.Raw): def format(self, value): return "Urgent" if value & 0x01 else "Normal" class UnreadItem(fields.Raw): def format(self, value): return "Unread" if value & 0x02 else "Read" fields = { 'name': fields.String, 'priority': UrgentItem(attribute='flags'), 'status': UnreadItem(attribute='flags'), } Url & Other Concrete Fields --------------------------- Flask-RESTful includes a special field, :class:`fields.Url`, that synthesizes a uri for the resource that's being requested. This is also a good example of how to add data to your response that's not actually present on your data object.:: class RandomNumber(fields.Raw): def output(self, key, obj): return random.random() fields = { 'name': fields.String, # todo_resource is the endpoint name when you called api.add_resource() 'uri': fields.Url('todo_resource'), 'random': RandomNumber, } By default :class:`fields.Url` returns a relative uri. To generate an absolute uri that includes the scheme, hostname and port, pass the keyword argument ``absolute=True`` in the field declaration. To override the default scheme, pass the ``scheme`` keyword argument:: fields = { 'uri': fields.Url('todo_resource', absolute=True) 'https_uri': fields.Url('todo_resource', absolute=True, scheme='https') } Complex Structures ------------------ You can have a flat structure that :meth:`marshal` will transform to a nested structure :: >>> from flask_restful import fields, marshal >>> import json >>> >>> resource_fields = {'name': fields.String} >>> resource_fields['address'] = {} >>> resource_fields['address']['line 1'] = fields.String(attribute='addr1') >>> resource_fields['address']['line 2'] = fields.String(attribute='addr2') >>> resource_fields['address']['city'] = fields.String >>> resource_fields['address']['state'] = fields.String >>> resource_fields['address']['zip'] = fields.String >>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> json.dumps(marshal(data, resource_fields)) '{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}' .. note :: The address field doesn't actually exist on the data object, but any of the sub-fields can access attributes directly from the object as if they were not nested. .. _list-field: List Field ---------- You can also unmarshal fields as lists :: >>> from flask_restful import fields, marshal >>> import json >>> >>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)} >>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']} >>> json.dumps(marshal(data, resource_fields)) >>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}' .. _nested-field: Advanced : Nested Field ----------------------- While nesting fields using dicts can turn a flat data object into a nested response, you can use :class:`~fields.Nested` to unmarshal nested data structures and render them appropriately. :: >>> from flask_restful import fields, marshal >>> import json >>> >>> address_fields = {} >>> address_fields['line 1'] = fields.String(attribute='addr1') >>> address_fields['line 2'] = fields.String(attribute='addr2') >>> address_fields['city'] = fields.String(attribute='city') >>> address_fields['state'] = fields.String(attribute='state') >>> address_fields['zip'] = fields.String(attribute='zip') >>> >>> resource_fields = {} >>> resource_fields['name'] = fields.String >>> resource_fields['billing_address'] = fields.Nested(address_fields) >>> resource_fields['shipping_address'] = fields.Nested(address_fields) >>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'} >>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2} >>> >>> json.dumps(marshal_with(data, resource_fields)) '{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}' This example uses two ``Nested`` fields. The ``Nested`` constructor takes a dict of fields to render as sub-fields. The important difference between the ``Nested`` constructor and nested dicts (previous example), is the context for attributes. In this example, ``billing_address`` is a complex object that has its own fields and the context passed to the nested field is the sub-object instead of the original ``data`` object. In other words: ``data.billing_address.addr1`` is in scope here, whereas in the previous example ``data.addr1`` was the location attribute. Remember: ``Nested`` and ``List`` objects create a new scope for attributes. Use :class:`~fields.Nested` with :class:`~fields.List` to marshal lists of more complex objects: :: user_fields = { 'id': fields.Integer, 'name': fields.String, } user_list_fields = { fields.List(fields.Nested(user_fields)), } flask-restful-0.3.6/docs/index.rst000066400000000000000000000006231311353067100170740ustar00rootroot00000000000000:orphan: Flask-RESTful ============= .. module:: flask_restful **Flask-RESTful** is an extension for Flask that adds support for quickly building REST APIs. It is a lightweight abstraction that works with your existing ORM/libraries. Flask-RESTful encourages best practices with minimal setup. If you are familiar with Flask, Flask-RESTful should be easy to pick up. .. include:: contents.rst.inc flask-restful-0.3.6/docs/installation.rst000066400000000000000000000011521311353067100204640ustar00rootroot00000000000000.. _installation: Installation ============ .. currentmodule:: flask_restful Install Flask-RESTful with ``pip`` :: pip install flask-restful The development version can be downloaded from `its page at GitHub `_. :: git clone https://github.com/flask-restful/flask-restful.git cd flask-restful python setup.py develop Flask-RESTful has the following dependencies (which will be automatically installed if you use ``pip``): * `Flask `_ version 0.8 or greater Flask-RESTful requires Python version 2.6, 2.7, 3.3, or 3.4. flask-restful-0.3.6/docs/intermediate-usage.rst000066400000000000000000000230641311353067100215450ustar00rootroot00000000000000.. _intermediate: Intermediate Usage ================== .. currentmodule:: flask_restful This page covers building a slightly more complex Flask-RESTful app that will cover out some best practices when setting up a real-world Flask-RESTful-based API. The :ref:`quickstart` section is great for getting started with your first Flask-RESTful app, so if you're new to Flask-RESTful you'd be better off checking that out first. Project Structure ----------------- There are many different ways to organize your Flask-RESTful app, but here we'll describe one that scales pretty well with larger apps and maintains a nice level organization. The basic idea is to split your app into three main parts: the routes, the resources, and any common infrastructure. Here's an example directory structure: :: myapi/ __init__.py app.py # this file contains your app and routes resources/ __init__.py foo.py # contains logic for /Foo bar.py # contains logic for /Bar common/ __init__.py util.py # just some common infrastructure The common directory would probably just contain a set of helper functions to fulfill common needs across your application. It could also contain, for example, any custom input/output types your resources need to get the job done. In the resource files, you just have your resource objects. So here's what ``foo.py`` might look like: :: from flask_restful import Resource class Foo(Resource): def get(self): pass def post(self): pass The key to this setup lies in ``app.py``: :: from flask import Flask from flask_restful import Api from myapi.resources.foo import Foo from myapi.resources.bar import Bar from myapi.resources.baz import Baz app = Flask(__name__) api = Api(app) api.add_resource(Foo, '/Foo', '/Foo/') api.add_resource(Bar, '/Bar', '/Bar/') api.add_resource(Baz, '/Baz', '/Baz/') As you can imagine with a particularly large or complex API, this file ends up being very valuable as a comprehensive list of all the routes and resources in your API. You would also use this file to set up any config values (:meth:`~flask.Flask.before_request`, :meth:`~flask.Flask.after_request`). Basically, this file configures your entire API. The things in the common directory are just things you'd want to support your resource modules. Use With Blueprints ------------------- See :ref:`blueprints` in the Flask documentation for what blueprints are and why you should use them. Here's an example of how to link an :class:`Api` up to a :class:`~flask.Blueprint`. :: from flask import Flask, Blueprint from flask_restful import Api, Resource, url_for app = Flask(__name__) api_bp = Blueprint('api', __name__) api = Api(api_bp) class TodoItem(Resource): def get(self, id): return {'task': 'Say "Hello, World!"'} api.add_resource(TodoItem, '/todos/') app.register_blueprint(api_bp) .. note :: Calling :meth:`Api.init_app` is not required here because registering the blueprint with the app takes care of setting up the routing for the application. Full Parameter Parsing Example ------------------------------ Elsewhere in the documentation, we've described how to use the reqparse example in detail. Here we'll set up a resource with multiple input parameters that exercise a larger amount of options. We'll define a resource named "User". :: from flask_restful import fields, marshal_with, reqparse, Resource def email(email_str): """Return email_str if valid, raise an exception in other case.""" if valid_email(email_str): return email_str else: raise ValueError('{} is not a valid email'.format(email_str)) post_parser = reqparse.RequestParser() post_parser.add_argument( 'username', dest='username', location='form', required=True, help='The user\'s username', ) post_parser.add_argument( 'email', dest='email', type=email, location='form', required=True, help='The user\'s email', ) post_parser.add_argument( 'user_priority', dest='user_priority', type=int, location='form', default=1, choices=range(5), help='The user\'s priority', ) user_fields = { 'id': fields.Integer, 'username': fields.String, 'email': fields.String, 'user_priority': fields.Integer, 'custom_greeting': fields.FormattedString('Hey there {username}!'), 'date_created': fields.DateTime, 'date_updated': fields.DateTime, 'links': fields.Nested({ 'friends': fields.Url('user_friends'), 'posts': fields.Url('user_posts'), }), } class User(Resource): @marshal_with(user_fields) def post(self): args = post_parser.parse_args() user = create_user(args.username, args.email, args.user_priority) return user @marshal_with(user_fields) def get(self, id): args = post_parser.parse_args() user = fetch_user(id) return user As you can see, we create a ``post_parser`` specifically to handle the parsing of arguments provided on POST. Let's step through the definition of each argument. :: post_parser.add_argument( 'username', dest='username', location='form', required=True, help='The user\'s username', ) The ``username`` field is the most normal out of all of them. It takes a string from the POST body and converts it to a string type. This argument is required (``required=True``), which means that if it isn't provided, Flask-RESTful will automatically return a 400 with a message along the lines of 'the username field is required'. :: post_parser.add_argument( 'email', dest='email', type=email, location='form', required=True, help='The user\'s email', ) The ``email`` field has a custom type of ``email``. A few lines earlier we defined an ``email`` function that takes a string and returns it if the type is valid, else it raises an exception, exclaiming that the email type was invalid. :: post_parser.add_argument( 'user_priority', dest='user_priority', type=int, location='form', default=1, choices=range(5), help='The user\'s priority', ) The ``user_priority`` type takes advantage of the ``choices`` argument. This means that if the provided `user_priority` value doesn't fall in the range specified by the ``choices`` argument (in this case ``[0, 1, 2, 3, 4]``), Flask-RESTful will automatically respond with a 400 and a descriptive error message. That covers the inputs. We also defined some interesting field types in the ``user_fields`` dictionary to showcase a couple of the more exotic types. :: user_fields = { 'id': fields.Integer, 'username': fields.String, 'email': fields.String, 'user_priority': fields.Integer, 'custom_greeting': fields.FormattedString('Hey there {username}!'), 'date_created': fields.DateTime, 'date_updated': fields.DateTime, 'links': fields.Nested({ 'friends': fields.Url('user_friends', absolute=True), 'posts': fields.Url('user_friends', absolute=True), }), } First up, there's :class:`fields.FormattedString`. :: 'custom_greeting': fields.FormattedString('Hey there {username}!'), This field is primarily used to interpolate values from the response into other values. In this instance, ``custom_greeting`` will always contain the value returned from the ``username`` field. Next up, check out :class:`fields.Nested`. :: 'links': fields.Nested({ 'friends': fields.Url('user_friends', absolute=True), 'posts': fields.Url('user_posts', absolute=True), }), This field is used to create a sub-object in the response. In this case, we want to create a ``links`` sub-object to contain urls of related objects. Note that we passed `fields.Nested` another dict which is built in such a way that it would be an acceptable argument to :func:`marshal` by itself. Finally, we used the :class:`fields.Url` field type. :: 'friends': fields.Url('user_friends', absolute=True), 'posts': fields.Url('user_friends', absolute=True), It takes as its first parameter the name of the endpoint associated with the urls of the objects in the ``links`` sub-object. Passing ``absolute=True`` ensures that the generated urls will have the hostname included. Passing Constructor Parameters Into Resources --------------------------------------------- Your :class:`Resource` implementation may require outside dependencies. Those dependencies are best passed-in through the constructor to loosely couple each other. The :meth:`Api.add_resource` method has two keyword arguments: ``resource_class_args`` and ``resource_class_kwargs``. Their values will be forwarded and passed into your Resource implementation's constructor. So you could have a :class:`Resource`: :: from flask_restful import Resource class TodoNext(Resource): def __init__(self, **kwargs): # smart_engine is a black box dependency self.smart_engine = kwargs['smart_engine'] def get(self): return self.smart_engine.next_todo() You can inject the required dependency into TodoNext like so: :: smart_engine = SmartEngine() api.add_resource(TodoNext, '/next', resource_class_kwargs={ 'smart_engine': smart_engine }) Same idea applies for forwarding `args`. flask-restful-0.3.6/docs/make.bat000066400000000000000000000117661311353067100166520ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs 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 if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 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 if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-RESTful.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-RESTful.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 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 if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end flask-restful-0.3.6/docs/quickstart.rst000066400000000000000000000257201311353067100201640ustar00rootroot00000000000000.. _quickstart: Quickstart ========== .. currentmodule:: flask_restful It's time to write your first REST API. This guide assumes you have a working understanding of `Flask `_, and that you have already installed both Flask and Flask-RESTful. If not, then follow the steps in the :ref:`installation` section. A Minimal API ------------- A minimal Flask-RESTful API looks like this: :: from flask import Flask from flask_restful import Resource, Api app = Flask(__name__) api = Api(app) class HelloWorld(Resource): def get(self): return {'hello': 'world'} api.add_resource(HelloWorld, '/') if __name__ == '__main__': app.run(debug=True) Save this as api.py and run it using your Python interpreter. Note that we've enabled `Flask debugging `_ mode to provide code reloading and better error messages. :: $ python api.py * Running on http://127.0.0.1:5000/ * Restarting with reloader .. warning:: Debug mode should never be used in a production environment! Now open up a new prompt to test out your API using curl :: $ curl http://127.0.0.1:5000/ {"hello": "world"} Resourceful Routing ------------------- The main building block provided by Flask-RESTful are resources. Resources are built on top of `Flask pluggable views `_, giving you easy access to multiple HTTP methods just by defining methods on your resource. A basic CRUD resource for a todo application (of course) looks like this: :: from flask import Flask, request from flask_restful import Resource, Api app = Flask(__name__) api = Api(app) todos = {} class TodoSimple(Resource): def get(self, todo_id): return {todo_id: todos[todo_id]} def put(self, todo_id): todos[todo_id] = request.form['data'] return {todo_id: todos[todo_id]} api.add_resource(TodoSimple, '/') if __name__ == '__main__': app.run(debug=True) You can try it like this: :: $ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT {"todo1": "Remember the milk"} $ curl http://localhost:5000/todo1 {"todo1": "Remember the milk"} $ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT {"todo2": "Change my brakepads"} $ curl http://localhost:5000/todo2 {"todo2": "Change my brakepads"} Or from python if you have the ``requests`` library installed:: >>> from requests import put, get >>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json() {u'todo1': u'Remember the milk'} >>> get('http://localhost:5000/todo1').json() {u'todo1': u'Remember the milk'} >>> put('http://localhost:5000/todo2', data={'data': 'Change my brakepads'}).json() {u'todo2': u'Change my brakepads'} >>> get('http://localhost:5000/todo2').json() {u'todo2': u'Change my brakepads'} Flask-RESTful understands multiple kinds of return values from view methods. Similar to Flask, you can return any iterable and it will be converted into a response, including raw Flask response objects. Flask-RESTful also support setting the response code and response headers using multiple return values, as shown below: :: class Todo1(Resource): def get(self): # Default to 200 OK return {'task': 'Hello world'} class Todo2(Resource): def get(self): # Set the response code to 201 return {'task': 'Hello world'}, 201 class Todo3(Resource): def get(self): # Set the response code to 201 and return custom headers return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'} Endpoints --------- Many times in an API, your resource will have multiple URLs. You can pass multiple URLs to the :meth:`~Api.add_resource` method on the `Api` object. Each one will be routed to your :class:`Resource` :: api.add_resource(HelloWorld, '/', '/hello') You can also match parts of the path as variables to your resource methods. :: api.add_resource(Todo, '/todo/', endpoint='todo_ep') .. note :: If a request does not match any of your application's endpoints, Flask-RESTful will return a 404 error message with suggestions of other endpoints that closely match the requested endpoint. This can be disabled by setting ``ERROR_404_HELP`` to ``False`` in your application config. Argument Parsing ---------------- While Flask provides easy access to request data (i.e. querystring or POST form encoded data), it's still a pain to validate form data. Flask-RESTful has built-in support for request data validation using a library similar to `argparse `_. :: from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('rate', type=int, help='Rate to charge for this resource') args = parser.parse_args() .. note :: Unlike the argparse module, :meth:`reqparse.RequestParser.parse_args` returns a Python dictionary instead of a custom data structure. Using the :class:`reqparse` module also gives you sane error messages for free. If an argument fails to pass validation, Flask-RESTful will respond with a 400 Bad Request and a response highlighting the error. :: $ curl -d 'rate=foo' http://127.0.0.1:5000/todos {'status': 400, 'message': 'foo cannot be converted to int'} The :class:`inputs` module provides a number of included common conversion functions such as :meth:`inputs.date` and :meth:`inputs.url`. Calling ``parse_args`` with ``strict=True`` ensures that an error is thrown if the request includes arguments your parser does not define. :: args = parser.parse_args(strict=True) Data Formatting --------------- By default, all fields in your return iterable will be rendered as-is. While this works great when you're just dealing with Python data structures, it can become very frustrating when working with objects. To solve this problem, Flask-RESTful provides the :class:`fields` module and the :meth:`marshal_with` decorator. Similar to the Django ORM and WTForm, you use the ``fields`` module to describe the structure of your response. :: from collections import OrderedDict from flask_restful import fields, marshal_with resource_fields = { 'task': fields.String, 'uri': fields.Url('todo_ep') } class TodoDao(object): def __init__(self, todo_id, task): self.todo_id = todo_id self.task = task # This field will not be sent in the response self.status = 'active' class Todo(Resource): @marshal_with(resource_fields) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task='Remember the milk') The above example takes a python object and prepares it to be serialized. The :meth:`marshal_with` decorator will apply the transformation described by ``resource_fields``. The only field extracted from the object is ``task``. The :class:`fields.Url` field is a special field that takes an endpoint name and generates a URL for that endpoint in the response. Many of the field types you need are already included. See the :class:`fields` guide for a complete list. Full Example ------------ Save this example in api.py :: from flask import Flask from flask_restful import reqparse, abort, Api, Resource app = Flask(__name__) api = Api(app) TODOS = { 'todo1': {'task': 'build an API'}, 'todo2': {'task': '?????'}, 'todo3': {'task': 'profit!'}, } def abort_if_todo_doesnt_exist(todo_id): if todo_id not in TODOS: abort(404, message="Todo {} doesn't exist".format(todo_id)) parser = reqparse.RequestParser() parser.add_argument('task') # Todo # shows a single todo item and lets you delete a todo item class Todo(Resource): def get(self, todo_id): abort_if_todo_doesnt_exist(todo_id) return TODOS[todo_id] def delete(self, todo_id): abort_if_todo_doesnt_exist(todo_id) del TODOS[todo_id] return '', 204 def put(self, todo_id): args = parser.parse_args() task = {'task': args['task']} TODOS[todo_id] = task return task, 201 # TodoList # shows a list of all todos, and lets you POST to add new tasks class TodoList(Resource): def get(self): return TODOS def post(self): args = parser.parse_args() todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1 todo_id = 'todo%i' % todo_id TODOS[todo_id] = {'task': args['task']} return TODOS[todo_id], 201 ## ## Actually setup the Api resource routing here ## api.add_resource(TodoList, '/todos') api.add_resource(Todo, '/todos/') if __name__ == '__main__': app.run(debug=True) Example usage :: $ python api.py * Running on http://127.0.0.1:5000/ * Restarting with reloader GET the list :: $ curl http://localhost:5000/todos {"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}} GET a single task :: $ curl http://localhost:5000/todos/todo3 {"task": "profit!"} DELETE a task :: $ curl http://localhost:5000/todos/todo2 -X DELETE -v > DELETE /todos/todo2 HTTP/1.1 > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3 > Host: localhost:5000 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 204 NO CONTENT < Content-Type: application/json < Content-Length: 0 < Server: Werkzeug/0.8.3 Python/2.7.2 < Date: Mon, 01 Oct 2012 22:10:32 GMT Add a new task :: $ curl http://localhost:5000/todos -d "task=something new" -X POST -v > POST /todos HTTP/1.1 > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3 > Host: localhost:5000 > Accept: */* > Content-Length: 18 > Content-Type: application/x-www-form-urlencoded > * HTTP 1.0, assume close after body < HTTP/1.0 201 CREATED < Content-Type: application/json < Content-Length: 25 < Server: Werkzeug/0.8.3 Python/2.7.2 < Date: Mon, 01 Oct 2012 22:12:58 GMT < * Closing connection #0 {"task": "something new"} Update a task :: $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v > PUT /todos/todo3 HTTP/1.1 > Host: localhost:5000 > Accept: */* > Content-Length: 20 > Content-Type: application/x-www-form-urlencoded > * HTTP 1.0, assume close after body < HTTP/1.0 201 CREATED < Content-Type: application/json < Content-Length: 27 < Server: Werkzeug/0.8.3 Python/2.7.3 < Date: Mon, 01 Oct 2012 22:13:00 GMT < * Closing connection #0 {"task": "something different"} flask-restful-0.3.6/docs/reqparse.rst000066400000000000000000000200571311353067100176120ustar00rootroot00000000000000.. _reqparse: Request Parsing =============== .. warning :: The whole request parser part of Flask-RESTful is slated for removal and will be replaced by documentation on how to integrate with other packages that do the input/output stuff better (such as `marshmallow `_). This means that it will be maintained until 2.0 but consider it deprecated. Don't worry, if you have code using that now and wish to continue doing so, it's not going to go away any time too soon. .. currentmodule:: flask_restful Flask-RESTful's request parsing interface, :mod:`reqparse`, is modeled after the `argparse `_ interface. It's designed to provide simple and uniform access to any variable on the :class:`flask.request` object in Flask. Basic Arguments --------------- Here's a simple example of the request parser. It looks for two arguments in the :attr:`flask.Request.values` dict: an integer and a string :: from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('rate', type=int, help='Rate cannot be converted') parser.add_argument('name') args = parser.parse_args() .. note :: The default argument type is a unicode string. This will be ``str`` in python3 and ``unicode`` in python2. If you specify the ``help`` value, it will be rendered as the error message when a type error is raised while parsing it. If you do not specify a help message, the default behavior is to return the message from the type error itself. See :ref:`error-messages` for more details. By default, arguments are **not** required. Also, arguments supplied in the request that are not part of the RequestParser will be ignored. Also note: Arguments declared in your request parser but not set in the request itself will default to ``None``. Required Arguments ------------------ To require a value be passed for an argument, just add ``required=True`` to the call to :meth:`~reqparse.RequestParser.add_argument`. :: parser.add_argument('name', required=True, help="Name cannot be blank!") Multiple Values & Lists ----------------------- If you want to accept multiple values for a key as a list, you can pass ``action='append'`` :: parser.add_argument('name', action='append') This will let you make queries like :: curl http://api.example.com -d "name=bob" -d "name=sue" -d "name=joe" And your args will look like this :: args = parser.parse_args() args['name'] # ['bob', 'sue', 'joe'] Other Destinations ------------------ If for some reason you'd like your argument stored under a different name once it's parsed, you can use the ``dest`` keyword argument. :: parser.add_argument('name', dest='public_name') args = parser.parse_args() args['public_name'] Argument Locations ------------------ By default, the :class:`~reqparse.RequestParser` tries to parse values from :attr:`flask.Request.values`, and :attr:`flask.Request.json`. Use the ``location`` argument to :meth:`~reqparse.RequestParser.add_argument` to specify alternate locations to pull the values from. Any variable on the :class:`flask.Request` can be used. For example: :: # Look only in the POST body parser.add_argument('name', type=int, location='form') # Look only in the querystring parser.add_argument('PageSize', type=int, location='args') # From the request headers parser.add_argument('User-Agent', location='headers') # From http cookies parser.add_argument('session_id', location='cookies') # From file uploads parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files') .. note :: Only use ``type=list`` when ``location='json'``. `See this issue for more details `_ Multiple Locations ------------------ Multiple argument locations can be specified by passing a list to ``location``:: parser.add_argument('text', location=['headers', 'values']) When multiple locations are specified, the arguments from all locations specified are combined into a single :class:`~werkzeug.datastructures.MultiDict`. The last ``location`` listed takes precedence in the result set. If the argument location list includes the :attr:`~flask.Request.headers` location the argument names will no longer be case insensitive and must match their title case names (see :meth:`str.title`). Specifying ``location='headers'`` (not as a list) will retain case insensitivity. Parser Inheritance ------------------ Often you will make a different parser for each resource you write. The problem with this is if parsers have arguments in common. Instead of rewriting arguments you can write a parent parser containing all the shared arguments and then extend the parser with :meth:`~reqparse.RequestParser.copy`. You can also overwrite any argument in the parent with :meth:`~reqparse.RequestParser.replace_argument`, or remove it completely with :meth:`~reqparse.RequestParser.remove_argument`. For example: :: from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('foo', type=int) parser_copy = parser.copy() parser_copy.add_argument('bar', type=int) # parser_copy has both 'foo' and 'bar' parser_copy.replace_argument('foo', required=True, location='json') # 'foo' is now a required str located in json, not an int as defined # by original parser parser_copy.remove_argument('foo') # parser_copy no longer has 'foo' argument Error Handling -------------- The default way errors are handled by the RequestParser is to abort on the first error that occurred. This can be beneficial when you have arguments that might take some time to process. However, often it is nice to have the errors bundled together and sent back to the client all at once. This behavior can be specified either at the Flask application level or on the specific RequestParser instance. To invoke a RequestParser with the bundling errors option, pass in the argument ``bundle_errors``. For example :: from flask_restful import reqparse parser = reqparse.RequestParser(bundle_errors=True) parser.add_argument('foo', type=int, required=True) parser.add_argument('bar', type=int, required=True) # If a request comes in not containing both 'foo' and 'bar', the error that # will come back will look something like this. { "message": { "foo": "foo error message", "bar": "bar error message" } } # The default behavior would only return the first error parser = RequestParser() parser.add_argument('foo', type=int, required=True) parser.add_argument('bar', type=int, required=True) { "message": { "foo": "foo error message" } } The application configuration key is "BUNDLE_ERRORS". For example :: from flask import Flask app = Flask(__name__) app.config['BUNDLE_ERRORS'] = True .. warning :: ``BUNDLE_ERRORS`` is a global setting that overrides the ``bundle_errors`` option in individual :class:`~reqparse.RequestParser` instances. .. _error-messages: Error Messages -------------- Error messages for each field may be customized using the ``help`` parameter to ``Argument`` (and also ``RequestParser.add_argument``). If no help parameter is provided, the error message for the field will be the string representation of the type error itself. If ``help`` is provided, then the error message will be the value of ``help``. ``help`` may include an interpolation token, ``{error_msg}``, that will be replaced with the string representation of the type error. This allows the message to be customized while preserving the original error :: from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument( 'foo', choices=('one', 'two'), help='Bad choice: {error_msg}' ) # If a request comes in with a value of "three" for `foo`: { "message": { "foo": "Bad choice: three is not a valid choice", } } flask-restful-0.3.6/docs/testing.rst000066400000000000000000000016141311353067100174430ustar00rootroot00000000000000.. _testing: Running the Tests ================= A ``Makefile`` is included to take care of setting up a virtualenv for running tests. All you need to do is run:: $ make test To change the Python version used to run the tests (default is Python 2.7), change the ``PYTHON_MAJOR`` and ``PYTHON_MINOR`` variables at the top of the ``Makefile``. You can run on all supported versions with:: $ make test-all Individual tests can be run using using a command with the format:: nosetests :ClassName.func_name Example:: $ source env/bin/activate $ nosetests tests/test_reqparse.py:ReqParseTestCase.test_parse_choices_insensitive Alternately, if you push changes to your fork on Github, Travis will run the tests for your branch automatically. A Tox config file is also provided so you can test against multiple python versions locally (2.6, 2.7, 3.3, and 3.4) :: $ tox flask-restful-0.3.6/examples/000077500000000000000000000000001311353067100161205ustar00rootroot00000000000000flask-restful-0.3.6/examples/todo.py000066400000000000000000000026301311353067100174400ustar00rootroot00000000000000from flask import Flask from flask_restful import reqparse, abort, Api, Resource app = Flask(__name__) api = Api(app) TODOS = { 'todo1': {'task': 'build an API'}, 'todo2': {'task': '?????'}, 'todo3': {'task': 'profit!'}, } def abort_if_todo_doesnt_exist(todo_id): if todo_id not in TODOS: abort(404, message="Todo {} doesn't exist".format(todo_id)) parser = reqparse.RequestParser() parser.add_argument('task') # Todo # show a single todo item and lets you delete them class Todo(Resource): def get(self, todo_id): abort_if_todo_doesnt_exist(todo_id) return TODOS[todo_id] def delete(self, todo_id): abort_if_todo_doesnt_exist(todo_id) del TODOS[todo_id] return '', 204 def put(self, todo_id): args = parser.parse_args() task = {'task': args['task']} TODOS[todo_id] = task return task, 201 # TodoList # shows a list of all todos, and lets you POST to add new tasks class TodoList(Resource): def get(self): return TODOS def post(self): args = parser.parse_args() todo_id = 'todo%d' % (len(TODOS) + 1) TODOS[todo_id] = {'task': args['task']} return TODOS[todo_id], 201 ## ## Actually setup the Api resource routing here ## api.add_resource(TodoList, '/todos') api.add_resource(Todo, '/todos/') if __name__ == '__main__': app.run(debug=True) flask-restful-0.3.6/examples/todo_simple.py000066400000000000000000000024641311353067100210160ustar00rootroot00000000000000from flask import Flask, request from flask_restful import Resource, Api app = Flask(__name__) api = Api(app) todos = {} class TodoSimple(Resource): """ You can try this example as follow: $ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT $ curl http://localhost:5000/todo1 {"todo1": "Remember the milk"} $ curl http://localhost:5000/todo2 -d "data=Change my breakpads" -X PUT $ curl http://localhost:5000/todo2 {"todo2": "Change my breakpads"} Or from python if you have requests : >>> from requests import put, get >>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json {u'todo1': u'Remember the milk'} >>> get('http://localhost:5000/todo1').json {u'todo1': u'Remember the milk'} >>> put('http://localhost:5000/todo2', data={'data': 'Change my breakpads'}).json {u'todo2': u'Change my breakpads'} >>> get('http://localhost:5000/todo2').json {u'todo2': u'Change my breakpads'} """ def get(self, todo_id): return {todo_id: todos[todo_id]} def put(self, todo_id): todos[todo_id] = request.form['data'] return {todo_id: todos[todo_id]} api.add_resource(TodoSimple, '/') if __name__ == '__main__': app.run(debug=False) flask-restful-0.3.6/examples/xml_representation.py000066400000000000000000000022171311353067100224160ustar00rootroot00000000000000# needs: pip install python-simplexml from simplexml import dumps from flask import make_response, Flask from flask_restful import Api, Resource def output_xml(data, code, headers=None): """Makes a Flask response with a XML encoded body""" resp = make_response(dumps({'response' :data}), code) resp.headers.extend(headers or {}) return resp app = Flask(__name__) api = Api(app, default_mediatype='application/xml') api.representations['application/xml'] = output_xml class Hello(Resource): """ # you need requests >>> from requests import get >>> get('http://localhost:5000/me').content # default_mediatype 'me' >>> get('http://localhost:5000/me', headers={"accept":"application/json"}).content '{"hello": "me"}' >>> get('http://localhost:5000/me', headers={"accept":"application/xml"}).content 'me' """ def get(self, entry): return {'hello': entry} api.add_resource(Hello, '/') if __name__ == '__main__': app.run(debug=True) flask-restful-0.3.6/flask_restful/000077500000000000000000000000001311353067100171465ustar00rootroot00000000000000flask-restful-0.3.6/flask_restful/__init__.py000066400000000000000000000675361311353067100213000ustar00rootroot00000000000000from __future__ import absolute_import import difflib from functools import wraps, partial import re from flask import request, url_for, current_app from flask import abort as original_flask_abort from flask import make_response as original_flask_make_response from flask.views import MethodView from flask.signals import got_request_exception from werkzeug.datastructures import Headers from werkzeug.exceptions import HTTPException, MethodNotAllowed, NotFound, NotAcceptable, InternalServerError from werkzeug.http import HTTP_STATUS_CODES from werkzeug.wrappers import Response as ResponseBase from flask_restful.utils import http_status_message, unpack, OrderedDict from flask_restful.representations.json import output_json import sys from flask.helpers import _endpoint_from_view_func from types import MethodType import operator from collections import Mapping __all__ = ('Api', 'Resource', 'marshal', 'marshal_with', 'marshal_with_field', 'abort') def abort(http_status_code, **kwargs): """Raise a HTTPException for the given http_status_code. Attach any keyword arguments to the exception for later processing. """ #noinspection PyUnresolvedReferences try: original_flask_abort(http_status_code) except HTTPException as e: if len(kwargs): e.data = kwargs raise DEFAULT_REPRESENTATIONS = [('application/json', output_json)] class Api(object): """ The main entry point for the application. You need to initialize it with a Flask Application: :: >>> app = Flask(__name__) >>> api = restful.Api(app) Alternatively, you can use :meth:`init_app` to set the Flask application after it has been constructed. :param app: the Flask application object :type app: flask.Flask :type app: flask.Blueprint :param prefix: Prefix all routes with a value, eg v1 or 2010-04-01 :type prefix: str :param default_mediatype: The default media type to return :type default_mediatype: str :param decorators: Decorators to attach to every resource :type decorators: list :param catch_all_404s: Use :meth:`handle_error` to handle 404 errors throughout your app :param serve_challenge_on_401: Whether to serve a challenge response to clients on receiving 401. This usually leads to a username/password popup in web browers. :param url_part_order: A string that controls the order that the pieces of the url are concatenated when the full url is constructed. 'b' is the blueprint (or blueprint registration) prefix, 'a' is the api prefix, and 'e' is the path component the endpoint is added with :type catch_all_404s: bool :param errors: A dictionary to define a custom response for each exception or error raised during a request :type errors: dict """ def __init__(self, app=None, prefix='', default_mediatype='application/json', decorators=None, catch_all_404s=False, serve_challenge_on_401=False, url_part_order='bae', errors=None): self.representations = OrderedDict(DEFAULT_REPRESENTATIONS) self.urls = {} self.prefix = prefix self.default_mediatype = default_mediatype self.decorators = decorators if decorators else [] self.catch_all_404s = catch_all_404s self.serve_challenge_on_401 = serve_challenge_on_401 self.url_part_order = url_part_order self.errors = errors or {} self.blueprint_setup = None self.endpoints = set() self.resources = [] self.app = None self.blueprint = None if app is not None: self.app = app self.init_app(app) def init_app(self, app): """Initialize this class with the given :class:`flask.Flask` application or :class:`flask.Blueprint` object. :param app: the Flask application or blueprint object :type app: flask.Flask :type app: flask.Blueprint Examples:: api = Api() api.add_resource(...) api.init_app(app) """ # If app is a blueprint, defer the initialization try: app.record(self._deferred_blueprint_init) # Flask.Blueprint has a 'record' attribute, Flask.Api does not except AttributeError: self._init_app(app) else: self.blueprint = app def _complete_url(self, url_part, registration_prefix): """This method is used to defer the construction of the final url in the case that the Api is created with a Blueprint. :param url_part: The part of the url the endpoint is registered with :param registration_prefix: The part of the url contributed by the blueprint. Generally speaking, BlueprintSetupState.url_prefix """ parts = { 'b': registration_prefix, 'a': self.prefix, 'e': url_part } return ''.join(parts[key] for key in self.url_part_order if parts[key]) @staticmethod def _blueprint_setup_add_url_rule_patch(blueprint_setup, rule, endpoint=None, view_func=None, **options): """Method used to patch BlueprintSetupState.add_url_rule for setup state instance corresponding to this Api instance. Exists primarily to enable _complete_url's function. :param blueprint_setup: The BlueprintSetupState instance (self) :param rule: A string or callable that takes a string and returns a string(_complete_url) that is the url rule for the endpoint being registered :param endpoint: See BlueprintSetupState.add_url_rule :param view_func: See BlueprintSetupState.add_url_rule :param **options: See BlueprintSetupState.add_url_rule """ if callable(rule): rule = rule(blueprint_setup.url_prefix) elif blueprint_setup.url_prefix: rule = blueprint_setup.url_prefix + rule options.setdefault('subdomain', blueprint_setup.subdomain) if endpoint is None: endpoint = _endpoint_from_view_func(view_func) defaults = blueprint_setup.url_defaults if 'defaults' in options: defaults = dict(defaults, **options.pop('defaults')) blueprint_setup.app.add_url_rule(rule, '%s.%s' % (blueprint_setup.blueprint.name, endpoint), view_func, defaults=defaults, **options) def _deferred_blueprint_init(self, setup_state): """Synchronize prefix between blueprint/api and registration options, then perform initialization with setup_state.app :class:`flask.Flask` object. When a :class:`flask_restful.Api` object is initialized with a blueprint, this method is recorded on the blueprint to be run when the blueprint is later registered to a :class:`flask.Flask` object. This method also monkeypatches BlueprintSetupState.add_url_rule with _blueprint_setup_add_url_rule_patch. :param setup_state: The setup state object passed to deferred functions during blueprint registration :type setup_state: flask.blueprints.BlueprintSetupState """ self.blueprint_setup = setup_state if setup_state.add_url_rule.__name__ != '_blueprint_setup_add_url_rule_patch': setup_state._original_add_url_rule = setup_state.add_url_rule setup_state.add_url_rule = MethodType(Api._blueprint_setup_add_url_rule_patch, setup_state) if not setup_state.first_registration: raise ValueError('flask-restful blueprints can only be registered once.') self._init_app(setup_state.app) def _init_app(self, app): """Perform initialization actions with the given :class:`flask.Flask` object. :param app: The flask application object :type app: flask.Flask """ app.handle_exception = partial(self.error_router, app.handle_exception) app.handle_user_exception = partial(self.error_router, app.handle_user_exception) if len(self.resources) > 0: for resource, urls, kwargs in self.resources: self._register_view(app, resource, *urls, **kwargs) def owns_endpoint(self, endpoint): """Tests if an endpoint name (not path) belongs to this Api. Takes in to account the Blueprint name part of the endpoint name. :param endpoint: The name of the endpoint being checked :return: bool """ if self.blueprint: if endpoint.startswith(self.blueprint.name): endpoint = endpoint.split(self.blueprint.name + '.', 1)[-1] else: return False return endpoint in self.endpoints def _should_use_fr_error_handler(self): """ Determine if error should be handled with FR or default Flask The goal is to return Flask error handlers for non-FR-related routes, and FR errors (with the correct media type) for FR endpoints. This method currently handles 404 and 405 errors. :return: bool """ adapter = current_app.create_url_adapter(request) try: adapter.match() except MethodNotAllowed as e: # Check if the other HTTP methods at this url would hit the Api valid_route_method = e.valid_methods[0] rule, _ = adapter.match(method=valid_route_method, return_rule=True) return self.owns_endpoint(rule.endpoint) except NotFound: return self.catch_all_404s except: # Werkzeug throws other kinds of exceptions, such as Redirect pass def _has_fr_route(self): """Encapsulating the rules for whether the request was to a Flask endpoint""" # 404's, 405's, which might not have a url_rule if self._should_use_fr_error_handler(): return True # for all other errors, just check if FR dispatched the route if not request.url_rule: return False return self.owns_endpoint(request.url_rule.endpoint) def error_router(self, original_handler, e): """This function decides whether the error occured in a flask-restful endpoint or not. If it happened in a flask-restful endpoint, our handler will be dispatched. If it happened in an unrelated view, the app's original error handler will be dispatched. In the event that the error occurred in a flask-restful endpoint but the local handler can't resolve the situation, the router will fall back onto the original_handler as last resort. :param original_handler: the original Flask error handler for the app :type original_handler: function :param e: the exception raised while handling the request :type e: Exception """ if self._has_fr_route(): try: return self.handle_error(e) except Exception: pass # Fall through to original handler return original_handler(e) def handle_error(self, e): """Error handler for the API transforms a raised exception into a Flask response, with the appropriate HTTP status code and body. :param e: the raised Exception object :type e: Exception """ got_request_exception.send(current_app._get_current_object(), exception=e) if not isinstance(e, HTTPException) and current_app.propagate_exceptions: exc_type, exc_value, tb = sys.exc_info() if exc_value is e: raise else: raise e headers = Headers() if isinstance(e, HTTPException): code = e.code default_data = { 'message': getattr(e, 'description', http_status_message(code)) } headers = e.get_response().headers else: code = 500 default_data = { 'message': http_status_message(code), } # Werkzeug exceptions generate a content-length header which is added # to the response in addition to the actual content-length header # https://github.com/flask-restful/flask-restful/issues/534 remove_headers = ('Content-Length',) for header in remove_headers: headers.pop(header, None) data = getattr(e, 'data', default_data) if code >= 500: exc_info = sys.exc_info() if exc_info[1] is None: exc_info = None current_app.log_exception(exc_info) help_on_404 = current_app.config.get("ERROR_404_HELP", True) if code == 404 and help_on_404: rules = dict([(re.sub('(<.*>)', '', rule.rule), rule.rule) for rule in current_app.url_map.iter_rules()]) close_matches = difflib.get_close_matches(request.path, rules.keys()) if close_matches: # If we already have a message, add punctuation and continue it. if "message" in data: data["message"] = data["message"].rstrip('.') + '. ' else: data["message"] = "" data['message'] += 'You have requested this URI [' + request.path + \ '] but did you mean ' + \ ' or '.join(( rules[match] for match in close_matches) ) + ' ?' error_cls_name = type(e).__name__ if error_cls_name in self.errors: custom_data = self.errors.get(error_cls_name, {}) code = custom_data.get('status', 500) data.update(custom_data) if code == 406 and self.default_mediatype is None: # if we are handling NotAcceptable (406), make sure that # make_response uses a representation we support as the # default mediatype (so that make_response doesn't throw # another NotAcceptable error). supported_mediatypes = list(self.representations.keys()) fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain" resp = self.make_response( data, code, headers, fallback_mediatype = fallback_mediatype ) else: resp = self.make_response(data, code, headers) if code == 401: resp = self.unauthorized(resp) return resp def mediatypes_method(self): """Return a method that returns a list of mediatypes """ return lambda resource_cls: self.mediatypes() + [self.default_mediatype] def add_resource(self, resource, *urls, **kwargs): """Adds a resource to the api. :param resource: the class name of your resource :type resource: :class:`Resource` :param urls: one or more url routes to match for the resource, standard flask routing rules apply. Any url variables will be passed to the resource method as args. :type urls: str :param endpoint: endpoint name (defaults to :meth:`Resource.__name__.lower` Can be used to reference this route in :class:`fields.Url` fields :type endpoint: str :param resource_class_args: args to be forwarded to the constructor of the resource. :type resource_class_args: tuple :param resource_class_kwargs: kwargs to be forwarded to the constructor of the resource. :type resource_class_kwargs: dict Additional keyword arguments not specified above will be passed as-is to :meth:`flask.Flask.add_url_rule`. Examples:: api.add_resource(HelloWorld, '/', '/hello') api.add_resource(Foo, '/foo', endpoint="foo") api.add_resource(FooSpecial, '/special/foo', endpoint="foo") """ if self.app is not None: self._register_view(self.app, resource, *urls, **kwargs) else: self.resources.append((resource, urls, kwargs)) def resource(self, *urls, **kwargs): """Wraps a :class:`~flask_restful.Resource` class, adding it to the api. Parameters are the same as :meth:`~flask_restful.Api.add_resource`. Example:: app = Flask(__name__) api = restful.Api(app) @api.resource('/foo') class Foo(Resource): def get(self): return 'Hello, World!' """ def decorator(cls): self.add_resource(cls, *urls, **kwargs) return cls return decorator def _register_view(self, app, resource, *urls, **kwargs): endpoint = kwargs.pop('endpoint', None) or resource.__name__.lower() self.endpoints.add(endpoint) resource_class_args = kwargs.pop('resource_class_args', ()) resource_class_kwargs = kwargs.pop('resource_class_kwargs', {}) # NOTE: 'view_functions' is cleaned up from Blueprint class in Flask 1.0 if endpoint in getattr(app, 'view_functions', {}): previous_view_class = app.view_functions[endpoint].__dict__['view_class'] # if you override the endpoint with a different class, avoid the collision by raising an exception if previous_view_class != resource: raise ValueError('This endpoint (%s) is already set to the class %s.' % (endpoint, previous_view_class.__name__)) resource.mediatypes = self.mediatypes_method() # Hacky resource.endpoint = endpoint resource_func = self.output(resource.as_view(endpoint, *resource_class_args, **resource_class_kwargs)) for decorator in self.decorators: resource_func = decorator(resource_func) for url in urls: # If this Api has a blueprint if self.blueprint: # And this Api has been setup if self.blueprint_setup: # Set the rule to a string directly, as the blueprint is already # set up. self.blueprint_setup.add_url_rule(url, view_func=resource_func, **kwargs) continue else: # Set the rule to a function that expects the blueprint prefix # to construct the final url. Allows deferment of url finalization # in the case that the associated Blueprint has not yet been # registered to an application, so we can wait for the registration # prefix rule = partial(self._complete_url, url) else: # If we've got no Blueprint, just build a url with no prefix rule = self._complete_url(url, '') # Add the url to the application or blueprint app.add_url_rule(rule, view_func=resource_func, **kwargs) def output(self, resource): """Wraps a resource (as a flask view function), for cases where the resource does not directly return a response object :param resource: The resource as a flask view function """ @wraps(resource) def wrapper(*args, **kwargs): resp = resource(*args, **kwargs) if isinstance(resp, ResponseBase): # There may be a better way to test return resp data, code, headers = unpack(resp) return self.make_response(data, code, headers=headers) return wrapper def url_for(self, resource, **values): """Generates a URL to the given resource. Works like :func:`flask.url_for`.""" endpoint = resource.endpoint if self.blueprint: endpoint = '{0}.{1}'.format(self.blueprint.name, endpoint) return url_for(endpoint, **values) def make_response(self, data, *args, **kwargs): """Looks up the representation transformer for the requested media type, invoking the transformer to create a response object. This defaults to default_mediatype if no transformer is found for the requested mediatype. If default_mediatype is None, a 406 Not Acceptable response will be sent as per RFC 2616 section 14.1 :param data: Python object containing response data to be transformed """ default_mediatype = kwargs.pop('fallback_mediatype', None) or self.default_mediatype mediatype = request.accept_mimetypes.best_match( self.representations, default=default_mediatype, ) if mediatype is None: raise NotAcceptable() if mediatype in self.representations: resp = self.representations[mediatype](data, *args, **kwargs) resp.headers['Content-Type'] = mediatype return resp elif mediatype == 'text/plain': resp = original_flask_make_response(str(data), *args, **kwargs) resp.headers['Content-Type'] = 'text/plain' return resp else: raise InternalServerError() def mediatypes(self): """Returns a list of requested mediatypes sent in the Accept header""" return [h for h, q in sorted(request.accept_mimetypes, key=operator.itemgetter(1), reverse=True)] def representation(self, mediatype): """Allows additional representation transformers to be declared for the api. Transformers are functions that must be decorated with this method, passing the mediatype the transformer represents. Three arguments are passed to the transformer: * The data to be represented in the response body * The http status code * A dictionary of headers The transformer should convert the data appropriately for the mediatype and return a Flask response object. Ex:: @api.representation('application/xml') def xml(data, code, headers): resp = make_response(convert_data_to_xml(data), code) resp.headers.extend(headers) return resp """ def wrapper(func): self.representations[mediatype] = func return func return wrapper def unauthorized(self, response): """ Given a response, change it to ask for credentials """ if self.serve_challenge_on_401: realm = current_app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful") challenge = u"{0} realm=\"{1}\"".format("Basic", realm) response.headers['WWW-Authenticate'] = challenge return response class Resource(MethodView): """ Represents an abstract RESTful resource. Concrete resources should extend from this class and expose methods for each supported HTTP method. If a resource is invoked with an unsupported HTTP method, the API will return a response with status 405 Method Not Allowed. Otherwise the appropriate method is called and passed all arguments from the url rule used when adding the resource to an Api instance. See :meth:`~flask_restful.Api.add_resource` for details. """ representations = None method_decorators = [] def dispatch_request(self, *args, **kwargs): # Taken from flask #noinspection PyUnresolvedReferences meth = getattr(self, request.method.lower(), None) if meth is None and request.method == 'HEAD': meth = getattr(self, 'get', None) assert meth is not None, 'Unimplemented method %r' % request.method if isinstance(self.method_decorators, Mapping): decorators = self.method_decorators.get(request.method.lower(), []) else: decorators = self.method_decorators for decorator in decorators: meth = decorator(meth) resp = meth(*args, **kwargs) if isinstance(resp, ResponseBase): # There may be a better way to test return resp representations = self.representations or OrderedDict() #noinspection PyUnresolvedReferences mediatype = request.accept_mimetypes.best_match(representations, default=None) if mediatype in representations: data, code, headers = unpack(resp) resp = representations[mediatype](data, code, headers) resp.headers['Content-Type'] = mediatype return resp return resp def marshal(data, fields, envelope=None): """Takes raw data (in the form of a dict, list, object) and a dict of fields to output and filters the data based on those fields. :param data: the actual object(s) from which the fields are taken from :param fields: a dict of whose keys will make up the final serialized response output :param envelope: optional key that will be used to envelop the serialized response >>> from flask_restful import fields, marshal >>> data = { 'a': 100, 'b': 'foo' } >>> mfields = { 'a': fields.Raw } >>> marshal(data, mfields) OrderedDict([('a', 100)]) >>> marshal(data, mfields, envelope='data') OrderedDict([('data', OrderedDict([('a', 100)]))]) """ def make(cls): if isinstance(cls, type): return cls() return cls if isinstance(data, (list, tuple)): return (OrderedDict([(envelope, [marshal(d, fields) for d in data])]) if envelope else [marshal(d, fields) for d in data]) items = ((k, marshal(data, v) if isinstance(v, dict) else make(v).output(k, data)) for k, v in fields.items()) return OrderedDict([(envelope, OrderedDict(items))]) if envelope else OrderedDict(items) class marshal_with(object): """A decorator that apply marshalling to the return values of your methods. >>> from flask_restful import fields, marshal_with >>> mfields = { 'a': fields.Raw } >>> @marshal_with(mfields) ... def get(): ... return { 'a': 100, 'b': 'foo' } ... ... >>> get() OrderedDict([('a', 100)]) >>> @marshal_with(mfields, envelope='data') ... def get(): ... return { 'a': 100, 'b': 'foo' } ... ... >>> get() OrderedDict([('data', OrderedDict([('a', 100)]))]) see :meth:`flask_restful.marshal` """ def __init__(self, fields, envelope=None): """ :param fields: a dict of whose keys will make up the final serialized response output :param envelope: optional key that will be used to envelop the serialized response """ self.fields = fields self.envelope = envelope def __call__(self, f): @wraps(f) def wrapper(*args, **kwargs): resp = f(*args, **kwargs) if isinstance(resp, tuple): data, code, headers = unpack(resp) return marshal(data, self.fields, self.envelope), code, headers else: return marshal(resp, self.fields, self.envelope) return wrapper class marshal_with_field(object): """ A decorator that formats the return values of your methods with a single field. >>> from flask_restful import marshal_with_field, fields >>> @marshal_with_field(fields.List(fields.Integer)) ... def get(): ... return ['1', 2, 3.0] ... >>> get() [1, 2, 3] see :meth:`flask_restful.marshal_with` """ def __init__(self, field): """ :param field: a single field with which to marshal the output. """ if isinstance(field, type): self.field = field() else: self.field = field def __call__(self, f): @wraps(f) def wrapper(*args, **kwargs): resp = f(*args, **kwargs) if isinstance(resp, tuple): data, code, headers = unpack(resp) return self.field.format(data), code, headers return self.field.format(resp) return wrapper flask-restful-0.3.6/flask_restful/__version__.py000066400000000000000000000000551311353067100220010ustar00rootroot00000000000000#!/usr/bin/env python __version__ = '0.3.6' flask-restful-0.3.6/flask_restful/fields.py000066400000000000000000000313731311353067100207750ustar00rootroot00000000000000from datetime import datetime from calendar import timegm import pytz from decimal import Decimal as MyDecimal, ROUND_HALF_EVEN from email.utils import formatdate import six try: from urlparse import urlparse, urlunparse except ImportError: # python3 from urllib.parse import urlparse, urlunparse from flask_restful import inputs, marshal from flask import url_for, request __all__ = ["String", "FormattedString", "Url", "DateTime", "Float", "Integer", "Arbitrary", "Nested", "List", "Raw", "Boolean", "Fixed", "Price"] class MarshallingException(Exception): """ This is an encapsulating Exception in case of marshalling error. """ def __init__(self, underlying_exception): # just put the contextual representation of the error to hint on what # went wrong without exposing internals super(MarshallingException, self).__init__(six.text_type(underlying_exception)) def is_indexable_but_not_string(obj): return not hasattr(obj, "strip") and hasattr(obj, "__iter__") def get_value(key, obj, default=None): """Helper for pulling a keyed value off various types of objects""" if isinstance(key, int): return _get_value_for_key(key, obj, default) elif callable(key): return key(obj) else: return _get_value_for_keys(key.split('.'), obj, default) def _get_value_for_keys(keys, obj, default): if len(keys) == 1: return _get_value_for_key(keys[0], obj, default) else: return _get_value_for_keys( keys[1:], _get_value_for_key(keys[0], obj, default), default) def _get_value_for_key(key, obj, default): if is_indexable_but_not_string(obj): try: return obj[key] except (IndexError, TypeError, KeyError): pass return getattr(obj, key, default) def to_marshallable_type(obj): """Helper for converting an object to a dictionary only if it is not dictionary already or an indexable object nor a simple type""" if obj is None: return None # make it idempotent for None if hasattr(obj, '__marshallable__'): return obj.__marshallable__() if hasattr(obj, '__getitem__'): return obj # it is indexable it is ok return dict(obj.__dict__) class Raw(object): """Raw provides a base field class from which others should extend. It applies no formatting by default, and should only be used in cases where data does not need to be formatted before being serialized. Fields should throw a :class:`MarshallingException` in case of parsing problem. :param default: The default value for the field, if no value is specified. :param attribute: If the public facing value differs from the internal value, use this to retrieve a different attribute from the response than the publicly named value. """ def __init__(self, default=None, attribute=None): self.attribute = attribute self.default = default def format(self, value): """Formats a field's value. No-op by default - field classes that modify how the value of existing object keys should be presented should override this and apply the appropriate formatting. :param value: The value to format :exception MarshallingException: In case of formatting problem Ex:: class TitleCase(Raw): def format(self, value): return unicode(value).title() """ return value def output(self, key, obj): """Pulls the value for the given key from the object, applies the field's formatting and returns the result. If the key is not found in the object, returns the default value. Field classes that create values which do not require the existence of the key in the object should override this and return the desired value. :exception MarshallingException: In case of formatting problem """ value = get_value(key if self.attribute is None else self.attribute, obj) if value is None: return self.default return self.format(value) class Nested(Raw): """Allows you to nest one set of fields inside another. See :ref:`nested-field` for more information :param dict nested: The dictionary to nest :param bool allow_null: Whether to return None instead of a dictionary with null keys, if a nested dictionary has all-null keys :param kwargs: If ``default`` keyword argument is present, a nested dictionary will be marshaled as its value if nested dictionary is all-null keys (e.g. lets you return an empty JSON object instead of null) """ def __init__(self, nested, allow_null=False, **kwargs): self.nested = nested self.allow_null = allow_null super(Nested, self).__init__(**kwargs) def output(self, key, obj): value = get_value(key if self.attribute is None else self.attribute, obj) if value is None: if self.allow_null: return None elif self.default is not None: return self.default return marshal(value, self.nested) class List(Raw): """ Field for marshalling lists of other fields. See :ref:`list-field` for more information. :param cls_or_instance: The field type the list will contain. """ def __init__(self, cls_or_instance, **kwargs): super(List, self).__init__(**kwargs) error_msg = ("The type of the list elements must be a subclass of " "flask_restful.fields.Raw") if isinstance(cls_or_instance, type): if not issubclass(cls_or_instance, Raw): raise MarshallingException(error_msg) self.container = cls_or_instance() else: if not isinstance(cls_or_instance, Raw): raise MarshallingException(error_msg) self.container = cls_or_instance def format(self, value): # Convert all instances in typed list to container type if isinstance(value, set): value = list(value) return [ self.container.output(idx, val if (isinstance(val, dict) or (self.container.attribute and hasattr(val, self.container.attribute))) and not isinstance(self.container, Nested) and not type(self.container) is Raw else value) for idx, val in enumerate(value) ] def output(self, key, data): value = get_value(key if self.attribute is None else self.attribute, data) # we cannot really test for external dict behavior if is_indexable_but_not_string(value) and not isinstance(value, dict): return self.format(value) if value is None: return self.default return [marshal(value, self.container.nested)] class String(Raw): """ Marshal a value as a string. Uses ``six.text_type`` so values will be converted to :class:`unicode` in python2 and :class:`str` in python3. """ def format(self, value): try: return six.text_type(value) except ValueError as ve: raise MarshallingException(ve) class Integer(Raw): """ Field for outputting an integer value. :param int default: The default value for the field, if no value is specified. """ def __init__(self, default=0, **kwargs): super(Integer, self).__init__(default=default, **kwargs) def format(self, value): try: if value is None: return self.default return int(value) except ValueError as ve: raise MarshallingException(ve) class Boolean(Raw): """ Field for outputting a boolean value. Empty collections such as ``""``, ``{}``, ``[]``, etc. will be converted to ``False``. """ def format(self, value): return bool(value) class FormattedString(Raw): """ FormattedString is used to interpolate other values from the response into this field. The syntax for the source string is the same as the string :meth:`~str.format` method from the python stdlib. Ex:: fields = { 'name': fields.String, 'greeting': fields.FormattedString("Hello {name}") } data = { 'name': 'Doug', } marshal(data, fields) """ def __init__(self, src_str): """ :param string src_str: the string to format with the other values from the response. """ super(FormattedString, self).__init__() self.src_str = six.text_type(src_str) def output(self, key, obj): try: data = to_marshallable_type(obj) return self.src_str.format(**data) except (TypeError, IndexError) as error: raise MarshallingException(error) class Url(Raw): """ A string representation of a Url :param endpoint: Endpoint name. If endpoint is ``None``, ``request.endpoint`` is used instead :type endpoint: str :param absolute: If ``True``, ensures that the generated urls will have the hostname included :type absolute: bool :param scheme: URL scheme specifier (e.g. ``http``, ``https``) :type scheme: str """ def __init__(self, endpoint=None, absolute=False, scheme=None): super(Url, self).__init__() self.endpoint = endpoint self.absolute = absolute self.scheme = scheme def output(self, key, obj): try: data = to_marshallable_type(obj) endpoint = self.endpoint if self.endpoint is not None else request.endpoint o = urlparse(url_for(endpoint, _external=self.absolute, **data)) if self.absolute: scheme = self.scheme if self.scheme is not None else o.scheme return urlunparse((scheme, o.netloc, o.path, "", "", "")) return urlunparse(("", "", o.path, "", "", "")) except TypeError as te: raise MarshallingException(te) class Float(Raw): """ A double as IEEE-754 double precision. ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf -inf """ def format(self, value): try: return float(value) except ValueError as ve: raise MarshallingException(ve) class Arbitrary(Raw): """ A floating point number with an arbitrary precision ex: 634271127864378216478362784632784678324.23432 """ def format(self, value): return six.text_type(MyDecimal(value)) class DateTime(Raw): """ Return a formatted datetime string in UTC. Supported formats are RFC 822 and ISO 8601. See :func:`email.utils.formatdate` for more info on the RFC 822 format. See :meth:`datetime.datetime.isoformat` for more info on the ISO 8601 format. :param dt_format: ``'rfc822'`` or ``'iso8601'`` :type dt_format: str """ def __init__(self, dt_format='rfc822', **kwargs): super(DateTime, self).__init__(**kwargs) self.dt_format = dt_format def format(self, value): try: if self.dt_format == 'rfc822': return _rfc822(value) elif self.dt_format == 'iso8601': return _iso8601(value) else: raise MarshallingException( 'Unsupported date format %s' % self.dt_format ) except AttributeError as ae: raise MarshallingException(ae) ZERO = MyDecimal() class Fixed(Raw): """ A decimal number with a fixed precision. """ def __init__(self, decimals=5, **kwargs): super(Fixed, self).__init__(**kwargs) self.precision = MyDecimal('0.' + '0' * (decimals - 1) + '1') def format(self, value): dvalue = MyDecimal(value) if not dvalue.is_normal() and dvalue != ZERO: raise MarshallingException('Invalid Fixed precision number.') return six.text_type(dvalue.quantize(self.precision, rounding=ROUND_HALF_EVEN)) """Alias for :class:`~fields.Fixed`""" Price = Fixed def _rfc822(dt): """Turn a datetime object into a formatted date. Example:: fields._rfc822(datetime(2011, 1, 1)) => "Sat, 01 Jan 2011 00:00:00 -0000" :param dt: The datetime to transform :type dt: datetime :return: A RFC 822 formatted date string """ return formatdate(timegm(dt.utctimetuple())) def _iso8601(dt): """Turn a datetime object into an ISO8601 formatted date. Example:: fields._iso8601(datetime(2012, 1, 1, 0, 0)) => "2012-01-01T00:00:00" :param dt: The datetime to transform :type dt: datetime :return: A ISO 8601 formatted date string """ return dt.isoformat() flask-restful-0.3.6/flask_restful/inputs.py000066400000000000000000000216321311353067100210460ustar00rootroot00000000000000from calendar import timegm from datetime import datetime, time, timedelta from email.utils import parsedate_tz, mktime_tz import re import aniso8601 import pytz # Constants for upgrading date-based intervals to full datetimes. START_OF_DAY = time(0, 0, 0, tzinfo=pytz.UTC) END_OF_DAY = time(23, 59, 59, 999999, tzinfo=pytz.UTC) # https://code.djangoproject.com/browser/django/trunk/django/core/validators.py # basic auth added by frank url_regex = re.compile( r'^(?:http|ftp)s?://' # http:// or https:// r'(?:[^:@]+?:[^:@]*?@|)' # basic auth r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) def url(value): """Validate a URL. :param string value: The URL to validate :returns: The URL if valid. :raises: ValueError """ if not url_regex.search(value): message = u"{0} is not a valid URL".format(value) if url_regex.search('http://' + value): message += u". Did you mean: http://{0}".format(value) raise ValueError(message) return value class regex(object): """Validate a string based on a regular expression. Example:: parser = reqparse.RequestParser() parser.add_argument('example', type=inputs.regex('^[0-9]+$')) Input to the ``example`` argument will be rejected if it contains anything but numbers. :param pattern: The regular expression the input must match :type pattern: str :param flags: Flags to change expression behavior :type flags: int """ def __init__(self, pattern, flags=0): self.pattern = pattern self.re = re.compile(pattern, flags) def __call__(self, value): if not self.re.search(value): message = 'Value does not match pattern: "{0}"'.format(self.pattern) raise ValueError(message) return value def __deepcopy__(self, memo): return regex(self.pattern) def _normalize_interval(start, end, value): """Normalize datetime intervals. Given a pair of datetime.date or datetime.datetime objects, returns a 2-tuple of tz-aware UTC datetimes spanning the same interval. For datetime.date objects, the returned interval starts at 00:00:00.0 on the first date and ends at 00:00:00.0 on the second. Naive datetimes are upgraded to UTC. Timezone-aware datetimes are normalized to the UTC tzdata. Params: - start: A date or datetime - end: A date or datetime """ if not isinstance(start, datetime): start = datetime.combine(start, START_OF_DAY) end = datetime.combine(end, START_OF_DAY) if start.tzinfo is None: start = pytz.UTC.localize(start) end = pytz.UTC.localize(end) else: start = start.astimezone(pytz.UTC) end = end.astimezone(pytz.UTC) return start, end def _expand_datetime(start, value): if not isinstance(start, datetime): # Expand a single date object to be the interval spanning # that entire day. end = start + timedelta(days=1) else: # Expand a datetime based on the finest resolution provided # in the original input string. time = value.split('T')[1] time_without_offset = re.sub('[+-].+', '', time) num_separators = time_without_offset.count(':') if num_separators == 0: # Hour resolution end = start + timedelta(hours=1) elif num_separators == 1: # Minute resolution: end = start + timedelta(minutes=1) else: # Second resolution end = start + timedelta(seconds=1) return end def _parse_interval(value): """Do some nasty try/except voodoo to get some sort of datetime object(s) out of the string. """ try: return sorted(aniso8601.parse_interval(value)) except ValueError: try: return aniso8601.parse_datetime(value), None except ValueError: return aniso8601.parse_date(value), None def iso8601interval(value, argument='argument'): """Parses ISO 8601-formatted datetime intervals into tuples of datetimes. Accepts both a single date(time) or a full interval using either start/end or start/duration notation, with the following behavior: - Intervals are defined as inclusive start, exclusive end - Single datetimes are translated into the interval spanning the largest resolution not specified in the input value, up to the day. - The smallest accepted resolution is 1 second. - All timezones are accepted as values; returned datetimes are localized to UTC. Naive inputs and date inputs will are assumed UTC. Examples:: "2013-01-01" -> datetime(2013, 1, 1), datetime(2013, 1, 2) "2013-01-01T12" -> datetime(2013, 1, 1, 12), datetime(2013, 1, 1, 13) "2013-01-01/2013-02-28" -> datetime(2013, 1, 1), datetime(2013, 2, 28) "2013-01-01/P3D" -> datetime(2013, 1, 1), datetime(2013, 1, 4) "2013-01-01T12:00/PT30M" -> datetime(2013, 1, 1, 12), datetime(2013, 1, 1, 12, 30) "2013-01-01T06:00/2013-01-01T12:00" -> datetime(2013, 1, 1, 6), datetime(2013, 1, 1, 12) :param str value: The ISO8601 date time as a string :return: Two UTC datetimes, the start and the end of the specified interval :rtype: A tuple (datetime, datetime) :raises: ValueError, if the interval is invalid. """ try: start, end = _parse_interval(value) if end is None: end = _expand_datetime(start, value) start, end = _normalize_interval(start, end, value) except ValueError: raise ValueError( "Invalid {arg}: {value}. {arg} must be a valid ISO8601 " "date/time interval.".format(arg=argument, value=value), ) return start, end def date(value): """Parse a valid looking date in the format YYYY-mm-dd""" date = datetime.strptime(value, "%Y-%m-%d") return date def _get_integer(value): try: return int(value) except (TypeError, ValueError): raise ValueError('{0} is not a valid integer'.format(value)) def natural(value, argument='argument'): """ Restrict input type to the natural numbers (0, 1, 2, 3...) """ value = _get_integer(value) if value < 0: error = ('Invalid {arg}: {value}. {arg} must be a non-negative ' 'integer'.format(arg=argument, value=value)) raise ValueError(error) return value def positive(value, argument='argument'): """ Restrict input type to the positive integers (1, 2, 3...) """ value = _get_integer(value) if value < 1: error = ('Invalid {arg}: {value}. {arg} must be a positive ' 'integer'.format(arg=argument, value=value)) raise ValueError(error) return value class int_range(object): """ Restrict input to an integer in a range (inclusive) """ def __init__(self, low, high, argument='argument'): self.low = low self.high = high self.argument = argument def __call__(self, value): value = _get_integer(value) if value < self.low or value > self.high: error = ('Invalid {arg}: {val}. {arg} must be within the range {lo} - {hi}' .format(arg=self.argument, val=value, lo=self.low, hi=self.high)) raise ValueError(error) return value def boolean(value): """Parse the string ``"true"`` or ``"false"`` as a boolean (case insensitive). Also accepts ``"1"`` and ``"0"`` as ``True``/``False`` (respectively). If the input is from the request JSON body, the type is already a native python boolean, and will be passed through without further parsing. """ if isinstance(value, bool): return value if not value: raise ValueError("boolean type must be non-null") value = value.lower() if value in ('true', '1',): return True if value in ('false', '0',): return False raise ValueError("Invalid literal for boolean(): {0}".format(value)) def datetime_from_rfc822(datetime_str): """Turns an RFC822 formatted date into a datetime object. Example:: inputs.datetime_from_rfc822("Wed, 02 Oct 2002 08:00:00 EST") :param datetime_str: The RFC822-complying string to transform :type datetime_str: str :return: A datetime """ return datetime.fromtimestamp(mktime_tz(parsedate_tz(datetime_str)), pytz.utc) def datetime_from_iso8601(datetime_str): """Turns an ISO8601 formatted date into a datetime object. Example:: inputs.datetime_from_iso8601("2012-01-01T23:30:00+02:00") :param datetime_str: The ISO8601-complying string to transform :type datetime_str: str :return: A datetime """ return aniso8601.parse_datetime(datetime_str) flask-restful-0.3.6/flask_restful/paging.py000066400000000000000000000022671311353067100207740ustar00rootroot00000000000000from flask_restful.utils.crypto import decrypt, encrypt DEFAULT_PAGE_SIZE = 50 def retrieve_next_page(key, seed, args, callback, initial_bookmark=None): """ A helper for the bookmark pager. :param key: the private key of you API :param seed: the crypo seed for this session :param args: the verbatim filtering+paging arguments :param callback: a function that takes (a dictionary of filters, the current bookmark, the page size) and return a tuple (next_results, dictionary_ready_for_next_iteration, approx_number_of_element_left) :param initial_bookmark: pass here an optional initial bookmark for the first request :return: the tuple result_list and new encrypted bookmark """ filter = dict(args) if 'pager_info' in filter: initial_bookmark = decrypt(filter.pop('pager_info'), key, seed) page_size = filter.pop('page_size', DEFAULT_PAGE_SIZE) result_list, new_bookmark, approx_result_size = callback(filter, initial_bookmark, page_size) # restore for the next iteration filter['pager_info'] = encrypt(new_bookmark, key, seed) filter['page_size'] = page_size return result_list, filter, approx_result_size flask-restful-0.3.6/flask_restful/representations/000077500000000000000000000000001311353067100223735ustar00rootroot00000000000000flask-restful-0.3.6/flask_restful/representations/__init__.py000066400000000000000000000000011311353067100244730ustar00rootroot00000000000000 flask-restful-0.3.6/flask_restful/representations/json.py000066400000000000000000000015511311353067100237200ustar00rootroot00000000000000from __future__ import absolute_import from flask import make_response, current_app from flask_restful.utils import PY3 from json import dumps def output_json(data, code, headers=None): """Makes a Flask response with a JSON encoded body""" settings = current_app.config.get('RESTFUL_JSON', {}) # If we're in debug mode, and the indent is not set, we set it to a # reasonable value here. Note that this won't override any existing value # that was set. We also set the "sort_keys" value. if current_app.debug: settings.setdefault('indent', 4) settings.setdefault('sort_keys', not PY3) # always end the json dumps with a new line # see https://github.com/mitsuhiko/flask/pull/1262 dumped = dumps(data, **settings) + "\n" resp = make_response(dumped, code) resp.headers.extend(headers or {}) return resp flask-restful-0.3.6/flask_restful/reqparse.py000066400000000000000000000322431311353067100213460ustar00rootroot00000000000000from copy import deepcopy import collections from flask import current_app, request from werkzeug.datastructures import MultiDict, FileStorage from werkzeug import exceptions import flask_restful import decimal import six class Namespace(dict): def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): self[name] = value _friendly_location = { u'json': u'the JSON body', u'form': u'the post body', u'args': u'the query string', u'values': u'the post body or the query string', u'headers': u'the HTTP headers', u'cookies': u'the request\'s cookies', u'files': u'an uploaded file', } text_type = lambda x: six.text_type(x) class Argument(object): """ :param name: Either a name or a list of option strings, e.g. foo or -f, --foo. :param default: The value produced if the argument is absent from the request. :param dest: The name of the attribute to be added to the object returned by :meth:`~reqparse.RequestParser.parse_args()`. :param bool required: Whether or not the argument may be omitted (optionals only). :param action: The basic type of action to be taken when this argument is encountered in the request. Valid options are "store" and "append". :param ignore: Whether to ignore cases where the argument fails type conversion :param type: The type to which the request argument should be converted. If a type raises an exception, the message in the error will be returned in the response. Defaults to :class:`unicode` in python2 and :class:`str` in python3. :param location: The attributes of the :class:`flask.Request` object to source the arguments from (ex: headers, args, etc.), can be an iterator. The last item listed takes precedence in the result set. :param choices: A container of the allowable values for the argument. :param help: A brief description of the argument, returned in the response when the argument is invalid. May optionally contain an "{error_msg}" interpolation token, which will be replaced with the text of the error raised by the type converter. :param bool case_sensitive: Whether argument values in the request are case sensitive or not (this will convert all values to lowercase) :param bool store_missing: Whether the arguments default value should be stored if the argument is missing from the request. :param bool trim: If enabled, trims whitespace around the argument. :param bool nullable: If enabled, allows null value in argument. """ def __init__(self, name, default=None, dest=None, required=False, ignore=False, type=text_type, location=('json', 'values',), choices=(), action='store', help=None, operators=('=',), case_sensitive=True, store_missing=True, trim=False, nullable=True): self.name = name self.default = default self.dest = dest self.required = required self.ignore = ignore self.location = location self.type = type self.choices = choices self.action = action self.help = help self.case_sensitive = case_sensitive self.operators = operators self.store_missing = store_missing self.trim = trim self.nullable = nullable def source(self, request): """Pulls values off the request in the provided location :param request: The flask request object to parse arguments from """ if isinstance(self.location, six.string_types): value = getattr(request, self.location, MultiDict()) if callable(value): value = value() if value is not None: return value else: values = MultiDict() for l in self.location: value = getattr(request, l, None) if callable(value): value = value() if value is not None: values.update(value) return values return MultiDict() def convert(self, value, op): # Don't cast None if value is None: if self.nullable: return None else: raise ValueError('Must not be null!') # and check if we're expecting a filestorage and haven't overridden `type` # (required because the below instantiation isn't valid for FileStorage) elif isinstance(value, FileStorage) and self.type == FileStorage: return value try: return self.type(value, self.name, op) except TypeError: try: if self.type is decimal.Decimal: return self.type(str(value), self.name) else: return self.type(value, self.name) except TypeError: return self.type(value) def handle_validation_error(self, error, bundle_errors): """Called when an error is raised while parsing. Aborts the request with a 400 status and an error message :param error: the error that was raised :param bundle_errors: do not abort when first error occurs, return a dict with the name of the argument and the error message to be bundled """ error_str = six.text_type(error) error_msg = self.help.format(error_msg=error_str) if self.help else error_str msg = {self.name: error_msg} if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors: return error, msg flask_restful.abort(400, message=msg) def parse(self, request, bundle_errors=False): """Parses argument value(s) from the request, converting according to the argument's type. :param request: The flask request object to parse arguments from :param do not abort when first error occurs, return a dict with the name of the argument and the error message to be bundled """ source = self.source(request) results = [] # Sentinels _not_found = False _found = True for operator in self.operators: name = self.name + operator.replace("=", "", 1) if name in source: # Account for MultiDict and regular dict if hasattr(source, "getlist"): values = source.getlist(name) else: values = source.get(name) if not isinstance(values, collections.MutableSequence): values = [values] for value in values: if hasattr(value, "strip") and self.trim: value = value.strip() if hasattr(value, "lower") and not self.case_sensitive: value = value.lower() if hasattr(self.choices, "__iter__"): self.choices = [choice.lower() for choice in self.choices] try: value = self.convert(value, operator) except Exception as error: if self.ignore: continue return self.handle_validation_error(error, bundle_errors) if self.choices and value not in self.choices: if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors: return self.handle_validation_error( ValueError(u"{0} is not a valid choice".format( value)), bundle_errors) self.handle_validation_error( ValueError(u"{0} is not a valid choice".format( value)), bundle_errors) if name in request.unparsed_arguments: request.unparsed_arguments.pop(name) results.append(value) if not results and self.required: if isinstance(self.location, six.string_types): error_msg = u"Missing required parameter in {0}".format( _friendly_location.get(self.location, self.location) ) else: friendly_locations = [_friendly_location.get(loc, loc) for loc in self.location] error_msg = u"Missing required parameter in {0}".format( ' or '.join(friendly_locations) ) if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors: return self.handle_validation_error(ValueError(error_msg), bundle_errors) self.handle_validation_error(ValueError(error_msg), bundle_errors) if not results: if callable(self.default): return self.default(), _not_found else: return self.default, _not_found if self.action == 'append': return results, _found if self.action == 'store' or len(results) == 1: return results[0], _found return results, _found class RequestParser(object): """Enables adding and parsing of multiple arguments in the context of a single request. Ex:: from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('foo') parser.add_argument('int_bar', type=int) args = parser.parse_args() :param bool trim: If enabled, trims whitespace on all arguments in this parser :param bool bundle_errors: If enabled, do not abort when first error occurs, return a dict with the name of the argument and the error message to be bundled and return all validation errors """ def __init__(self, argument_class=Argument, namespace_class=Namespace, trim=False, bundle_errors=False): self.args = [] self.argument_class = argument_class self.namespace_class = namespace_class self.trim = trim self.bundle_errors = bundle_errors def add_argument(self, *args, **kwargs): """Adds an argument to be parsed. Accepts either a single instance of Argument or arguments to be passed into :class:`Argument`'s constructor. See :class:`Argument`'s constructor for documentation on the available options. """ if len(args) == 1 and isinstance(args[0], self.argument_class): self.args.append(args[0]) else: self.args.append(self.argument_class(*args, **kwargs)) #Do not know what other argument classes are out there if self.trim and self.argument_class is Argument: #enable trim for appended element self.args[-1].trim = kwargs.get('trim', self.trim) return self def parse_args(self, req=None, strict=False): """Parse all arguments from the provided request and return the results as a Namespace :param strict: if req includes args not in parser, throw 400 BadRequest exception """ if req is None: req = request namespace = self.namespace_class() # A record of arguments not yet parsed; as each is found # among self.args, it will be popped out req.unparsed_arguments = dict(self.argument_class('').source(req)) if strict else {} errors = {} for arg in self.args: value, found = arg.parse(req, self.bundle_errors) if isinstance(value, ValueError): errors.update(found) found = None if found or arg.store_missing: namespace[arg.dest or arg.name] = value if errors: flask_restful.abort(400, message=errors) if strict and req.unparsed_arguments: raise exceptions.BadRequest('Unknown arguments: %s' % ', '.join(req.unparsed_arguments.keys())) return namespace def copy(self): """ Creates a copy of this RequestParser with the same set of arguments """ parser_copy = self.__class__(self.argument_class, self.namespace_class) parser_copy.args = deepcopy(self.args) parser_copy.trim = self.trim parser_copy.bundle_errors = self.bundle_errors return parser_copy def replace_argument(self, name, *args, **kwargs): """ Replace the argument matching the given name with a new version. """ new_arg = self.argument_class(name, *args, **kwargs) for index, arg in enumerate(self.args[:]): if new_arg.name == arg.name: del self.args[index] self.args.append(new_arg) break return self def remove_argument(self, name): """ Remove the argument matching the given name. """ for index, arg in enumerate(self.args[:]): if name == arg.name: del self.args[index] break return self flask-restful-0.3.6/flask_restful/utils/000077500000000000000000000000001311353067100203065ustar00rootroot00000000000000flask-restful-0.3.6/flask_restful/utils/__init__.py000066400000000000000000000013171311353067100224210ustar00rootroot00000000000000import sys try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict from werkzeug.http import HTTP_STATUS_CODES PY3 = sys.version_info > (3,) def http_status_message(code): """Maps an HTTP status code to the textual status""" return HTTP_STATUS_CODES.get(code, '') def unpack(value): """Return a three tuple of data, code, and headers""" if not isinstance(value, tuple): return value, 200, {} try: data, code, headers = value return data, code, headers except ValueError: pass try: data, code = value return data, code, {} except ValueError: pass return value, 200, {} flask-restful-0.3.6/flask_restful/utils/cors.py000066400000000000000000000040441311353067100216300ustar00rootroot00000000000000from datetime import timedelta from flask import make_response, request, current_app from functools import update_wrapper def crossdomain(origin=None, methods=None, headers=None, expose_headers=None, max_age=21600, attach_to_all=True, automatic_options=True, credentials=False): """ http://flask.pocoo.org/snippets/56/ """ if methods is not None: methods = ', '.join(sorted(x.upper() for x in methods)) if headers is not None and not isinstance(headers, str): headers = ', '.join(x.upper() for x in headers) if expose_headers is not None and not isinstance(expose_headers, str): expose_headers = ', '.join(x.upper() for x in expose_headers) if not isinstance(origin, str): origin = ', '.join(origin) if isinstance(max_age, timedelta): max_age = max_age.total_seconds() def get_methods(): if methods is not None: return methods options_resp = current_app.make_default_options_response() return options_resp.headers['allow'] def decorator(f): def wrapped_function(*args, **kwargs): if automatic_options and request.method == 'OPTIONS': resp = current_app.make_default_options_response() else: resp = make_response(f(*args, **kwargs)) if not attach_to_all and request.method != 'OPTIONS': return resp h = resp.headers h['Access-Control-Allow-Origin'] = origin h['Access-Control-Allow-Methods'] = get_methods() h['Access-Control-Max-Age'] = str(max_age) if credentials: h['Access-Control-Allow-Credentials'] = 'true' if headers is not None: h['Access-Control-Allow-Headers'] = headers if expose_headers is not None: h['Access-Control-Expose-Headers'] = expose_headers return resp f.provide_automatic_options = False return update_wrapper(wrapped_function, f) return decorator flask-restful-0.3.6/flask_restful/utils/crypto.py000066400000000000000000000017441311353067100222060ustar00rootroot00000000000000import pickle from Crypto.Cipher import AES from base64 import b64encode, b64decode __all__ = "encrypt", "decrypt" BLOCK_SIZE = 16 INTERRUPT = b'\0' # something impossible to put in a string PADDING = b'\1' def pad(data): return data + INTERRUPT + PADDING * (BLOCK_SIZE - (len(data) + 1) % BLOCK_SIZE) def strip(data): return data.rstrip(PADDING).rstrip(INTERRUPT) def create_cipher(key, seed): if len(seed) != 16: raise ValueError("Choose a seed of 16 bytes") if len(key) != 32: raise ValueError("Choose a key of 32 bytes") return AES.new(key, AES.MODE_CBC, seed) def encrypt(plaintext_data, key, seed): plaintext_data = pickle.dumps(plaintext_data, pickle.HIGHEST_PROTOCOL) # whatever you give me I need to be able to restitute it return b64encode(create_cipher(key, seed).encrypt(pad(plaintext_data))) def decrypt(encrypted_data, key, seed): return pickle.loads(strip(create_cipher(key, seed).decrypt(b64decode(encrypted_data)))) flask-restful-0.3.6/scripts/000077500000000000000000000000001311353067100157715ustar00rootroot00000000000000flask-restful-0.3.6/scripts/release.py000066400000000000000000000015601311353067100177650ustar00rootroot00000000000000import argparse import subprocess def point_release(version): parts = version.split('.') parts[-1] = str(int(parts[-1]) + 1) return '.'.join(parts) def main(): parser = argparse.ArgumentParser(description='Bump version number') parser.add_argument('version') args = parser.parse_args() branch = subprocess.check_output(['git', 'branch']).strip() if not "* master" in branch: raise Exception("Must be on master branch to release") if len(subprocess.check_output(["git", "status", "-s"]).strip()) > 0: raise Exception("Uncommitted changes, please commit or stash") subprocess.call(["git", "tag", args.version]) subprocess.call(["git", "push", "origin", "master"]) subprocess.call(["git", "push", "--tags"]) subprocess.call(["python", "setup.py", "sdist", "upload"]) if __name__ == "__main__": main() flask-restful-0.3.6/setup.cfg000066400000000000000000000000371311353067100161230ustar00rootroot00000000000000[bdist_wheel] universal = True flask-restful-0.3.6/setup.py000077500000000000000000000032771311353067100160300ustar00rootroot00000000000000#!/usr/bin/env python import re import sys from os import path from setuptools import setup, find_packages PY26 = sys.version_info[:2] == (2, 6,) requirements = [ 'aniso8601>=0.82', 'Flask>=0.8', 'six>=1.3.0', 'pytz', ] if PY26: requirements.append('ordereddict') version_file = path.join( path.dirname(__file__), 'flask_restful', '__version__.py' ) with open(version_file, 'r') as fp: m = re.search( r"^__version__ = ['\"]([^'\"]*)['\"]", fp.read(), re.M ) version = m.groups(1)[0] setup( name='Flask-RESTful', version=version, license='BSD', url='https://www.github.com/flask-restful/flask-restful/', author='Twilio API Team', author_email='help@twilio.com', description='Simple framework for creating REST APIs', packages=find_packages(exclude=['tests']), classifiers=[ 'Framework :: Flask', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'License :: OSI Approved :: BSD License', ], zip_safe=False, include_package_data=True, platforms='any', test_suite = 'nose.collector', install_requires=requirements, tests_require=['Flask-RESTful[paging]', 'mock>=0.8', 'blinker'], # Install these with "pip install -e '.[paging]'" or '.[docs]' extras_require={ 'paging': 'pycrypto>=2.6', 'docs': 'sphinx', } ) flask-restful-0.3.6/tests/000077500000000000000000000000001311353067100154445ustar00rootroot00000000000000flask-restful-0.3.6/tests/README.md000066400000000000000000000001171311353067100167220ustar00rootroot00000000000000# Running the tests Go to main directory and type: python2 setup.py test flask-restful-0.3.6/tests/__init__.py000066400000000000000000000005241311353067100175560ustar00rootroot00000000000000#!/usr/bin/env python import functools from nose import SkipTest def expected_failure(test): @functools.wraps(test) def inner(*args, **kwargs): try: test(*args, **kwargs) except Exception: raise SkipTest else: raise AssertionError('Failure expected') return inner flask-restful-0.3.6/tests/requirements.txt000066400000000000000000000000511311353067100207240ustar00rootroot00000000000000nose>=1.1.2 mock>=0.8 nosexcover blinker flask-restful-0.3.6/tests/test_accept.py000066400000000000000000000167641311353067100203320ustar00rootroot00000000000000import unittest from flask import Flask import flask_restful from werkzeug import exceptions from nose.tools import assert_equals class AcceptTestCase(unittest.TestCase): def test_accept_default_application_json(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'application/json')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'application/json') def test_accept_no_default_match_acceptable(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'application/json')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'application/json') def test_accept_default_override_accept(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'text/plain')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'application/json') def test_accept_default_any_pick_first(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app) @api.representation('text/plain') def text_rep(data, status_code, headers=None): resp = app.make_response((str(data), status_code, headers)) return resp api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', '*/*')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'application/json') def test_accept_no_default_no_match_not_acceptable(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'text/plain')]) assert_equals(res.status_code, 406) assert_equals(res.content_type, 'application/json') def test_accept_no_default_custom_repr_match(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) api.representations = {} @api.representation('text/plain') def text_rep(data, status_code, headers=None): resp = app.make_response((str(data), status_code, headers)) return resp api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'text/plain')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'text/plain') def test_accept_no_default_custom_repr_not_acceptable(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) api.representations = {} @api.representation('text/plain') def text_rep(data, status_code, headers=None): resp = app.make_response((str(data), status_code, headers)) return resp api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'application/json')]) assert_equals(res.status_code, 406) assert_equals(res.content_type, 'text/plain') def test_accept_no_default_match_q0_not_acceptable(self): """ q=0 should be considered NotAcceptable, but this depends on werkzeug >= 1.0 which is not yet released so this test is expected to fail until we depend on werkzeug >= 1.0 """ class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'application/json; q=0')]) assert_equals(res.status_code, 406) assert_equals(res.content_type, 'application/json') def test_accept_no_default_accept_highest_quality_of_two(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) @api.representation('text/plain') def text_rep(data, status_code, headers=None): resp = app.make_response((str(data), status_code, headers)) return resp api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'application/json; q=0.1, text/plain; q=1.0')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'text/plain') def test_accept_no_default_accept_highest_quality_of_three(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) @api.representation('text/html') @api.representation('text/plain') def text_rep(data, status_code, headers=None): resp = app.make_response((str(data), status_code, headers)) return resp api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'application/json; q=0.1, text/plain; q=0.3, text/html; q=0.2')]) assert_equals(res.status_code, 200) assert_equals(res.content_type, 'text/plain') def test_accept_no_default_no_representations(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype=None) api.representations = {} api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'text/plain')]) assert_equals(res.status_code, 406) assert_equals(res.content_type, 'text/plain') def test_accept_invalid_default_no_representations(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app, default_mediatype='nonexistant/mediatype') api.representations = {} api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/', headers=[('Accept', 'text/plain')]) assert_equals(res.status_code, 500) flask-restful-0.3.6/tests/test_api.py000066400000000000000000001115451311353067100176350ustar00rootroot00000000000000import unittest import json from flask import Flask, Blueprint, redirect, views, abort as flask_abort from flask.signals import got_request_exception, signals_available try: from mock import Mock except: # python3 from unittest.mock import Mock import flask import werkzeug from werkzeug.exceptions import HTTPException, Unauthorized, BadRequest, NotFound, _aborter from werkzeug.http import quote_etag, unquote_etag from flask_restful.utils import http_status_message, unpack import flask_restful import flask_restful.fields from flask_restful import OrderedDict from json import dumps, loads, JSONEncoder #noinspection PyUnresolvedReferences from nose.tools import assert_equals, assert_true, assert_false # you need it for tests in form of continuations import six def check_unpack(expected, value): assert_equals(expected, value) def test_unpack(): yield check_unpack, ("hey", 200, {}), unpack("hey") yield check_unpack, (("hey",), 200, {}), unpack(("hey",)) yield check_unpack, ("hey", 201, {}), unpack(("hey", 201)) yield check_unpack, ("hey", 201, "foo"), unpack(("hey", 201, "foo")) yield check_unpack, (["hey", 201], 200, {}), unpack(["hey", 201]) # Add a dummy Resource to verify that the app is properly set. class HelloWorld(flask_restful.Resource): def get(self): return {} class APITestCase(unittest.TestCase): def test_http_code(self): self.assertEquals(http_status_message(200), 'OK') self.assertEquals(http_status_message(404), 'Not Found') def test_unauthorized_no_challenge_by_default(self): app = Flask(__name__) api = flask_restful.Api(app) response = Mock() response.headers = {} with app.test_request_context('/foo'): response = api.unauthorized(response) assert_false('WWW-Authenticate' in response.headers) def test_unauthorized(self): app = Flask(__name__) api = flask_restful.Api(app, serve_challenge_on_401=True) response = Mock() response.headers = {} with app.test_request_context('/foo'): response = api.unauthorized(response) self.assertEquals(response.headers['WWW-Authenticate'], 'Basic realm="flask-restful"') def test_unauthorized_custom_realm(self): app = Flask(__name__) app.config['HTTP_BASIC_AUTH_REALM'] = 'Foo' api = flask_restful.Api(app, serve_challenge_on_401=True) response = Mock() response.headers = {} with app.test_request_context('/foo'): response = api.unauthorized(response) self.assertEquals(response.headers['WWW-Authenticate'], 'Basic realm="Foo"') def test_handle_error_401_no_challenge_by_default(self): app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context('/foo'): resp = api.handle_error(Unauthorized()) self.assertEquals(resp.status_code, 401) assert_false('WWW-Autheneticate' in resp.headers) def test_handle_error_401_sends_challege_default_realm(self): app = Flask(__name__) api = flask_restful.Api(app, serve_challenge_on_401=True) exception = HTTPException() exception.code = 401 exception.data = {'foo': 'bar'} with app.test_request_context('/foo'): resp = api.handle_error(exception) self.assertEquals(resp.status_code, 401) self.assertEquals(resp.headers['WWW-Authenticate'], 'Basic realm="flask-restful"') def test_handle_error_401_sends_challege_configured_realm(self): app = Flask(__name__) app.config['HTTP_BASIC_AUTH_REALM'] = 'test-realm' api = flask_restful.Api(app, serve_challenge_on_401=True) with app.test_request_context('/foo'): resp = api.handle_error(Unauthorized()) self.assertEquals(resp.status_code, 401) self.assertEquals(resp.headers['WWW-Authenticate'], 'Basic realm="test-realm"') def test_handle_error_does_not_swallow_exceptions(self): app = Flask(__name__) api = flask_restful.Api(app) exception = BadRequest('x') with app.test_request_context('/foo'): resp = api.handle_error(exception) self.assertEquals(resp.status_code, 400) self.assertEquals(resp.get_data(), b'{"message": "x"}\n') def test_marshal(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) marshal_dict = OrderedDict([('foo', 'bar'), ('bat', 'baz')]) output = flask_restful.marshal(marshal_dict, fields) self.assertEquals(output, {'foo': 'bar'}) def test_marshal_with_envelope(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) marshal_dict = OrderedDict([('foo', 'bar'), ('bat', 'baz')]) output = flask_restful.marshal(marshal_dict, fields, envelope='hey') self.assertEquals(output, {'hey': {'foo': 'bar'}}) def test_marshal_decorator(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) @flask_restful.marshal_with(fields) def try_me(): return OrderedDict([('foo', 'bar'), ('bat', 'baz')]) self.assertEquals(try_me(), {'foo': 'bar'}) def test_marshal_decorator_with_envelope(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) @flask_restful.marshal_with(fields, envelope='hey') def try_me(): return OrderedDict([('foo', 'bar'), ('bat', 'baz')]) self.assertEquals(try_me(), {'hey': {'foo': 'bar'}}) def test_marshal_decorator_tuple(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) @flask_restful.marshal_with(fields) def try_me(): return OrderedDict([('foo', 'bar'), ('bat', 'baz')]), 200, {'X-test': 123} self.assertEquals(try_me(), ({'foo': 'bar'}, 200, {'X-test': 123})) def test_marshal_decorator_tuple_with_envelope(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) @flask_restful.marshal_with(fields, envelope='hey') def try_me(): return OrderedDict([('foo', 'bar'), ('bat', 'baz')]), 200, {'X-test': 123} self.assertEquals(try_me(), ({'hey': {'foo': 'bar'}}, 200, {'X-test': 123})) def test_marshal_field_decorator(self): field = flask_restful.fields.Raw @flask_restful.marshal_with_field(field) def try_me(): return 'foo' self.assertEquals(try_me(), 'foo') def test_marshal_field_decorator_tuple(self): field = flask_restful.fields.Raw @flask_restful.marshal_with_field(field) def try_me(): return 'foo', 200, {'X-test': 123} self.assertEquals(('foo', 200, {'X-test': 123}), try_me()) def test_marshal_field(self): fields = OrderedDict({'foo': flask_restful.fields.Raw()}) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz')]) output = flask_restful.marshal(marshal_fields, fields) self.assertEquals(output, {'foo': 'bar'}) def test_marshal_tuple(self): fields = OrderedDict({'foo': flask_restful.fields.Raw}) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz')]) output = flask_restful.marshal((marshal_fields,), fields) self.assertEquals(output, [{'foo': 'bar'}]) def test_marshal_tuple_with_envelope(self): fields = OrderedDict({'foo': flask_restful.fields.Raw}) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz')]) output = flask_restful.marshal((marshal_fields,), fields, envelope='hey') self.assertEquals(output, {'hey': [{'foo': 'bar'}]}) def test_marshal_nested(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.Nested({ 'fye': flask_restful.fields.String, })) ]) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', {'fye': 'fum'})]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'bar'), ('fee', OrderedDict([('fye', 'fum')]))]) self.assertEquals(output, expected) def test_marshal_nested_with_non_null(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.Nested( OrderedDict([ ('fye', flask_restful.fields.String), ('blah', flask_restful.fields.String) ]), allow_null=False)) ]) marshal_fields = [OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', None)])] output = flask_restful.marshal(marshal_fields, fields) expected = [OrderedDict([('foo', 'bar'), ('fee', OrderedDict([('fye', None), ('blah', None)]))])] self.assertEquals(output, expected) def test_marshal_nested_with_null(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.Nested( OrderedDict([ ('fye', flask_restful.fields.String), ('blah', flask_restful.fields.String) ]), allow_null=True)) ]) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', None)]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'bar'), ('fee', None)]) self.assertEquals(output, expected) def test_allow_null_presents_data(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.Nested( OrderedDict([ ('fye', flask_restful.fields.String), ('blah', flask_restful.fields.String) ]), allow_null=True)) ]) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', {'blah': 'cool'})]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'bar'), ('fee', OrderedDict([('fye', None), ('blah', 'cool')]))]) self.assertEquals(output, expected) def test_marshal_nested_property(self): class TestObject(object): @property def fee(self): return {'blah': 'cool'} fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.Nested( OrderedDict([ ('fye', flask_restful.fields.String), ('blah', flask_restful.fields.String) ]), allow_null=True)) ]) obj = TestObject() obj.foo = 'bar' obj.bat = 'baz' output = flask_restful.marshal([obj], fields) expected = [OrderedDict([('foo', 'bar'), ('fee', OrderedDict([('fye', None), ('blah', 'cool')]))])] self.assertEquals(output, expected) def test_marshal_list(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.List(flask_restful.fields.String)) ]) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', ['fye', 'fum'])]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'bar'), ('fee', (['fye', 'fum']))]) self.assertEquals(output, expected) def test_marshal_list_of_nesteds(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.List(flask_restful.fields.Nested({ 'fye': flask_restful.fields.String }))) ]) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', {'fye': 'fum'})]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'bar'), ('fee', [OrderedDict([('fye', 'fum')])])]) self.assertEquals(output, expected) def test_marshal_list_of_lists(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('fee', flask_restful.fields.List(flask_restful.fields.List( flask_restful.fields.String))) ]) marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', [['fye'], ['fum']])]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'bar'), ('fee', [['fye'], ['fum']])]) self.assertEquals(output, expected) def test_marshal_nested_dict(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), ('bar', OrderedDict([ ('a', flask_restful.fields.Raw), ('b', flask_restful.fields.Raw), ])), ]) marshal_fields = OrderedDict([('foo', 'foo-val'), ('bar', 'bar-val'), ('bat', 'bat-val'), ('a', 1), ('b', 2), ('c', 3)]) output = flask_restful.marshal(marshal_fields, fields) expected = OrderedDict([('foo', 'foo-val'), ('bar', OrderedDict([('a', 1), ('b', 2)]))]) self.assertEquals(output, expected) def test_api_representation(self): app = Mock() api = flask_restful.Api(app) @api.representation('foo') def foo(): pass self.assertEquals(api.representations['foo'], foo) def test_api_base(self): app = Mock() app.configure_mock(**{'record.side_effect': AttributeError}) api = flask_restful.Api(app) self.assertEquals(api.urls, {}) self.assertEquals(api.prefix, '') self.assertEquals(api.default_mediatype, 'application/json') def test_api_delayed_initialization(self): app = Flask(__name__) api = flask_restful.Api() api.add_resource(HelloWorld, '/', endpoint="hello") api.init_app(app) with app.test_client() as client: self.assertEquals(client.get('/').status_code, 200) def test_api_prefix(self): app = Mock() app.configure_mock(**{'record.side_effect': AttributeError}) api = flask_restful.Api(app, prefix='/foo') self.assertEquals(api.prefix, '/foo') def test_handle_server_error(self): app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo"): resp = api.handle_error(Exception()) self.assertEquals(resp.status_code, 500) self.assertEquals(resp.data.decode(), dumps({ "message": "Internal Server Error" }) + "\n") def test_handle_error_with_code(self): app = Flask(__name__) api = flask_restful.Api(app, serve_challenge_on_401=True) exception = Exception() exception.code = "Not an integer" exception.data = {'foo': 'bar'} with app.test_request_context("/foo"): resp = api.handle_error(exception) self.assertEquals(resp.status_code, 500) self.assertEquals(resp.data.decode(), dumps({"foo": "bar"}) + "\n") def test_handle_auth(self): app = Flask(__name__) api = flask_restful.Api(app, serve_challenge_on_401=True) with app.test_request_context("/foo"): resp = api.handle_error(Unauthorized()) self.assertEquals(resp.status_code, 401) expected_data = dumps({'message': Unauthorized.description}) + "\n" self.assertEquals(resp.data.decode(), expected_data) self.assertTrue('WWW-Authenticate' in resp.headers) def test_handle_api_error(self): app = Flask(__name__) api = flask_restful.Api(app) class Test(flask_restful.Resource): def get(self): flask.abort(404) api.add_resource(Test(), '/api', endpoint='api') app = app.test_client() resp = app.get("/api") assert_equals(resp.status_code, 404) assert_equals('application/json', resp.headers['Content-Type']) data = loads(resp.data.decode()) assert_true('message' in data) def test_handle_non_api_error(self): app = Flask(__name__) flask_restful.Api(app) app = app.test_client() resp = app.get("/foo") self.assertEquals(resp.status_code, 404) self.assertEquals('text/html', resp.headers['Content-Type']) def test_non_api_error_404_catchall(self): app = Flask(__name__) api = flask_restful.Api(app, catch_all_404s=True) app = app.test_client() resp = app.get("/foo") self.assertEquals(api.default_mediatype, resp.headers['Content-Type']) def test_handle_error_signal(self): if not signals_available: # This test requires the blinker lib to run. print("Can't test signals without signal support") return app = Flask(__name__) api = flask_restful.Api(app) exception = BadRequest() recorded = [] def record(sender, exception): recorded.append(exception) got_request_exception.connect(record, app) try: with app.test_request_context("/foo"): api.handle_error(exception) self.assertEquals(len(recorded), 1) self.assertTrue(exception is recorded[0]) finally: got_request_exception.disconnect(record, app) def test_handle_error(self): app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo"): resp = api.handle_error(BadRequest()) self.assertEquals(resp.status_code, 400) self.assertEquals(resp.data.decode(), dumps({ 'message': BadRequest.description, }) + "\n") def test_handle_smart_errors(self): app = Flask(__name__) api = flask_restful.Api(app) view = flask_restful.Resource api.add_resource(view, '/foo', endpoint='bor') api.add_resource(view, '/fee', endpoint='bir') api.add_resource(view, '/fii', endpoint='ber') with app.test_request_context("/faaaaa"): resp = api.handle_error(NotFound()) self.assertEquals(resp.status_code, 404) self.assertEquals(resp.data.decode(), dumps({ "message": NotFound.description, }) + "\n") with app.test_request_context("/fOo"): resp = api.handle_error(NotFound()) self.assertEquals(resp.status_code, 404) self.assertTrue('did you mean /foo ?' in resp.data.decode()) app.config['ERROR_404_HELP'] = False with app.test_request_context("/fOo"): resp = api.handle_error(NotFound()) self.assertEquals(resp.status_code, 404) self.assertEquals(resp.data.decode(), dumps({ "message": NotFound.description }) + "\n") def test_error_router_falls_back_to_original(self): """Verify that if an exception occurs in the Flask-RESTful error handler, the error_router will call the original flask error handler instead. """ app = Flask(__name__) api = flask_restful.Api(app) app.handle_exception = Mock() api.handle_error = Mock(side_effect=Exception()) api._has_fr_route = Mock(return_value=True) exception = Mock(spec=HTTPException) with app.test_request_context('/foo'): api.error_router(exception, app.handle_exception) self.assertTrue(app.handle_exception.called_with(exception)) def test_media_types(self): app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo", headers={ 'Accept': 'application/json' }): self.assertEquals(api.mediatypes(), ['application/json']) def test_media_types_method(self): app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo", headers={ 'Accept': 'application/xml; q=.5' }): self.assertEquals(api.mediatypes_method()(Mock()), ['application/xml', 'application/json']) def test_media_types_q(self): app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo", headers={ 'Accept': 'application/json; q=1, application/xml; q=.5' }): self.assertEquals(api.mediatypes(), ['application/json', 'application/xml']) def test_decorator(self): def return_zero(func): return 0 app = Mock(flask.Flask) app.view_functions = {} view = Mock() api = flask_restful.Api(app) api.decorators.append(return_zero) api.output = Mock() api.add_resource(view, '/foo', endpoint='bar') app.add_url_rule.assert_called_with('/foo', view_func=0) def test_add_resource_endpoint(self): app = Mock() app.view_functions = {} view = Mock() api = flask_restful.Api(app) api.output = Mock() api.add_resource(view, '/foo', endpoint='bar') view.as_view.assert_called_with('bar') def test_add_two_conflicting_resources_on_same_endpoint(self): app = Flask(__name__) api = flask_restful.Api(app) class Foo1(flask_restful.Resource): def get(self): return 'foo1' class Foo2(flask_restful.Resource): def get(self): return 'foo2' api.add_resource(Foo1, '/foo', endpoint='bar') self.assertRaises(ValueError, api.add_resource, Foo2, '/foo/toto', endpoint='bar') def test_add_the_same_resource_on_same_endpoint(self): app = Flask(__name__) api = flask_restful.Api(app) class Foo1(flask_restful.Resource): def get(self): return 'foo1' api.add_resource(Foo1, '/foo', endpoint='bar') api.add_resource(Foo1, '/foo/toto', endpoint='blah') with app.test_client() as client: foo1 = client.get('/foo') self.assertEquals(foo1.data, b'"foo1"\n') foo2 = client.get('/foo/toto') self.assertEquals(foo2.data, b'"foo1"\n') def test_add_resource(self): app = Mock(flask.Flask) app.view_functions = {} api = flask_restful.Api(app) api.output = Mock() api.add_resource(views.MethodView, '/foo') app.add_url_rule.assert_called_with('/foo', view_func=api.output()) def test_resource_decorator(self): app = Mock(flask.Flask) app.view_functions = {} api = flask_restful.Api(app) api.output = Mock() @api.resource('/foo', endpoint='bar') class Foo(flask_restful.Resource): pass app.add_url_rule.assert_called_with('/foo', view_func=api.output()) def test_add_resource_kwargs(self): app = Mock(flask.Flask) app.view_functions = {} api = flask_restful.Api(app) api.output = Mock() api.add_resource(views.MethodView, '/foo', defaults={"bar": "baz"}) app.add_url_rule.assert_called_with('/foo', view_func=api.output(), defaults={"bar": "baz"}) def test_add_resource_forward_resource_class_parameters(self): app = Flask(__name__) api = flask_restful.Api(app) class Foo(flask_restful.Resource): def __init__(self, *args, **kwargs): self.one = args[0] self.two = kwargs['secret_state'] def get(self): return "{0} {1}".format(self.one, self.two) api.add_resource(Foo, '/foo', resource_class_args=('wonderful',), resource_class_kwargs={'secret_state': 'slurm'}) with app.test_client() as client: foo = client.get('/foo') self.assertEquals(foo.data, b'"wonderful slurm"\n') def test_output_unpack(self): def make_empty_response(): return {'foo': 'bar'} app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo"): wrapper = api.output(make_empty_response) resp = wrapper() self.assertEquals(resp.status_code, 200) self.assertEquals(resp.data.decode(), '{"foo": "bar"}\n') def test_output_func(self): def make_empty_resposne(): return flask.make_response('') app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context("/foo"): wrapper = api.output(make_empty_resposne) resp = wrapper() self.assertEquals(resp.status_code, 200) self.assertEquals(resp.data.decode(), '') def test_resource(self): app = Flask(__name__) resource = flask_restful.Resource() resource.get = Mock() with app.test_request_context("/foo"): resource.dispatch_request() def test_resource_resp(self): app = Flask(__name__) resource = flask_restful.Resource() resource.get = Mock() with app.test_request_context("/foo"): resource.get.return_value = flask.make_response('') resource.dispatch_request() def test_resource_text_plain(self): app = Flask(__name__) def text(data, code, headers=None): return flask.make_response(six.text_type(data)) class Foo(flask_restful.Resource): representations = { 'text/plain': text, } def get(self): return 'hello' with app.test_request_context("/foo", headers={'Accept': 'text/plain'}): resource = Foo() resp = resource.dispatch_request() self.assertEquals(resp.data.decode(), 'hello') def test_resource_error(self): app = Flask(__name__) resource = flask_restful.Resource() with app.test_request_context("/foo"): self.assertRaises(AssertionError, lambda: resource.dispatch_request()) def test_resource_head(self): app = Flask(__name__) resource = flask_restful.Resource() with app.test_request_context("/foo", method="HEAD"): self.assertRaises(AssertionError, lambda: resource.dispatch_request()) def test_abort_data(self): try: flask_restful.abort(404, foo='bar') assert False # We should never get here except Exception as e: self.assertEquals(e.data, {'foo': 'bar'}) def test_abort_no_data(self): try: flask_restful.abort(404) assert False # We should never get here except Exception as e: self.assertEquals(False, hasattr(e, "data")) def test_abort_custom_message(self): try: flask_restful.abort(404, message="no user") assert False # We should never get here except Exception as e: assert_equals(e.data['message'], "no user") def test_abort_type(self): self.assertRaises(HTTPException, lambda: flask_restful.abort(404)) def test_endpoints(self): app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(HelloWorld, '/ids/', endpoint="hello") with app.test_request_context('/foo'): self.assertFalse(api._has_fr_route()) with app.test_request_context('/ids/3'): self.assertTrue(api._has_fr_route()) def test_url_for(self): app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(HelloWorld, '/ids/') with app.test_request_context('/foo'): self.assertEqual(api.url_for(HelloWorld, id=123), '/ids/123') def test_url_for_with_blueprint(self): """Verify that url_for works when an Api object is mounted on a Blueprint. """ api_bp = Blueprint('api', __name__) app = Flask(__name__) api = flask_restful.Api(api_bp) api.add_resource(HelloWorld, '/foo/') app.register_blueprint(api_bp) with app.test_request_context('/foo'): self.assertEqual(api.url_for(HelloWorld, bar='baz'), '/foo/baz') def test_fr_405(self): app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(HelloWorld, '/ids/', endpoint="hello") app = app.test_client() resp = app.post('/ids/3') self.assertEquals(resp.status_code, 405) self.assertEquals(resp.content_type, api.default_mediatype) # Allow can be of the form 'GET, PUT, POST' allow = ', '.join(set(resp.headers.get_all('Allow'))) allow = set(method.strip() for method in allow.split(',')) self.assertEquals(allow, set(['HEAD', 'OPTIONS'] + HelloWorld.methods)) def test_exception_header_forwarded(self): """Test that HTTPException's headers are extended properly""" app = Flask(__name__) app.config['DEBUG'] = True api = flask_restful.Api(app) class NotModified(HTTPException): code = 304 def __init__(self, etag, *args, **kwargs): super(NotModified, self).__init__(*args, **kwargs) self.etag = quote_etag(etag) def get_headers(self, *args, **kwargs): """Get a list of headers.""" return [('ETag', self.etag)] class Foo1(flask_restful.Resource): def get(self): flask_abort(304, etag='myETag') api.add_resource(Foo1, '/foo') _aborter.mapping.update({304: NotModified}) with app.test_client() as client: foo = client.get('/foo') self.assertEquals(foo.get_etag(), unquote_etag(quote_etag('myETag'))) def test_exception_header_forwarding_doesnt_duplicate_headers(self): """Test that HTTPException's headers do not add a duplicate Content-Length header https://github.com/flask-restful/flask-restful/issues/534 """ app = Flask(__name__) api = flask_restful.Api(app) with app.test_request_context('/'): r = api.handle_error(BadRequest()) self.assertEqual(len(r.headers.getlist('Content-Length')), 1) def test_will_prettyprint_json_in_debug_mode(self): app = Flask(__name__) app.config['DEBUG'] = True api = flask_restful.Api(app) class Foo1(flask_restful.Resource): def get(self): return {'foo': 'bar', 'baz': 'asdf'} api.add_resource(Foo1, '/foo', endpoint='bar') with app.test_client() as client: foo = client.get('/foo') # Python's dictionaries have random order (as of "new" Pythons, # anyway), so we can't verify the actual output here. We just # assert that they're properly prettyprinted. lines = foo.data.splitlines() lines = [line.decode() for line in lines] self.assertEquals("{", lines[0]) self.assertTrue(lines[1].startswith(' ')) self.assertTrue(lines[2].startswith(' ')) self.assertEquals("}", lines[3]) # Assert our trailing newline. self.assertTrue(foo.data.endswith(b'\n')) def test_read_json_settings_from_config(self): class TestConfig(object): RESTFUL_JSON = {'indent': 2, 'sort_keys': True, 'separators': (', ', ': ')} app = Flask(__name__) app.config.from_object(TestConfig) api = flask_restful.Api(app) class Foo(flask_restful.Resource): def get(self): return {'foo': 'bar', 'baz': 'qux'} api.add_resource(Foo, '/foo') with app.test_client() as client: data = client.get('/foo').data expected = b'{\n "baz": "qux", \n "foo": "bar"\n}\n' self.assertEquals(data, expected) def test_use_custom_jsonencoder(self): class CabageEncoder(JSONEncoder): def default(self, obj): return 'cabbage' class TestConfig(object): RESTFUL_JSON = {'cls': CabageEncoder} app = Flask(__name__) app.config.from_object(TestConfig) api = flask_restful.Api(app) class Cabbage(flask_restful.Resource): def get(self): return {'frob': object()} api.add_resource(Cabbage, '/cabbage') with app.test_client() as client: data = client.get('/cabbage').data expected = b'{"frob": "cabbage"}\n' self.assertEquals(data, expected) def test_json_with_no_settings(self): app = Flask(__name__) api = flask_restful.Api(app) class Foo(flask_restful.Resource): def get(self): return {'foo': 'bar'} api.add_resource(Foo, '/foo') with app.test_client() as client: data = client.get('/foo').data expected = b'{"foo": "bar"}\n' self.assertEquals(data, expected) def test_redirect(self): app = Flask(__name__) api = flask_restful.Api(app) class FooResource(flask_restful.Resource): def get(self): return redirect('/') api.add_resource(FooResource, '/api') app = app.test_client() resp = app.get('/api') self.assertEquals(resp.status_code, 302) self.assertEquals(resp.headers['Location'], 'http://localhost/') def test_json_float_marshalled(self): app = Flask(__name__) api = flask_restful.Api(app) class FooResource(flask_restful.Resource): fields = {'foo': flask_restful.fields.Float} def get(self): return flask_restful.marshal({"foo": 3.0}, self.fields) api.add_resource(FooResource, '/api') app = app.test_client() resp = app.get('/api') self.assertEquals(resp.status_code, 200) self.assertEquals(resp.data.decode('utf-8'), '{"foo": 3.0}\n') def test_custom_error_message(self): errors = { 'FooError': { 'message': "api is foobar", 'status': 418, } } class FooError(ValueError): pass app = Flask(__name__) api = flask_restful.Api(app, errors=errors) exception = FooError() exception.code = 400 exception.data = {'message': 'FooError'} with app.test_request_context("/foo"): resp = api.handle_error(exception) self.assertEquals(resp.status_code, 418) self.assertEqual(loads(resp.data.decode('utf8')), {"message": "api is foobar", "status": 418}) def test_calling_owns_endpoint_before_api_init(self): api = flask_restful.Api() try: api.owns_endpoint('endpoint') except AttributeError as ae: self.fail(ae.message) def test_selectively_apply_method_decorators(self): def upper_deco(f): def upper(*args, **kwargs): return f(*args, **kwargs).upper() return upper class TestResource(flask_restful.Resource): method_decorators = {'get': [upper_deco]} def get(self): return 'get test' def post(self): return 'post test' app = Flask(__name__) with app.test_request_context('/', method='POST'): r = TestResource().dispatch_request() assert r == 'post test' with app.test_request_context('/', method='GET'): r = TestResource().dispatch_request() assert r == 'GET TEST' def test_apply_all_method_decorators_if_not_mapping(self): def upper_deco(f): def upper(*args, **kwargs): return f(*args, **kwargs).upper() return upper class TestResource(flask_restful.Resource): method_decorators = [upper_deco] def get(self): return 'get test' def post(self): return 'post test' app = Flask(__name__) with app.test_request_context('/', method='POST'): r = TestResource().dispatch_request() assert r == 'POST TEST' with app.test_request_context('/', method='GET'): r = TestResource().dispatch_request() assert r == 'GET TEST' def test_decorators_only_applied_at_dispatch(self): def upper_deco(f): def upper(*args, **kwargs): return f(*args, **kwargs).upper() return upper class TestResource(flask_restful.Resource): method_decorators = [upper_deco] def get(self): return 'get test' def post(self): return 'post test' r = TestResource() assert r.get() == 'get test' assert r.post() == 'post test' if __name__ == '__main__': unittest.main() flask-restful-0.3.6/tests/test_api_with_blueprint.py000066400000000000000000000174671311353067100227640ustar00rootroot00000000000000import unittest from flask import Flask, Blueprint, request try: from mock import Mock except: # python3 from unittest.mock import Mock import flask import flask_restful import flask_restful.fields #noinspection PyUnresolvedReferences from nose.tools import assert_true, assert_false # you need it for tests in form of continuations # Add a dummy Resource to verify that the app is properly set. class HelloWorld(flask_restful.Resource): def get(self): return {} class GoodbyeWorld(flask_restful.Resource): def __init__(self, err): self.err = err def get(self): flask.abort(self.err) class APIWithBlueprintTestCase(unittest.TestCase): def test_api_base(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) app = Flask(__name__) app.register_blueprint(blueprint) self.assertEquals(api.urls, {}) self.assertEquals(api.prefix, '') self.assertEquals(api.default_mediatype, 'application/json') def test_api_delayed_initialization(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api() api.init_app(blueprint) app = Flask(__name__) app.register_blueprint(blueprint) api.add_resource(HelloWorld, '/', endpoint="hello") def test_add_resource_endpoint(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) view = Mock(**{'as_view.return_value': Mock(__name__='test_view')}) api.add_resource(view, '/foo', endpoint='bar') app = Flask(__name__) app.register_blueprint(blueprint) view.as_view.assert_called_with('bar') def test_add_resource_endpoint_after_registration(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) app = Flask(__name__) app.register_blueprint(blueprint) view = Mock(**{'as_view.return_value': Mock(__name__='test_view')}) api.add_resource(view, '/foo', endpoint='bar') view.as_view.assert_called_with('bar') def test_url_with_api_prefix(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint, prefix='/api') api.add_resource(HelloWorld, '/hi', endpoint='hello') app = Flask(__name__) app.register_blueprint(blueprint) with app.test_request_context('/api/hi'): self.assertEquals(request.endpoint, 'test.hello') def test_url_with_blueprint_prefix(self): blueprint = Blueprint('test', __name__, url_prefix='/bp') api = flask_restful.Api(blueprint) api.add_resource(HelloWorld, '/hi', endpoint='hello') app = Flask(__name__) app.register_blueprint(blueprint) with app.test_request_context('/bp/hi'): self.assertEquals(request.endpoint, 'test.hello') def test_url_with_registration_prefix(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) api.add_resource(HelloWorld, '/hi', endpoint='hello') app = Flask(__name__) app.register_blueprint(blueprint, url_prefix='/reg') with app.test_request_context('/reg/hi'): self.assertEquals(request.endpoint, 'test.hello') def test_registration_prefix_overrides_blueprint_prefix(self): blueprint = Blueprint('test', __name__, url_prefix='/bp') api = flask_restful.Api(blueprint) api.add_resource(HelloWorld, '/hi', endpoint='hello') app = Flask(__name__) app.register_blueprint(blueprint, url_prefix='/reg') with app.test_request_context('/reg/hi'): self.assertEquals(request.endpoint, 'test.hello') def test_url_with_api_and_blueprint_prefix(self): blueprint = Blueprint('test', __name__, url_prefix='/bp') api = flask_restful.Api(blueprint, prefix='/api') api.add_resource(HelloWorld, '/hi', endpoint='hello') app = Flask(__name__) app.register_blueprint(blueprint) with app.test_request_context('/bp/api/hi'): self.assertEquals(request.endpoint, 'test.hello') def test_url_part_order_aeb(self): blueprint = Blueprint('test', __name__, url_prefix='/bp') api = flask_restful.Api(blueprint, prefix='/api', url_part_order='aeb') api.add_resource(HelloWorld, '/hi', endpoint='hello') app = Flask(__name__) app.register_blueprint(blueprint) with app.test_request_context('/api/hi/bp'): self.assertEquals(request.endpoint, 'test.hello') def test_error_routing(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) api.add_resource(HelloWorld(), '/hi', endpoint="hello") api.add_resource(GoodbyeWorld(404), '/bye', endpoint="bye") app = Flask(__name__) app.register_blueprint(blueprint) with app.test_request_context('/hi', method='POST'): assert_true(api._should_use_fr_error_handler()) assert_true(api._has_fr_route()) with app.test_request_context('/bye'): api._should_use_fr_error_handler = Mock(return_value=False) assert_true(api._has_fr_route()) def test_non_blueprint_rest_error_routing(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) api.add_resource(HelloWorld(), '/hi', endpoint="hello") api.add_resource(GoodbyeWorld(404), '/bye', endpoint="bye") app = Flask(__name__) app.register_blueprint(blueprint, url_prefix='/blueprint') api2 = flask_restful.Api(app) api2.add_resource(HelloWorld(), '/hi', endpoint="hello") api2.add_resource(GoodbyeWorld(404), '/bye', endpoint="bye") with app.test_request_context('/hi', method='POST'): assert_false(api._should_use_fr_error_handler()) assert_true(api2._should_use_fr_error_handler()) assert_false(api._has_fr_route()) assert_true(api2._has_fr_route()) with app.test_request_context('/blueprint/hi', method='POST'): assert_true(api._should_use_fr_error_handler()) assert_false(api2._should_use_fr_error_handler()) assert_true(api._has_fr_route()) assert_false(api2._has_fr_route()) api._should_use_fr_error_handler = Mock(return_value=False) api2._should_use_fr_error_handler = Mock(return_value=False) with app.test_request_context('/bye'): assert_false(api._has_fr_route()) assert_true(api2._has_fr_route()) with app.test_request_context('/blueprint/bye'): assert_true(api._has_fr_route()) assert_false(api2._has_fr_route()) def test_non_blueprint_non_rest_error_routing(self): blueprint = Blueprint('test', __name__) api = flask_restful.Api(blueprint) api.add_resource(HelloWorld(), '/hi', endpoint="hello") api.add_resource(GoodbyeWorld(404), '/bye', endpoint="bye") app = Flask(__name__) app.register_blueprint(blueprint, url_prefix='/blueprint') @app.route('/hi') def hi(): return 'hi' @app.route('/bye') def bye(): flask.abort(404) with app.test_request_context('/hi', method='POST'): assert_false(api._should_use_fr_error_handler()) assert_false(api._has_fr_route()) with app.test_request_context('/blueprint/hi', method='POST'): assert_true(api._should_use_fr_error_handler()) assert_true(api._has_fr_route()) api._should_use_fr_error_handler = Mock(return_value=False) with app.test_request_context('/bye'): assert_false(api._has_fr_route()) with app.test_request_context('/blueprint/bye'): assert_true(api._has_fr_route()) if __name__ == '__main__': unittest.main() flask-restful-0.3.6/tests/test_cors.py000066400000000000000000000043071311353067100200270ustar00rootroot00000000000000import unittest from flask import Flask import flask_restful from flask_restful.utils import cors from nose.tools import assert_equals, assert_true class CORSTestCase(unittest.TestCase): def test_crossdomain(self): class Foo(flask_restful.Resource): @cors.crossdomain(origin='*') def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/') assert_equals(res.status_code, 200) assert_equals(res.headers['Access-Control-Allow-Origin'], '*') assert_equals(res.headers['Access-Control-Max-Age'], '21600') assert_true('HEAD' in res.headers['Access-Control-Allow-Methods']) assert_true('OPTIONS' in res.headers['Access-Control-Allow-Methods']) assert_true('GET' in res.headers['Access-Control-Allow-Methods']) def test_access_control_expose_headers(self): class Foo(flask_restful.Resource): @cors.crossdomain(origin='*', expose_headers=['X-My-Header', 'X-Another-Header']) def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/') assert_equals(res.status_code, 200) assert_true('X-MY-HEADER' in res.headers['Access-Control-Expose-Headers']) assert_true('X-ANOTHER-HEADER' in res.headers['Access-Control-Expose-Headers']) def test_no_crossdomain(self): class Foo(flask_restful.Resource): def get(self): return "data" app = Flask(__name__) api = flask_restful.Api(app) api.add_resource(Foo, '/') with app.test_client() as client: res = client.get('/') assert_equals(res.status_code, 200) assert_true('Access-Control-Allow-Origin' not in res.headers) assert_true('Access-Control-Allow-Methods' not in res.headers) assert_true('Access-Control-Max-Age' not in res.headers) flask-restful-0.3.6/tests/test_crypto.py000066400000000000000000000005451311353067100204010ustar00rootroot00000000000000import unittest from flask_restful.utils.crypto import encrypt, decrypt class CryptoTestCase(unittest.TestCase): def test_encrypt_decrypt(self): key = '0123456789abcdef0123456789abcdef' seed = 'deadbeefcafebabe' message = 'It should go through' self.assertEqual(decrypt(encrypt(message, key, seed), key, seed), message)flask-restful-0.3.6/tests/test_fields.py000066400000000000000000000441651311353067100203350ustar00rootroot00000000000000from decimal import Decimal from functools import partial import pytz import unittest from mock import Mock from flask_restful.fields import MarshallingException from flask_restful.utils import OrderedDict from flask_restful import fields from datetime import datetime, timedelta, tzinfo from flask import Flask, Blueprint #noinspection PyUnresolvedReferences from nose.tools import assert_equals # you need it for tests in form of continuations class Foo(object): def __init__(self): self.hey = 3 class Bar(object): def __marshallable__(self): return {"hey": 3} def check_field(expected, field, value): assert_equals(expected, field.output('a', {'a': value})) def test_float(): values = [ ("-3.13", -3.13), (str(-3.13), -3.13), (3, 3.0), ] for value, expected in values: yield check_field, expected, fields.Float(), value def test_boolean(): values = [ (True, True), (False, False), ({}, False), ("false", True), # These are different from php ("0", True), # Will this be a problem? ] for value, expected in values: yield check_field, expected, fields.Boolean(), value def test_rfc822_datetime_formatters(): dates = [ (datetime(2011, 1, 1), "Sat, 01 Jan 2011 00:00:00 -0000"), (datetime(2011, 1, 1, 23, 59, 59), "Sat, 01 Jan 2011 23:59:59 -0000"), (datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc), "Sat, 01 Jan 2011 23:59:59 -0000"), (datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.timezone('CET')), "Sat, 01 Jan 2011 22:59:59 -0000") ] for date_obj, expected in dates: yield assert_equals, fields._rfc822(date_obj), expected def test_iso8601_datetime_formatters(): dates = [ (datetime(2011, 1, 1), "2011-01-01T00:00:00"), (datetime(2011, 1, 1, 23, 59, 59), "2011-01-01T23:59:59"), (datetime(2011, 1, 1, 23, 59, 59, 1000), "2011-01-01T23:59:59.001000"), (datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc), "2011-01-01T23:59:59+00:00"), (datetime(2011, 1, 1, 23, 59, 59, 1000, tzinfo=pytz.utc), "2011-01-01T23:59:59.001000+00:00"), (datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.timezone('CET')), "2011-01-01T23:59:59+01:00") ] for date_obj, expected in dates: yield assert_equals, fields._iso8601(date_obj), expected class FieldsTestCase(unittest.TestCase): def test_decimal_trash(self): self.assertRaises(MarshallingException, lambda: fields.Float().output('a', {'a': 'Foo'})) def test_basic_dictionary(self): obj = {"foo": 3} field = fields.String() self.assertEquals(field.output("foo", obj), "3") def test_no_attribute(self): obj = {"bar": 3} field = fields.String() self.assertEquals(field.output("foo", obj), None) def test_date_field_invalid(self): obj = {"bar": 3} field = fields.DateTime() self.assertRaises(MarshallingException, lambda: field.output("bar", obj)) def test_attribute(self): obj = {"bar": 3} field = fields.String(attribute="bar") self.assertEquals(field.output("foo", obj), "3") def test_formatting_field_none(self): obj = {} field = fields.FormattedString("/foo/{0[account_sid]}/{0[sid]}/") self.assertRaises(MarshallingException, lambda: field.output("foo", obj)) def test_formatting_field_tuple(self): obj = (3, 4) field = fields.FormattedString("/foo/{0[account_sid]}/{0[sid]}/") self.assertRaises(MarshallingException, lambda: field.output("foo", obj)) def test_formatting_field_dict(self): obj = { "sid": 3, "account_sid": 4, } field = fields.FormattedString("/foo/{account_sid}/{sid}/") self.assertEquals(field.output("foo", obj), "/foo/4/3/") def test_formatting_field(self): obj = Mock() obj.sid = 3 obj.account_sid = 4 field = fields.FormattedString("/foo/{account_sid}/{sid}/") self.assertEquals(field.output("foo", obj), "/foo/4/3/") def test_basic_field(self): obj = Mock() obj.foo = 3 field = fields.Raw() self.assertEquals(field.output("foo", obj), 3) def test_raw_field(self): obj = Mock() obj.foo = 3 field = fields.Raw() self.assertEquals(field.output("foo", obj), 3) def test_nested_raw_field(self): foo = Mock() bar = Mock() bar.value = 3 foo.bar = bar field = fields.Raw() self.assertEquals(field.output("bar.value", foo), 3) def test_formatted_string_invalid_obj(self): field = fields.FormattedString("{hey}") self.assertRaises(MarshallingException, lambda: field.output("hey", None)) def test_formatted_string(self): field = fields.FormattedString("{hey}") self.assertEquals("3", field.output("hey", Foo())) def test_string_with_attribute(self): field = fields.String(attribute="hey") self.assertEquals("3", field.output("foo", Foo())) def test_string_with_lambda(self): field = fields.String(attribute=lambda x: x.hey) self.assertEquals("3", field.output("foo", Foo())) def test_string_with_partial(self): def f(x, suffix): return "%s-%s" % (x.hey, suffix) p = partial(f, suffix="whatever") field = fields.String(attribute=p) self.assertEquals("3-whatever", field.output("foo", Foo())) def test_url_invalid_object(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url("foobar") with app.test_request_context("/"): self.assertRaises(MarshallingException, lambda: field.output("hey", None)) def test_url(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url("foobar") with app.test_request_context("/"): self.assertEquals("/3", field.output("hey", Foo())) def test_url_absolute(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url("foobar", absolute=True) with app.test_request_context("/"): self.assertEquals("http://localhost/3", field.output("hey", Foo())) def test_url_absolute_scheme(self): """Url.scheme should override current_request.scheme""" app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url("foobar", absolute=True, scheme='https') with app.test_request_context("/", base_url="http://localhost"): self.assertEquals("https://localhost/3", field.output("hey", Foo())) def test_url_without_endpoint_invalid_object(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url() with app.test_request_context("/hey"): self.assertRaises(MarshallingException, lambda: field.output("hey", None)) def test_url_without_endpoint(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url() with app.test_request_context("/hey"): self.assertEquals("/3", field.output("hey", Foo())) def test_url_without_endpoint_absolute(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url(absolute=True) with app.test_request_context("/hey"): self.assertEquals("http://localhost/3", field.output("hey", Foo())) def test_url_without_endpoint_absolute_scheme(self): app = Flask(__name__) app.add_url_rule("/", "foobar", view_func=lambda x: x) field = fields.Url(absolute=True, scheme='https') with app.test_request_context("/hey", base_url="http://localhost"): self.assertEquals("https://localhost/3", field.output("hey", Foo())) def test_url_with_blueprint_invalid_object(self): app = Flask(__name__) bp = Blueprint("foo", __name__, url_prefix="/foo") bp.add_url_rule("/", "foobar", view_func=lambda x: x) app.register_blueprint(bp) field = fields.Url() with app.test_request_context("/foo/hey"): self.assertRaises(MarshallingException, lambda: field.output("hey", None)) def test_url_with_blueprint(self): app = Flask(__name__) bp = Blueprint("foo", __name__, url_prefix="/foo") bp.add_url_rule("/", "foobar", view_func=lambda x: x) app.register_blueprint(bp) field = fields.Url() with app.test_request_context("/foo/hey"): self.assertEquals("/foo/3", field.output("hey", Foo())) def test_url_with_blueprint_absolute(self): app = Flask(__name__) bp = Blueprint("foo", __name__, url_prefix="/foo") bp.add_url_rule("/", "foobar", view_func=lambda x: x) app.register_blueprint(bp) field = fields.Url(absolute=True) with app.test_request_context("/foo/hey"): self.assertEquals("http://localhost/foo/3", field.output("hey", Foo())) def test_url_with_blueprint_absolute_scheme(self): app = Flask(__name__) bp = Blueprint("foo", __name__, url_prefix="/foo") bp.add_url_rule("/", "foobar", view_func=lambda x: x) app.register_blueprint(bp) field = fields.Url(absolute=True, scheme='https') with app.test_request_context("/foo/hey", base_url="http://localhost"): self.assertEquals("https://localhost/foo/3", field.output("hey", Foo())) def test_int(self): field = fields.Integer() self.assertEquals(3, field.output("hey", {'hey': 3})) def test_int_default(self): field = fields.Integer(default=1) self.assertEquals(1, field.output("hey", {'hey': None})) def test_no_int(self): field = fields.Integer() self.assertEquals(0, field.output("hey", {'hey': None})) def test_int_decode_error(self): field = fields.Integer() self.assertRaises(MarshallingException, lambda: field.output("hey", {'hey': 'Explode please I am nowhere looking like an int'})) def test_float(self): field = fields.Float() self.assertEquals(3.0, field.output("hey", {'hey': 3.0})) def test_float_decode_error(self): field = fields.Float() self.assertRaises(MarshallingException, lambda: field.output("hey", {'hey': 'Explode!'})) PI_STR = u'3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861' PI = Decimal(PI_STR) def test_arbitrary(self): field = fields.Arbitrary() self.assertEquals(self.PI_STR, field.output("hey", {'hey': self.PI})) def test_fixed(self): field5 = fields.Fixed(5) field4 = fields.Fixed(4) self.assertEquals('3.14159', field5.output("hey", {'hey': self.PI})) self.assertEquals('3.1416', field4.output("hey", {'hey': self.PI})) self.assertEquals('3.0000', field4.output("hey", {'hey': '3'})) self.assertEquals('3.0000', field4.output("hey", {'hey': '03'})) self.assertEquals('3.0000', field4.output("hey", {'hey': '03.0'})) def test_zero_fixed(self): field = fields.Fixed() self.assertEquals('0.00000', field.output('hey', {'hey': 0})) def test_infinite_fixed(self): field = fields.Fixed() self.assertRaises(MarshallingException, lambda: field.output("hey", {'hey': '+inf'})) self.assertRaises(MarshallingException, lambda: field.output("hey", {'hey': '-inf'})) def test_advanced_fixed(self): field = fields.Fixed() self.assertRaises(MarshallingException, lambda: field.output("hey", {'hey': 'NaN'})) def test_fixed_with_attribute(self): field = fields.Fixed(4, attribute="bar") self.assertEquals('3.0000', field.output("foo", {'bar': '3'})) def test_string(self): field = fields.String() self.assertEquals("3", field.output("hey", Foo())) def test_string_no_value(self): field = fields.String() self.assertEquals(None, field.output("bar", Foo())) def test_string_none(self): field = fields.String() self.assertEquals(None, field.output("empty", {'empty': None})) def test_rfc822_date_field_without_offset(self): obj = {"bar": datetime(2011, 8, 22, 20, 58, 45)} field = fields.DateTime() self.assertEquals("Mon, 22 Aug 2011 20:58:45 -0000", field.output("bar", obj)) def test_rfc822_date_field_with_offset(self): obj = {"bar": datetime(2011, 8, 22, 20, 58, 45, tzinfo=pytz.timezone('CET'))} field = fields.DateTime() self.assertEquals("Mon, 22 Aug 2011 19:58:45 -0000", field.output("bar", obj)) def test_iso8601_date_field_without_offset(self): obj = {"bar": datetime(2011, 8, 22, 20, 58, 45)} field = fields.DateTime(dt_format='iso8601') self.assertEquals("2011-08-22T20:58:45", field.output("bar", obj)) def test_iso8601_date_field_with_offset(self): obj = {"bar": datetime(2011, 8, 22, 20, 58, 45, tzinfo=pytz.timezone('CET'))} field = fields.DateTime(dt_format='iso8601') self.assertEquals("2011-08-22T20:58:45+01:00", field.output("bar", obj)) def test_unsupported_datetime_format(self): obj = {"bar": datetime(2011, 8, 22, 20, 58, 45)} field = fields.DateTime(dt_format='raw') self.assertRaises(MarshallingException, lambda: field.output('bar', obj)) def test_to_dict(self): obj = {"hey": 3} self.assertEquals(obj, fields.to_marshallable_type(obj)) def test_to_dict_obj(self): obj = {"hey": 3} self.assertEquals(obj, fields.to_marshallable_type(Foo())) def test_to_dict_custom_marshal(self): obj = {"hey": 3} self.assertEquals(obj, fields.to_marshallable_type(Bar())) def test_get_value(self): self.assertEquals(3, fields.get_value("hey", {"hey": 3})) def test_get_value_no_value(self): self.assertEquals(None, fields.get_value("foo", {"hey": 3})) def test_get_value_obj(self): self.assertEquals(3, fields.get_value("hey", Foo())) def test_list(self): obj = {'list': ['a', 'b', 'c']} field = fields.List(fields.String) self.assertEquals(['a', 'b', 'c'], field.output('list', obj)) def test_list_from_set(self): obj = {'list': set(['a', 'b', 'c'])} field = fields.List(fields.String) self.assertEquals(set(['a', 'b', 'c']), set(field.output('list', obj))) def test_list_from_object(self): class TestObject(object): def __init__(self, list): self.list = list obj = TestObject(['a', 'b', 'c']) field = fields.List(fields.String) self.assertEquals(['a', 'b', 'c'], field.output('list', obj)) def test_list_with_attribute(self): class TestObject(object): def __init__(self, list): self.foo = list obj = TestObject(['a', 'b', 'c']) field = fields.List(fields.String, attribute='foo') self.assertEquals(['a', 'b', 'c'], field.output('list', obj)) def test_list_with_scoped_attribute_on_dict_or_obj(self): class TestObject(object): def __init__(self, list_): self.bar = list_ class TestEgg(object): def __init__(self, val): self.attrib = val eggs = [TestEgg(i) for i in ['a', 'b', 'c']] test_obj = TestObject(eggs) test_dict = {'bar': [{'attrib': 'a'}, {'attrib':'b'}, {'attrib':'c'}]} field = fields.List(fields.String(attribute='attrib'), attribute='bar') self.assertEquals(['a', 'b', 'c'], field.output('bar', test_obj)) self.assertEquals(['a', 'b', 'c'], field.output('bar', test_dict)) def test_null_list(self): class TestObject(object): def __init__(self, list): self.list = list obj = TestObject(None) field = fields.List(fields.String) self.assertEquals(None, field.output('list', obj)) def test_indexable_object(self): class TestObject(object): def __init__(self, foo): self.foo = foo def __getitem__(self, n): if type(n) is int: if n < 3: return n raise IndexError raise TypeError obj = TestObject("hi") field = fields.String(attribute="foo") self.assertEquals("hi", field.output("foo", obj)) def test_list_from_dict_with_attribute(self): obj = {'list': [{'a': 1, 'b': 1}, {'a': 2, 'b': 1}, {'a': 3, 'b': 1}]} field = fields.List(fields.Integer(attribute='a')) self.assertEquals([1, 2, 3], field.output('list', obj)) def test_list_of_nested(self): obj = {'list': [{'a': 1, 'b': 1}, {'a': 2, 'b': 1}, {'a': 3, 'b': 1}]} field = fields.List(fields.Nested({'a': fields.Integer})) self.assertEquals([OrderedDict([('a', 1)]), OrderedDict([('a', 2)]), OrderedDict([('a', 3)])], field.output('list', obj)) def test_nested_with_default(self): obj = None field = fields.Nested({'a': fields.Integer, 'b': fields.String}, default={}) self.assertEquals({}, field.output('a', obj)) def test_list_of_raw(self): obj = {'list': [{'a': 1, 'b': 1}, {'a': 2, 'b': 1}, {'a': 3, 'b': 1}]} field = fields.List(fields.Raw) self.assertEquals([OrderedDict([('a', 1), ('b', 1), ]), OrderedDict([('a', 2), ('b', 1), ]), OrderedDict([('a', 3), ('b', 1), ])], field.output('list', obj)) obj = {'list': [1, 2, 'a']} field = fields.List(fields.Raw) self.assertEquals([1, 2, 'a'], field.output('list', obj)) if __name__ == '__main__': unittest.main() flask-restful-0.3.6/tests/test_inputs.py000066400000000000000000000302221311353067100203760ustar00rootroot00000000000000from datetime import datetime, timedelta, tzinfo import unittest import pytz import re #noinspection PyUnresolvedReferences from nose.tools import assert_equal, assert_raises # you need it for tests in form of continuations import six from flask_restful import inputs def test_reverse_rfc822_datetime(): dates = [ ("Sat, 01 Jan 2011 00:00:00 -0000", datetime(2011, 1, 1, tzinfo=pytz.utc)), ("Sat, 01 Jan 2011 23:59:59 -0000", datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc)), ("Sat, 01 Jan 2011 21:59:59 -0200", datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc)), ] for date_string, expected in dates: yield assert_equal, inputs.datetime_from_rfc822(date_string), expected def test_reverse_iso8601_datetime(): dates = [ ("2011-01-01T00:00:00+00:00", datetime(2011, 1, 1, tzinfo=pytz.utc)), ("2011-01-01T23:59:59+00:00", datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc)), ("2011-01-01T23:59:59.001000+00:00", datetime(2011, 1, 1, 23, 59, 59, 1000, tzinfo=pytz.utc)), ("2011-01-01T23:59:59+02:00", datetime(2011, 1, 1, 21, 59, 59, tzinfo=pytz.utc)) ] for date_string, expected in dates: yield assert_equal, inputs.datetime_from_iso8601(date_string), expected def test_urls(): urls = [ 'http://www.djangoproject.com/', 'http://localhost/', 'http://example.com/', 'http://www.example.com/', 'http://www.example.com:8000/test', 'http://valid-with-hyphens.com/', 'http://subdomain.example.com/', 'http://200.8.9.10/', 'http://200.8.9.10:8000/test', 'http://valid-----hyphens.com/', 'http://example.com?something=value', 'http://example.com/index.php?something=value&another=value2', 'http://foo:bar@example.com', 'http://foo:@example.com', 'http://foo:@2001:db8:85a3::8a2e:370:7334', 'http://foo2:qd1%r@example.com', ] for value in urls: yield assert_equal, inputs.url(value), value def check_bad_url_raises(value): try: inputs.url(value) assert False, "shouldn't get here" except ValueError as e: assert_equal(six.text_type(e), u"{0} is not a valid URL".format(value)) def test_bad_urls(): values = [ 'foo', 'http://', 'http://example', 'http://example.', 'http://.com', 'http://invalid-.com', 'http://-invalid.com', 'http://inv-.alid-.com', 'http://inv-.-alid.com', 'foo bar baz', u'foo \u2713', 'http://@foo:bar@example.com', 'http://:bar@example.com', 'http://bar:bar:bar@example.com', ] for value in values: yield check_bad_url_raises, value def test_bad_url_error_message(): values = [ 'google.com', 'domain.google.com', 'kevin:pass@google.com/path?query', u'google.com/path?\u2713', ] for value in values: yield check_url_error_message, value def check_url_error_message(value): try: inputs.url(value) assert False, u"inputs.url({0}) should raise an exception".format(value) except ValueError as e: assert_equal(six.text_type(e), (u"{0} is not a valid URL. Did you mean: http://{0}".format(value))) def test_regex_bad_input(): cases = ( 'abc', '123abc', 'abc123', '', ) num_only = inputs.regex(r'^[0-9]+$') for value in cases: yield assert_raises, ValueError, lambda: num_only(value) def test_regex_good_input(): cases = ( '123', '1234567890', '00000', ) num_only = inputs.regex(r'^[0-9]+$') for value in cases: yield assert_equal, num_only(value), value def test_regex_bad_pattern(): """Regex error raised immediately when regex input parser is created.""" assert_raises(re.error, inputs.regex, '[') def test_regex_flags_good_input(): cases = ( 'abcd', 'ABCabc', 'ABC', ) case_insensitive = inputs.regex(r'^[A-Z]+$', re.IGNORECASE) for value in cases: yield assert_equal, case_insensitive(value), value def test_regex_flags_bad_input(): cases = ( 'abcd', 'ABCabc' ) case_sensitive = inputs.regex(r'^[A-Z]+$') for value in cases: yield assert_raises, ValueError, lambda: case_sensitive(value) class TypesTestCase(unittest.TestCase): def test_boolean_false(self): assert_equal(inputs.boolean("False"), False) def test_boolean_is_false_for_0(self): assert_equal(inputs.boolean("0"), False) def test_boolean_true(self): assert_equal(inputs.boolean("true"), True) def test_boolean_is_true_for_1(self): assert_equal(inputs.boolean("1"), True) def test_boolean_upper_case(self): assert_equal(inputs.boolean("FaLSE"), False) def test_boolean(self): assert_equal(inputs.boolean("FaLSE"), False) def test_boolean_with_python_bool(self): """Input that is already a native python `bool` should be passed through without extra processing.""" assert_equal(inputs.boolean(True), True) assert_equal(inputs.boolean(False), False) def test_bad_boolean(self): assert_raises(ValueError, lambda: inputs.boolean("blah")) def test_date_later_than_1900(self): assert_equal(inputs.date("1900-01-01"), datetime(1900, 1, 1)) def test_date_input_error(self): assert_raises(ValueError, lambda: inputs.date("2008-13-13")) def test_date_input(self): assert_equal(inputs.date("2008-08-01"), datetime(2008, 8, 1)) def test_natual_negative(self): assert_raises(ValueError, lambda: inputs.natural(-1)) def test_natural(self): assert_equal(3, inputs.natural(3)) def test_natual_string(self): assert_raises(ValueError, lambda: inputs.natural('foo')) def test_positive(self): assert_equal(1, inputs.positive(1)) assert_equal(10000, inputs.positive(10000)) def test_positive_zero(self): assert_raises(ValueError, lambda: inputs.positive(0)) def test_positive_negative_input(self): assert_raises(ValueError, lambda: inputs.positive(-1)) def test_int_range_good(self): int_range = inputs.int_range(1, 5) assert_equal(3, int_range(3)) def test_int_range_inclusive(self): int_range = inputs.int_range(1, 5) assert_equal(5, int_range(5)) def test_int_range_low(self): int_range = inputs.int_range(0, 5) assert_raises(ValueError, lambda: int_range(-1)) def test_int_range_high(self): int_range = inputs.int_range(0, 5) assert_raises(ValueError, lambda: int_range(6)) def test_isointerval(): intervals = [ ( # Full precision with explicit UTC. "2013-01-01T12:30:00Z/P1Y2M3DT4H5M6S", ( datetime(2013, 1, 1, 12, 30, 0, tzinfo=pytz.utc), datetime(2014, 3, 5, 16, 35, 6, tzinfo=pytz.utc), ), ), ( # Full precision with alternate UTC indication "2013-01-01T12:30+00:00/P2D", ( datetime(2013, 1, 1, 12, 30, 0, tzinfo=pytz.utc), datetime(2013, 1, 3, 12, 30, 0, tzinfo=pytz.utc), ), ), ( # Implicit UTC with time "2013-01-01T15:00/P1M", ( datetime(2013, 1, 1, 15, 0, 0, tzinfo=pytz.utc), datetime(2013, 1, 31, 15, 0, 0, tzinfo=pytz.utc), ), ), ( # TZ conversion "2013-01-01T17:00-05:00/P2W", ( datetime(2013, 1, 1, 22, 0, 0, tzinfo=pytz.utc), datetime(2013, 1, 15, 22, 0, 0, tzinfo=pytz.utc), ), ), ( # Date upgrade to midnight-midnight period "2013-01-01/P3D", ( datetime(2013, 1, 1, 0, 0, 0, tzinfo=pytz.utc), datetime(2013, 1, 4, 0, 0, 0, 0, tzinfo=pytz.utc), ), ), ( # Start/end with UTC "2013-01-01T12:00:00Z/2013-02-01T12:00:00Z", ( datetime(2013, 1, 1, 12, 0, 0, tzinfo=pytz.utc), datetime(2013, 2, 1, 12, 0, 0, tzinfo=pytz.utc), ), ), ( # Start/end with time upgrade "2013-01-01/2013-06-30", ( datetime(2013, 1, 1, tzinfo=pytz.utc), datetime(2013, 6, 30, tzinfo=pytz.utc), ), ), ( # Start/end with TZ conversion "2013-02-17T12:00:00-07:00/2013-02-28T15:00:00-07:00", ( datetime(2013, 2, 17, 19, 0, 0, tzinfo=pytz.utc), datetime(2013, 2, 28, 22, 0, 0, tzinfo=pytz.utc), ), ), # Resolution expansion for single date(time) ( # Second with UTC "2013-01-01T12:30:45Z", ( datetime(2013, 1, 1, 12, 30, 45, tzinfo=pytz.utc), datetime(2013, 1, 1, 12, 30, 46, tzinfo=pytz.utc), ), ), ( # Second with tz conversion "2013-01-01T12:30:45+02:00", ( datetime(2013, 1, 1, 10, 30, 45, tzinfo=pytz.utc), datetime(2013, 1, 1, 10, 30, 46, tzinfo=pytz.utc), ), ), ( # Second with implicit UTC "2013-01-01T12:30:45", ( datetime(2013, 1, 1, 12, 30, 45, tzinfo=pytz.utc), datetime(2013, 1, 1, 12, 30, 46, tzinfo=pytz.utc), ), ), ( # Minute with UTC "2013-01-01T12:30+00:00", ( datetime(2013, 1, 1, 12, 30, tzinfo=pytz.utc), datetime(2013, 1, 1, 12, 31, tzinfo=pytz.utc), ), ), ( # Minute with conversion "2013-01-01T12:30+04:00", ( datetime(2013, 1, 1, 8, 30, tzinfo=pytz.utc), datetime(2013, 1, 1, 8, 31, tzinfo=pytz.utc), ), ), ( # Minute with implicit UTC "2013-01-01T12:30", ( datetime(2013, 1, 1, 12, 30, tzinfo=pytz.utc), datetime(2013, 1, 1, 12, 31, tzinfo=pytz.utc), ), ), ( # Hour, explicit UTC "2013-01-01T12Z", ( datetime(2013, 1, 1, 12, tzinfo=pytz.utc), datetime(2013, 1, 1, 13, tzinfo=pytz.utc), ), ), ( # Hour with offset "2013-01-01T12-07:00", ( datetime(2013, 1, 1, 19, tzinfo=pytz.utc), datetime(2013, 1, 1, 20, tzinfo=pytz.utc), ), ), ( # Hour with implicit UTC "2013-01-01T12", ( datetime(2013, 1, 1, 12, tzinfo=pytz.utc), datetime(2013, 1, 1, 13, tzinfo=pytz.utc), ), ), ( # Interval with trailing zero fractional seconds should # be accepted. "2013-01-01T12:00:00.0/2013-01-01T12:30:00.000000", ( datetime(2013, 1, 1, 12, tzinfo=pytz.utc), datetime(2013, 1, 1, 12, 30, tzinfo=pytz.utc), ), ), ] for value, expected in intervals: yield assert_equal, inputs.iso8601interval(value), expected def test_invalid_isointerval_error(): try: inputs.iso8601interval('2013-01-01/blah') except ValueError as error: assert_equal( str(error), "Invalid argument: 2013-01-01/blah. argument must be a valid ISO8601 " "date/time interval.", ) return assert False, 'Should raise a ValueError' def test_bad_isointervals(): bad_intervals = [ '2013-01T14:', '', 'asdf', '01/01/2013', ] for bad_interval in bad_intervals: yield ( assert_raises, ValueError, inputs.iso8601interval, bad_interval, ) if __name__ == '__main__': unittest.main() flask-restful-0.3.6/tests/test_paging.py000066400000000000000000000020351311353067100203220ustar00rootroot00000000000000import unittest from flask_restful.paging import retrieve_next_page class PagingTestCase(unittest.TestCase): def test_bookmark_paging(self): key = '0123456789abcdef0123456789abcdef' seed = 'deadbeefcafebabe' def fetch_data(filters, bookmark, page_size): self.assertEquals(filters['my_filter'], 'yes') self.assertEquals(len(filters), 1) # we don't want extraneous paging metadata in there if bookmark is None: bookmark = 0 return [i for i in range(bookmark, bookmark + page_size)], bookmark + page_size, 100000 filter = {'my_filter': 'yes', 'page_size': 3} result, filter, approx_result_size = retrieve_next_page(key, seed, filter, fetch_data) self.assertEquals(result, [0, 1, 2]) self.assertEquals(approx_result_size, 100000) result, filter, approx_result_size = retrieve_next_page(key, seed, filter, fetch_data) self.assertEquals(approx_result_size, 100000) self.assertEquals(result, [3, 4, 5]) flask-restful-0.3.6/tests/test_reqparse.py000066400000000000000000000761701311353067100207120ustar00rootroot00000000000000# -*- coding: utf-8 -*- import unittest from mock import Mock, patch from flask import Flask from werkzeug import exceptions, MultiDict from werkzeug.wrappers import Request from werkzeug.datastructures import FileStorage from flask_restful.reqparse import Argument, RequestParser, Namespace import six import decimal import json class ReqParseTestCase(unittest.TestCase): def test_default_help(self): arg = Argument("foo") self.assertEquals(arg.help, None) @patch('flask_restful.abort') def test_help_with_error_msg(self, abort): app = Flask(__name__) with app.app_context(): parser = RequestParser() parser.add_argument('foo', choices=('one', 'two'), help='Bad choice: {error_msg}') req = Mock(['values']) req.values = MultiDict([('foo', 'three')]) parser.parse_args(req) expected = {'foo': 'Bad choice: three is not a valid choice'} abort.assert_called_with(400, message=expected) @patch('flask_restful.abort') def test_help_with_unicode_error_msg(self, abort): app = Flask(__name__) with app.app_context(): parser = RequestParser() parser.add_argument('foo', choices=('one', 'two'), help=u'Bad choice: {error_msg}') req = Mock(['values']) req.values = MultiDict([('foo', u'\xf0\x9f\x8d\x95')]) parser.parse_args(req) expected = {'foo': u'Bad choice: \xf0\x9f\x8d\x95 is not a valid choice'} abort.assert_called_with(400, message=expected) @patch('flask_restful.abort') def test_help_no_error_msg(self, abort): app = Flask(__name__) with app.app_context(): parser = RequestParser() parser.add_argument('foo', choices=['one', 'two'], help='Please select a valid choice') req = Mock(['values']) req.values = MultiDict([('foo', 'three')]) parser.parse_args(req) expected = {'foo': 'Please select a valid choice'} abort.assert_called_with(400, message=expected) @patch('flask_restful.abort', side_effect=exceptions.BadRequest('Bad Request')) def test_no_help(self, abort): def bad_choice(): parser = RequestParser() parser.add_argument('foo', choices=['one', 'two']) req = Mock(['values']) req.values = MultiDict([('foo', 'three')]) parser.parse_args(req) abort.assert_called_with(400, message='three is not a valid choice') app = Flask(__name__) with app.app_context(): self.assertRaises(exceptions.BadRequest, bad_choice) def test_name(self): arg = Argument("foo") self.assertEquals(arg.name, "foo") def test_dest(self): arg = Argument("foo", dest="foobar") self.assertEquals(arg.dest, "foobar") def test_location_url(self): arg = Argument("foo", location="url") self.assertEquals(arg.location, "url") def test_location_url_list(self): arg = Argument("foo", location=["url"]) self.assertEquals(arg.location, ["url"]) def test_location_header(self): arg = Argument("foo", location="headers") self.assertEquals(arg.location, "headers") def test_location_json(self): arg = Argument("foo", location="json") self.assertEquals(arg.location, "json") def test_location_get_json(self): arg = Argument("foo", location="get_json") self.assertEquals(arg.location, "get_json") def test_location_header_list(self): arg = Argument("foo", location=["headers"]) self.assertEquals(arg.location, ["headers"]) def test_type(self): arg = Argument("foo", type=int) self.assertEquals(arg.type, int) def test_default(self): arg = Argument("foo", default=True) self.assertEquals(arg.default, True) def test_required(self): arg = Argument("foo", required=True) self.assertEquals(arg.required, True) def test_ignore(self): arg = Argument("foo", ignore=True) self.assertEquals(arg.ignore, True) def test_operator(self): arg = Argument("foo", operators=[">=", "<=", "="]) self.assertEquals(arg.operators, [">=", "<=", "="]) def test_action_filter(self): arg = Argument("foo", action="filter") self.assertEquals(arg.action, u"filter") def test_action(self): arg = Argument("foo", action="append") self.assertEquals(arg.action, u"append") def test_choices(self): arg = Argument("foo", choices=[1, 2]) self.assertEquals(arg.choices, [1, 2]) def test_default_dest(self): arg = Argument("foo") self.assertEquals(arg.dest, None) def test_default_operators(self): arg = Argument("foo") self.assertEquals(arg.operators[0], "=") self.assertEquals(len(arg.operators), 1) @patch('flask_restful.reqparse.six') def test_default_type(self, mock_six): arg = Argument("foo") sentinel = object() arg.type(sentinel) mock_six.text_type.assert_called_with(sentinel) def test_default_default(self): arg = Argument("foo") self.assertEquals(arg.default, None) def test_required_default(self): arg = Argument("foo") self.assertEquals(arg.required, False) def test_ignore_default(self): arg = Argument("foo") self.assertEquals(arg.ignore, False) def test_action_default(self): arg = Argument("foo") self.assertEquals(arg.action, u"store") def test_choices_default(self): arg = Argument("foo") self.assertEquals(len(arg.choices), 0) def test_source(self): req = Mock(['args', 'headers', 'values']) req.args = {'foo': 'bar'} req.headers = {'baz': 'bat'} arg = Argument('foo', location=['args']) self.assertEquals(arg.source(req), MultiDict(req.args)) arg = Argument('foo', location=['headers']) self.assertEquals(arg.source(req), MultiDict(req.headers)) def test_convert_default_type_with_null_input(self): arg = Argument('foo') self.assertEquals(arg.convert(None, None), None) def test_convert_with_null_input_when_not_nullable(self): arg = Argument('foo', nullable=False) self.assertRaises(ValueError, lambda: arg.convert(None, None)) def test_source_bad_location(self): req = Mock(['values']) arg = Argument('foo', location=['foo']) self.assertTrue(len(arg.source(req)) == 0) # yes, basically you don't find it def test_source_default_location(self): req = Mock(['values']) req._get_child_mock = lambda **kwargs: MultiDict() arg = Argument('foo') self.assertEquals(arg.source(req), req.values) def test_option_case_sensitive(self): arg = Argument("foo", choices=["bar", "baz"], case_sensitive=True) self.assertEquals(True, arg.case_sensitive) # Insensitive arg = Argument("foo", choices=["bar", "baz"], case_sensitive=False) self.assertEquals(False, arg.case_sensitive) # Default arg = Argument("foo", choices=["bar", "baz"]) self.assertEquals(True, arg.case_sensitive) def test_viewargs(self): req = Request.from_values() req.view_args = {"foo": "bar"} parser = RequestParser() parser.add_argument("foo", location=["view_args"]) args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") req = Mock() req.values = () req.json = None req.view_args = {"foo": "bar"} parser = RequestParser() parser.add_argument("foo", store_missing=True) args = parser.parse_args(req) self.assertEquals(args["foo"], None) def test_parse_unicode(self): req = Request.from_values("/bubble?foo=barß") parser = RequestParser() parser.add_argument("foo") args = parser.parse_args(req) self.assertEquals(args['foo'], u"barß") def test_parse_unicode_app(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo") with app.test_request_context('/bubble?foo=barß'): args = parser.parse_args() self.assertEquals(args['foo'], u"barß") def test_json_location(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", location="json", store_missing=True) with app.test_request_context('/bubble', method="post"): args = parser.parse_args() self.assertEquals(args['foo'], None) def test_get_json_location(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", location="json") with app.test_request_context('/bubble', method="post", data=json.dumps({"foo": "bar"}), content_type='application/json'): args = parser.parse_args() self.assertEquals(args['foo'], 'bar') def test_parse_append_ignore(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo", ignore=True, type=int, action="append", store_missing=True), args = parser.parse_args(req) self.assertEquals(args['foo'], None) def test_parse_append_default(self): req = Request.from_values("/bubble?") parser = RequestParser() parser.add_argument("foo", action="append", store_missing=True), args = parser.parse_args(req) self.assertEquals(args['foo'], None) def test_parse_append(self): req = Request.from_values("/bubble?foo=bar&foo=bat") parser = RequestParser() parser.add_argument("foo", action="append"), args = parser.parse_args(req) self.assertEquals(args['foo'], ["bar", "bat"]) def test_parse_append_single(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo", action="append"), args = parser.parse_args(req) self.assertEquals(args['foo'], ["bar"]) def test_parse_append_many(self): req = Request.from_values("/bubble?foo=bar&foo=bar2") parser = RequestParser() parser.add_argument("foo", action="append"), args = parser.parse_args(req) self.assertEquals(args['foo'], ["bar", "bar2"]) def test_parse_append_many_location_json(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", action='append', location="json") with app.test_request_context('/bubble', method="post", data=json.dumps({"foo": ["bar", "bar2"]}), content_type='application/json'): args = parser.parse_args() self.assertEquals(args['foo'], ['bar', 'bar2']) def test_parse_dest(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo", dest="bat") args = parser.parse_args(req) self.assertEquals(args['bat'], "bar") def test_parse_gte_lte_eq(self): req = Request.from_values("/bubble?foo>=bar&foo<=bat&foo=foo") parser = RequestParser() parser.add_argument("foo", operators=[">=", "<=", "="], action="append"), args = parser.parse_args(req) self.assertEquals(args['foo'], ["bar", "bat", "foo"]) def test_parse_gte(self): req = Request.from_values("/bubble?foo>=bar") parser = RequestParser() parser.add_argument("foo", operators=[">="]) args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse_foo_operators_four_hunderd(self): app = Flask(__name__) with app.app_context(): parser = RequestParser() parser.add_argument("foo", type=int), self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(Request.from_values("/bubble?foo=bar"))) def test_parse_foo_operators_ignore(self): parser = RequestParser() parser.add_argument("foo", ignore=True, store_missing=True) args = parser.parse_args(Request.from_values("/bubble")) self.assertEquals(args['foo'], None) def test_parse_lte_gte_mock(self): mock_type = Mock() req = Request.from_values("/bubble?foo<=bar") parser = RequestParser() parser.add_argument("foo", type=mock_type, operators=["<="]) parser.parse_args(req) mock_type.assert_called_with("bar", "foo", "<=") def test_parse_lte_gte_append(self): parser = RequestParser() parser.add_argument("foo", operators=["<=", "="], action="append") args = parser.parse_args(Request.from_values("/bubble?foo<=bar")) self.assertEquals(args['foo'], ["bar"]) def test_parse_lte_gte_missing(self): parser = RequestParser() parser.add_argument("foo", operators=["<=", "="]) args = parser.parse_args(Request.from_values("/bubble?foo<=bar")) self.assertEquals(args['foo'], "bar") def test_parse_eq_other(self): parser = RequestParser() parser.add_argument("foo"), args = parser.parse_args(Request.from_values("/bubble?foo=bar&foo=bat")) self.assertEquals(args['foo'], "bar") def test_parse_eq(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo"), args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse_lte(self): req = Request.from_values("/bubble?foo<=bar") parser = RequestParser() parser.add_argument("foo", operators=["<="]) args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse_required(self): app = Flask(__name__) with app.app_context(): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo", required=True, location='values') message = '' try: parser.parse_args(req) except exceptions.BadRequest as e: message = e.data['message'] self.assertEquals(message, ({'foo': 'Missing required parameter in ' 'the post body or the query ' 'string'})) parser = RequestParser() parser.add_argument("bar", required=True, location=['values', 'cookies']) try: parser.parse_args(req) except exceptions.BadRequest as e: message = e.data['message'] self.assertEquals(message, ({'bar': 'Missing required parameter in ' 'the post body or the query ' 'string or the request\'s ' 'cookies'})) def test_parse_error_bundling(self): app = Flask(__name__) app.config['BUNDLE_ERRORS']=True with app.app_context(): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo", required=True, location='values') parser.add_argument("bar", required=True, location=['values', 'cookies']) message = '' try: parser.parse_args(req) except exceptions.BadRequest as e: message = e.data['message'] error_message = {'foo': 'Missing required parameter in the post ' 'body or the query string', 'bar': 'Missing required parameter in the post ' 'body or the query string or the ' 'request\'s cookies'} self.assertEquals(message, error_message) def test_parse_error_bundling_w_parser_arg(self): app = Flask(__name__) app.config['BUNDLE_ERRORS']=False with app.app_context(): req = Request.from_values("/bubble") parser = RequestParser(bundle_errors=True) parser.add_argument("foo", required=True, location='values') parser.add_argument("bar", required=True, location=['values', 'cookies']) message = '' try: parser.parse_args(req) except exceptions.BadRequest as e: message = e.data['message'] error_message = {'foo': 'Missing required parameter in the post ' 'body or the query string', 'bar': 'Missing required parameter in the post ' 'body or the query string or the request\'s ' 'cookies'} self.assertEquals(message, error_message) def test_parse_default_append(self): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo", default="bar", action="append", store_missing=True) args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse_default(self): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo", default="bar", store_missing=True) args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse_callable_default(self): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo", default=lambda: "bar", store_missing=True) args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo"), args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") def test_parse_none(self): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo") args = parser.parse_args(req) self.assertEquals(args['foo'], None) def test_parse_store_missing(self): req = Request.from_values("/bubble") parser = RequestParser() parser.add_argument("foo", store_missing=False) args = parser.parse_args(req) self.assertFalse('foo' in args) def test_parse_choices_correct(self): req = Request.from_values("/bubble?foo=bat") parser = RequestParser() parser.add_argument("foo", choices=["bat"]), args = parser.parse_args(req) self.assertEquals(args['foo'], "bat") def test_parse_choices(self): app = Flask(__name__) with app.app_context(): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo", choices=["bat"]), self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(req)) def test_parse_choices_sensitive(self): app = Flask(__name__) with app.app_context(): req = Request.from_values("/bubble?foo=BAT") parser = RequestParser() parser.add_argument("foo", choices=["bat"], case_sensitive=True), self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(req)) def test_parse_choices_insensitive(self): req = Request.from_values("/bubble?foo=BAT") parser = RequestParser() parser.add_argument("foo", choices=["bat"], case_sensitive=False), args = parser.parse_args(req) self.assertEquals('bat', args.get('foo')) # both choices and args are case_insensitive req = Request.from_values("/bubble?foo=bat") parser = RequestParser() parser.add_argument("foo", choices=["BAT"], case_sensitive=False), args = parser.parse_args(req) self.assertEquals('bat', args.get('foo')) def test_parse_ignore(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument("foo", type=int, ignore=True, store_missing=True), args = parser.parse_args(req) self.assertEquals(args['foo'], None) def test_chaining(self): parser = RequestParser() self.assertTrue(parser is parser.add_argument("foo")) def test_namespace_existence(self): namespace = Namespace() namespace.foo = 'bar' namespace['bar'] = 'baz' self.assertEquals(namespace['foo'], 'bar') self.assertEquals(namespace.bar, 'baz') def test_namespace_missing(self): namespace = Namespace() self.assertRaises(AttributeError, lambda: namespace.spam) self.assertRaises(KeyError, lambda: namespace['eggs']) def test_namespace_configurability(self): req = Request.from_values() self.assertTrue(isinstance(RequestParser().parse_args(req), Namespace)) self.assertTrue(type(RequestParser(namespace_class=dict).parse_args(req)) is dict) def test_none_argument(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", location="json") with app.test_request_context('/bubble', method="post", data=json.dumps({"foo": None}), content_type='application/json'): args = parser.parse_args() self.assertEquals(args['foo'], None) def test_type_callable(self): req = Request.from_values("/bubble?foo=1") parser = RequestParser() parser.add_argument("foo", type=lambda x: x, required=False), args = parser.parse_args(req) self.assertEquals(args['foo'], "1") def test_type_callable_none(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", type=lambda x: x, location="json", required=False), with app.test_request_context('/bubble', method="post", data=json.dumps({"foo": None}), content_type='application/json'): try: args = parser.parse_args() self.assertEquals(args['foo'], None) except exceptions.BadRequest: self.fail() def test_type_decimal(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", type=decimal.Decimal, location="json") with app.test_request_context('/bubble', method='post', data=json.dumps({"foo": "1.0025"}), content_type='application/json'): args = parser.parse_args() self.assertEquals(args['foo'], decimal.Decimal("1.0025")) def test_type_filestorage(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", type=FileStorage, location='files') fdata = six.b('foo bar baz qux') with app.test_request_context('/bubble', method='POST', data={'foo': (six.BytesIO(fdata), 'baz.txt')}): args = parser.parse_args() self.assertEquals(args['foo'].name, 'foo') self.assertEquals(args['foo'].filename, 'baz.txt') self.assertEquals(args['foo'].read(), fdata) def test_filestorage_custom_type(self): def _custom_type(f): return FileStorage(stream=f.stream, filename="{0}aaaa".format(f.filename), name="{0}aaaa".format(f.name)) app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", type=_custom_type, location='files') fdata = six.b('foo bar baz qux') with app.test_request_context('/bubble', method='POST', data={'foo': (six.BytesIO(fdata), 'baz.txt')}): args = parser.parse_args() self.assertEquals(args['foo'].name, 'fooaaaa') self.assertEquals(args['foo'].filename, 'baz.txtaaaa') self.assertEquals(args['foo'].read(), fdata) def test_passing_arguments_object(self): req = Request.from_values("/bubble?foo=bar") parser = RequestParser() parser.add_argument(Argument("foo")) args = parser.parse_args(req) self.assertEquals(args['foo'], u"bar") def test_int_choice_types(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", type=int, choices=[1, 2, 3], location='json') with app.test_request_context( '/bubble', method='post', data=json.dumps({'foo': 5}), content_type='application/json' ): try: parser.parse_args() self.fail() except exceptions.BadRequest: pass def test_int_range_choice_types(self): app = Flask(__name__) parser = RequestParser() parser.add_argument("foo", type=int, choices=range(100), location='json') with app.test_request_context( '/bubble', method='post', data=json.dumps({'foo': 101}), content_type='application/json' ): try: parser.parse_args() self.fail() except exceptions.BadRequest: pass def test_request_parser_copy(self): req = Request.from_values("/bubble?foo=101&bar=baz") parser = RequestParser() foo_arg = Argument('foo', type=int) parser.args.append(foo_arg) parser_copy = parser.copy() # Deepcopy should create a clone of the argument object instead of # copying a reference to the new args list self.assertFalse(foo_arg in parser_copy.args) # Args added to new parser should not be added to the original bar_arg = Argument('bar') parser_copy.args.append(bar_arg) self.assertFalse(bar_arg in parser.args) args = parser_copy.parse_args(req) self.assertEquals(args['foo'], 101) self.assertEquals(args['bar'], u'baz') def test_request_parse_copy_including_settings(self): parser = RequestParser(trim=True, bundle_errors=True) parser_copy = parser.copy() self.assertEqual(parser.trim, parser_copy.trim) self.assertEqual(parser.bundle_errors, parser_copy.bundle_errors) def test_request_parser_replace_argument(self): req = Request.from_values("/bubble?foo=baz") parser = RequestParser() parser.add_argument('foo', type=int) parser_copy = parser.copy() parser_copy.replace_argument('foo') args = parser_copy.parse_args(req) self.assertEquals(args['foo'], u'baz') def test_both_json_and_values_location(self): app = Flask(__name__) parser = RequestParser() parser.add_argument('foo', type=int) parser.add_argument('baz', type=int) with app.test_request_context('/bubble?foo=1', method="post", data=json.dumps({"baz": 2}), content_type='application/json'): args = parser.parse_args() self.assertEquals(args['foo'], 1) self.assertEquals(args['baz'], 2) def test_not_json_location_and_content_type_json(self): app = Flask(__name__) parser = RequestParser() parser.add_argument('foo', location='args') with app.test_request_context('/bubble', method='get', content_type='application/json'): parser.parse_args() # Should not raise a 400: BadRequest def test_request_parser_remove_argument(self): req = Request.from_values("/bubble?foo=baz") parser = RequestParser() parser.add_argument('foo', type=int) parser_copy = parser.copy() parser_copy.remove_argument('foo') args = parser_copy.parse_args(req) self.assertEquals(args, {}) def test_strict_parsing_off(self): req = Request.from_values("/bubble?foo=baz") parser = RequestParser() args = parser.parse_args(req) self.assertEquals(args, {}) def test_strict_parsing_on(self): req = Request.from_values("/bubble?foo=baz") parser = RequestParser() self.assertRaises(exceptions.BadRequest, parser.parse_args, req, strict=True) def test_strict_parsing_off_partial_hit(self): req = Request.from_values("/bubble?foo=1&bar=bees&n=22") parser = RequestParser() parser.add_argument('foo', type=int) args = parser.parse_args(req) self.assertEquals(args['foo'], 1) def test_strict_parsing_on_partial_hit(self): req = Request.from_values("/bubble?foo=1&bar=bees&n=22") parser = RequestParser() parser.add_argument('foo', type=int) self.assertRaises(exceptions.BadRequest, parser.parse_args, req, strict=True) def test_trim_argument(self): req = Request.from_values("/bubble?foo= 1 &bar=bees&n=22") parser = RequestParser() parser.add_argument('foo') args = parser.parse_args(req) self.assertEquals(args['foo'], ' 1 ') parser = RequestParser() parser.add_argument('foo', trim=True) args = parser.parse_args(req) self.assertEquals(args['foo'], '1') parser = RequestParser() parser.add_argument('foo', trim=True, type=int) args = parser.parse_args(req) self.assertEquals(args['foo'], 1) def test_trim_request_parser(self): req = Request.from_values("/bubble?foo= 1 &bar=bees&n=22") parser = RequestParser(trim=False) parser.add_argument('foo') args = parser.parse_args(req) self.assertEquals(args['foo'], ' 1 ') parser = RequestParser(trim=True) parser.add_argument('foo') args = parser.parse_args(req) self.assertEquals(args['foo'], '1') parser = RequestParser(trim=True) parser.add_argument('foo', type=int) args = parser.parse_args(req) self.assertEquals(args['foo'], 1) def test_trim_request_parser_override_by_argument(self): parser = RequestParser(trim=True) parser.add_argument('foo', trim=False) self.assertFalse(parser.args[0].trim) def test_trim_request_parser_json(self): app = Flask(__name__) parser = RequestParser(trim=True) parser.add_argument("foo", location="json") parser.add_argument("int1", location="json", type=int) parser.add_argument("int2", location="json", type=int) with app.test_request_context('/bubble', method="post", data=json.dumps({"foo": " bar ", "int1": 1, "int2": " 2 "}), content_type='application/json'): args = parser.parse_args() self.assertEquals(args['foo'], 'bar') self.assertEquals(args['int1'], 1) self.assertEquals(args['int2'], 2) if __name__ == '__main__': unittest.main() flask-restful-0.3.6/tox.ini000066400000000000000000000006511311353067100156170ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py33, py34, py35, py36 [testenv] usedevelop = true commands = pip install -e .[paging] nosetests deps = -r{toxinidir}/tests/requirements.txt