litestar-org / polyfactory Goto Github PK
View Code? Open in Web Editor NEWSimple and powerful factories for mock data generation
Home Page: https://polyfactory.litestar.dev/
License: MIT License
Simple and powerful factories for mock data generation
Home Page: https://polyfactory.litestar.dev/
License: MIT License
Hello :)
I just wanted to try, but i get a missing import module.
from pydantic_factories.value_generators.constrained_number import (
File "/.../pypoetry/virtualenvs/8L729wLl-py3.8/lib/python3.8/site-packages/pydantic_factories/value_generators/constrained_number.py", line 7, in <module>
from tests.utils import passes_pydantic_multiple_validator
I not sure if this is missing or a wrong import but it seem this file/folder/module is not part of your package.
I just install pydantic_factories
with poetry add pydantic_factories
Anyway, thanks for your work it's seem promising
Hello!
New in version 1.5.0 this previously working code seems to be broken
from pydantic import BaseModel
from pydantic_factories import ModelFactory
class MySubClass(BaseModel):
sub_value:str
class MyClass(BaseModel):
sub_obj: MySubClass
class MyClassFactory(ModelFactory[MyClass]):
__model__ = MyClass
MyClassFactory.build(sub_obj=MySubClass(sub_value="wat"))
Produces the following error
.../.venv/bin/python .../reproduce.py
Traceback (most recent call last):
File ".../reproduce.py", line 17, in <module>
MyClassFactory.build(sub_obj=MySubClass(sub_value="wat"))
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 555, in build
if cls.should_set_field_value(field_name, model_field, **kwargs):
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 523, in should_set_field_value
not is_field_in_kwargs or cls._is_pydantic_with_partial_fields(field_name, model_field, **kwargs)
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 503, in _is_pydantic_with_partial_fields
return any(
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 504, in <genexpr>
cls._is_kwargs_missing_pydantic_fields(pydantic_model, pydantic_model_kwargs)
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 483, in _is_kwargs_missing_pydantic_fields
kwargs_field_names = set(pydantic_model_kwargs.keys())
AttributeError: 'tuple' object has no attribute 'keys'
Hello!
When setting a .build()
override for a dict where the value is a Pydantic model, it seems to be ignored.
pydantic-factories version 1.6.2
Following code repoduces: note that i made a simple dict to show that it doesn't work with classes only
from pydantic import BaseModel
from pydantic_factories import ModelFactory, Use
class MyMappedClass(BaseModel):
val: str
class MyClass(BaseModel):
my_mapping_obj: dict[str, MyMappedClass]
my_mapping_str: dict[str, str]
class MyClassFactory(ModelFactory[MyClass]):
__model__ = MyClass
obj = MyClassFactory.build(
my_mapping_obj={"foo": MyMappedClass(val="bar")},
my_mapping_str={"foo": "bar"},
)
print(obj.json(indent=2))
Output is
{
"my_mapping_obj": {
"xNLsYhvhemQDptZfpDWz": {
"val": "DzMmWPdDALDUROEsduqT"
}
},
"my_mapping_str": {
"foo": "bar"
}
}
expected output is
{
"my_mapping_obj": {
"foo": { <---
"val": "bar" <---
}
},
"my_mapping_str": {
"foo": "bar"
}
}
Thank you!
When using ModelFactory.seed_random
, UUID
fields do not generate deterministic values.
from pydantic import BaseModel
from pydantic_factories import ModelFactory
from uuid import UUID
class UUIDModel(BaseModel):
id: UUID
string: str
class UUIDModelFactory(ModelFactory):
__model__ = UUIDModel
UUIDModelFactory.seed_random(5)
rmodel1 = UUIDModelFactory.build()
UUIDModelFactory.seed_random(5)
rmodel2 = UUIDModelFactory.build()
# This assertion passes, as expected
assert rmodel1.string == rmodel2.string
# This assertion unexpectedly fails
assert rmodel1.id == rmodel2.id
What happened
Since version 1.5, the factory.build function ignores passed arguments for certain types. For instance, this was verified for a pydantic model which had a field of type Mapping[str, NestedModel]. On the other hand, a field of type Mapping[str, str] is still accepted and taken into account during the build call.
What did you expect to happen
Expected that the build function respects the passed arguments as version 1.4.1
How can we reproduce it
class NestedSchema(BaseModel):
v: str
z: int
class UpperSchema(BaseModel):
a: int
b: Mapping[str, str]
nested: Mapping[str, NestedSchema]
class NestedSchemaFactory(ModelFactory):
__model__ = NestedSchema
class UpperSchemaFactory(ModelFactory):
__model__ = UpperSchema
def test_factory_not_ok():
nested = NestedSchema(v="hello", z=0)
some_dict = {"test": "fine"}
upper = UpperSchemaFactory.build(b=some_dict, nested={"nested": nested})
assert upper.b["test"] == "fine" # this is ok
# all assertions below fail
assert "nested" in upper.nested
random_new_key = [k for k in upper.nested][0]
assert upper.nested[random_new_key].v == nested.v
assert upper.b[random_new_key].z == nested.z
Anything else we need to know
A nested schema without being nested in another dict (as shown in the example) works as intended
Affected versions
all 1.5 versions
Hey thanks for adding the register_fixtures decorator. One issue I've got with this is that I can't reference a factory fixture in other factories. We have something like this:
class BarFactory(ModelFactory):
__model__ = Bar
baz = 42
class FooFactory(ModelFactory):
__model__ = Foo
bar = Use(BarFactory.build)
We use BarFactory in some tests by itself, so if we register the fixture, that works great. But I'm having trouble figuring out how to use the bar_factory fixture inside of the FooFactory. We can't do this:
@register_fixture
class BarFactory(ModelFactory):
__model__ = Bar
baz = 42
@register_fixture
class FooFactory(ModelFactory):
__model__ = Foo
bar = Use(BarFactory.build)
... because BarFactory is now a FixtureFunction
and we get an AttributeError: 'function' object has no attribute 'build'. We can't do bar = Use(bar_factory.build)
since bar_factory is a fixture that isn't available to the field. If we could access fixtures within the Factory definition, then we could do other things like use the pytest request fixture to allow for parameterization. Actually, I suppose if we just had access to the pytest request fixture, we could get any other fixture we needed and we could parametrize, too.
Do you have any thoughts on how we can make the pytest request fixture (or other fixtures) available in the Factory definition?
I'm trying to generate some deeply nested models, and somewhere in the tree are fields defined as Optional[Exception]
. Pydantic Factories doesn't have a handler for Exception. I'm not sure if that's intentional, but it creates a problem for me. I tried to override get_provider_map
, but this didn't help because the field is in a nested model.
When a factory dynamically generates a new ModelFactory, the generated factory doesn't use the parent's overridden class methods. This makes it difficult or impossible to influence how those models are generated and resolve any errors in the process.
I was able to work around the issue by also overriding create_factory
, so that generated factories will inherit from their parent in the hierarchy. If this is the preferred solution, then it would be great to have that documented. Otherwise, it would be good to have an option to enable that inheritance or otherwise pass on the top level get_provider_map
.
Also, is it intentional that there's no provider for Exception?
Anyway, thank you this library is great and it's going to make my tests much more maintainable.
class RootFactory(ModelFactory):
__model__ = RootModel
@classmethod
def get_provider_map(cls) -> Dict[Any, Callable]:
provider_map = super().get_provider_map()
provider_map.update({Exception: Exception})
return provider_map
@classmethod
def create_factory(cls, model, base=None, **kwargs):
return super().create_factory(model, cls, **kwargs)
Hello, thanks for this useful project! ๐
As for the title of my question, in factory_boy we can access faker like this:
class RandomUserFactory(factory.Factory):
class Meta:
model = models.User
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
The goal is to generate realistic values, as for example first names, that will be handled by faker.
How to access faker, in a similar way it is possible in factory_boy?
I was able with
class PersonFactory(ModelFactory[Person]):
__model__ = Person
name = ModelFactory._get_faker().first_name
But being _get_faker()
private I was wondering why and if there is a public method to this.
Hi! I'm using Field to define JSON Schema. The regex randomisation is working, but it would be more useful if the seed would work as well.
Minimal working example:
from pydantic import BaseModel, Field
from pydantic_factories import ModelFactory
class MyModel(BaseModel):
id: int
special_id: str = Field(
title='study',
description='the study id for the data',
regex=r'study-[1-9]{3}\.[1-9]{3}'
)
class MyModelFactory(ModelFactory):
__model__ = MyModel
ModelFactory.seed_random(1651)
print(MyModelFactory.build())
# id=4 special_id='study-281.776'
# id=4 special_id='study-297.351'
# id=4 special_id='study-249.654'
# ...
Looks like introduced in #74
app_1 | File "/app/./app/main.py", line 14, in <module>
app_1 | from starlite import Provide, Starlite
app_1 | File "/usr/local/lib/python3.10/site-packages/starlite/__init__.py", line 1, in <module>
app_1 | from starlite.app import Starlite
app_1 | File "/usr/local/lib/python3.10/site-packages/starlite/app.py", line 11, in <module>
app_1 | from starlite.asgi import (
app_1 | File "/usr/local/lib/python3.10/site-packages/starlite/asgi.py", line 33, in <module>
app_1 | from starlite.utils import AsyncCallable
app_1 | File "/usr/local/lib/python3.10/site-packages/starlite/utils/__init__.py", line 8, in <module>
app_1 | from .model import convert_dataclass_to_model, create_parsed_model_field
app_1 | File "/usr/local/lib/python3.10/site-packages/starlite/utils/model.py", line 5, in <module>
app_1 | from pydantic_factories.utils import create_model_from_dataclass
app_1 | File "/usr/local/lib/python3.10/site-packages/pydantic_factories/__init__.py", line 11, in <module>
app_1 | from .plugins import register_fixture
app_1 | File "/usr/local/lib/python3.10/site-packages/pydantic_factories/plugins/__init__.py", line 1, in <module>
app_1 | from .pytest_plugin import register_fixture
app_1 | File "/usr/local/lib/python3.10/site-packages/pydantic_factories/plugins/pytest_plugin.py", line 6, in <module>
app_1 | import pytest
app_1 | ModuleNotFoundError: No module named 'pytest'
We apparently are missing the ConstrainedDate
type, and thus must add it.
Hello, thanks for making this library. Been looking forward to seeing something like this.
i noticed that in #5 it was decided to include None as a possible value for Optional
fields. i can certainly see the use case for that. In my case, i'm using pydantic-factories
to dynamically generate example data for FastAPI OpenAPI docs and would rather have these fields include values by default, and certainly don't want non-deterministic behavior.
Seems to me that a class variable could be set to support three possible behaviors: 1) always include, 2) sometimes include, 3) never include. We could leave the default behavior as sometimes include. That would satisfy my requirement for being able to reliably generate values for Optional fields dynamically.
That said, there is really a matrix of two cases: whether an Optional
field should be included, and whether its value could be None
. i noticed that you have a should_set_field_value
classmethod for the former case, and then the second case seems to be handled inside get_field_value
as a function of the result of the is_optional
and a random boolean value.
Because the code for the library is so well modularized, i was able to get something working for my needs, so maybe class variables won't be deemed as necessary.
class SchemaFactory(ModelFactory):
""" Factory which always creates a value for an Optional field.
"""
__model__ = model
@classmethod
def create_factory(cls, model: Type[BaseModel]) -> "ModelFactory":
# NOTE: i needed this because the create_factory method was no using my custom base factory class for child objects
# If something like the following works, it would make subclassing easier for similar uses
return cast(
cls,
type(
f"{model.__name__}Factory",
(cls,),
{
"__model__": model,
"__faker__": cls._get_faker(),
},
),
)
@classmethod
def should_set_field_value(cls, field_name: str, **kwargs) -> bool:
""" Always set a field value """
return True
@classmethod
def get_field_value(cls, model_field: ModelField) -> Any:
"""Returns a field value on the sub-class if existing, otherwise returns a mock value"""
if model_field.field_info.const:
return model_field.get_default()
# NOTE: skipping the `is_optional` check.
# if is_optional(model_field=model_field) and not create_random_boolean():
# return None
outer_type = model_field.outer_type_
if isinstance(outer_type, EnumMeta):
return cls.handle_enum(outer_type)
if is_pydantic_model(outer_type) or is_dataclass(outer_type):
return cls.create_factory(model=outer_type).build()
if cls.is_constrained_field(outer_type):
return cls.handle_constrained_field(model_field=model_field)
if model_field.sub_fields:
return handle_complex_type(model_field=model_field, model_factory=cls)
# this is a workaround for the following issue: https://github.com/samuelcolvin/pydantic/issues/3415
field_type = model_field.type_ if model_field.type_ is not Any else outer_type
if cls.is_ignored_type(field_type):
return None
return cls.get_mock_value(field_type=field_type)
Two things which would have made this implementation simpler are:
create_factory
class method (so it wouldn't have to be overridden to affect behavior for child objects)is_optional
/create_random_boolean
bit, so the get_field_value
method didn't have to be overridden.When using pydantic factories, we encountered a use case where we we sometimes want to create pydantic models without validating them
this can be achieved in pydantic by using the construct method
we were thinking about adding this functionality to pydantic factories as well,
probably by adding a new class bool variable
@classmethod
def build(cls, **kwargs) -> T:
"""builds an instance of the factory's Meta.model"""
for field_name, model_field in cls.get_model_fields(cls._get_model()):
if model_field.alias:
field_name = model_field.alias
if cls.should_set_field_value(field_name, **kwargs):
if hasattr(cls, field_name):
kwargs[field_name] = cls.handle_factory_field(field_name=field_name)
else:
kwargs[field_name] = cls.get_field_value(model_field=model_field)
if cls.__validate_pydantic_model__:
return cls.__model__(**kwargs)
return cls.__model__.construct(**kwargs)
Happy to open a PR if OK by you?
Hi,
Given the following Pydantic model and factory:
from pydantic import BaseModel, condecimal
from pydantic_factories import ModelFactory
class MyModel(BaseModel):
qty: condecimal(max_digits=8, decimal_places=5, gt=0, le=Decimal('999.99999'))
class MyFactory(ModelFactory[MyModel]):
__model__ = MyModel
If we run
for _ in range(10000):
MyFactory.build()
The following error would be raised:
ValidationError: 1 validation error for MyModel
qty
ensure that there are no more than 3 digits before the decimal point (type=value_error.decimal.whole_digits; whole_digits=3)
The issue comes from the fact that pydantic_factories.constraints.constrained_decimal_handler.handle_decimal_length
would return an invalid value.
For example:
handle_decimal_length(generated_decimal=Decimal('999.9'), max_digits=8, decimal_places=5)
would return Decimal('9990')
instead of Decimal('999.90000')
As a user, I want to easily extend the available mappers of the pydantic model attribute types, for example, if there is a class that has an attribute type not supported by pydantic-factories
and involves third-party libraries like this case, the extension of the mappers should be done on the user side.
Right now to do this the user has to subclass ModelFactory, override the method and extend the base value. This has the downsides that:
get_provider_map
is generated on the return
statement, to override the method the user has to copy and paste the whole dictionary potentially being outdated on future pydantic-factories
releases.What I'm proposing is that ModelFactory
has an internal attribute settable by the user where she can define the additional mappers, for example to give support to FakeDateTime
the user's code would be:
from freezegun.api import FakeDatetime
class CustomModelFactory(ModelFactory):
additional_mappers = { FakeDateTime: cls._get_faker().date_time_between }
We'll have to tweak get_provider_map
so that it merges the default mapper dictionary with the user's
It would be great to be able to customize the field of a sub-factory (or sub-sub-factory, etc.) right from the parent's build method. For example:
class Bar(BaseModel):
x: int
class Foo(BaseModel):
a: int
bar: Bar
class FooFactory(ModelFactory):
__model__ = Foo
test_foo = FooFactory.build(bar__x=42)
print(f'{test_foo!r}') # prints Foo(a=9817, bar=Bar(x=42))
A real example of code that I have to write is something like this:
def test_the_lambda_handler(item_of_interest_fixture):
test_event = SqsModelFactory.build((Records=[
SqsRecordsFactory.build(
body=WebhookEventFactory.build(
details=WebhookEventDetailsFactory.build(resource_id=item_of_interest_fixture.id)
).json()
)
]))
I'd love to be able to do something like this:
def test_the_lambda_handler(item_of_interest_fixture):
test_event = SqsModelFactory.build(
Records = SqsRecordsFactory.batch(size=1, body__details__resource_id=item_of_interest_fixture.id)
)
Everything else can be either the default values or whatever I've customized in the various sub-factories and the test has the value I'm interested in.
When specifying my model, using a Literal
, I run into an error in the generation.
class MyModel(BaseModel):
mykey: Literal['attribute']
Pydantic model error output
E pydantic.error_wrappers.ValidationError: 1 validation error for MyModel
E mykey
E none is not an allowed value (type=type_error.none.not_allowed)
Guessing it's currently returning None
rather than "attribute"
.
I might be mis-using pydantic here. Are you able to confirm whether this is a bug or user error @Goldziher? ๐ Happy to help resolve this if it's a bug.
Similar to other factory boy extensions, I'd expect to be able to simply construct the object and not have to call .build
, like the basic usage example from the factory boy docs
from pydantic import BaseModel
from pydantic_factories import ModelFactory
class Person(BaseModel):
id: int
name: str
phone_number: str
class PersonFactory(ModelFactory):
__model__ = Person
# rn I have to do PersonFactory.build()
print(PersonFactory())
#> id=6744 name='wqIOcAeNxRziXJlQEBqr' phone_number='lEJcvhasvbUJWRPvHMez'
It'd be nice if I could change this in my packages that serialize using Pydantic and not change every downstream test to use .build
Hi, when using the ModelFactory
I get the next mypy error:
Class cannot subclass "ModelFactory" (has type "Any") [misc]
For example with the code in the readme:
from datetime import date, datetime
from typing import List, Union
from pydantic import BaseModel, UUID4
from pydantic_factories import ModelFactory
class Person(BaseModel):
id: UUID4
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
class PersonFactory(ModelFactory):
__model__ = Person
I think this might be related with this pydantic issue.
Do you have any idea on how to solve it?
When I have
__allow_none_optionals__= True
I'm getting an error: RecursionError: maximum recursion depth exceeded while getting the repr of an object
# snip
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 94, in handle_complex_type
return model_factory.get_field_value(model_field=model_field)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/factory.py", line 431, in get_field_value
return handle_complex_type(model_field=model_field, model_factory=cls)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 94, in handle_complex_type
return model_factory.get_field_value(model_field=model_field)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/factory.py", line 431, in get_field_value
return handle_complex_type(model_field=model_field, model_factory=cls)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 88, in handle_complex_type
if is_union(model_field=model_field) and model_field.sub_fields:
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/utils.py", line 74, in is_union
return repr(model_field.outer_type_).split("[")[0] == "typing.Union"
RecursionError: maximum recursion depth exceeded while getting the repr of an object`
Here's a simple (python 3.10) reproduce script:
import uuid
from pydantic import BaseModel
from pydantic_factories import ModelFactory
class Location(BaseModel):
id: uuid.UUID | str | None
name: str | None
class LocationFactory(ModelFactory):
__model__ = Location
__allow_none_optionals__ = False
if __name__ == "__main__":
LocationFactory.build()
Version info:
โฏ pip freeze | grep pydantic
pydantic==1.9.0
pydantic-factories==1.2.6
โฏ python --version
Python 3.10.2
It would be super handy to have some method that would easily create a fixture out of a factory. For example:
class SomeLongNamedModel(BaseModel):
id: UUID
@register_fixture # Or some better name?
class SomeLongNamedModelFactory(ModelFactory):
"""Factory to create SomeLongNameModel instances
Use this factory to generate SomeLongNameModels filled with random data
"""
__model__ = SomeLongNamedModel
And then
$ pytest --fixtures
# other output...
some_long_named_model_factory
Factory to create SomeLongNameModel instances
Use this factory to generate SomeLongNameModels filled with random data
This is following the model of pytest-factoryboy
which automatically converts the PascalCase class names to be the snake_case equivalent for the fixtures.
Hi,
I have the problem integrating your tool with my models to keep correct constraints keys etc.
Please give me some examples or tips.
Hey,
Not a big deal, but could you reverse the order of entries in the CHANGELOG.md? Just wanted to see what got changed in the recent update and had to scroll the list to the bottom first ๐
Just like the main starlite repo for example - https://github.com/starlite-api/starlite/blob/main/CHANGELOG.md - new entries are always placed on top.
Hello!
Given the following models:
class B(BaseModel):
c: str
class A(BaseModel):
b: B
I want to generate instances of type A
, while keeping c
deterministic.
I tried to do the following:
class BFactory(ModelFactory):
__model__ = B
c = "deterministic-value"
class AFactory(ModelFactory):
__model__ = A
When I call AFactory.build()
, c
is randomly generated, so it's not using BFactory
.
Is there any way to make AFactory
use BFactory
without having to set it explicitly as b = BFactory.build()
in AFactory
?
When importing the ModelFactory
mypy returns the error:
Skipping analyzing "pydantic_factories": found module but no type hints or library stubs [import]
When using the code:
from pydantic_factories import ModelFactory # error raised here
This can be patched by adding the next lines to mypy.ini
:
[mypy-pydantic_factories.*]
ignore_missing_imports = True
But it would be better if pydantic-factories
provided the information. Mypy gives some hints on how to make your library compatible with mypy.
In all cases except a few, the feature to alternate between None
and a value for Optional fields is very useful. However, in some cases I need there to always be a value. Now, one could import Faker
and create a random value manually; however, something like the following would be super helpful:
MyModelFactory.build(x="some value", optional_y=Require())
In fact, I tried this on the offchance that it would work, but alas no!
If you have any thoughts on this feature, I could take a stab at implementing, but some guidance on how you would do it would be grand. Cheers!
I have something similar to:
class MyModel(BaseModel):
my_key: Literal["value1", "value2"]
And if I have a factory:
instance_1 = factories.MyModel.build()
instance_2 = factories.MyModel.build()
I get in both cases "value1"
for my_key
even if I try generate more instances.
Amazing library, thank you!
For the case when the data model is complex, there is a need to cover all non-trivial cases.
Looks like in pydantic_factories/factory.py:369 the check for is_optional and not create_random_boolean can be placed to a separate method like it's done with other checks. -> Old version
So if I have:
class B(BaseModel):
...
class A(BaseModel):
b: Optional[B] = None
class AFactory(ModelFactory):
__allow_none_optionals__ = False
__model__ = A
__allow_none_optionals__=False
behavior won't be applied to B, which is correct, but an option to enforce the "never none if optional" would very nice.
Currently, I work around it in tests with:
@pytest.fixture
def patch_pydantic_factories(monkeypatch):
from pydantic_factories import ModelFactory
monkeypatch.setattr(ModelFactory, "__allow_none_optionals__", False)
It would be a nice feature to have access to the already generated field values in a customised generator scenario, e.g.
from typing import Any
from pydantic_factories import ModelFactory
class CustomFactory(ModelFactory[Any]):
"""Tweak the ModelFactory to add our custom mocks."""
@classmethod
def get_mock_value(cls, field_type: Any, previous_fields: dict[str, Any]) -> Any:
"""Add our custom mock value."""
if str(field_type) == "my_dependant_field" and previous_fields["my_relying_on"] == 'special_value':
return cls._get_faker().date_time_between()
return super().get_mock_value(field_type)
I could even imagine some decorator or annotation based solution to the same problem, e.g.
class MyFactory(ModelFactory):
__model__ = MyModel
@depends('field_relying_on')
def dependant_field_name(self, current_value: Any, other_value: Any):
return 'special_value_generated_based_on_context'
When we have this:
class Foo(BaseModel):
name: str
class Foos(BaseModel):
__root__: dict(str, Foo)
and this:
class FooFactory(ModelFactory):
__model__ = Foo
class FoosFactory(ModelFactory):
__model__ = Foos
There is no way (that I can tell) to customize the key value of the root model. I'd like to be able to do this
test_foos = FoosFactory.build(root_key='asdf_qewr_1234_wow', foo=FooFactory.build(name='omg')) # root_key isn't a thing, but just pretend
print(test_foos.json(indent=2))
"""
this would output
{
'asdf_qwer_1234_wow': {
'name': 'omg'
}
}
"""
Or maybe this:
class Foos_factory(ModelFactory):
__model__ = Foos
root_key = f'FOO#{ModelFactory._get_faker().uuid4()}'
I don't know if root_key is the right value here, but it would be very useful to have something to hook into to customize this value.
Defined here:
# Remember all of the fields on our class (including bases). This
# also marks this class as being a dataclass.
setattr(cls, _FIELDS, fields)
Where _FIELDS
is:
# The name of an attribute on the class where we store the Field
# objects. Also used to check if a class is a Data Class.
_FIELDS = '__dataclass_fields__'
I ran into this when trying to use the types in Starlite:
/home/peter/PycharmProjects/starlite/starlite/types/partial.py
/home/peter/PycharmProjects/starlite/starlite/types/partial.py:137:49 - error: Protocol class "DataclassProtocol" does not define "__dataclass_fields__" as a ClassVar (reportGeneralTypeIssues)
Hi @Goldziher first of all, thank you, thank you and thank you. I've been close to create something like this several times. You're a life saver :)
In most of my pydantic models I have some property of type Optional[...]
. It would be awesome if pydantic-factories
was able to understand them and randomly return None
on some of the objects.
I dealt with this problem in the past, and created faker-optional
a faker
provider just for that, maybe it's interesting for this use case.
Thanks!
If the original pydantic model's field is aliased then only the aliased name could be overridden even if th allow_population_by_field_name
config is set to true in the original model.
It would be nice to support the original field names override.
Hi @Goldziher, I'm having issues using pydantic_factories
with pytest_freezegun
, the problem is that pytest_factories is not able to find the factory function for the datetime
attributes
tests/unit/test_source.py:16: in test_cant_merge_if_url_is_not_equal
original = SourceFactory.build()
../../.venvs/airss/lib/python3.9/site-packages/pydantic_factories/factory.py:495: in build
kwargs[field_name] = cls.get_field_value(model_field=model_field)
../../.venvs/airss/lib/python3.9/site-packages/pydantic_factories/factory.py:438: in get_field_value
return cls.get_mock_value(field_type=field_type)
../../.venvs/airss/lib/python3.9/site-packages/pydantic_factories/factory.py:339: in get_mock_value
raise ParameterError(
E pydantic_factories.exceptions.ParameterError: Unsupported type: <class 'datetime.datetime'>
E
E Either extend the providers map or add a factory function for this model field
Adding a pdb
trace on line 339 I get the next result:
(Pdb) cls.get_provider_map().get(field_type)
(Pdb) cls.get_provider_map().get(datetime)
<bound method Provider.date_time_between of <faker.providers.date_time.en_US.Provider object at 0x7f004133b580>>
(Pdb) type(field_type)
<class 'type'>
(Pdb) field_type
<class 'datetime.datetime'>
(Pdb) datetime
<class 'freezegun.api.FakeDatetime'>
If you feel it's a corner case and you don't want to support pytest-freezegun
, can you point me in the direction on how to extend the providers map locally?
Thanks! :)
Hello,
TL;DR:
I think pydantic-factory can't handle this line:
Message: Union[str, TypingType[BaseModel]]
I'm trying to build a factory for this model:
class SnsModel(BaseModel):
Records: List[SnsRecordModel]
class SnsModelFactory(ModelFactory[SnsModel]):
__model__ = SnsModel
sns_event = SnsModelFactory.build()
I get the error:
Unsupported type: typing.Type[pydantic.main.BaseModel]
By digging the entire hierarchy of nested models I found it comes from this:
class SnsNotificationModel(BaseModel):
Subject: Optional[str]
TopicArn: str
UnsubscribeUrl: HttpUrl
Type: Literal["Notification"]
MessageAttributes: Optional[Dict[str, SnsMsgAttributeModel]]
Message: Union[str, TypingType[BaseModel]] # <- Trouble HERE
MessageId: str
SigningCertUrl: HttpUrl
Signature: str
Timestamp: datetime
SignatureVersion: str
So I've tried to recreate the entire hierarchy of nested factories (entire reproducible example) based on the hierarchy of nested models (sns events having as payload s3 events) from here and here:
from aws_lambda_powertools.utilities.parser.models import SnsNotificationModel, SnsModel, SnsRecordModel
from aws_lambda_powertools.utilities.parser.models.s3 import S3Model, S3RecordModel, S3Message, S3Object, S3Bucket
from pydantic_factories import ModelFactory
# S3 MODELS
# ------------
class S3ObjectFactory(ModelFactory[S3Object]):
__model__ = S3Object
key = 'aaabbb'
class S3BucketFactory(ModelFactory[S3Bucket]):
__model__ = S3Bucket
key = 'aaabbb'
class S3MessageFactory(ModelFactory[S3Message]):
__model__ = S3Message
object = S3ObjectFactory
bucket = S3BucketFactory
class S3RecordModelFactory(ModelFactory[S3RecordModel]):
__model__ = S3RecordModel
s3 = S3MessageFactory
s3_records = S3RecordModelFactory.batch(size=5)
class S3ModelFactory(ModelFactory[S3Model]):
__model__ = S3Model
Records = s3_records
# SNS MODELS
class SnsNotificationModelFactory(ModelFactory[SnsNotificationModel]):
__model__ = SnsNotificationModel
Message = S3ModelFactory
class SnsRecordModelFactory(ModelFactory[SnsRecordModel]):
__model__ = SnsRecordModel
Sns = SnsNotificationModelFactory
sns_records = SnsRecordModelFactory.batch(size=4)
class SnsModelFactory(ModelFactory[SnsModel]):
__model__ = SnsModel
Records = sns_records
sns_event = SnsModelFactory.build()
And this time I get the error:
2 validation errors for SnsNotificationModel
Message
str type expected (type=type_error.str)
Message
subclass of BaseModel expected (type=type_error.subclass; expected_class=BaseModel)
Somehow it's not happy that the field Message is an S3ModelFactory
class SnsNotificationModelFactory(ModelFactory[SnsNotificationModel]):
__model__ = SnsNotificationModel
Message = S3ModelFactory
but that is odd because the S3ModelFactory
is a factory for the S3Model
that is a direct subclass of BaseModel
.
While randomizing the input is a good idea, big test suits want to remain determined, so the reason of failure can be investigated properly.
In multiple places in the codebase random.choice is used, so if a field belongs to a composite type like Union, random results can be expected even if faker seed is set.
What would be very cool, is a global option or similar where randomization can be disabled completely and only determined results are delivered.
This is mostly a question: would you consider adding shortcut functions to the library @Goldziher?
I run into this pattern a lot. I have a mock like this:
mock = mocker.patch.object(my_object, 'my_method', AsyncMock(return_value=...))
and realize I can use pydantic-factories to provide the return data (yay ๐ )
My issue is that the declaration needed to create a one-off model here is a little bit much for the scenarios where I just want valid data (I'm not testing inputs, I'm testing unrelated behavior, and just need valid filler). To make this work, I currently have to do this:
class MyMockModel(ModelFactory):
__model__ = MyModel
mock_data = MyMockModel().build().dict()
mock = mocker.patch.object(my_object, 'my_method', AsyncMock(return_value=mock_data))
Could it make sense to provide a shortcut function for these cases, where we simply do something like this?
mock_data = default_factory(MyModel).build().dict()
mock = mocker.patch.object(my_object, 'my_method', AsyncMock(return_value=mock_data))
Haven't thought about the naming or implementation - wanted to hear your thoughts first ๐ Happy to help implement this if it seems reasonable.
Hi, there seems to be a bug when generating decimal values between 0 and 1.
With this example:
from decimal import Decimal
from pydantic import BaseModel, Field
from pydantic_factories import ModelFactory
class FractionExample(BaseModel):
fraction: Decimal = Field(ge=0, le=1, decimal_places=2, max_digits=3)
class FractionExampleFactory(ModelFactory):
__model__ = FractionExample
# This raises the error:
FractionExampleFactory.build()
I get following error
Traceback (most recent call last):
File ".../site-packages/pydantic_factories/factory.py", line 370, in handle_constrained_field
return handle_constrained_decimal(field=cast("ConstrainedDecimal", outer_type))
File ".../site-packages/pydantic_factories/constraints/constrained_decimal_handler.py", line 75, in handle_constrained_decimal
validate_max_digits(max_digits=max_digits, minimum=cast("Decimal", minimum), decimal_places=decimal_places)
File ".../site-packages/pydantic_factories/constraints/constrained_decimal_handler.py", line 24, in validate_max_digits
assert max_digits >= len(str(abs(minimum))), "minimum is greater than max_digits"
AssertionError: minimum is greater than max_digits
I'd expect that the factory would create fractions like 0.00, 0.12 and 1.00.
Versions:
The library currently doesnt support TypedDict - but pydantic does. We should extend the library to support it.
HI.
I use sqlmodel-model which combine pydantic and sqlalchemy in one.
Your fantastic library helped me a LOT for unit tests and I really want to tell you THANK YOU.
However, when I implement the persistence handler protocol, somehow, it does save items with an id of None
(since the id is an Optional field, otherwise we would not be able to create a new instance).
I saw that from this issue, you added the None
random value for Optional fields, but there are some fields that are declared as Optional but are not really optional when dealing with persistence (like primary key attribute).
So I wonder how to handle this case :
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
Hi @Goldziher!
First of all, thanks for this superb library, I just started integrating it into my project and it seems very promising. I stumbled upon a problem, though, and I think it might be a problem with the library itself.
The OrmarModelFactory
overrides the get_field_value
method to handle choices field. However, in my model I have a ormar.ForeignKey
field:
user: Optional[Union[User, Dict]] = ormar.ForeignKey(User)
When trying to create an instance of this model using pydantic_factories
, the aforementioned method raises an error:
@classmethod
def get_field_value(cls, model_field: ModelField) -> Any:
"""
We need to handle here both choices and the fact that ormar sets values to be optional
"""
model_field.required = True
> if hasattr(model_field.field_info, "choices") and len(model_field.field_info.choices) > 0: # type: ignore
E TypeError: object of type 'bool' has no len()
The problem is that this model_field actually does have the choices
attribute in the field_info
dict, but it is set to False
. The hasattr(model_field.field_info, "choices")
check does not accommodate for that and returns true, and then len(False)
obviously fails.
I am not sure if I am thinking correctly, but if so, then simply replacing hasattr(model_field.field_info, "choices")
with:
getattr(model_field.field_info, "choices", False)
will resolve the issue (it did it for me).
If I am not missing anything and my solution is right, I can make a PR tomorrow as well :) It's just a 1 line change anyway.
Thanks!
Currently this library does not handle self-referencing models and models with cyclical references. For example:
from pydantic import BaseModel
class A(BaseModel):
bs: List["B"]
class B(BaseModel):
a: A
In the above there are two models with cyclical dependencies - A that has a list of B and B that has A. If we simply resolve the forward ref and then generate values for one of these, we will hit a recursion exception. While my original thinking was that we should not handle this case and instead let users customize factories to handle it manually - this creates problems downstream, see: litestar-org/litestar#196. I would like to discuss how to resolve this.
I tried to create a factory for a model that makes use of a discriminated union and it ends up in an infinite recursion.
python: 3.7
pydantic_factories version: 1.7.1
Below is a minimal example using the pydantic example at https://pydantic-docs.helpmanual.io/usage/types/#nested-discriminated-unions
as a starting point
from typing import Literal, Union
from typing_extensions import Annotated
from pydantic import BaseModel, Field, ValidationError
class BlackCat(BaseModel):
pet_type: Literal['cat']
color: Literal['black']
black_name: str
class WhiteCat(BaseModel):
pet_type: Literal['cat']
color: Literal['white']
white_name: str
Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]
class Dog(BaseModel):
pet_type: Literal['dog']
name: str
Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]
class Model(BaseModel):
pet: Pet
n: int
from pydantic_factories import ModelFactory
class MyModelFactory(ModelFactory):
__model__ = Model
# This will create a max recursion error
obj = MyModelFactory.build()
Hello again!
Sorry to be the bearer of bad news!
We found another problem with the new partial factories when giving a None for an optional attribute.
pydantic-factories==1.5.1
from typing import Optional
from pydantic import BaseModel
from pydantic_factories import ModelFactory
class MySubClass(BaseModel):
sub_value:str
class MyClass(BaseModel):
sub_obj: Optional[MySubClass]
class MyClassFactory(ModelFactory[MyClass]):
__model__ = MyClass
MyClassFactory.build(sub_obj=None)
Will Produce the following
.../.venv/bin/python .../scratch/pydantic_factories_check/reproduce_with_none.py
Traceback (most recent call last):
File ".../reproduce_with_none.py", line 19, in <module>
MyClassFactory.build(sub_obj=None)
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 556, in build
if cls.should_set_field_value(field_name, model_field, **kwargs):
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 524, in should_set_field_value
not is_field_in_kwargs or cls._is_pydantic_with_partial_fields(field_name, model_field, **kwargs)
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 502, in _is_pydantic_with_partial_fields
return cls._is_kwargs_missing_pydantic_fields(pydantic_model, list_of_pydantic_models_kwargs)
File ".../.venv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 486, in _is_kwargs_missing_pydantic_fields
kwargs_field_names = set(pydantic_model_kwargs.keys())
AttributeError: 'NoneType' object has no attribute 'keys'
I'm starting to use the library in clinv, when using the VPCFactory
defined as:
from clinv.model import aws
class VPCFactory(ModelFactory): # type: ignore
"""Define the factory for the model VPC."""
__model__ = aws.VPC
And running VPCFactory.build()
I get the error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/lyz/.venvs/clinv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 363, in build
kwargs[field_name] = cls.get_field_value(field_name=field_name, model_field=model_field)
File "/home/lyz/.venvs/clinv/lib/python3.9/site-packages/pydantic_factories/factory.py", line 333, in get_field_value
return handle_complex_type(model_field=model_field, model_factory=cls)
File "/home/lyz/.venvs/clinv/lib/python3.9/site-packages/pydantic_factories/value_generators/complex_types.py", line 90, in handle_complex_type
return handle_container_type(
File "/home/lyz/.venvs/clinv/lib/python3.9/site-packages/pydantic_factories/value_generators/complex_types.py", line 69, in handle_container_type
value = handle_complex_type(model_field=choice(model_field.sub_fields), model_factory=model_factory)
File "/home/lyz/.venvs/clinv/lib/python3.9/site-packages/pydantic_factories/value_generators/complex_types.py", line 109, in handle_complex_type
raise ParameterError(
pydantic_factories.exceptions.ParameterError: Unsupported type: <enum 'Environment'>
Either extend the providers map or add a factory function for this model field
When the property environment
is not None
.
The interesting snippets of the model definition are:
src/model/aws.py
VPC
inherits from AWSEntity
in the same file that uses the Environment
Enum from another file.
from .entity import Entity, Environment
class AWSEntity(Entity):
environment: Optional[List[Environment]] = Field(default_factory=list)
class VPCID(ConstrainedStr):
"""Define the resource id format."""
regex = re.compile("^vpc-.*$")
class VPC(AWSEntity):
id_: VPCID
cidr: IPvAnyNetwork
region: str
src/model/entity.py
from enum import Enum
class Environment(str, Enum):
STAGING = "Staging"
PRODUCTION = "Production"
TESTING = "Testing"
class Entity(BaseModel):
...
After some debugging, it looks like the handle_complex_type
function is not able to handle the <enum 'Environment'>
. But I have no idea why, as a simple test works:
import re
from enum import Enum
from model import Server
from pydantic import BaseModel, ConstrainedStr
from pydantic_factories import ModelFactory
class ServerID(ConstrainedStr):
"""Define the resource id format."""
regex = re.compile("^server-.*$")
class Environment(str, Enum):
"""Set the possible logical environments."""
STAGING = "Staging"
PRODUCTION = "Production"
TESTING = "Testing"
class Server(BaseModel):
id_: ServerID
name: str
environment: Environment
class ServerFactory(ModelFactory):
__model__ = Server
ServerFactory.build()
Hi!
Thank you for this tool, it's a great idea!
Python: 3.9.9
Libraries:
While trying to use it with mypy in strict mode
[tool.mypy]
strict=true
I would get
error: Missing type parameters for generic type "ModelFactory"
If trying to specify the type with class PersonFactory(ModelFactory[Person])
, i would get
error: Value of type variable "T" of "ModelFactory" cannot be "Person"
I've put up a reproduction repo here: https://github.com/lindycoder/pydantic-factories-mypy-issue
(Disclaimer : still new to mypy)
I think the TypeVar might be misdefined, as per https://mypy.readthedocs.io/en/stable/generics.html#type-variables-with-upper-bounds
Maybe it should be
T = TypeVar("T", bound=Union[BaseModel, DataclassProtocol])
I will try open a PR for this
Given the following Pydantic models and Factory:
from pydantic_factories import ModelFactory
from pydantic import BaseModel
class Pet(BaseModel):
name: str
age: int
class Person(BaseModel):
name: str
pets: list[Pet]
age: int
class PersonFactory(ModelFactory[Person]):
__model__ = Person
When trying to build:
data = {
'name': 'John',
'pets': [
{'name': 'dog'},
{'name': 'cat'},
],
}
PersonFactory.build(**data)
Then the following exception is raised:
ValidationError: 2 validation errors for Person
pets -> 0 -> age
field required (type=value_error.missing)
pets -> 1 -> age
field required (type=value_error.missing)
We see that the age is missing in the data, so the factory is not able to construct the model.
If we add the age to the data:
data = {
'name': 'John',
'pets': [
{'name': 'dog', 'age': 3},
{'name': 'cat', 'age': 4},
],
}
PersonFactory.build(**data)
The factory will construct the model instance:
Person(name='John', pets=[Pet(name='dog', age=3), Pet(name='cat', age=4)], age=5978)
Note: only the age of the Pets (child model) is necessary, ModelFactory handles to randomly fill the age of Person.
It would be great to have the same behaviour of Factory Boy. If we pass only part of the attributes of a child model, then the factory will generate the missing attributes for us.
Can we work on this feature?
Hi, is it true? I got an error:
E pydantic_factories.exceptions.ParameterError: Unsupported type: <class 'xxx.xxx.models.UrlValue'>
E
E Either extend the providers map or add a factory function for this model field
I have two models User
, Review
and their ModelFactory
s.
from pydantic import BaseModel, Field
from pydantic_factories import ModelFactory
class User(BaseModel):
login: str
class Review(BaseModel):
body: str
author: User = Field(alias="user")
class Config:
allow_population_by_field_name = True
class UserFactory(ModelFactory):
__model__ = User
class ReviewFactory(ModelFactory):
__model__ = Review
if __name__ == '__main__':
author: User = UserFactory.build(login="me")
review: Review = ReviewFactory.build(author=author)
assert id(author) != id(review.author)
assert review.author.login != author.login
review: Review = ReviewFactory.build(user=author)
assert id(author) != id(review.author) # ๐ why?
assert review.author.login == author.login
*note: all assertion are successful
Review
model has the allow_population_by_field_name
flag set to True
which means
Review
model should be able to accept both author
and user
attributes to
populate the User
model, however it's not recognized on building the instance and
new instance gets created.
I also noticed that new object was created on supplying a valid User
instance to the ReviewFactory
!!
see the why
line
Hello, I stumbled upon this recently:
class C(BaseModel):
batch: int
class CFactory(ModelFactory):
__model__ = C
CFactory.build() # TypeError: ModelFactory.batch() missing 1 required positional argument: 'size'
โ looks like it fails due to CFactory.build
detecting and incorrectly recognizing CFactory.batch
as a factory field instead of its own classmethod. Similar issue occurs with e.g. build
or get_model_fields
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.