Flask-API-0.6.4+dfsg/ 0000755 0000765 0000062 00000000000 12605627265 012066 5 ustar staff Flask-API-0.6.4+dfsg/setup.py 0000755 0000765 0000062 00000005120 12527506550 013575 0 ustar staff #!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
from setuptools import setup
import re
import os
import sys
name = 'Flask-API'
package = 'flask_api'
description = 'Browsable web APIs for Flask.'
url = 'http://www.flaskapi.org'
author = 'Tom Christie'
author_email = 'tom@tomchristie.com'
license = 'BSD'
install_requires = [
'Flask == 0.10.1',
]
long_description = """Browsable web APIs for Flask."""
def get_version(package):
"""
Return package version as listed in `__version__` in `init.py`.
"""
init_py = open(os.path.join(package, '__init__.py')).read()
return re.search("^__version__ = ['\"]([^'\"]+)['\"]", init_py, re.MULTILINE).group(1)
def get_packages(package):
"""
Return root package and all sub-packages.
"""
return [dirpath
for dirpath, dirnames, filenames in os.walk(package)
if os.path.exists(os.path.join(dirpath, '__init__.py'))]
def get_package_data(package):
"""
Return all files under the root package, that are not in a
package themselves.
"""
walk = [(dirpath.replace(package + os.sep, '', 1), filenames)
for dirpath, dirnames, filenames in os.walk(package)
if not os.path.exists(os.path.join(dirpath, '__init__.py'))]
filepaths = []
for base, filenames in walk:
filepaths.extend([os.path.join(base, filename)
for filename in filenames])
return {package: filepaths}
if sys.argv[-1] == 'publish':
os.system("python setup.py sdist upload")
args = {'version': get_version(package)}
print("You probably want to also tag the version now:")
print(" git tag -a %(version)s -m 'version %(version)s'" % args)
print(" git push --tags")
sys.exit()
setup(
name=name,
version=get_version(package),
url=url,
license=license,
description=description,
long_description=long_description,
author=author,
author_email=author_email,
packages=get_packages(package),
package_data=get_package_data(package),
install_requires=install_requires,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet :: WWW/HTTP',
]
)
Flask-API-0.6.4+dfsg/setup.cfg 0000644 0000765 0000062 00000000073 12605627265 013707 0 ustar staff [egg_info]
tag_build =
tag_svn_revision = 0
tag_date = 0
Flask-API-0.6.4+dfsg/PKG-INFO 0000644 0000765 0000062 00000001372 12605627265 013166 0 ustar staff Metadata-Version: 1.1
Name: Flask-API
Version: 0.6.4
Summary: Browsable web APIs for Flask.
Home-page: http://www.flaskapi.org
Author: Tom Christie
Author-email: tom@tomchristie.com
License: BSD
Description: Browsable web APIs for Flask.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Topic :: Internet :: WWW/HTTP
Flask-API-0.6.4+dfsg/Flask_API.egg-info/ 0000755 0000765 0000062 00000000000 12605627265 015311 5 ustar staff Flask-API-0.6.4+dfsg/Flask_API.egg-info/top_level.txt 0000644 0000765 0000062 00000000032 12631321323 020017 0 ustar staff flask_api
flask_api/tests
Flask-API-0.6.4+dfsg/Flask_API.egg-info/SOURCES.txt 0000644 0000765 0000062 00000002073 12631321323 017160 0 ustar staff setup.cfg
setup.py
Flask_API.egg-info/PKG-INFO
Flask_API.egg-info/SOURCES.txt
Flask_API.egg-info/dependency_links.txt
Flask_API.egg-info/requires.txt
Flask_API.egg-info/top_level.txt
flask_api/__init__.py
flask_api/app.py
flask_api/compat.py
flask_api/decorators.py
flask_api/exceptions.py
flask_api/mediatypes.py
flask_api/negotiation.py
flask_api/parsers.py
flask_api/renderers.py
flask_api/request.py
flask_api/response.py
flask_api/settings.py
flask_api/status.py
flask_api/static/css/bootstrap-tweaks.css
flask_api/static/css/default.css
flask_api/static/img/glyphicons-halflings-white.png
flask_api/static/img/glyphicons-halflings.png
flask_api/static/img/grid.png
flask_api/static/js/default.js
flask_api/templates/base.html
flask_api/tests/__init__.py
flask_api/tests/runtests.py
flask_api/tests/test_app.py
flask_api/tests/test_exceptions.py
flask_api/tests/test_mediatypes.py
flask_api/tests/test_negotiation.py
flask_api/tests/test_parsers.py
flask_api/tests/test_renderers.py
flask_api/tests/test_request.py
flask_api/tests/test_settings.py
flask_api/tests/test_status.py Flask-API-0.6.4+dfsg/Flask_API.egg-info/requires.txt 0000644 0000765 0000062 00000000020 12631321323 017662 0 ustar staff Flask == 0.10.1
Flask-API-0.6.4+dfsg/Flask_API.egg-info/PKG-INFO 0000644 0000765 0000062 00000001372 12631321323 016372 0 ustar staff Metadata-Version: 1.1
Name: Flask-API
Version: 0.6.4
Summary: Browsable web APIs for Flask.
Home-page: http://www.flaskapi.org
Author: Tom Christie
Author-email: tom@tomchristie.com
License: BSD
Description: Browsable web APIs for Flask.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Topic :: Internet :: WWW/HTTP
Flask-API-0.6.4+dfsg/Flask_API.egg-info/dependency_links.txt 0000644 0000765 0000062 00000000001 12631321323 021340 0 ustar staff
Flask-API-0.6.4+dfsg/flask_api/ 0000755 0000765 0000062 00000000000 12631321323 014000 5 ustar staff Flask-API-0.6.4+dfsg/flask_api/tests/ 0000755 0000765 0000062 00000000000 12631321323 015142 5 ustar staff Flask-API-0.6.4+dfsg/flask_api/tests/test_status.py 0000644 0000765 0000062 00000002302 12523124120 020067 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask_api import status
import unittest
class TestStatus(unittest.TestCase):
def test_status_categories(self):
self.assertFalse(status.is_informational(99))
self.assertTrue(status.is_informational(100))
self.assertTrue(status.is_informational(199))
self.assertFalse(status.is_informational(200))
self.assertFalse(status.is_success(199))
self.assertTrue(status.is_success(200))
self.assertTrue(status.is_success(299))
self.assertFalse(status.is_success(300))
self.assertFalse(status.is_redirect(299))
self.assertTrue(status.is_redirect(300))
self.assertTrue(status.is_redirect(399))
self.assertFalse(status.is_redirect(400))
self.assertFalse(status.is_client_error(399))
self.assertTrue(status.is_client_error(400))
self.assertTrue(status.is_client_error(499))
self.assertFalse(status.is_client_error(500))
self.assertFalse(status.is_server_error(499))
self.assertTrue(status.is_server_error(500))
self.assertTrue(status.is_server_error(599))
self.assertFalse(status.is_server_error(600))
Flask-API-0.6.4+dfsg/flask_api/tests/test_settings.py 0000644 0000765 0000062 00000001414 12523124120 020407 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask_api.settings import APISettings
import unittest
class SettingsTests(unittest.TestCase):
def test_bad_import(self):
settings = APISettings({'DEFAULT_PARSERS': 'foobarz.FailedImport'})
with self.assertRaises(ImportError) as context:
settings.DEFAULT_PARSERS
msg = str(context.exception)
excepted_py2 = (
"Could not import 'foobarz.FailedImport' for API setting "
"'DEFAULT_PARSERS'. No module named foobarz."
)
excepted_py3 = (
"Could not import 'foobarz.FailedImport' for API setting "
"'DEFAULT_PARSERS'. No module named 'foobarz'."
)
self.assertIn(msg, (excepted_py2, excepted_py3))
Flask-API-0.6.4+dfsg/flask_api/tests/test_request.py 0000644 0000765 0000062 00000003174 12527515176 020267 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask import request
from flask_api import exceptions
import flask_api
import io
import unittest
app = flask_api.FlaskAPI(__name__)
class MediaTypeParsingTests(unittest.TestCase):
def test_json_request(self):
kwargs = {
'method': 'PUT',
'input_stream': io.BytesIO(b'{"key": 1, "other": "two"}'),
'content_type': 'application/json'
}
with app.test_request_context(**kwargs):
self.assertEqual(request.data, {"key": 1, "other": "two"})
def test_invalid_content_type_request(self):
kwargs = {
'method': 'PUT',
'input_stream': io.BytesIO(b'Cannot parse this content type.'),
'content_type': 'text/plain'
}
with app.test_request_context(**kwargs):
with self.assertRaises(exceptions.UnsupportedMediaType):
request.data
def test_no_content_request(self):
"""
Ensure that requests with no data do not populate the
`.data`, `.form` or `.files` attributes.
"""
with app.test_request_context(method='PUT'):
self.assertFalse(request.data)
with app.test_request_context(method='PUT'):
self.assertFalse(request.form)
with app.test_request_context(method='PUT'):
self.assertFalse(request.files)
def test_encode_request(self):
"""
Ensure that `.full_path` is correctly decoded in python 3
"""
with app.test_request_context(method='GET', path='/?a=b'):
self.assertEqual(request.full_path, '/?a=b')
Flask-API-0.6.4+dfsg/flask_api/tests/test_renderers.py 0000644 0000765 0000062 00000011521 12523477466 020570 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask_api import renderers, status, FlaskAPI
from flask_api.decorators import set_renderers
from flask_api.mediatypes import MediaType
import unittest
class RendererTests(unittest.TestCase):
def test_render_json(self):
renderer = renderers.JSONRenderer()
content = renderer.render({'example': 'example'}, MediaType('application/json'))
expected = '{"example": "example"}'
self.assertEqual(content, expected)
def test_render_json_with_indent(self):
renderer = renderers.JSONRenderer()
content = renderer.render({'example': 'example'}, MediaType('application/json; indent=4'))
expected = '{\n "example": "example"\n}'
self.assertEqual(content, expected)
def test_render_browsable_encoding(self):
app = FlaskAPI(__name__)
@app.route('/_love', methods=['GET'])
def love():
return {"test": "I <3 Python"}
with app.test_client() as client:
response = client.get('/_love',
headers={"Accept": "text/html"})
html = str(response.get_data())
self.assertTrue('I <3 Python' in html)
self.assertTrue('
Love
' in html)
self.assertTrue('/_love' in html)
def test_render_browsable_linking(self):
app = FlaskAPI(__name__)
@app.route('/_happiness', methods=['GET'])
def happiness():
return {"url": "http://example.org",
"a tag": "
"}
with app.test_client() as client:
response = client.get('/_happiness',
headers={"Accept": "text/html"})
html = str(response.get_data())
self.assertTrue('http://example.org' in html)
self.assertTrue('<br />'in html)
self.assertTrue('Happiness
' in html)
self.assertTrue('/_happiness' in html)
def test_renderer_negotiation_not_implemented(self):
renderer = renderers.BaseRenderer()
with self.assertRaises(NotImplementedError) as context:
renderer.render(None, None)
msg = str(context.exception)
expected = '`render()` method must be implemented for class "BaseRenderer"'
self.assertEqual(msg, expected)
class OverrideParserSettings(unittest.TestCase):
def setUp(self):
class CustomRenderer1(renderers.BaseRenderer):
media_type = 'application/example1'
def render(self, data, media_type, **options):
return 'custom renderer 1'
class CustomRenderer2(renderers.BaseRenderer):
media_type = 'application/example2'
def render(self, data, media_type, **options):
return 'custom renderer 2'
app = FlaskAPI(__name__)
app.config['DEFAULT_RENDERERS'] = [CustomRenderer1]
app.config['PROPAGATE_EXCEPTIONS'] = True
@app.route('/custom_renderer_1/', methods=['GET'])
def custom_renderer_1():
return {'data': 'example'}
@app.route('/custom_renderer_2/', methods=['GET'])
@set_renderers([CustomRenderer2])
def custom_renderer_2():
return {'data': 'example'}
@app.route('/custom_renderer_2_as_args/', methods=['GET'])
@set_renderers(CustomRenderer2)
def custom_renderer_2_as_args():
return {'data': 'example'}
self.app = app
def test_overridden_parsers_with_settings(self):
with self.app.test_client() as client:
response = client.get('/custom_renderer_1/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/example1')
data = response.get_data().decode('utf8')
self.assertEqual(data, "custom renderer 1")
def test_overridden_parsers_with_decorator(self):
with self.app.test_client() as client:
data = {'example': 'example'}
response = client.get('/custom_renderer_2/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/example2')
data = response.get_data().decode('utf8')
self.assertEqual(data, "custom renderer 2")
def test_overridden_parsers_with_decorator_as_args(self):
with self.app.test_client() as client:
data = {'example': 'example'}
response = client.get('/custom_renderer_2_as_args/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/example2')
data = response.get_data().decode('utf8')
self.assertEqual(data, "custom renderer 2")
Flask-API-0.6.4+dfsg/flask_api/tests/test_parsers.py 0000644 0000765 0000062 00000016424 12523124120 020235 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask import request
from flask_api import exceptions, parsers, status, mediatypes, FlaskAPI
from flask_api.decorators import set_parsers
import io
import json
import unittest
app = FlaskAPI(__name__)
@app.route('/', methods=['POST'])
def data():
return {
'data': request.data,
'form': request.form,
'files': dict([
(key, {'name': val.filename, 'contents': val.read().decode('utf8')})
for key, val in request.files.items()
])
}
class ParserTests(unittest.TestCase):
def test_valid_json(self):
parser = parsers.JSONParser()
stream = io.BytesIO(b'{"key": 1, "other": "two"}')
data = parser.parse(stream, 'application/json')
self.assertEqual(data, {"key": 1, "other": "two"})
def test_invalid_json(self):
parser = parsers.JSONParser()
stream = io.BytesIO(b'{key: 1, "other": "two"}')
with self.assertRaises(exceptions.ParseError) as context:
parser.parse(stream, mediatypes.MediaType('application/json'))
detail = str(context.exception)
expected_py2 = 'JSON parse error - Expecting property name: line 1 column 1 (char 1)'
expected_py3 = 'JSON parse error - Expecting property name enclosed in double quotes: line 1 column 2 (char 1)'
self.assertIn(detail, (expected_py2, expected_py3))
def test_invalid_multipart(self):
parser = parsers.MultiPartParser()
stream = io.BytesIO(b'invalid')
media_type = mediatypes.MediaType('multipart/form-data; boundary="foo"')
with self.assertRaises(exceptions.ParseError) as context:
parser.parse(stream, media_type, content_length=len('invalid'))
detail = str(context.exception)
expected = 'Multipart parse error - Expected boundary at start of multipart data'
self.assertEqual(detail, expected)
def test_invalid_multipart_no_boundary(self):
parser = parsers.MultiPartParser()
stream = io.BytesIO(b'invalid')
with self.assertRaises(exceptions.ParseError) as context:
parser.parse(stream, mediatypes.MediaType('multipart/form-data'))
detail = str(context.exception)
expected = 'Multipart message missing boundary in Content-Type header'
self.assertEqual(detail, expected)
def test_renderer_negotiation_not_implemented(self):
parser = parsers.BaseParser()
with self.assertRaises(NotImplementedError) as context:
parser.parse(None, None)
msg = str(context.exception)
expected = '`parse()` method must be implemented for class "BaseParser"'
self.assertEqual(msg, expected)
def test_accessing_json(self):
with app.test_client() as client:
data = json.dumps({'example': 'example'})
response = client.post('/', data=data, content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/json')
data = json.loads(response.get_data().decode('utf8'))
expected = {
"data": {"example": "example"},
"form": {},
"files": {}
}
self.assertEqual(data, expected)
def test_accessing_url_encoded(self):
with app.test_client() as client:
data = {'example': 'example'}
response = client.post('/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/json')
data = json.loads(response.get_data().decode('utf8'))
expected = {
"data": {"example": "example"},
"form": {"example": "example"},
"files": {}
}
self.assertEqual(data, expected)
def test_accessing_multipart(self):
with app.test_client() as client:
data = {'example': 'example', 'upload': (io.BytesIO(b'file contents'), 'name.txt')}
response = client.post('/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/json')
data = json.loads(response.get_data().decode('utf8'))
expected = {
"data": {"example": "example"},
"form": {"example": "example"},
"files": {"upload": {"name": "name.txt", "contents": "file contents"}}
}
self.assertEqual(data, expected)
class OverrideParserSettings(unittest.TestCase):
def setUp(self):
class CustomParser1(parsers.BaseParser):
media_type = '*/*'
def parse(self, stream, media_type, content_length=None):
return 'custom parser 1'
class CustomParser2(parsers.BaseParser):
media_type = '*/*'
def parse(self, stream, media_type, content_length=None):
return 'custom parser 2'
app = FlaskAPI(__name__)
app.config['DEFAULT_PARSERS'] = [CustomParser1]
@app.route('/custom_parser_1/', methods=['POST'])
def custom_parser_1():
return {'data': request.data}
@app.route('/custom_parser_2/', methods=['POST'])
@set_parsers([CustomParser2])
def custom_parser_2():
return {'data': request.data}
@app.route('/custom_parser_2_as_args/', methods=['POST'])
@set_parsers(CustomParser2, CustomParser1)
def custom_parser_2_as_args():
return {'data': request.data}
self.app = app
def test_overridden_parsers_with_settings(self):
with self.app.test_client() as client:
data = {'example': 'example'}
response = client.post('/custom_parser_1/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/json')
data = json.loads(response.get_data().decode('utf8'))
expected = {
"data": "custom parser 1",
}
self.assertEqual(data, expected)
def test_overridden_parsers_with_decorator(self):
with self.app.test_client() as client:
data = {'example': 'example'}
response = client.post('/custom_parser_2/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/json')
data = json.loads(response.get_data().decode('utf8'))
expected = {
"data": "custom parser 2",
}
self.assertEqual(data, expected)
def test_overridden_parsers_with_decorator_as_args(self):
with self.app.test_client() as client:
data = {'example': 'example'}
response = client.post('/custom_parser_2_as_args/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Content-Type'], 'application/json')
data = json.loads(response.get_data().decode('utf8'))
expected = {
"data": "custom parser 2",
}
self.assertEqual(data, expected)
Flask-API-0.6.4+dfsg/flask_api/tests/test_negotiation.py 0000644 0000765 0000062 00000007120 12523124120 021067 0 ustar staff # coding: utf8
from __future__ import unicode_literals
import unittest
import flask_api
from flask_api import exceptions
from flask_api.negotiation import BaseNegotiation, DefaultNegotiation
app = flask_api.FlaskAPI(__name__)
class JSON(object):
media_type = 'application/json'
class HTML(object):
media_type = 'application/html'
class URLEncodedForm(object):
media_type = 'application/x-www-form-urlencoded'
class TestRendererNegotiation(unittest.TestCase):
def test_select_renderer_client_preference(self):
negotiation = DefaultNegotiation()
renderers = [JSON, HTML]
headers = {'Accept': 'application/html'}
with app.test_request_context(headers=headers):
renderer, media_type = negotiation.select_renderer(renderers)
self.assertEqual(renderer, HTML)
self.assertEqual(str(media_type), 'application/html')
def test_select_renderer_no_accept_header(self):
negotiation = DefaultNegotiation()
renderers = [JSON, HTML]
with app.test_request_context():
renderer, media_type = negotiation.select_renderer(renderers)
self.assertEqual(renderer, JSON)
self.assertEqual(str(media_type), 'application/json')
def test_select_renderer_server_preference(self):
negotiation = DefaultNegotiation()
renderers = [JSON, HTML]
headers = {'Accept': '*/*'}
with app.test_request_context(headers=headers):
renderer, media_type = negotiation.select_renderer(renderers)
self.assertEqual(renderer, JSON)
self.assertEqual(str(media_type), 'application/json')
def test_select_renderer_failed(self):
negotiation = DefaultNegotiation()
renderers = [JSON, HTML]
headers = {'Accept': 'application/xml'}
with app.test_request_context(headers=headers):
with self.assertRaises(exceptions.NotAcceptable):
renderer, media_type = negotiation.select_renderer(renderers)
def test_renderer_negotiation_not_implemented(self):
negotiation = BaseNegotiation()
with self.assertRaises(NotImplementedError) as context:
negotiation.select_renderer([])
msg = str(context.exception)
expected = '`select_renderer()` method must be implemented for class "BaseNegotiation"'
self.assertEqual(msg, expected)
class TestParserNegotiation(unittest.TestCase):
def test_select_parser(self):
negotiation = DefaultNegotiation()
parsers = [JSON, URLEncodedForm]
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
with app.test_request_context(headers=headers):
renderer, media_type = negotiation.select_parser(parsers)
self.assertEqual(renderer, URLEncodedForm)
self.assertEqual(str(media_type), 'application/x-www-form-urlencoded')
def test_select_parser_failed(self):
negotiation = DefaultNegotiation()
parsers = [JSON, URLEncodedForm]
headers = {'Content-Type': 'application/xml'}
with app.test_request_context(headers=headers):
with self.assertRaises(exceptions.UnsupportedMediaType):
renderer, media_type = negotiation.select_parser(parsers)
def test_parser_negotiation_not_implemented(self):
negotiation = BaseNegotiation()
with self.assertRaises(NotImplementedError) as context:
negotiation.select_parser([])
msg = str(context.exception)
expected = '`select_parser()` method must be implemented for class "BaseNegotiation"'
self.assertEqual(msg, expected)
Flask-API-0.6.4+dfsg/flask_api/tests/test_mediatypes.py 0000644 0000765 0000062 00000011633 12523124120 020717 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask_api.mediatypes import MediaType, parse_accept_header
import unittest
class MediaTypeParsingTests(unittest.TestCase):
def test_media_type_with_params(self):
media = MediaType('application/xml; schema=foobar, q=0.5')
self.assertEqual(str(media), 'application/xml; q="0.5", schema="foobar"')
self.assertEqual(media.main_type, 'application')
self.assertEqual(media.sub_type, 'xml')
self.assertEqual(media.full_type, 'application/xml')
self.assertEqual(media.params, {'schema': 'foobar', 'q': '0.5'})
self.assertEqual(media.precedence, 3)
self.assertEqual(repr(media), '')
def test_media_type_with_q_params(self):
media = MediaType('application/xml; q=0.5')
self.assertEqual(str(media), 'application/xml; q="0.5"')
self.assertEqual(media.main_type, 'application')
self.assertEqual(media.sub_type, 'xml')
self.assertEqual(media.full_type, 'application/xml')
self.assertEqual(media.params, {'q': '0.5'})
self.assertEqual(media.precedence, 2)
def test_media_type_without_params(self):
media = MediaType('application/xml')
self.assertEqual(str(media), 'application/xml')
self.assertEqual(media.main_type, 'application')
self.assertEqual(media.sub_type, 'xml')
self.assertEqual(media.full_type, 'application/xml')
self.assertEqual(media.params, {})
self.assertEqual(media.precedence, 2)
def test_media_type_with_wildcard_sub_type(self):
media = MediaType('application/*')
self.assertEqual(str(media), 'application/*')
self.assertEqual(media.main_type, 'application')
self.assertEqual(media.sub_type, '*')
self.assertEqual(media.full_type, 'application/*')
self.assertEqual(media.params, {})
self.assertEqual(media.precedence, 1)
def test_media_type_with_wildcard_main_type(self):
media = MediaType('*/*')
self.assertEqual(str(media), '*/*')
self.assertEqual(media.main_type, '*')
self.assertEqual(media.sub_type, '*')
self.assertEqual(media.full_type, '*/*')
self.assertEqual(media.params, {})
self.assertEqual(media.precedence, 0)
class MediaTypeMatchingTests(unittest.TestCase):
def test_media_type_includes_params(self):
media_type = MediaType('application/json')
other = MediaType('application/json; version=1.0')
self.assertTrue(media_type.satisfies(other))
def test_media_type_missing_params(self):
media_type = MediaType('application/json; version=1.0')
other = MediaType('application/json')
self.assertFalse(media_type.satisfies(other))
def test_media_type_matching_params(self):
media_type = MediaType('application/json; version=1.0')
other = MediaType('application/json; version=1.0')
self.assertTrue(media_type.satisfies(other))
def test_media_type_non_matching_params(self):
media_type = MediaType('application/json; version=1.0')
other = MediaType('application/json; version=2.0')
self.assertFalse(media_type.satisfies(other))
def test_media_type_main_type_match(self):
media_type = MediaType('image/*')
other = MediaType('image/png')
self.assertTrue(media_type.satisfies(other))
def test_media_type_sub_type_mismatch(self):
media_type = MediaType('image/jpeg')
other = MediaType('image/png')
self.assertFalse(media_type.satisfies(other))
def test_media_type_wildcard_match(self):
media_type = MediaType('*/*')
other = MediaType('image/png')
self.assertTrue(media_type.satisfies(other))
def test_media_type_wildcard_mismatch(self):
media_type = MediaType('image/*')
other = MediaType('audio/*')
self.assertFalse(media_type.satisfies(other))
class AcceptHeaderTests(unittest.TestCase):
def test_parse_simple_accept_header(self):
parsed = parse_accept_header('*/*, application/json')
self.assertEqual(parsed, [
set([MediaType('application/json')]),
set([MediaType('*/*')])
])
def test_parse_complex_accept_header(self):
"""
The accept header should be parsed into a list of sets of MediaType.
The list is an ordering of precedence.
Note that we disregard 'q' values when determining precedence, and
instead differentiate equal values by using the server preference.
"""
header = 'application/xml; schema=foo, application/json; q=0.9, application/xml, */*'
parsed = parse_accept_header(header)
self.assertEqual(parsed, [
set([MediaType('application/xml; schema=foo')]),
set([MediaType('application/json; q=0.9'), MediaType('application/xml')]),
set([MediaType('*/*')]),
])
Flask-API-0.6.4+dfsg/flask_api/tests/test_exceptions.py 0000644 0000765 0000062 00000001476 12523124120 020740 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask_api import exceptions
from flask_api import status
import unittest
class Conflict(exceptions.APIException):
status_code = status.HTTP_409_CONFLICT
detail = 'Could not update the resource'
class TestExceptions(unittest.TestCase):
def test_custom_exception(self):
try:
raise Conflict()
except Conflict as exc:
self.assertEqual(str(exc), 'Could not update the resource')
self.assertEqual(exc.status_code, 409)
def test_override_exception_detail(self):
try:
raise Conflict('A widget with this id already exists')
except Conflict as exc:
self.assertEqual(str(exc), 'A widget with this id already exists')
self.assertEqual(exc.status_code, 409)
Flask-API-0.6.4+dfsg/flask_api/tests/test_app.py 0000644 0000765 0000062 00000011124 12527513523 017343 0 ustar staff # coding: utf8
from __future__ import unicode_literals
from flask import abort, make_response, request
from flask_api.decorators import set_renderers
from flask_api import exceptions, renderers, status, FlaskAPI
import json
import unittest
app = FlaskAPI(__name__)
app.config['TESTING'] = True
class JSONVersion1(renderers.JSONRenderer):
media_type = 'application/json; api-version="1.0"'
class JSONVersion2(renderers.JSONRenderer):
media_type = 'application/json; api-version="2.0"'
@app.route('/set_status_and_headers/')
def set_status_and_headers():
headers = {'Location': 'http://example.com/456'}
return {'example': 'content'}, status.HTTP_201_CREATED, headers
@app.route('/set_headers/')
def set_headers():
headers = {'Location': 'http://example.com/456'}
return {'example': 'content'}, headers
@app.route('/make_response_view/')
def make_response_view():
response = make_response({'example': 'content'})
response.headers['Location'] = 'http://example.com/456'
return response
@app.route('/api_exception/')
def api_exception():
raise exceptions.PermissionDenied()
@app.route('/abort_view/')
def abort_view():
abort(status.HTTP_403_FORBIDDEN)
@app.route('/options/')
def options_view():
return {}
@app.route('/accepted_media_type/')
@set_renderers([JSONVersion2, JSONVersion1])
def accepted_media_type():
return {'accepted_media_type': str(request.accepted_media_type)}
class AppTests(unittest.TestCase):
def test_set_status_and_headers(self):
with app.test_client() as client:
response = client.get('/set_status_and_headers/')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.headers['Location'], 'http://example.com/456')
self.assertEqual(response.content_type, 'application/json')
expected = '{"example": "content"}'
self.assertEqual(response.get_data().decode('utf8'), expected)
def test_set_headers(self):
with app.test_client() as client:
response = client.get('/set_headers/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.headers['Location'], 'http://example.com/456')
self.assertEqual(response.content_type, 'application/json')
expected = '{"example": "content"}'
self.assertEqual(response.get_data().decode('utf8'), expected)
def test_make_response(self):
with app.test_client() as client:
response = client.get('/make_response_view/')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.headers['Location'], 'http://example.com/456')
self.assertEqual(response.content_type, 'application/json')
expected = '{"example": "content"}'
self.assertEqual(response.get_data().decode('utf8'), expected)
def test_api_exception(self):
with app.test_client() as client:
response = client.get('/api_exception/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.content_type, 'application/json')
expected = '{"message": "You do not have permission to perform this action."}'
self.assertEqual(response.get_data().decode('utf8'), expected)
def test_abort_view(self):
with app.test_client() as client:
response = client.get('/abort_view/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_options_view(self):
with app.test_client() as client:
response = client.options('/options/')
# Errors if `response.response` is `None`
response.get_data()
def test_accepted_media_type_property(self):
with app.test_client() as client:
# Explicitly request the "api-version 1.0" renderer.
headers = {'Accept': 'application/json; api-version="1.0"'}
response = client.get('/accepted_media_type/', headers=headers)
data = json.loads(response.get_data().decode('utf8'))
expected = {'accepted_media_type': 'application/json; api-version="1.0"'}
self.assertEqual(data, expected)
# Request the default renderer, which is "api-version 2.0".
headers = {'Accept': '*/*'}
response = client.get('/accepted_media_type/', headers=headers)
data = json.loads(response.get_data().decode('utf8'))
expected = {'accepted_media_type': 'application/json; api-version="2.0"'}
self.assertEqual(data, expected)
Flask-API-0.6.4+dfsg/flask_api/tests/runtests.py 0000644 0000765 0000062 00000000131 12527516065 017413 0 ustar staff import unittest
if __name__ == '__main__':
unittest.main(module='flask_api.tests')
Flask-API-0.6.4+dfsg/flask_api/tests/__init__.py 0000644 0000765 0000062 00000000661 12523124120 017252 0 ustar staff # This is a fudge that allows us to easily specify test modules.
# For example:
# ./runtests test_parsers
# ./runtests test_rendereres.RendererTests.test_render_json
import os
modules = [filename.rsplit('.', 1)[0]
for filename in os.listdir(os.path.dirname(__file__))
if filename.endswith('.py') and filename.startswith('test_')]
for module in modules:
exec("from flask_api.tests.%s import *" % module)
Flask-API-0.6.4+dfsg/flask_api/templates/ 0000755 0000765 0000062 00000000000 12605627265 016015 5 ustar staff Flask-API-0.6.4+dfsg/flask_api/templates/base.html 0000644 0000765 0000062 00000021174 12605625516 017617 0 ustar staff
{% block head %}
{% block meta %}
{% endblock %}
{% block title %}Flask API{% endblock %}
{% block style %}
{% block bootstrap_theme %}
{% endblock %}
{% endblock %}
{% endblock %}
{% block navbar %}
{% endblock %}
{% if 'GET' in allowed_methods %}
{% endif %}
{% if 'DELETE' in allowed_methods %}
{% endif %}
{% if view_description %}
{{ view_description|safe }}
{% endif %}
{{ request.method }} {{ request.full_path }}
HTTP {{ status }}{% autoescape off %}
{% for key, val in headers.items() %}{{ key }}: {{ val|e }}
{% endfor %}
{% if content %}{{ content|urlize_quoted_links }}{% endif %}
{% endautoescape %}
{% if 'POST' in allowed_methods or 'PUT' in allowed_methods or 'PATCH' in allowed_methods %}
{% endif %}
{% block footer %}
{% endblock %}
{% block script %}
{% endblock %}
Flask-API-0.6.4+dfsg/flask_api/status.py 0000644 0000765 0000062 00000003737 12523124120 015703 0 ustar staff # coding: utf8
"""
Descriptive HTTP status codes, for code readability.
See RFC 2616 and RFC 6585.
RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
RFC 6585: http://tools.ietf.org/html/rfc6585
"""
from __future__ import unicode_literals
def is_informational(code):
return code >= 100 and code <= 199
def is_success(code):
return code >= 200 and code <= 299
def is_redirect(code):
return code >= 300 and code <= 399
def is_client_error(code):
return code >= 400 and code <= 499
def is_server_error(code):
return code >= 500 and code <= 599
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
HTTP_204_NO_CONTENT = 204
HTTP_205_RESET_CONTENT = 205
HTTP_206_PARTIAL_CONTENT = 206
HTTP_300_MULTIPLE_CHOICES = 300
HTTP_301_MOVED_PERMANENTLY = 301
HTTP_302_FOUND = 302
HTTP_303_SEE_OTHER = 303
HTTP_304_NOT_MODIFIED = 304
HTTP_305_USE_PROXY = 305
HTTP_306_RESERVED = 306
HTTP_307_TEMPORARY_REDIRECT = 307
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_402_PAYMENT_REQUIRED = 402
HTTP_403_FORBIDDEN = 403
HTTP_404_NOT_FOUND = 404
HTTP_405_METHOD_NOT_ALLOWED = 405
HTTP_406_NOT_ACCEPTABLE = 406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
HTTP_408_REQUEST_TIMEOUT = 408
HTTP_409_CONFLICT = 409
HTTP_410_GONE = 410
HTTP_411_LENGTH_REQUIRED = 411
HTTP_412_PRECONDITION_FAILED = 412
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
HTTP_428_PRECONDITION_REQUIRED = 428
HTTP_429_TOO_MANY_REQUESTS = 429
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
Flask-API-0.6.4+dfsg/flask_api/static/ 0000755 0000765 0000062 00000000000 12605627265 015306 5 ustar staff Flask-API-0.6.4+dfsg/flask_api/static/js/ 0000755 0000765 0000062 00000000000 12631321323 015703 5 ustar staff Flask-API-0.6.4+dfsg/flask_api/static/js/default.js 0000644 0000765 0000062 00000003054 12523124120 017663 0 ustar staff function getCookie(c_name)
{
// From http://www.w3schools.com/js/js_cookies.asp
var c_value = document.cookie;
var c_start = c_value.indexOf(" " + c_name + "=");
if (c_start == -1) {
c_start = c_value.indexOf(c_name + "=");
}
if (c_start == -1) {
c_value = null;
} else {
c_start = c_value.indexOf("=", c_start) + 1;
var c_end = c_value.indexOf(";", c_start);
if (c_end == -1) {
c_end = c_value.length;
}
c_value = unescape(c_value.substring(c_start,c_end));
}
return c_value;
}
// JSON highlighting.
prettyPrint();
// Bootstrap tooltips.
$('.js-tooltip').tooltip({
delay: 1000
});
// Deal with rounded tab styling after tab clicks.
$('a[data-toggle="tab"]:first').on('shown', function (e) {
$(e.target).parents('.tabbable').addClass('first-tab-active');
});
$('a[data-toggle="tab"]:not(:first)').on('shown', function (e) {
$(e.target).parents('.tabbable').removeClass('first-tab-active');
});
$('a[data-toggle="tab"]').click(function(){
document.cookie="tabstyle=" + this.name + "; path=/";
});
// Store tab preference in cookies & display appropriate tab on load.
var selectedTab = null;
var selectedTabName = getCookie('tabstyle');
if (selectedTabName) {
selectedTab = $('.form-switcher a[name=' + selectedTabName + ']');
}
if (selectedTab && selectedTab.length > 0) {
// Display whichever tab is selected.
selectedTab.tab('show');
} else {
// If no tab selected, display rightmost tab.
$('.form-switcher a:first').tab('show');
}
Flask-API-0.6.4+dfsg/flask_api/static/img/ 0000755 0000765 0000062 00000000000 12605627265 016062 5 ustar staff Flask-API-0.6.4+dfsg/flask_api/static/img/grid.png 0000644 0000765 0000062 00000002662 12523124120 017500 0 ustar staff PNG
IHDR Z 2 zxǁ pHYs niTXtXML:com.adobe.xmp
?
A IDATxrU@?6@cR5<]f,M[13?Ûvffۣޜ/{zt /#tD#BG:"tD#BG9Ɔ%aqtD:"tD#BG:"tD#
oaAm4v:"tD#BG:"tD#BG%=,GGD#BG:"tD#BG:ba8:"BG:"tD#BG:"tD
K4:"tD#BG:"tD#BGذDlX9#BG:"tD#BG:"t'G|}4:"tD#BG:"tD#BGxK{X9#BG:"tD#BG:"tĆ%bqtD:"tD#BG:"tD#6,h#"tD#BG:"tD#BGaذDs|tls4ȕCo[]A':劎:"tD#BG:rO/xޞmX~}xq|Xk=GGd
%#BG1Y?!cK:
#BG{9fǙ8ۆMwk?yގg{#B߷M~0⊎1W#B{rymX^?8Yk}2y#E\ё+;u[:ˎ/I#G6{:ṙym}ur۰xZki|tlݷ)|EoE#BGݻdї:"tDБc>wcS簜8Z\`γ_KgW[l:"tD#BGΘ#' IENDB` Flask-API-0.6.4+dfsg/flask_api/static/img/glyphicons-halflings.png 0000644 0000765 0000062 00000030732 12523124120 022676 0 ustar staff PNG
IHDR 1IDATx}ml\EW^ɺD$|nw';vю8m0kQSnSV;1KGsԩ>UoTU1cƖYuּcaC,pؚ>kںULW
-sn3Vq~NocI~L {- H8%_M£wB6EW,ĢpY2+(Y@&A/3kXhߍ-aA<>P'\J;(}# Qz:4%m?nfntK*l9J+DIYu1YZ^(]YYEf@ОlXz]Ut u
&5-PW}@t|#LY=s܂,w#+R+?Ƌax X0"ea)tG*ԡwVwV^ rf%xB(qּ4>WG#lWU<ЁXJVѶlR$kDVrI7:X%X1NEzw;y9z9O%~~uɗ*=Ixcy}Y(ou
±N$^je\iX];Y-r Ѳ&>!zlYaVHVN9=]=mRMdOUCJUiT}rWW'ڹu)ʢF"YU#P&ܑЅROwyzm$Os? +^FTIEq%&~>M}]ԖwA?
[Nteexn(措BdMTpʥnqqS?bWXmW6x*{V_!VjΧsVL^j
XkQjU6sk̩n~[qǸ-`
O:G7l"ksRe2vQ=QƼJUX`gQy~ ďKȰE]#P:td\T/u;س:Jc-%'eq
?j"/yh48Zi1|JUu>_N;hxwNUJQU7\j̮bT:B?6oJ1Ί%I
UY-Ii4{=rǤ7@)HKJ+f4X8Cd?'j1 N<39EWo
VTGzg#
%D0#ܠ3[tiآ(U,]125|Ṋfw7w u+]Db]K xbW՛7|ВX㕛{UcGXk¬|( h)IUa)lp 3luPU]D)/7~4Wt5J}V
X0z VM;>Gԙ^|gF:jaZ^)74C#jwr,еSlGu;1vm><)}ZQՖ&mZ:1UMB~
a:/:KWWOҠ&Y2f7cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘g*3fF5LbN2#Tf=C`!ZGUee2V<1mkS4iϗ*.{N8Xaj~ڀnAx,%fE:|YDVj
¢lg6(:k~MM5?4 ]WO>诋WZiG|QGJeK[YcյpmjE\f/ǎ8&OQ3 .3tt2'-V8pXSrY#J!Q ",ub@FK:u^iy[]<.Cw +W\)b
kr-.MtڀMqʄ۰#$^X$"V`T4m~w%Pp1|+&UxY8*r8: k7QЃҀT$Ўƙ
S>~Sjs:5q.w&_Z.X=:ވbw` _kd{'0:ds#qi!224nq\9-KUTsSUuVo@;Uz>^=Np>oPO
@I@'Gj5o*U>^*ew>ͫʧQ5̈́<$#5Jٻj6e)_
d]2B:^(*:8JYS鬆Kݗ]U4_rj{5ׇaǑ/yV?GtGb@xPU7O3|鍪 IQ5QGw *(;wf0*PUU<YƔvbt5{2!,}Ҧ:)j2OkΪ'֊0I.q\(%ojQĖՇa<ԍexAgt'[d;`rcdjPFU$UeJI6T&Z}z(zvfuz {}ۿߝݞlxUZ謊.Y岟b%nw@ǩS9|źs%>_o#9\EU~/ځt(r[QZuOo;!MrU]0TcpDő? .cPuF;L_Sb}R/J_+h2$aiUǩS9>Є}76rzu~国4oĨ
1J
^̘~i C55G]gwsnzTuO=?/zƲc>Οb#7ֻcgkޛTUj*-T=]uu}>ݨNЭ
[
]:%/_Sz]6D.mD7Uƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1c>J4hPP+A;'G_XKmL5I.},wFFum$S-E-;Õ
C3I-`BRx1ғTJݕ;hΊ8DYJo;Y5MKɰM;%P d9KhnD[zgVh,'C
p!^M(WK2X>UQ%^p8 ˽^#Ζ+.@gCz%ɔ-Pr
KX
n>=ՔѨeSvRLz5%9UQS \WիK'hp)ô
Jrh
M0F(f_R5///G+x 1"eS5
:Tf=+7Qɧ\TEs༬rYs8&k#pSՊ5MTbD܊[Ng5Q\s 5PB@[ 8ɨV1&4Wsy[Ǿ
wU2V77jމd^~YfC_h;a.&M
i UWpzs`>/"'OI۲y:BzdTq£=йb:"m/-/PWDQǴ͐57m`H%AV!Hԛ@"Q zދ|ߒT-*OU^Ҧ6!Cwk|h&Hd5LEYy'ƣ7%*