]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
utils: modeling utils moved to separate directory
authorAleš Mrázek <ales.mrazek@nic.cz>
Fri, 8 Jul 2022 20:11:03 +0000 (22:11 +0200)
committerAleš Mrázek <ales.mrazek@nic.cz>
Fri, 8 Jul 2022 20:11:03 +0000 (22:11 +0200)
42 files changed:
manager/knot_resolver_manager/client/__init__.py
manager/knot_resolver_manager/client/__main__.py
manager/knot_resolver_manager/config_store.py
manager/knot_resolver_manager/datamodel/cache_schema.py
manager/knot_resolver_manager/datamodel/config_schema.py
manager/knot_resolver_manager/datamodel/dns64_schema.py
manager/knot_resolver_manager/datamodel/dnssec_schema.py
manager/knot_resolver_manager/datamodel/forward_zone_schema.py
manager/knot_resolver_manager/datamodel/logging_schema.py
manager/knot_resolver_manager/datamodel/lua_schema.py
manager/knot_resolver_manager/datamodel/management_schema.py
manager/knot_resolver_manager/datamodel/monitoring_schema.py
manager/knot_resolver_manager/datamodel/network_schema.py
manager/knot_resolver_manager/datamodel/options_schema.py
manager/knot_resolver_manager/datamodel/policy_schema.py
manager/knot_resolver_manager/datamodel/rpz_schema.py
manager/knot_resolver_manager/datamodel/slice_schema.py
manager/knot_resolver_manager/datamodel/static_hints_schema.py
manager/knot_resolver_manager/datamodel/stub_zone_schema.py
manager/knot_resolver_manager/datamodel/types/base_types.py
manager/knot_resolver_manager/datamodel/types/types.py
manager/knot_resolver_manager/datamodel/view_schema.py
manager/knot_resolver_manager/datamodel/webmgmt_schema.py
manager/knot_resolver_manager/exceptions.py
manager/knot_resolver_manager/kres_manager.py
manager/knot_resolver_manager/server.py
manager/knot_resolver_manager/utils/__init__.py
manager/knot_resolver_manager/utils/modeling/__init__.py [new file with mode: 0644]
manager/knot_resolver_manager/utils/modeling/custom_value_type.py [moved from manager/knot_resolver_manager/utils/custom_types.py with 96% similarity]
manager/knot_resolver_manager/utils/modeling/exceptions.py [new file with mode: 0644]
manager/knot_resolver_manager/utils/modeling/parsed_tree.py [moved from manager/knot_resolver_manager/utils/parsing.py with 91% similarity]
manager/knot_resolver_manager/utils/modeling/schema_node.py [moved from manager/knot_resolver_manager/utils/modelling.py with 90% similarity]
manager/knot_resolver_manager/utils/modeling/types.py [moved from manager/knot_resolver_manager/utils/types.py with 100% similarity]
manager/setup.py
manager/tests/unit/datamodel/test_lua_schema.py
manager/tests/unit/datamodel/test_management_schema.py
manager/tests/unit/datamodel/test_network_schema.py
manager/tests/unit/datamodel/test_policy_schema.py
manager/tests/unit/datamodel/test_rpz_schema.py
manager/tests/unit/datamodel/types/test_custom_types.py
manager/tests/unit/utils/test_modeling.py [moved from manager/tests/unit/utils/test_modelling.py with 92% similarity]
manager/tests/unit/utils/test_types.py

index ae7f312766dc2786e279c8d0ee6d73e52b63f784..70433c972e9e479e5763dc81c4fabce1e0ae1516 100644 (file)
@@ -11,7 +11,7 @@ import requests
 
 from knot_resolver_manager import compat
 from knot_resolver_manager.server import start_server
-from knot_resolver_manager.utils.parsing import ParsedTree
+from knot_resolver_manager.utils.modeling import ParsedTree
 
 
 class KnotManagerClient:
index 0e14488f3d8a61cbdf0ff55c1dd5620c77eaffbe..1c1ba5a5da8d4c989ea36f42be3bf42f3b89b381 100644 (file)
@@ -7,7 +7,7 @@ from click.exceptions import ClickException
 from knot_resolver_manager.client import KnotManagerClient
 from knot_resolver_manager.datamodel.config_schema import KresConfig
 from knot_resolver_manager.exceptions import KresManagerException
-from knot_resolver_manager.utils.parsing import parse_yaml
+from knot_resolver_manager.utils.modeling import parse_yaml
 
 BASE_URL = "base_url"
 
index 43d5d960e8235c6b25cd930d5507077e13b48155..6a8d58e06a1f5183399a422a9ceb220b7eb9d5d5 100644 (file)
@@ -3,8 +3,9 @@ from asyncio import Lock
 from typing import Any, Awaitable, Callable, List, Tuple
 
 from knot_resolver_manager.datamodel import KresConfig
-from knot_resolver_manager.exceptions import DataException, KresManagerException
+from knot_resolver_manager.exceptions import KresManagerException
 from knot_resolver_manager.utils.functional import Result
+from knot_resolver_manager.utils.modeling.exceptions import DataParsingError
 
 VerifyCallback = Callable[[KresConfig, KresConfig], Awaitable[Result[None, str]]]
 UpdateCallback = Callable[[KresConfig], Awaitable[None]]
@@ -39,7 +40,7 @@ class ConfigStore:
         self._verifiers.append(verifier)
         res = await verifier(self.get(), self.get())
         if res.is_err():
-            raise DataException(f"Initial config verification failed with error: {res.unwrap_err()}")
+            raise DataParsingError(f"Initial config verification failed with error: {res.unwrap_err()}")
 
     async def register_on_change_callback(self, callback: UpdateCallback) -> None:
         """
index 87901433365cd4f08810e1d8b39d41195199aafa..6b2a73419b3be91e46e54ed3d5c9bfed91cbab59 100644 (file)
@@ -1,7 +1,7 @@
 from typing import List, Optional
 
 from knot_resolver_manager.datamodel.types import CheckedPath, DomainName, SizeUnit, TimeUnit
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class PrefillSchema(SchemaNode):
index 6fcc0888b91228f8b52654d9363a4d954ea2e015..dfcf82814f9976ba36adba16c5f28683dcd531f5 100644 (file)
@@ -26,8 +26,7 @@ from knot_resolver_manager.datamodel.stub_zone_schema import StubZoneSchema
 from knot_resolver_manager.datamodel.types.types import IntPositive, UncheckedPath
 from knot_resolver_manager.datamodel.view_schema import ViewSchema
 from knot_resolver_manager.datamodel.webmgmt_schema import WebmgmtSchema
-from knot_resolver_manager.exceptions import DataException
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 logger = logging.getLogger(__name__)
 
@@ -61,21 +60,14 @@ def _import_lua_template() -> Template:
 _MAIN_TEMPLATE = _import_lua_template()
 
 
-def _cpu_count() -> int:
+def _cpu_count() -> Optional[int]:
     try:
         return len(os.sched_getaffinity(0))
     except (NotImplementedError, AttributeError):
-        logger.warning(
-            "The number of usable CPUs could not be determined using 'os.sched_getaffinity()'."
-            "Attempting to get the number of system CPUs using 'os.cpu_count()'"
-        )
+        logger.warning("The number of usable CPUs could not be determined using 'os.sched_getaffinity()'.")
         cpus = os.cpu_count()
         if cpus is None:
-            raise DataException(
-                "The number of available CPUs to automatically set the number of running"
-                "'kresd' workers could not be determined."
-                "The number can be specified manually in 'server:instances' configuration option."
-            )
+            logger.warning("The number of usable CPUs could not be determined using 'os.cpu_count()'.")
         return cpus
 
 
@@ -164,7 +156,13 @@ class KresConfig(SchemaNode):
 
     def _workers(self, obj: Raw) -> Any:
         if obj.workers == "auto":
-            return IntPositive(_cpu_count())
+            count = _cpu_count()
+            if count:
+                return IntPositive(count)
+            raise ValueError(
+                "The number of available CPUs to automatically set the number of running 'kresd' workers could not be determined."
+                "The number of workers can be configured manually in 'workers' option."
+            )
         return obj.workers
 
     def _dnssec(self, obj: Raw) -> Any:
@@ -178,13 +176,12 @@ class KresConfig(SchemaNode):
         return obj.dns64
 
     def _validate(self) -> None:
-        try:
-            cpu_count = _cpu_count()
-            if int(self.workers) > 10 * cpu_count:
-                raise ValueError("refusing to run with more then instances 10 instances per cpu core")
-        except DataException:
-            # sometimes, we won't be able to get information about the cpu count
-            pass
+        cpu_count = _cpu_count()
+
+        if cpu_count and int(self.workers) > 10 * cpu_count:
+            raise ValueError("refusing to run with more then 10 workers per cpu core")
+        elif int(self.workers) > MAX_WORKERS:
+            raise ValueError(f"refusing to run with more workers then allowed maximum {MAX_WORKERS}")
 
     def render_lua(self) -> str:
         # FIXME the `cwd` argument is used only for configuring control socket path
index ec3385f1748899d426bceb63a0f1651c3bb180fc..b6cdb31536104c78d98be91acdb957d663ff68cf 100644 (file)
@@ -1,5 +1,5 @@
 from knot_resolver_manager.datamodel.types import IPv6Network96
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class Dns64Schema(SchemaNode):
index add4eeebe6b195a0eb0e57e820f5df5963503fc0..ab9e325b06adfce8bcc735325e413653a230e85d 100644 (file)
@@ -1,7 +1,7 @@
 from typing import List, Optional
 
 from knot_resolver_manager.datamodel.types import IntNonNegative, TimeUnit
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class TrustAnchorFileSchema(SchemaNode):
index ab55371a3aa1c52b3b026f6b6f6cdc5f99739817..04acef15497692b49cc7090e230e58b289861b59 100644 (file)
@@ -2,7 +2,7 @@ from typing import List, Optional, Union
 
 from knot_resolver_manager.datamodel.policy_schema import ForwardServerSchema
 from knot_resolver_manager.datamodel.types import DomainName, IPAddressOptionalPort, PolicyFlagEnum
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class ForwardZoneSchema(SchemaNode):
index cc405be1fd3821b0e64fdcf31da95795ade74815..22d390c484c9b6931c9a6b83b0a58147010915dd 100644 (file)
@@ -3,7 +3,7 @@ from typing import List, Optional, Set, Union
 from typing_extensions import Literal, TypeAlias
 
 from knot_resolver_manager.datamodel.types import CheckedPath, TimeUnit
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 LogLevelEnum = Literal["crit", "err", "warning", "notice", "info", "debug"]
 LogTargetEnum = Literal["syslog", "stderr", "stdout"]
index 9de468f413326d3ec96ae090a042ba84c5125180..7dfb0d62e81326af590f69a33c1722f32bb4f591 100644 (file)
@@ -1,6 +1,6 @@
 from typing import Optional
 
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class LuaSchema(SchemaNode):
index 8c6d8477a74421fa50e1977b916948f159b95410..75b8613f02b1caf4e586724dfb4fb8850819348a 100644 (file)
@@ -1,7 +1,7 @@
 from typing import Optional
 
 from knot_resolver_manager.datamodel.types import CheckedPath, IPAddressPort
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class ManagementSchema(SchemaNode):
index 80859b4c3f511924f640c09af8df209546a3714a..adc40640742e9f5937ac257a0178d69fc6ce260a 100644 (file)
@@ -3,7 +3,7 @@ from typing import Union
 from typing_extensions import Literal
 
 from knot_resolver_manager.datamodel.types import DomainName, IPAddress, PortNumber, TimeUnit
-from knot_resolver_manager.utils.modelling import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class GraphiteSchema(SchemaNode):
index 9bc970807a290896805b510416c1f4fd213918d9..d4f296d5d2cbbb5c631dd55ea4d87a48239eb563 100644 (file)
@@ -14,7 +14,7 @@ from knot_resolver_manager.datamodel.types import (
     PortNumber,
     SizeUnit,
 )
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 KindEnum = Literal["dns", "xdp", "dot", "doh-legacy", "doh2"]
 
index 12b99ef28bce8d7e243880940dd31f4e3836deaa..6d23248e580e8273fdc77c159f4b74bc7a15b794 100644 (file)
@@ -3,7 +3,7 @@ from typing import Any, Union
 from typing_extensions import Literal
 
 from knot_resolver_manager.datamodel.types import IntNonNegative, TimeUnit
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 GlueCheckingEnum = Literal["normal", "strict", "permissive"]
 
index a100c8ad1dacb63abc120bc812e2bfc166a2b14d..d17d462af3c2374969d12e24c146fd987a8f6a35 100644 (file)
@@ -10,7 +10,7 @@ from knot_resolver_manager.datamodel.types import (
     PolicyFlagEnum,
     TimeUnit,
 )
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class FilterSchema(SchemaNode):
index 4a951c6f3659ef51de2b9f4c06a4f7262a474de5..078a4aad2030f7555dc83730baebe757a9e1c484 100644 (file)
@@ -1,7 +1,7 @@
 from typing import List, Optional
 
 from knot_resolver_manager.datamodel.types import CheckedPath, PolicyActionEnum, PolicyFlagEnum
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class RPZSchema(SchemaNode):
index 68f3bd2eb49374dcc9c0cf0612d1a45e2cb2f0a9..8606da08231ce56f16819e3f567e31415035cf71 100644 (file)
@@ -3,7 +3,7 @@ from typing import List, Optional
 from typing_extensions import Literal
 
 from knot_resolver_manager.datamodel.policy_schema import ActionSchema
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class SliceSchema(SchemaNode):
index 9529fde0a1ad7faea832392f02841b4c4faa09ef..33144c083e9eda7533b601a5b17464f20cc34e5d 100644 (file)
@@ -1,7 +1,7 @@
 from typing import Dict, List, Optional
 
 from knot_resolver_manager.datamodel.types import CheckedPath, DomainName, IPAddress, TimeUnit
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class StaticHintsSchema(SchemaNode):
index 0d41f2f29867a23046bb704d112fb36558d2cb80..f3be8f8b642af539796164252d1bda6de585f42b 100644 (file)
@@ -1,7 +1,7 @@
 from typing import List, Optional, Union
 
 from knot_resolver_manager.datamodel.types import DomainName, IPAddressOptionalPort, PolicyFlagEnum
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class StubServerSchema(SchemaNode):
index 954b94a9a06e99112041693cafed575fd3ceee03..1ceab0a87610749dc66743878a9cefcc8bf6d466 100644 (file)
@@ -1,8 +1,8 @@
 import re
 from typing import Any, Dict, Pattern, Type
 
-from knot_resolver_manager.exceptions import SchemaException
-from knot_resolver_manager.utils import CustomValueType
+from knot_resolver_manager.utils.modeling import CustomValueType
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 class IntBase(CustomValueType):
@@ -75,12 +75,12 @@ class IntRangeBase(IntBase):
         super().__init__(source_value)
         if isinstance(source_value, int) and not isinstance(source_value, bool):
             if hasattr(self, "_min") and (source_value < self._min):
-                raise SchemaException(f"value {source_value} is lower than the minimum {self._min}.", object_path)
+                raise DataValidationError(f"value {source_value} is lower than the minimum {self._min}.", object_path)
             if hasattr(self, "_max") and (source_value > self._max):
-                raise SchemaException(f"value {source_value} is higher than the maximum {self._max}", object_path)
+                raise DataValidationError(f"value {source_value} is higher than the maximum {self._max}", object_path)
             self._value = source_value
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 f"expected integer, got '{type(source_value)}'",
                 object_path,
             )
@@ -112,9 +112,9 @@ class PatternBase(StrBase):
             if type(self)._re.match(source_value):
                 self._value: str = source_value
             else:
-                raise SchemaException(f"'{source_value}' does not match '{self._re.pattern}' pattern", object_path)
+                raise DataValidationError(f"'{source_value}' does not match '{self._re.pattern}' pattern", object_path)
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 f"expected string, got '{type(source_value)}'",
                 object_path,
             )
@@ -146,25 +146,25 @@ class UnitBase(IntBase):
             if grouped:
                 val, unit = grouped.groups()
                 if unit is None:
-                    raise SchemaException(
+                    raise DataValidationError(
                         f"Missing units. Accepted units are {list(type(self)._units.keys())}", object_path
                     )
                 elif unit not in type(self)._units:
-                    raise SchemaException(
+                    raise DataValidationError(
                         f"Used unexpected unit '{unit}' for {type(self).__name__}."
                         f" Accepted units are {list(type(self)._units.keys())}",
                         object_path,
                     )
                 self._value = int(val) * type(self)._units[unit]
             else:
-                raise SchemaException(f"{type(self._value)} Failed to convert: {self}", object_path)
+                raise DataValidationError(f"{type(self._value)} Failed to convert: {self}", object_path)
         elif isinstance(source_value, int):
-            raise SchemaException(
+            raise DataValidationError(
                 f"number without units, please convert to string and add unit  - {list(type(self)._units.keys())}",
                 object_path,
             )
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 f"expected number with units in a string, got '{type(source_value)}'.",
                 object_path,
             )
index 42f8296316658ab84e2713b4cdbc2ef27f28e79f..c88aa026a71e7de6de9aefa5648b683f3fc00c0d 100644 (file)
@@ -4,8 +4,8 @@ from pathlib import Path
 from typing import Any, Dict, Optional, Type, Union
 
 from knot_resolver_manager.datamodel.types.base_types import IntRangeBase, PatternBase, StrBase, UnitBase
-from knot_resolver_manager.exceptions import SchemaException
-from knot_resolver_manager.utils import CustomValueType
+from knot_resolver_manager.utils.modeling import CustomValueType
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 class IntNonNegative(IntRangeBase):
@@ -35,7 +35,7 @@ class PortNumber(IntRangeBase):
         try:
             return cls(int(port), object_path)
         except ValueError as e:
-            raise SchemaException(f"invalid port number {port}", object_path) from e
+            raise DataValidationError(f"invalid port number {port}", object_path) from e
 
 
 class SizeUnit(UnitBase):
@@ -77,7 +77,7 @@ class DomainName(StrBase):
             try:
                 punycode = source_value.encode("idna").decode("utf-8") if source_value != "." else "."
             except ValueError:
-                raise SchemaException(
+                raise DataValidationError(
                     f"conversion of '{source_value}' to IDN punycode representation failed",
                     object_path,
                 )
@@ -86,12 +86,12 @@ class DomainName(StrBase):
                 self._value = source_value
                 self._punycode = punycode
             else:
-                raise SchemaException(
+                raise DataValidationError(
                     f"'{source_value}' represented in punycode '{punycode}' does not match '{self._re.pattern}' pattern",
                     object_path,
                 )
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for '<domain-name>'."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -137,18 +137,18 @@ class InterfacePort(StrBase):
                 except ValueError as e1:
                     try:
                         self.if_name = InterfaceName(parts[0])
-                    except SchemaException as e2:
-                        raise SchemaException(
+                    except DataValidationError as e2:
+                        raise DataValidationError(
                             f"expected IP address or interface name, got '{parts[0]}'.", object_path
                         ) from e1 and e2
                 self.port = PortNumber.from_str(parts[1], object_path)
             else:
-                raise SchemaException(
+                raise DataValidationError(
                     f"expected '<ip-address|interface-name>@<port>', got '{source_value}'.", object_path
                 )
             self._value = source_value
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for '<ip-address|interface-name>@<port>'."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -170,17 +170,19 @@ class InterfaceOptionalPort(StrBase):
                 except ValueError as e1:
                     try:
                         self.if_name = InterfaceName(parts[0])
-                    except SchemaException as e2:
-                        raise SchemaException(
+                    except DataValidationError as e2:
+                        raise DataValidationError(
                             f"expected IP address or interface name, got '{parts[0]}'.", object_path
                         ) from e1 and e2
                 if len(parts) == 2:
                     self.port = PortNumber.from_str(parts[1], object_path)
             else:
-                raise SchemaException(f"expected '<ip-address|interface-name>[@<port>]', got '{parts}'.", object_path)
+                raise DataValidationError(
+                    f"expected '<ip-address|interface-name>[@<port>]', got '{parts}'.", object_path
+                )
             self._value = source_value
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for '<ip-address|interface-name>[@<port>]'."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -200,12 +202,12 @@ class IPAddressPort(StrBase):
                 try:
                     self.addr = ipaddress.ip_address(parts[0])
                 except ValueError as e:
-                    raise SchemaException(f"failed to parse IP address '{parts[0]}'.", object_path) from e
+                    raise DataValidationError(f"failed to parse IP address '{parts[0]}'.", object_path) from e
             else:
-                raise SchemaException(f"expected '<ip-address>@<port>', got '{source_value}'.", object_path)
+                raise DataValidationError(f"expected '<ip-address>@<port>', got '{source_value}'.", object_path)
             self._value = source_value
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for '<ip-address>@<port>'."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -224,14 +226,14 @@ class IPAddressOptionalPort(StrBase):
                 try:
                     self.addr = ipaddress.ip_address(parts[0])
                 except ValueError as e:
-                    raise SchemaException(f"failed to parse IP address '{parts[0]}'.", object_path) from e
+                    raise DataValidationError(f"failed to parse IP address '{parts[0]}'.", object_path) from e
                 if len(parts) == 2:
                     self.port = PortNumber.from_str(parts[1], object_path)
             else:
-                raise SchemaException(f"expected '<ip-address>[@<port>]', got '{parts}'.", object_path)
+                raise DataValidationError(f"expected '<ip-address>[@<port>]', got '{parts}'.", object_path)
             self._value = source_value
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for a '<ip-address>[@<port>]'."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -247,9 +249,9 @@ class IPv4Address(CustomValueType):
             try:
                 self._value: ipaddress.IPv4Address = ipaddress.IPv4Address(source_value)
             except ValueError as e:
-                raise SchemaException("failed to parse IPv4 address.", object_path) from e
+                raise DataValidationError("failed to parse IPv4 address.", object_path) from e
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for a IPv4 address."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -289,9 +291,9 @@ class IPv6Address(CustomValueType):
             try:
                 self._value: ipaddress.IPv6Address = ipaddress.IPv6Address(source_value)
             except ValueError as e:
-                raise SchemaException("failed to parse IPv6 address.", object_path) from e
+                raise DataValidationError("failed to parse IPv6 address.", object_path) from e
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for a IPv6 address."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -334,9 +336,9 @@ class IPNetwork(CustomValueType):
             try:
                 self._value: Union[ipaddress.IPv4Network, ipaddress.IPv6Network] = ipaddress.ip_network(source_value)
             except ValueError as e:
-                raise SchemaException("failed to parse IP network.", object_path) from e
+                raise DataValidationError("failed to parse IP network.", object_path) from e
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for a network subnet."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -370,10 +372,10 @@ class IPv6Network96(CustomValueType):
             try:
                 self._value: ipaddress.IPv6Network = ipaddress.IPv6Network(source_value)
             except ValueError as e:
-                raise SchemaException("failed to parse IPv6 /96 network.", object_path) from e
+                raise DataValidationError("failed to parse IPv6 /96 network.", object_path) from e
 
             if self._value.prefixlen == 128:
-                raise SchemaException(
+                raise DataValidationError(
                     "Expected IPv6 network address with /96 prefix length."
                     " Submitted address has been interpreted as /128."
                     " Maybe, you forgot to add /96 after the base address?",
@@ -381,13 +383,13 @@ class IPv6Network96(CustomValueType):
                 )
 
             if self._value.prefixlen != 96:
-                raise SchemaException(
+                raise DataValidationError(
                     "expected IPv6 network address with /96 prefix length."
                     f" Got prefix lenght of {self._value.prefixlen}",
                     object_path,
                 )
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 "Unexpected value for a network subnet."
                 f" Expected string, got '{source_value}' with type '{type(source_value)}'",
                 object_path,
@@ -426,7 +428,7 @@ class UncheckedPath(CustomValueType):
         if isinstance(source_value, str):
             self._value: Path = Path(source_value)
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 f"expected file path in a string, got '{source_value}' with type '{type(source_value)}'.", object_path
             )
 
@@ -466,4 +468,4 @@ class CheckedPath(UncheckedPath):
         try:
             self._value = self._value.resolve(strict=False)
         except RuntimeError as e:
-            raise SchemaException("Failed to resolve given file path. Is there a symlink loop?", object_path) from e
+            raise DataValidationError("Failed to resolve given file path. Is there a symlink loop?", object_path) from e
index c6027535d11b8b9deec7a9e521e5bf6331b529df..caac0940e07627ba49f18d206e3b7d6261c62457 100644 (file)
@@ -1,7 +1,7 @@
 from typing import List, Optional
 
 from knot_resolver_manager.datamodel.types import IPNetwork, PolicyFlagEnum
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class ViewSchema(SchemaNode):
index b2af0e19ff4b835076cb99f09f8d0146ebf9effe..b745f95726d87e0043dca45b9d7b4ef8314cc7b5 100644 (file)
@@ -1,7 +1,7 @@
 from typing import Optional
 
 from knot_resolver_manager.datamodel.types import CheckedPath, InterfacePort
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
 
 
 class WebmgmtSchema(SchemaNode):
index 9dbaeb863ed9fc9255b1d12127371a9ed2dc0036..5b05d98ebace7908692600e1e6fd81b68a382689 100644 (file)
@@ -1,4 +1,4 @@
-from typing import Iterable, List
+from typing import List
 
 
 class CancelStartupExecInsteadException(Exception):
@@ -26,50 +26,3 @@ class SubprocessControllerException(KresManagerException):
 
 class SubprocessControllerTimeoutException(KresManagerException):
     pass
-
-
-class SchemaException(KresManagerException):
-    def __init__(self, msg: str, tree_path: str, child_exceptions: "Iterable[SchemaException]" = tuple()) -> None:
-        super().__init__(msg)
-        self._tree_path = tree_path
-        self._child_exceptions = child_exceptions
-
-    def where(self) -> str:
-        return self._tree_path
-
-    def msg(self):
-        return f"[{self.where()}] " + super().__str__()
-
-    def recursive_msg(self, indentation_level: int = 0) -> str:
-        INDENT = indentation_level * "\t"
-        msg_parts: List[str] = [f"{INDENT}{self.msg()}"]
-        for c in self._child_exceptions:
-            msg_parts.append(c.recursive_msg(indentation_level + 1))
-        return "\n".join(msg_parts)
-
-    def __str__(self) -> str:
-        return self.recursive_msg()
-
-
-class AggregateSchemaException(SchemaException):
-    def __init__(self, object_path: str, child_exceptions: "Iterable[SchemaException]") -> None:
-        super().__init__("error due to lower level exceptions", object_path, child_exceptions)
-
-    def recursive_msg(self, indentation_level: int = 0) -> str:
-        inc = 0
-        msg_parts: List[str] = []
-        if indentation_level == 0:
-            inc = 1
-            msg_parts.append("multiple configuration errors detected:")
-
-        for c in self._child_exceptions:
-            msg_parts.append(c.recursive_msg(indentation_level + inc))
-        return "\n".join(msg_parts)
-
-
-class DataException(KresManagerException):
-    pass
-
-
-class ParsingException(KresManagerException):
-    pass
index a94fb6af70e640cace48737bab39545b137d93f2..ec88b2949d1b3461a0eee6a0fe28a88ef33ac761 100644 (file)
@@ -20,7 +20,7 @@ from knot_resolver_manager.kresd_controller.interface import (
     SubprocessType,
 )
 from knot_resolver_manager.utils.functional import Result
-from knot_resolver_manager.utils.types import NoneType
+from knot_resolver_manager.utils.modeling.types import NoneType
 
 from .datamodel import KresConfig
 
index f26763f73e186d2f1acb155169f2939ea7d2bb78..a65bed3d31fed60cc7e520a4117597f85bb120ea 100644 (file)
@@ -22,18 +22,14 @@ from knot_resolver_manager.config_store import ConfigStore
 from knot_resolver_manager.constants import DEFAULT_MANAGER_CONFIG_FILE, PID_FILE_NAME, init_user_constants
 from knot_resolver_manager.datamodel.config_schema import KresConfig
 from knot_resolver_manager.datamodel.management_schema import ManagementSchema
-from knot_resolver_manager.exceptions import (
-    CancelStartupExecInsteadException,
-    DataException,
-    KresManagerException,
-    SchemaException,
-)
+from knot_resolver_manager.exceptions import CancelStartupExecInsteadException, KresManagerException
 from knot_resolver_manager.kresd_controller import get_best_controller_implementation
 from knot_resolver_manager.utils.async_utils import readfile
 from knot_resolver_manager.utils.functional import Result
-from knot_resolver_manager.utils.parsing import ParsedTree, parse, parse_yaml
+from knot_resolver_manager.utils.modeling import ParsedTree, parse, parse_yaml
+from knot_resolver_manager.utils.modeling.exceptions import DataParsingError, DataValidationError
+from knot_resolver_manager.utils.modeling.types import NoneType
 from knot_resolver_manager.utils.systemd_notify import systemd_notify
-from knot_resolver_manager.utils.types import NoneType
 
 from .kres_manager import KresManager
 
@@ -51,12 +47,11 @@ async def error_handler(request: web.Request, handler: Any) -> web.Response:
 
     try:
         return await handler(request)
+    except DataValidationError as e:
+        return web.Response(text=f"validation of configuration failed: {e}", status=HTTPStatus.BAD_REQUEST)
     except KresManagerException as e:
-        if isinstance(e, (SchemaException, DataException)):
-            return web.Response(text=f"validation of configuration failed: {e}", status=HTTPStatus.BAD_REQUEST)
-        else:
-            logger.error("Request processing failed", exc_info=True)
-            return web.Response(text=f"Request processing failed: {e}", status=HTTPStatus.INTERNAL_SERVER_ERROR)
+        logger.error("Request processing failed", exc_info=True)
+        return web.Response(text=f"Request processing failed: {e}", status=HTTPStatus.INTERNAL_SERVER_ERROR)
 
 
 class Server:
@@ -106,7 +101,7 @@ class Server:
         else:
             try:
                 data = await readfile(self._config_path)
-                config = KresConfig(parse_yaml(data))
+                config = KresConfig(ParsedTree(data))
                 await self.config_store.update(config)
                 logger.info("Configuration file successfully reloaded")
             except FileNotFoundError:
@@ -115,7 +110,7 @@ class Server:
                     " Something must have happened to it while we were running."
                 )
                 logger.error("Configuration have NOT been changed.")
-            except SchemaException as e:
+            except (DataParsingError, DataValidationError) as e:
                 logger.error(f"Failed to parse the updated configuration file: {e}")
                 logger.error("Configuration have NOT been changed.")
             except KresManagerException as e:
index e748a6349fb81c6dca3b11aa14b8e4e85676da09..062b740dc69f346420f46b116d216bbf0c952df9 100644 (file)
@@ -1,8 +1,5 @@
 from typing import Any, Callable, Optional, Type, TypeVar
 
-from .custom_types import CustomValueType
-from .modelling import SchemaNode
-
 T = TypeVar("T")
 
 
@@ -46,9 +43,3 @@ def phantom_use(var: Any) -> None:  # pylint: disable=unused-argument
     Function, which consumes its argument doing absolutely nothing with it. Useful
     for convincing pylint, that we need the variable even when its unused.
     """
-
-
-__all__ = [
-    "CustomValueType",
-    "SchemaNode",
-]
diff --git a/manager/knot_resolver_manager/utils/modeling/__init__.py b/manager/knot_resolver_manager/utils/modeling/__init__.py
new file mode 100644 (file)
index 0000000..cbe0178
--- /dev/null
@@ -0,0 +1,12 @@
+from .custom_value_type import CustomValueType
+from .parsed_tree import ParsedTree, parse, parse_json, parse_yaml
+from .schema_node import SchemaNode
+
+__all__ = [
+    "CustomValueType",
+    "SchemaNode",
+    "ParsedTree",
+    "parse",
+    "parse_yaml",
+    "parse_json",
+]
similarity index 96%
rename from manager/knot_resolver_manager/utils/custom_types.py
rename to manager/knot_resolver_manager/utils/modeling/custom_value_type.py
index ea17c49e708929ea36600f5fe275283db96b51ef..4b0024ce7eb98fc5c7f886840a1ec1d6ebf57a9f 100644 (file)
@@ -18,7 +18,7 @@ class CustomValueType:
 
     There is no validation done on the wrapped value. The only condition is that
     it can't be `None`. If you want to perform any validation during creation,
-    raise a `SchemaException` in case of errors.
+    raise a `DataValidationError` in case of errors.
     """
 
     def __init__(self, source_value: Any, object_path: str = "/") -> None:
diff --git a/manager/knot_resolver_manager/utils/modeling/exceptions.py b/manager/knot_resolver_manager/utils/modeling/exceptions.py
new file mode 100644 (file)
index 0000000..77b9ca7
--- /dev/null
@@ -0,0 +1,54 @@
+from typing import Iterable, List
+
+
+class DataModelingBaseException(Exception):
+    """
+    Base class for all exceptions used in modelling.
+    """
+
+
+class DataParsingError(DataModelingBaseException):
+    pass
+
+
+class DataDescriptionError(DataModelingBaseException):
+    pass
+
+
+class DataValidationError(DataModelingBaseException):
+    def __init__(self, msg: str, tree_path: str, child_exceptions: "Iterable[DataValidationError]" = tuple()) -> None:
+        super().__init__(msg)
+        self._tree_path = tree_path
+        self._child_exceptions = child_exceptions
+
+    def where(self) -> str:
+        return self._tree_path
+
+    def msg(self):
+        return f"[{self.where()}] " + super().__str__()
+
+    def recursive_msg(self, indentation_level: int = 0) -> str:
+        INDENT = indentation_level * "\t"
+        msg_parts: List[str] = [f"{INDENT}{self.msg()}"]
+        for c in self._child_exceptions:
+            msg_parts.append(c.recursive_msg(indentation_level + 1))
+        return "\n".join(msg_parts)
+
+    def __str__(self) -> str:
+        return self.recursive_msg()
+
+
+class AggregateDataValidationError(DataValidationError):
+    def __init__(self, object_path: str, child_exceptions: "Iterable[DataValidationError]") -> None:
+        super().__init__("error due to lower level exceptions", object_path, child_exceptions)
+
+    def recursive_msg(self, indentation_level: int = 0) -> str:
+        inc = 0
+        msg_parts: List[str] = []
+        if indentation_level == 0:
+            inc = 1
+            msg_parts.append("multiple configuration errors detected:")
+
+        for c in self._child_exceptions:
+            msg_parts.append(c.recursive_msg(indentation_level + inc))
+        return "\n".join(msg_parts)
similarity index 91%
rename from manager/knot_resolver_manager/utils/parsing.py
rename to manager/knot_resolver_manager/utils/modeling/parsed_tree.py
index 12c65e2ee2b6bd0e80cb0a78eb28039e74b22134..27ad37c62c999a03f596487e8489d8edcce6722c 100644 (file)
@@ -8,8 +8,8 @@ import yaml
 from yaml.constructor import ConstructorError
 from yaml.nodes import MappingNode
 
-from knot_resolver_manager.exceptions import DataException, ParsingException
-from knot_resolver_manager.utils.types import is_internal_field_name
+from .exceptions import DataParsingError
+from .types import is_internal_field_name
 
 
 class ParsedTree:
@@ -60,9 +60,9 @@ class ParsedTree:
         # prepare and validate the path object
         path = path[:-1] if path.endswith("/") else path
         if re.match(ParsedTree._SUBTREE_MUTATION_PATH_PATTERN, path) is None:
-            raise ParsingException("Provided object path for mutation is invalid.")
+            raise DataParsingError("Provided object path for mutation is invalid.")
         if "_" in path:
-            raise ParsingException("Provided object path contains character '_', which is illegal")
+            raise DataParsingError("Provided object path contains character '_', which is illegal")
         # Note: mutation happens on the internal dict only, therefore we are working with external
         # naming only. That means, there are '-' in between words.
         path = path[1:] if path.startswith("/") else path
@@ -82,9 +82,9 @@ class ParsedTree:
             assert isinstance(obj, dict)
 
             if segment == "":
-                raise ParsingException(f"Unexpectedly empty segment in path '{path}'")
+                raise DataParsingError(f"Unexpectedly empty segment in path '{path}'")
             elif is_internal_field_name(segment):
-                raise ParsingException(
+                raise DataParsingError(
                     "No, changing internal fields (starting with _) is not allowed. Nice try though."
                 )
             elif segment in obj:
@@ -109,7 +109,7 @@ def _json_raise_duplicates(pairs: List[Tuple[Any, Any]]) -> Optional[Any]:
     dict_out: Dict[Any, Any] = {}
     for key, val in pairs:
         if key in dict_out:
-            raise DataException(f"Duplicate attribute key detected: {key}")
+            raise DataParsingError(f"Duplicate attribute key detected: {key}")
         dict_out[key] = val
     return dict_out
 
@@ -136,7 +136,7 @@ class _RaiseDuplicatesLoader(yaml.SafeLoader):
 
             # check for duplicate keys
             if key in mapping:
-                raise DataException(f"duplicate key detected: {key_node.start_mark}")
+                raise DataParsingError(f"duplicate key detected: {key_node.start_mark}")
             value = self.construct_object(value_node, deep=deep)  # type: ignore
             mapping[key] = value
         return mapping
@@ -172,7 +172,7 @@ class _Format(Enum):
             "text/vnd.yaml": _Format.YAML,
         }
         if mime_type not in formats:
-            raise DataException("Unsupported MIME type")
+            raise DataParsingError("Unsupported MIME type")
         return formats[mime_type]
 
 
similarity index 90%
rename from manager/knot_resolver_manager/utils/modelling.py
rename to manager/knot_resolver_manager/utils/modeling/schema_node.py
index 2d7ab5926d1d651968cc310a030a95fda82e1454..f670ee6f7640d602a9ca858e663ed642b431ad93 100644 (file)
@@ -4,11 +4,12 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union, cast
 
 import yaml
 
-from knot_resolver_manager.exceptions import AggregateSchemaException, DataException, SchemaException
-from knot_resolver_manager.utils.custom_types import CustomValueType
 from knot_resolver_manager.utils.functional import all_matches
-from knot_resolver_manager.utils.parsing import ParsedTree
-from knot_resolver_manager.utils.types import (
+
+from .custom_value_type import CustomValueType
+from .exceptions import AggregateDataValidationError, DataDescriptionError, DataValidationError
+from .parsed_tree import ParsedTree
+from .types import (
     NoneType,
     get_generic_type_argument,
     get_generic_type_arguments,
@@ -114,7 +115,7 @@ def _get_properties_schema(typ: Type[Any]) -> Dict[Any, Any]:
         # description
         if attribute_documentation is not None:
             if field_name not in attribute_documentation:
-                raise SchemaException(f"The docstring does not describe field '{field_name}'", str(typ))
+                raise DataDescriptionError(f"The docstring does not describe field '{field_name}'", str(typ))
             schema[name]["description"] = attribute_documentation[field_name]
             del attribute_documentation[field_name]
 
@@ -126,7 +127,7 @@ def _get_properties_schema(typ: Type[Any]) -> Dict[Any, Any]:
             schema[name]["default"] = Serializable.serialize(getattr(typ, field_name))
 
     if attribute_documentation is not None and len(attribute_documentation) > 0:
-        raise SchemaException(
+        raise DataDescriptionError(
             f"The docstring describes attributes which are not present - {tuple(attribute_documentation.keys())}",
             str(typ),
         )
@@ -186,56 +187,56 @@ def _describe_type(typ: Type[Any]) -> Dict[Any, Any]:
 
 def _validated_tuple(cls: Type[Any], obj: Tuple[Any, ...], object_path: str) -> Tuple[Any, ...]:
     types = get_generic_type_arguments(cls)
-    errs: List[SchemaException] = []
+    errs: List[DataValidationError] = []
     res: List[Any] = []
     for i, (tp, val) in enumerate(zip(types, obj)):
         try:
             res.append(_validated_object_type(tp, val, object_path=f"{object_path}[{i}]"))
-        except SchemaException as e:
+        except DataValidationError as e:
             errs.append(e)
     if len(errs) == 1:
         raise errs[0]
     elif len(errs) > 1:
-        raise AggregateSchemaException(object_path, child_exceptions=errs)
+        raise AggregateDataValidationError(object_path, child_exceptions=errs)
     return tuple(res)
 
 
 def _validated_dict(cls: Type[Any], obj: Dict[Any, Any], object_path: str) -> Dict[Any, Any]:
     key_type, val_type = get_generic_type_arguments(cls)
     try:
-        errs: List[SchemaException] = []
+        errs: List[DataValidationError] = []
         res: Dict[Any, Any] = {}
         for key, val in obj.items():
             try:
                 nkey = _validated_object_type(key_type, key, object_path=f"{object_path}[{key}]")
                 nval = _validated_object_type(val_type, val, object_path=f"{object_path}[{key}]")
                 res[nkey] = nval
-            except SchemaException as e:
+            except DataValidationError as e:
                 errs.append(e)
         if len(errs) == 1:
             raise errs[0]
         elif len(errs) > 1:
-            raise AggregateSchemaException(object_path, child_exceptions=errs)
+            raise AggregateDataValidationError(object_path, child_exceptions=errs)
         return res
     except AttributeError as e:
-        raise SchemaException(
+        raise DataValidationError(
             f"Expected dict-like object, but failed to access its .items() method. Value was {obj}", object_path
         ) from e
 
 
 def _validated_list(cls: Type[Any], obj: List[Any], object_path: str) -> List[Any]:
     inner_type = get_generic_type_argument(cls)
-    errs: List[SchemaException] = []
+    errs: List[DataValidationError] = []
     res: List[Any] = []
     for i, val in enumerate(obj):
         try:
             res.append(_validated_object_type(inner_type, val, object_path=f"{object_path}[{i}]"))
-        except SchemaException as e:
+        except DataValidationError as e:
             errs.append(e)
     if len(errs) == 1:
         raise errs[0]
     elif len(errs) > 1:
-        raise AggregateSchemaException(object_path, child_exceptions=errs)
+        raise AggregateDataValidationError(object_path, child_exceptions=errs)
     return res
 
 
@@ -259,7 +260,7 @@ def _validated_object_type(
         if obj is None:
             return None
         else:
-            raise SchemaException(f"expected None, found '{obj}'.", object_path)
+            raise DataValidationError(f"expected None, found '{obj}'.", object_path)
 
     # Optional[T]  (could be technically handled by Union[*variants], but this way we have better error reporting)
     elif is_optional(cls):
@@ -272,18 +273,18 @@ def _validated_object_type(
     # Union[*variants]
     elif is_union(cls):
         variants = get_generic_type_arguments(cls)
-        errs: List[SchemaException] = []
+        errs: List[DataValidationError] = []
         for v in variants:
             try:
                 return _validated_object_type(v, obj, object_path=object_path)
-            except SchemaException as e:
+            except DataValidationError as e:
                 errs.append(e)
 
-        raise SchemaException("could not parse any of the possible variants", object_path, child_exceptions=errs)
+        raise DataValidationError("could not parse any of the possible variants", object_path, child_exceptions=errs)
 
     # after this, there is no place for a None object
     elif obj is None:
-        raise SchemaException(f"unexpected value 'None' for type {cls}", object_path)
+        raise DataValidationError(f"unexpected value 'None' for type {cls}", object_path)
 
     # int
     elif cls == int:
@@ -291,7 +292,7 @@ def _validated_object_type(
         # except for CustomValueType class instances
         if is_obj_type(obj, int) or isinstance(obj, CustomValueType):
             return int(obj)
-        raise SchemaException(f"expected int, found {type(obj)}", object_path)
+        raise DataValidationError(f"expected int, found {type(obj)}", object_path)
 
     # str
     elif cls == str:
@@ -299,14 +300,14 @@ def _validated_object_type(
         if is_obj_type(obj, (str, float, int)) or isinstance(obj, CustomValueType):
             return str(obj)
         elif is_obj_type(obj, bool):
-            raise SchemaException(
+            raise DataValidationError(
                 "Expected str, found bool. Be careful, that YAML parsers consider even"
                 ' "no" and "yes" as a bool. Search for the Norway Problem for more'
                 " details. And please use quotes explicitly.",
                 object_path,
             )
         else:
-            raise SchemaException(
+            raise DataValidationError(
                 f"expected str (or number that would be cast to string), but found type {type(obj)}", object_path
             )
 
@@ -315,7 +316,7 @@ def _validated_object_type(
         if is_obj_type(obj, bool):
             return obj
         else:
-            raise SchemaException(f"expected bool, found {type(obj)}", object_path)
+            raise DataValidationError(f"expected bool, found {type(obj)}", object_path)
 
     # float
     elif cls == float:
@@ -330,7 +331,7 @@ def _validated_object_type(
         if obj in expected:
             return obj
         else:
-            raise SchemaException(f"'{obj}' does not match any of the expected values {expected}", object_path)
+            raise DataValidationError(f"'{obj}' does not match any of the expected values {expected}", object_path)
 
     # Dict[K,V]
     elif is_dict(cls):
@@ -341,12 +342,12 @@ def _validated_object_type(
         if isinstance(obj, cls):
             return obj
         else:
-            raise SchemaException(f"unexpected value '{obj}' for enum '{cls}'", object_path)
+            raise DataValidationError(f"unexpected value '{obj}' for enum '{cls}'", object_path)
 
     # List[T]
     elif is_list(cls):
         if isinstance(obj, str):
-            raise SchemaException("expected list, got string", object_path)
+            raise DataValidationError("expected list, got string", object_path)
         return _validated_list(cls, obj, object_path)
 
     # Tuple[A,B,C,D,...]
@@ -372,7 +373,7 @@ def _validated_object_type(
         # because we can construct a DataParser from it
         if isinstance(obj, (dict, SchemaNode)):
             return cls(obj, object_path=object_path)  # type: ignore
-        raise SchemaException(f"expected 'dict' or 'SchemaNode' object, found '{type(obj)}'", object_path)
+        raise DataValidationError(f"expected 'dict' or 'SchemaNode' object, found '{type(obj)}'", object_path)
 
     # if the object matches, just pass it through
     elif inspect.isclass(cls) and isinstance(obj, cls):
@@ -380,7 +381,7 @@ def _validated_object_type(
 
     # default error handler
     else:
-        raise SchemaException(
+        raise DataValidationError(
             f"Type {cls} cannot be parsed. This is a implementation error. "
             "Please fix your types in the class or improve the parser/validator.",
             object_path,
@@ -484,7 +485,7 @@ class SchemaNode(Serializable):
         """
         cls = self.__class__
         annot = cls.__dict__.get("__annotations__", {})
-        errs: List[SchemaException] = []
+        errs: List[DataValidationError] = []
 
         used_keys: Set[str] = set()
         for name, python_type in annot.items():
@@ -521,14 +522,14 @@ class SchemaNode(Serializable):
 
                 # we expected a value but it was not there
                 else:
-                    errs.append(SchemaException(f"missing attribute '{name}'.", object_path))
-            except SchemaException as e:
+                    errs.append(DataValidationError(f"missing attribute '{name}'.", object_path))
+            except DataValidationError as e:
                 errs.append(e)
 
         if len(errs) == 1:
             raise errs[0]
         elif len(errs) > 1:
-            raise AggregateSchemaException(object_path, errs)
+            raise AggregateDataValidationError(object_path, errs)
         return used_keys
 
     def __init__(self, source: TSource = None, object_path: str = ""):
@@ -553,7 +554,7 @@ class SchemaNode(Serializable):
             unused = source.keys() - used_keys
             if len(unused) > 0:
                 keys = ", ".join((f"'{u}'" for u in unused))
-                raise SchemaException(
+                raise DataValidationError(
                     f"unexpected extra key(s) {keys}",
                     object_path,
                 )
@@ -562,7 +563,7 @@ class SchemaNode(Serializable):
         try:
             self._validate()
         except ValueError as e:
-            raise SchemaException(e.args[0] if len(e.args) > 0 else "Validation error", object_path) from e
+            raise DataValidationError(e.args[0] if len(e.args) > 0 else "Validation error", object_path) from e
 
     def get_unparsed_data(self) -> ParsedTree:
         if isinstance(self._source, SchemaNode):
@@ -585,12 +586,12 @@ class SchemaNode(Serializable):
                 return func(_create_untouchable("self"), source)
             else:
                 raise RuntimeError("Transformation function has wrong number of arguments")
-        except (ValueError, DataException) as e:
+        except ValueError as e:
             if len(e.args) > 0 and isinstance(e.args[0], str):
                 msg = e.args[0]
             else:
                 msg = "Failed to validate value type"
-            raise SchemaException(msg, object_path) from e
+            raise DataValidationError(msg, object_path) from e
 
     def __getitem__(self, key: str) -> Any:
         if not hasattr(self, key):
index d4e2f3cdd567f9074132afec389def1f09eace77..655e4f4d22627bdd6f4ec248eacb70b3d0801adb 100644 (file)
@@ -10,7 +10,8 @@ packages = \
  'knot_resolver_manager.kresd_controller',
  'knot_resolver_manager.kresd_controller.supervisord',
  'knot_resolver_manager.kresd_controller.supervisord.plugin',
- 'knot_resolver_manager.utils']
+ 'knot_resolver_manager.utils',
+ 'knot_resolver_manager.utils.modeling']
 
 package_data = \
 {'': ['*'],
index 1210da053152695b0eb8ef218bde8181f9a060c1..30d69bd9953cce456b51f09b09820922d0bf1633 100644 (file)
@@ -1,9 +1,9 @@
 from pytest import raises
 
 from knot_resolver_manager.datamodel.lua_schema import LuaSchema
-from knot_resolver_manager.exceptions import KresManagerException
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 def test_invalid():
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         LuaSchema({"script": "-- lua script", "script-file": "path/to/file"})
index 2da89d2c511bd08beb0a87379f92632aedafa364..1d3d90ca06270ba247e5c33399599e32be47cbce 100644 (file)
@@ -3,7 +3,7 @@ from typing import Any, Dict, Optional
 import pytest
 
 from knot_resolver_manager.datamodel.management_schema import ManagementSchema
-from knot_resolver_manager.exceptions import KresManagerException
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 @pytest.mark.parametrize("val", [{"interface": "::1@53"}, {"unix-socket": "/path/socket"}])
@@ -17,5 +17,5 @@ def test_management_valid(val: Dict[str, Any]):
 
 @pytest.mark.parametrize("val", [None, {"interface": "::1@53", "unix-socket": "/path/socket"}])
 def test_management_invalid(val: Optional[Dict[str, Any]]):
-    with pytest.raises(KresManagerException):
+    with pytest.raises(DataValidationError):
         ManagementSchema(val)
index ee44c1b7b00d7d47593fce94933445e660cbbee2..59f105b2f3d0f6fbd2975c2fbd516914ec08bfe5 100644 (file)
@@ -6,7 +6,7 @@ from pytest import raises
 from knot_resolver_manager.datamodel.network_schema import ListenSchema, NetworkSchema
 from knot_resolver_manager.datamodel.types import PortNumber
 from knot_resolver_manager.datamodel.types.types import InterfaceOptionalPort
-from knot_resolver_manager.exceptions import KresManagerException
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 def test_listen_defaults():
@@ -76,5 +76,5 @@ def test_listen_valid(listen: Dict[str, Any]):
     ],
 )
 def test_listen_invalid(listen: Dict[str, Any]):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         ListenSchema(listen)
index f62000d93d346be7d729c42ae5fb1547f184fe93..ac3761b460bf5486aa45f8e95c290ed51d92b48f 100644 (file)
@@ -5,8 +5,8 @@ from pytest import raises
 
 from knot_resolver_manager.datamodel.policy_schema import ActionSchema, PolicySchema
 from knot_resolver_manager.datamodel.types import PolicyActionEnum
-from knot_resolver_manager.exceptions import KresManagerException
-from knot_resolver_manager.utils.types import get_generic_type_arguments
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
+from knot_resolver_manager.utils.modeling.types import get_generic_type_arguments
 
 noconfig_actions = [
     "pass",
@@ -30,9 +30,9 @@ def test_policy_action_valid(val: Any):
 
 @pytest.mark.parametrize("val", [{"action": "invalid-action"}])
 def test_action_invalid(val: Dict[str, Any]):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         PolicySchema(val)
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         ActionSchema(val)
 
 
@@ -72,9 +72,9 @@ def test_policy_valid(val: Dict[str, Any]):
     ],
 )
 def test_policy_invalid(val: Dict[str, Any]):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         PolicySchema(val)
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         ActionSchema(val)
 
 
@@ -83,7 +83,7 @@ def test_policy_invalid(val: Dict[str, Any]):
     noconfig_actions,
 )
 def test_policy_message_invalid(val: str):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         PolicySchema({"action": f"{val}", "message": "this is deny message"})
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         ActionSchema({"action": f"{val}", "message": "this is deny message"})
index 9db896ff1157895aa965ce1dd2f2f6d58366f206..6603deed22ad443ec88daffd6b6114f3e3d04afa 100644 (file)
@@ -2,7 +2,7 @@ import pytest
 from pytest import raises
 
 from knot_resolver_manager.datamodel.rpz_schema import RPZSchema
-from knot_resolver_manager.exceptions import KresManagerException
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 @pytest.mark.parametrize(
@@ -19,5 +19,5 @@ from knot_resolver_manager.exceptions import KresManagerException
     ],
 )
 def test_message_invalid(val: str):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         RPZSchema({"action": f"{val}", "file": "whitelist.rpz", "message": "this is deny message"})
index a18910c8a39ec79a06c9f1e4096069b7ad9f00f0..e89711eceb5599e730491434100a92852294e673 100644 (file)
@@ -22,8 +22,8 @@ from knot_resolver_manager.datamodel.types import (
     SizeUnit,
     TimeUnit,
 )
-from knot_resolver_manager.exceptions import KresManagerException
-from knot_resolver_manager.utils import SchemaNode
+from knot_resolver_manager.utils.modeling import SchemaNode
+from knot_resolver_manager.utils.modeling.exceptions import DataValidationError
 
 
 def _rand_domain(label_chars: int, levels: int = 1) -> str:
@@ -39,7 +39,7 @@ def test_port_number_valid(val: int):
 
 @pytest.mark.parametrize("val", [0, 65_636, -1, "53"])
 def test_port_number_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         PortNumber(val)
 
 
@@ -53,7 +53,7 @@ def test_size_unit_valid(val: str):
 
 @pytest.mark.parametrize("val", ["-5B", 5, -5242880, "45745mB"])
 def test_size_unit_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         SizeUnit(val)
 
 
@@ -68,7 +68,7 @@ def test_time_unit_valid(val: str):
 
 @pytest.mark.parametrize("val", ["-1", "-24h", "1440mm", 6575, -1440])
 def test_time_unit_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         TimeUnit("-1")
 
 
@@ -124,7 +124,7 @@ def test_domain_name_valid(val: str):
     ],
 )
 def test_domain_name_invalid(val: str):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         DomainName(val)
 
 
@@ -135,7 +135,7 @@ def test_interface_name_valid(val: str):
 
 @pytest.mark.parametrize("val", ["_lo", "-wlo1", "lo_", "wlo1-", "e8--2", "web__ifgrp"])
 def test_interface_name_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         InterfaceName(val)
 
 
@@ -150,7 +150,7 @@ def test_interface_port_valid(val: str):
 
 @pytest.mark.parametrize("val", ["lo", "2001:db8::1000", "53"])
 def test_interface_port_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         InterfacePort(val)
 
 
@@ -165,7 +165,7 @@ def test_interface_optional_port_valid(val: str):
 
 @pytest.mark.parametrize("val", ["lo@", "@53"])
 def test_interface_optional_port_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         InterfaceOptionalPort(val)
 
 
@@ -182,7 +182,7 @@ def test_ip_address_port_valid(val: str):
     "val", ["123.4.5.6", "2001:db8::1000", "123.4.5.6.7@5000", "2001:db8::10000@5001", "123.4.5.6@"]
 )
 def test_ip_address_port_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         IPAddressPort(val)
 
 
@@ -197,7 +197,7 @@ def test_ip_address_optional_port_valid(val: str):
 
 @pytest.mark.parametrize("val", ["123.4.5.6.7", "2001:db8::10000", "123.4.5.6@", "@55"])
 def test_ip_address_optional_port_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         IPAddressOptionalPort(val)
 
 
@@ -210,7 +210,7 @@ def test_ipv4_address_valid(val: str):
 
 @pytest.mark.parametrize("val", ["123456", "2001:db8::1000"])
 def test_ipv4_address_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         IPv4Address(val)
 
 
@@ -223,7 +223,7 @@ def test_ipv6_address_valid(val: str):
 
 @pytest.mark.parametrize("val", ["123.4.5.6", "2001::db8::1000"])
 def test_ipv6_address_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         IPv6Address(val)
 
 
@@ -237,7 +237,7 @@ def test_ip_network_valid(val: str):
 
 @pytest.mark.parametrize("val", ["10.11.12.13/8", "10.11.12.5/128"])
 def test_ip_network_invalid(val: str):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         IPNetwork(val)
 
 
@@ -248,5 +248,5 @@ def test_ipv6_96_network_valid(val: str):
 
 @pytest.mark.parametrize("val", ["fe80::/95", "10.11.12.3/96", "64:ff9b::1/96"])
 def test_ipv6_96_network_invalid(val: Any):
-    with raises(KresManagerException):
+    with raises(DataValidationError):
         IPv6Network96(val)
similarity index 92%
rename from manager/tests/unit/utils/test_modelling.py
rename to manager/tests/unit/utils/test_modeling.py
index 627ef39c6284a5eff37d1923058efe9e9a1fe0a0..f7190b8e6479a46e7d3a57fda50543e436b43395 100644 (file)
@@ -4,9 +4,8 @@ import pytest
 from pytest import raises
 from typing_extensions import Literal
 
-from knot_resolver_manager.exceptions import SchemaException
-from knot_resolver_manager.utils import SchemaNode
-from knot_resolver_manager.utils.parsing import parse_json, parse_yaml
+from knot_resolver_manager.utils.modeling import SchemaNode, parse_json, parse_yaml
+from knot_resolver_manager.utils.modeling.exceptions import DataDescriptionError, DataValidationError
 
 
 class _TestBool(SchemaNode):
@@ -28,7 +27,7 @@ def test_parsing_bool_valid(val: str, exp: bool):
 
 @pytest.mark.parametrize("val", ["0", "1", "5", "'true'", "'false'", "5.5"])  # int, str, float
 def test_parsing_bool_invalid(val: str):
-    with raises(SchemaException):
+    with raises(DataValidationError):
         _TestBool(parse_yaml(f"v: {val}"))
 
 
@@ -39,7 +38,7 @@ def test_parsing_int_valid(val: str, exp: int):
 
 @pytest.mark.parametrize("val", ["false", "'5'", "5.5"])  # bool, str, float
 def test_parsing_int_invalid(val: str):
-    with raises(SchemaException):
+    with raises(DataValidationError):
         _TestInt(parse_yaml(f"v: {val}"))
 
 
@@ -50,7 +49,7 @@ def test_parsing_str_valid(val: Any, exp: str):
 
 
 def test_parsing_str_invalid():
-    with raises(SchemaException):
+    with raises(DataValidationError):
         _TestStr(parse_yaml("v: false"))  # bool
 
 
@@ -174,8 +173,8 @@ def test_partial_mutations():
     assert o.workers == 8
     assert o.inner.size == 33
 
-    # raise validation SchemaException
-    with raises(SchemaException):
+    # raise validation DataValidationError
+    with raises(DataValidationError):
         o = ConfSchema(d.update("/", parse_json('{"workers": -5}')))
 
 
@@ -250,7 +249,7 @@ def test_docstring_parsing_invalid():
 
         nothing: str
 
-    with raises(SchemaException):
+    with raises(DataDescriptionError):
         _ = AdditionalItem.json_schema()
 
     class WrongDescription(SchemaNode):
@@ -262,5 +261,5 @@ def test_docstring_parsing_invalid():
 
         nothing: str
 
-    with raises(SchemaException):
+    with raises(DataDescriptionError):
         _ = WrongDescription.json_schema()
index cb5087d8702f601df137876ab20b9e45a77e383e..6b9a246af636ca14da7898f668164b0df21dc609 100644 (file)
@@ -3,8 +3,8 @@ from typing import Any, Dict, List, Tuple, Union
 import pytest
 from typing_extensions import Literal
 
-from knot_resolver_manager.utils.modelling import SchemaNode
-from knot_resolver_manager.utils.types import is_list, is_literal
+from knot_resolver_manager.utils.modeling import SchemaNode
+from knot_resolver_manager.utils.modeling.types import is_list, is_literal
 
 types = [
     bool,