Skip to content
Snippets Groups Projects
Commit 641c8be4 authored by Vincent Hatakeyama's avatar Vincent Hatakeyama
Browse files

:hammer::sparkles: validator package does not assume a odoo.addons package name, provide full package name instead

parent a3cfdf8a
No related branches found
No related tags found
1 merge request!57🔨✨ validator package does not assume a odoo.addons package name, provide full package name instead
Changelog Changelog
========= =========
18.0.3.0.0
----------
Breaking change: validator package does not assume a odoo.addons package name, provide full package name instead.
18.0.2.2.0 18.0.2.2.0
---------- ----------
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
"name": "Converter", "name": "Converter",
"license": "AGPL-3", "license": "AGPL-3",
"summary": "Convert odoo records to/from plain data structures.", "summary": "Convert odoo records to/from plain data structures.",
"version": "18.0.2.2.0", "version": "18.0.3.0.0",
"category": "Hidden", "category": "Hidden",
"author": "XCG Consulting", "author": "XCG Consulting",
"website": "https://orbeet.io/", "website": "https://orbeet.io/",
......
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
"*.xml", "*.xml",
"*.py", "*.py",
"*.svg", "*.svg",
"*.png" "*.png",
"*.json"
] ]
[tool.hatch.build.targets.wheel.sources] [tool.hatch.build.targets.wheel.sources]
......
...@@ -6,4 +6,5 @@ ...@@ -6,4 +6,5 @@
test_mail_template, test_mail_template,
test_relation, test_relation,
test_switch, test_switch,
test_validate,
) )
import json
import pkgutil
from typing import Optional, Any
def get_schemas() -> list[Any]:
for file_prefix in ("product",):
data: bytes | None = pkgutil.get_data(__name__, f"{file_prefix}.schema.json")
if data:
yield json.loads(data)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"productId": {
"description": "The unique identifier for a product",
"type": "integer"
},
"productName": {
"description": "Name of the product",
"type": "string"
},
"price": {
"description": "The price of the product",
"type": "number",
"exclusiveMinimum": 0
},
"tags": {
"description": "Tags for the product",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": [
"productId",
"productName",
"price"
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"productId": {
"description": "The unique identifier for a product",
"type": "integer"
},
"productName": {
"description": "Name of the product",
"type": "string"
},
"price": {
"description": "The price of the product",
"type": "number",
"exclusiveMinimum": 0
},
"tags": {
"description": "Tags for the product",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": [
"productId",
"productName",
"price"
]
}
##############################################################################
#
# Converter Odoo module
# Copyright © 2024 XCG Consulting <https://xcg-consulting.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import json
from odoo import tests # type: ignore[import-untyped]
from ..validate import Validator
class TestValidate(tests.TransactionCase):
def test_validate(self):
validator = Validator(
"odoo.addons.converter.tests.schemas", "https://example.com/{}.schema.json"
)
validator.initialize()
validator.validate(
"product",
json.loads("""{
"productId": 1,
"productName": "An ice sculpture",
"price": 12.5,
"tags": [
"cold",
"ice"
]
}"""),
)
def test_validate_dir(self):
validator = Validator(
"odoo.addons.converter.tests",
"https://example.com/{}.schema.json",
"schemas_dir",
)
validator.initialize()
validator.validate(
"product",
json.loads("""{
"productId": 1,
"productName": "An ice sculpture",
"price": 12.5,
"tags": [
"cold",
"ice"
]
}"""),
)
...@@ -18,4 +18,5 @@ ...@@ -18,4 +18,5 @@
# #
############################################################################## ##############################################################################
import json import json
import logging
import os import os
...@@ -21,2 +22,3 @@ ...@@ -21,2 +22,3 @@
import os import os
from collections.abc import Callable
from enum import Enum from enum import Enum
...@@ -22,4 +24,5 @@ ...@@ -22,4 +24,5 @@
from enum import Enum from enum import Enum
from importlib import import_module
from typing import Any, LiteralString from typing import Any, LiteralString
import fastjsonschema # type: ignore[import-untyped] import fastjsonschema # type: ignore[import-untyped]
...@@ -23,7 +26,7 @@ ...@@ -23,7 +26,7 @@
from typing import Any, LiteralString from typing import Any, LiteralString
import fastjsonschema # type: ignore[import-untyped] import fastjsonschema # type: ignore[import-untyped]
import odoo.addons # type: ignore[import-untyped] _logger = logging.getLogger(__name__)
class Validation(str, Enum): class Validation(str, Enum):
...@@ -38,6 +41,13 @@ ...@@ -38,6 +41,13 @@
pass pass
def _add_schema(schemas, schema):
if "$id" in schema:
schemas[schema["$id"]] = schema
else:
_logger.warning("Schema without $id (schema ignored)")
class Validator: class Validator:
def __init__( def __init__(
self, self,
...@@ -41,6 +51,5 @@ ...@@ -41,6 +51,5 @@
class Validator: class Validator:
def __init__( def __init__(
self, self,
repository_module_name: str, package_name: str,
repository: str,
default_url_pattern: str, default_url_pattern: str,
...@@ -46,2 +55,3 @@ ...@@ -46,2 +55,3 @@
default_url_pattern: str, default_url_pattern: str,
directory: str | None = None,
): ):
...@@ -47,5 +57,10 @@ ...@@ -47,5 +57,10 @@
): ):
self.repository_module_name = repository_module_name """
self.repository = repository :param package_name: Package where the schema can be found
:param default_url_pattern: pattern for url ({} will be replaced by $id)
:param directory: directory to search for json, not used if a get_schema is
provided in the package.
"""
self.package_name = package_name
# exemple "https://annonces-legales.fr/xbus/schemas/v1/{}.schema.json" # exemple "https://annonces-legales.fr/xbus/schemas/v1/{}.schema.json"
self.default_url_pattern = default_url_pattern self.default_url_pattern = default_url_pattern
...@@ -50,5 +65,5 @@ ...@@ -50,5 +65,5 @@
# exemple "https://annonces-legales.fr/xbus/schemas/v1/{}.schema.json" # exemple "https://annonces-legales.fr/xbus/schemas/v1/{}.schema.json"
self.default_url_pattern = default_url_pattern self.default_url_pattern = default_url_pattern
self.validators: dict[LiteralString, Any] = {} self.validators: dict[LiteralString, Callable] = {}
self.initialized = False self.initialized = False
self.encoding = "UTF-8" self.encoding = "UTF-8"
...@@ -53,4 +68,5 @@ ...@@ -53,4 +68,5 @@
self.initialized = False self.initialized = False
self.encoding = "UTF-8" self.encoding = "UTF-8"
self.directory = directory
def initialize(self) -> None: def initialize(self) -> None:
...@@ -55,23 +71,27 @@ ...@@ -55,23 +71,27 @@
def initialize(self) -> None: def initialize(self) -> None:
# TODO Not working if module is installed compressed if self.initialized:
repo_module_basepath = os.path.dirname( return
getattr(odoo.addons, self.repository_module_name).__file__ schemas: dict[LiteralString, Any] = {}
) module = import_module(self.package_name)
if hasattr(module, "get_schemas"):
# Read local schema definitions. for schema in module.get_schemas():
schemas = {} _add_schema(schemas, schema)
schema_search_path = os.path.abspath( else:
os.path.join(repo_module_basepath, self.repository) # Fallback on searching schema json files
) schema_search_path = os.path.dirname(module.__file__)
for root, _dirs, files in os.walk(schema_search_path): schema_search_path = os.path.abspath(
for fname in files: os.path.join(schema_search_path, self.directory)
fpath = os.path.join(root, fname) if self.directory is not None
if fpath.endswith((".json",)): else schema_search_path
with open(fpath, encoding=self.encoding) as schema_fd: )
schema = json.load(schema_fd) for root, _dirs, files in os.walk(schema_search_path):
if "$id" in schema: for fname in files:
schemas[schema["$id"]] = schema fpath = os.path.join(root, fname)
if fpath.endswith((".json",)):
with open(fpath, encoding=self.encoding) as schema_fd:
schema = json.load(schema_fd)
_add_schema(schemas, schema)
# Prepare validators for each schema. We add an HTTPS handler that # Prepare validators for each schema. We add an HTTPS handler that
# points back to our schema definition cache built above. # points back to our schema definition cache built above.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment