import pytest import factory from collections import namedtuple from wtforms import Form, fields from pydantic_form import PydanticForm from pydantic import BaseModel, ValidationError from pydantic.types import StrictStr from typing import List DataFactories = namedtuple( "DataFactories", ['valid', 'bad', 'missing', 'mixed'] ) ExpectedErrors = namedtuple( "ExpectedErrors", ['valid', 'bad', 'missing', 'mixed'] ) Scenario = namedtuple( "ScenarioClasses", ['form', 'schema', 'keys', 'data_factory', 'errors'] ) Keys = namedtuple( "Keys", ["class_", "instance"] ) @pytest.fixture def scenario(request): return request.getfixturevalue(request.param) class MissingDataFactory(factory.Factory): class Meta: model = dict # Simplest case simple_keys = Keys( [('integer',), ('string',), ('float',)], [('integer',), ('string',), ('float',)] ) class SimpleSchema(BaseModel): integer: int string: StrictStr float: float class SimpleWTForm(Form): _schema = SimpleSchema integer = fields.IntegerField() string = fields.StringField() float = fields.FloatField() class SimpleForm(SimpleWTForm, PydanticForm): _schema = SimpleSchema class SimpleDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') string = factory.Faker('pystr') float = factory.Faker('pyfloat') class SimpleBadDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pystr') string = factory.Faker('pyint') float = factory.Faker('pystr') class SimpleMixedDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') float = factory.Faker('pystr') simple_data_factories = DataFactories( SimpleDataFactory, SimpleBadDataFactory, MissingDataFactory, SimpleMixedDataFactory ) simple_expected_errors = ExpectedErrors( {}, { 'integer': 'type_error.integer', 'string': 'type_error.str', 'float': 'type_error.float' }, { 'integer': 'value_error.missing', 'string': 'value_error.missing', 'float': 'value_error.missing', }, { 'string': 'value_error.missing', 'float': 'type_error.float' } ) @pytest.fixture def scenario_simple(): return Scenario( SimpleForm, SimpleSchema, simple_keys, simple_data_factories, simple_expected_errors ) # Simple list case simple_list_keys = Keys( [('integers',), ('strings',), ('floats',)], [ ('integers', 0), ('integers', 1), ('strings', 0), ('strings', 1), ('floats', 0), ('floats', 1) ], ) class SimpleListSchema(BaseModel): integers: List[int] strings: List[StrictStr] floats: List[float] class SimpleListWTForm(Form): _schema = SimpleSchema integers = fields.FieldList(fields.IntegerField(), min_entries=2) strings = fields.FieldList(fields.StringField(), min_entries=2) floats = fields.FieldList(fields.FloatField(), min_entries=2) class SimpleListForm(SimpleListWTForm, PydanticForm): _schema = SimpleListSchema class SimpleListDataFactory(factory.Factory): class Meta: model = dict integers = factory.List([factory.Faker('pyint'), factory.Faker('pyint')]) strings = factory.List([factory.Faker('pystr'), factory.Faker('pystr')]) floats = factory.List([factory.Faker('pyfloat'), factory.Faker('pyfloat')]) class SimpleListBadDataFactory(factory.Factory): class Meta: model = dict integers = factory.List([factory.Faker('pystr'), factory.Faker('pystr')]) strings = factory.List([factory.Faker('pyint'), factory.Faker('pyint')]) floats = factory.List([factory.Faker('pystr'), factory.Faker('pystr')]) class SimpleListMixedDataFactory(factory.Factory): class Meta: model = dict integers = factory.List([factory.Faker('pyint'), factory.Faker('pyint')]) floats = factory.List([factory.Faker('pystr'), factory.Faker('pystr')]) simple_list_data_factories = DataFactories( SimpleListDataFactory, SimpleListBadDataFactory, MissingDataFactory, SimpleListMixedDataFactory ) simple_list_expected_errors = ExpectedErrors( {}, { 'integers': ['type_error.integer', 'type_error.integer'], 'strings': ['type_error.str', 'type_error.str'], 'floats': ['type_error.float', 'type_error.float'] }, { 'integers': 'value_error.missing', 'strings': 'value_error.missing', 'floats': 'value_error.missing' }, { 'strings': 'value_error.missing', 'floats': ['type_error.float', 'type_error.float'] } ) @pytest.fixture def scenario_simple_list(): return Scenario( SimpleListForm, SimpleListSchema, simple_list_keys, simple_list_data_factories, simple_list_expected_errors ) # Case with one level of nesting nested_keys = Keys( [('integer',), ('nested', 'integer'), ('nested', 'string'), ('nested', 'float',)], [('integer',), ('nested', 'integer'), ('nested', 'string'), ('nested', 'float',)] ) class NestedSchema(BaseModel): integer: int nested: SimpleSchema class NestedWTForm(Form): _schema = NestedSchema integer = fields.IntegerField() nested = fields.FormField(SimpleForm) class NestedForm(NestedWTForm, PydanticForm): _schema = NestedSchema class NestedDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') nested = factory.SubFactory(SimpleDataFactory) class NestedBadDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pystr') nested = factory.SubFactory(SimpleBadDataFactory) class NestedMixedDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') nested = factory.SubFactory(SimpleMixedDataFactory) nested_data_factories = DataFactories( NestedDataFactory, NestedBadDataFactory, MissingDataFactory, NestedMixedDataFactory ) nested_expected_errors = ExpectedErrors( {}, { 'integer': 'type_error.integer', 'nested': simple_expected_errors.bad }, { 'integer': 'value_error.missing', 'nested': 'value_error.missing', }, { 'nested': simple_expected_errors.mixed }, ) @pytest.fixture def scenario_nested(): return Scenario( NestedForm, NestedSchema, nested_keys, nested_data_factories, nested_expected_errors ) # Case with one level of nesting inside a list nested_list_keys = Keys( [('integer',), ('nested_list', 'integer'), ('nested_list', 'string'), ('nested_list', 'float')], [ ('integer',), ('nested_list', 0, 'integer'), ('nested_list', 1, 'integer'), ('nested_list', 0, 'string'), ('nested_list', 1, 'string'), ('nested_list', 0, 'float'), ('nested_list', 1, 'float') ] ) class NestedListSchema(BaseModel): integer: int nested_list: List[SimpleSchema] class NestedListWTForm(Form): _schema = NestedListSchema integer = fields.IntegerField() nested_list = fields.FieldList(fields.FormField(SimpleForm), min_entries=2) class NestedListForm(NestedListWTForm, PydanticForm): _schema = NestedListSchema class NestedListDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') nested_list = factory.List([ factory.SubFactory(SimpleDataFactory), factory.SubFactory(SimpleDataFactory) ]) class NestedListBadDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pystr') nested_list = factory.List([ factory.SubFactory(SimpleBadDataFactory), factory.SubFactory(SimpleBadDataFactory) ]) class NestedListMixedDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') nested_list = factory.List([ factory.SubFactory(SimpleMixedDataFactory), factory.SubFactory(SimpleMixedDataFactory) ]) nested_list_data_factories = DataFactories( NestedListDataFactory, NestedListBadDataFactory, MissingDataFactory, NestedListMixedDataFactory ) nested_list_expected_errors = ExpectedErrors( {}, { 'integer': 'type_error.integer', 'nested_list': [ {'integer': 'type_error.integer'}, {'string': 'type_error.str'} ], }, { 'integer': 'value_error.missing', 'nested_list': 'value_error.missing', }, { 'nested_list': [ simple_expected_errors.mixed ] }, ) @pytest.fixture def scenario_nested_list(): return Scenario( NestedListForm, NestedListSchema, nested_list_keys, nested_list_data_factories, nested_list_expected_errors ) # Case with two levels of nesting double_nested_keys = Keys([ ('integer',), ('double_nested', 'integer'), ('double_nested', 'nested', 'integer'), ('double_nested', 'nested', 'string'), ('double_nested', 'nested', 'float') ], [ ('integer',), ('double_nested', 'integer'), ('double_nested', 'nested', 'integer'), ('double_nested', 'nested', 'string'), ('double_nested', 'nested', 'float') ] ) class DoubleNestedSchema(BaseModel): integer: int double_nested: NestedSchema class DoubleNestedWTForm(Form): _schema = DoubleNestedSchema integer = fields.IntegerField() double_nested = fields.FormField(NestedWTForm) class DoubleNestedForm(DoubleNestedWTForm, PydanticForm): _schema = DoubleNestedSchema double_nested = fields.FormField(NestedForm) class DoubleNestedDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') double_nested = factory.SubFactory(NestedDataFactory) class DoubleNestedBadDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pystr') double_nested = factory.SubFactory(NestedBadDataFactory) class DoubleNestedMixedDataFactory(factory.Factory): class Meta: model = dict integer = factory.Faker('pyint') double_nested = factory.SubFactory(NestedMixedDataFactory) double_nested_data_factories = DataFactories( DoubleNestedDataFactory, DoubleNestedBadDataFactory, MissingDataFactory, DoubleNestedMixedDataFactory ) double_nested_expected_errors = ExpectedErrors( {}, { 'integer': 'type_error.integer', 'double_nested': { 'integer': 'type_error.integer', 'nested': simple_expected_errors.bad } }, { 'integer': 'value_error.missing', 'double_nested': 'value_error.missing', }, { 'double_nested': { 'nested': simple_expected_errors.mixed } }, ) @pytest.fixture def scenario_double_nested(): return Scenario( DoubleNestedForm, DoubleNestedSchema, double_nested_keys, double_nested_data_factories, double_nested_expected_errors )