From 000bb2ff0e56f6d06d374dbe46abe968631f03c8 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Fri, 11 Jun 2021 16:06:04 +1000 Subject: [PATCH] Fix compatibility to Sanic v21.3 Replace sanic-plugins-framework with sanic-plugin-toolkit Continue to slowly fix some tests --- .travis.yml | 9 +- README.rst | 17 ++-- doc/scaling.rst | 2 +- doc/swagger.rst | 2 +- examples/complex.py | 6 +- examples/todo.py | 6 +- examples/todo_blueprint.py | 2 +- package-lock.json | 2 +- requirements/install.pip | 6 +- requirements/test.pip | 1 + sanic_restplus/__about__.py | 2 +- sanic_restplus/api.py | 166 +++++++++++++++++++++--------------- sanic_restplus/restplus.py | 32 +++---- sanic_restplus/swagger.py | 4 +- setup.py | 9 +- test1.py | 60 ------------- tests/conftest.py | 13 +-- tests/test_api.py | 14 +-- tests/test_errors.py | 2 +- tests/test_fields.py | 6 +- tests/test_swagger.py | 4 +- 21 files changed, 166 insertions(+), 199 deletions(-) delete mode 100644 test1.py diff --git a/.travis.yml b/.travis.yml index f90314ab..755d344a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,18 @@ language: python python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 - 3.7 + - 3.8 + - 3.9 - pypy - pypy3 matrix: include: - - python: 3.8-dev + - python: 3.8 dist: xenial allow_failures: - python: pypy3 - - python: 3.8-dev install: - pip install .[dev] diff --git a/README.rst b/README.rst index 763bc1db..eaaacad4 100644 --- a/README.rst +++ b/README.rst @@ -12,18 +12,21 @@ and expose its documentation properly using `Swagger`_. Compatibility ============= -Sanic-RestPlus requires Python 3.5+. -Sanic-RestPlus works with Sanic v18.12+ +* Sanic-RestPlus requires Python 3.7+. +* Sanic-RestPlus works with Sanic v21.3+ + Important Compatibility Notice ------------------------------ +Sanic-RestPlus version 0.6.0 was reworked and now requires Sanic v21.3 or later. + Sanic-RestPlus version 0.4.1 (and previous versions) **does not work** on Sanic 19.12+, see this bug here: https://github.com/ashleysommer/sanicpluginsframework/issues/15 -Please use latest Sanic-Restplus (v0.5.3 or greater) if you need to deploy on Sanic v19.12+. +Please use Sanic-Restplus v0.5.x if you need to deploy on Sanic v19.12 or v20.12 -If you are using the new Sanic v20.12LTS, please use Sanic-RestPlus v0.5.5 or greater. +If you are using the Sanic v20.12LTS, please use Sanic-RestPlus v0.5.6. Installation @@ -52,10 +55,10 @@ With Sanic-Restplus, you only import the api instance to route and document your from sanic import Sanic from sanic_restplus import Api, Resource, fields from sanic_restplus.restplus import restplus - from spf import SanicPluginsFramework + from sanic_plugin_toolkit import SanicPluginRealm app = Sanic(__name__) - spf = SanicPluginsFramework(app) - rest_assoc = spf.register_plugin(restplus) + realm = SanicPluginRealm(app) + rest_assoc = realm.register_plugin(restplus) api = Api(version='1.0', title='TodoMVC API', description='A simple TodoMVC API') diff --git a/doc/scaling.rst b/doc/scaling.rst index beb02e65..19f1baac 100644 --- a/doc/scaling.rst +++ b/doc/scaling.rst @@ -228,7 +228,7 @@ Each `apivX` module will have the following pattern: # ... from .apis.namespaceX import api as nsX - blueprint = Blueprint('api', __name__, url_prefix='/api/1') + blueprint = Blueprint('api', url_prefix='/api/1') api = Api(blueprint title='My Title', version='1.0', diff --git a/doc/swagger.rst b/doc/swagger.rst index e48f76e3..22cc3d59 100644 --- a/doc/swagger.rst +++ b/doc/swagger.rst @@ -932,7 +932,7 @@ You can control the Swagger UI path with the ``doc`` parameter (defaults to the from flask_restplus import Api app = Flask(__name__) - blueprint = Blueprint('api', __name__, url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = Api(blueprint, doc='/doc/') app.register_blueprint(blueprint) diff --git a/examples/complex.py b/examples/complex.py index a6215f6d..d86d5d0b 100644 --- a/examples/complex.py +++ b/examples/complex.py @@ -1,11 +1,11 @@ from sanic import Sanic from sanic_restplus.restplus import restplus -from spf import SanicPluginsFramework +from sanic_plugin_toolkit import SanicPluginRealm from examples.zoo import api app = Sanic(__name__) -spf = SanicPluginsFramework(app) -rest_assoc = spf.register_plugin(restplus) +realm = SanicPluginRealm(app) +rest_assoc = realm.register_plugin(restplus) rest_assoc.api(api) app.run(debug=True, auto_reload=False) diff --git a/examples/todo.py b/examples/todo.py index 88d9bec6..7d3ceb9c 100644 --- a/examples/todo.py +++ b/examples/todo.py @@ -3,10 +3,10 @@ from sanic_restplus import Api, fields, Resource from sanic_restplus.cors import crossdomain from sanic_restplus.restplus import restplus -from spf import SanicPluginsFramework +from sanic_plugin_toolkit import SanicPluginRealm app = Sanic(__name__) -spf = SanicPluginsFramework(app) -rest_assoc = spf.register_plugin(restplus) +realm = SanicPluginRealm(app) +rest_assoc = realm.register_plugin(restplus) api = Api(version='1.0', title='Todo API', description='A simple TODO API') diff --git a/examples/todo_blueprint.py b/examples/todo_blueprint.py index ef66990e..50304219 100644 --- a/examples/todo_blueprint.py +++ b/examples/todo_blueprint.py @@ -1,7 +1,7 @@ from flask import Flask, Blueprint from flask_restplus import Api, Resource, fields -api_v1 = Blueprint('api', __name__, url_prefix='/api/1') +api_v1 = Blueprint('api', url_prefix='/api/1') api = Api(api_v1, version='1.0', title='Todo API', description='A simple TODO API', diff --git a/package-lock.json b/package-lock.json index c76b16f6..7e515a5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sanic_restplus", - "version": "0.5.6", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/requirements/install.pip b/requirements/install.pip index 1ca89160..03374c3f 100644 --- a/requirements/install.pip +++ b/requirements/install.pip @@ -1,6 +1,6 @@ aniso8601>=0.82 jsonschema methodtools -sanic>=18.12.0,<21 -sanic-jinja2-spf>=0.8.0 -sanic-plugins-framework>=0.9.5 +sanic>=21.3.4,<22 +sanic-jinja2>=0.10.0 +sanic-plugin-toolkit>=1.0.1 diff --git a/requirements/test.pip b/requirements/test.pip index fc6dc897..151677c8 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -8,4 +8,5 @@ pytest-asyncio pytest-mock==1.10.4 pytest-profiling==1.7.0 pytest-sugar==0.9.2 +sanic-testing>=0.4.0 tzlocal diff --git a/sanic_restplus/__about__.py b/sanic_restplus/__about__.py index 06ada5df..5363ad27 100644 --- a/sanic_restplus/__about__.py +++ b/sanic_restplus/__about__.py @@ -1,4 +1,4 @@ # -*- coding: latin-1 -*- # -__version__ = '0.5.6' +__version__ = '0.6.0' __description__ = 'Fully featured framework for fast, easy and documented API development with Sanic' diff --git a/sanic_restplus/api.py b/sanic_restplus/api.py index 4c619d10..08f22f64 100644 --- a/sanic_restplus/api.py +++ b/sanic_restplus/api.py @@ -16,22 +16,17 @@ from types import MethodType from jinja2 import PackageLoader -from sanic.router import RouteExists, url_hash +from sanic_routing.exceptions import RouteExists #, url_hash from sanic.response import text, BaseHTTPResponse from sanic.views import HTTPMethodView from sanic_jinja2 import SanicJinja2 -from spf.plugin import FutureRoute, FutureStatic +from sanic_plugin_toolkit.plugin import FutureRoute, FutureStatic -try: - from sanic.helpers import STATUS_CODES as ALL_STATUS_CODES -except ImportError: - try: - from sanic.response import ALL_STATUS_CODES - except ImportError: - from sanic.response import STATUS_CODES as ALL_STATUS_CODES +from sanic.helpers import STATUS_CODES from sanic.handlers import ErrorHandler from sanic.exceptions import SanicException, InvalidUsage, NotFound from sanic import exceptions, Sanic, Blueprint +from sanic.base import BaseSanic try: from sanic.compat import Header except ImportError: @@ -39,7 +34,8 @@ from sanic.server import CIMultiDict as Header except ImportError: from sanic.server import CIDict as Header -from spf import SanicPluginsFramework +from sanic_plugin_toolkit.plugin import SanicPlugin +from sanic_plugin_toolkit.realm import SanicPluginRealm from jsonschema import RefResolver from .restplus import restplus @@ -101,15 +97,15 @@ class Api(object): ''' uid_counter = 0 - def __init__(self, spf_reg=None, version='1.0', title=None, description=None, - terms_url=None, license=None, license_url=None, - contact=None, contact_url=None, contact_email=None, - authorizations=None, security=None, doc='/', default_id=default_id, - default='default', default_label='Default namespace', validate=None, - tags=None, prefix='', ordered=False, - default_mediatype='application/json', decorators=None, - catch_all_404s=False, serve_challenge_on_401=False, format_checker=None, - additional_css=None, **kwargs): + def __init__(self, plugin_reg=None, version='1.0', title=None, description=None, + terms_url=None, license=None, license_url=None, + contact=None, contact_url=None, contact_email=None, + authorizations=None, security=None, doc='/', default_id=default_id, + default='default', default_label='Default namespace', validate=None, + tags=None, prefix='', ordered=False, + default_mediatype='application/json', decorators=None, + catch_all_404s=False, serve_challenge_on_401=False, format_checker=None, + additional_css=None, **kwargs): self.version = version self.title = title or 'API' self.description = description @@ -145,10 +141,7 @@ def __init__(self, spf_reg=None, version='1.0', title=None, description=None, path='/', ) self.ns_paths = dict() - if py_36 > cur_py_version: #py3.5 or below - self.representations = OrderedDict(DEFAULT_REPRESENTATIONS) - else: - self.representations = dict(DEFAULT_REPRESENTATIONS) + self.representations = dict(DEFAULT_REPRESENTATIONS) self.urls = {} self.prefix = prefix self.default_mediatype = default_mediatype @@ -158,29 +151,29 @@ def __init__(self, spf_reg=None, version='1.0', title=None, description=None, self.blueprint_setup = None self.endpoints = set() self.resources = [] - self.spf_reg = None + self.plugin_reg = None self.blueprint = None self.additional_css = additional_css Api.uid_counter += 1 self._uid = Api.uid_counter - if spf_reg is not None: - if isinstance(spf_reg, Sanic): + if plugin_reg is not None: + if isinstance(plugin_reg, BaseSanic): # use legacy init method - spf = SanicPluginsFramework(spf_reg) + realm = SanicPluginRealm(plugin_reg) try: - spf_reg = restplus.find_plugin_registration(spf) + plugin_reg = restplus.find_plugin_registration(realm) except LookupError: - raise RuntimeError("Cannot create Api before sanic_restplus is registered on the SPF.") - self.init_api(reg=spf_reg, **kwargs) + raise RuntimeError("Cannot create Api before sanic_restplus is registered on SPTK.") + self.init_api(reg=plugin_reg, **kwargs) def init_api(self, reg=None, **kwargs): ''' - Allow to lazy register the API on a SPF instance:: + Allow to lazy register the API on a SPTK instance:: >>> app = Sanic(__name__) - >>> spf = SanicPluginsFramework(app) - >>> reg = spf.register_plugin(restplus) + >>> realm = SanicPluginRealm(app) + >>> reg = realm.register_plugin(restplus) >>> api = Api() >>> api.init_api(reg) @@ -193,11 +186,11 @@ def init_api(self, reg=None, **kwargs): :param str license_url: The license page URL (used in Swagger documentation) ''' - if self.spf_reg is None: + if self.plugin_reg is None: if reg is not None: - self.spf_reg = reg + self.plugin_reg = reg else: - raise RuntimeError("Cannot init_api without self.spf_reg") + raise RuntimeError("Cannot init_api without self.plugin_reg") self.title = kwargs.get('title', self.title) self.description = kwargs.get('description', self.description) self.terms_url = kwargs.get('terms_url', self.terms_url) @@ -209,7 +202,7 @@ def init_api(self, reg=None, **kwargs): self.additional_css = kwargs.get('additional_css', self.additional_css) self._add_specs = kwargs.get('add_specs', True) - context = restplus.get_context_from_spf(self.spf_reg) + context = restplus.get_context_from_realm(self.plugin_reg) app = context.app # If app is a blueprint, defer the initialization if isinstance(app, Blueprint): @@ -270,7 +263,7 @@ def _complete_url(self, url_part, registration_prefix): return ''.join(part for part in parts if part) # def _register_apidoc(self, app): - # context = restplus.get_context_from_spf(self.spf_reg) + # context = restplus.get_context_from_realm(self.plugin_reg) # if not context.get('apidoc_registered', False): # app.blueprint(apidoc.apidoc, url_prefix=self.prefix) # context['apidoc_registered'] = True @@ -278,19 +271,22 @@ def _complete_url(self, url_part, registration_prefix): # warnings.warn("Attempting to re-register the apidoc blueprint, skipped.") def _setup_jinja2_renderer(self): - spf, plugin_name, plugin_prefix = self.spf_reg - loader = PackageLoader(__name__, 'templates') + realm, plugin_name, plugin_prefix = self.plugin_reg + package_name = str(__name__) + if package_name.endswith(".api"): + package_name = package_name[:-4] + loader = PackageLoader(package_name, 'templates') enable_async = cur_py_version >= async_req_version - context = restplus.get_context_from_spf(self.spf_reg) + context = restplus.get_context_from_realm(self.plugin_reg) # Don't try to use an already registered Jinja2-plugin, it causes too much incompatibility with template # loaders. Just use a new one of our own. j2 = SanicJinja2(context.app, loader=loader, pkg_name=plugin_name, enable_async=enable_async) def swagger_static(filename): nonlocal self - spf, plugin_name, plugin_prefix = self.spf_reg + realm, plugin_name, plugin_prefix = self.plugin_reg endpoint = '{}.static'.format(str(self._uid)) - return restplus.spf_resolve_url_for(spf, endpoint, filename=filename) + return restplus.resolve_url_for(realm, endpoint, filename=filename) def config(): nonlocal self, context @@ -318,17 +314,24 @@ def api_renderer(request, api, request_context): return api_renderer def _register_static(self): - (spf, plugin_name, plugin_url_prefix) = self.spf_reg - context = restplus.get_context_from_spf(self.spf_reg) + (realm, plugin_name, plugin_url_prefix) = self.plugin_reg + context = restplus.get_context_from_realm(self.plugin_reg) module_path = os.path.abspath(os.path.dirname(__file__)) module_static = os.path.join(module_path, 'static') endpoint = '{}.static'.format(str(self._uid)) kwargs = { "name": endpoint } + kwargs.setdefault('pattern', r'/?.+') + kwargs.setdefault('use_modified_since', True) + kwargs.setdefault('use_content_range', False) + kwargs.setdefault('stream_large_files', False) + kwargs.setdefault('host', None) + kwargs.setdefault('strict_slashes', None) + kwargs.setdefault('content_type', None) if os.path.isdir(module_static): s = FutureStatic('/swaggerui', module_static, (), kwargs) else: s = FutureStatic('/swaggerui', './sanic_restplus/static', (), kwargs) - spf._register_static_helper(s, spf, restplus, context, plugin_name, plugin_url_prefix) + realm._register_static_helper(s, realm, restplus, context, plugin_name, plugin_url_prefix) def _register_specs(self): @@ -345,8 +348,19 @@ def _register_specs(self): def _register_doc(self, api_renderer): root_path = self.prefix or '/' - (spf, plugin_name, plugin_url_prefix) = self.spf_reg - context = restplus.get_context_from_spf(self.spf_reg) + (realm, plugin_name, plugin_url_prefix) = self.plugin_reg + context = restplus.get_context_from_realm(self.plugin_reg) + route_kwargs = {"methods": ['GET']} + route_kwargs.setdefault('host', None) + route_kwargs.setdefault('strict_slashes', False) + route_kwargs.setdefault('stream', False) + route_kwargs.setdefault('name', None) + route_kwargs.setdefault('version', None) + route_kwargs.setdefault('ignore_body', False) + route_kwargs.setdefault('websocket', False) + route_kwargs.setdefault('subprotocols', None) + route_kwargs.setdefault('unquote', False) + route_kwargs.setdefault('static', False) if self._add_specs and self._doc: doc_endpoint_name = '{}_doc'.format(str(self._uid)) @@ -355,8 +369,8 @@ def _render_doc(*args, **kwargs): return self.render_doc(*args, api_renderer=api_renderer, **kwargs) render_doc = wraps(self.render_doc)(_render_doc) render_doc.__name__ = doc_endpoint_name - r = FutureRoute(render_doc, self._doc, (), {'with_context': True}) - spf._register_route_helper(r, spf, restplus, context, plugin_name, plugin_url_prefix) + r = FutureRoute(render_doc, self._doc, (), {'with_context': True, **route_kwargs}) + realm._register_route_helper(r, realm, restplus, context, plugin_name, plugin_url_prefix) if self._doc != root_path: try:# app_or_blueprint.add_url_rule(self.prefix or '/', 'root', self.render_root) root_endpoint_name = '{}_root'.format(str(self._uid)) @@ -365,8 +379,8 @@ def _render_root(*args, **kwargs): return self.render_root(*args, **kwargs) render_root = wraps(self.render_root)(_render_root) render_root.__name__ = root_endpoint_name - r = FutureRoute(render_root, root_path, (), {}) - spf._register_route_helper(r, spf, restplus, context, plugin_name, plugin_url_prefix) + r = FutureRoute(render_root, root_path, (), route_kwargs) + realm._register_route_helper(r, realm, restplus, context, plugin_name, plugin_url_prefix) except RouteExists: pass @@ -378,7 +392,7 @@ def register_resource(self, namespace, resource, *urls, **kwargs): kwargs['endpoint'] = endpoint self.endpoints.add(endpoint) - if self.spf_reg is not None: + if self.plugin_reg is not None: self._register_view(resource, namespace, *urls, **kwargs) else: self.resources.append((resource, namespace, urls, kwargs)) @@ -388,7 +402,7 @@ def _register_view(self, resource, namespace, *urls, **kwargs): endpoint = kwargs.pop('endpoint', None) or camel_to_dash(resource.__name__) resource_class_args = kwargs.pop('resource_class_args', ()) resource_class_kwargs = kwargs.pop('resource_class_kwargs', {}) - (spf, plugin_name, plugin_url_prefix) = self.spf_reg + (realm, plugin_name, plugin_url_prefix) = self.plugin_reg resource.mediatypes = self.mediatypes_method() # Hacky resource.endpoint = endpoint methods = resource.methods @@ -403,7 +417,7 @@ def _register_view(self, resource, namespace, *urls, **kwargs): for decorator in chain(namespace.decorators, self.decorators): resource_func = decorator(resource_func) - context = restplus.get_context_from_spf(self.spf_reg) + context = restplus.get_context_from_realm(self.plugin_reg) for url in urls: # If this Api has a blueprint if self.blueprint: @@ -423,9 +437,19 @@ def _register_view(self, resource, namespace, *urls, **kwargs): else: # If we've got no Blueprint, just build a url with no prefix rule = self._complete_url(url, '') + kwargs.setdefault('host', None) + kwargs.setdefault('strict_slashes', True) + kwargs.setdefault('stream', False) + kwargs.setdefault('name', None) + kwargs.setdefault('version', None) + kwargs.setdefault('ignore_body', False) + kwargs.setdefault('websocket', False) + kwargs.setdefault('subprotocols', None) + kwargs.setdefault('unquote', False) + kwargs.setdefault('static', False) # Add the url to the application or blueprint - r = FutureRoute(resource_func, rule, (), {'methods': methods, 'with_context': True}) - spf._register_route_helper(r, spf, restplus, context, plugin_name, plugin_url_prefix) + r = FutureRoute(resource_func, rule, (), {'methods': methods, 'with_context': True, **kwargs}) + realm._register_route_helper(r, realm, restplus, context, plugin_name, plugin_url_prefix) def output(self, resource): """ @@ -586,7 +610,7 @@ def specs_url(self): :rtype: str ''' try: - specs_url = restplus.spf_resolve_url_for(self.spf_reg, self.endpoint('{}_specs'.format(str(self._uid))), _external=False) + specs_url = restplus.resolve_url_for(self.plugin_reg, self.endpoint('{}_specs'.format(str(self._uid))), _external=False) except (AttributeError, KeyError): raise RuntimeError("The API object does not have an `app` assigned.") return specs_url @@ -600,9 +624,9 @@ def base_url(self): root_path = self.prefix or '/' try: if self._doc == root_path: - base_url = restplus.spf_resolve_url_for(self.spf_reg, self.endpoint('{}_doc'.format(str(self._uid))), _external=False) + base_url = restplus.resolve_url_for(self.plugin_reg, self.endpoint('{}_doc'.format(str(self._uid))), _external=False) else: - base_url = restplus.spf_resolve_url_for(self.spf_reg, self.endpoint('{}_root'.format(str(self._uid))), _external=False) + base_url = restplus.resolve_url_for(self.plugin_reg, self.endpoint('{}_root'.format(str(self._uid))), _external=False) except (AttributeError, KeyError): raise RuntimeError("The API object does not have an `app` assigned.") return base_url @@ -616,12 +640,12 @@ def base_path(self): :rtype: str ''' root_path = self.prefix or '/' - (spf, _, _) = self.spf_reg + (realm, _, _) = self.plugin_reg try: if self._doc == root_path: - base_url = restplus.spf_resolve_url_for(self.spf_reg, self.endpoint('{}_doc'.format(str(self._uid)))) + base_url = restplus.resolve_url_for(self.plugin_reg, self.endpoint('{}_doc'.format(str(self._uid)))) else: - base_url = restplus.spf_resolve_url_for(self.spf_reg, self.endpoint('{}_root'.format(str(self._uid)))) + base_url = restplus.resolve_url_for(self.plugin_reg, self.endpoint('{}_root'.format(str(self._uid)))) except (AttributeError, KeyError): raise RuntimeError("The API object does not have an `app` assigned.") return base_url @@ -742,7 +766,7 @@ def _should_use_fr_error_handler(self, request): try: return self._dummy_router_get(app.router, request.method, request) except InvalidUsage as e: - (_, plugin_name, _) = self.spf_reg + (_, plugin_name, _) = self.plugin_reg plugin_name_prefix = "{}.".format(plugin_name) # Check if the other HTTP methods at this url would hit the Api try: @@ -773,7 +797,7 @@ def _has_fr_route(self, request): if not route or not route.handler or not route.name: return False route_endpoint_name = route.name - (_, plugin_name, _) = self.spf_reg + (_, plugin_name, _) = self.plugin_reg plugin_name_prefix = "{}.".format(plugin_name) if str(route_endpoint_name).startswith(plugin_name_prefix): route_endpoint_name = route_endpoint_name[len(plugin_name_prefix):] @@ -788,7 +812,7 @@ def handle_error(self, request, e): :param e: the raised Exception object :type e: Exception """ - context = restplus.get_context_from_spf(self.spf_reg) + context = restplus.get_context_from_realm(self.plugin_reg) app = context.app #got_request_exception.send(app._get_current_object(), exception=e) if not isinstance(e, SanicException) and app.config.get('PROPAGATE_EXCEPTIONS', False): @@ -814,7 +838,7 @@ def handle_error(self, request, e): status = e.args[0] assert isinstance(status, (str, bytes)) except (AttributeError, LookupError, AssertionError): - if sanic_code is 200: + if sanic_code == 200: status = b'OK' # x is y comparison only works between -5 and 256 elif sanic_code == 404: @@ -822,7 +846,7 @@ def handle_error(self, request, e): elif sanic_code == 500: status = b'Internal Server Error' else: - status = ALL_STATUS_CODES.get(int(sanic_code)) + status = STATUS_CODES.get(int(sanic_code)) code = HTTPStatus(sanic_code, None) if status and isinstance(status, bytes): status = status.decode('ascii') @@ -836,7 +860,7 @@ def handle_error(self, request, e): default_data, code, headers = unpack(result, HTTPStatus.INTERNAL_SERVER_ERROR) else: code = HTTPStatus.INTERNAL_SERVER_ERROR - status = ALL_STATUS_CODES.get(code.value, str(e)) + status = STATUS_CODES.get(code.value, str(e)) if status and isinstance(status, bytes): status = status.decode('ascii') if include_message_in_response: @@ -1034,10 +1058,10 @@ def url_for(self, resource, **values): Works like :func:`app.url_for`. ''' endpoint = resource.endpoint - (spf, _, _) = self.spf_reg + (realm, _, _) = self.plugin_reg if self.blueprint: endpoint = '{0}.{1}'.format(self.blueprint.name, endpoint) - return restplus.spf_resolve_url_for(self.spf_reg, endpoint, **values) + return restplus.resolve_url_for(self.plugin_reg, endpoint, **values) class ApiErrorHandler(ErrorHandler): diff --git a/sanic_restplus/restplus.py b/sanic_restplus/restplus.py index c6388e51..4f11b9fa 100644 --- a/sanic_restplus/restplus.py +++ b/sanic_restplus/restplus.py @@ -1,6 +1,6 @@ -from sanic import Sanic, Blueprint -from spf import SanicPlugin, SanicPluginsFramework -from spf.plugin import PluginRegistration, PluginAssociated +from sanic.base import BaseSanic +from sanic_plugin_toolkit.plugin import SanicPlugin, PluginRegistration, PluginAssociated +from sanic_plugin_toolkit.realm import SanicPluginRealm class RestPlusAssociated(PluginAssociated): @@ -31,30 +31,30 @@ def on_registered(self, context, reg, *args, **kwargs): context['apis'] = apis = set() app = context.app try: - ext = getattr(app, 'extensions', None) + ext = getattr(app.ctx, 'extensions', None) assert ext is not None except (AttributeError, AssertionError): - setattr(app, 'extensions', dict()) + setattr(app.ctx, 'extensions', dict()) def api(self, reg, *args, api_class=None, **kwargs): from .api import Api if isinstance(reg, PluginAssociated): (pl, reg) = reg - (spf, _, _) = reg + (realm, _, _) = reg elif isinstance(reg, PluginRegistration): - (spf, _, _) = reg - elif isinstance(reg, SanicPluginsFramework): - spf = reg - reg = self.find_plugin_registration(spf) - elif isinstance(reg, Sanic): + (realm, _, _) = reg + elif isinstance(reg, SanicPluginRealm): + realm = reg + reg = self.find_plugin_registration(realm) + elif isinstance(reg, BaseSanic): app = reg - spf = SanicPluginsFramework(app) - reg = self.find_plugin_registration(spf) + realm = SanicPluginRealm(app) + reg = self.find_plugin_registration(realm) else: - raise RuntimeError("the 'reg' argument must be a SPF, an Association, Registration, or an App!") - context = self.get_context_from_spf(reg) + raise RuntimeError("the 'reg' argument must be a Realm, an Association, Registration, or an App!") + context = self.get_context_from_realm(reg) app = context.app - assert isinstance(app, (Sanic, Blueprint)) + assert isinstance(app, BaseSanic) if api_class is None: if args and len(args) > 0 and isinstance(args[0], Api): args = list(args) diff --git a/sanic_restplus/swagger.py b/sanic_restplus/swagger.py index e3bff4ea..f392dace 100644 --- a/sanic_restplus/swagger.py +++ b/sanic_restplus/swagger.py @@ -235,7 +235,7 @@ def as_dict(self): return not_none(specs) def get_host(self): - current_app = restplus.get_app_from_spf_context(self.api.spf_reg) + current_app = restplus.get_app_from_realm_context(self.api.plugin_reg) hostname = current_app.config.get('SERVER_NAME', None) or None if hostname and self.api.blueprint and self.api.blueprint.subdomain: hostname = '.'.join((self.api.blueprint.subdomain, hostname)) @@ -476,7 +476,7 @@ def parameters_for(self, doc): params.append(param) # Handle fields mask - context = restplus.get_context_from_spf(self.api.spf_reg) + context = restplus.get_context_from_realm(self.api.plugin_reg) mask = doc.get('__mask__') if mask and context.get('MASK_SWAGGER', False): param = { diff --git a/setup.py b/setup.py index c7782a36..1629dc73 100644 --- a/setup.py +++ b/setup.py @@ -70,8 +70,8 @@ def pip(filename): exec(compile(open('sanic_restplus/__about__.py', encoding="latin-1").read(), 'sanic_restplus/__about__.py', 'exec')) install_requires = pip('install') -if sys.version_info < (3, 5): - raise RuntimeError("Cannot install on Python version < 3.5") +if sys.version_info < (3, 7): + raise RuntimeError("Cannot install on Python version < 3.7") doc_require = pip('doc') tests_require = pip('test') @@ -89,6 +89,7 @@ def pip(filename): ['RestPlus = sanic_restplus.restplus:instance'] }, include_package_data=True, + python_requires=">=3.7", install_requires=install_requires, tests_require=tests_require, extras_require={ @@ -112,9 +113,9 @@ def pip(filename): 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', 'License :: OSI Approved :: MIT License', diff --git a/test1.py b/test1.py deleted file mode 100644 index 06a85ce2..00000000 --- a/test1.py +++ /dev/null @@ -1,60 +0,0 @@ -from sanic import Sanic, Blueprint -from sanic import response -from spf import SanicPluginsFramework - -from sanic_restplus import restplus, Resource, Api, fields -from sanic_restplus.utils import get_accept_mimetypes -from util import ns as util_ns - -app = Sanic(__name__) -bp = Blueprint("bp1", url_prefix="/bp1/") -spf_b = SanicPluginsFramework(bp) -reg = spf_b.register_plugin(restplus) -api = Api() -api.add_namespace(util_ns, "/data/v2/electrical") -reg.api(api) - -todos = {} - - -resource_fields = api.model('Resource', { - 'data': fields.String, -}) - -@api.route('/') -@api.doc(params={'todo_id': 'A TODO ID'}) -class TodoSimple(Resource): - """ - You can try this example as follow: - $ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT - $ curl http://localhost:5000/todo1 - {"todo1": "Remember the milk"} - $ curl http://localhost:5000/todo2 -d "data=Change my breakpads" -X PUT - $ curl http://localhost:5000/todo2 - {"todo2": "Change my breakpads"} - - Or from python if you have requests : - >>> from requests import put, get - >>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json - {u'todo1': u'Remember the milk'} - >>> get('http://localhost:5000/todo1').json - {u'todo1': u'Remember the milk'} - >>> put('http://localhost:5000/todo2', data={'data': 'Change my breakpads'}).json - {u'todo2': u'Change my breakpads'} - >>> get('http://localhost:5000/todo2').json - {u'todo2': u'Change my breakpads'} - - """ - def get(self, request, todo_id): - return {todo_id: todos[todo_id]} - - @api.expect(resource_fields) - def put(self, request, todo_id): - - todos[todo_id] = request.form['data'] - return {todo_id: todos[todo_id]} - -if __name__ == '__main__': - app.run(port=8001, debug=True) - - diff --git a/tests/conftest.py b/tests/conftest.py index d6fa8c6f..14552378 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- import pytest - +import pytest_asyncio from sanic import Sanic, Blueprint -from sanic.websocket import WebSocketProtocol from uuid import uuid4 -from spf import SanicPluginsFramework +from sanic_plugin_toolkit import SanicPluginRealm import sanic_restplus from sanic_restplus import restplus @@ -27,13 +26,15 @@ # return self.get_json('{0}/swagger.json'.format(prefix), status=status, **kwargs) +pytestmark = pytest.mark.asyncio + @pytest.fixture def app(): guid = str(uuid4()) app = Sanic(__name__+guid) #app.test_client_class = TestClient - spf = SanicPluginsFramework(app) - spf.register_plugin(restplus) + realm = SanicPluginRealm(app) + realm.register_plugin(restplus) yield app @@ -48,7 +49,7 @@ def api(request, app): if 'subdomain' in marker.kwargs: bpkwargs['subdomain'] = marker.kwargs.pop('subdomain') kwargs = marker.kwargs - blueprint = Blueprint('api', __name__, **bpkwargs) + blueprint = Blueprint('api', **bpkwargs) api = sanic_restplus.Api(blueprint, **kwargs) app.register_blueprint(blueprint) yield api diff --git a/tests/test_api.py b/tests/test_api.py index 2fd8a869..6c2375c8 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -26,7 +26,7 @@ def test_root_endpoint_lazy(self, app): assert api.base_url == 'http://localhost/' def test_root_endpoint_with_blueprint(self, app): - blueprint = Blueprint('api', __name__, url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint, version='1.0') app.register_blueprint(blueprint) @@ -36,7 +36,7 @@ def test_root_endpoint_with_blueprint(self, app): assert api.base_url == 'http://localhost/api/' def test_root_endpoint_with_blueprint_with_subdomain(self, app): - blueprint = Blueprint('api', __name__, subdomain='api', url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint, version='1.0') app.register_blueprint(blueprint) @@ -47,7 +47,7 @@ def test_root_endpoint_with_blueprint_with_subdomain(self, app): def test_parser(self): api = sanic_restplus.Api() - assert isinstance(api.parser(), restplus.reqparse.RequestParser) + assert isinstance(api.parser(), sanic_restplus.reqparse.RequestParser) def test_doc_decorator(self, app): api = sanic_restplus.Api(app, prefix='/api', version='1.0') @@ -117,7 +117,7 @@ class TestResource(sanic_restplus.Resource): assert url_for('test_resource') == '/test/' def test_default_endpoint_with_blueprint(self, app): - blueprint = Blueprint('api', __name__, url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint) app.register_blueprint(blueprint) @@ -129,7 +129,7 @@ class TestResource(sanic_restplus.Resource): assert url_for('api.test_resource') == '/api/test/' def test_default_endpoint_with_blueprint_with_subdomain(self, app): - blueprint = Blueprint('api', __name__, subdomain='api', url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint) app.register_blueprint(blueprint) @@ -165,7 +165,7 @@ class TestResource(sanic_restplus.Resource): assert url_for('ns_test_resource') == '/ns/test/' def test_default_endpoint_for_namespace_with_blueprint(self, app): - blueprint = Blueprint('api', __name__, url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint) ns = api.namespace('ns', 'Test namespace') @@ -233,7 +233,7 @@ class TestResource(sanic_restplus.Resource): assert url_for('ns_test_resource_2') == '/ns/test2/' def test_multiple_default_endpoint_for_namespace_with_blueprint(self, app): - blueprint = Blueprint('api', __name__, url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint) ns = api.namespace('ns', 'Test namespace') diff --git a/tests/test_errors.py b/tests/test_errors.py index f58c53e0..5f5247bc 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -266,7 +266,7 @@ async def get(self, request): assert 'message' in data async def test_default_errorhandler_with_propagate_true(self, app, client): - blueprint = Blueprint('api', __name__, url_prefix='/api') + blueprint = Blueprint('api', url_prefix='/api') api = sanic_restplus.Api(blueprint) @api.route('/test/') diff --git a/tests/test_fields.py b/tests/test_fields.py index 7c760e97..bf735819 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -7,7 +7,7 @@ from functools import partial import pytest -from spf import SanicPluginsFramework +from sanic_plugin_toolkit import SanicPluginRealm from sanic import Blueprint from sanic_restplus import fields, Api, restplus cet = timezone(timedelta(hours=1), 'CET') @@ -18,8 +18,8 @@ class FieldTestCase(object): @pytest.fixture def api(self, app): blueprint = Blueprint('api', __name__) - spf = SanicPluginsFramework(blueprint) - plugin, reg = spf.register_plugin(restplus) + realm = SanicPluginRealm(blueprint) + plugin, reg = realm.register_plugin(restplus) api = Api(reg) app.register_blueprint(blueprint) yield api diff --git a/tests/test_swagger.py b/tests/test_swagger.py index daa05dbd..9444b4d4 100644 --- a/tests/test_swagger.py +++ b/tests/test_swagger.py @@ -162,7 +162,7 @@ def test_specs_endpoint_host(self, app, client): @pytest.mark.options(server_name='api.restplus.org') def test_specs_endpoint_host_with_url_prefix(self, app, client): - blueprint = Blueprint('api', __name__, url_prefix='/api/1') + blueprint = Blueprint('api', url_prefix='/api/1') sanic_restplus.Api(blueprint) app.register_blueprint(blueprint) @@ -172,7 +172,7 @@ def test_specs_endpoint_host_with_url_prefix(self, app, client): @pytest.mark.options(server_name='restplus.org') def test_specs_endpoint_host_and_subdomain(self, app, client): - blueprint = Blueprint('api', __name__, subdomain='api') + blueprint = Blueprint('api', __name__) sanic_restplus.Api(blueprint) app.register_blueprint(blueprint)