mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 11:52:00 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1,80 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import inspect
|
||||
import textwrap
|
||||
|
||||
from apispec import APISpec
|
||||
|
||||
from ..core import _rest_services_databases
|
||||
from ..tools import ROUTING_DECORATOR_ATTR
|
||||
from .rest_method_param_plugin import RestMethodParamPlugin
|
||||
from .rest_method_security_plugin import RestMethodSecurityPlugin
|
||||
from .restapi_method_route_plugin import RestApiMethodRoutePlugin
|
||||
|
||||
|
||||
class BaseRestServiceAPISpec(APISpec):
|
||||
"""
|
||||
APISpec object from base.rest.service component
|
||||
"""
|
||||
|
||||
def __init__(self, service_component, **params):
|
||||
self._service = service_component
|
||||
super(BaseRestServiceAPISpec, self).__init__(
|
||||
title="%s REST services" % self._service._usage,
|
||||
version="",
|
||||
openapi_version="3.0.0",
|
||||
info={
|
||||
"description": textwrap.dedent(
|
||||
getattr(self._service, "_description", "") or ""
|
||||
)
|
||||
},
|
||||
servers=self._get_servers(),
|
||||
plugins=self._get_plugins(),
|
||||
)
|
||||
self._params = params
|
||||
|
||||
def _get_servers(self):
|
||||
env = self._service.env
|
||||
services_registry = _rest_services_databases.get(env.cr.dbname, {})
|
||||
collection_path = ""
|
||||
for path, spec in list(services_registry.items()):
|
||||
if spec["collection_name"] == self._service._collection:
|
||||
collection_path = path
|
||||
break
|
||||
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,
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
def _get_plugins(self):
|
||||
return [
|
||||
RestApiMethodRoutePlugin(self._service),
|
||||
RestMethodParamPlugin(self._service),
|
||||
RestMethodSecurityPlugin(self._service),
|
||||
]
|
||||
|
||||
def _add_method_path(self, method):
|
||||
description = textwrap.dedent(method.__doc__ or "")
|
||||
routing = getattr(method, ROUTING_DECORATOR_ATTR)
|
||||
for paths, method in routing["routes"]:
|
||||
for path in paths:
|
||||
self.path(
|
||||
path,
|
||||
operations={method.lower(): {"summary": description}},
|
||||
**{ROUTING_DECORATOR_ATTR: routing},
|
||||
)
|
||||
|
||||
def generate_paths(self):
|
||||
for _name, method in inspect.getmembers(self._service, inspect.ismethod):
|
||||
routing = getattr(method, ROUTING_DECORATOR_ATTR, None)
|
||||
if not routing:
|
||||
continue
|
||||
self._add_method_path(method)
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from apispec import BasePlugin
|
||||
|
||||
from ..restapi import RestMethodParam
|
||||
from ..tools import ROUTING_DECORATOR_ATTR
|
||||
|
||||
|
||||
class RestMethodParamPlugin(BasePlugin):
|
||||
"""
|
||||
APISpec plugin to generate path parameters and responses from a services
|
||||
method
|
||||
"""
|
||||
|
||||
def __init__(self, service):
|
||||
super(RestMethodParamPlugin, self).__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)
|
||||
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
|
||||
)
|
||||
if not operations:
|
||||
return
|
||||
for method, params in operations.items():
|
||||
parameters = self._generate_parameters(routing, method, params)
|
||||
if parameters:
|
||||
params["parameters"] = parameters
|
||||
responses = self._generate_responses(routing, method, params)
|
||||
if responses:
|
||||
params["responses"] = responses
|
||||
|
||||
def _generate_parameters(self, routing, method, params):
|
||||
parameters = params.get("parameters", [])
|
||||
# add default paramters provided by the sevice
|
||||
parameters.extend(self._default_parameters)
|
||||
input_param = routing.get("input_param")
|
||||
if input_param and isinstance(input_param, RestMethodParam):
|
||||
if method == "get":
|
||||
# get quey params from RequestMethodParam object
|
||||
parameters.extend(
|
||||
input_param.to_openapi_query_parameters(self._service, self.spec)
|
||||
)
|
||||
else:
|
||||
# get requestBody from RequestMethodParam object
|
||||
request_body = params.get("requestBody", {})
|
||||
request_body.update(
|
||||
input_param.to_openapi_requestbody(self._service, self.spec)
|
||||
)
|
||||
params["requestBody"] = request_body
|
||||
# sort paramters to ease comparison into unittests
|
||||
parameters.sort(key=lambda a: a["name"])
|
||||
return parameters
|
||||
|
||||
def _generate_responses(self, routing, method, params):
|
||||
responses = params.get("responses", {})
|
||||
# add default responses provided by the service
|
||||
responses.update(self._default_responses.copy())
|
||||
output_param = routing.get("output_param")
|
||||
if output_param and isinstance(output_param, RestMethodParam):
|
||||
responses = params.get("responses", {})
|
||||
# get response from RequestMethodParam object
|
||||
responses.update(self._default_responses.copy())
|
||||
responses.update(
|
||||
output_param.to_openapi_responses(self._service, self.spec)
|
||||
)
|
||||
return responses
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright 2021 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from apispec import BasePlugin
|
||||
|
||||
from ..tools import ROUTING_DECORATOR_ATTR
|
||||
|
||||
|
||||
class RestMethodSecurityPlugin(BasePlugin):
|
||||
"""
|
||||
APISpec plugin to generate path security from a services method
|
||||
"""
|
||||
|
||||
def __init__(self, service, user_auths=("user",)):
|
||||
super(RestMethodSecurityPlugin, self).__init__()
|
||||
self._service = service
|
||||
self._supported_user_auths = user_auths
|
||||
|
||||
# pylint: disable=W8110
|
||||
def init_spec(self, spec):
|
||||
super(RestMethodSecurityPlugin, self).init_spec(spec)
|
||||
self.spec = spec
|
||||
self.openapi_version = spec.openapi_version
|
||||
user_scheme = {"type": "apiKey", "in": "cookie", "name": "session_id"}
|
||||
spec.components.security_scheme("user", user_scheme)
|
||||
|
||||
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
|
||||
)
|
||||
if not operations:
|
||||
return
|
||||
auth = routing.get("auth", self.spec._params.get("default_auth"))
|
||||
if auth in self._supported_user_auths:
|
||||
for _method, params in operations.items():
|
||||
security = params.setdefault("security", [])
|
||||
security.append({"user": []})
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
import re
|
||||
|
||||
import werkzeug.routing
|
||||
from apispec import BasePlugin
|
||||
from werkzeug.routing import Map, Rule
|
||||
|
||||
# from flask-restplus
|
||||
RE_URL = re.compile(r"<(?:[^:<>]+:)?([^<>]+)>")
|
||||
|
||||
DEFAULT_CONVERTER_MAPPING = {
|
||||
werkzeug.routing.UnicodeConverter: ("string", None),
|
||||
werkzeug.routing.IntegerConverter: ("integer", "int32"),
|
||||
werkzeug.routing.FloatConverter: ("number", "float"),
|
||||
werkzeug.routing.UUIDConverter: ("string", "uuid"),
|
||||
}
|
||||
DEFAULT_TYPE = ("string", None)
|
||||
|
||||
|
||||
class RestApiMethodRoutePlugin(BasePlugin):
|
||||
"""
|
||||
APISpec plugin to generate path from a restapi.method route
|
||||
"""
|
||||
|
||||
def __init__(self, service):
|
||||
super(RestApiMethodRoutePlugin, self).__init__()
|
||||
self.converter_mapping = dict(DEFAULT_CONVERTER_MAPPING)
|
||||
self._service = service
|
||||
|
||||
@staticmethod
|
||||
def route2openapi(path):
|
||||
"""Convert an odoo route to an OpenAPI-compliant path.
|
||||
|
||||
:param str path: Odoo route path template.
|
||||
"""
|
||||
return RE_URL.sub(r"{\1}", path)
|
||||
|
||||
# Greatly inspired by flask-apispec
|
||||
def route_to_params(self, route):
|
||||
"""Get parameters from Odoo route"""
|
||||
# odoo route are Werkzeug Rule
|
||||
rule = Rule(route)
|
||||
Map(rules=[rule])
|
||||
|
||||
params = []
|
||||
for argument in rule.arguments:
|
||||
param = {"in": "path", "name": argument, "required": True}
|
||||
type_, format_ = self.converter_mapping.get(
|
||||
type(rule._converters[argument]), DEFAULT_TYPE
|
||||
)
|
||||
schema = {"type": type_}
|
||||
if format_ is not None:
|
||||
schema["format"] = format_
|
||||
param["schema"] = schema
|
||||
params.append(param)
|
||||
return params
|
||||
|
||||
def path_helper(self, path, operations, parameters, **kwargs):
|
||||
for param in self.route_to_params(path):
|
||||
parameters.append(param)
|
||||
return self.route2openapi(path)
|
||||
Loading…
Add table
Add a link
Reference in a new issue