Add pre-commit and ruff

Change-Id: Ide485356c1cb24510e9ea1c0aa8d84ac115916ff
Signed-off-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
This commit is contained in:
Takashi Kajinami
2026-07-30 10:36:09 +09:00
parent 5a9b0ba2f9
commit 06a68b527f
45 changed files with 1408 additions and 1232 deletions
+20
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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',
)
+1 -1
View File
@@ -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'
+3 -8
View File
@@ -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):
+5 -7
View File
@@ -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'],
)
+16
View File
@@ -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 -4
View File
@@ -1,6 +1,3 @@
import setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True
)
setuptools.setup(setup_requires=['pbr'], pbr=True)
+11 -13
View File
@@ -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']),
)
+7 -7
View File
@@ -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),
)
+2 -2
View File
@@ -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):
+15 -30
View File
@@ -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 dun révolutionnaire pour servir à "
u"lhistoire de la révolution de février",
author=Author(lastname=u"Proudhon")
name="Les Confessions dun révolutionnaire pour servir à "
"lhistoire 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 dun révolutionnaire pour servir à "
u"lhistoire de la révolution de février",
name="Les Confessions dun révolutionnaire pour servir à "
"lhistoire 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=[])]
+3 -4
View File
@@ -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)
+6 -12
View File
@@ -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::
+28 -59
View File
@@ -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
+1 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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'&gt;')
.replace(b'<', b'&lt;'))
f"error :\n{e}"
)
return html_body % {
'css': '',
'content': '<pre>{}</pre>'.format(
content.replace(b'>', b'&gt;').replace(b'<', b'&lt;')
),
}
+121 -98
View File
@@ -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
View File
@@ -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
View File
@@ -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
)
-2
View File
@@ -1,5 +1,3 @@
# encoding=utf8
import unittest
from wsme import WSRoot
+12 -21
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+7 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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):