Add pre-commit and ruff
Change-Id: Ide485356c1cb24510e9ea1c0aa8d84ac115916ff Signed-off-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: mixed-line-ending
|
||||
args: ['--fix', 'lf']
|
||||
exclude: '.*\.(svg)$'
|
||||
- id: fix-byte-order-marker
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: debug-statements
|
||||
- id: check-yaml
|
||||
files: .*\.(yaml|yml)$
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.8
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: ['--fix', '--unsafe-fixes']
|
||||
- id: ruff-format
|
||||
@@ -217,7 +217,7 @@ report etc (see the documentation).
|
||||
|
||||
* Adapted the wsme.sphinxext module to work with the function exposed by the
|
||||
``wsme.pecan`` adapter.
|
||||
|
||||
|
||||
* Allow promotion of ``int`` to ``float`` on float attributes (Doug Hellman)
|
||||
|
||||
* Add a ``samples_slot`` option to the ``.. autotype`` directive to
|
||||
@@ -385,7 +385,7 @@ wsme-extdirect
|
||||
- wsattr is now a python Descriptor, which makes it possible
|
||||
to retrieve the attribute definition on a class while
|
||||
manipulating values on the instance.
|
||||
|
||||
|
||||
- Add strong type validation on assignment (made possible by
|
||||
the use of Descriptors).
|
||||
|
||||
|
||||
+10
-12
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Web Services Made Easy documentation build configuration file
|
||||
|
||||
@@ -17,8 +16,8 @@ extensions = [
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Web Services Made Easy'
|
||||
copyright = u'2011, Christophe de Vienne'
|
||||
project = 'Web Services Made Easy'
|
||||
copyright = '2011, Christophe de Vienne'
|
||||
|
||||
suppress_warnings = ['app.add_directive']
|
||||
|
||||
@@ -41,20 +40,19 @@ autodoc_member_order = 'bysource'
|
||||
|
||||
# -- Options for sphinx.ext.intersphinx extension -----------------------------
|
||||
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/', None),
|
||||
}
|
||||
intersphinx_mapping = {'python': ('http://docs.python.org/', None)}
|
||||
|
||||
|
||||
# -- Options for wsme.sphinxext extension -------------------------------------
|
||||
|
||||
wsme_protocols = [
|
||||
'restjson', 'restxml',
|
||||
]
|
||||
wsme_protocols = ['restjson', 'restxml']
|
||||
|
||||
|
||||
def setup(app):
|
||||
# confval directive taken from the sphinx doc
|
||||
app.add_object_type('confval', 'confval',
|
||||
objname='configuration value',
|
||||
indextemplate='pair: %s; configuration value')
|
||||
app.add_object_type(
|
||||
'confval',
|
||||
'confval',
|
||||
objname='configuration value',
|
||||
indextemplate='pair: %s; configuration value',
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ For example, the '/ws/person/get' result looks like:
|
||||
And in case of error:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
|
||||
{
|
||||
'faultcode': 'Client',
|
||||
'faultstring': 'id is missing'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# coding=utf8
|
||||
"""
|
||||
A mini-demo of what wsme can do.
|
||||
|
||||
@@ -19,7 +18,7 @@ import bottle
|
||||
import logging
|
||||
|
||||
|
||||
class Person(object):
|
||||
class Person:
|
||||
id = int
|
||||
firstname = str
|
||||
lastname = str
|
||||
@@ -27,11 +26,7 @@ class Person(object):
|
||||
hobbies = [str]
|
||||
|
||||
def __repr__(self):
|
||||
return "Person(%s, %s %s, %s)" % (
|
||||
self.id,
|
||||
self.firstname, self.lastname,
|
||||
self.hobbies
|
||||
)
|
||||
return f"Person({self.id}, {self.firstname} {self.lastname}, {self.hobbies})"
|
||||
|
||||
|
||||
class DemoRoot(WSRoot):
|
||||
@@ -47,7 +42,7 @@ class DemoRoot(WSRoot):
|
||||
|
||||
@expose(str)
|
||||
def helloworld(self):
|
||||
return u"Здраво, свете (<- Hello World in Serbian !)"
|
||||
return "Здраво, свете (<- Hello World in Serbian !)"
|
||||
|
||||
@expose(Person)
|
||||
def getperson(self):
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(name='demo',
|
||||
install_requires=[
|
||||
'WSME',
|
||||
'Bottle',
|
||||
'Pygments',
|
||||
],
|
||||
package=['demo'])
|
||||
setup(
|
||||
name='demo',
|
||||
install_requires=['WSME', 'Bottle', 'Pygments'],
|
||||
package=['demo'],
|
||||
)
|
||||
|
||||
@@ -39,3 +39,19 @@ namespaces = true
|
||||
rest = "wsme.rest.protocol:RestProtocol"
|
||||
restjson = "wsme.rest.protocol:RestProtocol"
|
||||
restxml = "wsme.rest.protocol:RestProtocol"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 79
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "preserve"
|
||||
docstring-code-format = true
|
||||
skip-magic-trailing-comma = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["C4", "E4", "E5", "E7", "E9", "F", "LOG", "UP"]
|
||||
ignore = [
|
||||
# TODO(tkajinam): Fix these
|
||||
"E501",
|
||||
"E741",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True
|
||||
)
|
||||
setuptools.setup(setup_requires=['pbr'], pbr=True)
|
||||
|
||||
+11
-13
@@ -1,22 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
from setuptools import setup, find_packages
|
||||
except ImportError:
|
||||
from ez_setup import use_setuptools
|
||||
|
||||
use_setuptools()
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name = 'test',
|
||||
version = '0.1',
|
||||
description = '',
|
||||
author = '',
|
||||
author_email = '',
|
||||
install_requires = [
|
||||
"pecan",
|
||||
],
|
||||
test_suite = 'test',
|
||||
zip_safe = False,
|
||||
include_package_data = True,
|
||||
packages = find_packages(exclude=['ez_setup'])
|
||||
name='test',
|
||||
version='0.1',
|
||||
description='',
|
||||
author='',
|
||||
author_email='',
|
||||
install_requires=["pecan"],
|
||||
test_suite='test',
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from pecan import make_app
|
||||
from test import model
|
||||
|
||||
|
||||
def setup_app(config):
|
||||
|
||||
model.init_model()
|
||||
|
||||
|
||||
return make_app(
|
||||
config.app.root,
|
||||
static_root = config.app.static_root,
|
||||
template_path = config.app.template_path,
|
||||
logging = getattr(config, 'logging', {}),
|
||||
debug = getattr(config.app, 'debug', False),
|
||||
force_canonical = getattr(config.app, 'force_canonical', True)
|
||||
static_root=config.app.static_root,
|
||||
template_path=config.app.template_path,
|
||||
logging=getattr(config, 'logging', {}),
|
||||
debug=getattr(config.app, 'debug', False),
|
||||
force_canonical=getattr(config.app, 'force_canonical', True),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from .ws import AuthorsController
|
||||
from wsmeext.pecan import wsexpose
|
||||
|
||||
|
||||
class RootController(object):
|
||||
class RootController:
|
||||
authors = AuthorsController()
|
||||
|
||||
@expose('error.html')
|
||||
@@ -14,7 +14,7 @@ class RootController(object):
|
||||
except ValueError: # pragma: no cover
|
||||
status = 500
|
||||
message = getattr(status_map.get(status), 'explanation', '')
|
||||
return dict(status=status, message=message)
|
||||
return {'status': status, 'message': message}
|
||||
|
||||
@wsexpose()
|
||||
def divide_by_zero(self):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# encoding=utf8
|
||||
from pecan.rest import RestController
|
||||
|
||||
from wsme.types import Base, text, wsattr
|
||||
@@ -31,7 +30,7 @@ class BookNotFound(Exception):
|
||||
|
||||
def __init__(self, id):
|
||||
message = self.message.format(id=id)
|
||||
super(BookNotFound, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class NonHttpException(Exception):
|
||||
@@ -40,17 +39,16 @@ class NonHttpException(Exception):
|
||||
|
||||
def __init__(self, id):
|
||||
message = self.message.format(id=id)
|
||||
super(NonHttpException, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BooksController(RestController):
|
||||
|
||||
@wsmeext.pecan.wsexpose(Book, int, int)
|
||||
def get(self, author_id, id):
|
||||
book = Book(
|
||||
name=u"Les Confessions d’un révolutionnaire pour servir à "
|
||||
u"l’histoire de la révolution de février",
|
||||
author=Author(lastname=u"Proudhon")
|
||||
name="Les Confessions d’un révolutionnaire pour servir à "
|
||||
"l’histoire de la révolution de février",
|
||||
author=Author(lastname="Proudhon"),
|
||||
)
|
||||
return book
|
||||
|
||||
@@ -68,29 +66,17 @@ class Criterion(Base):
|
||||
|
||||
|
||||
class AuthorsController(RestController):
|
||||
|
||||
_custom_actions = {
|
||||
'json_only': ['GET'],
|
||||
'xml_only': ['GET']
|
||||
}
|
||||
_custom_actions = {'json_only': ['GET'], 'xml_only': ['GET']}
|
||||
|
||||
books = BooksController()
|
||||
|
||||
@wsmeext.pecan.wsexpose([Author], [str], [Criterion])
|
||||
def get_all(self, q=None, r=None):
|
||||
if q:
|
||||
return [
|
||||
Author(id=i, firstname=value)
|
||||
for i, value in enumerate(q)
|
||||
]
|
||||
return [Author(id=i, firstname=value) for i, value in enumerate(q)]
|
||||
if r:
|
||||
return [
|
||||
Author(id=i, firstname=c.value)
|
||||
for i, c in enumerate(r)
|
||||
]
|
||||
return [
|
||||
Author(id=1, firstname=u'FirstName')
|
||||
]
|
||||
return [Author(id=i, firstname=c.value) for i, c in enumerate(r)]
|
||||
return [Author(id=1, firstname='FirstName')]
|
||||
|
||||
@wsmeext.pecan.wsexpose(Author, int)
|
||||
def get(self, id):
|
||||
@@ -107,8 +93,7 @@ class AuthorsController(RestController):
|
||||
raise wsme.exc.ClientSideError('Disabled ID', status_code=403)
|
||||
|
||||
if id == 911:
|
||||
return wsme.api.Response(Author(),
|
||||
status_code=401)
|
||||
return wsme.api.Response(Author(), status_code=401)
|
||||
if id == 912:
|
||||
return wsme.api.Response(None, status_code=204)
|
||||
|
||||
@@ -117,11 +102,11 @@ class AuthorsController(RestController):
|
||||
|
||||
author = Author()
|
||||
author.id = id
|
||||
author.firstname = u"aname"
|
||||
author.firstname = "aname"
|
||||
author.books = [
|
||||
Book(
|
||||
name=u"Les Confessions d’un révolutionnaire pour servir à "
|
||||
u"l’histoire de la révolution de février",
|
||||
name="Les Confessions d’un révolutionnaire pour servir à "
|
||||
"l’histoire de la révolution de février"
|
||||
)
|
||||
]
|
||||
return author
|
||||
@@ -141,8 +126,8 @@ class AuthorsController(RestController):
|
||||
|
||||
@wsmeext.pecan.wsexpose([Author], rest_content_types=('json',))
|
||||
def json_only(self):
|
||||
return [Author(id=1, firstname=u"aname", books=[])]
|
||||
return [Author(id=1, firstname="aname", books=[])]
|
||||
|
||||
@wsmeext.pecan.wsexpose([Author], rest_content_types=('xml',))
|
||||
def xml_only(self):
|
||||
return [Author(id=1, firstname=u"aname", books=[])]
|
||||
return [Author(id=1, firstname="aname", books=[])]
|
||||
|
||||
@@ -13,10 +13,9 @@ class FunctionalTest(TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.app = testing.load_test_app(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'config.py'
|
||||
))
|
||||
self.app = testing.load_test_app(
|
||||
os.path.join(os.path.dirname(__file__), 'config.py')
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
set_config({}, overwrite=True)
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
'port' : '8080',
|
||||
'host' : '0.0.0.0'
|
||||
}
|
||||
server = {'port': '8080', 'host': '0.0.0.0'}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root' : 'test.controllers.root.RootController',
|
||||
'modules' : ['test'],
|
||||
'static_root' : '%(confdir)s/../../public',
|
||||
'template_path' : '%(confdir)s/../templates',
|
||||
'errors' : {
|
||||
'404' : '/error/404',
|
||||
'__force_dict__' : True
|
||||
}
|
||||
'root': 'test.controllers.root.RootController',
|
||||
'modules': ['test'],
|
||||
'static_root': '%(confdir)s/../../public',
|
||||
'template_path': '%(confdir)s/../templates',
|
||||
'errors': {'404': '/error/404', '__force_dict__': True},
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
|
||||
@@ -8,10 +8,10 @@ from test.tests import FunctionalTest
|
||||
used_status_codes = [400, 401, 403, 404, 500]
|
||||
http_response_messages = {}
|
||||
for code in used_status_codes:
|
||||
http_response_messages[code] = '%s %s' % (code, http_client.responses[code])
|
||||
http_response_messages[code] = f'{code} {http_client.responses[code]}'
|
||||
|
||||
|
||||
class TestWS(FunctionalTest):
|
||||
|
||||
def test_get_all(self):
|
||||
self.app.get('/authors')
|
||||
|
||||
@@ -44,26 +44,23 @@ class TestWS(FunctionalTest):
|
||||
assert l[1]['firstname'] == 'b'
|
||||
|
||||
def test_get_author(self):
|
||||
a = self.app.get(
|
||||
'/authors/1.json',
|
||||
)
|
||||
a = self.app.get('/authors/1.json')
|
||||
a = json.loads(a.body.decode('utf-8'))
|
||||
|
||||
assert a['id'] == 1
|
||||
assert a['firstname'] == 'aname'
|
||||
|
||||
a = self.app.get(
|
||||
'/authors/1.xml',
|
||||
)
|
||||
a = self.app.get('/authors/1.xml')
|
||||
body = a.body.decode('utf-8')
|
||||
assert '<id>1</id>' in body
|
||||
assert '<firstname>aname</firstname>' in body
|
||||
|
||||
def test_post_body_parameter_validation(self):
|
||||
res = self.app.post(
|
||||
'/authors', '{"firstname": "Robert"}',
|
||||
'/authors',
|
||||
'{"firstname": "Robert"}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True
|
||||
expect_errors=True,
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
@@ -72,8 +69,9 @@ class TestWS(FunctionalTest):
|
||||
|
||||
def test_post_body_parameter(self):
|
||||
res = self.app.post(
|
||||
'/authors', '{"firstname": "test"}',
|
||||
headers={"Content-Type": "application/json"}
|
||||
'/authors',
|
||||
'{"firstname": "test"}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
assert res.status_int == 201
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
@@ -82,9 +80,10 @@ class TestWS(FunctionalTest):
|
||||
|
||||
def test_put_parameter_validate(self):
|
||||
res = self.app.put(
|
||||
'/authors/foobar', '{"firstname": "test"}',
|
||||
'/authors/foobar',
|
||||
'{"firstname": "test"}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True
|
||||
expect_errors=True,
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
@@ -92,102 +91,72 @@ class TestWS(FunctionalTest):
|
||||
a['faultstring'],
|
||||
"Invalid input for field/attribute author_id. "
|
||||
"Value: 'foobar'. unable to convert to int. Error: invalid "
|
||||
"literal for int() with base 10: 'foobar'")
|
||||
"literal for int() with base 10: 'foobar'",
|
||||
)
|
||||
|
||||
def test_clientsideerror(self):
|
||||
expected_status_code = 400
|
||||
expected_status = http_response_messages[expected_status_code]
|
||||
res = self.app.get(
|
||||
'/authors/999.json',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/999.json', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
assert a['faultcode'] == 'Client'
|
||||
|
||||
res = self.app.get(
|
||||
'/authors/999.xml',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/999.xml', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
|
||||
|
||||
def test_custom_clientside_error(self):
|
||||
expected_status_code = 404
|
||||
expected_status = http_response_messages[expected_status_code]
|
||||
res = self.app.get(
|
||||
'/authors/998.json',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/998.json', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
assert a['faultcode'] == 'Client'
|
||||
|
||||
res = self.app.get(
|
||||
'/authors/998.xml',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/998.xml', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
|
||||
|
||||
def test_custom_non_http_clientside_error(self):
|
||||
expected_status_code = 500
|
||||
expected_status = http_response_messages[expected_status_code]
|
||||
res = self.app.get(
|
||||
'/authors/997.json',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/997.json', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
assert a['faultcode'] == 'Server'
|
||||
|
||||
res = self.app.get(
|
||||
'/authors/997.xml',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/997.xml', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
assert '<faultcode>Server</faultcode>' in res.body.decode('utf-8')
|
||||
|
||||
def test_clientsideerror_status_code(self):
|
||||
expected_status_code = 403
|
||||
expected_status = http_response_messages[expected_status_code]
|
||||
res = self.app.get(
|
||||
'/authors/996.json',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/996.json', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
a = json.loads(res.body.decode('utf-8'))
|
||||
assert a['faultcode'] == 'Client'
|
||||
|
||||
res = self.app.get(
|
||||
'/authors/996.xml',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/996.xml', expect_errors=True)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
assert '<faultcode>Client</faultcode>' in res.body.decode('utf-8')
|
||||
|
||||
def test_non_default_response(self):
|
||||
expected_status_code = 401
|
||||
expected_status = http_response_messages[expected_status_code]
|
||||
res = self.app.get(
|
||||
'/authors/911.json',
|
||||
expect_errors=True
|
||||
)
|
||||
res = self.app.get('/authors/911.json', expect_errors=True)
|
||||
self.assertEqual(res.status_int, expected_status_code)
|
||||
self.assertEqual(res.status, expected_status)
|
||||
|
||||
def test_non_default_response_return_type(self):
|
||||
res = self.app.get(
|
||||
'/authors/913',
|
||||
)
|
||||
res = self.app.get('/authors/913')
|
||||
self.assertEqual(res.status_int, 200)
|
||||
self.assertEqual(res.body, b'"foo"')
|
||||
self.assertEqual(res.content_length, 5)
|
||||
|
||||
def test_non_default_response_return_type_no_content(self):
|
||||
res = self.app.get(
|
||||
'/authors/912',
|
||||
)
|
||||
res = self.app.get('/authors/912')
|
||||
self.assertEqual(res.status_int, 204)
|
||||
self.assertEqual(res.body, b'')
|
||||
self.assertEqual(res.content_length, 0)
|
||||
@@ -216,7 +185,7 @@ class TestWS(FunctionalTest):
|
||||
assert res.status_int == 200
|
||||
body = json.loads(res.body.decode('utf-8'))
|
||||
assert len(body) == 1
|
||||
assert body[0]['firstname'] == u"aname"
|
||||
assert body[0]['firstname'] == "aname"
|
||||
assert body[0]['books'] == []
|
||||
assert body[0]['id'] == 1
|
||||
res = self.app.get('/authors/json_only.xml', expect_errors=True)
|
||||
@@ -233,7 +202,7 @@ class TestWS(FunctionalTest):
|
||||
res = self.app.put(
|
||||
'/authors/1/books/2.json',
|
||||
'{"name": "Alice au pays des merveilles"}',
|
||||
headers={"Content-Type": "application/json"}
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
book = json.loads(res.body.decode('utf-8'))
|
||||
assert book['id'] == 2
|
||||
|
||||
@@ -7,14 +7,12 @@ from wsme.rest import json
|
||||
|
||||
|
||||
class TestArgs(unittest.TestCase):
|
||||
|
||||
def test_args_from_body(self):
|
||||
|
||||
funcdef = mock.MagicMock()
|
||||
body = mock.MagicMock()
|
||||
mimetype = "application/json"
|
||||
funcdef.ignore_extra_args = True
|
||||
json.parse = mock.MagicMock()
|
||||
json.parse.side_effect = (exc.UnknownArgument(""))
|
||||
json.parse.side_effect = exc.UnknownArgument("")
|
||||
resp = args.args_from_body(funcdef, body, mimetype)
|
||||
self.assertEqual(resp, ((), {}))
|
||||
|
||||
+58
-52
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Web Services Made Easy documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Oct 2 20:27:45 2011.
|
||||
@@ -11,7 +10,10 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import sys
|
||||
import os
|
||||
|
||||
import pbr.version
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -21,7 +23,7 @@ sys.path.insert(0, os.path.abspath('..'))
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
@@ -34,7 +36,7 @@ templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
@@ -47,7 +49,6 @@ copyright = '2011, Christophe de Vienne'
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
import pbr.version
|
||||
version_info = pbr.version.VersionInfo('WSME')
|
||||
|
||||
# The short X.Y version.
|
||||
@@ -57,37 +58,37 @@ release = version_info.version_string()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
@@ -95,82 +96,79 @@ pygments_style = 'sphinx'
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'agogo'
|
||||
html_theme_options = {
|
||||
"pagewidth": "60em",
|
||||
"documentwidth": "40em",
|
||||
}
|
||||
html_theme_options = {"pagewidth": "60em", "documentwidth": "40em"}
|
||||
|
||||
html_style = 'wsme.css'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = "WSME %s" % release
|
||||
html_title = f"WSME {release}"
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
#html_static_path = ['_static']
|
||||
# html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'WebServicesMadeEasydoc'
|
||||
@@ -179,40 +177,45 @@ htmlhelp_basename = 'WebServicesMadeEasydoc'
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
# latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
# latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'WebServicesMadeEasy.tex', 'Web Services Made Easy Documentation',
|
||||
'Christophe de Vienne', 'manual'),
|
||||
(
|
||||
'index',
|
||||
'WebServicesMadeEasy.tex',
|
||||
'Web Services Made Easy Documentation',
|
||||
'Christophe de Vienne',
|
||||
'manual',
|
||||
)
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
# latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
@@ -220,13 +223,16 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'webservicesmadeeasy', 'Web Services Made Easy Documentation',
|
||||
['Christophe de Vienne'], 1)
|
||||
(
|
||||
'index',
|
||||
'webservicesmadeeasy',
|
||||
'Web Services Made Easy Documentation',
|
||||
['Christophe de Vienne'],
|
||||
1,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
autodoc_member_order = 'bysource'
|
||||
|
||||
wsme_protocols = [
|
||||
'restjson', 'restxml'
|
||||
]
|
||||
wsme_protocols = ['restjson', 'restxml']
|
||||
|
||||
+29
-27
@@ -1,4 +1,3 @@
|
||||
# encoding=utf8
|
||||
import unittest
|
||||
from flask import Flask, json, abort
|
||||
import flask_restful as restful
|
||||
@@ -18,6 +17,7 @@ class Criterion(Base):
|
||||
attr = text
|
||||
value = text
|
||||
|
||||
|
||||
test_app = Flask(__name__)
|
||||
api = restful.Api(test_app)
|
||||
|
||||
@@ -61,6 +61,7 @@ def model_secret(name):
|
||||
def model_custom_error(name):
|
||||
class CustomError(Exception):
|
||||
code = 412
|
||||
|
||||
raise CustomError("FOO!")
|
||||
|
||||
|
||||
@@ -85,17 +86,17 @@ def get_status_response():
|
||||
class RestFullApi(restful.Resource):
|
||||
@signature(Model)
|
||||
def get(self):
|
||||
return Model(id=1, name=u"Gérard")
|
||||
return Model(id=1, name="Gérard")
|
||||
|
||||
@signature(int, body=Model)
|
||||
def post(self, model):
|
||||
return model.id
|
||||
|
||||
|
||||
api.add_resource(RestFullApi, '/restful')
|
||||
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
test_app.config['TESTING'] = True
|
||||
self.app = test_app.test_client()
|
||||
@@ -118,9 +119,7 @@ class FlaskrTestCase(unittest.TestCase):
|
||||
def test_array_parameter(self):
|
||||
resp = self.app.get('/models?q.op=%3D&q.attr=name&q.value=second')
|
||||
assert resp.status_code == 200
|
||||
self.assertEqual(
|
||||
resp.data, b'[{"name": "second"}]'
|
||||
)
|
||||
self.assertEqual(resp.data, b'[{"name": "second"}]')
|
||||
|
||||
def test_post_model(self):
|
||||
resp = self.app.post('/models', data={"body.name": "test"})
|
||||
@@ -128,7 +127,7 @@ class FlaskrTestCase(unittest.TestCase):
|
||||
resp = self.app.post(
|
||||
'/models',
|
||||
data=json.dumps({"name": "test"}),
|
||||
content_type="application/json"
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
@@ -142,40 +141,40 @@ class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def test_custom_clientside_error(self):
|
||||
r = self.app.get(
|
||||
'/models/test/secret',
|
||||
headers={'Accept': 'application/json'}
|
||||
'/models/test/secret', headers={'Accept': 'application/json'}
|
||||
)
|
||||
assert r.status_code == 403, r.status_code
|
||||
assert '403 Forbidden:' in json.loads(r.data)['faultstring']
|
||||
|
||||
r = self.app.get(
|
||||
'/models/test/secret',
|
||||
headers={'Accept': 'application/xml'}
|
||||
'/models/test/secret', headers={'Accept': 'application/xml'}
|
||||
)
|
||||
assert r.status_code == 403, r.status_code
|
||||
assert r.data == (b"<error><faultcode>Client</faultcode>"
|
||||
b"<faultstring>403 Forbidden: You don't have the "
|
||||
b"permission to access the requested resource. It "
|
||||
b"is either read-protected or not readable by the "
|
||||
b"server."
|
||||
b"</faultstring><debuginfo /></error>")
|
||||
assert r.data == (
|
||||
b"<error><faultcode>Client</faultcode>"
|
||||
b"<faultstring>403 Forbidden: You don't have the "
|
||||
b"permission to access the requested resource. It "
|
||||
b"is either read-protected or not readable by the "
|
||||
b"server."
|
||||
b"</faultstring><debuginfo /></error>"
|
||||
)
|
||||
|
||||
def test_custom_non_http_clientside_error(self):
|
||||
r = self.app.get(
|
||||
'/models/test/custom-error',
|
||||
headers={'Accept': 'application/json'}
|
||||
'/models/test/custom-error', headers={'Accept': 'application/json'}
|
||||
)
|
||||
assert r.status_code == 412, r.status_code
|
||||
assert json.loads(r.data)['faultstring'] == 'FOO!'
|
||||
|
||||
r = self.app.get(
|
||||
'/models/test/custom-error',
|
||||
headers={'Accept': 'application/xml'}
|
||||
'/models/test/custom-error', headers={'Accept': 'application/xml'}
|
||||
)
|
||||
assert r.status_code == 412, r.status_code
|
||||
assert r.data == (b'<error><faultcode>Client</faultcode>'
|
||||
b'<faultstring>FOO!</faultstring>'
|
||||
b'<debuginfo /></error>')
|
||||
assert r.data == (
|
||||
b'<error><faultcode>Client</faultcode>'
|
||||
b'<faultstring>FOO!</faultstring>'
|
||||
b'<debuginfo /></error>'
|
||||
)
|
||||
|
||||
def test_serversideerror(self):
|
||||
r = self.app.get('/divide_by_zero')
|
||||
@@ -192,20 +191,23 @@ class FlaskrTestCase(unittest.TestCase):
|
||||
data = json.loads(r.data)
|
||||
|
||||
self.assertEqual(data['id'], 1)
|
||||
self.assertEqual(data['name'], u"Gérard")
|
||||
self.assertEqual(data['name'], "Gérard")
|
||||
|
||||
def test_restful_post(self):
|
||||
r = self.app.post(
|
||||
'/restful',
|
||||
data=json.dumps({'id': 5, 'name': u'Huguette'}),
|
||||
data=json.dumps({'id': 5, 'name': 'Huguette'}),
|
||||
headers={
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'})
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
data = json.loads(r.data)
|
||||
|
||||
self.assertEqual(data, 5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_app.run()
|
||||
|
||||
+14
-13
@@ -5,12 +5,10 @@ import os.path
|
||||
import wsme.types
|
||||
from wsmeext import sphinxext
|
||||
|
||||
docpath = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'sphinxexample')
|
||||
docpath = os.path.join(os.path.dirname(__file__), 'sphinxexample')
|
||||
|
||||
|
||||
class ASampleType(object):
|
||||
class ASampleType:
|
||||
somebytes = wsme.types.bytes
|
||||
sometext = wsme.types.text
|
||||
someint = int
|
||||
@@ -21,13 +19,17 @@ class TestSphinxExt(unittest.TestCase):
|
||||
if not os.path.exists('.test_sphinxext/'):
|
||||
os.makedirs('.test_sphinxext/')
|
||||
try:
|
||||
build.main([
|
||||
# '',
|
||||
'-b', 'html',
|
||||
'-d', '.test_sphinxext/doctree',
|
||||
docpath,
|
||||
'.test_sphinxext/html'
|
||||
])
|
||||
build.main(
|
||||
[
|
||||
# '',
|
||||
'-b',
|
||||
'html',
|
||||
'-d',
|
||||
'.test_sphinxext/doctree',
|
||||
docpath,
|
||||
'.test_sphinxext/html',
|
||||
]
|
||||
)
|
||||
assert Exception("Should raise SystemExit 0")
|
||||
except SystemExit as e:
|
||||
print(e)
|
||||
@@ -36,8 +38,7 @@ class TestSphinxExt(unittest.TestCase):
|
||||
|
||||
class TestDataTypeName(unittest.TestCase):
|
||||
def test_user_type(self):
|
||||
self.assertEqual(sphinxext.datatypename(ASampleType),
|
||||
'ASampleType')
|
||||
self.assertEqual(sphinxext.datatypename(ASampleType), 'ASampleType')
|
||||
|
||||
def test_dict_type(self):
|
||||
d = wsme.types.DictType(str, str)
|
||||
|
||||
@@ -25,9 +25,10 @@ commands =
|
||||
sphinx-build -W -b html doc/source doc/build/html
|
||||
|
||||
[testenv:pep8]
|
||||
deps = flake8
|
||||
skip_install = True
|
||||
deps = pre-commit
|
||||
commands =
|
||||
flake8 wsme wsmeext setup.py
|
||||
pre-commit run -a
|
||||
|
||||
[testenv:venv]
|
||||
usedevelop = True
|
||||
|
||||
+6
-2
@@ -4,7 +4,11 @@ from wsme.root import WSRoot
|
||||
from wsme.types import wsattr, wsproperty, Unset
|
||||
|
||||
__all__ = [
|
||||
'expose', 'validate', 'signature',
|
||||
'expose',
|
||||
'validate',
|
||||
'signature',
|
||||
'WSRoot',
|
||||
'wsattr', 'wsproperty', 'Unset'
|
||||
'wsattr',
|
||||
'wsproperty',
|
||||
'Unset',
|
||||
]
|
||||
|
||||
+29
-19
@@ -19,6 +19,7 @@ def wrapfunc(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
wrapper._wsme_original_func = f
|
||||
return wrapper
|
||||
|
||||
@@ -28,10 +29,11 @@ def getargspec(f):
|
||||
return inspect.getfullargspec(f)
|
||||
|
||||
|
||||
class FunctionArgument(object):
|
||||
class FunctionArgument:
|
||||
"""
|
||||
An argument definition of an api entry
|
||||
"""
|
||||
|
||||
def __init__(self, name, datatype, mandatory, default):
|
||||
#: argument name
|
||||
self.name = name
|
||||
@@ -49,10 +51,11 @@ class FunctionArgument(object):
|
||||
self.datatype = registry.resolve_type(self.datatype)
|
||||
|
||||
|
||||
class FunctionDefinition(object):
|
||||
class FunctionDefinition:
|
||||
"""
|
||||
An api entry definition
|
||||
"""
|
||||
|
||||
def __init__(self, func):
|
||||
#: Function name
|
||||
self.name = func.__name__
|
||||
@@ -111,8 +114,14 @@ class FunctionDefinition(object):
|
||||
for arg in self.arguments:
|
||||
arg.resolve_type(registry)
|
||||
|
||||
def set_options(self, body=None, ignore_extra_args=False, status_code=200,
|
||||
rest_content_types=('json', 'xml'), **extra_options):
|
||||
def set_options(
|
||||
self,
|
||||
body=None,
|
||||
ignore_extra_args=False,
|
||||
status_code=200,
|
||||
rest_content_types=('json', 'xml'),
|
||||
**extra_options,
|
||||
):
|
||||
self.body_type = body
|
||||
self.status_code = status_code
|
||||
self.ignore_extra_args = ignore_extra_args
|
||||
@@ -136,12 +145,12 @@ class FunctionDefinition(object):
|
||||
if datatype is wsme.types.HostRequest:
|
||||
self.pass_request = argname
|
||||
else:
|
||||
self.arguments.append(FunctionArgument(argname, datatype,
|
||||
mandatory, default))
|
||||
self.arguments.append(
|
||||
FunctionArgument(argname, datatype, mandatory, default)
|
||||
)
|
||||
|
||||
|
||||
class signature(object):
|
||||
|
||||
class signature:
|
||||
"""Decorator that specify the argument types of an exposed function.
|
||||
|
||||
:param return_type: Type of the value returned by the function
|
||||
@@ -186,12 +195,14 @@ class signature(object):
|
||||
sig = signature
|
||||
|
||||
|
||||
class Response(object):
|
||||
class Response:
|
||||
"""
|
||||
Object to hold the "response" from a view function
|
||||
"""
|
||||
def __init__(self, obj, status_code=None, error=None,
|
||||
return_type=wsme.types.Unset):
|
||||
|
||||
def __init__(
|
||||
self, obj, status_code=None, error=None, return_type=wsme.types.Unset
|
||||
):
|
||||
#: Store the result object from the view
|
||||
self.obj = obj
|
||||
|
||||
@@ -215,23 +226,22 @@ def format_exception(excinfo, debug=False):
|
||||
error = excinfo[1]
|
||||
code = getattr(error, 'code', None)
|
||||
if code and utils.is_valid_code(code) and utils.is_client_error(code):
|
||||
faultstring = (error.faultstring if hasattr(error, 'faultstring')
|
||||
else str(error))
|
||||
faultstring = (
|
||||
error.faultstring if hasattr(error, 'faultstring') else str(error)
|
||||
)
|
||||
faultcode = getattr(error, 'faultcode', 'Client')
|
||||
r = dict(faultcode=faultcode,
|
||||
faultstring=faultstring)
|
||||
log.debug("Client-side error: %s" % r['faultstring'])
|
||||
r = {'faultcode': faultcode, 'faultstring': faultstring}
|
||||
log.debug("Client-side error: {}".format(r['faultstring']))
|
||||
r['debuginfo'] = None
|
||||
return r
|
||||
else:
|
||||
faultstring = str(error)
|
||||
debuginfo = "\n".join(traceback.format_exception(*excinfo))
|
||||
|
||||
log.error('Server-side error: "%s". Detail: \n%s' % (
|
||||
faultstring, debuginfo))
|
||||
log.error(f'Server-side error: "{faultstring}". Detail: \n{debuginfo}')
|
||||
|
||||
faultcode = getattr(error, 'faultcode', 'Server')
|
||||
r = dict(faultcode=faultcode, faultstring=faultstring)
|
||||
r = {'faultcode': faultcode, 'faultstring': faultstring}
|
||||
if debug:
|
||||
r['debuginfo'] = debuginfo
|
||||
else:
|
||||
|
||||
+15
-11
@@ -6,7 +6,7 @@ class ClientSideError(RuntimeError):
|
||||
self.msg = msg
|
||||
self.code = status_code
|
||||
self.faultcode = faultcode
|
||||
super(ClientSideError, self).__init__(self.faultstring)
|
||||
super().__init__(self.faultstring)
|
||||
|
||||
@property
|
||||
def faultstring(self):
|
||||
@@ -22,43 +22,47 @@ class InvalidInput(ClientSideError):
|
||||
def __init__(self, fieldname, value, msg=''):
|
||||
self.fieldname = fieldname
|
||||
self.value = value
|
||||
super(InvalidInput, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
|
||||
@property
|
||||
def faultstring(self):
|
||||
return _("Invalid input for field/attribute %s. Value: '%s'. %s") % (
|
||||
self.fieldname, self.value, self.msg,
|
||||
self.fieldname,
|
||||
self.value,
|
||||
self.msg,
|
||||
)
|
||||
|
||||
|
||||
class MissingArgument(ClientSideError):
|
||||
def __init__(self, argname, msg=''):
|
||||
self.argname = argname
|
||||
super(MissingArgument, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
|
||||
@property
|
||||
def faultstring(self):
|
||||
return _('Missing argument: "%s"%s') % (
|
||||
self.argname, self.msg and ": " + self.msg or "",
|
||||
self.argname,
|
||||
self.msg and ": " + self.msg or "",
|
||||
)
|
||||
|
||||
|
||||
class UnknownArgument(ClientSideError):
|
||||
def __init__(self, argname, msg=''):
|
||||
self.argname = argname
|
||||
super(UnknownArgument, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
|
||||
@property
|
||||
def faultstring(self):
|
||||
return _('Unknown argument: "%s"%s') % (
|
||||
self.argname, self.msg and ": " + self.msg or "",
|
||||
self.argname,
|
||||
self.msg and ": " + self.msg or "",
|
||||
)
|
||||
|
||||
|
||||
class UnknownFunction(ClientSideError):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
super(UnknownFunction, self).__init__()
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def faultstring(self):
|
||||
@@ -70,7 +74,7 @@ class UnknownAttribute(ClientSideError):
|
||||
self.fieldname = fieldname
|
||||
self.attributes = attributes
|
||||
self.msg = msg
|
||||
super(UnknownAttribute, self).__init__(self.msg)
|
||||
super().__init__(self.msg)
|
||||
|
||||
@property
|
||||
def faultstring(self):
|
||||
@@ -87,7 +91,7 @@ class UnknownAttribute(ClientSideError):
|
||||
calls to this method will prepend ``name`` to the hierarchy of names.
|
||||
"""
|
||||
if self.fieldname is not None:
|
||||
self.fieldname = "{}.{}".format(name, self.fieldname)
|
||||
self.fieldname = f"{name}.{self.fieldname}"
|
||||
else:
|
||||
self.fieldname = name
|
||||
super(UnknownAttribute, self).__init__(self.msg)
|
||||
super().__init__(self.msg)
|
||||
|
||||
+10
-15
@@ -4,11 +4,7 @@ import weakref
|
||||
from wsme.exc import ClientSideError
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CallContext',
|
||||
|
||||
'register_protocol', 'getprotocol',
|
||||
]
|
||||
__all__ = ['CallContext', 'register_protocol', 'getprotocol']
|
||||
|
||||
registered_protocols = {}
|
||||
|
||||
@@ -20,7 +16,7 @@ def _cfg(f):
|
||||
return cfg
|
||||
|
||||
|
||||
class expose(object):
|
||||
class expose:
|
||||
def __init__(self, path, content_type):
|
||||
self.path = path
|
||||
self.content_type = content_type
|
||||
@@ -33,7 +29,7 @@ class expose(object):
|
||||
return func
|
||||
|
||||
|
||||
class CallContext(object):
|
||||
class CallContext:
|
||||
def __init__(self, request):
|
||||
self._request = weakref.ref(request)
|
||||
self.path = None
|
||||
@@ -46,7 +42,7 @@ class CallContext(object):
|
||||
return self._request()
|
||||
|
||||
|
||||
class ObjectDict(object):
|
||||
class ObjectDict:
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
@@ -54,7 +50,7 @@ class ObjectDict(object):
|
||||
return getattr(self.obj, name)
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
class Protocol:
|
||||
name = None
|
||||
displayname = None
|
||||
content_types = []
|
||||
@@ -62,6 +58,7 @@ class Protocol(object):
|
||||
def resolve_path(self, path):
|
||||
if '$' in path:
|
||||
from string import Template
|
||||
|
||||
s = Template(path)
|
||||
path = s.substitute(ObjectDict(self))
|
||||
return path
|
||||
@@ -106,12 +103,12 @@ def getprotocol(name, **options):
|
||||
protocol_class = registered_protocols.get(name)
|
||||
if protocol_class is None:
|
||||
for entry_point in importlib.metadata.entry_points(
|
||||
group='wsme.protocols',
|
||||
group='wsme.protocols'
|
||||
):
|
||||
if entry_point.name == name:
|
||||
protocol_class = entry_point.load()
|
||||
if protocol_class is None:
|
||||
raise ValueError("Cannot find protocol '%s'" % name)
|
||||
raise ValueError(f"Cannot find protocol '{name}'")
|
||||
registered_protocols[name] = protocol_class
|
||||
return protocol_class(**options)
|
||||
|
||||
@@ -128,8 +125,7 @@ def media_type_accept(request, content_types):
|
||||
if request.accept:
|
||||
if request.accept.acceptable_offers(content_types):
|
||||
return True
|
||||
error_message = ('Unacceptable Accept type: %s not in %s'
|
||||
% (request.accept, content_types))
|
||||
error_message = f'Unacceptable Accept type: {request.accept} not in {content_types}'
|
||||
raise ClientSideError(error_message, status_code=406)
|
||||
elif request.method in ['PUT', 'POST', 'PATCH']:
|
||||
content_type = request.headers.get('Content-Type')
|
||||
@@ -137,8 +133,7 @@ def media_type_accept(request, content_types):
|
||||
for ct in content_types:
|
||||
if request.headers.get('Content-Type', '').startswith(ct):
|
||||
return True
|
||||
error_message = ('Unacceptable Content-Type: %s not in %s'
|
||||
% (content_type, content_types))
|
||||
error_message = f'Unacceptable Content-Type: {content_type} not in {content_types}'
|
||||
raise ClientSideError(error_message, status_code=415)
|
||||
else:
|
||||
raise ClientSideError('missing Content-Type header')
|
||||
|
||||
@@ -4,7 +4,7 @@ import wsme.api
|
||||
APIPATH_MAXLEN = 20
|
||||
|
||||
|
||||
class expose(object):
|
||||
class expose:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.signature = wsme.api.signature(*args, **kwargs)
|
||||
|
||||
@@ -33,7 +33,7 @@ class expose(object):
|
||||
return cls.with_method('DELETE', *args, **kwargs)
|
||||
|
||||
|
||||
class validate(object):
|
||||
class validate:
|
||||
"""
|
||||
Decorator that define the arguments types of a function.
|
||||
|
||||
@@ -46,6 +46,7 @@ class validate(object):
|
||||
def format(self, d, t):
|
||||
return d.isoformat() + ' ' + t.isoformat()
|
||||
"""
|
||||
|
||||
def __init__(self, *param_types):
|
||||
self.param_types = param_types
|
||||
|
||||
@@ -74,5 +75,4 @@ def scan_api(controller, path=[], objects=[]):
|
||||
else:
|
||||
if len(path) > APIPATH_MAXLEN:
|
||||
raise ValueError("Path is too long: " + str(path))
|
||||
for i in scan_api(a, path + [name], objects + [a]):
|
||||
yield i
|
||||
yield from scan_api(a, path + [name], objects + [a])
|
||||
|
||||
+34
-38
@@ -38,6 +38,7 @@ def filetype_from_param(datatype, value):
|
||||
# TODO(johnsom) Remove once python 3.13 is the minimum supported version.
|
||||
try:
|
||||
import cgi
|
||||
|
||||
if isinstance(value, cgi.FieldStorage):
|
||||
return File(fieldstorage=value)
|
||||
except ImportError:
|
||||
@@ -47,18 +48,14 @@ def filetype_from_param(datatype, value):
|
||||
|
||||
@from_param.when_type(UserType)
|
||||
def usertype_from_param(datatype, value):
|
||||
return datatype.frombasetype(
|
||||
from_param(datatype.basetype, value))
|
||||
return datatype.frombasetype(from_param(datatype.basetype, value))
|
||||
|
||||
|
||||
@from_param.when_type(ArrayType)
|
||||
def array_from_param(datatype, value):
|
||||
if value is None:
|
||||
return value
|
||||
return [
|
||||
from_param(datatype.item_type, item)
|
||||
for item in value
|
||||
]
|
||||
return [from_param(datatype.item_type, item) for item in value]
|
||||
|
||||
|
||||
@generic
|
||||
@@ -74,7 +71,9 @@ def from_params(datatype, params, path, hit_paths):
|
||||
for attrdef in list_attributes(datatype):
|
||||
value = from_params(
|
||||
attrdef.datatype,
|
||||
params, '%s.%s' % (path, attrdef.key), hit_paths
|
||||
params,
|
||||
f'{path}.{attrdef.key}',
|
||||
hit_paths,
|
||||
)
|
||||
if value is not Unset:
|
||||
setattr(r, attrdef.key, value)
|
||||
@@ -96,15 +95,17 @@ def array_from_params(datatype, params, path, hit_paths):
|
||||
# werkzeug multidict
|
||||
def getall(params, path): # noqa
|
||||
return params.getlist(path)
|
||||
|
||||
if path in params:
|
||||
hit_paths.add(path)
|
||||
return [
|
||||
from_param(datatype.item_type, value)
|
||||
for value in getall(params, path)]
|
||||
for value in getall(params, path)
|
||||
]
|
||||
|
||||
if iscomplex(datatype.item_type):
|
||||
attributes = set()
|
||||
r = re.compile(r'^%s\.(?P<attrname>[^\.])' % re.escape(path))
|
||||
r = re.compile(rf'^{re.escape(path)}\.(?P<attrname>[^\.])')
|
||||
for p in params.keys():
|
||||
m = r.match(p)
|
||||
if m:
|
||||
@@ -112,7 +113,7 @@ def array_from_params(datatype, params, path, hit_paths):
|
||||
if attributes:
|
||||
value = []
|
||||
for attrdef in list_attributes(datatype.item_type):
|
||||
attrpath = '%s.%s' % (path, attrdef.key)
|
||||
attrpath = f'{path}.{attrdef.key}'
|
||||
hit_paths.add(attrpath)
|
||||
attrvalues = getall(params, attrpath)
|
||||
if len(value) < len(attrvalues):
|
||||
@@ -124,12 +125,12 @@ def array_from_params(datatype, params, path, hit_paths):
|
||||
setattr(
|
||||
value[i],
|
||||
attrdef.key,
|
||||
from_param(attrdef.datatype, attrvalue)
|
||||
from_param(attrdef.datatype, attrvalue),
|
||||
)
|
||||
return value
|
||||
|
||||
indexes = set()
|
||||
r = re.compile(r'^%s\[(?P<index>\d+)\]' % re.escape(path))
|
||||
r = re.compile(rf'^{re.escape(path)}\[(?P<index>\d+)\]')
|
||||
|
||||
for p in params.keys():
|
||||
m = r.match(p)
|
||||
@@ -142,16 +143,16 @@ def array_from_params(datatype, params, path, hit_paths):
|
||||
indexes = list(indexes)
|
||||
indexes.sort()
|
||||
|
||||
return [from_params(datatype.item_type, params,
|
||||
'%s[%s]' % (path, index), hit_paths)
|
||||
for index in indexes]
|
||||
return [
|
||||
from_params(datatype.item_type, params, f'{path}[{index}]', hit_paths)
|
||||
for index in indexes
|
||||
]
|
||||
|
||||
|
||||
@from_params.when_type(DictType)
|
||||
def dict_from_params(datatype, params, path, hit_paths):
|
||||
|
||||
keys = set()
|
||||
r = re.compile(r'^%s\[(?P<key>[a-zA-Z0-9_\.]+)\]' % re.escape(path))
|
||||
r = re.compile(rf'^{re.escape(path)}\[(?P<key>[a-zA-Z0-9_\.]+)\]')
|
||||
|
||||
for p in params.keys():
|
||||
m = r.match(p)
|
||||
@@ -161,10 +162,12 @@ def dict_from_params(datatype, params, path, hit_paths):
|
||||
if not keys:
|
||||
return Unset
|
||||
|
||||
return dict((
|
||||
(key, from_params(datatype.value_type,
|
||||
params, '%s[%s]' % (path, key), hit_paths))
|
||||
for key in keys))
|
||||
return {
|
||||
key: from_params(
|
||||
datatype.value_type, params, f'{path}[{key}]', hit_paths
|
||||
)
|
||||
for key in keys
|
||||
}
|
||||
|
||||
|
||||
@from_params.when_type(UserType)
|
||||
@@ -177,7 +180,7 @@ def usertype_from_params(datatype, params, path, hit_paths):
|
||||
|
||||
def args_from_args(funcdef, args, kwargs):
|
||||
newargs = []
|
||||
for argdef, arg in zip(funcdef.arguments[:len(args)], args):
|
||||
for argdef, arg in zip(funcdef.arguments[: len(args)], args):
|
||||
try:
|
||||
newargs.append(from_param(argdef.datatype, arg))
|
||||
except Exception as e:
|
||||
@@ -190,8 +193,8 @@ def args_from_args(funcdef, args, kwargs):
|
||||
raise InvalidInput(
|
||||
argdef.name,
|
||||
arg,
|
||||
"unable to convert to %(datatype)s. Error: %(error)s" % {
|
||||
'datatype': datatype_name, 'error': e})
|
||||
f"unable to convert to {datatype_name}. Error: {e}",
|
||||
)
|
||||
newkwargs = {}
|
||||
for argname, value in kwargs.items():
|
||||
newkwargs[argname] = from_param(
|
||||
@@ -204,8 +207,7 @@ def args_from_params(funcdef, params):
|
||||
kw = {}
|
||||
hit_paths = set()
|
||||
for argdef in funcdef.arguments:
|
||||
value = from_params(
|
||||
argdef.datatype, params, argdef.name, hit_paths)
|
||||
value = from_params(argdef.datatype, params, argdef.name, hit_paths)
|
||||
if value is not Unset:
|
||||
kw[argdef.name] = value
|
||||
paths = set(params.keys())
|
||||
@@ -224,7 +226,7 @@ def args_from_body(funcdef, body, mimetype):
|
||||
if funcdef.body_type is not None:
|
||||
datatypes = {funcdef.arguments[-1].name: funcdef.body_type}
|
||||
else:
|
||||
datatypes = dict(((a.name, a.datatype) for a in funcdef.arguments))
|
||||
datatypes = {a.name: a.datatype for a in funcdef.arguments}
|
||||
|
||||
if not body:
|
||||
return (), {}
|
||||
@@ -236,8 +238,7 @@ def args_from_body(funcdef, body, mimetype):
|
||||
elif mimetype in restxml.accept_content_types:
|
||||
dataformat = restxml
|
||||
else:
|
||||
raise ClientSideError("Unknown mimetype: %s" % mimetype,
|
||||
status_code=415)
|
||||
raise ClientSideError(f"Unknown mimetype: {mimetype}", status_code=415)
|
||||
|
||||
try:
|
||||
kw = dataformat.parse(
|
||||
@@ -257,14 +258,12 @@ def combine_args(funcdef, akw, allow_override=False):
|
||||
for i, arg in enumerate(args):
|
||||
n = funcdef.arguments[i].name
|
||||
if not allow_override and n in newkwargs:
|
||||
raise ClientSideError(
|
||||
"Parameter %s was given several times" % n)
|
||||
raise ClientSideError(f"Parameter {n} was given several times")
|
||||
newkwargs[n] = arg
|
||||
for name, value in kwargs.items():
|
||||
n = str(name)
|
||||
if not allow_override and n in newkwargs:
|
||||
raise ClientSideError(
|
||||
"Parameter %s was given several times" % n)
|
||||
raise ClientSideError(f"Parameter {n} was given several times")
|
||||
newkwargs[n] = value
|
||||
return newargs, newkwargs
|
||||
|
||||
@@ -299,14 +298,11 @@ def get_args(funcdef, args, kwargs, params, form, body, mimetype):
|
||||
|
||||
# combine params and body arguments
|
||||
from_params_and_body = combine_args(
|
||||
funcdef,
|
||||
(from_params, from_form_params, from_body)
|
||||
funcdef, (from_params, from_form_params, from_body)
|
||||
)
|
||||
|
||||
args, kwargs = combine_args(
|
||||
funcdef,
|
||||
(from_args, from_params_and_body),
|
||||
allow_override=True
|
||||
funcdef, (from_args, from_params_and_body), allow_override=True
|
||||
)
|
||||
wsme.runtime.check_arguments(funcdef, args, kwargs)
|
||||
return args, kwargs
|
||||
|
||||
+47
-32
@@ -16,7 +16,7 @@ content_type = 'application/json'
|
||||
accept_content_types = [
|
||||
content_type,
|
||||
'text/javascript',
|
||||
'application/javascript'
|
||||
'application/javascript',
|
||||
]
|
||||
ENUM_TRUE = ('true', 't', 'yes', 'y', 'on', '1')
|
||||
ENUM_FALSE = ('false', 'f', 'no', 'n', 'off', '0')
|
||||
@@ -34,6 +34,7 @@ def tojson(datatype, value):
|
||||
|
||||
myspecialtype = object()
|
||||
|
||||
|
||||
@tojson.when_object(myspecialtype)
|
||||
def myspecialtype_tojson(datatype, value):
|
||||
return str(value)
|
||||
@@ -41,7 +42,7 @@ def tojson(datatype, value):
|
||||
if value is None:
|
||||
return None
|
||||
if wsme.types.iscomplex(datatype):
|
||||
d = dict()
|
||||
d = {}
|
||||
for attr in wsme.types.list_attributes(datatype):
|
||||
attr_value = getattr(value, attr.key)
|
||||
if attr_value is not Unset:
|
||||
@@ -70,11 +71,12 @@ def array_tojson(datatype, value):
|
||||
def dict_tojson(datatype, value):
|
||||
if value is None:
|
||||
return None
|
||||
return dict((
|
||||
(tojson(datatype.key_type, item[0]),
|
||||
tojson(datatype.value_type, item[1]))
|
||||
return {
|
||||
tojson(datatype.key_type, item[0]): tojson(
|
||||
datatype.value_type, item[1]
|
||||
)
|
||||
for item in value.items()
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@tojson.when_object(decimal.Decimal)
|
||||
@@ -114,9 +116,11 @@ def fromjson(datatype, value):
|
||||
|
||||
from wsme.protocol.restjson import fromjson
|
||||
|
||||
|
||||
class MySpecialType(object):
|
||||
pass
|
||||
|
||||
|
||||
@fromjson.when_object(MySpecialType)
|
||||
def myspecialtype_fromjson(datatype, value):
|
||||
return MySpecialType(value)
|
||||
@@ -130,30 +134,34 @@ def fromjson(datatype, value):
|
||||
# Here we check that all the attributes in the value are also defined
|
||||
# in our type definition, otherwise we raise an Error.
|
||||
v_keys = set(value.keys())
|
||||
a_keys = set(adef.name for adef in attributes)
|
||||
a_keys = {adef.name for adef in attributes}
|
||||
if not v_keys <= a_keys:
|
||||
raise wsme.exc.UnknownAttribute(None, v_keys - a_keys)
|
||||
|
||||
for attrdef in attributes:
|
||||
if attrdef.name in value:
|
||||
try:
|
||||
val_fromjson = fromjson(attrdef.datatype,
|
||||
value[attrdef.name])
|
||||
val_fromjson = fromjson(
|
||||
attrdef.datatype, value[attrdef.name]
|
||||
)
|
||||
except wsme.exc.UnknownAttribute as e:
|
||||
e.add_fieldname(attrdef.name)
|
||||
raise
|
||||
if getattr(attrdef, 'readonly', False):
|
||||
raise wsme.exc.InvalidInput(attrdef.name, val_fromjson,
|
||||
"Cannot set read only field.")
|
||||
raise wsme.exc.InvalidInput(
|
||||
attrdef.name,
|
||||
val_fromjson,
|
||||
"Cannot set read only field.",
|
||||
)
|
||||
setattr(obj, attrdef.key, val_fromjson)
|
||||
elif attrdef.mandatory:
|
||||
raise wsme.exc.InvalidInput(attrdef.name, None,
|
||||
"Mandatory field missing.")
|
||||
raise wsme.exc.InvalidInput(
|
||||
attrdef.name, None, "Mandatory field missing."
|
||||
)
|
||||
|
||||
return wsme.types.validate_value(datatype, obj)
|
||||
elif wsme.types.isusertype(datatype):
|
||||
value = datatype.frombasetype(
|
||||
fromjson(datatype.basetype, value))
|
||||
value = datatype.frombasetype(fromjson(datatype.basetype, value))
|
||||
return value
|
||||
|
||||
|
||||
@@ -162,7 +170,7 @@ def array_fromjson(datatype, value):
|
||||
if value is None:
|
||||
return None
|
||||
if not isinstance(value, list):
|
||||
raise ValueError("Value not a valid list: %s" % value)
|
||||
raise ValueError(f"Value not a valid list: {value}")
|
||||
return [fromjson(datatype.item_type, item) for item in value]
|
||||
|
||||
|
||||
@@ -171,18 +179,22 @@ def dict_fromjson(datatype, value):
|
||||
if value is None:
|
||||
return None
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("Value not a valid dict: %s" % value)
|
||||
return dict((
|
||||
(fromjson(datatype.key_type, item[0]),
|
||||
fromjson(datatype.value_type, item[1]))
|
||||
for item in value.items()))
|
||||
raise ValueError(f"Value not a valid dict: {value}")
|
||||
return {
|
||||
fromjson(datatype.key_type, item[0]): fromjson(
|
||||
datatype.value_type, item[1]
|
||||
)
|
||||
for item in value.items()
|
||||
}
|
||||
|
||||
|
||||
@fromjson.when_object(bytes)
|
||||
def str_fromjson(datatype, value):
|
||||
if (isinstance(value, str) or
|
||||
isinstance(value, int) or
|
||||
isinstance(value, float)):
|
||||
if (
|
||||
isinstance(value, str)
|
||||
or isinstance(value, int)
|
||||
or isinstance(value, float)
|
||||
):
|
||||
return str(value).encode('utf8')
|
||||
|
||||
|
||||
@@ -206,13 +218,13 @@ def bool_fromjson(datatype, value):
|
||||
"""Convert to bool, restricting strings to just unambiguous values."""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (int, bool,)):
|
||||
if isinstance(value, (int, bool)):
|
||||
return bool(value)
|
||||
if value in ENUM_TRUE:
|
||||
return True
|
||||
if value in ENUM_FALSE:
|
||||
return False
|
||||
raise ValueError("Value not an unambiguous boolean: %s" % value)
|
||||
raise ValueError(f"Value not an unambiguous boolean: {value}")
|
||||
|
||||
|
||||
@fromjson.when_object(decimal.Decimal)
|
||||
@@ -300,8 +312,9 @@ def encode_error(context, errordetail):
|
||||
|
||||
def encode_sample_value(datatype, value, format=False):
|
||||
r = tojson(datatype, value)
|
||||
content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0,
|
||||
sort_keys=format)
|
||||
content = json.dumps(
|
||||
r, ensure_ascii=False, indent=4 if format else 0, sort_keys=format
|
||||
)
|
||||
return ('javascript', content)
|
||||
|
||||
|
||||
@@ -309,13 +322,15 @@ def encode_sample_params(params, format=False):
|
||||
kw = {}
|
||||
for name, datatype, value in params:
|
||||
kw[name] = tojson(datatype, value)
|
||||
content = json.dumps(kw, ensure_ascii=False, indent=4 if format else 0,
|
||||
sort_keys=format)
|
||||
content = json.dumps(
|
||||
kw, ensure_ascii=False, indent=4 if format else 0, sort_keys=format
|
||||
)
|
||||
return ('javascript', content)
|
||||
|
||||
|
||||
def encode_sample_result(datatype, value, format=False):
|
||||
r = tojson(datatype, value)
|
||||
content = json.dumps(r, ensure_ascii=False, indent=4 if format else 0,
|
||||
sort_keys=format)
|
||||
content = json.dumps(
|
||||
r, ensure_ascii=False, indent=4 if format else 0, sort_keys=format
|
||||
)
|
||||
return ('javascript', content)
|
||||
|
||||
+15
-16
@@ -15,10 +15,7 @@ log = logging.getLogger(__name__)
|
||||
class RestProtocol(Protocol):
|
||||
name = 'rest'
|
||||
displayname = 'REST'
|
||||
formatters = {
|
||||
'json': json,
|
||||
'xml': xml,
|
||||
}
|
||||
formatters = {'json': json, 'xml': xml}
|
||||
|
||||
def __init__(self, dataformats=None):
|
||||
if dataformats is None:
|
||||
@@ -75,19 +72,19 @@ class RestProtocol(Protocol):
|
||||
}
|
||||
if not inmime and informat:
|
||||
inmime = informat.content_type
|
||||
log.debug("Inferred input type: %s" % inmime)
|
||||
log.debug(f"Inferred input type: {inmime}")
|
||||
context.inmime = inmime
|
||||
yield context
|
||||
|
||||
def extract_path(self, context):
|
||||
path = context.request.path
|
||||
assert path.startswith(self.root._webpath)
|
||||
path = path[len(self.root._webpath):]
|
||||
path = path[len(self.root._webpath) :]
|
||||
path = path.strip('/').split('/')
|
||||
|
||||
for dataformat in self.dataformats:
|
||||
if path[-1].endswith('.' + dataformat):
|
||||
path[-1] = path[-1][:-len(dataformat) - 1]
|
||||
path[-1] = path[-1][: -len(dataformat) - 1]
|
||||
|
||||
# Check if the path is actually a function, and if not
|
||||
# see if the http method make a difference
|
||||
@@ -101,8 +98,11 @@ class RestProtocol(Protocol):
|
||||
# No function at this path. Now check for function that have
|
||||
# this path as a prefix, and declared an http method
|
||||
for p, fdef in self.root.getapi():
|
||||
if len(p) == len(path) + 1 and p[:len(path)] == path and \
|
||||
fdef.extra_options.get('method') == context.request.method:
|
||||
if (
|
||||
len(p) == len(path) + 1
|
||||
and p[: len(path)] == path
|
||||
and fdef.extra_options.get('method') == context.request.method
|
||||
):
|
||||
return p
|
||||
|
||||
return path
|
||||
@@ -119,21 +119,20 @@ class RestProtocol(Protocol):
|
||||
|
||||
args, kwargs = wsme.rest.args.combine_args(
|
||||
funcdef,
|
||||
(wsme.rest.args.args_from_params(funcdef, request.params),
|
||||
wsme.rest.args.args_from_body(funcdef, body, context.inmime))
|
||||
(
|
||||
wsme.rest.args.args_from_params(funcdef, request.params),
|
||||
wsme.rest.args.args_from_body(funcdef, body, context.inmime),
|
||||
),
|
||||
)
|
||||
wsme.runtime.check_arguments(funcdef, args, kwargs)
|
||||
return kwargs
|
||||
|
||||
def encode_result(self, context, result):
|
||||
out = context.outformat.encode_result(
|
||||
result, context.funcdef.return_type,
|
||||
**context.outformat_options
|
||||
result, context.funcdef.return_type, **context.outformat_options
|
||||
)
|
||||
return out
|
||||
|
||||
def encode_error(self, context, errordetail):
|
||||
out = context.outformat.encode_error(
|
||||
context, errordetail
|
||||
)
|
||||
out = context.outformat.encode_error(context, errordetail)
|
||||
return out
|
||||
|
||||
+24
-20
@@ -10,9 +10,7 @@ from wsme.exc import UnknownArgument, InvalidInput
|
||||
import re
|
||||
|
||||
content_type = 'text/xml'
|
||||
accept_content_types = [
|
||||
content_type,
|
||||
]
|
||||
accept_content_types = [content_type]
|
||||
|
||||
time_re = re.compile(r'(?P<h>[0-2][0-9]):(?P<m>[0-5][0-9]):(?P<s>[0-6][0-9])')
|
||||
|
||||
@@ -42,6 +40,7 @@ def toxml(datatype, key, value):
|
||||
|
||||
myspecialtype = object()
|
||||
|
||||
|
||||
@toxml.when_object(myspecialtype)
|
||||
def myspecialtype_toxml(datatype, key, value):
|
||||
el = et.Element(key)
|
||||
@@ -56,14 +55,12 @@ def toxml(datatype, key, value):
|
||||
el.set('nil', 'true')
|
||||
else:
|
||||
if wsme.types.isusertype(datatype):
|
||||
return toxml(datatype.basetype,
|
||||
key, datatype.tobasetype(value))
|
||||
return toxml(datatype.basetype, key, datatype.tobasetype(value))
|
||||
elif wsme.types.iscomplex(datatype):
|
||||
for attrdef in datatype._wsme_attributes:
|
||||
attrvalue = getattr(value, attrdef.key)
|
||||
if attrvalue is not wsme.types.Unset:
|
||||
el.append(toxml(attrdef.datatype, attrdef.name,
|
||||
attrvalue))
|
||||
el.append(toxml(attrdef.datatype, attrdef.name, attrvalue))
|
||||
else:
|
||||
el.text = str(value)
|
||||
return el
|
||||
@@ -79,9 +76,11 @@ def fromxml(datatype, element):
|
||||
|
||||
from wsme.protocol.restxml import fromxml
|
||||
|
||||
|
||||
class MySpecialType(object):
|
||||
pass
|
||||
|
||||
|
||||
@fromxml.when_object(MySpecialType)
|
||||
def myspecialtype_fromxml(datatype, element):
|
||||
if element.get('nil', False):
|
||||
@@ -99,12 +98,16 @@ def fromxml(datatype, element):
|
||||
if sub is not None:
|
||||
val_fromxml = fromxml(attrdef.datatype, sub)
|
||||
if getattr(attrdef, 'readonly', False):
|
||||
raise InvalidInput(attrdef.name, val_fromxml,
|
||||
"Cannot set read only field.")
|
||||
raise InvalidInput(
|
||||
attrdef.name,
|
||||
val_fromxml,
|
||||
"Cannot set read only field.",
|
||||
)
|
||||
setattr(obj, attrdef.key, val_fromxml)
|
||||
elif attrdef.mandatory:
|
||||
raise InvalidInput(attrdef.name, None,
|
||||
"Mandatory field missing.")
|
||||
raise InvalidInput(
|
||||
attrdef.name, None, "Mandatory field missing."
|
||||
)
|
||||
return wsme.types.validate_value(datatype, obj)
|
||||
if datatype is wsme.types.bytes:
|
||||
return element.text.encode('ascii')
|
||||
@@ -183,8 +186,7 @@ def array_fromxml(datatype, element):
|
||||
if element.get('nil') == 'true':
|
||||
return None
|
||||
return [
|
||||
fromxml(datatype.item_type, item)
|
||||
for item in element.findall('item')
|
||||
fromxml(datatype.item_type, item) for item in element.findall('item')
|
||||
]
|
||||
|
||||
|
||||
@@ -199,10 +201,12 @@ def bool_fromxml(datatype, element):
|
||||
def dict_fromxml(datatype, element):
|
||||
if element.get('nil') == 'true':
|
||||
return None
|
||||
return dict((
|
||||
(fromxml(datatype.key_type, item.find('key')),
|
||||
fromxml(datatype.value_type, item.find('value')))
|
||||
for item in element.findall('item')))
|
||||
return {
|
||||
fromxml(datatype.key_type, item.find('key')): fromxml(
|
||||
datatype.value_type, item.find('value')
|
||||
)
|
||||
for item in element.findall('item')
|
||||
}
|
||||
|
||||
|
||||
@fromxml.when_object(wsme.types.text)
|
||||
@@ -254,9 +258,9 @@ def parse(s, datatypes, bodyarg):
|
||||
|
||||
|
||||
def encode_result(value, datatype, **options):
|
||||
return et.tostring(toxml(
|
||||
datatype, options.get('nested_result_attrname', 'result'), value
|
||||
))
|
||||
return et.tostring(
|
||||
toxml(datatype, options.get('nested_result_attrname', 'result'), value)
|
||||
)
|
||||
|
||||
|
||||
def encode_error(context, errordetail):
|
||||
|
||||
+55
-38
@@ -51,7 +51,7 @@ class DummyTransaction:
|
||||
pass
|
||||
|
||||
|
||||
class WSRoot(object):
|
||||
class WSRoot:
|
||||
"""
|
||||
Root controller for webservices.
|
||||
|
||||
@@ -72,10 +72,12 @@ class WSRoot(object):
|
||||
module will be imported and used.
|
||||
|
||||
"""
|
||||
|
||||
__registry__ = wsme.types.registry
|
||||
|
||||
def __init__(self, protocols=[], webpath='', transaction=None,
|
||||
scan_api=scan_api):
|
||||
def __init__(
|
||||
self, protocols=[], webpath='', transaction=None, scan_api=scan_api
|
||||
):
|
||||
self._debug = True
|
||||
self._webpath = webpath
|
||||
self.protocols = []
|
||||
@@ -84,6 +86,7 @@ class WSRoot(object):
|
||||
self._transaction = transaction
|
||||
if self._transaction is True:
|
||||
import transaction
|
||||
|
||||
self._transaction = transaction
|
||||
|
||||
for protocol in protocols:
|
||||
@@ -94,6 +97,7 @@ class WSRoot(object):
|
||||
def wsgiapp(self):
|
||||
"""Returns a wsgi application"""
|
||||
from webob.dec import wsgify
|
||||
|
||||
return wsgify(self._handle_request)
|
||||
|
||||
def begin(self):
|
||||
@@ -127,10 +131,7 @@ class WSRoot(object):
|
||||
]
|
||||
for path, f, fdef, args in self._api:
|
||||
fdef.resolve_types(self.__registry__)
|
||||
return [
|
||||
(path, fdef)
|
||||
for path, f, fdef, args in self._api
|
||||
]
|
||||
return [(path, fdef) for path, f, fdef, args in self._api]
|
||||
|
||||
def _get_protocol(self, name):
|
||||
for protocol in self.protocols:
|
||||
@@ -138,21 +139,26 @@ class WSRoot(object):
|
||||
return protocol
|
||||
|
||||
def _select_protocol(self, request):
|
||||
log.debug("Selecting a protocol for the following request :\n"
|
||||
"headers: %s\nbody: %s", request.headers.items(),
|
||||
request.content_length and (
|
||||
request.content_length > 512 and
|
||||
request.body[:512] or
|
||||
request.body) or '')
|
||||
log.debug(
|
||||
"Selecting a protocol for the following request :\n"
|
||||
"headers: %s\nbody: %s",
|
||||
request.headers.items(),
|
||||
request.content_length
|
||||
and (
|
||||
request.content_length > 512
|
||||
and request.body[:512]
|
||||
or request.body
|
||||
)
|
||||
or '',
|
||||
)
|
||||
protocol = None
|
||||
error = ClientSideError(status_code=406)
|
||||
path = str(request.path)
|
||||
assert path.startswith(self._webpath)
|
||||
path = path[len(self._webpath) + 1:]
|
||||
path = path[len(self._webpath) + 1 :]
|
||||
if 'wsmeproto' in request.params:
|
||||
return self._get_protocol(request.params['wsmeproto'])
|
||||
else:
|
||||
|
||||
for p in self.protocols:
|
||||
try:
|
||||
if p.accept(request):
|
||||
@@ -175,11 +181,13 @@ class WSRoot(object):
|
||||
|
||||
if context.path is None:
|
||||
raise ClientSideError(
|
||||
'The %s protocol was unable to extract a function '
|
||||
'path from the request' % protocol.name)
|
||||
f'The {protocol.name} protocol was unable to extract a function '
|
||||
'path from the request'
|
||||
)
|
||||
|
||||
context.func, context.funcdef, args = \
|
||||
self._lookup_function(context.path)
|
||||
context.func, context.funcdef, args = self._lookup_function(
|
||||
context.path
|
||||
)
|
||||
kw = protocol.read_arguments(context)
|
||||
args = list(args)
|
||||
|
||||
@@ -217,7 +225,7 @@ class WSRoot(object):
|
||||
|
||||
path = request.path
|
||||
if path.startswith(self._webpath):
|
||||
path = path[len(self._webpath):]
|
||||
path = path[len(self._webpath) :]
|
||||
routepath, func = self.find_route(path)
|
||||
if routepath:
|
||||
content = func()
|
||||
@@ -237,16 +245,19 @@ class WSRoot(object):
|
||||
msg = e.faultstring
|
||||
protocol = None
|
||||
except Exception as e:
|
||||
msg = ("Unexpected error while selecting protocol: %s" % str(e))
|
||||
msg = f"Unexpected error while selecting protocol: {str(e)}"
|
||||
log.exception(msg)
|
||||
protocol = None
|
||||
error_status = 500
|
||||
|
||||
if protocol is None:
|
||||
if not msg:
|
||||
msg = ("None of the following protocols can handle this "
|
||||
"request : %s" % ','.join((
|
||||
p.name for p in self.protocols)))
|
||||
msg = (
|
||||
"None of the following protocols can handle this "
|
||||
"request : {}".format(
|
||||
','.join(p.name for p in self.protocols)
|
||||
)
|
||||
)
|
||||
res.status = error_status
|
||||
res.content_type = 'text/plain'
|
||||
try:
|
||||
@@ -262,7 +273,6 @@ class WSRoot(object):
|
||||
request.server_errorcount = 0
|
||||
|
||||
try:
|
||||
|
||||
context = None
|
||||
|
||||
if hasattr(protocol, 'prepare_response_body'):
|
||||
@@ -270,9 +280,13 @@ class WSRoot(object):
|
||||
else:
|
||||
prepare_response_body = default_prepare_response_body
|
||||
|
||||
body = prepare_response_body(request, (
|
||||
self._do_call(protocol, context)
|
||||
for context in protocol.iter_calls(request)))
|
||||
body = prepare_response_body(
|
||||
request,
|
||||
(
|
||||
self._do_call(protocol, context)
|
||||
for context in protocol.iter_calls(request)
|
||||
),
|
||||
)
|
||||
|
||||
if isinstance(body, str):
|
||||
res.text = body
|
||||
@@ -323,7 +337,7 @@ class WSRoot(object):
|
||||
|
||||
# TODO should we consider the encoding asked by
|
||||
# the web browser ?
|
||||
res.headers['Content-Type'] = "%s; charset=UTF-8" % res_content_type
|
||||
res.headers['Content-Type'] = f"{res_content_type}; charset=UTF-8"
|
||||
|
||||
return res
|
||||
|
||||
@@ -353,15 +367,18 @@ class WSRoot(object):
|
||||
if lexer is None:
|
||||
raise ValueError("No lexer found")
|
||||
formatter = HtmlFormatter()
|
||||
return html_body % dict(
|
||||
css=formatter.get_style_defs(),
|
||||
content=highlight(content, lexer, formatter).encode('utf8'))
|
||||
return html_body % {
|
||||
'css': formatter.get_style_defs(),
|
||||
'content': highlight(content, lexer, formatter).encode('utf8'),
|
||||
}
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
"Could not pygment the content because of the following "
|
||||
"error :\n%s" % e)
|
||||
return html_body % dict(
|
||||
css='',
|
||||
content='<pre>%s</pre>' %
|
||||
content.replace(b'>', b'>')
|
||||
.replace(b'<', b'<'))
|
||||
f"error :\n{e}"
|
||||
)
|
||||
return html_body % {
|
||||
'css': '',
|
||||
'content': '<pre>{}</pre>'.format(
|
||||
content.replace(b'>', b'>').replace(b'<', b'<')
|
||||
),
|
||||
}
|
||||
|
||||
+121
-98
@@ -1,5 +1,3 @@
|
||||
# coding=utf-8
|
||||
|
||||
import datetime
|
||||
import decimal
|
||||
import unittest
|
||||
@@ -29,22 +27,20 @@ class CallException(RuntimeError):
|
||||
self.debuginfo = debuginfo
|
||||
|
||||
def __str__(self):
|
||||
return 'faultcode=%s, faultstring=%s, debuginfo=%s' % (
|
||||
self.faultcode, self.faultstring, self.debuginfo
|
||||
)
|
||||
return f'faultcode={self.faultcode}, faultstring={self.faultstring}, debuginfo={self.debuginfo}'
|
||||
|
||||
|
||||
myenumtype = wsme.types.Enum(wsme.types.bytes, 'v1', 'v2')
|
||||
|
||||
|
||||
class NestedInner(object):
|
||||
class NestedInner:
|
||||
aint = int
|
||||
|
||||
def __init__(self, aint=None):
|
||||
self.aint = aint
|
||||
|
||||
|
||||
class NestedOuter(object):
|
||||
class NestedOuter:
|
||||
inner = NestedInner
|
||||
inner_array = wsme.types.wsattr([NestedInner])
|
||||
inner_dict = {wsme.types.text: NestedInner}
|
||||
@@ -53,7 +49,7 @@ class NestedOuter(object):
|
||||
self.inner = NestedInner(0)
|
||||
|
||||
|
||||
class NamedAttrsObject(object):
|
||||
class NamedAttrsObject:
|
||||
def __init__(self, v1=Unset, v2=Unset):
|
||||
self.attr_1 = v1
|
||||
self.attr_2 = v2
|
||||
@@ -62,7 +58,7 @@ class NamedAttrsObject(object):
|
||||
attr_2 = wsme.types.wsattr(int, name='attr.2')
|
||||
|
||||
|
||||
class CustomObject(object):
|
||||
class CustomObject:
|
||||
aint = int
|
||||
name = wsme.types.text
|
||||
|
||||
@@ -72,17 +68,17 @@ class ExtendedInt(wsme.types.UserType):
|
||||
name = "Extended integer"
|
||||
|
||||
|
||||
class NestedInnerApi(object):
|
||||
class NestedInnerApi:
|
||||
@expose(bool)
|
||||
def deepfunction(self):
|
||||
return True
|
||||
|
||||
|
||||
class NestedOuterApi(object):
|
||||
class NestedOuterApi:
|
||||
inner = NestedInnerApi()
|
||||
|
||||
|
||||
class ReturnTypes(object):
|
||||
class ReturnTypes:
|
||||
@expose(wsme.types.bytes)
|
||||
def getbytes(self):
|
||||
return b"astring"
|
||||
@@ -153,10 +149,7 @@ class ReturnTypes(object):
|
||||
@expose(NestedOuter)
|
||||
def getobjectdictattribute(self):
|
||||
obj = NestedOuter()
|
||||
obj.inner_dict = {
|
||||
'12': NestedInner(12),
|
||||
'13': NestedInner(13)
|
||||
}
|
||||
obj.inner_dict = {'12': NestedInner(12), '13': NestedInner(13)}
|
||||
return obj
|
||||
|
||||
@expose(myenumtype)
|
||||
@@ -168,14 +161,15 @@ class ReturnTypes(object):
|
||||
return NamedAttrsObject(5, 6)
|
||||
|
||||
|
||||
class ArgTypes(object):
|
||||
class ArgTypes:
|
||||
def assertEqual(self, a, b):
|
||||
if not (a == b):
|
||||
raise AssertionError('%s != %s' % (a, b))
|
||||
raise AssertionError(f'{a} != {b}')
|
||||
|
||||
def assertIsInstance(self, value, v_type):
|
||||
assert isinstance(value, v_type), ("%s is not instance of type %s" %
|
||||
(value, v_type))
|
||||
assert isinstance(value, v_type), (
|
||||
f"{value} is not instance of type {v_type}"
|
||||
)
|
||||
|
||||
@expose(wsme.types.bytes)
|
||||
@validate(wsme.types.bytes)
|
||||
@@ -333,10 +327,10 @@ class ArgTypes(object):
|
||||
return value
|
||||
|
||||
|
||||
class BodyTypes(object):
|
||||
class BodyTypes:
|
||||
def assertEqual(self, a, b):
|
||||
if not (a == b):
|
||||
raise AssertionError('%s != %s' % (a, b))
|
||||
raise AssertionError(f'{a} != {b}')
|
||||
|
||||
@expose(int, body={wsme.types.text: int})
|
||||
@validate(int)
|
||||
@@ -357,13 +351,13 @@ class BodyTypes(object):
|
||||
return body[0]
|
||||
|
||||
|
||||
class WithErrors(object):
|
||||
class WithErrors:
|
||||
@expose()
|
||||
def divide_by_zero(self):
|
||||
1 / 0
|
||||
|
||||
|
||||
class MiscFunctions(object):
|
||||
class MiscFunctions:
|
||||
@expose(int)
|
||||
@validate(int, int)
|
||||
def multiply(self, a, b):
|
||||
@@ -429,8 +423,10 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
assert "No error raised"
|
||||
except CallException as e:
|
||||
self.assertEqual(e.faultcode, 'Client')
|
||||
self.assertEqual(e.faultstring.lower(),
|
||||
'unknown function name: invalid_function')
|
||||
self.assertEqual(
|
||||
e.faultstring.lower(),
|
||||
'unknown function name: invalid_function',
|
||||
)
|
||||
|
||||
def test_serverside_error(self):
|
||||
try:
|
||||
@@ -514,29 +510,32 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
self.assertEqual(r, [{'inner': {'aint': 0}}, {'inner': {'aint': 0}}])
|
||||
|
||||
def test_return_nesteddict(self):
|
||||
r = self.call('returntypes/getnesteddict',
|
||||
_rt={wsme.types.bytes: NestedOuter})
|
||||
self.assertEqual(r, {
|
||||
b'a': {'inner': {'aint': 0}},
|
||||
b'b': {'inner': {'aint': 0}}
|
||||
})
|
||||
r = self.call(
|
||||
'returntypes/getnesteddict', _rt={wsme.types.bytes: NestedOuter}
|
||||
)
|
||||
self.assertEqual(
|
||||
r, {b'a': {'inner': {'aint': 0}}, b'b': {'inner': {'aint': 0}}}
|
||||
)
|
||||
|
||||
def test_return_objectarrayattribute(self):
|
||||
r = self.call('returntypes/getobjectarrayattribute', _rt=NestedOuter)
|
||||
self.assertEqual(r, {
|
||||
'inner': {'aint': 0},
|
||||
'inner_array': [{'aint': 12}, {'aint': 13}]
|
||||
})
|
||||
self.assertEqual(
|
||||
r,
|
||||
{
|
||||
'inner': {'aint': 0},
|
||||
'inner_array': [{'aint': 12}, {'aint': 13}],
|
||||
},
|
||||
)
|
||||
|
||||
def test_return_objectdictattribute(self):
|
||||
r = self.call('returntypes/getobjectdictattribute', _rt=NestedOuter)
|
||||
self.assertEqual(r, {
|
||||
'inner': {'aint': 0},
|
||||
'inner_dict': {
|
||||
'12': {'aint': 12},
|
||||
'13': {'aint': 13}
|
||||
}
|
||||
})
|
||||
self.assertEqual(
|
||||
r,
|
||||
{
|
||||
'inner': {'aint': 0},
|
||||
'inner_dict': {'12': {'aint': 12}, '13': {'aint': 13}},
|
||||
},
|
||||
)
|
||||
|
||||
def test_return_enum(self):
|
||||
r = self.call('returntypes/getenum', _rt=myenumtype)
|
||||
@@ -547,21 +546,30 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
self.assertEqual(r, {'attr.1': 5, 'attr.2': 6})
|
||||
|
||||
def test_setbytes(self):
|
||||
assert self.call('argtypes/setbytes', value=b'astring',
|
||||
_rt=wsme.types.bytes) == b'astring'
|
||||
assert (
|
||||
self.call(
|
||||
'argtypes/setbytes', value=b'astring', _rt=wsme.types.bytes
|
||||
)
|
||||
== b'astring'
|
||||
)
|
||||
|
||||
def test_settext(self):
|
||||
assert self.call('argtypes/settext', value='\xe3\x81\xae',
|
||||
_rt=wsme.types.text) == '\xe3\x81\xae'
|
||||
assert (
|
||||
self.call(
|
||||
'argtypes/settext', value='\xe3\x81\xae', _rt=wsme.types.text
|
||||
)
|
||||
== '\xe3\x81\xae'
|
||||
)
|
||||
|
||||
def test_settext_empty(self):
|
||||
assert self.call('argtypes/settext', value='',
|
||||
_rt=wsme.types.text) == ''
|
||||
assert (
|
||||
self.call('argtypes/settext', value='', _rt=wsme.types.text) == ''
|
||||
)
|
||||
|
||||
def test_settext_none(self):
|
||||
self.assertEqual(
|
||||
None,
|
||||
self.call('argtypes/settextnone', value=None, _rt=wsme.types.text)
|
||||
self.call('argtypes/settextnone', value=None, _rt=wsme.types.text),
|
||||
)
|
||||
|
||||
def test_setint(self):
|
||||
@@ -569,8 +577,7 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
self.assertEqual(r, 3)
|
||||
|
||||
def test_setfloat(self):
|
||||
assert self.call('argtypes/setfloat', value=3.54,
|
||||
_rt=float) == 3.54
|
||||
assert self.call('argtypes/setfloat', value=3.54, _rt=float) == 3.54
|
||||
|
||||
def test_setbool_true(self):
|
||||
r = self.call('argtypes/setbool', value=True, _rt=bool)
|
||||
@@ -582,61 +589,70 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
|
||||
def test_setdecimal(self):
|
||||
value = decimal.Decimal('3.14')
|
||||
assert self.call('argtypes/setdecimal', value=value,
|
||||
_rt=decimal.Decimal) == value
|
||||
assert (
|
||||
self.call('argtypes/setdecimal', value=value, _rt=decimal.Decimal)
|
||||
== value
|
||||
)
|
||||
|
||||
def test_setdate(self):
|
||||
value = datetime.date(2008, 4, 6)
|
||||
r = self.call('argtypes/setdate', value=value,
|
||||
_rt=datetime.date)
|
||||
r = self.call('argtypes/setdate', value=value, _rt=datetime.date)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_settime(self):
|
||||
value = datetime.time(12, 12, 15)
|
||||
r = self.call('argtypes/settime', value=value,
|
||||
_rt=datetime.time)
|
||||
r = self.call('argtypes/settime', value=value, _rt=datetime.time)
|
||||
self.assertEqual(r, datetime.time(12, 12, 15))
|
||||
|
||||
def test_setdatetime(self):
|
||||
value = datetime.datetime(2008, 4, 6, 12, 12, 15)
|
||||
r = self.call('argtypes/setdatetime', value=value,
|
||||
_rt=datetime.datetime)
|
||||
r = self.call(
|
||||
'argtypes/setdatetime', value=value, _rt=datetime.datetime
|
||||
)
|
||||
self.assertEqual(r, datetime.datetime(2008, 4, 6, 12, 12, 15))
|
||||
|
||||
def test_setbinary(self):
|
||||
value = binarysample
|
||||
r = self.call('argtypes/setbinary', value=(value, wsme.types.binary),
|
||||
_rt=wsme.types.binary) == value
|
||||
r = (
|
||||
self.call(
|
||||
'argtypes/setbinary',
|
||||
value=(value, wsme.types.binary),
|
||||
_rt=wsme.types.binary,
|
||||
)
|
||||
== value
|
||||
)
|
||||
print(r)
|
||||
|
||||
def test_setnested(self):
|
||||
value = {'inner': {'aint': 54}}
|
||||
r = self.call('argtypes/setnested',
|
||||
value=(value, NestedOuter),
|
||||
_rt=NestedOuter)
|
||||
r = self.call(
|
||||
'argtypes/setnested', value=(value, NestedOuter), _rt=NestedOuter
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setnested_nullobj(self):
|
||||
value = {'inner': None}
|
||||
r = self.call(
|
||||
'argtypes/setnested',
|
||||
value=(value, NestedOuter),
|
||||
_rt=NestedOuter
|
||||
'argtypes/setnested', value=(value, NestedOuter), _rt=NestedOuter
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setbytesarray(self):
|
||||
value = [b"1", b"2", b"three"]
|
||||
r = self.call('argtypes/setbytesarray',
|
||||
value=(value, [wsme.types.bytes]),
|
||||
_rt=[wsme.types.bytes])
|
||||
r = self.call(
|
||||
'argtypes/setbytesarray',
|
||||
value=(value, [wsme.types.bytes]),
|
||||
_rt=[wsme.types.bytes],
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_settextarray(self):
|
||||
value = [u"1"]
|
||||
r = self.call('argtypes/settextarray',
|
||||
value=(value, [wsme.types.text]),
|
||||
_rt=[wsme.types.text])
|
||||
value = ["1"]
|
||||
r = self.call(
|
||||
'argtypes/settextarray',
|
||||
value=(value, [wsme.types.text]),
|
||||
_rt=[wsme.types.text],
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setdatetimearray(self):
|
||||
@@ -644,19 +660,20 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
datetime.datetime(2008, 3, 6, 12, 12, 15),
|
||||
datetime.datetime(2008, 4, 6, 2, 12, 15),
|
||||
]
|
||||
r = self.call('argtypes/setdatetimearray',
|
||||
value=(value, [datetime.datetime]),
|
||||
_rt=[datetime.datetime])
|
||||
r = self.call(
|
||||
'argtypes/setdatetimearray',
|
||||
value=(value, [datetime.datetime]),
|
||||
_rt=[datetime.datetime],
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setnestedarray(self):
|
||||
value = [
|
||||
{'inner': {'aint': 54}},
|
||||
{'inner': {'aint': 55}},
|
||||
]
|
||||
r = self.call('argtypes/setnestedarray',
|
||||
value=(value, [NestedOuter]),
|
||||
_rt=[NestedOuter])
|
||||
value = [{'inner': {'aint': 54}}, {'inner': {'aint': 55}}]
|
||||
r = self.call(
|
||||
'argtypes/setnestedarray',
|
||||
value=(value, [NestedOuter]),
|
||||
_rt=[NestedOuter],
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setnesteddict(self):
|
||||
@@ -664,23 +681,26 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
b'o1': {'inner': {'aint': 54}},
|
||||
b'o2': {'inner': {'aint': 55}},
|
||||
}
|
||||
r = self.call('argtypes/setnesteddict',
|
||||
value=(value, {bytes: NestedOuter}),
|
||||
_rt={bytes: NestedOuter})
|
||||
r = self.call(
|
||||
'argtypes/setnesteddict',
|
||||
value=(value, {bytes: NestedOuter}),
|
||||
_rt={bytes: NestedOuter},
|
||||
)
|
||||
print(r)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setenum(self):
|
||||
value = b'v1'
|
||||
r = self.call('argtypes/setenum', value=value,
|
||||
_rt=myenumtype)
|
||||
r = self.call('argtypes/setenum', value=value, _rt=myenumtype)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_setnamedattrsobj(self):
|
||||
value = {'attr.1': 10, 'attr.2': 20}
|
||||
r = self.call('argtypes/setnamedattrsobj',
|
||||
value=(value, NamedAttrsObject),
|
||||
_rt=NamedAttrsObject)
|
||||
r = self.call(
|
||||
'argtypes/setnamedattrsobj',
|
||||
value=(value, NamedAttrsObject),
|
||||
_rt=NamedAttrsObject,
|
||||
)
|
||||
self.assertEqual(r, value)
|
||||
|
||||
def test_nested_api(self):
|
||||
@@ -700,8 +720,9 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
self.assertEqual(self.call('misc/multiply', a=5, b=2, _rt=int), 10)
|
||||
|
||||
def test_html_format(self):
|
||||
res = self.call('argtypes/setdatetime', _accept="text/html",
|
||||
_no_result_decode=True)
|
||||
res = self.call(
|
||||
'argtypes/setdatetime', _accept="text/html", _no_result_decode=True
|
||||
)
|
||||
self.assertEqual(res.content_type, 'text/html')
|
||||
|
||||
|
||||
@@ -711,7 +732,9 @@ class RestOnlyProtocolTestCase(ProtocolTestCase):
|
||||
self.assertEqual(r, 10)
|
||||
|
||||
def test_body_dict(self):
|
||||
r = self.call('bodytypes/setdict',
|
||||
body=({'test': 10}, {wsme.types.text: int}),
|
||||
_rt=int)
|
||||
r = self.call(
|
||||
'bodytypes/setdict',
|
||||
body=({'test': 10}, {wsme.types.text: int}),
|
||||
_rt=int,
|
||||
)
|
||||
self.assertEqual(r, 10)
|
||||
|
||||
+103
-68
@@ -1,5 +1,3 @@
|
||||
# encoding=utf8
|
||||
|
||||
import unittest
|
||||
import webtest
|
||||
|
||||
@@ -20,13 +18,13 @@ class TestController(unittest.TestCase):
|
||||
def getint(self):
|
||||
return 1
|
||||
|
||||
assert MyWS.getint._wsme_definition.return_type == int
|
||||
assert MyWS.getint._wsme_definition.return_type is int
|
||||
|
||||
def test_validate(self):
|
||||
class ComplexType(object):
|
||||
class ComplexType:
|
||||
attr = int
|
||||
|
||||
class MyWS(object):
|
||||
class MyWS:
|
||||
@expose(int)
|
||||
@validate(int, int, int)
|
||||
def add(self, a, b, c=0):
|
||||
@@ -42,24 +40,24 @@ class TestController(unittest.TestCase):
|
||||
args = MyWS.add._wsme_definition.arguments
|
||||
|
||||
assert args[0].name == 'a'
|
||||
assert args[0].datatype == int
|
||||
assert args[0].datatype is int
|
||||
assert args[0].mandatory
|
||||
assert args[0].default is None
|
||||
|
||||
assert args[1].name == 'b'
|
||||
assert args[1].datatype == int
|
||||
assert args[1].datatype is int
|
||||
assert args[1].mandatory
|
||||
assert args[1].default is None
|
||||
|
||||
assert args[2].name == 'c'
|
||||
assert args[2].datatype == int
|
||||
assert args[2].datatype is int
|
||||
assert not args[2].mandatory
|
||||
assert args[2].default == 0
|
||||
|
||||
assert types.iscomplex(ComplexType)
|
||||
|
||||
def test_validate_enum_with_none(self):
|
||||
class Version(object):
|
||||
class Version:
|
||||
number = types.Enum(str, 'v1', 'v2', None)
|
||||
|
||||
class MyWS(WSRoot):
|
||||
@@ -70,20 +68,25 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyWS(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/setcplx', params={'version': {'number': 'arf'}},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
res = app.post_json(
|
||||
'/setcplx',
|
||||
params={'version': {'number': 'arf'}},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertTrue(
|
||||
res.json_body['faultstring'].startswith(
|
||||
"Invalid input for field/attribute number. Value: 'arf'. "
|
||||
"Value should be one of:"))
|
||||
"Value should be one of:"
|
||||
)
|
||||
)
|
||||
self.assertIn('v1', res.json_body['faultstring'])
|
||||
self.assertIn('v2', res.json_body['faultstring'])
|
||||
self.assertIn('None', res.json_body['faultstring'])
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_validate_enum_with_wrong_type(self):
|
||||
class Version(object):
|
||||
class Version:
|
||||
number = types.Enum(str, 'v1', 'v2', None)
|
||||
|
||||
class MyWS(WSRoot):
|
||||
@@ -94,20 +97,25 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyWS(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/setcplx', params={'version': {'number': 1}},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
res = app.post_json(
|
||||
'/setcplx',
|
||||
params={'version': {'number': 1}},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertTrue(
|
||||
res.json_body['faultstring'].startswith(
|
||||
"Invalid input for field/attribute number. Value: '1'. "
|
||||
"Value should be one of:"))
|
||||
"Value should be one of:"
|
||||
)
|
||||
)
|
||||
self.assertIn('v1', res.json_body['faultstring'])
|
||||
self.assertIn('v2', res.json_body['faultstring'])
|
||||
self.assertIn('None', res.json_body['faultstring'])
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_scan_api(self):
|
||||
class NS(object):
|
||||
class NS:
|
||||
@expose(int)
|
||||
@validate(int, int)
|
||||
def multiply(self, a, b):
|
||||
@@ -127,7 +135,7 @@ class TestController(unittest.TestCase):
|
||||
|
||||
def test_scan_subclass(self):
|
||||
class MyRoot(WSRoot):
|
||||
class SubClass(object):
|
||||
class SubClass:
|
||||
pass
|
||||
|
||||
r = MyRoot()
|
||||
@@ -136,7 +144,7 @@ class TestController(unittest.TestCase):
|
||||
assert len(api) == 0
|
||||
|
||||
def test_scan_api_too_deep(self):
|
||||
class Loop(object):
|
||||
class Loop:
|
||||
pass
|
||||
|
||||
ell = Loop()
|
||||
@@ -199,8 +207,12 @@ class TestController(unittest.TestCase):
|
||||
print(res.status_int)
|
||||
assert res.status_int == 406
|
||||
print(res.body)
|
||||
assert res.body.find(
|
||||
b"None of the following protocols can handle this request") != -1
|
||||
assert (
|
||||
res.body.find(
|
||||
b"None of the following protocols can handle this request"
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
|
||||
def test_return_content_type_guess(self):
|
||||
class DummierProto(DummyProtocol):
|
||||
@@ -210,23 +222,27 @@ class TestController(unittest.TestCase):
|
||||
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
|
||||
res = app.get('/', expect_errors=True, headers={
|
||||
'Accept': 'text/xml,q=0.8'})
|
||||
res = app.get(
|
||||
'/', expect_errors=True, headers={'Accept': 'text/xml,q=0.8'}
|
||||
)
|
||||
assert res.status_int == 400
|
||||
assert res.content_type == 'text/xml', res.content_type
|
||||
|
||||
res = app.get('/', expect_errors=True, headers={
|
||||
'Accept': 'text/plain'})
|
||||
res = app.get(
|
||||
'/', expect_errors=True, headers={'Accept': 'text/plain'}
|
||||
)
|
||||
assert res.status_int == 400
|
||||
assert res.content_type == 'text/plain', res.content_type
|
||||
|
||||
def test_double_expose(self):
|
||||
try:
|
||||
|
||||
class MyRoot(WSRoot):
|
||||
@expose()
|
||||
@expose()
|
||||
def atest(self):
|
||||
pass
|
||||
|
||||
assert False, "A ValueError should have been raised"
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -238,38 +254,36 @@ class TestController(unittest.TestCase):
|
||||
|
||||
mul_int = expose(int, int, int, wrap=True)(multiply)
|
||||
|
||||
mul_float = expose(
|
||||
float, float, float,
|
||||
wrap=True)(multiply)
|
||||
mul_float = expose(float, float, float, wrap=True)(multiply)
|
||||
|
||||
mul_string = expose(
|
||||
wsme.types.text, wsme.types.text, int,
|
||||
wrap=True)(multiply)
|
||||
wsme.types.text, wsme.types.text, int, wrap=True
|
||||
)(multiply)
|
||||
|
||||
r = MyRoot(['restjson'])
|
||||
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
|
||||
res = app.get('/mul_int?a=2&b=5', headers={
|
||||
'Accept': 'application/json'
|
||||
})
|
||||
res = app.get(
|
||||
'/mul_int?a=2&b=5', headers={'Accept': 'application/json'}
|
||||
)
|
||||
|
||||
self.assertEqual(res.body, b'10')
|
||||
|
||||
res = app.get('/mul_float?a=1.2&b=2.9', headers={
|
||||
'Accept': 'application/json'
|
||||
})
|
||||
res = app.get(
|
||||
'/mul_float?a=1.2&b=2.9', headers={'Accept': 'application/json'}
|
||||
)
|
||||
|
||||
self.assertEqual(res.body, b'3.48')
|
||||
|
||||
res = app.get('/mul_string?a=hello&b=2', headers={
|
||||
'Accept': 'application/json'
|
||||
})
|
||||
res = app.get(
|
||||
'/mul_string?a=hello&b=2', headers={'Accept': 'application/json'}
|
||||
)
|
||||
|
||||
self.assertEqual(res.body, b'"hellohello"')
|
||||
|
||||
def test_wsattr_mandatory(self):
|
||||
class ComplexType(object):
|
||||
class ComplexType:
|
||||
attr = wsme.types.wsattr(int, mandatory=True)
|
||||
|
||||
class MyRoot(WSRoot):
|
||||
@@ -280,12 +294,16 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyRoot(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/clx', params={}, expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
res = app.post_json(
|
||||
'/clx',
|
||||
params={},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_wsattr_readonly(self):
|
||||
class ComplexType(object):
|
||||
class ComplexType:
|
||||
attr = wsme.types.wsattr(int, readonly=True)
|
||||
|
||||
class MyRoot(WSRoot):
|
||||
@@ -296,17 +314,23 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyRoot(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/clx', params={'attr': 1005}, expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
self.assertIn('Cannot set read only field.',
|
||||
res.json_body['faultstring'])
|
||||
res = app.post_json(
|
||||
'/clx',
|
||||
params={'attr': 1005},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertIn(
|
||||
'Cannot set read only field.', res.json_body['faultstring']
|
||||
)
|
||||
self.assertIn('1005', res.json_body['faultstring'])
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_wsattr_default(self):
|
||||
class ComplexType(object):
|
||||
attr = wsme.types.wsattr(wsme.types.Enum(str, 'or', 'and'),
|
||||
default='and')
|
||||
class ComplexType:
|
||||
attr = wsme.types.wsattr(
|
||||
wsme.types.Enum(str, 'or', 'and'), default='and'
|
||||
)
|
||||
|
||||
class MyRoot(WSRoot):
|
||||
@expose(int)
|
||||
@@ -316,12 +340,16 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyRoot(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/clx', params={}, expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
res = app.post_json(
|
||||
'/clx',
|
||||
params={},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_wsproperty_mandatory(self):
|
||||
class ComplexType(object):
|
||||
class ComplexType:
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
@@ -335,14 +363,19 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyRoot(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/clx', params={}, expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
res = app.post_json(
|
||||
'/clx',
|
||||
params={},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_validate_enum_mandatory(self):
|
||||
class Version(object):
|
||||
number = wsme.types.wsattr(wsme.types.Enum(str, 'v1', 'v2'),
|
||||
mandatory=True)
|
||||
class Version:
|
||||
number = wsme.types.wsattr(
|
||||
wsme.types.Enum(str, 'v1', 'v2'), mandatory=True
|
||||
)
|
||||
|
||||
class MyWS(WSRoot):
|
||||
@expose(str)
|
||||
@@ -352,14 +385,16 @@ class TestController(unittest.TestCase):
|
||||
|
||||
r = MyWS(['restjson'])
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
res = app.post_json('/setcplx', params={'version': {}},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'})
|
||||
res = app.post_json(
|
||||
'/setcplx',
|
||||
params={'version': {}},
|
||||
expect_errors=True,
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
|
||||
class TestFunctionDefinition(unittest.TestCase):
|
||||
|
||||
def test_get_arg(self):
|
||||
def myfunc(self):
|
||||
pass
|
||||
@@ -372,7 +407,6 @@ class TestFunctionDefinition(unittest.TestCase):
|
||||
|
||||
|
||||
class TestFormatException(unittest.TestCase):
|
||||
|
||||
def _test_format_exception(self, exception, debug=False):
|
||||
fake_exc_info = (None, exception, None)
|
||||
return wsme_api.format_exception(fake_exc_info, debug=debug)
|
||||
@@ -385,7 +419,7 @@ class TestFormatException(unittest.TestCase):
|
||||
self.assertEqual(faultstring, ret['faultstring'])
|
||||
|
||||
def test_format_client_exception_unicode(self):
|
||||
faultstring = u'\xc3\xa3o'
|
||||
faultstring = '\xc3\xa3o'
|
||||
ret = self._test_format_exception(exc.ClientSideError(faultstring))
|
||||
self.assertIsNone(ret['debuginfo'])
|
||||
self.assertEqual('Client', ret['faultcode'])
|
||||
@@ -395,7 +429,8 @@ class TestFormatException(unittest.TestCase):
|
||||
faultcode = 'AccessDenied'
|
||||
faultstring = 'boom'
|
||||
ret = self._test_format_exception(
|
||||
exc.ClientSideError(faultstring, faultcode=faultcode))
|
||||
exc.ClientSideError(faultstring, faultcode=faultcode)
|
||||
)
|
||||
self.assertIsNone(ret['debuginfo'])
|
||||
self.assertEqual('AccessDenied', ret['faultcode'])
|
||||
self.assertEqual(faultstring, ret['faultstring'])
|
||||
@@ -408,7 +443,7 @@ class TestFormatException(unittest.TestCase):
|
||||
self.assertEqual(faultstring, ret['faultstring'])
|
||||
|
||||
def test_format_server_exception_unicode(self):
|
||||
faultstring = u'\xc3\xa3o'
|
||||
faultstring = '\xc3\xa3o'
|
||||
ret = self._test_format_exception(Exception(faultstring))
|
||||
self.assertIsNone(ret['debuginfo'])
|
||||
self.assertEqual('Server', ret['faultcode'])
|
||||
|
||||
+12
-8
@@ -1,7 +1,9 @@
|
||||
# encoding=utf8
|
||||
|
||||
from wsme.exc import (ClientSideError, InvalidInput, MissingArgument,
|
||||
UnknownArgument)
|
||||
from wsme.exc import (
|
||||
ClientSideError,
|
||||
InvalidInput,
|
||||
MissingArgument,
|
||||
UnknownArgument,
|
||||
)
|
||||
|
||||
|
||||
def test_clientside_error():
|
||||
@@ -28,12 +30,14 @@ def test_invalidinput():
|
||||
def test_missingargument():
|
||||
e = MissingArgument('argname', "error message")
|
||||
|
||||
assert e.faultstring == \
|
||||
('Missing argument: "argname": error message'), e.faultstring
|
||||
assert e.faultstring == ('Missing argument: "argname": error message'), (
|
||||
e.faultstring
|
||||
)
|
||||
|
||||
|
||||
def test_unknownargument():
|
||||
e = UnknownArgument('argname', "error message")
|
||||
|
||||
assert e.faultstring == \
|
||||
('Unknown argument: "argname": error message'), e.faultstring
|
||||
assert e.faultstring == ('Unknown argument: "argname": error message'), (
|
||||
e.faultstring
|
||||
)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding=utf8
|
||||
|
||||
import unittest
|
||||
|
||||
from wsme import WSRoot
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding=utf8
|
||||
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
@@ -24,16 +22,19 @@ class DictBasedUserType(UserType):
|
||||
|
||||
class TestProtocolsCommons(unittest.TestCase):
|
||||
def test_from_param_date(self):
|
||||
assert from_param(datetime.date, '2008-02-28') == \
|
||||
datetime.date(2008, 2, 28)
|
||||
assert from_param(datetime.date, '2008-02-28') == datetime.date(
|
||||
2008, 2, 28
|
||||
)
|
||||
|
||||
def test_from_param_time(self):
|
||||
assert from_param(datetime.time, '12:14:56') == \
|
||||
datetime.time(12, 14, 56)
|
||||
assert from_param(datetime.time, '12:14:56') == datetime.time(
|
||||
12, 14, 56
|
||||
)
|
||||
|
||||
def test_from_param_datetime(self):
|
||||
assert from_param(datetime.datetime, '2009-12-23T12:14:56') == \
|
||||
datetime.datetime(2009, 12, 23, 12, 14, 56)
|
||||
assert from_param(
|
||||
datetime.datetime, '2009-12-23T12:14:56'
|
||||
) == datetime.datetime(2009, 12, 23, 12, 14, 56)
|
||||
|
||||
def test_from_param_usertype(self):
|
||||
assert from_param(MyUserType(), 'test') == 'test'
|
||||
@@ -45,6 +46,7 @@ class TestProtocolsCommons(unittest.TestCase):
|
||||
class params(dict):
|
||||
def getall(self, path):
|
||||
return ['1', '2']
|
||||
|
||||
p = params({'a': []})
|
||||
assert from_params(ArrayType(int), p, 'a', set()) == [1, 2]
|
||||
|
||||
@@ -53,10 +55,7 @@ class TestProtocolsCommons(unittest.TestCase):
|
||||
|
||||
def test_from_params_dict(self):
|
||||
value = from_params(
|
||||
DictType(int, str),
|
||||
{'a[2]': 'a2', 'a[3]': 'a3'},
|
||||
'a',
|
||||
set()
|
||||
DictType(int, str), {'a[2]': 'a2', 'a[3]': 'a3'}, 'a', set()
|
||||
)
|
||||
assert value == {2: 'a2', 3: 'a3'}, value
|
||||
|
||||
@@ -64,16 +63,10 @@ class TestProtocolsCommons(unittest.TestCase):
|
||||
assert from_params(DictType(int, str), {}, 'a', set()) is Unset
|
||||
|
||||
def test_from_params_usertype(self):
|
||||
value = from_params(
|
||||
DictBasedUserType(),
|
||||
{'a[2]': '2'},
|
||||
'a',
|
||||
set()
|
||||
)
|
||||
value = from_params(DictBasedUserType(), {'a[2]': '2'}, 'a', set())
|
||||
self.assertEqual(value, {2: 2})
|
||||
|
||||
def test_args_from_args_usertype(self):
|
||||
|
||||
class FakeType(UserType):
|
||||
name = 'fake-type'
|
||||
basetype = int
|
||||
@@ -94,7 +87,6 @@ class TestProtocolsCommons(unittest.TestCase):
|
||||
self.fail('Should have thrown an InvalidInput')
|
||||
|
||||
def test_args_from_args_custom_exc(self):
|
||||
|
||||
class FakeType(UserType):
|
||||
name = 'fake-type'
|
||||
basetype = int
|
||||
@@ -131,7 +123,6 @@ class TestProtocolsCommons(unittest.TestCase):
|
||||
|
||||
|
||||
class ArgTypeConversion(unittest.TestCase):
|
||||
|
||||
def test_int_zero(self):
|
||||
self.assertEqual(0, from_param(int, 0))
|
||||
self.assertEqual(0, from_param(int, '0'))
|
||||
|
||||
+168
-178
@@ -18,11 +18,12 @@ def prepare_value(value, datatype):
|
||||
return [prepare_value(item, datatype[0]) for item in value]
|
||||
if isinstance(datatype, dict):
|
||||
key_type, value_type = list(datatype.items())[0]
|
||||
return dict((
|
||||
(prepare_value(item[0], key_type),
|
||||
prepare_value(item[1], value_type))
|
||||
return {
|
||||
prepare_value(item[0], key_type): prepare_value(
|
||||
item[1], value_type
|
||||
)
|
||||
for item in value.items()
|
||||
))
|
||||
}
|
||||
if datatype in (datetime.date, datetime.time, datetime.datetime):
|
||||
return value.isoformat()
|
||||
if datatype == decimal.Decimal:
|
||||
@@ -47,17 +48,19 @@ def prepare_result(value, datatype):
|
||||
if isarray(datatype):
|
||||
return [prepare_result(item, datatype.item_type) for item in value]
|
||||
if isinstance(datatype, dict):
|
||||
return dict((
|
||||
(prepare_result(item[0], list(datatype.keys())[0]),
|
||||
prepare_result(item[1], list(datatype.values())[0]))
|
||||
return {
|
||||
prepare_result(item[0], list(datatype.keys())[0]): prepare_result(
|
||||
item[1], list(datatype.values())[0]
|
||||
)
|
||||
for item in value.items()
|
||||
))
|
||||
}
|
||||
if isdict(datatype):
|
||||
return dict((
|
||||
(prepare_result(item[0], datatype.key_type),
|
||||
prepare_result(item[1], datatype.value_type))
|
||||
return {
|
||||
prepare_result(item[0], datatype.key_type): prepare_result(
|
||||
item[1], datatype.value_type
|
||||
)
|
||||
for item in value.items()
|
||||
))
|
||||
}
|
||||
if datatype == datetime.date:
|
||||
return parse_isodate(value)
|
||||
if datatype == datetime.time:
|
||||
@@ -92,7 +95,7 @@ class NestedObj(wsme.types.Base):
|
||||
o = Obj
|
||||
|
||||
|
||||
class CRUDResult(object):
|
||||
class CRUDResult:
|
||||
data = Obj
|
||||
message = wsme.types.text
|
||||
|
||||
@@ -101,7 +104,7 @@ class CRUDResult(object):
|
||||
self.message = message
|
||||
|
||||
|
||||
class MiniCrud(object):
|
||||
class MiniCrud:
|
||||
@expose(CRUDResult, method='PUT')
|
||||
@validate(Obj)
|
||||
def create(self, data):
|
||||
@@ -142,8 +145,15 @@ wsme.tests.protocol.WSTestRoot.crud = MiniCrud()
|
||||
class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
protocol = 'restjson'
|
||||
|
||||
def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False,
|
||||
body=None, **kw):
|
||||
def call(
|
||||
self,
|
||||
fpath,
|
||||
_rt=None,
|
||||
_accept=None,
|
||||
_no_result_decode=False,
|
||||
body=None,
|
||||
**kw,
|
||||
):
|
||||
if body:
|
||||
if isinstance(body, tuple):
|
||||
body, datatype = body
|
||||
@@ -160,16 +170,12 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
datatype = type(value)
|
||||
kw[key] = prepare_value(value, datatype)
|
||||
content = json.dumps(kw)
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if _accept is not None:
|
||||
headers["Accept"] = _accept
|
||||
res = self.app.post(
|
||||
'/' + fpath,
|
||||
content,
|
||||
headers=headers,
|
||||
expect_errors=True)
|
||||
'/' + fpath, content, headers=headers, expect_errors=True
|
||||
)
|
||||
print("Received:", res.body)
|
||||
|
||||
if _no_result_decode:
|
||||
@@ -182,9 +188,7 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
return r
|
||||
else:
|
||||
raise wsme.tests.protocol.CallException(
|
||||
r['faultcode'],
|
||||
r['faultstring'],
|
||||
r.get('debuginfo')
|
||||
r['faultcode'], r['faultstring'], r.get('debuginfo')
|
||||
)
|
||||
|
||||
return json.loads(res.text)
|
||||
@@ -202,33 +206,37 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
print(r)
|
||||
assert json.loads(r.text) == [
|
||||
{'inner': {'aint': 54}},
|
||||
{'inner': {'aint': 55}}]
|
||||
{'inner': {'aint': 55}},
|
||||
]
|
||||
|
||||
def test_form_urlencoded_args(self):
|
||||
params = {
|
||||
'value[0].inner.aint': 54,
|
||||
'value[1].inner.aint': 55
|
||||
}
|
||||
params = {'value[0].inner.aint': 54, 'value[1].inner.aint': 55}
|
||||
body = urlencode(params)
|
||||
r = self.app.post(
|
||||
'/argtypes/setnestedarray.json',
|
||||
body,
|
||||
headers={'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
headers={'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
)
|
||||
print(r)
|
||||
|
||||
assert json.loads(r.text) == [
|
||||
{'inner': {'aint': 54}},
|
||||
{'inner': {'aint': 55}}]
|
||||
{'inner': {'aint': 55}},
|
||||
]
|
||||
|
||||
def test_body_and_params(self):
|
||||
r = self.app.post('/argtypes/setint.json?value=2', '{"value": 2}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True)
|
||||
r = self.app.post(
|
||||
'/argtypes/setint.json?value=2',
|
||||
'{"value": 2}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True,
|
||||
)
|
||||
print(r)
|
||||
assert r.status_int == 400
|
||||
assert json.loads(r.text)['faultstring'] == \
|
||||
"Parameter value was given several times"
|
||||
assert (
|
||||
json.loads(r.text)['faultstring']
|
||||
== "Parameter value was given several times"
|
||||
)
|
||||
|
||||
def test_inline_body(self):
|
||||
params = urlencode({'__body__': '{"value": 4}'})
|
||||
@@ -243,27 +251,40 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert json.loads(r.text) == 2
|
||||
|
||||
def test_invalid_content_type_body(self):
|
||||
r = self.app.post('/argtypes/setint.json', '{"value": 2}',
|
||||
headers={"Content-Type": "application/invalid"},
|
||||
expect_errors=True)
|
||||
r = self.app.post(
|
||||
'/argtypes/setint.json',
|
||||
'{"value": 2}',
|
||||
headers={"Content-Type": "application/invalid"},
|
||||
expect_errors=True,
|
||||
)
|
||||
print(r)
|
||||
assert r.status_int == 415
|
||||
assert json.loads(r.text)['faultstring'] == \
|
||||
"Unknown mimetype: application/invalid"
|
||||
assert (
|
||||
json.loads(r.text)['faultstring']
|
||||
== "Unknown mimetype: application/invalid"
|
||||
)
|
||||
|
||||
def test_invalid_json_body(self):
|
||||
r = self.app.post('/argtypes/setint.json', '{"value": 2',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True)
|
||||
r = self.app.post(
|
||||
'/argtypes/setint.json',
|
||||
'{"value": 2',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True,
|
||||
)
|
||||
print(r)
|
||||
assert r.status_int == 400
|
||||
assert json.loads(r.text)['faultstring'] == \
|
||||
"Request is not in valid JSON format"
|
||||
assert (
|
||||
json.loads(r.text)['faultstring']
|
||||
== "Request is not in valid JSON format"
|
||||
)
|
||||
|
||||
def test_unknown_arg(self):
|
||||
r = self.app.post('/returntypes/getint.json', '{"a": 2}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True)
|
||||
r = self.app.post(
|
||||
'/returntypes/getint.json',
|
||||
'{"a": 2}',
|
||||
headers={"Content-Type": "application/json"},
|
||||
expect_errors=True,
|
||||
)
|
||||
print(r)
|
||||
assert r.status_int == 400
|
||||
assert json.loads(r.text)['faultstring'].startswith(
|
||||
@@ -281,7 +302,7 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
r = self.app.post(
|
||||
'/argtypes/setcustomobject',
|
||||
'{"value": {"aint": 2, "name": "test"}}',
|
||||
headers={"Content-Type": "application/json"}
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(r.status_int, 200)
|
||||
self.assertEqual(r.json, {'aint': 2, 'name': 'test'})
|
||||
@@ -290,13 +311,13 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
r = self.app.post(
|
||||
'/argtypes/setextendedint',
|
||||
'{"value": 3}',
|
||||
headers={"Content-Type": "application/json"}
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(r.status_int, 200)
|
||||
self.assertEqual(r.json, 3)
|
||||
|
||||
def test_unset_attrs(self):
|
||||
class AType(object):
|
||||
class AType:
|
||||
attr = int
|
||||
|
||||
wsme.types.register_type(AType)
|
||||
@@ -314,13 +335,25 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert tojson({int: str}, {5: '5'}) == {5: '5'}
|
||||
|
||||
def test_None_tojson(self):
|
||||
for dt in (datetime.date, datetime.time, datetime.datetime,
|
||||
decimal.Decimal):
|
||||
for dt in (
|
||||
datetime.date,
|
||||
datetime.time,
|
||||
datetime.datetime,
|
||||
decimal.Decimal,
|
||||
):
|
||||
assert tojson(dt, None) is None
|
||||
|
||||
def test_None_fromjson(self):
|
||||
for dt in (str, int, datetime.date, datetime.time, datetime.datetime,
|
||||
decimal.Decimal, [int], {int: int}):
|
||||
for dt in (
|
||||
str,
|
||||
int,
|
||||
datetime.date,
|
||||
datetime.time,
|
||||
datetime.datetime,
|
||||
decimal.Decimal,
|
||||
[int],
|
||||
{int: int},
|
||||
):
|
||||
assert fromjson(dt, None) is None
|
||||
|
||||
def test_parse_valid_date(self):
|
||||
@@ -338,35 +371,35 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
def test_invalid_list_fromjson(self):
|
||||
jlist = "invalid"
|
||||
try:
|
||||
parse('{"a": "%s"}' % jlist, {'a': ArrayType(str)}, False)
|
||||
parse(f'{{"a": "{jlist}"}}', {'a': ArrayType(str)}, False)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, InvalidInput)
|
||||
assert e.fieldname == 'a'
|
||||
assert e.value == jlist
|
||||
assert e.msg == "Value not a valid list: %s" % jlist
|
||||
assert e.msg == f"Value not a valid list: {jlist}"
|
||||
|
||||
def test_invalid_dict_fromjson(self):
|
||||
jdict = "invalid"
|
||||
try:
|
||||
parse('{"a": "%s"}' % jdict, {'a': DictType(str, str)}, False)
|
||||
parse(f'{{"a": "{jdict}"}}', {'a': DictType(str, str)}, False)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, InvalidInput)
|
||||
assert e.fieldname == 'a'
|
||||
assert e.value == jdict
|
||||
assert e.msg == "Value not a valid dict: %s" % jdict
|
||||
assert e.msg == f"Value not a valid dict: {jdict}"
|
||||
|
||||
def test_invalid_date_fromjson(self):
|
||||
jdate = "2015-01-invalid"
|
||||
try:
|
||||
parse('{"a": "%s"}' % jdate, {'a': datetime.date}, False)
|
||||
parse(f'{{"a": "{jdate}"}}', {'a': datetime.date}, False)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, InvalidInput)
|
||||
assert e.fieldname == 'a'
|
||||
assert e.value == jdate
|
||||
assert e.msg == "'%s' is not a legal date value" % jdate
|
||||
assert e.msg == f"'{jdate}' is not a legal date value"
|
||||
|
||||
def test_parse_valid_date_bodyarg(self):
|
||||
j = parse('"2011-01-01"', {'a': datetime.date}, True)
|
||||
@@ -375,13 +408,13 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
def test_invalid_date_fromjson_bodyarg(self):
|
||||
jdate = "2015-01-invalid"
|
||||
try:
|
||||
parse('"%s"' % jdate, {'a': datetime.date}, True)
|
||||
parse(f'"{jdate}"', {'a': datetime.date}, True)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, InvalidInput)
|
||||
assert e.fieldname == 'a'
|
||||
assert e.value == jdate
|
||||
assert e.msg == "'%s' is not a legal date value" % jdate
|
||||
assert e.msg == f"'{jdate}' is not a legal date value"
|
||||
|
||||
def test_valid_str_to_builtin_fromjson(self):
|
||||
types = (int, bool, float)
|
||||
@@ -391,11 +424,10 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
jd = '%s' if ba else '{"a": %s}'
|
||||
i = parse(jd % value, {'a': t}, ba)
|
||||
self.assertEqual(
|
||||
i, {'a': t(value)},
|
||||
"Parsed value does not correspond for %s: "
|
||||
"%s != {'a': %s}" % (
|
||||
t, repr(i), repr(t(value))
|
||||
)
|
||||
i,
|
||||
{'a': t(value)},
|
||||
f"Parsed value does not correspond for {t}: "
|
||||
f"{repr(i)} != {{'a': {repr(t(value))}}}",
|
||||
)
|
||||
self.assertIsInstance(i['a'], t)
|
||||
|
||||
@@ -425,8 +457,7 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
try:
|
||||
parse(jd % value, {'a': t}, ba)
|
||||
assert False, (
|
||||
"Value '%s' should not parse correctly for %s." %
|
||||
(value, t)
|
||||
f"Value '{value}' should not parse correctly for {t}."
|
||||
)
|
||||
except ClientSideError as e:
|
||||
self.assertIsInstance(e, InvalidInput)
|
||||
@@ -441,8 +472,7 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
try:
|
||||
parse(jd % value, {'a': bool}, ba)
|
||||
assert False, (
|
||||
"Value '%s' should not parse correctly for %s." %
|
||||
(value, bool)
|
||||
f"Value '{value}' should not parse correctly for {bool}."
|
||||
)
|
||||
except ClientSideError as e:
|
||||
self.assertIsInstance(e, InvalidInput)
|
||||
@@ -504,8 +534,7 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
self.assertEqual(e.fieldname, 'a')
|
||||
self.assertEqual(e.value, value)
|
||||
self.assertEqual(
|
||||
e.msg,
|
||||
"invalid literal for int() with base 10: '%s'" % value
|
||||
e.msg, f"invalid literal for int() with base 10: '{value}'"
|
||||
)
|
||||
|
||||
def test_parse_unexpected_attribute(self):
|
||||
@@ -521,22 +550,16 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
parse(json.dumps(jd), {'o': Obj}, ba)
|
||||
raise AssertionError("Object should not parse correcty.")
|
||||
except wsme.exc.UnknownAttribute as e:
|
||||
self.assertEqual(e.attributes, set(['other', 'other2']))
|
||||
self.assertEqual(e.attributes, {'other', 'other2'})
|
||||
|
||||
def test_parse_unexpected_nested_attribute(self):
|
||||
no = {
|
||||
"o": {
|
||||
"id": "1",
|
||||
"name": "test",
|
||||
"other": "unknown",
|
||||
},
|
||||
}
|
||||
no = {"o": {"id": "1", "name": "test", "other": "unknown"}}
|
||||
for ba in False, True:
|
||||
jd = no if ba else {"no": no}
|
||||
try:
|
||||
parse(json.dumps(jd), {'no': NestedObj}, ba)
|
||||
except wsme.exc.UnknownAttribute as e:
|
||||
self.assertEqual(e.attributes, set(['other']))
|
||||
self.assertEqual(e.attributes, {'other'})
|
||||
self.assertEqual(e.fieldname, "no.o")
|
||||
|
||||
def test_nest_result(self):
|
||||
@@ -546,7 +569,7 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert json.loads(r.text) == {"result": 2}
|
||||
|
||||
def test_encode_sample_value(self):
|
||||
class MyType(object):
|
||||
class MyType:
|
||||
aint = int
|
||||
astr = str
|
||||
|
||||
@@ -559,40 +582,39 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
r = wsme.rest.json.encode_sample_value(MyType, v, True)
|
||||
print(r)
|
||||
assert r[0] == ('javascript')
|
||||
assert r[1] == json.dumps({'aint': 4, 'astr': 's'}, ensure_ascii=False,
|
||||
indent=4, sort_keys=True)
|
||||
assert r[1] == json.dumps(
|
||||
{'aint': 4, 'astr': 's'},
|
||||
ensure_ascii=False,
|
||||
indent=4,
|
||||
sort_keys=True,
|
||||
)
|
||||
|
||||
def test_bytes_tojson(self):
|
||||
assert tojson(wsme.types.bytes, None) is None
|
||||
assert tojson(wsme.types.bytes, b'ascii') == 'ascii'
|
||||
|
||||
def test_encode_sample_params(self):
|
||||
r = wsme.rest.json.encode_sample_params(
|
||||
[('a', int, 2)], True
|
||||
)
|
||||
r = wsme.rest.json.encode_sample_params([('a', int, 2)], True)
|
||||
assert r[0] == 'javascript', r[0]
|
||||
assert r[1] == '''{
|
||||
assert (
|
||||
r[1]
|
||||
== '''{
|
||||
"a": 2
|
||||
}''', r[1]
|
||||
}'''
|
||||
), r[1]
|
||||
|
||||
def test_encode_sample_result(self):
|
||||
r = wsme.rest.json.encode_sample_result(
|
||||
int, 2, True
|
||||
)
|
||||
r = wsme.rest.json.encode_sample_result(int, 2, True)
|
||||
assert r[0] == 'javascript', r[0]
|
||||
assert r[1] == '''2'''
|
||||
|
||||
def test_PUT(self):
|
||||
data = {"id": 1, "name": "test"}
|
||||
content = json.dumps(dict(data=data))
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
content = json.dumps({'data': data})
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
res = self.app.put(
|
||||
'/crud',
|
||||
content,
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
'/crud', content, headers=headers, expect_errors=False
|
||||
)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
@@ -601,13 +623,10 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert result['message'] == "create"
|
||||
|
||||
def test_GET(self):
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
headers = {'Accept': 'application/json'}
|
||||
res = self.app.get(
|
||||
'/crud?ref.id=1',
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
'/crud?ref.id=1', headers=headers, expect_errors=False
|
||||
)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
@@ -615,13 +634,10 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert result['data']['name'] == "test"
|
||||
|
||||
def test_GET_complex_accept(self):
|
||||
headers = {
|
||||
'Accept': 'text/html,application/xml;q=0.9,*/*;q=0.8'
|
||||
}
|
||||
headers = {'Accept': 'text/html,application/xml;q=0.9,*/*;q=0.8'}
|
||||
res = self.app.get(
|
||||
'/crud?ref.id=1',
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
'/crud?ref.id=1', headers=headers, expect_errors=False
|
||||
)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
@@ -629,54 +645,42 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert result['data']['name'] == "test"
|
||||
|
||||
def test_GET_complex_choose_xml(self):
|
||||
headers = {
|
||||
'Accept': 'text/html,text/xml;q=0.9,*/*;q=0.8'
|
||||
}
|
||||
headers = {'Accept': 'text/html,text/xml;q=0.9,*/*;q=0.8'}
|
||||
res = self.app.get(
|
||||
'/crud?ref.id=1',
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
'/crud?ref.id=1', headers=headers, expect_errors=False
|
||||
)
|
||||
print("Received:", res.body)
|
||||
assert res.content_type == 'text/xml'
|
||||
|
||||
def test_GET_complex_accept_no_match(self):
|
||||
headers = {
|
||||
'Accept': 'text/html,application/xml;q=0.9'
|
||||
}
|
||||
res = self.app.get(
|
||||
'/crud?ref.id=1',
|
||||
headers=headers,
|
||||
status=406)
|
||||
headers = {'Accept': 'text/html,application/xml;q=0.9'}
|
||||
res = self.app.get('/crud?ref.id=1', headers=headers, status=406)
|
||||
print("Received:", res.body)
|
||||
assert res.body == (
|
||||
b"Unacceptable Accept type: "
|
||||
b"text/html, application/xml;q=0.9 not in "
|
||||
b"['application/json', 'text/javascript', "
|
||||
b"'application/javascript', 'text/xml']")
|
||||
b"'application/javascript', 'text/xml']"
|
||||
)
|
||||
|
||||
def test_GET_bad_simple_accept(self):
|
||||
headers = {
|
||||
'Accept': 'text/plain',
|
||||
}
|
||||
res = self.app.get(
|
||||
'/crud?ref.id=1',
|
||||
headers=headers,
|
||||
status=406)
|
||||
headers = {'Accept': 'text/plain'}
|
||||
res = self.app.get('/crud?ref.id=1', headers=headers, status=406)
|
||||
print("Received:", res.body)
|
||||
assert res.body == (
|
||||
b"Unacceptable Accept type: text/plain not in "
|
||||
b"['application/json', 'text/javascript', "
|
||||
b"'application/javascript', 'text/xml']")
|
||||
b"'application/javascript', 'text/xml']"
|
||||
)
|
||||
|
||||
def test_POST(self):
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
res = self.app.post(
|
||||
'/crud',
|
||||
json.dumps(dict(data=dict(id=1, name='test'))),
|
||||
json.dumps({'data': {'id': 1, 'name': 'test'}}),
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
expect_errors=False,
|
||||
)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
@@ -685,24 +689,22 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert result['message'] == "update"
|
||||
|
||||
def test_POST_bad_content_type(self):
|
||||
headers = {
|
||||
'Content-Type': 'text/plain',
|
||||
}
|
||||
headers = {'Content-Type': 'text/plain'}
|
||||
res = self.app.post(
|
||||
'/crud',
|
||||
json.dumps(dict(data=dict(id=1, name='test'))),
|
||||
json.dumps({'data': {'id': 1, 'name': 'test'}}),
|
||||
headers=headers,
|
||||
status=415)
|
||||
status=415,
|
||||
)
|
||||
print("Received:", res.body)
|
||||
assert res.body == (
|
||||
b"Unacceptable Content-Type: text/plain not in "
|
||||
b"['application/json', 'text/javascript', "
|
||||
b"'application/javascript', 'text/xml']")
|
||||
b"'application/javascript', 'text/xml']"
|
||||
)
|
||||
|
||||
def test_DELETE(self):
|
||||
res = self.app.delete(
|
||||
'/crud.json?ref.id=1',
|
||||
expect_errors=False)
|
||||
res = self.app.delete('/crud.json?ref.id=1', expect_errors=False)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
@@ -711,13 +713,10 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert result['message'] == "delete"
|
||||
|
||||
def test_extra_arguments(self):
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
headers = {'Accept': 'application/json'}
|
||||
res = self.app.get(
|
||||
'/crud?ref.id=1&extraarg=foo',
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
'/crud?ref.id=1&extraarg=foo', headers=headers, expect_errors=False
|
||||
)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
@@ -726,41 +725,32 @@ class TestRestJson(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
assert result['message'] == "read"
|
||||
|
||||
def test_unexpected_extra_arg(self):
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {"id": 1, "name": "test"}
|
||||
content = json.dumps({"data": data, "other": "unexpected"})
|
||||
res = self.app.put(
|
||||
'/crud',
|
||||
content,
|
||||
headers=headers,
|
||||
expect_errors=True)
|
||||
'/crud', content, headers=headers, expect_errors=True
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_unexpected_extra_attribute(self):
|
||||
"""Expect a failure if we send an unexpected object attribute."""
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {"id": 1, "name": "test", "other": "unexpected"}
|
||||
content = json.dumps({"data": data})
|
||||
res = self.app.put(
|
||||
'/crud',
|
||||
content,
|
||||
headers=headers,
|
||||
expect_errors=True)
|
||||
'/crud', content, headers=headers, expect_errors=True
|
||||
)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_body_arg(self):
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
res = self.app.post(
|
||||
'/crud/update_with_body?msg=hello',
|
||||
json.dumps(dict(id=1, name='test')),
|
||||
json.dumps({'id': 1, 'name': 'test'}),
|
||||
headers=headers,
|
||||
expect_errors=False)
|
||||
expect_errors=False,
|
||||
)
|
||||
print("Received:", res.body)
|
||||
result = json.loads(res.text)
|
||||
print(result)
|
||||
|
||||
+65
-42
@@ -22,15 +22,15 @@ def dumpxml(key, obj, datatype=None):
|
||||
node = et.SubElement(el, 'item')
|
||||
node.append(dumpxml('key', item[0], key_type))
|
||||
node.append(dumpxml('value', item[1], value_type))
|
||||
elif datatype == wsme.types.binary:
|
||||
elif datatype is wsme.types.binary:
|
||||
el.text = base64.encodebytes(obj).decode('ascii')
|
||||
elif isinstance(obj, wsme.types.bytes):
|
||||
el.text = obj.decode('ascii')
|
||||
elif isinstance(obj, wsme.types.text):
|
||||
el.text = obj
|
||||
elif type(obj) in (int, float, bool, decimal.Decimal):
|
||||
elif isinstance(obj, (int, float, bool, decimal.Decimal)):
|
||||
el.text = str(obj)
|
||||
elif type(obj) in (datetime.date, datetime.time, datetime.datetime):
|
||||
elif isinstance(obj, (datetime.date, datetime.time, datetime.datetime)):
|
||||
el.text = obj.isoformat()
|
||||
elif isinstance(obj, type(None)):
|
||||
el.set('nil', 'true')
|
||||
@@ -60,23 +60,26 @@ def loadxml(el, datatype):
|
||||
]
|
||||
elif isinstance(datatype, dict):
|
||||
key_type, value_type = list(datatype.items())[0]
|
||||
return dict((
|
||||
(loadxml(item.find('key'), key_type),
|
||||
loadxml(item.find('value'), value_type))
|
||||
return {
|
||||
loadxml(item.find('key'), key_type): loadxml(
|
||||
item.find('value'), value_type
|
||||
)
|
||||
for item in el.findall('item')
|
||||
))
|
||||
}
|
||||
elif isdict(datatype):
|
||||
return dict((
|
||||
(loadxml(item.find('key'), datatype.key_type),
|
||||
loadxml(item.find('value'), datatype.value_type))
|
||||
return {
|
||||
loadxml(item.find('key'), datatype.key_type): loadxml(
|
||||
item.find('value'), datatype.value_type
|
||||
)
|
||||
for item in el.findall('item')
|
||||
))
|
||||
}
|
||||
elif isdict(datatype):
|
||||
return dict((
|
||||
(loadxml(item.find('key'), datatype.key_type),
|
||||
loadxml(item.find('value'), datatype.value_type))
|
||||
return {
|
||||
loadxml(item.find('key'), datatype.key_type): loadxml(
|
||||
item.find('value'), datatype.value_type
|
||||
)
|
||||
for item in el.findall('item')
|
||||
))
|
||||
}
|
||||
elif len(el):
|
||||
d = {}
|
||||
for attr in datatype._wsme_attributes:
|
||||
@@ -88,19 +91,19 @@ def loadxml(el, datatype):
|
||||
print(d)
|
||||
return d
|
||||
else:
|
||||
if datatype == wsme.types.binary:
|
||||
if datatype is wsme.types.binary:
|
||||
return base64.decodebytes(el.text.encode('ascii'))
|
||||
if isusertype(datatype):
|
||||
datatype = datatype.basetype
|
||||
if datatype == datetime.date:
|
||||
if datatype is datetime.date:
|
||||
return parse_isodate(el.text)
|
||||
if datatype == datetime.time:
|
||||
if datatype is datetime.time:
|
||||
return parse_isotime(el.text)
|
||||
if datatype == datetime.datetime:
|
||||
if datatype is datetime.datetime:
|
||||
return parse_isodatetime(el.text)
|
||||
if datatype == wsme.types.text:
|
||||
if datatype is wsme.types.text:
|
||||
return datatype(el.text if el.text else '')
|
||||
if datatype == bool:
|
||||
if datatype is bool:
|
||||
return el.text.lower() != 'false'
|
||||
if datatype is None:
|
||||
return el.text
|
||||
@@ -112,23 +115,26 @@ def loadxml(el, datatype):
|
||||
class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
protocol = 'restxml'
|
||||
|
||||
def call(self, fpath, _rt=None, _accept=None, _no_result_decode=False,
|
||||
body=None, **kw):
|
||||
def call(
|
||||
self,
|
||||
fpath,
|
||||
_rt=None,
|
||||
_accept=None,
|
||||
_no_result_decode=False,
|
||||
body=None,
|
||||
**kw,
|
||||
):
|
||||
if body:
|
||||
el = dumpxml('body', body)
|
||||
else:
|
||||
el = dumpxml('parameters', kw)
|
||||
content = et.tostring(el)
|
||||
headers = {
|
||||
'Content-Type': 'text/xml',
|
||||
}
|
||||
headers = {'Content-Type': 'text/xml'}
|
||||
if _accept is not None:
|
||||
headers['Accept'] = _accept
|
||||
res = self.app.post(
|
||||
'/' + fpath,
|
||||
content,
|
||||
headers=headers,
|
||||
expect_errors=True)
|
||||
'/' + fpath, content, headers=headers, expect_errors=True
|
||||
)
|
||||
print("Received:", res.body)
|
||||
|
||||
if _no_result_decode:
|
||||
@@ -139,15 +145,16 @@ class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
raise wsme.tests.protocol.CallException(
|
||||
el.find('faultcode').text,
|
||||
el.find('faultstring').text,
|
||||
el.find('debuginfo') is not None and
|
||||
el.find('debuginfo').text or None
|
||||
el.find('debuginfo') is not None
|
||||
and el.find('debuginfo').text
|
||||
or None,
|
||||
)
|
||||
|
||||
else:
|
||||
return loadxml(et.fromstring(res.body), _rt)
|
||||
|
||||
def test_encode_sample_value(self):
|
||||
class MyType(object):
|
||||
class MyType:
|
||||
aint = int
|
||||
atext = wsme.types.text
|
||||
|
||||
@@ -158,18 +165,23 @@ class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
value.atext = 'test'
|
||||
|
||||
language, sample = wsme.rest.xml.encode_sample_value(
|
||||
MyType, value, True)
|
||||
MyType, value, True
|
||||
)
|
||||
print(language, sample)
|
||||
|
||||
assert language == 'xml'
|
||||
assert sample == b"""<value>
|
||||
assert (
|
||||
sample
|
||||
== b"""<value>
|
||||
<aint>5</aint>
|
||||
<atext>test</atext>
|
||||
</value>"""
|
||||
)
|
||||
|
||||
def test_encode_sample_params(self):
|
||||
lang, content = wsme.rest.xml.encode_sample_params(
|
||||
[('a', int, 2)], True)
|
||||
[('a', int, 2)], True
|
||||
)
|
||||
assert lang == 'xml', lang
|
||||
assert content == b'<parameters>\n <a>2</a>\n</parameters>', content
|
||||
|
||||
@@ -180,21 +192,32 @@ class TestRestXML(wsme.tests.protocol.RestOnlyProtocolTestCase):
|
||||
|
||||
def test_nil_fromxml(self):
|
||||
for dt in (
|
||||
str, [int], {int: str}, bool,
|
||||
datetime.date, datetime.time, datetime.datetime):
|
||||
str,
|
||||
[int],
|
||||
{int: str},
|
||||
bool,
|
||||
datetime.date,
|
||||
datetime.time,
|
||||
datetime.datetime,
|
||||
):
|
||||
e = et.Element('value', nil='true')
|
||||
assert fromxml(dt, e) is None
|
||||
|
||||
def test_nil_toxml(self):
|
||||
for dt in (
|
||||
wsme.types.bytes,
|
||||
[int], {int: str}, bool,
|
||||
datetime.date, datetime.time, datetime.datetime):
|
||||
wsme.types.bytes,
|
||||
[int],
|
||||
{int: str},
|
||||
bool,
|
||||
datetime.date,
|
||||
datetime.time,
|
||||
datetime.datetime,
|
||||
):
|
||||
x = et.tostring(toxml(dt, 'value', None))
|
||||
assert x == b'<value nil="true" />', x
|
||||
|
||||
def test_unset_attrs(self):
|
||||
class AType(object):
|
||||
class AType:
|
||||
someattr = wsme.types.bytes
|
||||
|
||||
wsme.types.register_type(AType)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding=utf8
|
||||
|
||||
import unittest
|
||||
|
||||
from wsme import WSRoot
|
||||
@@ -13,6 +11,7 @@ from webob import Request
|
||||
class TestRoot(unittest.TestCase):
|
||||
def test_default_transaction(self):
|
||||
import transaction
|
||||
|
||||
root = WSRoot(transaction=True)
|
||||
assert root._transaction is transaction
|
||||
|
||||
@@ -36,15 +35,18 @@ class TestRoot(unittest.TestCase):
|
||||
root.addprotocol(P())
|
||||
|
||||
from webob import Request
|
||||
|
||||
req = Request.blank('/test?check=a&check=b&name=Bob')
|
||||
res = root._handle_request(req)
|
||||
assert res.status_int == 500
|
||||
assert res.content_type == 'text/plain'
|
||||
assert (res.text ==
|
||||
'Unexpected error while selecting protocol: test'), req.text
|
||||
assert res.text == 'Unexpected error while selecting protocol: test', (
|
||||
req.text
|
||||
)
|
||||
|
||||
def test_protocol_selection_accept_mismatch(self):
|
||||
"""Verify that we get a 406 error on wrong Accept header."""
|
||||
|
||||
class P(wsme.protocol.Protocol):
|
||||
name = "test"
|
||||
|
||||
@@ -66,6 +68,7 @@ class TestRoot(unittest.TestCase):
|
||||
|
||||
def test_protocol_selection_content_type_mismatch(self):
|
||||
"""Verify that we get a 415 error on wrong Content-Type header."""
|
||||
|
||||
class P(wsme.protocol.Protocol):
|
||||
name = "test"
|
||||
|
||||
|
||||
+97
-63
@@ -31,7 +31,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert not u
|
||||
|
||||
def test_flat_type(self):
|
||||
class Flat(object):
|
||||
class Flat:
|
||||
aint = int
|
||||
abytes = bytes
|
||||
atext = str
|
||||
@@ -46,7 +46,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert attrs[0].key == 'aint'
|
||||
assert attrs[0].name == 'aint'
|
||||
assert isinstance(attrs[0], types.wsattr)
|
||||
assert attrs[0].datatype == int
|
||||
assert attrs[0].datatype is int
|
||||
assert attrs[0].mandatory is False
|
||||
assert attrs[1].key == 'abytes'
|
||||
assert attrs[1].name == 'abytes'
|
||||
@@ -56,7 +56,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert attrs[3].name == 'afloat'
|
||||
|
||||
def test_private_attr(self):
|
||||
class WithPrivateAttrs(object):
|
||||
class WithPrivateAttrs:
|
||||
_private = 12
|
||||
|
||||
types.register_type(WithPrivateAttrs)
|
||||
@@ -64,7 +64,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert len(WithPrivateAttrs._wsme_attributes) == 0
|
||||
|
||||
def test_attribute_order(self):
|
||||
class ForcedOrder(object):
|
||||
class ForcedOrder:
|
||||
_wsme_attr_order = ('a2', 'a1', 'a3')
|
||||
a1 = int
|
||||
a2 = int
|
||||
@@ -93,7 +93,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert c._wsme_attributes[2].key == 'a3'
|
||||
|
||||
def test_wsproperty(self):
|
||||
class WithWSProp(object):
|
||||
class WithWSProp:
|
||||
def __init__(self):
|
||||
self._aint = 0
|
||||
|
||||
@@ -111,7 +111,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert len(WithWSProp._wsme_attributes) == 1
|
||||
a = WithWSProp._wsme_attributes[0]
|
||||
assert a.key == 'aint'
|
||||
assert a.datatype == int
|
||||
assert a.datatype is int
|
||||
assert a.mandatory
|
||||
|
||||
o = WithWSProp()
|
||||
@@ -120,10 +120,10 @@ class TestTypes(unittest.TestCase):
|
||||
assert o.aint == 12
|
||||
|
||||
def test_nested(self):
|
||||
class Inner(object):
|
||||
class Inner:
|
||||
aint = int
|
||||
|
||||
class Outer(object):
|
||||
class Outer:
|
||||
inner = Inner
|
||||
|
||||
types.register_type(Outer)
|
||||
@@ -132,7 +132,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert len(Inner._wsme_attributes) == 1
|
||||
|
||||
def test_inspect_with_inheritance(self):
|
||||
class Parent(object):
|
||||
class Parent:
|
||||
parent_attribute = int
|
||||
|
||||
class Child(Parent):
|
||||
@@ -144,7 +144,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert len(Child._wsme_attributes) == 2
|
||||
|
||||
def test_selfreftype(self):
|
||||
class SelfRefType(object):
|
||||
class SelfRefType:
|
||||
pass
|
||||
|
||||
SelfRefType.parent = SelfRefType
|
||||
@@ -152,7 +152,7 @@ class TestTypes(unittest.TestCase):
|
||||
types.register_type(SelfRefType)
|
||||
|
||||
def test_inspect_with_property(self):
|
||||
class AType(object):
|
||||
class AType:
|
||||
@property
|
||||
def test(self):
|
||||
return 'test'
|
||||
@@ -166,7 +166,7 @@ class TestTypes(unittest.TestCase):
|
||||
aenum = types.Enum(str, 'v1', 'v2')
|
||||
assert aenum.basetype is str
|
||||
|
||||
class AType(object):
|
||||
class AType:
|
||||
a = aenum
|
||||
|
||||
types.register_type(AType)
|
||||
@@ -184,10 +184,11 @@ class TestTypes(unittest.TestCase):
|
||||
setattr,
|
||||
obj,
|
||||
'a',
|
||||
'v3')
|
||||
'v3',
|
||||
)
|
||||
|
||||
def test_attribute_validation(self):
|
||||
class AType(object):
|
||||
class AType:
|
||||
alist = [int]
|
||||
aint = int
|
||||
|
||||
@@ -204,7 +205,7 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertRaises(exc.InvalidInput, setattr, obj, 'alist', [2, 'a'])
|
||||
|
||||
def test_attribute_validation_minimum(self):
|
||||
class ATypeInt(object):
|
||||
class ATypeInt:
|
||||
attr = types.IntegerType(minimum=1, maximum=5)
|
||||
|
||||
types.register_type(ATypeInt)
|
||||
@@ -217,7 +218,7 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertRaises(exc.InvalidInput, setattr, obj, 'attr', 'zero')
|
||||
|
||||
def test_text_attribute_conversion(self):
|
||||
class SType(object):
|
||||
class SType:
|
||||
atext = types.text
|
||||
abytes = types.bytes
|
||||
|
||||
@@ -234,7 +235,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert isinstance(obj.abytes, types.bytes)
|
||||
|
||||
def test_named_attribute(self):
|
||||
class ABCDType(object):
|
||||
class ABCDType:
|
||||
a_list = types.wsattr([int], name='a.list')
|
||||
astr = str
|
||||
|
||||
@@ -249,7 +250,7 @@ class TestTypes(unittest.TestCase):
|
||||
assert attrs[1].name == 'astr', attrs[1].name
|
||||
|
||||
def test_wsattr_del(self):
|
||||
class MyType(object):
|
||||
class MyType:
|
||||
a = types.wsattr(int)
|
||||
|
||||
types.register_type(MyType)
|
||||
@@ -264,13 +265,13 @@ class TestTypes(unittest.TestCase):
|
||||
def test_validate_dict(self):
|
||||
assert types.validate_value({int: str}, {1: '1', 5: '5'})
|
||||
|
||||
self.assertRaises(ValueError, types.validate_value,
|
||||
{int: str}, [])
|
||||
self.assertRaises(ValueError, types.validate_value, {int: str}, [])
|
||||
|
||||
assert types.validate_value({int: str}, {'1': '1', 5: '5'})
|
||||
|
||||
self.assertRaises(ValueError, types.validate_value,
|
||||
{int: str}, {1: 1, 5: '5'})
|
||||
self.assertRaises(
|
||||
ValueError, types.validate_value, {int: str}, {1: 1, 5: '5'}
|
||||
)
|
||||
|
||||
def test_validate_list_valid(self):
|
||||
assert types.validate_value([int], [1, 2])
|
||||
@@ -284,8 +285,9 @@ class TestTypes(unittest.TestCase):
|
||||
assert v.validate(None) is None
|
||||
|
||||
def test_validate_list_invalid_member(self):
|
||||
self.assertRaises(ValueError, types.validate_value, [int],
|
||||
['not-a-number'])
|
||||
self.assertRaises(
|
||||
ValueError, types.validate_value, [int], ['not-a-number']
|
||||
)
|
||||
|
||||
def test_validate_list_invalid_type(self):
|
||||
self.assertRaises(ValueError, types.validate_value, [int], 1)
|
||||
@@ -295,8 +297,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertEqual(types.validate_value(float, '1'), 1.0)
|
||||
self.assertEqual(types.validate_value(float, 1.1), 1.1)
|
||||
self.assertRaises(ValueError, types.validate_value, float, [])
|
||||
self.assertRaises(ValueError, types.validate_value, float,
|
||||
'not-a-float')
|
||||
self.assertRaises(
|
||||
ValueError, types.validate_value, float, 'not-a-float'
|
||||
)
|
||||
|
||||
def test_validate_int(self):
|
||||
self.assertEqual(types.validate_value(int, 1), 1)
|
||||
@@ -313,8 +316,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertRaises(ValueError, v.validate, 11)
|
||||
|
||||
def test_validate_string_type(self):
|
||||
v = types.StringType(min_length=1, max_length=10,
|
||||
pattern='^[a-zA-Z0-9]*$')
|
||||
v = types.StringType(
|
||||
min_length=1, max_length=10, pattern='^[a-zA-Z0-9]*$'
|
||||
)
|
||||
v.validate('1')
|
||||
v.validate('12345')
|
||||
v.validate('1234567890')
|
||||
@@ -328,8 +332,7 @@ class TestTypes(unittest.TestCase):
|
||||
|
||||
def test_validate_string_type_precompile(self):
|
||||
precompile = re.compile('^[a-zA-Z0-9]*$')
|
||||
v = types.StringType(min_length=1, max_length=10,
|
||||
pattern=precompile)
|
||||
v = types.StringType(min_length=1, max_length=10, pattern=precompile)
|
||||
|
||||
# Test a pattern validation
|
||||
v.validate('a')
|
||||
@@ -339,27 +342,32 @@ class TestTypes(unittest.TestCase):
|
||||
def test_validate_string_type_pattern_exception_message(self):
|
||||
v = types.StringType(pattern='^[a-zA-Z0-9]*$')
|
||||
self.assertRaisesRegex(
|
||||
ValueError, r'Value should match the pattern \^\[a-zA-Z0-9\]\*\$',
|
||||
v.validate, '_')
|
||||
ValueError,
|
||||
r'Value should match the pattern \^\[a-zA-Z0-9\]\*\$',
|
||||
v.validate,
|
||||
'_',
|
||||
)
|
||||
|
||||
def test_validate_ipv4_address_type(self):
|
||||
v = types.IPv4AddressType()
|
||||
self.assertEqual(v.validate('127.0.0.1'), '127.0.0.1')
|
||||
self.assertEqual(v.validate('192.168.0.1'), '192.168.0.1')
|
||||
self.assertEqual(v.validate(u'8.8.1.1'), u'8.8.1.1')
|
||||
self.assertEqual(v.validate('8.8.1.1'), '8.8.1.1')
|
||||
self.assertRaises(ValueError, v.validate, '')
|
||||
self.assertRaises(ValueError, v.validate, 'foo')
|
||||
self.assertRaises(ValueError, v.validate,
|
||||
'2001:0db8:bd05:01d2:288a:1fc0:0001:10ee')
|
||||
self.assertRaises(
|
||||
ValueError, v.validate, '2001:0db8:bd05:01d2:288a:1fc0:0001:10ee'
|
||||
)
|
||||
self.assertRaises(ValueError, v.validate, '1.2.3')
|
||||
|
||||
def test_validate_ipv6_address_type(self):
|
||||
v = types.IPv6AddressType()
|
||||
self.assertEqual(v.validate('0:0:0:0:0:0:0:1'),
|
||||
'0:0:0:0:0:0:0:1')
|
||||
self.assertEqual(v.validate(u'0:0:0:0:0:0:0:1'), u'0:0:0:0:0:0:0:1')
|
||||
self.assertEqual(v.validate('2001:0db8:bd05:01d2:288a:1fc0:0001:10ee'),
|
||||
'2001:0db8:bd05:01d2:288a:1fc0:0001:10ee')
|
||||
self.assertEqual(v.validate('0:0:0:0:0:0:0:1'), '0:0:0:0:0:0:0:1')
|
||||
self.assertEqual(v.validate('0:0:0:0:0:0:0:1'), '0:0:0:0:0:0:0:1')
|
||||
self.assertEqual(
|
||||
v.validate('2001:0db8:bd05:01d2:288a:1fc0:0001:10ee'),
|
||||
'2001:0db8:bd05:01d2:288a:1fc0:0001:10ee',
|
||||
)
|
||||
self.assertRaises(ValueError, v.validate, '')
|
||||
self.assertRaises(ValueError, v.validate, 'foo')
|
||||
self.assertRaises(ValueError, v.validate, '192.168.0.1')
|
||||
@@ -367,14 +375,19 @@ class TestTypes(unittest.TestCase):
|
||||
|
||||
def test_validate_uuid_type(self):
|
||||
v = types.UuidType()
|
||||
self.assertEqual(v.validate('6a0a707c-45ef-4758-b533-e55adddba8ce'),
|
||||
'6a0a707c-45ef-4758-b533-e55adddba8ce')
|
||||
self.assertEqual(v.validate('6a0a707c45ef4758b533e55adddba8ce'),
|
||||
'6a0a707c-45ef-4758-b533-e55adddba8ce')
|
||||
self.assertEqual(
|
||||
v.validate('6a0a707c-45ef-4758-b533-e55adddba8ce'),
|
||||
'6a0a707c-45ef-4758-b533-e55adddba8ce',
|
||||
)
|
||||
self.assertEqual(
|
||||
v.validate('6a0a707c45ef4758b533e55adddba8ce'),
|
||||
'6a0a707c-45ef-4758-b533-e55adddba8ce',
|
||||
)
|
||||
self.assertRaises(ValueError, v.validate, '')
|
||||
self.assertRaises(ValueError, v.validate, 'foo')
|
||||
self.assertRaises(ValueError, v.validate,
|
||||
'6a0a707c-45ef-4758-b533-e55adddba8ce-a')
|
||||
self.assertRaises(
|
||||
ValueError, v.validate, '6a0a707c-45ef-4758-b533-e55adddba8ce-a'
|
||||
)
|
||||
|
||||
def test_register_invalid_array(self):
|
||||
self.assertRaises(ValueError, types.register_type, [])
|
||||
@@ -383,13 +396,13 @@ class TestTypes(unittest.TestCase):
|
||||
|
||||
def test_register_invalid_dict(self):
|
||||
self.assertRaises(ValueError, types.register_type, {})
|
||||
self.assertRaises(ValueError, types.register_type,
|
||||
{int: str, str: int})
|
||||
self.assertRaises(ValueError, types.register_type,
|
||||
{types.Unset: str})
|
||||
self.assertRaises(
|
||||
ValueError, types.register_type, {int: str, str: int}
|
||||
)
|
||||
self.assertRaises(ValueError, types.register_type, {types.Unset: str})
|
||||
|
||||
def test_list_attribute_no_auto_register(self):
|
||||
class MyType(object):
|
||||
class MyType:
|
||||
aint = int
|
||||
|
||||
assert not hasattr(MyType, '_wsme_attributes')
|
||||
@@ -399,10 +412,10 @@ class TestTypes(unittest.TestCase):
|
||||
assert not hasattr(MyType, '_wsme_attributes')
|
||||
|
||||
def test_list_of_complextypes(self):
|
||||
class A(object):
|
||||
class A:
|
||||
bs = types.wsattr(['B'])
|
||||
|
||||
class B(object):
|
||||
class B:
|
||||
i = int
|
||||
|
||||
types.register_type(A)
|
||||
@@ -411,10 +424,10 @@ class TestTypes(unittest.TestCase):
|
||||
assert A.bs.datatype.item_type is B
|
||||
|
||||
def test_cross_referenced_types(self):
|
||||
class A(object):
|
||||
class A:
|
||||
b = types.wsattr('B')
|
||||
|
||||
class B(object):
|
||||
class B:
|
||||
a = A
|
||||
|
||||
types.register_type(A)
|
||||
@@ -457,12 +470,14 @@ class TestTypes(unittest.TestCase):
|
||||
|
||||
def test_binary_to_base(self):
|
||||
import base64
|
||||
|
||||
assert types.binary.tobasetype(None) is None
|
||||
expected = base64.encodebytes(b'abcdef')
|
||||
assert types.binary.tobasetype(b'abcdef') == expected
|
||||
|
||||
def test_binary_from_base(self):
|
||||
import base64
|
||||
|
||||
assert types.binary.frombasetype(None) is None
|
||||
encoded = base64.encodebytes(b'abcdef')
|
||||
assert types.binary.frombasetype(encoded) == b'abcdef'
|
||||
@@ -472,6 +487,7 @@ class TestTypes(unittest.TestCase):
|
||||
# should be converted to the real type when accessed again by
|
||||
# the property getter.
|
||||
import weakref
|
||||
|
||||
a = types.wsattr(int)
|
||||
a.datatype = weakref.ref(int)
|
||||
assert a.datatype is int
|
||||
@@ -481,6 +497,7 @@ class TestTypes(unittest.TestCase):
|
||||
# to types, it should be converted to the real types when
|
||||
# accessed again by the property getter.
|
||||
import weakref
|
||||
|
||||
a = types.wsattr(int)
|
||||
a.datatype = [weakref.ref(int)]
|
||||
assert isinstance(a.datatype, list)
|
||||
@@ -490,6 +507,7 @@ class TestTypes(unittest.TestCase):
|
||||
class buffer:
|
||||
def read(self):
|
||||
return 'abcdef'
|
||||
|
||||
f = types.File(file=buffer())
|
||||
assert f.content == 'abcdef'
|
||||
|
||||
@@ -497,6 +515,7 @@ class TestTypes(unittest.TestCase):
|
||||
class buffer:
|
||||
def read(self):
|
||||
return 'from-file'
|
||||
|
||||
f = types.File(content='from-content', file=buffer())
|
||||
assert f.content == 'from-content'
|
||||
|
||||
@@ -504,6 +523,7 @@ class TestTypes(unittest.TestCase):
|
||||
class buffer:
|
||||
def read(self):
|
||||
return 'from-file'
|
||||
|
||||
f = types.File(file=buffer())
|
||||
f.content = 'from-content'
|
||||
assert f.content == 'from-content'
|
||||
@@ -517,6 +537,7 @@ class TestTypes(unittest.TestCase):
|
||||
filename = 'static.json'
|
||||
file = buffer()
|
||||
type = 'application/json'
|
||||
|
||||
f = types.File(fieldstorage=fieldstorage)
|
||||
assert f.content == 'from-file'
|
||||
|
||||
@@ -530,6 +551,7 @@ class TestTypes(unittest.TestCase):
|
||||
file = None
|
||||
type = 'application/json'
|
||||
value = 'from-value'
|
||||
|
||||
f = types.File(fieldstorage=fieldstorage)
|
||||
assert f.content == 'from-value'
|
||||
|
||||
@@ -537,6 +559,7 @@ class TestTypes(unittest.TestCase):
|
||||
class buffer:
|
||||
def read(self):
|
||||
return 'from-file'
|
||||
|
||||
buf = buffer()
|
||||
f = types.File(file=buf)
|
||||
assert f.file is buf
|
||||
@@ -545,12 +568,14 @@ class TestTypes(unittest.TestCase):
|
||||
class buffer:
|
||||
def read(self):
|
||||
return 'from-file'
|
||||
|
||||
f = types.File(content=b'from-content')
|
||||
assert f.file.read() == b'from-content'
|
||||
|
||||
def test_unregister(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
types.registry.register(TempType)
|
||||
v = types.registry.lookup('TempType')
|
||||
self.assertIs(v, TempType)
|
||||
@@ -559,8 +584,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertIs(after, None)
|
||||
|
||||
def test_unregister_twice(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
types.registry.register(TempType)
|
||||
v = types.registry.lookup('TempType')
|
||||
self.assertIs(v, TempType)
|
||||
@@ -571,8 +597,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertIs(after, None)
|
||||
|
||||
def test_unregister_array_type(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
t = [TempType]
|
||||
types.registry.register(t)
|
||||
self.assertNotEqual(types.registry.array_types, set())
|
||||
@@ -580,8 +607,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertEqual(types.registry.array_types, set())
|
||||
|
||||
def test_unregister_array_type_twice(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
t = [TempType]
|
||||
types.registry.register(t)
|
||||
self.assertNotEqual(types.registry.array_types, set())
|
||||
@@ -591,8 +619,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertEqual(types.registry.array_types, set())
|
||||
|
||||
def test_unregister_dict_type(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
t = {str: TempType}
|
||||
types.registry.register(t)
|
||||
self.assertNotEqual(types.registry.dict_types, set())
|
||||
@@ -600,8 +629,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertEqual(types.registry.dict_types, set())
|
||||
|
||||
def test_unregister_dict_type_twice(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
t = {str: TempType}
|
||||
types.registry.register(t)
|
||||
self.assertNotEqual(types.registry.dict_types, set())
|
||||
@@ -611,8 +641,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertEqual(types.registry.dict_types, set())
|
||||
|
||||
def test_reregister(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
types.registry.register(TempType)
|
||||
v = types.registry.lookup('TempType')
|
||||
self.assertIs(v, TempType)
|
||||
@@ -621,8 +652,9 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertIs(after, TempType)
|
||||
|
||||
def test_reregister_and_add_attr(self):
|
||||
class TempType(object):
|
||||
class TempType:
|
||||
pass
|
||||
|
||||
types.registry.register(TempType)
|
||||
attrs = types.list_attributes(TempType)
|
||||
self.assertEqual(attrs, [])
|
||||
@@ -634,6 +666,7 @@ class TestTypes(unittest.TestCase):
|
||||
def test_dynamicbase_add_attributes(self):
|
||||
class TempType(types.DynamicBase):
|
||||
pass
|
||||
|
||||
types.registry.register(TempType)
|
||||
attrs = types.list_attributes(TempType)
|
||||
self.assertEqual(attrs, [])
|
||||
@@ -644,6 +677,7 @@ class TestTypes(unittest.TestCase):
|
||||
def test_dynamicbase_add_attributes_second(self):
|
||||
class TempType(types.DynamicBase):
|
||||
pass
|
||||
|
||||
types.registry.register(TempType)
|
||||
attrs = types.list_attributes(TempType)
|
||||
self.assertEqual(attrs, [])
|
||||
|
||||
+69
-45
@@ -10,13 +10,8 @@ class TestUtils(unittest.TestCase):
|
||||
('2008-02-01', datetime.date(2008, 2, 1)),
|
||||
('2009-01-04', datetime.date(2009, 1, 4)),
|
||||
]
|
||||
ill_formatted_dates = [
|
||||
'24-12-2004'
|
||||
]
|
||||
out_of_range_dates = [
|
||||
'0000-00-00',
|
||||
'2012-02-30',
|
||||
]
|
||||
ill_formatted_dates = ['24-12-2004']
|
||||
out_of_range_dates = ['0000-00-00', '2012-02-30']
|
||||
for s, d in good_dates:
|
||||
assert utils.parse_isodate(s) == d
|
||||
for s in ill_formatted_dates + out_of_range_dates:
|
||||
@@ -26,18 +21,32 @@ class TestUtils(unittest.TestCase):
|
||||
good_times = [
|
||||
('12:03:54', datetime.time(12, 3, 54)),
|
||||
('23:59:59.000004', datetime.time(23, 59, 59, 4)),
|
||||
('01:02:03+00:00', datetime.time(
|
||||
1, 2, 3, 0, datetime.timezone.utc)),
|
||||
('01:02:03+23:59', datetime.time(
|
||||
1, 2, 3, 0,
|
||||
datetime.timezone(datetime.timedelta(minutes=1439)))),
|
||||
('01:02:03-23:59', datetime.time(
|
||||
1, 2, 3, 0,
|
||||
datetime.timezone(datetime.timedelta(minutes=-1439)))),
|
||||
]
|
||||
ill_formatted_times = [
|
||||
'24-12-2004'
|
||||
(
|
||||
'01:02:03+00:00',
|
||||
datetime.time(1, 2, 3, 0, datetime.timezone.utc),
|
||||
),
|
||||
(
|
||||
'01:02:03+23:59',
|
||||
datetime.time(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
datetime.timezone(datetime.timedelta(minutes=1439)),
|
||||
),
|
||||
),
|
||||
(
|
||||
'01:02:03-23:59',
|
||||
datetime.time(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
datetime.timezone(datetime.timedelta(minutes=-1439)),
|
||||
),
|
||||
),
|
||||
]
|
||||
ill_formatted_times = ['24-12-2004']
|
||||
out_of_range_times = [
|
||||
'32:12:00',
|
||||
'00:54:60',
|
||||
@@ -51,30 +60,46 @@ class TestUtils(unittest.TestCase):
|
||||
|
||||
def test_parse_isodatetime(self):
|
||||
good_datetimes = [
|
||||
('2008-02-12T12:03:54',
|
||||
datetime.datetime(2008, 2, 12, 12, 3, 54)),
|
||||
('2012-05-14T23:59:59.000004',
|
||||
datetime.datetime(2012, 5, 14, 23, 59, 59, 4)),
|
||||
('1856-07-10T01:02:03+00:00',
|
||||
datetime.datetime(1856, 7, 10, 1, 2, 3, 0,
|
||||
datetime.timezone.utc)),
|
||||
('1856-07-10T01:02:03+23:59',
|
||||
datetime.datetime(
|
||||
1856, 7, 10, 1, 2, 3, 0,
|
||||
datetime.timezone(datetime.timedelta(minutes=1439)))),
|
||||
('1856-07-10T01:02:03-23:59',
|
||||
datetime.datetime(
|
||||
1856, 7, 10, 1, 2, 3, 0,
|
||||
datetime.timezone(datetime.timedelta(minutes=-1439)))),
|
||||
]
|
||||
ill_formatted_datetimes = [
|
||||
'24-32-2004',
|
||||
'1856-07-10+33:00'
|
||||
]
|
||||
out_of_range_datetimes = [
|
||||
'2008-02-12T32:12:00',
|
||||
'2012-13-12T00:54:60',
|
||||
('2008-02-12T12:03:54', datetime.datetime(2008, 2, 12, 12, 3, 54)),
|
||||
(
|
||||
'2012-05-14T23:59:59.000004',
|
||||
datetime.datetime(2012, 5, 14, 23, 59, 59, 4),
|
||||
),
|
||||
(
|
||||
'1856-07-10T01:02:03+00:00',
|
||||
datetime.datetime(
|
||||
1856, 7, 10, 1, 2, 3, 0, datetime.timezone.utc
|
||||
),
|
||||
),
|
||||
(
|
||||
'1856-07-10T01:02:03+23:59',
|
||||
datetime.datetime(
|
||||
1856,
|
||||
7,
|
||||
10,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
datetime.timezone(datetime.timedelta(minutes=1439)),
|
||||
),
|
||||
),
|
||||
(
|
||||
'1856-07-10T01:02:03-23:59',
|
||||
datetime.datetime(
|
||||
1856,
|
||||
7,
|
||||
10,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
datetime.timezone(datetime.timedelta(minutes=-1439)),
|
||||
),
|
||||
),
|
||||
]
|
||||
ill_formatted_datetimes = ['24-32-2004', '1856-07-10+33:00']
|
||||
out_of_range_datetimes = ['2008-02-12T32:12:00', '2012-13-12T00:54:60']
|
||||
for s, t in good_datetimes:
|
||||
assert utils.parse_isodatetime(s) == t
|
||||
for s in ill_formatted_datetimes + out_of_range_datetimes:
|
||||
@@ -83,20 +108,19 @@ class TestUtils(unittest.TestCase):
|
||||
def test_validator_with_valid_code(self):
|
||||
valid_code = 404
|
||||
self.assertTrue(
|
||||
utils.is_valid_code(valid_code),
|
||||
"Valid status code not detected"
|
||||
utils.is_valid_code(valid_code), "Valid status code not detected"
|
||||
)
|
||||
|
||||
def test_validator_with_invalid_int_code(self):
|
||||
invalid_int_code = 648
|
||||
self.assertFalse(
|
||||
utils.is_valid_code(invalid_int_code),
|
||||
"Invalid status code not detected"
|
||||
"Invalid status code not detected",
|
||||
)
|
||||
|
||||
def test_validator_with_invalid_str_code(self):
|
||||
invalid_str_code = '404'
|
||||
self.assertFalse(
|
||||
utils.is_valid_code(invalid_str_code),
|
||||
"Invalid status code not detected"
|
||||
"Invalid status code not detected",
|
||||
)
|
||||
|
||||
+107
-82
@@ -25,7 +25,7 @@ bytes = bytes
|
||||
text = str
|
||||
|
||||
|
||||
class ArrayType(object):
|
||||
class ArrayType:
|
||||
def __init__(self, item_type):
|
||||
if iscomplex(item_type):
|
||||
self._item_type = weakref.ref(item_type)
|
||||
@@ -36,8 +36,9 @@ class ArrayType(object):
|
||||
return hash(self.item_type)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, ArrayType) \
|
||||
and self.item_type == other.item_type
|
||||
return (
|
||||
isinstance(other, ArrayType) and self.item_type == other.item_type
|
||||
)
|
||||
|
||||
def sample(self):
|
||||
return [getattr(self.item_type, 'sample', self.item_type)()]
|
||||
@@ -53,16 +54,13 @@ class ArrayType(object):
|
||||
if value is None:
|
||||
return
|
||||
if not isinstance(value, list):
|
||||
raise ValueError("Wrong type. Expected '[%s]', got '%s'" % (
|
||||
self.item_type, type(value)
|
||||
))
|
||||
return [
|
||||
validate_value(self.item_type, item)
|
||||
for item in value
|
||||
]
|
||||
raise ValueError(
|
||||
f"Wrong type. Expected '[{self.item_type}]', got '{type(value)}'"
|
||||
)
|
||||
return [validate_value(self.item_type, item) for item in value]
|
||||
|
||||
|
||||
class DictType(object):
|
||||
class DictType:
|
||||
def __init__(self, key_type, value_type):
|
||||
if key_type not in pod_types:
|
||||
raise ValueError("Dictionaries key can only be a pod type")
|
||||
@@ -89,18 +87,18 @@ class DictType(object):
|
||||
|
||||
def validate(self, value):
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("Wrong type. Expected '{%s: %s}', got '%s'" % (
|
||||
self.key_type, self.value_type, type(value)
|
||||
))
|
||||
return dict((
|
||||
(
|
||||
validate_value(self.key_type, key),
|
||||
validate_value(self.value_type, v)
|
||||
) for key, v in value.items()
|
||||
))
|
||||
raise ValueError(
|
||||
f"Wrong type. Expected '{{{self.key_type}: {self.value_type}}}', got '{type(value)}'"
|
||||
)
|
||||
return {
|
||||
validate_value(self.key_type, key): validate_value(
|
||||
self.value_type, v
|
||||
)
|
||||
for key, v in value.items()
|
||||
}
|
||||
|
||||
|
||||
class UserType(object):
|
||||
class UserType:
|
||||
basetype = None
|
||||
name = None
|
||||
|
||||
@@ -122,6 +120,7 @@ class BinaryType(UserType):
|
||||
"""
|
||||
A user type that use base64 strings to carry binary data.
|
||||
"""
|
||||
|
||||
basetype = bytes
|
||||
name = 'binary'
|
||||
|
||||
@@ -152,6 +151,7 @@ class IntegerType(UserType):
|
||||
Price = IntegerType(minimum=1)
|
||||
|
||||
"""
|
||||
|
||||
basetype = int
|
||||
name = "integer"
|
||||
|
||||
@@ -165,11 +165,11 @@ class IntegerType(UserType):
|
||||
|
||||
def validate(self, value):
|
||||
if self.minimum is not None and value < self.minimum:
|
||||
error = 'Value should be greater or equal to %s' % self.minimum
|
||||
error = f'Value should be greater or equal to {self.minimum}'
|
||||
raise ValueError(error)
|
||||
|
||||
if self.maximum is not None and value > self.maximum:
|
||||
error = 'Value should be lower or equal to %s' % self.maximum
|
||||
error = f'Value should be lower or equal to {self.maximum}'
|
||||
raise ValueError(error)
|
||||
|
||||
return value
|
||||
@@ -188,6 +188,7 @@ class StringType(UserType):
|
||||
Name = StringType(min_length=1, pattern='^[a-zA-Z ]*$')
|
||||
|
||||
"""
|
||||
|
||||
basetype = str
|
||||
name = "string"
|
||||
|
||||
@@ -205,17 +206,15 @@ class StringType(UserType):
|
||||
raise ValueError(error)
|
||||
|
||||
if self.min_length is not None and len(value) < self.min_length:
|
||||
error = 'Value should have a minimum character requirement of %s' \
|
||||
% self.min_length
|
||||
error = f'Value should have a minimum character requirement of {self.min_length}'
|
||||
raise ValueError(error)
|
||||
|
||||
if self.max_length is not None and len(value) > self.max_length:
|
||||
error = 'Value should have a maximum character requirement of %s' \
|
||||
% self.max_length
|
||||
error = f'Value should have a maximum character requirement of {self.max_length}'
|
||||
raise ValueError(error)
|
||||
|
||||
if self.pattern is not None and not self.pattern.search(value):
|
||||
error = 'Value should match the pattern %s' % self.pattern.pattern
|
||||
error = f'Value should match the pattern {self.pattern.pattern}'
|
||||
raise ValueError(error)
|
||||
|
||||
return value
|
||||
@@ -225,6 +224,7 @@ class IPv4AddressType(UserType):
|
||||
"""
|
||||
A simple IPv4 type.
|
||||
"""
|
||||
|
||||
basetype = str
|
||||
name = "ipv4address"
|
||||
|
||||
@@ -245,6 +245,7 @@ class IPv6AddressType(UserType):
|
||||
|
||||
This type represents IPv6 addresses in the short format.
|
||||
"""
|
||||
|
||||
basetype = str
|
||||
name = "ipv6address"
|
||||
|
||||
@@ -267,6 +268,7 @@ class UuidType(UserType):
|
||||
having dashes. For example, '6a0a707c-45ef-4758-b533-e55adddba8ce'
|
||||
and '6a0a707c45ef4758b533e55adddba8ce' are distinguished as valid.
|
||||
"""
|
||||
|
||||
basetype = str
|
||||
name = "uuid"
|
||||
|
||||
@@ -294,18 +296,22 @@ class Enum(UserType):
|
||||
Specie = Enum(str, 'cat', 'dog')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, basetype, *values, **kw):
|
||||
self.basetype = basetype
|
||||
self.values = set(values)
|
||||
name = kw.pop('name', None)
|
||||
if name is None:
|
||||
name = "Enum(%s)" % ', '.join((str(v) for v in values))
|
||||
name = "Enum({})".format(', '.join(str(v) for v in values))
|
||||
self.name = name
|
||||
|
||||
def validate(self, value):
|
||||
if value not in self.values:
|
||||
raise ValueError("Value should be one of: %s" %
|
||||
', '.join(map(str, self.values)))
|
||||
raise ValueError(
|
||||
"Value should be one of: {}".format(
|
||||
', '.join(map(str, self.values))
|
||||
)
|
||||
)
|
||||
return value
|
||||
|
||||
def tobasetype(self, value):
|
||||
@@ -315,11 +321,13 @@ class Enum(UserType):
|
||||
return value
|
||||
|
||||
|
||||
class UnsetType(object):
|
||||
class UnsetType:
|
||||
if sys.version < '3':
|
||||
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
else:
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
@@ -344,8 +352,9 @@ _promotable_types = (int, str, bytes)
|
||||
|
||||
|
||||
def iscomplex(datatype):
|
||||
return inspect.isclass(datatype) \
|
||||
and '_wsme_attributes' in datatype.__dict__
|
||||
return (
|
||||
inspect.isclass(datatype) and '_wsme_attributes' in datatype.__dict__
|
||||
)
|
||||
|
||||
|
||||
def isarray(datatype):
|
||||
@@ -395,10 +404,7 @@ def validate_value(datatype, value):
|
||||
value = value.encode()
|
||||
|
||||
if not isinstance(value, datatype):
|
||||
raise ValueError(
|
||||
"Wrong type. Expected '%s', got '%s'" % (
|
||||
datatype, v_type
|
||||
))
|
||||
raise ValueError(f"Wrong type. Expected '{datatype}', got '{v_type}'")
|
||||
return value
|
||||
|
||||
|
||||
@@ -417,8 +423,10 @@ class wsproperty(property):
|
||||
|
||||
aint = wsproperty(int, get_aint, set_aint, mandatory=True)
|
||||
"""
|
||||
def __init__(self, datatype, fget, fset=None,
|
||||
mandatory=False, doc=None, name=None):
|
||||
|
||||
def __init__(
|
||||
self, datatype, fget, fset=None, mandatory=False, doc=None, name=None
|
||||
):
|
||||
property.__init__(self, fget, fset)
|
||||
#: The property name in the parent python class
|
||||
self.key = None
|
||||
@@ -431,7 +439,7 @@ class wsproperty(property):
|
||||
self.mandatory = mandatory
|
||||
|
||||
|
||||
class wsattr(object):
|
||||
class wsattr:
|
||||
"""
|
||||
Complex type attribute definition.
|
||||
|
||||
@@ -450,8 +458,15 @@ class wsattr(object):
|
||||
mandatoryvalue = wsattr(int, mandatory=True)
|
||||
|
||||
"""
|
||||
def __init__(self, datatype, mandatory=False, name=None, default=Unset,
|
||||
readonly=False):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
datatype,
|
||||
mandatory=False,
|
||||
name=None,
|
||||
default=Unset,
|
||||
readonly=False,
|
||||
):
|
||||
#: The attribute name in the parent python class.
|
||||
#: Set by :func:`inspect_class`
|
||||
self.key = None # will be set by class inspection
|
||||
@@ -479,11 +494,7 @@ class wsattr(object):
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return getattr(
|
||||
self._get_dataholder(instance),
|
||||
self.key,
|
||||
self.default
|
||||
)
|
||||
return getattr(self._get_dataholder(instance), self.key, self.default)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
try:
|
||||
@@ -502,8 +513,9 @@ class wsattr(object):
|
||||
|
||||
def _get_datatype(self):
|
||||
if isinstance(self._datatype, tuple):
|
||||
self._datatype = \
|
||||
self.complextype().__registry__.resolve_type(self._datatype[0])
|
||||
self._datatype = self.complextype().__registry__.resolve_type(
|
||||
self._datatype[0]
|
||||
)
|
||||
if isinstance(self._datatype, weakref.ref):
|
||||
return self._datatype()
|
||||
if isinstance(self._datatype, list):
|
||||
@@ -546,7 +558,7 @@ def sort_attributes(class_, attributes):
|
||||
if not len(attributes):
|
||||
return
|
||||
|
||||
attrs = dict((a.key, a) for a in attributes)
|
||||
attrs = {a.key: a for a in attributes}
|
||||
|
||||
if hasattr(class_, '_wsme_attr_order'):
|
||||
names_order = class_._wsme_attr_order
|
||||
@@ -558,18 +570,19 @@ def sort_attributes(class_, attributes):
|
||||
for cls in inspect.getmro(class_):
|
||||
if cls is object:
|
||||
continue
|
||||
lines[len(lines):] = inspect.getsourcelines(cls)[0]
|
||||
lines[len(lines) :] = inspect.getsourcelines(cls)[0]
|
||||
for line in lines:
|
||||
line = line.strip().replace(" ", "")
|
||||
if '=' in line:
|
||||
aname = line[:line.index('=')]
|
||||
aname = line[: line.index('=')]
|
||||
if aname in names and aname not in names_order:
|
||||
names_order.append(aname)
|
||||
if len(names_order) < len(names):
|
||||
names_order.extend((
|
||||
name for name in names if name not in names_order))
|
||||
names_order.extend(
|
||||
name for name in names if name not in names_order
|
||||
)
|
||||
assert len(names_order) == len(names)
|
||||
except (TypeError, IOError):
|
||||
except (OSError, TypeError):
|
||||
names_order = list(names)
|
||||
names_order.sort()
|
||||
|
||||
@@ -589,8 +602,8 @@ def inspect_class(class_):
|
||||
attrdef = attr
|
||||
else:
|
||||
if attr not in native_types and (
|
||||
inspect.isclass(attr) or
|
||||
isinstance(attr, (list, dict))):
|
||||
inspect.isclass(attr) or isinstance(attr, (list, dict))
|
||||
):
|
||||
register_type(attr)
|
||||
attrdef = getattr(class_, '__wsattrclass__', wsattr)(attr)
|
||||
|
||||
@@ -620,14 +633,14 @@ def make_dataholder(class_):
|
||||
# things if one of the slots is named 'attr'.
|
||||
slots = [attr.key for attr in class_._wsme_attributes]
|
||||
|
||||
class DataHolder(object):
|
||||
class DataHolder:
|
||||
__slots__ = slots
|
||||
|
||||
DataHolder.__name__ = class_.__name__ + 'DataHolder'
|
||||
return DataHolder
|
||||
|
||||
|
||||
class Registry(object):
|
||||
class Registry:
|
||||
def __init__(self):
|
||||
self._complex_types = []
|
||||
self.array_types = set()
|
||||
@@ -646,15 +659,19 @@ class Registry(object):
|
||||
Unless you want to control when the class inspection is done there
|
||||
is no need to call it.
|
||||
"""
|
||||
if class_ is None or \
|
||||
class_ in native_types or \
|
||||
isusertype(class_) or iscomplex(class_) or \
|
||||
isarray(class_) or isdict(class_):
|
||||
if (
|
||||
class_ is None
|
||||
or class_ in native_types
|
||||
or isusertype(class_)
|
||||
or iscomplex(class_)
|
||||
or isarray(class_)
|
||||
or isdict(class_)
|
||||
):
|
||||
return class_
|
||||
|
||||
if isinstance(class_, list):
|
||||
if len(class_) != 1:
|
||||
raise ValueError("Cannot register type %s" % repr(class_))
|
||||
raise ValueError(f"Cannot register type {repr(class_)}")
|
||||
dt = ArrayType(class_[0])
|
||||
self.register(dt.item_type)
|
||||
self.array_types.add(dt)
|
||||
@@ -662,7 +679,7 @@ class Registry(object):
|
||||
|
||||
if isinstance(class_, dict):
|
||||
if len(class_) != 1:
|
||||
raise ValueError("Cannot register type %s" % repr(class_))
|
||||
raise ValueError(f"Cannot register type {repr(class_)}")
|
||||
dt = DictType(*list(class_.items())[0])
|
||||
self.register(dt.value_type)
|
||||
self.dict_types.add(dt)
|
||||
@@ -677,14 +694,12 @@ class Registry(object):
|
||||
return class_
|
||||
|
||||
def reregister(self, class_):
|
||||
"""Register a type which may already have been registered.
|
||||
"""
|
||||
"""Register a type which may already have been registered."""
|
||||
self._unregister(class_)
|
||||
return self.register(class_)
|
||||
|
||||
def _unregister(self, class_):
|
||||
"""Remove a previously registered type.
|
||||
"""
|
||||
"""Remove a previously registered type."""
|
||||
# Clear the existing attribute reference so it is rebuilt if
|
||||
# the class is registered again later.
|
||||
if hasattr(class_, '_wsme_attributes'):
|
||||
@@ -699,27 +714,30 @@ class Registry(object):
|
||||
pass
|
||||
elif isinstance(class_, dict):
|
||||
key_type, value_type = list(class_.items())[0]
|
||||
self.dict_types = set(
|
||||
dt for dt in self.dict_types
|
||||
self.dict_types = {
|
||||
dt
|
||||
for dt in self.dict_types
|
||||
if (dt.key_type, dt.value_type) != (key_type, value_type)
|
||||
)
|
||||
}
|
||||
# We can't use remove() here because the items in
|
||||
# _complex_types are weakref objects pointing to the classes,
|
||||
# so we can't compare with them directly.
|
||||
self._complex_types = [
|
||||
ct for ct in self._complex_types
|
||||
if ct() is not class_
|
||||
ct for ct in self._complex_types if ct() is not class_
|
||||
]
|
||||
|
||||
def lookup(self, typename):
|
||||
log.debug('Lookup %s' % typename)
|
||||
log.debug(f'Lookup {typename}')
|
||||
modname = None
|
||||
if '.' in typename:
|
||||
modname, typename = typename.rsplit('.', 1)
|
||||
for ct in self._complex_types:
|
||||
ct = ct()
|
||||
if ct is not None and typename == ct.__name__ and (
|
||||
modname is None or modname == ct.__module__):
|
||||
if (
|
||||
ct is not None
|
||||
and typename == ct.__name__
|
||||
and (modname is None or modname == ct.__module__)
|
||||
):
|
||||
return ct
|
||||
|
||||
def resolve_type(self, type_):
|
||||
@@ -734,8 +752,7 @@ class Registry(object):
|
||||
self.array_types.add(type_)
|
||||
elif isinstance(type_, DictType):
|
||||
type_ = DictType(
|
||||
type_.key_type,
|
||||
self.resolve_type(type_.value_type)
|
||||
type_.key_type, self.resolve_type(type_.value_type)
|
||||
)
|
||||
self.dict_types.add(type_)
|
||||
else:
|
||||
@@ -764,6 +781,7 @@ class BaseMeta(type):
|
||||
|
||||
class Base(metaclass=BaseMeta):
|
||||
"""Base type for complex types"""
|
||||
|
||||
def __init__(self, **kw):
|
||||
for key, value in kw.items():
|
||||
if hasattr(self, key):
|
||||
@@ -776,6 +794,7 @@ class File(Base):
|
||||
In the particular case of protocol accepting form encoded data as
|
||||
input, File can be loaded from a form file field.
|
||||
"""
|
||||
|
||||
#: The file name
|
||||
filename = wsattr(str)
|
||||
|
||||
@@ -794,8 +813,14 @@ class File(Base):
|
||||
#: File content
|
||||
content = wsproperty(binary, _get_content, _set_content)
|
||||
|
||||
def __init__(self, filename=None, file=None, content=None,
|
||||
contenttype=None, fieldstorage=None):
|
||||
def __init__(
|
||||
self,
|
||||
filename=None,
|
||||
file=None,
|
||||
content=None,
|
||||
contenttype=None,
|
||||
fieldstorage=None,
|
||||
):
|
||||
self.filename = filename
|
||||
self.contenttype = contenttype
|
||||
self._file = file
|
||||
|
||||
+25
-20
@@ -10,20 +10,24 @@ except ImportError:
|
||||
dateutil = None # noqa
|
||||
|
||||
date_re = r'(?P<year>-?\d{4,})-(?P<month>\d{2})-(?P<day>\d{2})'
|
||||
time_re = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})' + \
|
||||
r'(\.(?P<sec_frac>\d+))?'
|
||||
tz_re = r'((?P<tz_sign>[+-])(?P<tz_hour>\d{2}):(?P<tz_min>\d{2}))' + \
|
||||
r'|(?P<tz_z>Z)'
|
||||
time_re = (
|
||||
r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
|
||||
+ r'(\.(?P<sec_frac>\d+))?'
|
||||
)
|
||||
tz_re = (
|
||||
r'((?P<tz_sign>[+-])(?P<tz_hour>\d{2}):(?P<tz_min>\d{2}))'
|
||||
+ r'|(?P<tz_z>Z)'
|
||||
)
|
||||
|
||||
datetime_re = re.compile(
|
||||
'%sT%s(%s)?' % (date_re, time_re, tz_re))
|
||||
datetime_re = re.compile(f'{date_re}T{time_re}({tz_re})?')
|
||||
date_re = re.compile(date_re)
|
||||
time_re = re.compile('%s(%s)?' % (time_re, tz_re))
|
||||
time_re = re.compile(f'{time_re}({tz_re})?')
|
||||
|
||||
|
||||
if hasattr(builtins, '_'):
|
||||
_ = builtins._
|
||||
else:
|
||||
|
||||
def _(s):
|
||||
return s
|
||||
|
||||
@@ -31,20 +35,19 @@ else:
|
||||
def parse_isodate(value):
|
||||
m = date_re.match(value)
|
||||
if m is None:
|
||||
raise ValueError("'%s' is not a legal date value" % (value))
|
||||
raise ValueError(f"'{value}' is not a legal date value")
|
||||
try:
|
||||
return datetime.date(
|
||||
int(m.group('year')),
|
||||
int(m.group('month')),
|
||||
int(m.group('day')))
|
||||
int(m.group('year')), int(m.group('month')), int(m.group('day'))
|
||||
)
|
||||
except ValueError:
|
||||
raise ValueError("'%s' is a out-of-range date" % (value))
|
||||
raise ValueError(f"'{value}' is a out-of-range date")
|
||||
|
||||
|
||||
def parse_isotime(value):
|
||||
m = time_re.match(value)
|
||||
if m is None:
|
||||
raise ValueError("'%s' is not a legal time value" % (value))
|
||||
raise ValueError(f"'{value}' is not a legal time value")
|
||||
try:
|
||||
ms = 0
|
||||
if m.group('sec_frac') is not None:
|
||||
@@ -57,9 +60,10 @@ def parse_isotime(value):
|
||||
int(m.group('min')),
|
||||
int(m.group('sec')),
|
||||
ms,
|
||||
tz)
|
||||
tz,
|
||||
)
|
||||
except ValueError:
|
||||
raise ValueError("'%s' is a out-of-range time" % (value))
|
||||
raise ValueError(f"'{value}' is a out-of-range time")
|
||||
|
||||
|
||||
def parse_isodatetime(value):
|
||||
@@ -67,7 +71,7 @@ def parse_isodatetime(value):
|
||||
return dateutil.parser.parse(value)
|
||||
m = datetime_re.match(value)
|
||||
if m is None:
|
||||
raise ValueError("'%s' is not a legal datetime value" % (value))
|
||||
raise ValueError(f"'{value}' is not a legal datetime value")
|
||||
try:
|
||||
ms = 0
|
||||
if m.group('sec_frac') is not None:
|
||||
@@ -83,9 +87,10 @@ def parse_isodatetime(value):
|
||||
int(m.group('min')),
|
||||
int(m.group('sec')),
|
||||
ms,
|
||||
tz)
|
||||
tz,
|
||||
)
|
||||
except ValueError:
|
||||
raise ValueError("'%s' is a out-of-range datetime" % (value))
|
||||
raise ValueError(f"'{value}' is a out-of-range datetime")
|
||||
|
||||
|
||||
def _parse_tzparts(parts):
|
||||
@@ -94,7 +99,7 @@ def _parse_tzparts(parts):
|
||||
if 'tz_min' not in parts or not parts['tz_min']:
|
||||
return None
|
||||
|
||||
tz_minute_offset = (int(parts['tz_hour']) * 60 + int(parts['tz_min']))
|
||||
tz_minute_offset = int(parts['tz_hour']) * 60 + int(parts['tz_min'])
|
||||
tz_multiplier = -1 if parts['tz_sign'] == '-' else 1
|
||||
minutes = tz_multiplier * tz_minute_offset
|
||||
|
||||
@@ -109,5 +114,5 @@ def is_valid_code(code_value):
|
||||
|
||||
|
||||
def is_client_error(code):
|
||||
""" Checks client error code (RFC 2616)."""
|
||||
"""Checks client error code (RFC 2616)."""
|
||||
return 400 <= code < 500
|
||||
|
||||
+12
-9
@@ -33,8 +33,10 @@ def get_dataformat():
|
||||
if req_dataformat in TYPES:
|
||||
return TYPES[req_dataformat]
|
||||
|
||||
log.info('Could not determine what format is wanted by the caller, '
|
||||
'falling back to JSON')
|
||||
log.info(
|
||||
'Could not determine what format is wanted by the caller, '
|
||||
'falling back to JSON'
|
||||
)
|
||||
return wsme.rest.json
|
||||
|
||||
|
||||
@@ -53,10 +55,13 @@ def signature(*args, **kw):
|
||||
if ismethod:
|
||||
self, args = args[0], args[1:]
|
||||
args, kwargs = wsme.rest.args.get_args(
|
||||
funcdef, args, kwargs,
|
||||
flask.request.args, flask.request.form,
|
||||
funcdef,
|
||||
args,
|
||||
kwargs,
|
||||
flask.request.args,
|
||||
flask.request.form,
|
||||
flask.request.data,
|
||||
flask.request.mimetype
|
||||
flask.request.mimetype,
|
||||
)
|
||||
|
||||
if funcdef.pass_request:
|
||||
@@ -76,10 +81,7 @@ def signature(*args, **kw):
|
||||
result = result.obj
|
||||
|
||||
res = flask.make_response(
|
||||
dataformat.encode_result(
|
||||
result,
|
||||
funcdef.return_type
|
||||
)
|
||||
dataformat.encode_result(result, funcdef.return_type)
|
||||
)
|
||||
res.mimetype = dataformat.content_type
|
||||
res.status_code = status_code
|
||||
@@ -103,4 +105,5 @@ def signature(*args, **kw):
|
||||
|
||||
wrapper.wsme_func = f
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
+20
-24
@@ -11,7 +11,7 @@ import wsme.rest.xml
|
||||
from wsme.utils import is_valid_code
|
||||
|
||||
|
||||
class JSonRenderer(object):
|
||||
class JSonRenderer:
|
||||
@staticmethod
|
||||
def __init__(path, extra_vars):
|
||||
pass
|
||||
@@ -21,12 +21,11 @@ class JSonRenderer(object):
|
||||
if 'faultcode' in namespace:
|
||||
return wsme.rest.json.encode_error(None, namespace)
|
||||
return wsme.rest.json.encode_result(
|
||||
namespace['result'],
|
||||
namespace['datatype']
|
||||
namespace['result'], namespace['datatype']
|
||||
)
|
||||
|
||||
|
||||
class XMLRenderer(object):
|
||||
class XMLRenderer:
|
||||
@staticmethod
|
||||
def __init__(path, extra_vars):
|
||||
pass
|
||||
@@ -36,8 +35,7 @@ class XMLRenderer(object):
|
||||
if 'faultcode' in namespace:
|
||||
return wsme.rest.xml.encode_error(None, namespace)
|
||||
return wsme.rest.xml.encode_result(
|
||||
namespace['result'],
|
||||
namespace['datatype']
|
||||
namespace['result'], namespace['datatype']
|
||||
)
|
||||
|
||||
|
||||
@@ -45,18 +43,13 @@ pecan.templating._builtin_renderers['wsmejson'] = JSonRenderer
|
||||
pecan.templating._builtin_renderers['wsmexml'] = XMLRenderer
|
||||
|
||||
pecan_json_decorate = pecan.expose(
|
||||
template='wsmejson:',
|
||||
content_type='application/json',
|
||||
generic=False)
|
||||
template='wsmejson:', content_type='application/json', generic=False
|
||||
)
|
||||
pecan_xml_decorate = pecan.expose(
|
||||
template='wsmexml:',
|
||||
content_type='application/xml',
|
||||
generic=False
|
||||
template='wsmexml:', content_type='application/xml', generic=False
|
||||
)
|
||||
pecan_text_xml_decorate = pecan.expose(
|
||||
template='wsmexml:',
|
||||
content_type='text/xml',
|
||||
generic=False
|
||||
template='wsmexml:', content_type='text/xml', generic=False
|
||||
)
|
||||
|
||||
|
||||
@@ -74,8 +67,13 @@ def wsexpose(*args, **kwargs):
|
||||
|
||||
try:
|
||||
args, kwargs = wsme.rest.args.get_args(
|
||||
funcdef, args, kwargs, pecan.request.params, None,
|
||||
pecan.request.body, pecan.request.content_type
|
||||
funcdef,
|
||||
args,
|
||||
kwargs,
|
||||
pecan.request.params,
|
||||
None,
|
||||
pecan.request.body,
|
||||
pecan.request.content_type,
|
||||
)
|
||||
if funcdef.pass_request:
|
||||
kwargs[funcdef.pass_request] = pecan.request
|
||||
@@ -92,8 +90,9 @@ def wsexpose(*args, **kwargs):
|
||||
# content-length is 0
|
||||
if result.status_code == 204:
|
||||
return_type = None
|
||||
elif not isinstance(result.return_type,
|
||||
wsme.types.UnsetType):
|
||||
elif not isinstance(
|
||||
result.return_type, wsme.types.UnsetType
|
||||
):
|
||||
return_type = result.return_type
|
||||
|
||||
result = result.obj
|
||||
@@ -105,7 +104,7 @@ def wsexpose(*args, **kwargs):
|
||||
orig_code = getattr(orig_exception, 'code', None)
|
||||
data = wsme.api.format_exception(
|
||||
exception_info,
|
||||
pecan.conf.get('wsme', {}).get('debug', False)
|
||||
pecan.conf.get('wsme', {}).get('debug', False),
|
||||
)
|
||||
finally:
|
||||
del exception_info
|
||||
@@ -122,10 +121,7 @@ def wsexpose(*args, **kwargs):
|
||||
pecan.response.content_type = None
|
||||
return ''
|
||||
|
||||
return dict(
|
||||
datatype=return_type,
|
||||
result=result
|
||||
)
|
||||
return {'datatype': return_type, 'result': result}
|
||||
|
||||
if 'xml' in funcdef.rest_content_types:
|
||||
pecan_xml_decorate(callfunction)
|
||||
|
||||
+116
-115
@@ -29,10 +29,9 @@ def datatypename(datatype):
|
||||
if isinstance(datatype, wsme.types.UserType):
|
||||
return datatype.name
|
||||
if isinstance(datatype, wsme.types.DictType):
|
||||
return 'dict(%s: %s)' % (datatypename(datatype.key_type),
|
||||
datatypename(datatype.value_type))
|
||||
return f'dict({datatypename(datatype.key_type)}: {datatypename(datatype.value_type)})'
|
||||
if isinstance(datatype, wsme.types.ArrayType):
|
||||
return 'list(%s)' % datatypename(datatype.item_type)
|
||||
return f'list({datatypename(datatype.item_type)})'
|
||||
return datatype.__name__
|
||||
|
||||
|
||||
@@ -65,7 +64,7 @@ def get_protocols(names):
|
||||
return protocols
|
||||
|
||||
|
||||
class SampleType(object):
|
||||
class SampleType:
|
||||
"""A Sample Type"""
|
||||
|
||||
#: A Int
|
||||
@@ -123,8 +122,7 @@ def scan_services(service, path=[]):
|
||||
continue
|
||||
if len(path) > wsme.rest.APIPATH_MAXLEN:
|
||||
raise ValueError("Path is too long: " + str(path))
|
||||
for value in scan_services(a, path + [name]):
|
||||
yield value
|
||||
yield from scan_services(a, path + [name])
|
||||
if has_functions:
|
||||
yield service, path
|
||||
|
||||
@@ -144,22 +142,25 @@ class TypeDirective(PyClasslike):
|
||||
return _('%s (webservice type)') % name_cls[0]
|
||||
|
||||
def add_target_and_index(self, name_cls, sig, signode):
|
||||
ret = super(TypeDirective, self).add_target_and_index(
|
||||
name_cls, sig, signode
|
||||
)
|
||||
ret = super().add_target_and_index(name_cls, sig, signode)
|
||||
name = name_cls[0]
|
||||
types = self.env.domaindata['wsme']['types']
|
||||
if name in types:
|
||||
self.state_machine.reporter.warning(
|
||||
'duplicate type description of %s ' % name)
|
||||
f'duplicate type description of {name} '
|
||||
)
|
||||
types[name] = self.env.docname
|
||||
return ret
|
||||
|
||||
|
||||
class AttributeDirective(PyAttribute):
|
||||
doc_field_types = [
|
||||
Field('datatype', label=_('Type'), has_arg=False,
|
||||
names=('type', 'datatype'))
|
||||
Field(
|
||||
'datatype',
|
||||
label=_('Type'),
|
||||
has_arg=False,
|
||||
names=('type', 'datatype'),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -174,10 +175,12 @@ def check_samples_slot(value):
|
||||
return 'after-docstring'
|
||||
val = directives.choice(
|
||||
value,
|
||||
('none', # do not include
|
||||
'before-docstring', # show samples then docstring
|
||||
'after-docstring', # show docstring then samples
|
||||
))
|
||||
(
|
||||
'none', # do not include
|
||||
'before-docstring', # show samples then docstring
|
||||
'after-docstring', # show docstring then samples
|
||||
),
|
||||
)
|
||||
return val
|
||||
|
||||
|
||||
@@ -191,9 +194,11 @@ class TypeDocumenter(autodoc.ClassDocumenter):
|
||||
|
||||
option_spec = dict(
|
||||
autodoc.ClassDocumenter.option_spec,
|
||||
**{'protocols': lambda line: [v.strip() for v in line.split(',')],
|
||||
'samples-slot': check_samples_slot,
|
||||
})
|
||||
**{
|
||||
'protocols': lambda line: [v.strip() for v in line.split(',')],
|
||||
'samples-slot': check_samples_slot,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def can_document_member(member, membername, isattr, parent):
|
||||
@@ -205,19 +210,20 @@ class TypeDocumenter(autodoc.ClassDocumenter):
|
||||
return self.object.__name__
|
||||
|
||||
def format_signature(self):
|
||||
return u''
|
||||
return ''
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
super(TypeDocumenter, self).add_directive_header(sig)
|
||||
super().add_directive_header(sig)
|
||||
# remove the :module: option that was added by ClassDocumenter
|
||||
result_len = len(self.directive.result)
|
||||
for index, item in zip(reversed(range(result_len)),
|
||||
reversed(self.directive.result)):
|
||||
for index, item in zip(
|
||||
reversed(range(result_len)), reversed(self.directive.result)
|
||||
):
|
||||
if ':module:' in item:
|
||||
self.directive.result.pop(index)
|
||||
|
||||
def import_object(self):
|
||||
if super(TypeDocumenter, self).import_object():
|
||||
if super().import_object():
|
||||
wsme.types.register_type(self.object)
|
||||
return True
|
||||
else:
|
||||
@@ -237,27 +243,23 @@ class TypeDocumenter(autodoc.ClassDocumenter):
|
||||
content = []
|
||||
if protocols:
|
||||
sample_obj = make_sample_object(self.object)
|
||||
content.extend([
|
||||
_(u'Data samples:'),
|
||||
u'',
|
||||
u'.. cssclass:: toggle',
|
||||
u''
|
||||
])
|
||||
content.extend(
|
||||
[_('Data samples:'), '', '.. cssclass:: toggle', '']
|
||||
)
|
||||
for name, protocol in protocols:
|
||||
language, sample = protocol.encode_sample_value(
|
||||
self.object, sample_obj, format=True)
|
||||
content.extend([
|
||||
name,
|
||||
u' .. code-block:: ' + language,
|
||||
u'',
|
||||
])
|
||||
self.object, sample_obj, format=True
|
||||
)
|
||||
content.extend(
|
||||
u' ' * 8 + line
|
||||
for line in str(sample).split('\n'))
|
||||
[name, ' .. code-block:: ' + language, '']
|
||||
)
|
||||
content.extend(
|
||||
' ' * 8 + line for line in str(sample).split('\n')
|
||||
)
|
||||
for line in content:
|
||||
self.add_line(line, u'<wsmeext.sphinxext')
|
||||
self.add_line(line, '<wsmeext.sphinxext')
|
||||
|
||||
self.add_line(u'', '<wsmeext.sphinxext>')
|
||||
self.add_line('', '<wsmeext.sphinxext>')
|
||||
|
||||
if samples_slot == 'after-docstring':
|
||||
add_docstring()
|
||||
@@ -278,34 +280,32 @@ class AttributeDocumenter(autodoc.AttributeDocumenter):
|
||||
return isinstance(parent, TypeDocumenter)
|
||||
|
||||
def import_object(self):
|
||||
success = super(AttributeDocumenter, self).import_object()
|
||||
success = super().import_object()
|
||||
if success:
|
||||
self.datatype = self.object.datatype
|
||||
return success
|
||||
|
||||
def add_content(self, more_content):
|
||||
self.add_line(
|
||||
u':type: %s' % datatypename(self.datatype),
|
||||
'<wsmeext.sphinxext>'
|
||||
f':type: {datatypename(self.datatype)}', '<wsmeext.sphinxext>'
|
||||
)
|
||||
self.add_line(u'', '<wsmeext.sphinxext>')
|
||||
super(AttributeDocumenter, self).add_content(more_content)
|
||||
self.add_line('', '<wsmeext.sphinxext>')
|
||||
super().add_content(more_content)
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
super(AttributeDocumenter, self).add_directive_header(sig)
|
||||
super().add_directive_header(sig)
|
||||
|
||||
|
||||
class RootDirective(Directive):
|
||||
"""
|
||||
This directive is to tell what class is the Webservice root
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'webpath': directives.unchanged
|
||||
}
|
||||
option_spec = {'webpath': directives.unchanged}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
@@ -351,16 +351,17 @@ class ServiceDocumenter(autodoc.ClassDocumenter):
|
||||
directivetype = 'service'
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
super(ServiceDocumenter, self).add_directive_header(sig)
|
||||
super().add_directive_header(sig)
|
||||
# remove the :module: option that was added by ClassDocumenter
|
||||
result_len = len(self.directive.result)
|
||||
for index, item in zip(reversed(range(result_len)),
|
||||
reversed(self.directive.result)):
|
||||
for index, item in zip(
|
||||
reversed(range(result_len)), reversed(self.directive.result)
|
||||
):
|
||||
if ':module:' in item:
|
||||
self.directive.result.pop(index)
|
||||
|
||||
def format_signature(self):
|
||||
return u''
|
||||
return ''
|
||||
|
||||
def format_name(self):
|
||||
path = find_service_path(self.env, self.object)
|
||||
@@ -396,28 +397,28 @@ def document_function(funcdef, docstrings=None, protocols=['restjson']):
|
||||
|
||||
for arg in funcdef.arguments:
|
||||
content = [
|
||||
u':type %s: :wsme:type:`%s`' % (
|
||||
arg.name, datatypename(arg.datatype))
|
||||
f':type {arg.name}: :wsme:type:`{datatypename(arg.datatype)}`'
|
||||
]
|
||||
if arg.name not in found_params:
|
||||
content.insert(0, u':param %s: ' % (arg.name))
|
||||
content.insert(0, f':param {arg.name}: ')
|
||||
pos = next_param_pos
|
||||
else:
|
||||
for si, docstring in enumerate(docstrings):
|
||||
for i, line in enumerate(docstring):
|
||||
m = field_re.match(line)
|
||||
if m and m.group('field') == 'param' \
|
||||
and m.group('name') == arg.name:
|
||||
if (
|
||||
m
|
||||
and m.group('field') == 'param'
|
||||
and m.group('name') == arg.name
|
||||
):
|
||||
pos = (si, i + 1)
|
||||
break
|
||||
docstring = docstrings[pos[0]]
|
||||
docstring[pos[1]:pos[1]] = content
|
||||
docstring[pos[1] : pos[1]] = content
|
||||
next_param_pos = (pos[0], pos[1] + len(content))
|
||||
|
||||
if funcdef.return_type:
|
||||
content = [
|
||||
u':rtype: %s' % datatypename(funcdef.return_type)
|
||||
]
|
||||
content = [f':rtype: {datatypename(funcdef.return_type)}']
|
||||
pos = None
|
||||
for si, docstring in enumerate(docstrings):
|
||||
for i, line in enumerate(docstring):
|
||||
@@ -428,7 +429,7 @@ def document_function(funcdef, docstrings=None, protocols=['restjson']):
|
||||
else:
|
||||
pos = next_param_pos
|
||||
docstring = docstrings[pos[0]]
|
||||
docstring[pos[1]:pos[1]] = content
|
||||
docstring[pos[1] : pos[1]] = content
|
||||
|
||||
codesamples = []
|
||||
|
||||
@@ -436,49 +437,47 @@ def document_function(funcdef, docstrings=None, protocols=['restjson']):
|
||||
params = []
|
||||
for arg in funcdef.arguments:
|
||||
wsme.types.register_type(arg.datatype)
|
||||
params.append((
|
||||
arg.name,
|
||||
arg.datatype,
|
||||
make_sample_object(arg.datatype)
|
||||
))
|
||||
codesamples.extend([
|
||||
u':%s:' % _(u'Parameters samples'),
|
||||
u' .. cssclass:: toggle',
|
||||
u''
|
||||
])
|
||||
params.append(
|
||||
(arg.name, arg.datatype, make_sample_object(arg.datatype))
|
||||
)
|
||||
codesamples.extend(
|
||||
[
|
||||
':{}:'.format(_('Parameters samples')),
|
||||
' .. cssclass:: toggle',
|
||||
'',
|
||||
]
|
||||
)
|
||||
for name, protocol in protocols:
|
||||
language, sample = protocol.encode_sample_params(
|
||||
params, format=True)
|
||||
codesamples.extend([
|
||||
u' ' * 4 + name,
|
||||
u' .. code-block:: ' + language,
|
||||
u'',
|
||||
])
|
||||
codesamples.extend((
|
||||
u' ' * 12 + line
|
||||
for line in str(sample).split('\n')
|
||||
))
|
||||
params, format=True
|
||||
)
|
||||
codesamples.extend(
|
||||
[' ' * 4 + name, ' .. code-block:: ' + language, '']
|
||||
)
|
||||
codesamples.extend(
|
||||
' ' * 12 + line for line in str(sample).split('\n')
|
||||
)
|
||||
|
||||
if funcdef.return_type:
|
||||
codesamples.extend([
|
||||
u':%s:' % _(u'Return samples'),
|
||||
u' .. cssclass:: toggle',
|
||||
u''
|
||||
])
|
||||
codesamples.extend(
|
||||
[
|
||||
':{}:'.format(_('Return samples')),
|
||||
' .. cssclass:: toggle',
|
||||
'',
|
||||
]
|
||||
)
|
||||
wsme.types.register_type(funcdef.return_type)
|
||||
sample_obj = make_sample_object(funcdef.return_type)
|
||||
for name, protocol in protocols:
|
||||
language, sample = protocol.encode_sample_result(
|
||||
funcdef.return_type, sample_obj, format=True)
|
||||
codesamples.extend([
|
||||
u' ' * 4 + name,
|
||||
u' .. code-block:: ' + language,
|
||||
u'',
|
||||
])
|
||||
codesamples.extend((
|
||||
u' ' * 12 + line
|
||||
for line in str(sample).split('\n')
|
||||
))
|
||||
funcdef.return_type, sample_obj, format=True
|
||||
)
|
||||
codesamples.extend(
|
||||
[' ' * 4 + name, ' .. code-block:: ' + language, '']
|
||||
)
|
||||
codesamples.extend(
|
||||
' ' * 12 + line for line in str(sample).split('\n')
|
||||
)
|
||||
|
||||
docstrings[0:0] = [codesamples]
|
||||
return docstrings
|
||||
@@ -492,16 +491,17 @@ class FunctionDocumenter(autodoc.MethodDocumenter):
|
||||
|
||||
option_spec = {
|
||||
'path': directives.unchanged,
|
||||
'method': directives.unchanged
|
||||
'method': directives.unchanged,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def can_document_member(member, membername, isattr, parent):
|
||||
return (isinstance(parent, ServiceDocumenter) and
|
||||
wsme.api.iswsmefunction(member))
|
||||
return isinstance(
|
||||
parent, ServiceDocumenter
|
||||
) and wsme.api.iswsmefunction(member)
|
||||
|
||||
def import_object(self):
|
||||
ret = super(FunctionDocumenter, self).import_object()
|
||||
ret = super().import_object()
|
||||
self.directivetype = 'function'
|
||||
self.wsme_fd = wsme.api.FunctionDefinition.get(self.object)
|
||||
self.retann = datatypename(self.wsme_fd.return_type)
|
||||
@@ -534,17 +534,18 @@ class FunctionDocumenter(autodoc.MethodDocumenter):
|
||||
return self._wsme_docstrings
|
||||
|
||||
def add_content(self, more_content):
|
||||
super(FunctionDocumenter, self).add_content(more_content)
|
||||
super().add_content(more_content)
|
||||
|
||||
def format_name(self):
|
||||
return self.wsme_fd.name
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
super(FunctionDocumenter, self).add_directive_header(sig)
|
||||
super().add_directive_header(sig)
|
||||
# remove the :module: option that was added by ClassDocumenter
|
||||
result_len = len(self.directive.result)
|
||||
for index, item in zip(reversed(range(result_len)),
|
||||
reversed(self.directive.result)):
|
||||
for index, item in zip(
|
||||
reversed(range(result_len)), reversed(self.directive.result)
|
||||
):
|
||||
if ':module:' in item:
|
||||
self.directive.result.pop(index)
|
||||
|
||||
@@ -555,23 +556,21 @@ class WSMEDomain(Domain):
|
||||
|
||||
object_types = {
|
||||
'type': ObjType(_('type'), 'type', 'obj'),
|
||||
'service': ObjType(_('service'), 'service', 'obj')
|
||||
'service': ObjType(_('service'), 'service', 'obj'),
|
||||
}
|
||||
|
||||
directives = {
|
||||
'type': TypeDirective,
|
||||
'attribute': AttributeDirective,
|
||||
'attribute': AttributeDirective,
|
||||
'service': ServiceDirective,
|
||||
'root': RootDirective,
|
||||
'function': FunctionDirective,
|
||||
}
|
||||
|
||||
roles = {
|
||||
'type': XRefRole()
|
||||
}
|
||||
roles = {'type': XRefRole()}
|
||||
|
||||
initial_data = {
|
||||
'types': {}, # fullname -> docname
|
||||
'types': {} # fullname -> docname
|
||||
}
|
||||
|
||||
def clear_doc(self, docname):
|
||||
@@ -581,13 +580,15 @@ class WSMEDomain(Domain):
|
||||
if value == docname:
|
||||
del self.data['types'][key]
|
||||
|
||||
def resolve_xref(self, env, fromdocname, builder,
|
||||
type, target, node, contnode):
|
||||
def resolve_xref(
|
||||
self, env, fromdocname, builder, type, target, node, contnode
|
||||
):
|
||||
if target not in self.data['types']:
|
||||
return None
|
||||
todocname = self.data['types'][target]
|
||||
return make_refnode(
|
||||
builder, fromdocname, todocname, target, contnode, target)
|
||||
builder, fromdocname, todocname, target, contnode, target
|
||||
)
|
||||
|
||||
|
||||
def setup(app):
|
||||
|
||||
Reference in New Issue
Block a user