I have various classes that try to load different schemas from an input, and these classes can pose an additional input value validation. I'm wondering what the proper pythonic (and mypy satisfactory) way to structure this is. Here's what I have so far:
from dataclasses import dataclass
from abc import ABC, abstractproperty
from typing import Union
from dataclasses_jsonschema import JsonSchemaMixin
from jsonschema import exceptions as jsonschexc, validate
""" Validator parent class """
class BaseValidator(ABC):
@abstractproperty
def schema(self) -> JsonSchemaMixin:
...
@classmethod
def validate_data(cls, instance: dict):
# by default, there is no additional data validation
return True
@classmethod
def instance_is_valid(cls, instance: dict, raise_exception: bool = False) -> bool:
try:
# perform json_schema loading validation
validate(instance=instance, schema=cls.schema.json_schema()) # type: ignore
# perform optional additional validation with override "validate_data" method
is_valid = cls.validate_data(instance)
if raise_exception and not is_valid:
raise ValueError()
except jsonschexc.ValidationError:
if raise_exception:
raise ValueError()
@classmethod
def get_object(cls, instance: dict, validate=True) -> JsonSchemaMixin:
if validate:
cls.instance_is_valid(instance, raise_exception=True) # type: ignore
return cls.schema.from_dict(instance) # type: ignore
""" App parent class """
class BaseApp(ABC):
@abstractproperty
def validator(self) -> BaseValidator:
...
def __init__(self, msg: dict):
self.validated_msg = self.validator.get_object(msg)
""" Below is a specific example for a handler which
expects to extract "foo" and "bar" from the input,
and tries to validate the input foo==42
"""
@dataclass
class SomeDataModel(JsonSchemaMixin):
foo: int
bar: str
class HandlerAppValidator(BaseValidator):
schema = SomeDataModel # type: ignore
@classmethod
def validate_data(cls, instance: dict): # type: ignore
# make specific validation checks for the input instance,
# even after the schema is successfully loaded
return instance["foo"] == 42
class HandlerApp(BaseApp):
validator = HandlerAppValidator # type: ignore
def __init__(self, input_: dict):
self.instance_is_valid = True
try:
super().__init__(input_)
except ValueError:
self.instance_is_valid = False
def execute(self, *args, **kwargs):
# if data model could not be read from sb message, exit
if not self.instance_is_valid:
return
... # do something with the self.validated_msg
self.validated_msg
The concept is that many different HandlerApps can use this same structure, where each of them have their own Validator with their own Schema.
I feel like there are improvements to be made here as there are a few places where the mypy linter is unhappy (hence the # type: ignore). I'm wondering if there is some best practice approach when multiple applications are built on the same core validation structure