Add custom exceptions, simplify generation template
This commit is contained in:
22
src/megasniff/exceptions.py
Normal file
22
src/megasniff/exceptions.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class MissingFieldException(Exception):
|
||||||
|
def __init__(self, required_field: str, required_types: str):
|
||||||
|
message = f"No required field provided: {required_field} with type {required_types}"
|
||||||
|
super().__init__(message)
|
||||||
|
self.required_field = required_field
|
||||||
|
self.required_types = required_types
|
||||||
|
|
||||||
|
|
||||||
|
class FieldValidationException(Exception):
|
||||||
|
def __init__(self,
|
||||||
|
required_field: str,
|
||||||
|
required_types: str,
|
||||||
|
provided: Any,
|
||||||
|
exceptions: Optional[list[Exception]] = None):
|
||||||
|
message = f"Required field {required_field} with type {required_types}, provided: {provided}"
|
||||||
|
super().__init__(message)
|
||||||
|
self.required_field = required_field
|
||||||
|
self.required_types = required_types
|
||||||
|
self.exceptions = exceptions or []
|
||||||
@@ -17,7 +17,6 @@ class RenderData:
|
|||||||
argname: str
|
argname: str
|
||||||
constrs: list[tuple[str, bool]] # typecall / use lookup table
|
constrs: list[tuple[str, bool]] # typecall / use lookup table
|
||||||
typename: str
|
typename: str
|
||||||
is_union: bool
|
|
||||||
is_optional: bool
|
is_optional: bool
|
||||||
allow_none: bool
|
allow_none: bool
|
||||||
default_option: Optional[str]
|
default_option: Optional[str]
|
||||||
@@ -85,7 +84,6 @@ class SchemaInflatorGenerator:
|
|||||||
argname,
|
argname,
|
||||||
out_argtypes,
|
out_argtypes,
|
||||||
repr(argtype),
|
repr(argtype),
|
||||||
len(argtypes) > 1,
|
|
||||||
has_default,
|
has_default,
|
||||||
allow_none,
|
allow_none,
|
||||||
default_option
|
default_option
|
||||||
|
|||||||
@@ -1,65 +1,42 @@
|
|||||||
{% set ns = namespace(retry_indent=0) %}
|
{% set ns = namespace(retry_indent=0) %}
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from megasniff.exceptions import MissingFieldException, FieldValidationException
|
||||||
|
|
||||||
def inflate(from_data: dict[str, Any]):
|
def inflate(from_data: dict[str, Any]):
|
||||||
from_data_keys = from_data.keys()
|
from_data_keys = from_data.keys()
|
||||||
|
|
||||||
{% for conv in conversions %}
|
{% for conv in conversions %}
|
||||||
{% if not conv.is_optional or conv.default_option is not none%}
|
|
||||||
if '{{conv.argname}}' not in from_data_keys:
|
if '{{conv.argname}}' not in from_data_keys:
|
||||||
{% if not conv.is_optional %}
|
{% if conv.is_optional %}
|
||||||
raise ValueError(f"No required field provided: {{conv.argname}} with type {{conv.typename | replace('"', "'")}}")
|
{{conv.argname}} = {{conv.default_option}}
|
||||||
{% endif %}
|
|
||||||
{% if conv.default_option is not none %}
|
|
||||||
from_data['{{conv.argname}}'] = {{conv.default_option}}
|
|
||||||
{% endif%}
|
|
||||||
{% endif %}
|
|
||||||
{%endfor%}
|
|
||||||
|
|
||||||
{% for conv in conversions %}
|
|
||||||
conv_data = from_data['{{conv.argname}}']
|
|
||||||
if conv_data is None:
|
|
||||||
{% if not conv.allow_none %}
|
|
||||||
raise ValueError(f"Field {{conv.argname}} required type {{conv.typename | replace('"', "'")}}, null provided")
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{conv.argname}} = None
|
raise MissingFieldException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}")
|
||||||
{% endif %}
|
{% endif %}
|
||||||
else:
|
else:
|
||||||
|
conv_data = from_data['{{conv.argname}}']
|
||||||
{% if conv.is_union %}
|
if conv_data is None:
|
||||||
{% set ns.retry_indent = 0 %}
|
{% if not conv.allow_none %}
|
||||||
{% for union_type, is_builtin in conv.constrs %}
|
raise FieldValidationException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}", conv_data)
|
||||||
{{ ' ' * ns.retry_indent }}try:
|
|
||||||
{% if is_builtin %}
|
|
||||||
{{ ' ' * ns.retry_indent }} {{conv.argname}} = {{union_type}}(conv_data)
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ ' ' * ns.retry_indent }} {{conv.argname}} = _lookup_table['{{union_type}}'](conv_data)
|
|
||||||
{% endif %}
|
|
||||||
{{ ' ' * ns.retry_indent }}except Exception as e:
|
|
||||||
{% set ns.retry_indent = ns.retry_indent + 1 %}
|
|
||||||
{% endfor %}
|
|
||||||
{{ ' ' * ns.retry_indent }} raise e from e
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% if conv.constrs[0][1] %}
|
|
||||||
{% if conv.is_optional %}
|
|
||||||
if '{{conv.argname}}' not in from_data_keys:
|
|
||||||
{{conv.argname}} = None
|
{{conv.argname}} = None
|
||||||
|
{% endif %}
|
||||||
else:
|
else:
|
||||||
{{conv.argname}} = {{conv.constrs[0][0]}}(conv_data)
|
|
||||||
{% else %}
|
|
||||||
{{conv.argname}} = {{conv.constrs[0][0]}}(conv_data)
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if conv.is_optional %}
|
|
||||||
if '{{conv.argname}}' not in from_data_keys:
|
|
||||||
{{conv.argname}} = None
|
|
||||||
else:
|
|
||||||
{{conv.argname}} = _lookup_table['{{conv.constrs[0][0]}}'](conv_data)
|
|
||||||
{% else %}
|
|
||||||
{{conv.argname}} = _lookup_table['{{conv.constrs[0][0]}}'](conv_data)
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
{% set ns.retry_indent = 0 %}
|
||||||
|
all_conv_exceptions = []
|
||||||
|
{% for union_type, is_builtin in conv.constrs %}
|
||||||
|
{{ ' ' * ns.retry_indent }}try:
|
||||||
|
{% if is_builtin %}
|
||||||
|
{{ ' ' * ns.retry_indent }} {{conv.argname}} = {{union_type}}(conv_data)
|
||||||
|
{% else %}
|
||||||
|
{{ ' ' * ns.retry_indent }} {{conv.argname}} = _lookup_table['{{union_type}}'](conv_data)
|
||||||
|
{% endif %}
|
||||||
|
{{ ' ' * ns.retry_indent }}except Exception as e:
|
||||||
|
{{ ' ' * ns.retry_indent }} all_conv_exceptions.append(e)
|
||||||
|
{% set ns.retry_indent = ns.retry_indent + 1 %}
|
||||||
|
{% endfor %}
|
||||||
|
{{ ' ' * ns.retry_indent }}raise FieldValidationException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}", conv_data, all_conv_exceptions)
|
||||||
|
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
return _tgt_type({% for conv in conversions %}{{conv.argname}}={{conv.argname}}, {% endfor %})
|
return _tgt_type({% for conv in conversions %}{{conv.argname}}={{conv.argname}}, {% endfor %})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from src.megasniff import SchemaInflatorGenerator
|
from src.megasniff import SchemaInflatorGenerator
|
||||||
|
|
||||||
@@ -32,3 +33,14 @@ def test_circular():
|
|||||||
a = fn({'b': {'a': None}})
|
a = fn({'b': {'a': None}})
|
||||||
|
|
||||||
return isinstance(a.b, CircB)
|
return isinstance(a.b, CircB)
|
||||||
|
|
||||||
|
|
||||||
|
def test_optional():
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
a: Optional[int] = None
|
||||||
|
|
||||||
|
infl = SchemaInflatorGenerator()
|
||||||
|
fn = infl.schema_to_generator(C)
|
||||||
|
c = fn({})
|
||||||
|
assert c.a is None
|
||||||
|
|||||||
39
tests/test_exceptions.py
Normal file
39
tests/test_exceptions.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from megasniff import SchemaInflatorGenerator
|
||||||
|
from megasniff.exceptions import MissingFieldException, FieldValidationException
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_field():
|
||||||
|
@dataclass
|
||||||
|
class A:
|
||||||
|
a: int
|
||||||
|
|
||||||
|
infl = SchemaInflatorGenerator()
|
||||||
|
fn = infl.schema_to_generator(A)
|
||||||
|
with pytest.raises(MissingFieldException):
|
||||||
|
fn({})
|
||||||
|
|
||||||
|
|
||||||
|
def test_null():
|
||||||
|
@dataclass
|
||||||
|
class A:
|
||||||
|
a: int
|
||||||
|
|
||||||
|
infl = SchemaInflatorGenerator()
|
||||||
|
fn = infl.schema_to_generator(A)
|
||||||
|
with pytest.raises(FieldValidationException):
|
||||||
|
fn({'a': None})
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_field():
|
||||||
|
@dataclass
|
||||||
|
class A:
|
||||||
|
a: float | int | None
|
||||||
|
|
||||||
|
infl = SchemaInflatorGenerator()
|
||||||
|
fn = infl.schema_to_generator(A)
|
||||||
|
with pytest.raises(FieldValidationException):
|
||||||
|
fn({'a': {}})
|
||||||
Reference in New Issue
Block a user