--- flask-restful-0.3.3.orig/.gitignore +++ flask-restful-0.3.3/.gitignore @@ -1,5 +1,5 @@ *.py[cod] -env +venv # C extensions *.so @@ -18,14 +18,12 @@ .installed.cfg lib lib64 -.cache # Installer logs pip-log.txt # Unit test / coverage reports .coverage -htmlcov/ .tox nosetests.xml --- flask-restful-0.3.3.orig/.pc/.quilt_patches +++ flask-restful-0.3.3/.pc/.quilt_patches @@ -0,0 +1 @@ +debian/patches --- flask-restful-0.3.3.orig/.pc/.quilt_series +++ flask-restful-0.3.3/.pc/.quilt_series @@ -0,0 +1 @@ +series --- flask-restful-0.3.3.orig/.pc/.version +++ flask-restful-0.3.3/.pc/.version @@ -0,0 +1 @@ +2 --- flask-restful-0.3.3.orig/.pc/applied-patches +++ flask-restful-0.3.3/.pc/applied-patches @@ -0,0 +1 @@ +skip-bad-test --- flask-restful-0.3.3.orig/.pc/skip-bad-test/tests/test_accept.py +++ flask-restful-0.3.3/.pc/skip-bad-test/tests/test_accept.py @@ -0,0 +1,222 @@ +import 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_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.3.orig/.travis.yml +++ flask-restful-0.3.3/.travis.yml @@ -1,14 +1,10 @@ language: python python: - - 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 + - "3.3" + - "2.7" + - "2.6" +# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors +install: + - "pip install -e '.[paging]' --use-mirrors" +# command to run tests, e.g. python setup.py test +script: python setup.py nosetests --- flask-restful-0.3.3.orig/AUTHORS.md +++ flask-restful-0.3.3/AUTHORS.md @@ -1,97 +1,19 @@ -Authors -======= +## Contributors -A huge thanks to all of our contributors: +We'd like to thank the following people who have contributed to this project. - -- Alex Gaynor -- Alex M -- Alex Morken -- Andrew Dunham -- Andriy Yurchuk -- Anil Kulkarni -- Antonio Dourado -- Antonio Herraiz -- Artur Rodrigues -- Axel Haustant -- Belousow Makc -- Bennett, Bryan -- Bryan Bennett -- Bulat Bochkariov -- Cameron Brandon White -- Catherine Devlin -- Dan Quirk -- David Arnold -- David Baumgold -- David Boucha -- David Crawford -- Dimitris Theodorou -- Doug Black -- Evan Dale Aromin -- Frank Stratton -- Garret Raziel -- Gary Belvin -- Gilles Dartiguelongue -- Giorgio Salluzzo -- Guillaume BINET -- Jacob Magnusson -- James Booth -- James Ogura -- 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 -- Marek Hlobil -- Matt Wright -- Max Peterson -- Maxim -- Michael Hwang -- Miguel Grinberg -- Mihai Tomescu -- Pavel Tyslyatsky -- Petrus J.v.Rensburg -- Philippe Ndiaye -- Piotr Husiatyński -- Prasanna Swaminathan -- Robert Warner -- Ryan Horn -- Sam Kimbrel -- Sander Sink -- Sasha Baranov -- Saul Diez-Guerra -- Sven-Hendrik Haase -- Usman Ehtesham Gul -- Victor Neo -- Vladimir Pal -- WooParadog -- Yaniv Aknin -- bret barker -- k-funk -- kelvinhammond -- kenjones -- lyschoening -- mailto1587 -- mniebla -- mozillazg -- muchosalsa -- nixdata -- papaeye -- pingz -- saml -- siavashg -- silasray -- soasme -- y-p +- Andrew Dunham <andrew@du.nham.ca> +- Frank Stratton ☺ <epanastasi@epanastasi.com> +- Giorgio Salluzzo <giorgio.salluzzo@gmail.com> +- Guillaume BINET <gbin@gootz.net> +- Kamil Gałuszka <galuszkak@gmail.com> +- Kevin Burke <kevin@twilio.com> +- Kyle Conroy <kyle@twilio.com> +- Piotr Husiatyński <phusiatynski@gmail.com> +- Robert Warner <radicalphoenix@gmail.com> +- Ryan Horn <ryanhorn@twilio.com> +- Victor Neo <icyisamu@gmail.com> +- Yaniv Aknin <yaniv@aknin.name> +- Bret Barker <bret@abitrandom.net> +- y-p <yoval@gmx.com> +- Doug Black <dblack@twilio.com> --- flask-restful-0.3.3.orig/CHANGES.md +++ flask-restful-0.3.3/CHANGES.md @@ -3,136 +3,6 @@ Here you can see the full list of changes between each Flask-RESTful release. - -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 ------------- @@ -142,6 +12,7 @@ - allow field type Fixed to take an attribute argument - added url_for() wrapper as Api.url_for(resource) + Version 0.2.4 ------------- --- flask-restful-0.3.3.orig/CONTRIBUTING.md +++ flask-restful-0.3.3/CONTRIBUTING.md @@ -2,16 +2,13 @@ ============================= 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. - -Please refrain from using force pushes to update pull requests. +issue pull requests. 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) +Bugs can be filed as Issues on github. --- flask-restful-0.3.3.orig/Makefile +++ flask-restful-0.3.3/Makefile @@ -1,195 +1,26 @@ -# 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-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 - -.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 +.PHONY: test install docs analysis + +install: venv + . venv/bin/activate; python setup.py develop + +venv: + virtualenv venv + +docs-dependencies: install + . venv/bin/activate; pip install -e '.[docs]' --use-mirrors + +docs: + . venv/bin/activate; cd docs && make html + +# Pycrypto is required to run the unit tests +test-dependencies: install + . venv/bin/activate; pip install -e '.[paging]' --use-mirrors + +test: + . venv/bin/activate; python setup.py nosetests + +analysis: + . venv/bin/activate; flake8 --select=E112,E113,E901,E902,W601,W602,W603,W604,W402,W403,W404,W802,W803,W804,W805,W806 --ignore=W801 flask_restful tests + +release: test + $(shell python scripts/release.py $(shell python setup.py -V)) --- flask-restful-0.3.3.orig/README.md +++ flask-restful-0.3.3/README.md @@ -1,9 +1,6 @@ # 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) +[![Build Status](https://secure.travis-ci.org/twilio/flask-restful.png)](http://travis-ci.org/twilio/flask-restful) Flask-RESTful provides the building blocks for creating a great REST API. --- flask-restful-0.3.3.orig/debian/changelog +++ flask-restful-0.3.3/debian/changelog @@ -0,0 +1,29 @@ +flask-restful (0.3.3-1+git20150715~ppa14.04+2) trusty; urgency=medium + + * Disable tests, as it makes build failing. + + -- Nicolas Derive Sun, 30 Aug 2015 17:25:13 +0200 + +flask-restful (0.3.3-1+git20150715~ppa15.04+1) vivid; urgency=medium + + * New git snapshot from https://github.com/CanalTP/flask-restful. + + -- Nicolas Derive Sun, 30 Aug 2015 17:13:44 +0200 + +flask-restful (0.3.3-1~ppa15.04+2) vivid; urgency=medium + + * Add missing python3-nose build-dep. + + -- Nicolas Derive Fri, 28 Aug 2015 21:26:49 +0200 + +flask-restful (0.3.3-1~ppa15.04+1) vivid; urgency=medium + + * Merge from Debian mentors. + + -- Nicolas Derive Fri, 28 Aug 2015 18:59:41 +0200 + +flask-restful (0.3.3-1) unstable; urgency=low + + * Initial release (Closes: #763962) + + -- Jonathan Carter Sat, 04 Oct 2014 11:27:07 +0200 --- flask-restful-0.3.3.orig/debian/compat +++ flask-restful-0.3.3/debian/compat @@ -0,0 +1 @@ +9 --- flask-restful-0.3.3.orig/debian/control +++ flask-restful-0.3.3/debian/control @@ -0,0 +1,50 @@ +Source: flask-restful +Section: python +Priority: optional +Maintainer: Jonathan Carter +Build-Depends: debhelper (>= 9), + dh-python, + python-all, + python-aniso8601, + python-blinker, + python-crypto, + python-flask, + python-mock, + python-nose, + python-setuptools, + python-six, + python-tz, + python3-all, + python3-aniso8601, + python3-blinker, + python3-crypto, + python3-flask, + python3-mock, + python3-nose, + python3-setuptools, + python3-six, + python3-tz +Standards-Version: 3.9.6 +Homepage: https://github.com/twilio/flask-restful + +Package: python-flask-restful +Architecture: all +Depends: ${misc:Depends}, ${python:Depends} +Description: REST API framework for Flask applications (Python Module) + 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. + . + This package provides the module for Python 2. + +Package: python3-flask-restful +Architecture: all +Depends: ${misc:Depends}, ${python3:Depends} +Description: REST API framework for Flask applications (Python3 Module) + 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. + . + This package provides the module for Python 3. --- flask-restful-0.3.3.orig/debian/copyright +++ flask-restful-0.3.3/debian/copyright @@ -0,0 +1,36 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: flask-restful +Source: https://github.com/twilio/flask-restful + +Files: * +Copyright: 2013, Twilio, Inc +License: BSD-3-Clause + +Files: debian/* +Copyright: 2014 Jonathan Carter +License: BSD-3-Clause + +License: BSD-3-Clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. Neither the name of 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 HOLDERS 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.3.orig/debian/docs +++ flask-restful-0.3.3/debian/docs @@ -0,0 +1 @@ +README.md --- flask-restful-0.3.3.orig/debian/patches/series +++ flask-restful-0.3.3/debian/patches/series @@ -0,0 +1 @@ +skip-bad-test --- flask-restful-0.3.3.orig/debian/patches/skip-bad-test +++ flask-restful-0.3.3/debian/patches/skip-bad-test @@ -0,0 +1,39 @@ +Description: skip test that is broken + test_accept_no_default_match_q0_not_acceptable will fail until Werkzeug + version 1.0 is released. Until then, this test is disabled. + . + flask-restful (0.3.3-1) unstable; urgency=low + . + * Initial release (Closes: #763962) +Author: Jonathan Carter +Bug-Debian: https://bugs.debian.org/763962 + +--- flask-restful-0.3.3.orig/tests/test_accept.py ++++ flask-restful-0.3.3/tests/test_accept.py +@@ -121,26 +121,6 @@ class AcceptTestCase(unittest.TestCase): + 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): --- flask-restful-0.3.3.orig/debian/python-flast-restful.docs +++ flask-restful-0.3.3/debian/python-flast-restful.docs @@ -0,0 +1,3 @@ +README.md +docs +examples --- flask-restful-0.3.3.orig/debian/python3-flask-restful.docs +++ flask-restful-0.3.3/debian/python3-flask-restful.docs @@ -0,0 +1,3 @@ +README.md +docs +examples --- flask-restful-0.3.3.orig/debian/rules +++ flask-restful-0.3.3/debian/rules @@ -0,0 +1,8 @@ +#!/usr/bin/make -f + +export PYBUILD_NAME=flask-restful + +%: + dh $@ --buildsystem=pybuild --with=python2,python3 + +override_dh_auto_test: --- flask-restful-0.3.3.orig/debian/source/format +++ flask-restful-0.3.3/debian/source/format @@ -0,0 +1 @@ +1.0 --- flask-restful-0.3.3.orig/debian/source/lintian-overrides +++ flask-restful-0.3.3/debian/source/lintian-overrides @@ -0,0 +1,2 @@ +# The .html file that lintian reports is part of a template, and not documentation in itself +python3-flask-restful binary: possible-documentation-but-no-doc-base-registration --- flask-restful-0.3.3.orig/debian/source/options +++ flask-restful-0.3.3/debian/source/options @@ -0,0 +1 @@ +tar-ignore = "usr/share/doc/python3-flask-restful/docs/_themes/.gitignore usr/share/doc/python3-flask-restful/docs/_themes/LICENSE" --- flask-restful-0.3.3.orig/debian/watch +++ flask-restful-0.3.3/debian/watch @@ -0,0 +1,3 @@ +version=3 +opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/-$1\.tar\.gz/ \ + https://github.com/twilio/flask-restful/tags .*/v?(\d\S*)\.tar\.gz --- flask-restful-0.3.3.orig/docs/_templates/sidebarintro.html +++ flask-restful-0.3.3/docs/_templates/sidebarintro.html @@ -3,7 +3,7 @@ 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. + internal APIs.

Useful Links

+ +Fork me on GitHub --- flask-restful-0.3.3.orig/docs/api.rst +++ flask-restful-0.3.3/docs/api.rst @@ -3,13 +3,13 @@ API Docs ======== -.. module:: flask_restful +.. module:: flask.ext.restful .. autofunction:: marshal .. autofunction:: marshal_with -.. autofunction:: marshal_with_field .. autofunction:: abort +.. autofunction:: unauthorized Api @@ -17,16 +17,12 @@ .. autoclass:: Api :members: - .. automethod:: unauthorized - .. autoclass:: Resource :members: ReqParse -------- -.. module:: reqparse - -.. autoclass:: RequestParser +.. automodule:: reqparse :members: .. autoclass:: Argument @@ -38,10 +34,13 @@ ------ .. automodule:: fields :members: - :undoc-members: -Inputs ------- -.. automodule:: inputs - :members: - :undoc-members: +Types +----- + +.. module:: flask.ext.restful.types +.. autofunction:: url +.. autofunction:: date +.. autofunction:: natural +.. autofunction:: boolean +.. autofunction:: rfc822 --- flask-restful-0.3.3.orig/docs/conf.py +++ flask-restful-0.3.3/docs/conf.py @@ -255,9 +255,4 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' -intersphinx_mapping = { - 'flask': ('http://flask.pocoo.org/docs/', None), - 'python': ('https://docs.python.org/2/', None), - 'python3': ('https://docs.python.org/3/', None), - 'six': ('http://pythonhosted.org/six/', None), -} +intersphinx_mapping = {'flask': ('http://flask.pocoo.org/docs/', None)} --- flask-restful-0.3.3.orig/docs/contents.rst.inc +++ flask-restful-0.3.3/docs/contents.rst.inc @@ -12,7 +12,6 @@ reqparse fields extending - intermediate-usage API Reference ------------- --- flask-restful-0.3.3.orig/docs/extending.rst +++ flask-restful-0.3.3/docs/extending.rst @@ -3,7 +3,8 @@ Extending Flask-RESTful ======================= -.. currentmodule:: flask_restful +.. currentmodule:: flask.ext.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 @@ -14,38 +15,30 @@ ------------------- 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 +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 +mediatypes to your api, you’ll need to declare your supported representations on the :class:`~Api` object. :: app = Flask(__name__) api = restful.Api(app) @api.representation('application/json') - def output_json(data, code, headers=None): + def json(data, code, headers): resp = make_response(json.dumps(data), code) - resp.headers.extend(headers or {}) + resp.headers.extend(headers) 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. - 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 based on the data your own data types. Fields ~~~~~~ @@ -57,7 +50,7 @@ class AllCapsString(fields.Raw): def format(self, value): return value.upper() - + # example usage fields = { @@ -69,7 +62,7 @@ ~~~~~~ For parsing arguments, you might want to perform custom validation. Creating -your own input types lets you extend request parsing with ease. :: +your own input type lets you extend request parsing with ease. :: def odd_number(value): if value % 2 == 0: @@ -77,8 +70,7 @@ 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. :: +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: @@ -86,7 +78,7 @@ return value -You can also convert public parameter values to internal representations: :: +You can also convert public parameter values to internal representations :: # maps the strings to their internal integer representation # 'init' => 0 @@ -98,8 +90,7 @@ return statuses.index(value) -Then you can use these custom input types in your -:class:`~reqparse.RequestParser`: :: +Then you can use these custom types in your RequestParser :: parser = reqparse.RequestParser() parser.add_argument('OddNumber', type=odd_number) @@ -110,9 +101,8 @@ 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. :: +To support other representations (like xml, csv, html) you can use the +:meth:`~Api.representation` decorator. You need to have a reference to your api :: api = restful.Api(app) @@ -126,41 +116,40 @@ ``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. :: +response. Your output function should return a 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, - } + 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. :: +There is a property on the :meth:`~flask.ext.restful.Resource` 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: @@ -171,7 +160,7 @@ class Resource(restful.Resource): - method_decorators = [authenticate] # applies to all inherited resources + method_decorators = [authenticate] # applies to all inherited resources Since Flask-RESTful Resources are actually Flask view objects, you can also use standard `flask view decorators `_. @@ -183,11 +172,11 @@ 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` +Flask-RESTful will call the :meth:`~flask.ext.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. :: +`catch_all_404s` parameter of the :class:`~flask.ext.restful.Api` constructor :: app = Flask(__name__) api = flask_restful.Api(app, catch_all_404s=True) @@ -205,30 +194,3 @@ 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.3.orig/docs/fields.rst +++ flask-restful-0.3.3/docs/fields.rst @@ -3,13 +3,13 @@ Output Fields =============== -.. currentmodule:: flask_restful +.. currentmodule:: flask.ext.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 +your response. With the ``fields`` module, you can use whatever objects (ORM +models/custom classes/etc) you want in your resource and ``fields`` 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 @@ -18,22 +18,21 @@ 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) :: +You can define a dict 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 Strings and +one is a DateTime (formatted as rfc822 date strings) :: - from flask_restful import Resource, fields, marshal_with + from flask.ext.restful import Resource, fields, marshal_with resource_fields = { 'name': fields.String, 'address': fields.String, - 'date_updated': fields.DateTime(dt_format='rfc822'), + 'date_updated': fields.DateTime, } - + class Todo(Resource): - @marshal_with(resource_fields, envelope='resource') + @marshal_with(resource_fields) def get(self, **kwargs): return db_get_todo() # Some function that queries the db @@ -41,42 +40,27 @@ 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 :: +output. - class Todo(Resource): - def get(self, **kwargs): - return marshal(db_get_todo(), resource_fields), 200 +The decorator ``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. -This explicit expression can be used to return HTTP status codes other than 200 -along with a successful response (see :func:`abort` for errors). +Note: marshal_with is a convenience decorator, that is functionally equivalent to the following ``return marshal(db_get_todo(), resource_fields), 200``. +This explicit expression can be used to return other HTTP status codes than 200 along with a successful response (see ``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. :: +Often times your public facing field name is different from your internal +attribute naming. To configure this mapping, use the ``attribute`` kwarg. :: 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, - } - Default Values -------------- @@ -94,16 +78,16 @@ ------------------------------- 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 +``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. :: +"Normal" or "Urgent" item, and bit 2 signifies "Read" vs "Unread". These +items might be easy to store in a bitfield, but for human readable output it's +nice to convert them to seperate string fields. :: class UrgentItem(fields.Raw): def format(self, value): @@ -111,7 +95,7 @@ class UnreadItem(fields.Raw): def format(self, value): - return "Unread" if value & 0x02 else "Read" + return "Unread" if value & 0x02 else "Read" fields = { 'name': fields.String, @@ -119,42 +103,42 @@ 'status': UnreadItem(attribute='flags'), } -Url & Other Concrete Fields ---------------------------- +Url Field +--------- -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.:: +Flask-RESTful includes a special field, ``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): + def format(self, value): return random.random() fields = { 'name': fields.String, - # todo_resource is the endpoint name when you called api.add_resource() + # 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:: + + +By default ``fields.Url`` returns a relative uri. To generate an absolute uri that includes +the scheme, hostname and port pass ``absolute = True`` in the field declaration. :: fields = { - 'uri': fields.Url('todo_resource', absolute=True) - 'https_uri': fields.Url('todo_resource', absolute=True, scheme='https') + 'uri': fields.Url('todo_resource', external = True) } + Complex Structures ------------------ -You can have a flat structure that :meth:`marshal` will -transform to a nested structure :: +You can have a flat structure that marshal_with will transform to a nested structure :: - >>> from flask_restful import fields, marshal + >>> from flask.ext.restful import fields, marshal >>> import json >>> >>> resource_fields = {'name': fields.String} @@ -165,23 +149,21 @@ >>> 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)) + >>> json.dumps(marshal(data, 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 +the sub-fields can access attributes directly of 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 + >>> from flask.ext.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)) @@ -193,19 +175,19 @@ ----------------------- 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 +response, you can use ``Nested`` to unmarshal nested data structures and render them appropriately. :: - >>> from flask_restful import fields, marshal + >>> from flask.ext.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) @@ -213,16 +195,15 @@ >>> 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.input 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. +This example uses two Nested fields. The ``Nested`` constructor takes a dict of +fields to render as sub-fields. The important difference when using ``Nested`` +vs nested dicts as the previous example is the context for attributes. In +this example "billing_address" is a complex object that has it's own fields, +the contexted 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. --- flask-restful-0.3.3.orig/docs/index.rst +++ flask-restful-0.3.3/docs/index.rst @@ -3,11 +3,11 @@ Flask-RESTful ============= -.. module:: flask_restful +.. module:: flask.ext.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. +setup. If you are familiar with Flask, Flask-RESTful should be easy to pick up. .. include:: contents.rst.inc --- flask-restful-0.3.3.orig/docs/installation.rst +++ flask-restful-0.3.3/docs/installation.rst @@ -3,7 +3,7 @@ Installation ============ -.. currentmodule:: flask_restful +.. currentmodule:: flask.ext.restful Install Flask-RESTful with ``pip`` :: @@ -11,12 +11,12 @@ The development version can be downloaded from `its page at GitHub -`_. :: +`_. :: - git clone https://github.com/flask-restful/flask-restful.git + git clone https://github.com/twilio/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``): @@ -24,4 +24,4 @@ * `Flask `_ version 0.8 or greater -Flask-RESTful requires Python version 2.6, 2.7, 3.3, or 3.4. +Flask-RESTful requires Python version 2.6, 2.7, or 3.3. --- flask-restful-0.3.3.orig/docs/quickstart.rst +++ flask-restful-0.3.3/docs/quickstart.rst @@ -3,12 +3,12 @@ Quickstart ========== -.. currentmodule:: flask_restful +.. currentmodule:: flask.ext.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. +It1s time to write your first REST API. This guide assumes you have `Flask +`_ and Flask-RESTful installed and a working +understanding of Flask. If not, follow the steps in the Installation section, +or read through the Flask Quickstart. @@ -18,12 +18,12 @@ A minimal Flask-RESTful API looks like this: :: from flask import Flask - from flask_restful import Resource, Api + from flask.ext import restful app = Flask(__name__) - api = Api(app) + api = restful.Api(app) - class HelloWorld(Resource): + class HelloWorld(restful.Resource): def get(self): return {'hello': 'world'} @@ -35,15 +35,11 @@ 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. :: +mode to provide code reloading and better error messages. Debug mode should +never be used in a production environment. :: $ 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 :: @@ -57,12 +53,12 @@ ------------------- 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 +giving you easy access to multiple HTTP methods just be 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 + from flask.ext.restful import Resource, Api app = Flask(__name__) api = Api(app) @@ -84,27 +80,26 @@ 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:: + You can try this example as follow: + $ 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 breakpads" -X PUT + {"todo2": "Change my breakpads"} + $ curl http://localhost:5000/todo2 + {"todo2": "Change my breakpads"} + 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'} + >>> 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 brakepads'} + {u'todo2': u'Change my breakpads'} 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 @@ -132,8 +127,8 @@ --------- 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` :: +multiple URLs to the :py:meth:`Api.add_resource` method on the Api object. Each one +will be routed to your :py:class:`Resource` :: api.add_resource(HelloWorld, '/', @@ -152,7 +147,7 @@ has built-in support for request data validation using a library similar to `argparse `_. :: - from flask_restful import reqparse + from flask.ext.restful import reqparse parser = reqparse.RequestParser() parser.add_argument('rate', type=int, help='Rate to charge for this resource') @@ -160,37 +155,33 @@ Note that unlike the argparse module, -:meth:`reqparse.RequestParser.parse_args` returns a Python dictionary +:py: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 +Using the :py: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 + $ curl -d 'rate=foo' http://127.0.0.1:5000/ {'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. +The :py:class:`types` module provides a number of included common conversion +functions such as :py:meth:`types.date` and :py:meth:`types.url`. - args = parser.parse_args(strict=True) Data Formatting --------------- -By default, all fields in your return iterable will be rendered as-is. While +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. :: +it can become very frustrating when working with objects. To solve with +problem, Flask-RESTful provides the :py:class:`fields` module and the +:py: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 + from flask.ext.restful import fields, marshal_with resource_fields = { 'task': fields.String, @@ -211,11 +202,11 @@ 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 +:py: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 +:py: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 :py:class:`fields` guide for a complete list. Full Example @@ -224,7 +215,7 @@ Save this example in api.py :: from flask import Flask - from flask_restful import reqparse, abort, Api, Resource + from flask.ext.restful import reqparse, abort, Api, Resource app = Flask(__name__) api = Api(app) @@ -245,7 +236,7 @@ # Todo - # shows a single todo item and lets you delete a todo item + # 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) @@ -264,15 +255,14 @@ # TodoList - # shows a list of all todos, and lets you POST to add new tasks + # 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 + todo_id = 'todo%d' % (len(TODOS) + 1) TODOS[todo_id] = {'task': args['task']} return TODOS[todo_id], 201 @@ -280,7 +270,7 @@ ## Actually setup the Api resource routing here ## api.add_resource(TodoList, '/todos') - api.add_resource(Todo, '/todos/') + api.add_resource(Todo, '/todos/') if __name__ == '__main__': @@ -342,9 +332,9 @@ Update a task :: - $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v + $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X POST -v - > PUT /todos/todo3 HTTP/1.1 + > POST /todos/todo3 HTTP/1.1 > Host: localhost:5000 > Accept: */* > Content-Length: 20 --- flask-restful-0.3.3.orig/docs/reqparse.rst +++ flask-restful-0.3.3/docs/reqparse.rst @@ -3,31 +3,31 @@ Request Parsing =============== -.. currentmodule:: flask_restful +.. currentmodule:: flask.ext.restful + +Flask-RESTful's request parsing interface is modeled after the ``argparse`` +interface. It's designed to provide simple and uniform access to any +variable on the :py:class:`flask.request` object in Flask. -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: one of type ``int``, and the other of +the :py:attr:`flask.Request.values` dict. One of type ``int``, and the other of type ``str`` :: - from flask_restful import reqparse - + from flask.ext.restful import reqparse + parser = reqparse.RequestParser() parser.add_argument('rate', type=int, help='Rate cannot be converted') parser.add_argument('name', type=str) args = parser.parse_args() -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. +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. By default, arguments are **not** required. Also, arguments supplied in the request that are not part of the RequestParser will be ignored. @@ -35,13 +35,14 @@ 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`. :: +the call to :py:meth:`~reqparse.RequestParser.add_argument`. :: - parser.add_argument('name', type=str, required=True, + parser.add_argument('name', type=str, required=True, help="Name cannot be blank!") Multiple Values & Lists @@ -54,7 +55,7 @@ This will let you make queries like :: - curl http://api.example.com -d "name=bob" -d "name=sue" -d "name=joe" + curl http://api.example.com -d "Name=bob" -d "Name=sue" -d "Name=joe" And your args will look like this :: @@ -65,22 +66,24 @@ ------------------ 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. :: +it's parsed, you can use the ``dest`` kwarg. :: parser.add_argument('name', type=str, dest='public_name') args = parser.parse_args() - args['public_name'] + 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`. +Other Locations +--------------- -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: :: +By default, the :py:class:`~reqparse.RequestParser` tries to parse values +from :py:attr:`flask.Request.values`. This dict will contain both querystring +arguments and POST/PUT body arguments. + +:py:meth:`~reqparse.RequestParser.add_argument` lets you specify +alternate locations to pull the values from. Any variable on the +:py:class:`flask.Request` can be used. For example: :: # Look only in the POST body parser.add_argument('name', type=int, location='form') @@ -97,88 +100,3 @@ # From file uploads parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files') -Multiple Locations ------------------- - -Multiple argument locations can be specified by passing a list to ``location``:: - - parser.add_argument('text', location=['headers', 'values']) - -The last ``location`` listed takes precedence in the result set. - -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 RequestParser - - parser = 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', type=str, 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 RequestParser - - parser = 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_ERRRORS] = True - -Note: If ``BUNDLE_ERRORS`` is set on the application, setting ``bundle_errors`` -to ``False`` in the RequestParser keyword argument will not work. --- flask-restful-0.3.3.orig/docs/testing.rst +++ flask-restful-0.3.3/docs/testing.rst @@ -3,29 +3,15 @@ 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:: +To run the Flask-RESTful test suite, you need to do two things. - $ make test +1. Install the extra required dependency :: -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``. + pip install -e '.[paging]' --use-mirrors -You can run on all supported versions with:: +2. Run the tests :: - $ make test-all + python setup.py nosetests -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 +Alternately, push changes to your fork on Github, and 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.3.orig/examples/todo.py +++ flask-restful-0.3.3/examples/todo.py @@ -1,5 +1,5 @@ from flask import Flask -from flask_restful import reqparse, abort, Api, Resource +from flask.ext.restful import reqparse, abort, Api, Resource app = Flask(__name__) api = Api(app) --- flask-restful-0.3.3.orig/examples/todo_simple.py +++ flask-restful-0.3.3/examples/todo_simple.py @@ -1,5 +1,5 @@ from flask import Flask, request -from flask_restful import Resource, Api +from flask.ext.restful import Resource, Api app = Flask(__name__) api = Api(app) --- flask-restful-0.3.3.orig/examples/xml_representation.py +++ flask-restful-0.3.3/examples/xml_representation.py @@ -1,7 +1,7 @@ # needs: pip install python-simplexml from simplexml import dumps from flask import make_response, Flask -from flask_restful import Api, Resource +from flask.ext.restful import Api, Resource def output_xml(data, code, headers=None): """Makes a Flask response with a XML encoded body""" --- flask-restful-0.3.3.orig/flask_restful/__init__.py +++ flask-restful-0.3.3/flask_restful/__init__.py @@ -1,24 +1,23 @@ -from __future__ import absolute_import import difflib from functools import wraps, partial import re -from flask import request, url_for, current_app +from flask import request, Response, url_for 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.exceptions import HTTPException, MethodNotAllowed, NotFound, NotAcceptable, InternalServerError +from werkzeug.exceptions import HTTPException, MethodNotAllowed, NotFound from werkzeug.http import HTTP_STATUS_CODES -from werkzeug.wrappers import Response as ResponseBase -from flask_restful.utils import error_data, unpack, OrderedDict -from flask_restful.representations.json import output_json +from flask.ext.restful.utils import unauthorized, error_data, unpack +from flask.ext.restful.representations.json import output_json import sys -from flask.helpers import _endpoint_from_view_func -from types import MethodType -import operator +try: + #noinspection PyUnresolvedReferences + from collections import OrderedDict +except ImportError: + from .utils.ordereddict import OrderedDict -__all__ = ('Api', 'Resource', 'marshal', 'marshal_with', 'marshal_with_field', 'abort') +__all__ = ('Api', 'Resource', 'marshal', 'marshal_with', 'abort') def abort(http_status_code, **kwargs): @@ -31,7 +30,7 @@ except HTTPException as e: if len(kwargs): e.data = kwargs - raise + raise e DEFAULT_REPRESENTATIONS = {'application/json': output_json} @@ -57,161 +56,44 @@ :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): + catch_all_404s=False): self.representations = dict(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) + else: + self.app = None def init_app(self, app): """Initialize this class with the given :class:`flask.Flask` - application or :class:`flask.Blueprint` object. + application object. - :param app: the Flask application or blueprint object + :param app: the Flask application object :type app: flask.Flask - :type app: flask.Blueprint Examples:: api = Api() - api.add_resource(...) api.init_app(app) + api.add_resource(...) """ - # 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 - """ + self.app = app + self.endpoints = set() 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 @@ -222,7 +104,7 @@ :return: bool """ - adapter = current_app.create_url_adapter(request) + adapter = self.app.create_url_adapter(request) try: adapter.match() @@ -230,31 +112,27 @@ # 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) + return rule.endpoint in self.endpoints 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) + return request.url_rule and request.url_rule.endpoint in self.endpoints 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 @@ -263,10 +141,7 @@ """ if self._has_fr_route(): - try: - return self.handle_error(e) - except Exception: - pass # Fall through to original handler + return self.handle_error(e) return original_handler(e) def handle_error(self, e): @@ -277,32 +152,31 @@ :type e: Exception """ - got_request_exception.send(current_app._get_current_object(), exception=e) + got_request_exception.send(self.app, exception=e) - if not isinstance(e, HTTPException) and current_app.propagate_exceptions: + if not hasattr(e, 'code') and self.app.propagate_exceptions: exc_type, exc_value, tb = sys.exc_info() if exc_value is e: - raise + raise exc_type, exc_value, tb else: raise e + code = getattr(e, 'code', 500) data = getattr(e, 'data', error_data(code)) - headers = {} if code >= 500: # There's currently a bug in Python3 that disallows calling # logging.exception() when an exception hasn't actually be raised if sys.exc_info() == (None, None, None): - current_app.logger.error("Internal Error") + self.app.logger.error("Internal Error") else: - current_app.logger.exception("Internal Error") + self.app.logger.exception("Internal Error") - help_on_404 = current_app.config.get("ERROR_404_HELP", True) - if code == 404 and help_on_404 and ('message' not in data or - data['message'] == HTTP_STATUS_CODES[404]): + if code == 404 and ('message' not in data or + data['message'] == HTTP_STATUS_CODES[404]): rules = dict([(re.sub('(<.*>)', '', rule.rule), rule.rule) - for rule in current_app.url_map.iter_rules()]) + for rule in self.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. @@ -312,38 +186,16 @@ data["message"] = "" data['message'] += 'You have requested this URI [' + request.path + \ - '] but did you mean ' + \ - ' or '.join(( - rules[match] for match in close_matches) - ) + ' ?' - - if code == 405: - headers['Allow'] = e.valid_methods - - 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) + '] but did you mean ' + \ + ' or '.join((rules[match] + for match in close_matches)) + ' ?' + + resp = self.make_response(data, code) if code == 401: - resp = self.unauthorized(resp) + resp = unauthorized(resp, + self.app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful")) + return resp def mediatypes_method(self): @@ -365,14 +217,6 @@ 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`. @@ -383,39 +227,11 @@ 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', {}) - if endpoint in app.view_functions.keys(): - previous_view_class = app.view_functions[endpoint].__dict__['view_class'] + if endpoint in self.app.view_functions.keys(): + previous_view_class = self.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: @@ -423,33 +239,14 @@ resource.mediatypes = self.mediatypes_method() # Hacky resource.endpoint = endpoint - resource_func = self.output(resource.as_view(endpoint, *resource_class_args, - **resource_class_kwargs)) + resource_func = self.output(resource.as_view(endpoint)) 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) + self.app.add_url_rule(self.prefix + url, view_func=resource_func, **kwargs) def output(self, resource): """Wraps a resource (as a flask view function), for cases where the @@ -460,52 +257,33 @@ @wraps(resource) def wrapper(*args, **kwargs): resp = resource(*args, **kwargs) - if isinstance(resp, ResponseBase): # There may be a better way to test + if isinstance(resp, Response): # 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) - + """Generates a URL to the given resource.""" + return url_for(resource.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 + defaults to (application/json) if no transformer is found for the + requested mediatype. :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() + for mediatype in self.mediatypes() + [self.default_mediatype]: + if mediatype in self.representations: + resp = self.representations[mediatype](data, *args, **kwargs) + resp.headers['Content-Type'] = mediatype + return resp 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)] + return [h for h, q in request.accept_mimetypes] def representation(self, mediatype): """Allows additional representation transformers to be declared for the @@ -533,26 +311,15 @@ 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. + 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 Api.add_resource for details. """ representations = None method_decorators = [] @@ -571,64 +338,61 @@ resp = meth(*args, **kwargs) - if isinstance(resp, ResponseBase): # There may be a better way to test + if isinstance(resp, Response): # There may be a better way to test return resp representations = self.representations or {} #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 + for mediatype in self.mediatypes(): + 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): +def marshal(data, fields, display_null=True): """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 - + :param data: the actual object(s) from which the fields are taken from + :param display_null: Whether to display or not key : value + when value is null - >>> from flask_restful import fields, marshal + >>> from flask.ext.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]) + return [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) + items = [] + for k, v in fields.items(): + tmp = marshal(data, v, display_null) if isinstance(v, dict)\ + else make(v).output(k, data) + if display_null or not(tmp is None): + items.append((k, tmp)) + return 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 + >>> from flask.ext.restful import fields, marshal_with >>> mfields = { 'a': fields.Raw } >>> @marshal_with(mfields) ... def get(): @@ -638,25 +402,13 @@ >>> 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` + see :meth:`flask.ext.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 - """ + def __init__(self, fields, display_null=False): + """:param fields: a dict of whose keys will make up the final + serialized response output""" self.fields = fields - self.envelope = envelope + self.display_null = display_null def __call__(self, f): @wraps(f) @@ -664,43 +416,7 @@ resp = f(*args, **kwargs) if isinstance(resp, tuple): data, code, headers = unpack(resp) - return marshal(data, self.fields, self.envelope), code, headers + return marshal(data, self.fields, self.display_null), 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 marshal(resp, self.fields, self.display_null) return wrapper --- flask-restful-0.3.3.orig/flask_restful/fields.py +++ flask-restful-0.3.3/flask_restful/fields.py @@ -1,8 +1,4 @@ -from 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 @@ -10,12 +6,11 @@ # python3 from urllib.parse import urlparse, urlunparse -from flask_restful import inputs, marshal -from flask import url_for, request +from flask_restful import types, marshal +from flask import url_for __all__ = ["String", "FormattedString", "Url", "DateTime", "Float", - "Integer", "Arbitrary", "Nested", "List", "Raw", "Boolean", - "Fixed", "Price"] + "Integer", "Arbitrary", "Nested", "List", "Raw"] class MarshallingException(Exception): @@ -30,15 +25,13 @@ def is_indexable_but_not_string(obj): - return not hasattr(obj, "strip") and hasattr(obj, "__iter__") + return not hasattr(obj, "strip") and hasattr(obj, "__getitem__") def get_value(key, obj, default=None): """Helper for pulling a keyed value off various types of objects""" if type(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) @@ -55,9 +48,11 @@ if is_indexable_but_not_string(obj): try: return obj[key] - except (IndexError, TypeError, KeyError): - pass - return getattr(obj, key, default) + except KeyError: + return default + if hasattr(obj, key): + return getattr(obj, key) + return default def to_marshallable_type(obj): @@ -79,13 +74,7 @@ """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. + throw a MarshallingException in case of parsing problem. """ def __init__(self, default=None, attribute=None): @@ -93,8 +82,7 @@ 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 + """Formats a field's value. No-op by default, concrete fields should override this and apply the appropriate formatting. :param value: The value to format @@ -110,11 +98,7 @@ 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. - + field's formatting and returns the result. :exception MarshallingException: In case of formatting problem """ @@ -133,84 +117,54 @@ :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) + :param bool display: Params pass to marshal to display null object or not """ - def __init__(self, nested, allow_null=False, **kwargs): + def __init__(self, nested, allow_null=False, display_null=True, **kwargs): self.nested = nested self.allow_null = allow_null + self.display_null = display_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) - + if self.allow_null and value is None: + return None + return marshal(value, self.nested, self.display_null) 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): + def __init__(self, cls_or_instance, display_empty=True, **kwargs): super(List, self).__init__(**kwargs) - error_msg = ("The type of the list elements must be a subclass of " - "flask_restful.fields.Raw") + self.display_empty = display_empty if isinstance(cls_or_instance, type): if not issubclass(cls_or_instance, Raw): - raise MarshallingException(error_msg) + raise MarshallingException("The type of the list elements " + "must be a subclass of " + "flask_restful.fields.Raw") self.container = cls_or_instance() else: if not isinstance(cls_or_instance, Raw): - raise MarshallingException(error_msg) + raise MarshallingException("The instances of the list " + "elements must be of type " + "flask_restful.fields.Raw") 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) + # Convert all instances in typed list to container type + if not self.display_empty and len(value) == 0: + return None + return [self.container.output(idx, value) for idx, val + in enumerate(value)] if value is None: return self.default - - return [marshal(value, self.container.nested)] + return [marshal(value, self.container.nested, self.display_empty)] 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) @@ -219,13 +173,8 @@ 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 __init__(self, default=0, attribute=None): + super(Integer, self).__init__(default, attribute) def format(self, value): try: @@ -237,39 +186,12 @@ 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) @@ -284,30 +206,18 @@ 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): + def __init__(self, endpoint, absolute = False): 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)) + o = urlparse(url_for(self.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.scheme, o.netloc, o.path, "", "", "")) return urlunparse(("", "", o.path, "", "", "")) except TypeError as te: raise MarshallingException(te) @@ -316,13 +226,12 @@ class Float(Raw): """ A double as IEEE-754 double precision. - ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf - -inf + ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf -inf """ def format(self, value): try: - return float(value) + return repr(float(value)) except ValueError as ve: raise MarshallingException(ve) @@ -338,32 +247,11 @@ 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 + """Return a RFC822-formatted datetime string in UTC""" 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 - ) + return types.rfc822(value) except AttributeError as ae: raise MarshallingException(ae) @@ -371,9 +259,6 @@ 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') @@ -384,34 +269,4 @@ 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.3.orig/flask_restful/representations/json.py +++ flask-restful-0.3.3/flask_restful/representations/json.py @@ -7,7 +7,6 @@ # function, used below. settings = {} - def output_json(data, code, headers=None): """Makes a Flask response with a JSON encoded body""" --- flask-restful-0.3.3.orig/flask_restful/reqparse.py +++ flask-restful-0.3.3/flask_restful/reqparse.py @@ -1,73 +1,49 @@ -from copy import deepcopy -from flask import current_app, request -from werkzeug.datastructures import MultiDict, FileStorage -from werkzeug import exceptions +from flask import request +from werkzeug.datastructures import MultiDict import flask_restful -import decimal -import inspect 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 with the name of the argument and - the message passed to any exception raised by a 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. - """ - def __init__(self, name, default=None, dest=None, required=False, - ignore=False, type=text_type, location=('json', 'values',), + ignore=False, type=six.text_type, location=('values',), choices=(), action='store', help=None, operators=('=',), - case_sensitive=True, store_missing=True, trim=False): + case_sensitive=True): + """ + :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 parse_args(req). + :param 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. + :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 a ValidationError, the message in the + error will be returned in the response. + :param location: Where to source the arguments from the Flask request + (ex: headers, args, etc.), can be an iterator + :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. This takes precedence over + the message passed to a ValidationError raised by a type converter. + :param case_sensitive: Whether the arguments in the request are case + sensitive or not + """ + self.name = name self.default = default self.dest = dest @@ -80,8 +56,6 @@ self.help = help self.case_sensitive = case_sensitive self.operators = operators - self.store_missing = store_missing - self.trim = trim def source(self, request): """Pulls values off the request in the provided location @@ -94,72 +68,43 @@ 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 value return MultiDict() def convert(self, value, op): - # Don't cast None - if value is None: - return None - - # 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) + return self.type(value, self.name) except TypeError: return self.type(value) - def handle_validation_error(self, error, bundle_errors): + def handle_validation_error(self, error): """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 """ - help_str = '(%s) ' % self.help if self.help else '' - error_msg = ' '.join([help_str, str(error)]) if help_str else str(error) - if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors: - msg = {self.name: "%s" % (error_msg)} - return error, msg - msg = {self.name: "%s" % (error_msg)} + msg = self.help if self.help is not None else str(error) flask_restful.abort(400, message=msg) - def parse(self, request, bundle_errors=False): + def parse(self, request): """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: @@ -170,62 +115,43 @@ values = [source.get(name)] for value in values: - if hasattr(value, "strip") and self.trim: - value = value.strip() - if hasattr(value, "lower") and not self.case_sensitive: + if not self.case_sensitive: value = value.lower() - - if hasattr(self.choices, "__iter__"): - self.choices = [choice.lower() - for choice in self.choices] - + if self.choices and value not in self.choices: + self.handle_validation_error(ValueError( + u"{0} is not a valid choice".format(value))) 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) + self.handle_validation_error(error) - 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) + error_msg = u"{0} is required in {1}".format( + self.name, + 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) + error_msg = u"{0} is required in {1}".format( + self.name, + ' or '.join(self.location) ) - 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) + self.handle_validation_error(ValueError(error_msg)) if not results: - if callable(self.default): - return self.default(), _not_found - else: - return self.default, _not_found + return self.default if self.action == 'append': - return results, _found + return results if self.action == 'store' or len(results) == 1: - return results[0], _found - return results, _found + return results[0] + return results class RequestParser(object): @@ -238,95 +164,31 @@ 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): + def __init__(self, argument_class=Argument, namespace_class=Namespace): 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. + """Adds an argument to be parsed. 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 = True - + self.args.append(self.argument_class(*args, **kwargs)) return self - def parse_args(self, req=None, strict=False): + def parse_args(self, req=None): """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())) + namespace[arg.dest or arg.name] = arg.parse(req) 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) - 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.3.orig/flask_restful/types.py +++ flask-restful-0.3.3/flask_restful/types.py @@ -0,0 +1,63 @@ +from datetime import datetime +from email.utils import formatdate +import re + +# https://code.djangoproject.com/browser/django/trunk/django/core/validators.py +# basic auth added by frank +from calendar import timegm + +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 regex.search(value): + message = u"{0} is not a valid URL".format(value) + if regex.search('http://' + value): + message += u". Did you mean: http://{0}".format(value) + raise ValueError(message) + return value + + +def date(value): + """Parse a valid looking date in the format YYYY-mm-dd""" + date = datetime.strptime(value, "%Y-%m-%d") + if date.year < 1900: + raise ValueError(u"Year must be >= 1900") + return date + + +def natural(value): + """Parse a non-negative integer value""" + value = int(value) + if value < 0: + raise ValueError("Invalid literal for natural(): '{}'".format(value)) + return value + + +def boolean(value): + """Parse the string "true" or "false" as a boolean (case insensitive)""" + value = value.lower() + if value == 'true': + return True + if value == 'false': + return False + raise ValueError("Invalid literal for boolean(): {}".format(value)) + + +def rfc822(dt): + return formatdate(timegm(dt.utctimetuple())) --- flask-restful-0.3.3.orig/flask_restful/utils/__init__.py +++ flask-restful-0.3.3/flask_restful/utils/__init__.py @@ -1,16 +1,21 @@ -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict - from werkzeug.http import HTTP_STATUS_CODES - def http_status_message(code): """Maps an HTTP status code to the textual status""" return HTTP_STATUS_CODES.get(code, '') +def challenge(authentication, realm): + """Constructs the string to be sent in the WWW-Authenticate header""" + return u"{0} realm=\"{1}\"".format(authentication, realm) + + +def unauthorized(response, realm): + """ Given a response, change it to ask for credentials""" + response.headers['WWW-Authenticate'] = challenge("Basic", realm) + return response + + def error_data(code): """Constructs a dictionary with status and message for returning in an error response""" --- flask-restful-0.3.3.orig/flask_restful/utils/crypto.py +++ flask-restful-0.3.3/flask_restful/utils/crypto.py @@ -2,22 +2,18 @@ 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") @@ -25,11 +21,9 @@ 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.3.orig/flask_restful/utils/ordereddict.py +++ flask-restful-0.3.3/flask_restful/utils/ordereddict.py @@ -0,0 +1,130 @@ +# Copyright (c) 2009 Raymond Hettinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +from UserDict import DictMixin +import six + +class OrderedDict(dict, DictMixin): + + #noinspection PyMissingConstructor + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = six.advance_iterator(reversed(self)) + else: + key = six.advance_iterator(iter(self)) + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return self.__class__, (items,), inst_dict + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + #noinspection PyMethodOverriding + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other --- flask-restful-0.3.3.orig/scripts/release.py +++ flask-restful-0.3.3/scripts/release.py @@ -19,9 +19,21 @@ 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") + raise Exception("Uncommited changes, please commit or stash") - subprocess.call(["git", "tag", args.version]) + new_version = point_release(args.version) + option = "version='{}'" + + setup = open('setup.py').read() + + with open('setup.py', 'w') as f: + f.write(setup.replace(option.format(args.version), + option.format(new_version))) + + subprocess.call(["git", "add", "setup.py"]) + subprocess.call(["git", "commit", "-am", + "Bump to version {}".format(new_version)]) + subprocess.call(["git", "tag", new_version]) subprocess.call(["git", "push", "origin", "master"]) subprocess.call(["git", "push", "--tags"]) subprocess.call(["python", "setup.py", "sdist", "upload"]) --- flask-restful-0.3.3.orig/setup.py +++ flask-restful-0.3.3/setup.py @@ -1,33 +1,28 @@ #!/usr/bin/env python from setuptools import setup, find_packages -import sys - -PY26 = sys.version_info[:2] == (2, 6,) - -requirements = [ - 'aniso8601>=0.82', - 'Flask>=0.8', - 'six>=1.3.0', - 'pytz', -] -if PY26: - requirements.append('ordereddict') setup( name='Flask-RESTful', - version='0.3.3', - url='https://www.github.com/flask-restful/flask-restful/', - author='Twilio API Team', + version='0.2.5', + url='https://www.github.com/twilio/flask-restful/', + author='Kyle Conroy', author_email='help@twilio.com', description='Simple framework for creating REST APIs', - packages=find_packages(exclude=['tests']), + packages=find_packages(), 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'], + #setup_requires=[ + # 'nose==1.3.1', + # 'mock==1.0.1', + # 'six==1.5.2', + # 'blinker==1.3', + #], + install_requires=[ + 'Flask==0.10.1', + ], # Install these with "pip install -e '.[paging]'" or '.[docs]' extras_require={ 'paging': 'pycrypto>=2.6', --- flask-restful-0.3.3.orig/tests/__init__.py +++ flask-restful-0.3.3/tests/__init__.py @@ -1,17 +0,0 @@ -#!/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.3.orig/tests/test_api.py +++ flask-restful-0.3.3/tests/test_api.py @@ -1,5 +1,5 @@ import unittest -from flask import Flask, Blueprint, redirect, views +from flask import Flask, views from flask.signals import got_request_exception, signals_available try: from mock import Mock, patch @@ -8,20 +8,18 @@ from unittest.mock import Mock, patch import flask import werkzeug -from flask_restful.utils import http_status_message, error_data, unpack +from flask.ext.restful.utils import http_status_message, challenge, unauthorized, error_data, unpack import flask_restful import flask_restful.fields from flask_restful import OrderedDict from json import dumps, loads #noinspection PyUnresolvedReferences -from nose.tools import assert_equals, assert_true, assert_false # you need it for tests in form of continuations +from nose.tools import assert_equals, assert_true # 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",)) @@ -29,63 +27,40 @@ 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-Autheneticate' in response.headers) + + def test_challenge(self): + self.assertEquals(challenge('Basic', 'Foo'), 'Basic realm="Foo"') + 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) + unauthorized(response, "flask-restful") self.assertEquals(response.headers['WWW-Authenticate'], - 'Basic realm="flask-restful"') + '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) + unauthorized(response, realm='Foo') 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) - exception = Mock() - exception.code = 401 - exception.data = {'foo': 'bar'} - - with app.test_request_context('/foo'): - resp = api.handle_error(exception) - 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) + api = flask_restful.Api(app) exception = Mock() exception.code = 401 exception.data = {'foo': 'bar'} @@ -94,12 +69,13 @@ resp = api.handle_error(exception) self.assertEquals(resp.status_code, 401) self.assertEquals(resp.headers['WWW-Authenticate'], - 'Basic realm="flask-restful"') + '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) + api = flask_restful.Api(app) exception = Mock() exception.code = 401 exception.data = {'foo': 'bar'} @@ -108,13 +84,15 @@ resp = api.handle_error(exception) self.assertEquals(resp.status_code, 401) self.assertEquals(resp.headers['WWW-Authenticate'], - 'Basic realm="test-realm"') + 'Basic realm="test-realm"') + def test_error_data(self): self.assertEquals(error_data(400), { 'status': 400, 'message': 'Bad Request', - }) + }) + def test_marshal(self): fields = OrderedDict([('foo', flask_restful.fields.Raw)]) @@ -122,12 +100,6 @@ 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)]) @@ -136,15 +108,6 @@ 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)]) @@ -153,48 +116,19 @@ 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([ @@ -237,6 +171,37 @@ expected = OrderedDict([('foo', 'bar'), ('fee', None)]) self.assertEquals(output, expected) + + + def test_marshal_nested_with_non_null_and_no_display_empty(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, display_null=False)) + ]) + marshal_fields = [OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', None)])] + output = flask_restful.marshal(marshal_fields, fields, display_null=False) + expected = [OrderedDict([('foo', 'bar'), ('fee', OrderedDict([]))])] + self.assertEquals(output, expected) + + def test_marshal_nested_with_null_and_no_display_empty(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, display_null=False)) + ]) + marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', None)]) + output = flask_restful.marshal(marshal_fields, fields, display_null=False) + expected = OrderedDict([('foo', 'bar')]) + self.assertEquals(output, expected) + + def test_allow_null_presents_data(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), @@ -251,6 +216,22 @@ expected = OrderedDict([('foo', 'bar'), ('fee', OrderedDict([('fye', None), ('blah', 'cool')]))]) self.assertEquals(output, expected) + + def test_allow_null_presents_data_and_no_display_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, display_null=False)) + ]) + marshal_fields = OrderedDict([('foo', 'bar'), ('bat', 'baz'), ('fee', {'blah': 'cool'})]) + output = flask_restful.marshal(marshal_fields, fields) + expected = OrderedDict([('foo', 'bar'), ('fee', OrderedDict([('blah', 'cool')]))]) + self.assertEquals(output, expected) + + def test_marshal_nested_property(self): class TestObject(object): @property @@ -281,6 +262,7 @@ 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), @@ -293,6 +275,7 @@ 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), @@ -304,6 +287,7 @@ expected = OrderedDict([('foo', 'bar'), ('fee', [['fye'], ['fum']])]) self.assertEquals(output, expected) + def test_marshal_nested_dict(self): fields = OrderedDict([ ('foo', flask_restful.fields.Raw), @@ -318,6 +302,7 @@ 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) @@ -328,28 +313,29 @@ 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) + + api.add_resource(HelloWorld, '/', endpoint="hello") + 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) @@ -365,9 +351,10 @@ 'foo': 'bar', })) + def test_handle_auth(self): app = Flask(__name__) - api = flask_restful.Api(app, serve_challenge_on_401=True) + api = flask_restful.Api(app) exception = Mock() exception.code = 401 @@ -376,18 +363,17 @@ with app.test_request_context("/foo"): resp = api.handle_error(exception) self.assertEquals(resp.status_code, 401) - self.assertEquals(resp.data.decode(), dumps({'foo': 'bar'})) + self.assertEquals(resp.data.decode(), dumps({'foo': 'bar',})) 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() @@ -398,6 +384,7 @@ assert_equals(data.get('status'), 404) assert_true('message' in data) + def test_handle_non_api_error(self): app = Flask(__name__) flask_restful.Api(app) @@ -415,11 +402,10 @@ 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 + self.skipTest("Can't test signals without signal support") app = Flask(__name__) api = flask_restful.Api(app) @@ -428,7 +414,6 @@ exception.data = {'foo': 'bar'} recorded = [] - def record(sender, exception): recorded.append(exception) @@ -468,6 +453,7 @@ api.add_resource(view, '/fee', endpoint='bir') api.add_resource(view, '/fii', endpoint='ber') + with app.test_request_context("/faaaaa"): resp = api.handle_error(exception) self.assertEquals(resp.status_code, 404) @@ -490,66 +476,41 @@ "status": 404, "message": "You have requested this URI [/fOo] but did you mean /foo ?", })) - app.config['ERROR_404_HELP'] = False - - with app.test_request_context("/fOo"): - del exception.data["message"] - resp = api.handle_error(exception) - self.assertEquals(resp.status_code, 404) - self.assertEquals(resp.data.decode(), dumps({ - "status": 404 - })) - - 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() - - 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' - }): + 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' - }): + with app.test_request_context("/foo", + headers={'Accept': 'application/xml; q=.5'}): self.assertEquals(api.mediatypes_method()(Mock()), - ['application/xml', 'application/json']) + ['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' - }): + with app.test_request_context("/foo", + headers={'Accept': 'application/json; q=1; application/xml; q=.5'}): self.assertEquals(api.mediatypes(), - ['application/json', 'application/xml']) + ['application/json', 'application/xml']) + def test_decorator(self): def return_zero(func): return 0 - app = Mock(flask.Flask) + app = Mock() app.view_functions = {} view = Mock() api = flask_restful.Api(app) @@ -559,6 +520,7 @@ app.add_url_rule.assert_called_with('/foo', view_func=0) + def test_add_resource_endpoint(self): app = Mock() app.view_functions = {} @@ -602,59 +564,27 @@ foo2 = client.get('/foo/toto') self.assertEquals(foo2.data, b'"foo1"') + def test_add_resource(self): - app = Mock(flask.Flask) + app = Mock() 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()) + view_func=api.output()) def test_add_resource_kwargs(self): - app = Mock(flask.Flask) + app = Mock() 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) + view_func=api.output(), defaults={"bar": "baz"}) - 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"') def test_output_unpack(self): @@ -670,6 +600,7 @@ self.assertEquals(resp.status_code, 200) self.assertEquals(resp.data.decode(), '{"foo": "bar"}') + def test_output_func(self): def make_empty_resposne(): @@ -684,6 +615,7 @@ self.assertEquals(resp.status_code, 200) self.assertEquals(resp.data.decode(), '') + def test_resource(self): app = Flask(__name__) resource = flask_restful.Resource() @@ -691,6 +623,7 @@ with app.test_request_context("/foo"): resource.dispatch_request() + def test_resource_resp(self): app = Flask(__name__) resource = flask_restful.Resource() @@ -699,6 +632,7 @@ resource.get.return_value = flask.make_response('') resource.dispatch_request() + def test_resource_text_plain(self): app = Flask(__name__) @@ -709,7 +643,7 @@ representations = { 'text/plain': text, - } + } def get(self): return 'hello' @@ -719,18 +653,21 @@ 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') @@ -738,6 +675,7 @@ except Exception as e: self.assertEquals(e.data, {'foo': 'bar'}) + def test_abort_no_data(self): try: flask_restful.abort(404) @@ -745,6 +683,7 @@ except Exception as e: self.assertEquals(False, hasattr(e, "data")) + def test_abort_custom_message(self): try: flask_restful.abort(404, message="no user") @@ -752,9 +691,11 @@ except Exception as e: assert_equals(e.data['message'], "no user") + def test_abort_type(self): self.assertRaises(werkzeug.exceptions.HTTPException, lambda: flask_restful.abort(404)) + def test_endpoints(self): app = Flask(__name__) api = flask_restful.Api(app) @@ -765,25 +706,15 @@ 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') + self.assertEqual(api.url_for(HelloWorld, id = 123), '/ids/123') + def test_fr_405(self): app = Flask(__name__) api = flask_restful.Api(app) @@ -792,8 +723,6 @@ resp = app.post('/ids/3') self.assertEquals(resp.status_code, 405) self.assertEquals(resp.content_type, api.default_mediatype) - self.assertEquals(set(resp.headers.get_all('Allow')), - set(['HEAD', 'OPTIONS'] + HelloWorld.methods)) def test_will_prettyprint_json_in_debug_mode(self): app = Flask(__name__) @@ -837,82 +766,20 @@ # 1. Set the settings dict() with some value # 2. Patch the json.dumps function in the module with a Mock object. - from flask_restful.representations import json as json_rep + from flask.ext.restful.representations import json as json_rep json_dumps_mock = Mock(return_value='bar') new_settings = {'indent': 123} with patch.multiple(json_rep, dumps=json_dumps_mock, settings=new_settings): with app.test_client() as client: - client.get('/foo') + foo = client.get('/foo') # Assert that the function was called with the above settings. data, kwargs = json_dumps_mock.call_args self.assertTrue(json_dumps_mock.called) self.assertEqual(123, kwargs['indent']) - 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}') - - 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) - if __name__ == '__main__': unittest.main() --- flask-restful-0.3.3.orig/tests/test_fields.py +++ flask-restful-0.3.3/tests/test_fields.py @@ -1,39 +1,32 @@ from 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.ext.restful.fields import MarshallingException from flask_restful import fields -from datetime import datetime, timedelta, tzinfo -from flask import Flask, Blueprint +from datetime import datetime +from flask import Flask #noinspection PyUnresolvedReferences -from nose.tools import assert_equals # you need it for tests in form of continuations +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), + ("-3.13", "-3.13"), + (str(-3.13), "-3.13"), + (3, "3"), ] for value, expected in values: - yield check_field, expected, fields.Float(), value + yield check_field, expected, fields.Arbitrary(), value def test_boolean(): @@ -43,43 +36,11 @@ ({}, 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): @@ -90,39 +51,46 @@ 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 @@ -130,18 +98,21 @@ 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() @@ -150,30 +121,21 @@ 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__) @@ -183,6 +145,7 @@ 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) @@ -191,103 +154,26 @@ 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) + 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})) @@ -298,7 +184,7 @@ def test_float(self): field = fields.Float() - self.assertEquals(3.0, field.output("hey", {'hey': 3.0})) + self.assertEquals('3.0', field.output("hey", {'hey': 3.0})) def test_float_decode_error(self): field = fields.Float() @@ -330,6 +216,7 @@ 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'})) @@ -338,47 +225,33 @@ 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): + + def test_date_field_with_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())) @@ -390,9 +263,11 @@ 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())) @@ -401,11 +276,6 @@ 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): @@ -422,23 +292,6 @@ 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): @@ -447,50 +300,5 @@ 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.3.orig/tests/test_reqparse.py +++ flask-restful-0.3.3/tests/test_reqparse.py @@ -1,63 +1,65 @@ # -*- coding: utf-8 -*- import unittest -from mock import Mock, patch +from mock import Mock, patch, NonCallableMock from flask import Flask -from werkzeug import exceptions, MultiDict +from flask import request as flask_request +from werkzeug import exceptions 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(self, abort): - app = Flask(__name__) - with app.app_context(): - parser = RequestParser() - parser.add_argument('foo', choices=['one', 'two'], help='Bad choice') - 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) + from werkzeug.datastructures import MultiDict + parser = RequestParser() + parser.add_argument('foo', choices=['one', 'two'], help='Bad choice') + req = Mock(['values']) + req.values = MultiDict([('foo', 'three')]) + parser.parse_args(req) + abort.assert_called_with(400, message='Bad choice') @patch('flask_restful.abort', side_effect=exceptions.BadRequest('Bad Request')) def test_no_help(self, abort): def bad_choice(): + from werkzeug.datastructures import MultiDict 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) + + + 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") @@ -74,100 +76,111 @@ 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): + def test_default_type(self): arg = Argument("foo") - sentinel = object() - arg.type(sentinel) - mock_six.text_type.assert_called_with(sentinel) + self.assertEquals(arg.type, six.text_type) + 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)) + self.assertEquals(arg.source(req), req.args) arg = Argument('foo', location=['headers']) - self.assertEquals(arg.source(req), MultiDict(req.headers)) + self.assertEquals(arg.source(req), req.headers) - def test_convert_default_type_with_null_input(self): - """convert() should properly handle case where input is None""" - arg = Argument('foo') - self.assertEquals(arg.convert(None, 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() + req._get_child_mock = lambda **kwargs: NonCallableMock(**kwargs) 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) @@ -180,8 +193,9 @@ arg = Argument("foo", choices=["bar", "baz"]) self.assertEquals(True, arg.case_sensitive) + def test_viewargs(self): - req = Request.from_values() + req = Mock() req.view_args = {"foo": "bar"} parser = RequestParser() parser.add_argument("foo", location=["view_args"], type=str) @@ -190,13 +204,13 @@ req = Mock() req.values = () - req.json = None req.view_args = {"foo": "bar"} parser = RequestParser() - parser.add_argument("foo", type=str, store_missing=True) + parser.add_argument("foo", type=str) args = parser.parse_args(req) self.assertEquals(args["foo"], None) + def test_parse_unicode(self): req = Request.from_values("/bubble?foo=barß") parser = RequestParser() @@ -205,6 +219,7 @@ args = parser.parse_args(req) self.assertEquals(args['foo'], u"barß") + def test_parse_unicode_app(self): app = Flask(__name__) @@ -215,21 +230,23 @@ 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) + parser.add_argument("foo", location="json") 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") + parser.add_argument("foo", location="get_json") with app.test_request_context('/bubble', method="post", data=json.dumps({"foo": "bar"}), @@ -237,25 +254,27 @@ 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), + parser.add_argument("foo", ignore=True, type=int, action="append"), 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), + parser.add_argument("foo", action="append"), args = parser.parse_args(req) self.assertEquals(args['foo'], None) + def test_parse_append(self): req = Request.from_values("/bubble?foo=bar&foo=bat") @@ -274,6 +293,7 @@ args = parser.parse_args(req) self.assertEquals(args['foo'], ["bar"]) + def test_parse_dest(self): req = Request.from_values("/bubble?foo=bar") @@ -283,6 +303,7 @@ 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") @@ -292,6 +313,7 @@ args = parser.parse_args(req) self.assertEquals(args['foo'], ["bar", "bat", "foo"]) + def test_parse_gte(self): req = Request.from_values("/bubble?foo>=bar") @@ -301,21 +323,22 @@ 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), + parser = RequestParser() + parser.add_argument("foo", type=int), + + self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(Request.from_values("/bubble?foo=bar"))) - 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) + parser.add_argument("foo", ignore=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") @@ -326,6 +349,7 @@ 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") @@ -333,18 +357,21 @@ 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() @@ -352,6 +379,7 @@ args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") + def test_parse_lte(self): req = Request.from_values("/bubble?foo<=bar") parser = RequestParser() @@ -360,107 +388,51 @@ 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') + def test_parse_required(self): + req = Request.from_values("/bubble") - 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("foo", required=True, location='values') - parser = RequestParser() - parser.add_argument("bar", required=True, location=['values', 'cookies']) + message = '' + try: + parser.parse_args(req) + except exceptions.BadRequest as e: + message = e.data['message'] - 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'})) + self.assertEquals(message, 'foo is required in values') - 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("bar", required=True, location=['values', 'cookies']) - parser = RequestParser() - parser.add_argument("foo", required=True, location='values') - parser.add_argument("bar", required=True, location=['values', 'cookies']) + try: + parser.parse_args(req) + except exceptions.BadRequest as e: + message = e.data['message'] - 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) + self.assertEquals(message, 'bar is required in values or cookies') - 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) + parser.add_argument("foo", default="bar", action="append") 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) + parser.add_argument("foo", default="bar") 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") @@ -471,6 +443,7 @@ args = parser.parse_args(req) self.assertEquals(args['foo'], "bar") + def test_parse_none(self): req = Request.from_values("/bubble") @@ -480,14 +453,6 @@ 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") @@ -498,25 +463,23 @@ 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") + req = Request.from_values("/bubble?foo=bar") - parser = RequestParser() - parser.add_argument("foo", choices=["bat"]), + parser = RequestParser() + parser.add_argument("foo", choices=["bat"]), - self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(req)) + 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") + req = Request.from_values("/bubble?foo=BAT") - parser = RequestParser() - parser.add_argument("foo", choices=["bat"], case_sensitive=True), + parser = RequestParser() + parser.add_argument("foo", choices=["bat"], case_sensitive=True), + + self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(req)) - self.assertRaises(exceptions.BadRequest, lambda: parser.parse_args(req)) def test_parse_choices_insensitive(self): req = Request.from_values("/bubble?foo=BAT") @@ -527,20 +490,12 @@ 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), + parser.add_argument("foo", type=int, ignore=True), args = parser.parse_args(req) self.assertEquals(args['foo'], None) @@ -562,272 +517,8 @@ 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", type=str, 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", type=str)) - - 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_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', type=str) - - 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_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) + self.assertTrue(isinstance(RequestParser().parse_args(), Namespace)) + self.assertTrue(type(RequestParser(namespace_class=dict).parse_args()) is dict) if __name__ == '__main__': unittest.main() --- flask-restful-0.3.3.orig/tests/test_types.py +++ flask-restful-0.3.3/tests/test_types.py @@ -0,0 +1,151 @@ +import unittest +from flask_restful import types +import datetime +#noinspection PyUnresolvedReferences +from nose.tools import assert_equals # you need it for tests in form of continuations +import six + +# http://docs.python.org/library/datetime.html?highlight=datetime#datetime.tzinfo.fromutc +ZERO = datetime.timedelta(0) +HOUR = datetime.timedelta(hours=1) + + +class UTC(datetime.tzinfo): + """UTC""" + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + +def test_datetime_formatters(): + dates = [ + (datetime.datetime(2011, 1, 1), "Sat, 01 Jan 2011 00:00:00 -0000"), + (datetime.datetime(2011, 1, 1, 23, 59, 59), + "Sat, 01 Jan 2011 23:59:59 -0000"), + (datetime.datetime(2011, 1, 1, 23, 59, 59, tzinfo=UTC()), + "Sat, 01 Jan 2011 23:59:59 -0000"), + ] + for date_obj, expected in dates: + yield assert_equals, types.rfc822(date_obj), 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_equals, types.url(value), value + +def check_bad_url_raises(value): + try: + types.url(value) + assert False, "shouldn't get here" + except ValueError as e: + assert_equals(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: + types.url(value) + assert False, u"types.url({0}) should raise an exception".format(value) + except ValueError as e: + assert_equals(six.text_type(e), + (u"{0} is not a valid URL. Did you mean: http://{0}".format(value))) + + +class TypesTestCase(unittest.TestCase): + + def test_boolean_false(self): + self.assertEquals(types.boolean("False"), False) + + + def test_boolean_true(self): + self.assertEquals(types.boolean("true"), True) + + + def test_boolean_upper_case(self): + self.assertEquals(types.boolean("FaLSE"), False) + + + def test_boolean(self): + self.assertEquals(types.boolean("FaLSE"), False) + + + def test_date_later_than_1900(self): + self.assertEquals(types.date("1900-01-01"), datetime.datetime(1900, 1, 1)) + + + def test_date_too_early(self): + self.assertRaises(ValueError, lambda: types.date("0001-01-01")) + + + def test_date_input_error(self): + self.assertRaises(ValueError, lambda: types.date("2008-13-13")) + + def test_date_input(self): + self.assertEquals(types.date("2008-08-01"), datetime.datetime(2008, 8, 1)) + + def test_natual_negative(self): + self.assertRaises(ValueError, lambda: types.natural(-1)) + + def test_natual(self): + self.assertEquals(3, types.natural(3)) + + + def test_natual_string(self): + self.assertRaises(ValueError, lambda: types.natural('foo')) + + +if __name__ == '__main__': + unittest.main()