Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -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)

View file

@ -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

View file

@ -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": []})

View file

@ -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)