diff --git a/base_rest/apispec/base_rest_service_apispec.py b/base_rest/apispec/base_rest_service_apispec.py index 72276d40a..a9c4bdbf4 100644 --- a/base_rest/apispec/base_rest_service_apispec.py +++ b/base_rest/apispec/base_rest_service_apispec.py @@ -20,8 +20,8 @@ class BaseRestServiceAPISpec(APISpec): def __init__(self, service_component, **params): self._service = service_component - super(BaseRestServiceAPISpec, self).__init__( - title="%s REST services" % self._service._usage, + super().__init__( + title=f"{self._service._usage} REST services", version="", openapi_version="3.0.0", info={ @@ -45,11 +45,9 @@ def _get_servers(self): base_url = env["ir.config_parameter"].sudo().get_param("web.base.url") return [ { - "url": "%s/%s/%s" - % ( - base_url.strip("/"), - collection_path.strip("/"), - self._service._usage, + "url": ( + f"{base_url.strip('/')}/{collection_path.strip('/')}" + f"/{self._service._usage}" ) } ] diff --git a/base_rest/apispec/rest_method_param_plugin.py b/base_rest/apispec/rest_method_param_plugin.py index dde70782e..26a3517ee 100644 --- a/base_rest/apispec/rest_method_param_plugin.py +++ b/base_rest/apispec/rest_method_param_plugin.py @@ -14,23 +14,21 @@ class RestMethodParamPlugin(BasePlugin): """ def __init__(self, service): - super(RestMethodParamPlugin, self).__init__() + super().__init__() self._service = service self._default_parameters = service._get_openapi_default_parameters() self._default_responses = service._get_openapi_default_responses() # pylint: disable=W8110 def init_spec(self, spec): - super(RestMethodParamPlugin, self).init_spec(spec) + super().init_spec(spec) self.spec = spec self.openapi_version = spec.openapi_version def operation_helper(self, path=None, operations=None, **kwargs): routing = kwargs.get(ROUTING_DECORATOR_ATTR) if not routing: - super(RestMethodParamPlugin, self).operation_helper( - path, operations, **kwargs - ) + super().operation_helper(path, operations, **kwargs) if not operations: return for method, params in operations.items(): diff --git a/base_rest/apispec/rest_method_security_plugin.py b/base_rest/apispec/rest_method_security_plugin.py index bdc3d0e5c..20fe55d2d 100644 --- a/base_rest/apispec/rest_method_security_plugin.py +++ b/base_rest/apispec/rest_method_security_plugin.py @@ -12,13 +12,13 @@ class RestMethodSecurityPlugin(BasePlugin): """ def __init__(self, service, user_auths=("user",)): - super(RestMethodSecurityPlugin, self).__init__() + super().__init__() self._service = service self._supported_user_auths = user_auths # pylint: disable=W8110 def init_spec(self, spec): - super(RestMethodSecurityPlugin, self).init_spec(spec) + super().init_spec(spec) self.spec = spec self.openapi_version = spec.openapi_version user_scheme = {"type": "apiKey", "in": "cookie", "name": "session_id"} @@ -27,9 +27,7 @@ def init_spec(self, spec): def operation_helper(self, path=None, operations=None, **kwargs): routing = kwargs.get(ROUTING_DECORATOR_ATTR) if not routing: - super(RestMethodSecurityPlugin, self).operation_helper( - path, operations, **kwargs - ) + super().operation_helper(path, operations, **kwargs) if not operations: return auth = routing.get("auth", self.spec._params.get("default_auth")) diff --git a/base_rest/apispec/restapi_method_route_plugin.py b/base_rest/apispec/restapi_method_route_plugin.py index a88be5519..ccef836b4 100644 --- a/base_rest/apispec/restapi_method_route_plugin.py +++ b/base_rest/apispec/restapi_method_route_plugin.py @@ -24,7 +24,7 @@ class RestApiMethodRoutePlugin(BasePlugin): """ def __init__(self, service): - super(RestApiMethodRoutePlugin, self).__init__() + super().__init__() self.converter_mapping = dict(DEFAULT_CONVERTER_MAPPING) self._service = service diff --git a/base_rest/controllers/api_docs.py b/base_rest/controllers/api_docs.py index fcbc171e4..05a7d090c 100644 --- a/base_rest/controllers/api_docs.py +++ b/base_rest/controllers/api_docs.py @@ -61,9 +61,8 @@ def _get_api_urls(self): for service in self._get_service_in_collection(collection_name): api_urls.append( { - "name": "{}: {}".format(collection_path, service._usage), - "url": "/api-docs/%s/%s.json" - % (collection_path, service._usage), + "name": f"{collection_path}: {service._usage}", + "url": f"/api-docs/{collection_path}/{service._usage}.json", } ) api_urls = sorted(api_urls, key=lambda k: k["name"]) diff --git a/base_rest/controllers/main.py b/base_rest/controllers/main.py index b4166d741..afc297a2b 100644 --- a/base_rest/controllers/main.py +++ b/base_rest/controllers/main.py @@ -16,7 +16,7 @@ _logger = logging.getLogger(__name__) -class _PseudoCollection(object): +class _PseudoCollection: __slots__ = "_name", "env", "id" def __init__(self, name, env): diff --git a/base_rest/http.py b/base_rest/http.py index af68c0540..ab33c0cfc 100644 --- a/base_rest/http.py +++ b/base_rest/http.py @@ -43,7 +43,7 @@ try: import pyquerystring from accept_language import parse_accept_language -except (ImportError, IOError) as err: +except (OSError, ImportError) as err: _logger.debug(err) @@ -55,7 +55,7 @@ def default(self, obj): # pylint: disable=E0202,arguments-differ return obj.isoformat() elif isinstance(obj, decimal.Decimal): return float(obj) - return super(JSONEncoder, self).default(obj) + return super().default(obj) BLACKLISTED_LOG_PARAMS = ("password",) @@ -138,8 +138,8 @@ def pre_dispatch(self, rule, args): try: self.request.params.update(json.loads(data)) except (ValueError, json.decoder.JSONDecodeError) as e: - msg = "Invalid JSON data: %s" % str(e) - _logger.info("%s: %s", self.request.httprequest.path, msg) + msg = f"Invalid JSON data: {str(e)}" + _logger.info(f"{self.request.httprequest.path}: {msg}") raise BadRequest(msg) from e elif httprequest.mimetype == "multipart/form-data": # Do not reassign self.params @@ -229,10 +229,10 @@ def handle_error(self, exception): if isinstance(exception, MissingError): extra_info = getattr(exception, "rest_json_info", None) return wrapJsonException(NotFound(ustr(exception)), extra_info=extra_info) - if isinstance(exception, (AccessError, AccessDenied)): + if isinstance(exception, (AccessError | AccessDenied)): extra_info = getattr(exception, "rest_json_info", None) return wrapJsonException(Forbidden(ustr(exception)), extra_info=extra_info) - if isinstance(exception, (UserError, ValidationError)): + if isinstance(exception, (UserError | ValidationError)): extra_info = getattr(exception, "rest_json_info", None) return wrapJsonException( BadRequest(exception.args[0]), diff --git a/base_rest/models/rest_service_registration.py b/base_rest/models/rest_service_registration.py index ee44c3dd2..60e06fd3b 100644 --- a/base_rest/models/rest_service_registration.py +++ b/base_rest/models/rest_service_registration.py @@ -10,6 +10,7 @@ This code is inspired by ``odoo.addons.component.builder.ComponentBuilder`` """ + import inspect import logging @@ -94,7 +95,7 @@ def _build_controller(self, service, controller_def): ) base_controller_cls._identifier = identifier # put our new controller into the new addon module - ctrl_cls.__module__ = "odoo.addons.{}".format(addon_name) + ctrl_cls.__module__ = f"odoo.addons.{addon_name}" self.env.registry._init_modules.add(addon_name) @@ -137,7 +138,7 @@ def _apply_default_auth_if_not_set(self, controller_class, routing): if auth == "public_or_default": alternative_auth = "public_or_" + default_auth if getattr( - self.env["ir.http"], "_auth_method_%s" % alternative_auth, None + self.env["ir.http"], f"_auth_method_{alternative_auth}", None ): routing["auth"] = alternative_auth else: @@ -224,7 +225,8 @@ def load_services(self, module, services_registry): and current_controller != controller_def["controller_class"] ): _logger.error( - "Only one REST controller can be safely declared for root path %s\n " + "Only one REST controller can be safely declared for root " + "path %s\n " "Registering controller %s\n " "Registered controller%s\n", root_path, @@ -245,7 +247,7 @@ def _register_rest_route(self, route_path): _rest_services_routes[self.env.cr.dbname].add(route_path) -class RestApiMethodTransformer(object): +class RestApiMethodTransformer: """Helper class to generate and apply the missing restapi.method decorator to service's methods defined without decorator. @@ -297,7 +299,7 @@ def _method_to_routes(self, method): method_name = method.__name__ signature = inspect.signature(method) id_in_path_required = "_id" in signature.parameters - path = "/{}".format(method_name) + path = f"/{method_name}" if id_in_path_required: path = "/" + path if method_name in ("get", "search"): @@ -341,15 +343,15 @@ def _method_to_param(self, validator_method_name, direction): return None def _method_to_input_param(self, method): - validator_method_name = "_validator_{}".format(method.__name__) + validator_method_name = f"_validator_{method.__name__}" return self._method_to_param(validator_method_name, "input") def _method_to_output_param(self, method): - validator_method_name = "_validator_return_{}".format(method.__name__) + validator_method_name = f"_validator_return_{method.__name__}" return self._method_to_param(validator_method_name, "output") -class RestApiServiceControllerGenerator(object): +class RestApiServiceControllerGenerator: """ An object helper used to generate the http.Controller required to serve the method decorated with the `@restappi.method` decorator @@ -391,13 +393,13 @@ def _generate_methods(self): path_sep = "" if root_path[-1] != "/": path_sep = "/" - root_path = "{}{}{}".format(root_path, path_sep, self._service._usage) + root_path = f"{root_path}{path_sep}{self._service._usage}" for name, method in _inspect_methods(self._service.__class__): routing = getattr(method, ROUTING_DECORATOR_ATTR, None) if routing is None: continue for routes, http_method in routing["routes"]: - method_name = "{}_{}".format(http_method.lower(), name) + method_name = f"{http_method.lower()}_{name}" default_route = routes[0] rule = Rule(default_route) Map(rules=[rule]) @@ -417,7 +419,7 @@ def _generate_methods(self): exec(method, _globals) method_exec = _globals[method_name] route_params = dict( - route=["{}{}".format(root_path, r) for r in routes], + route=[f"{root_path}{r}" for r in routes], methods=[http_method], type="restapi", ) diff --git a/base_rest/restapi.py b/base_rest/restapi.py index 4160b29b9..12c9e2d10 100644 --- a/base_rest/restapi.py +++ b/base_rest/restapi.py @@ -299,7 +299,7 @@ def __init__(self, schema, min_items=None, max_items=None, unique_items=None): contain unique items. (Not enforced at validation time) """ - super(CerberusListValidator, self).__init__(schema=schema) + super().__init__(schema=schema) self._min_items = min_items self._max_items = max_items self._unique_items = unique_items @@ -331,7 +331,8 @@ def _do_validate(self, service, data, direction): if self._min_items is not None and len(values) < self._min_items: raise ExceptionClass( _( - "BadRequest: Not enough items in the list (%(current)s < %(expected)s)", + "BadRequest: Not enough items in the list (%(current)s " + "< %(expected)s)", current=len(values), expected=self._min_items, ) @@ -339,7 +340,8 @@ def _do_validate(self, service, data, direction): if self._max_items is not None and len(values) > self._max_items: raise ExceptionClass( _( - "BadRequest: Too many items in the list (%(current)s > %(expected)s)", + "BadRequest: Too many items in the list (%(current)s " + "> %(expected)s)", current=len(values), expected=self._max_items, ) @@ -408,8 +410,8 @@ def from_params(self, service, params): ) # multipart ony sends its parts as string except json.JSONDecodeError as error: raise ValidationError( - _("{}'s JSON content is malformed: {}".format(key, error)) - ) + _(f"{key}'s JSON content is malformed: {error}") + ) from error param = part.from_params(service, json_param) params[key] = param return params diff --git a/base_rest/tests/common.py b/base_rest/tests/common.py index cd2426781..3907b0e1d 100644 --- a/base_rest/tests/common.py +++ b/base_rest/tests/common.py @@ -29,7 +29,7 @@ from ..tools import ROUTING_DECORATOR_ATTR, _inspect_methods -class RegistryMixin(object): +class RegistryMixin: @classmethod def setUpRegistry(cls): with new_rollbacked_env() as env: @@ -53,7 +53,6 @@ def setUpRegistry(cls): class RestServiceRegistryCase(ComponentRegistryCase): - # pylint: disable=W8106 @staticmethod def _setup_registry(class_or_instance): @@ -146,13 +145,13 @@ def _teardown_registry(class_or_instance): ) db_name = get_db_name() _component_databases[db_name] = class_or_instance._original_components - _rest_services_databases[ - db_name - ] = class_or_instance._original_services_registry + _rest_services_databases[db_name] = ( + class_or_instance._original_services_registry + ) class_or_instance._service_registry = {} - _rest_controllers_per_module[ - _get_addon_name(class_or_instance.__module__) - ] = class_or_instance._original_addon_rest_controllers_per_module + _rest_controllers_per_module[_get_addon_name(class_or_instance.__module__)] = ( + class_or_instance._original_addon_rest_controllers_per_module + ) @staticmethod def _build_services(class_or_instance, *classes): diff --git a/base_rest/tests/test_cerberus_list_validator.py b/base_rest/tests/test_cerberus_list_validator.py index 1d5fb03e3..26c3e0e54 100644 --- a/base_rest/tests/test_cerberus_list_validator.py +++ b/base_rest/tests/test_cerberus_list_validator.py @@ -217,7 +217,7 @@ def test_to_response_validation(self): self.simple_schema_list_validator.to_response(None, result=[{}]) def test_schema_lookup_from_string(self): - class MyService(object): + class MyService: def _get_simple_schema(self): return {"name": {"type": "string", "required": True, "nullable": True}} @@ -233,7 +233,7 @@ def component(self, *args, **kwargs): ) def test_schema_lookup_from_string_custom_validator(self): - class MyService(object): + class MyService: def _get_simple_schema(self): return Validator( {"name": {"type": "string", "required": False}}, require_all=True diff --git a/base_rest/tests/test_cerberus_validator.py b/base_rest/tests/test_cerberus_validator.py index e6c8360ef..292298675 100644 --- a/base_rest/tests/test_cerberus_validator.py +++ b/base_rest/tests/test_cerberus_validator.py @@ -274,7 +274,7 @@ def test_to_response_validation(self): self.simple_schema_cerberus_validator.to_response(None, result={}) def test_schema_lookup_from_string(self): - class MyService(object): + class MyService: def _get_simple_schema(self): return {"name": {"type": "string", "required": True, "nullable": True}} @@ -290,7 +290,7 @@ def component(self, *args, **kwargs): ) def test_schema_lookup_from_string_custom_validator(self): - class MyService(object): + class MyService: def _get_simple_schema(self): return Validator( {"name": {"type": "string", "required": False}}, require_all=True @@ -322,7 +322,7 @@ def get_validator_handler(self, service, method_name, direction): def has_validator_handler(self, service, method_name, direction): return True - class MyService(object): + class MyService: def component(self, *args, **kwargs): return CustomBaseRestCerberusValidator(unittest.mock.Mock()) diff --git a/base_rest/tests/test_controller_builder.py b/base_rest/tests/test_controller_builder.py index 4a0d0cb3d..29b7ea8b3 100644 --- a/base_rest/tests/test_controller_builder.py +++ b/base_rest/tests/test_controller_builder.py @@ -55,7 +55,7 @@ def create(self, **params): return {"response": "POST called with message " + params["message"]} def delete(self, _id): - return {"response": "DELETE called with id %s " % _id} + return {"response": f"DELETE called with id {_id} "} def my_method(self, **params): pass @@ -513,7 +513,7 @@ def _validator_get(self): attr ], default, - "wrong %s" % attr, + f"wrong {attr}", ) self.assertEqual( getattr(routes["get_new_api_method_with"], ROUTING_DECORATOR_ATTR)["auth"], @@ -551,7 +551,7 @@ def _validator_get(self): attr ], default, - "wrong %s" % attr, + f"wrong {attr}", ) routing = getattr(routes["my_controller_route_with"], ROUTING_DECORATOR_ATTR) @@ -561,11 +561,10 @@ def _validator_get(self): ("csrf", "False"), ("save_session", "False"), ]: - self.assertEqual( routing[attr], value, - "wrong %s" % attr, + f"wrong {attr}", ) self.assertEqual( getattr( @@ -579,7 +578,8 @@ def test_05(self): """Test auth="public_or_default" on restapi.method The auth method on the route should be public_or_my_default_auth - since the ir.http model provides the _auth_method_public_or_my_default_auth methods + since the ir.http model provides the _auth_method_public_or_my_default_auth + methods """ default_auth = "my_default_auth" self._BaseTestController._default_auth = default_auth @@ -624,9 +624,9 @@ def _validator_get(self): def test_06(self): """Test auth="public_or_default" on restapi.method - The auth method on the route should be the default_auth configurerd on the controller - since the ir.http model doesn't provides the _auth_method_public_or_my_default_auth - methods + The auth method on the route should be the default_auth configured on the + controller since the ir.http model doesn't provide the + _auth_method_public_or_my_default_auth methods """ default_auth = "my_default_auth" self._BaseTestController._default_auth = default_auth diff --git a/base_rest/tests/test_openapi_generator.py b/base_rest/tests/test_openapi_generator.py index fb39e0b0b..f555d64c5 100644 --- a/base_rest/tests/test_openapi_generator.py +++ b/base_rest/tests/test_openapi_generator.py @@ -52,7 +52,7 @@ def _get_partner_schema(self): # The title is generated from the service usage # The service info must contains a title and a description info = openapi["info"] - self.assertEqual(info["title"], "%s REST services" % PartnerService._usage) + self.assertEqual(info["title"], f"{PartnerService._usage} REST services") self.assertEqual(info["description"], PartnerService._description) paths = openapi["paths"] diff --git a/base_rest/tests/test_service_context_provider.py b/base_rest/tests/test_service_context_provider.py index 08523b631..63807c008 100644 --- a/base_rest/tests/test_service_context_provider.py +++ b/base_rest/tests/test_service_context_provider.py @@ -46,9 +46,8 @@ def get(self, _id): self._build_services(self, TestServiceNewApi) controller = self._get_controller_for(TestServiceNewApi) - with MockRequest(self.env), controller().service_component( - "partner" - ) as service: + service_component = controller.service_component + with MockRequest(self.env), service_component("partner") as service: self.assertFalse(service.work.authenticated_partner_id) def test_02(self): @@ -91,9 +90,8 @@ def get(self, _id): self._build_components(TestComponentContextprovider) self._build_services(self, TestServiceNewApi) controller = self._get_controller_for(TestServiceNewApi) - with MockRequest(self.env), controller().service_component( - "partner" - ) as service: + service_component = controller.service_component + with MockRequest(self.env), service_component("partner") as service: self.assertEqual( service.work.authenticated_partner_id, self.env.user.partner_id.id ) @@ -137,14 +135,12 @@ def get(self, _id): self._build_components(TestComponentContextprovider) self._build_services(self, TestServiceNewApi) controller = self._get_controller_for(TestServiceNewApi) - with MockRequest(self.env), controller().service_component( - "partner" - ) as service: + service_component = controller.service_component + with MockRequest(self.env), service_component("partner") as service: self.assertEqual(service.work.authenticated_partner_id, 9999) class CommonCase(BaseRestCase): - # dummy test method to pass codecov def test_04(self): self.assertEqual(self.registry.test_cr, self.cr) diff --git a/base_rest/views/base_rest_view.xml b/base_rest/views/base_rest_view.xml index 13eedaac2..13ac1dbcd 100644 --- a/base_rest/views/base_rest_view.xml +++ b/base_rest/views/base_rest_view.xml @@ -2,10 +2,9 @@ - REST API - + base_rest,static/description/icon.png @@ -16,9 +15,9 @@ - + Docs - - + +