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
|
||||
constrs: list[tuple[str, bool]] # typecall / use lookup table
|
||||
typename: str
|
||||
is_union: bool
|
||||
is_optional: bool
|
||||
allow_none: bool
|
||||
default_option: Optional[str]
|
||||
@@ -85,7 +84,6 @@ class SchemaInflatorGenerator:
|
||||
argname,
|
||||
out_argtypes,
|
||||
repr(argtype),
|
||||
len(argtypes) > 1,
|
||||
has_default,
|
||||
allow_none,
|
||||
default_option
|
||||
|
||||
@@ -1,65 +1,42 @@
|
||||
{% set ns = namespace(retry_indent=0) %}
|
||||
from typing import Any
|
||||
from megasniff.exceptions import MissingFieldException, FieldValidationException
|
||||
|
||||
def inflate(from_data: dict[str, Any]):
|
||||
from_data_keys = from_data.keys()
|
||||
|
||||
{% 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 not conv.is_optional %}
|
||||
raise ValueError(f"No required field provided: {{conv.argname}} with type {{conv.typename | replace('"', "'")}}")
|
||||
{% 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")
|
||||
{% if conv.is_optional %}
|
||||
{{conv.argname}} = {{conv.default_option}}
|
||||
{% else %}
|
||||
{{conv.argname}} = None
|
||||
raise MissingFieldException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}")
|
||||
{% endif %}
|
||||
else:
|
||||
|
||||
{% if conv.is_union %}
|
||||
{% set ns.retry_indent = 0 %}
|
||||
{% for union_type, is_builtin in conv.constrs %}
|
||||
{{ ' ' * ns.retry_indent }}try:
|
||||
{% if is_builtin %}
|
||||
{{ ' ' * ns.retry_indent }} {{conv.argname}} = {{union_type}}(conv_data)
|
||||
conv_data = from_data['{{conv.argname}}']
|
||||
if conv_data is None:
|
||||
{% if not conv.allow_none %}
|
||||
raise FieldValidationException('{{conv.argname}}', "{{conv.typename | replace('"', "'")}}", conv_data)
|
||||
{% 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
|
||||
{% endif %}
|
||||
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 %}
|
||||
return _tgt_type({% for conv in conversions %}{{conv.argname}}={{conv.argname}}, {% endfor %})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from src.megasniff import SchemaInflatorGenerator
|
||||
|
||||
@@ -32,3 +33,14 @@ def test_circular():
|
||||
a = fn({'b': {'a': None}})
|
||||
|
||||
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