#Create values for nested factories:

1 messages · Page 1 of 1 (latest)

neat oriole
#

How can I create data for a nested factory ?

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

    DiagnosisType = Require()

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis
    StringLengthLimit = Use(StringLengthLimitFactory.build)

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    Activities = Ignore()
    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    Observations = Ignore()

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0

    Timestamp = Ignore()
    Report = Use(ReportFactory.build)

In this case StringLengthLimitFactoryis required but

data_entry = StringFactory.build(DiagnosisType = "abc")

Isnt working. I tried a lot of different attempts but I rly dont understand how you are supposed to do this. Create each factory manual seems crazy.

visual hollow
#

Could you show an example of what you're trying to achieve along with the models??

neat oriole
#

@visual hollow I want to run some test on the validation

class StringLengthLimit(ExtraForbidden):
    DiagnosisType: str = Field(..., max_length=10, min_length=1)

so test 1:
data_entry = StringFactory.build(DiagnosisType = "valid")

test 2:
data_entry = StringFactory.build(DiagnosisType = "this_is_to_long")

test 3:
data_entry = StringFactory.build(DiagnosisType = "0")

#

I could create a new factory for each test but that seems very redunant and creating each factory manual too.
Thats why I thought you could somehow "easy" use parent factory

neat oriole
#

& yes I could use random.choice with a perm seed but I am looking for a way to finetune multiple fields very specifc

neat oriole
#

another attempt was something like this:

data_entry = StringFactory.build(**{"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "1234567890"}}}}})
visual hollow
neat oriole
#

@visual hollow Okay thanks.
Tbh I am right now just lost. I feel like I dont understand the lib at all.

visual hollow
#

Could you show all the models, the factories and the test that you are trying to do? I think I'll be able to explain better if I have all those details.

#

A simplified version is also fine, or the models that you are actually using is also fine. Your choice.

neat oriole
#

Take this as an example:

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    #Activities = Ignore()
    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    #Observations = Ignore()

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)


class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = Use(ReportFactory.build)

If i run this test twice it will generate the same values for all related Observations, it seems like it is seeded

#

data_entry = StringFactory.build()

visual hollow
#

Can you show the INTERNTEST_v1_0_0, Report and Data models?

neat oriole
#
class ExtraForbidden(BaseModel):
    class Config:
        extra = Extra.forbid

class HiddenAttributes(ExtraForbidden):
    class Config:
        def schema_extra(schema: dict, _):
            props = {}        
            for k, v in schema.get('properties', {}).items():
                if not v.get('hidden', False):
                    props[k] = v
            schema['properties'] = props

class MetaDataClass(HiddenAttributes):
    Timestamp: datetime = Field(default_factory=datetime.now, hidden=True)
    RequestType: str = Field("POST", const=True, hidden=True)
    ReportName: str = Field("InternalTest", const=True, hidden=True)
    ReportVersion: str = Field("0", const=True, hidden=True)

class FloatLimit(ExtraForbidden):
    ResultValue: float = Field(..., ge=-10.5, le=10.5)

class FloatNoLimit(ExtraForbidden):
    ResultValue: float

class IntegerLimit(ExtraForbidden):
    ResultValue: int = Field(..., ge=-100, le=100)

class IntegerNoLimit(ExtraForbidden):
    ResultValue: int

class StringEnumLimit_DiagnosisType(str, Enum):
    UDFALD_1 = "Udfald_1"
    UDFALD_2 = "Udfald_2"
    UDFALD_3 = "Udfald_3"
    
class StringLengthLimit(ExtraForbidden):
    DiagnosisType: str = Field(..., max_length=10, min_length=1)

class StringNoLimit(ExtraForbidden):
    DiagnosisType: str
    DiagnoseType: Optional[str]

class DatetimeLimit(ExtraForbidden):
    StartDateTime: datetime

class DatetimeNoLimit(ExtraForbidden):
    StartDateTime: datetime
    DateTime: datetime

class OrganizationEnum(str, Enum):
    SOR = "SOR"
    SHAK = "SHAK"
    REGIONSKODE = "Regionskode"
    KOMMUNEKODE = "Kommunekode"
    YDERNUMMER = "Ydernummer"
    LOKATIONSNUMMER = "Lokationsnummer"
#
class Patient(ExtraForbidden):
    CivilRegistrationNumber: str = Field(..., regex='((((0[1-9]|1[0-9]|2[0-9]|3[0-1])(01|03|05|07|08|10|12))|((0[1-9]|1[0-9]|2[0-9]|30)(04|06|09|11))|((0[1-9]|1[0-9]|2[0-9])(02)))[0-9]{2}[0-9]([0-9]{2}|[a-zA-Z]{2})[0-9])')
    PatientId: Optional[str]

class Observations(ExtraForbidden):
    IntegerNoLimit: IntegerNoLimit
    IntegerLimit: IntegerLimit
    FloatNoLimit: FloatNoLimit
    FloatLimit: FloatLimit

class StringEnumLimit(ExtraForbidden):
    DiagnosisType: StringEnumLimit_DiagnosisType
    DiagnoseType: Optional[str]

class Diagnosis(ExtraForbidden):
    StringNoLimit: Optional[StringNoLimit]
    StringLengthLimit: Optional[StringLengthLimit]
    StringEnumLimit: Optional[StringEnumLimit]

class Activities(ExtraForbidden):
    DatetimeNoLimit: Optional[DatetimeNoLimit]
    DatetimeLimit: Optional[DatetimeLimit]

class Data(ExtraForbidden):
    Activities: Activities
    Diagnosis: Diagnosis
    Observations: Observations

class Organization(ExtraForbidden):
    Organization: OrganizationEnum
    identifier: str

class Report(ExtraForbidden):
    Patient: Patient
    Organization: Organization
    Data: Data

class InternalTest_v0_input_model(HiddenAttributes):
    Report: Report
    MetaData: MetaDataClass = Field({}, hidden=True)

visual hollow
#

Which version of Pydantic are you using?

neat oriole
#

havent upgraded to v2, latest v1

visual hollow
#

Okay so from these, what is the test that you'd like to do?

neat oriole
#

let´s start with

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

    #DiagnosisType = Require()

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis

    StringLengthLimit = Use(StringLengthLimitFactory.build)

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    #Activities = Ignore()  
    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    #Observations = Ignore() <<<<<<<<


class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = Use(ReportFactory.build)

why is this code, always generating the same values inside the observation object?
data_entry = StringFactory.build()

#

I always get
'Observations': {'IntegerNoLimit': {'ResultValue': 8991}, 'IntegerLimit': {'ResultValue': 44}, 'FloatNoLimit': {'ResultValue': -88.72773479887}, 'FloatLimit': {'ResultValue': 9.350684606632367}}}}

#

It is using the DataFactory but since Observations are not definied I would have assumed that it creates random Observation data

#

but if you run the test 2-3 times u always get the same data

visual hollow
#

For the StringFactory model is it okay if I use InternalTest_v0_input_model?

neat oriole
#
from ... import InternalTest_v0_input_model as INTERNTEST_v1_0_0, Report, Data, Diagnosis, StringLengthLimit
#

yes I am just renaming it

visual hollow
# neat oriole but if you run the test 2-3 times u always get the same data

Could you explain what you mean by running the tests multiple times? It should be giving different Observation instances. For example, the following assertion does not hold true:

one = StringFactory.build()
two = StringFactory.build()
three = StringFactory.build()


obs_one = one.Report.Data.Observations
obs_two = two.Report.Data.Observations
obs_three = three.Report.Data.Observations

assert obs_one == obs_two == obs_three
neat oriole
#

I am running pytest

from ... import StringFactory
import pytest

@pytest.mark.this
def test_1_1_str_lenght_succes():

    data_entry = StringFactory.build()
    data_dict = pydantic_to_json(data_entry)

    print(data_dict)

If i run pytest 3x its generating the same obj 3x

#

so 3 seperated executions

#

it´s updating timestamps but not the observation object, so I assuming it is using an internal seeding but that makes no sense for me

visual hollow
#

Let me check really quickly.

#

I'm not getting this behavior.

#

Can you show the output of the test, as I've shown?

neat oriole
#
===================================================== test session starts =====================================================
platform win32 -- Python 3.11.1, pytest-7.3.1, pluggy-1.0.0
rootdir: xxx
configfile: pytest.ini
plugins: anyio-3.6.2, Faker-18.10.1, asyncio-0.21.0, order-1.1.0, repeat-0.9.1
asyncio: mode=Mode.STRICT
collected 466 items / 465 deselected / 1 selected
test\internal_test\test_sections\1\test_1_1.py Everything okay, server is started. Uvicorn running on None:None
{'Report': {'Patient': {'CivilRegistrationNumber': '0502777gF0', 'PatientId': None}, 'Organization': {'Organization': 'Kommunekode', 'identifier': 'ZSgrLKWvRIpETSAEbmPv'}, 'Data': {'Activities': {'DatetimeNoLimit': {'StartDateTime': '1999-08-18T11:36:39', 
'DateTime': '2006-03-26T21:05:25'}, 'DatetimeLimit': None}, 'Diagnosis': {'StringNoLimit': None, 'StringLengthLimit': {'DiagnosisType': 'a8'}, 'StringEnumLimit': None}, 'Observations': {'IntegerNoLimit': {'ResultValue': 8991}, 'IntegerLimit': {'ResultValue': 44}, 'FloatNoLimit': {'ResultValue': -88.72773479887}, 'FloatLimit': {'ResultValue': 9.350684606632367}}}}, 'MetaData': {'Timestamp': '2011-01-15T22:15:02', 'RequestType': 'POST', 'ReportName': 'InternalTest', 'ReportVersion': '0'}}
.Preparing shutdown...
#

test 2:

===================================================== test session starts =====================================================
platform win32 -- Python 3.11.1, pytest-7.3.1, pluggy-1.0.0
rootdir: xxx
configfile: pytest.ini
plugins: anyio-3.6.2, Faker-18.10.1, asyncio-0.21.0, order-1.1.0, repeat-0.9.1
asyncio: mode=Mode.STRICT
collected 466 items / 465 deselected / 1 selected

test\internal_test\test_sections\1\test_1_1.py Everything okay, server is started. Uvicorn running on None:None
{'Report': {'Patient': {'CivilRegistrationNumber': '0502777gF0', 'PatientId': None}, 'Organization': {'Organization': 'Kommunekode', 'identifier': 'ZSgrLKWvRIpETSAEbmPv'}, 'Data': {'Activities': {'DatetimeNoLimit': {'StartDateTime': '1999-08-18T11:37:32', 
'DateTime': '2006-03-26T21:06:18'}, 'DatetimeLimit': None}, 'Diagnosis': {'StringNoLimit': None, 'StringLengthLimit': {'DiagnosisType': '97'}, 'StringEnumLimit': None}, 'Observations': {'IntegerNoLimit': {'ResultValue': 8991}, 'IntegerLimit': {'ResultValue': 44}, 'FloatNoLimit': {'ResultValue': -88.72773479887}, 'FloatLimit': {'ResultValue': 9.350684606632367}}}}, 'MetaData': {'Timestamp': '2011-01-15T22:15:55', 'RequestType': 'POST', 'ReportName': 'InternalTest', 'ReportVersion': '0'}}
.Preparing shutdown...
#

I am running
pytest -m this -s

#

The Diagnosis object is updated but not the Observation

visual hollow
#

Hm...this is very weird indeed.

#

And you haven't set the seed or configured faker or anything anywhere?

neat oriole
#

I used the faker in a different test

#

but its deselected

#

and else I wouldnt know where this seeding is coming from
this is 1:1 the code i have

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

    #DiagnosisType = Require()

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis

    StringLengthLimit = Use(StringLengthLimitFactory.build)

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    #Activities = Ignore()
    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    #Observations = Ignore()

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = Use(ReportFactory.build)
visual hollow
neat oriole
#

I removed the pytest fixture

visual hollow
#

Also, what version of polyfactory are you using?

neat oriole
#
@pytest.mark.this
def test_1_1_str_lenght_succes():
    data_entry = StringFactory.build()
    data_dict = pydantic_to_json(data_entry)

    print(data_dict)

No fixture anymore still same result

polyfactory 2.2.0

#

pydantic 1.10.7
pytest 7.3.1
pytest-asyncio 0.21.0
pytest-order 1.1.0
pytest-repeat 0.9.1

visual hollow
#

Can you try updating polyfactory to the latest version and see if this behavior is still there?

#

There were some seeding related issues that existed before which was fixed somewhere around 2.5, 2.6 or something.

neat oriole
#

Well okay that fixed that issue 🙂

{'Report': {'Patient': {'CivilRegistrationNumber': '0105745dj6', 'PatientId': 'ZSgrLKWvRIpETSAEbmPv'}, 'Organization': {'Organization': 'Kommunekode', 'identifier': 'iCKJtwAOBuzbcKZEuiAZ'}, 'Data': {'Activities': {'DatetimeNoLimit': None, 'DatetimeLimit': {'StartDateTime': '2019-12-17T03:54:22'}}, 'Diagnosis': {'StringNoLimit': None, 'StringLengthLimit': {'DiagnosisType': '1c0'}, 'StringEnumLimit': {'DiagnosisType': 'Udfald_3', 'DiagnoseType': None}}, 'Observations': {'IntegerNoLimit': {'ResultValue': 3268}, 'IntegerLimit': {'ResultValue': -84}, 'FloatNoLimit': {'ResultValue': 424101.551658122}, 'FloatLimit': {'ResultValue': -3.103338974198956}}}}, 'MetaData': {'Timestamp': '2005-06-10T08:29:27', 'RequestType': 
'POST', 'ReportName': 'InternalTest', 'ReportVersion': '0'}}
{'Report': {'Patient': {'CivilRegistrationNumber': '2202798OK5', 'PatientId': 'ZSgrLKWvRIpETSAEbmPv'}, 'Organization': {'Organization': 'Kommunekode', 'identifier': 'iCKJtwAOBuzbcKZEuiAZ'}, 'Data': {'Activities': {'DatetimeNoLimit': None, 'DatetimeLimit': {'StartDateTime': '2019-12-17T03:54:37'}}, 'Diagnosis': {'StringNoLimit': {'DiagnosisType': 'kVCvrSsVIFLtSXWAgOkX', 'DiagnoseType': None}, 'StringLengthLimit': {'DiagnosisType': '3f'}, 'StringEnumLimit': None}, 'Observations': {'IntegerNoLimit': {'ResultValue': 7882}, 'IntegerLimit': {'ResultValue': 81}, 'FloatNoLimit': {'ResultValue': -9.28125826989206}, 'FloatLimit': {'ResultValue': 7.259546198505198}}}}, 'MetaData': {'Timestamp': '2010-11-30T21:10:26', 'RequestType': 'POST', 'ReportName': 'InternalTest', 'ReportVersion': '0'}}
visual hollow
#

I should have asked for the version in the beginning itself 🤣

neat oriole
#

didnt consider that they were so many updates lately, but that was like the first issue

#

but just to recap. If dont specifie an object, it is creating random objects
like here

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    #Observations = Ignore()

& Ignore is always ignoring it? Because yesteday I posted about the None issue, where an ignored field still got the value of None but that might be also related to an outdated version?

visual hollow
#

If a field is specified with Ignore() then that value will not be passed to the constructor of the object itself. So for example, consider the following:


class Foo(BaseModel):
    foo: int
    bar: int
    baz: int

class FooFactory(ModelFactory[Foo]):
    __model__ = Foo

    foo = Ignore()

# FooFactory.build() essentially becomes like the following
foo = Foo(bar=2, baz=4)
#

Of course, this would result in an error since foo is a required value.

#

In the case of the None you'd posted, it was something like this:

class Foo(BaseModel):
    foo: int = 10
    bar: int
    baz: int

class FooFactory(ModelFactory[Foo]):
    __model__ = Foo

    foo = Ignore()

# FooFactory.build() essentially becomes like the following
foo_instance = Foo(bar=2, baz=4)

assert foo_instance.foo == 10 # `foo` in `foo_instance` has the value of 10 since that's the default value
neat oriole
#

My case had not a default value but Optional[str]

#

Here, this is it

class UserUpdateIn(BaseModel):
    email: Optional[EmailStr] = Field(None, description="user email")
    password: Optional[str] = Field(None, min_length=5, max_length=24, description="user password")
    contact_person_name: Optional[str]
def test_1_1_str_lenght_succes():

    class UserFactory(ModelFactory[UserUpdateIn]):
        __model__ = UserUpdateIn
        email = Ignore()
        password = "new_pw"
        contact_person_name =  Ignore()

    pytest_user = UserFactory.build()
    print(pytest_user)

{'email': None, 'password': 'new_pw', 'contact_person_name': None

#

So I would assume it is, if its a specific type on a class, Ignore() is setting it to None.
But if Ignore is set on a Custom child object, its just not generating the child node

#

Or no, maybe it is just because of the Optional

#

Yes okay, so the default behaviour is if its called Ignore on an Optional field then it is set to None

visual hollow
#

To be clear, it isn't polyfactory that is setting the value as None. That is being done by pydantiic.

neat oriole
#

Yes that is totally fair, its just something that I didnt consider & I didnt know about this default behaviour of pydantic.

2nd Question:

Do you need to define the "empty" factories:

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    Observations = Ignore()

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = Use(ReportFactory.build)

Could I remove the ReportFactory & yet define that the string factory needs to use the DataFactory

visual hollow
#

Yeah for this you only need to define the StringFactory. The factories for the subtypes will be created dynamically by polyfactory.

neat oriole
#

Yes I know that they are created dynamicly but I am defining a specific DataFactory with the ignores right?

#

So the question is, if i need to keep the link

visual hollow
#

Well, yes that's true, but you still wouldn't need to define it. It should still find that same factory and use that.

#

Actually, I'm not a 100% sure about that.

#

Let me check.

neat oriole
#

This is just a thought right.

I have testcase 1

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    Observations = Ignore()


class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0

Import StringFactory and it will still use the custom DataFactory.

visual hollow
#

Yeah it should find the same factory, provided that it was defined before the other factory.

neat oriole
#

Okay but only if its within the same file right?

#

not that they get definied global somewhere

#

so if i make a second file with only:

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0

then it would be totally random

visual hollow
visual hollow
# neat oriole not that they get definied global somewhere

If you've set the flag that I've mentioned, then it'll use that even if it was defined in another file. Essentially, when you define a factory and you've set the flag, polyfactory will map that type to that factory and reuse that the next time that same type is seen.

neat oriole
#

Yea & that might be a hard to track behaviour.
So using:

class DataFactory(ModelFactory[Data]):
    __model__ = Data
    __set_as_default_factory_for_type__ = True

    Contacts = Ignore()
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)
    Observations = Ignore()


class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0

Is working but could possible mess up other test when u are not aware of this default factory

visual hollow
#

Yes. Exactly.

neat oriole
#

So I need to define all child factories to have a implicit behaviour okay.
Last question was about setting a specific variable:

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

    DiagnosisType = Require()

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis

    StringLengthLimit = Use(StringLengthLimitFactory.build)

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    Diagnosis = Use(DiagnosisFactory.build)
    Observations = Ignore()

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = Use(ReportFactory.build)

Asssuming that StringLengthLimitFactory i want to test different values of DiagnosisType . I could make a choice with permanent seed but I dont like that.
How could i specificly tell the builder to set it to a value:

I tried many things, for example

data_entry = StringFactory.build(**{"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "1234567890"}}}}})

or

data_entry = StringFactory.build(DiagnosisType="1234567890")

but nothing is working

#

I am trying to give the StringFactory, the value for
StringFactory -> Report -> Data -> Diagnosis -> StringLengthLimit -> DiagnosisType

visual hollow
#

Hm...I'm not sure if this is a feature that exists.

#

It feels like it should though....

neat oriole
#

I havent seen an example online

#

neither google/stackoverflow or the docs

#

another attempt was trying to build the "last" factory first and move upwards but that seems aswell way to complex for a simple "set a value on a specific required field"

visual hollow
#

Okay nevermind. It does work.

#
from dataclasses import dataclass

from polyfactory.factories.dataclass_factory import DataclassFactory


@dataclass
class Nested:
    nested_val: int


@dataclass
class Foo:
    nested: Nested


@dataclass
class Bar:
    foo: Foo


class BarFactory(DataclassFactory[Bar]):
    __model__ = Bar


kwargs = {"foo": {"nested": {"nested_val": 1235}}}
bar = BarFactory.build(**kwargs)

assert bar.foo.nested.nested_val == 1235
neat oriole
#

where is my error?

    kwargs = {"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "1234567890"}}}}}
    data_entry = StringFactory.build(**kwargs)

raise MissingBuildKwargException(f"Require kwarg {field_meta.name} is missing")
E polyfactory.exceptions.MissingBuildKwargException: Require kwarg DiagnosisType is missing

#
class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit
    DiagnosisType = Require()

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis
    StringLengthLimit = Use(StringLengthLimitFactory.build)

class DataFactory(ModelFactory[Data]):
    __model__ = Data
    Courses = Ignore()
    Diagnosis = Use(DiagnosisFactory.build)


class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = Use(DataFactory.build)

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = Use(ReportFactory.build)
visual hollow
#

It should be:

kwargs = {"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "1234567890"}}}}}
data_entry = StringFactory.build(False, **kwargs)

The ModelFactory.build takes a positional argument unlike the other factories.

neat oriole
#
    kwargs = {"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "1234567890"}}}}}
    data_entry = StringFactory.build(False, **kwargs)

Still same error

visual hollow
#

Hm...seems like a bug. It works if you remove the Require.

neat oriole
#

xD

#

if it is indeed a bug, will u create an Issue on it?

visual hollow
#

Would you be willing to create one?

neat oriole
#

Sure you helped me a lot I will head over and create an Issue.
Thank you

neat oriole
fossil lagoonBOT
#

Description

I cant set the Required attribute with **kwargs in an pydantic model/factory.

MCVE

from pydantic import BaseModel, Field
from typing import Optional

from polyfactory.factories.pydantic_factory import ModelFactory
from polyfactory import Ignore, Require, Use

import pytest

class Son(BaseModel):
    Teddy: str 

class Daddy(BaseModel):
    Name: str
    Son: Optional[Son]

class GrandPa(BaseModel):
    Age: Optional[int]
    Kid: Daddy


class SonFactory(ModelFactory[Son]):
    __model__ = Son
    Teddy = Require()

class DaddyFactory(ModelFactory[Daddy]):
    __model__ = Daddy
    Son = Use(SonFactory.build)

class GrandPaFactory(ModelFactory[GrandPa]):
    __model__ = GrandPa
    Kid = Use(DaddyFactory.build)

def test_it():

    kwargs = {"Kid": {"Son": {"Teddy": "Wolf"}}}
    data_entry = GrandPaFactory.build(False, **kwargs)

Logs

raise MissingBuildKwargException(f"Require kwarg {field_meta.name} is missing")
E                       polyfactory.exceptions.MissingBuildKwargException: Require kwarg Teddy is missing

Release Version

polyfactory 2.9.0

Platform

  • ◻️ Linux
  • ◻️ Mac
  • ✅ Windows
  • ◻️ Other (Please specify in the description above)

Funding

  • If you would like to see an issue prioritized, make a pledge towards it!
  • We receive the pledge once the issue is completed & verified
Labels

bug

visual hollow
#

@neat oriole so this actually wasn't a bug. I didn't realize it yesterday. Sorry about that 😛

neat oriole
#

@visual hollow i rly don't understand the use () command and neither how to fix this

neat oriole
#

@visual hollow
kwargs = {"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "1234567890"}}}}}

This is working if i change the structur to :

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis
    StringLengthLimit = StringLengthLimitFactory

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    #Activities = Ignore()
    Diagnosis = DiagnosisFactory

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = DataFactory

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = ReportFactory

I used to have Use(<name>.build) in each sub factory but that is wrong?

Was the root object creating a data shema, then when it came to the first child it rerolled all attributed on all childs, then went to the next one, and rerolled all data on all child and below and so on?

visual hollow
#

The Use is a wrapper around a callable that is used to get the value for a specific field. It can be SomeFactory.build or any other callable. polyfactory does not know what the callable is, and nor should it. That is, it doesn't know that the callable is a build method of another factory and that the kwargs passed in should be passed to that callable.

When you specify Use, then that is directly used without going through the logic of figuring out how to create a value for that field based on its annotation.

neat oriole
#

@visual hollow For a non naritive english speaking person this is very hard to understand. I have seen this on the official website too but its just adding confusion. So .build is allrdy creating a factory, why re-build it? What other callable things are we talking about.
Is this syntax the intended syntax for my usecase?

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis
    StringLengthLimit = StringLengthLimitFactory

class DataFactory(ModelFactory[Data]):
    __model__ = Data

    #Activities = Ignore()
    Diagnosis = DiagnosisFactory

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = DataFactory

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = ReportFactory
visual hollow
#

This is not how you would do it. If you want to use a specific factory for a given type all the time, then you need to specify the __set_as_default_factory_for_type__ as True on that factory. If not, you don't have to specify a value at all. It will be dynamically generated.

You only need the following for it to create instances of INTERNTEST_v1_0_0:

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0

If you refer the docs, then you'll see that DataclassFactory.__random__.choice is a callable i.e. essentially it acts as a function that can be called. So to get the value for that field, polyfactory will call that function. You can refer this article for more details on callables.

In this tutorial, you'll learn what a callable is in Python and how to create callable instances using the .call() special method in your custom classes. You'll also code several examples of practical use cases for callable instances in Python.

neat oriole
#

@visual hollow Sorry but I am almost giving up. I rly makes no sense and it very frustrating for me.

  • Generating a whole model is good,
  • setting defaults is okay (if u dont need to reuse it)
  • but customizing some nested factories is a nightmare.

I am at the point where it is easier to just generate a whole dict and del / update any given keys then to try to customize sub factories

neat oriole
#

It cant be the only one who is struggling and maybe the whole fine tuning sub factories & declaration needs a PR

summer smelt
#

I looked into subfactories based on comments on issue. Turns out polyfactory supports using Factories as fields already which may be preferable to setting a global default edit: misunderstood context of last message

from dataclasses import dataclass

from polyfactory.factories import DataclassFactory


@dataclass
class B:
    attr: str


@dataclass
class A:
    b: B


class BFactory(DataclassFactory[B]):
    __model__ = B


class AFactory(DataclassFactory[A]):
    __model__ = A

    b = BFactory


print(AFactory.build(**{"b": {"attr": "passed value"}}).b.attr)  # "passed value"


class AFactoryWithOverrides(DataclassFactory[A]):
    __model__ = A

    b = DataclassFactory.create_factory(B, attr="value")


print(AFactoryWithOverrides.build().b.attr)  # "value"
#

In this example, I think : Ignore should be changed to = Ignore(). The former is a type annotation and the latter is an assignment

neat oriole
summer smelt
#

Ah sorry misread some of the intermidate messages here

neat oriole
#

One thing I thought of would be a local overwrite attribute where you can skip to the relevant sub factory and just define that.

summer smelt
#
from typing import Any

from polyfactory.factories import DataclassFactory as _DataclassFactory
from polyfactory.factories.base import T


class Subfactory:
    def __init__(self, model: type, **kwargs) -> None:
        self.model = model
        self.kwargs: dict[str, Any] = kwargs


class DataclassFactory(_DataclassFactory[T]):
    __is_base_factory__ = True

    @classmethod
    def _handle_factory_field(cls, field_value: Any, field_build_parameters: Any | None = None) -> Any:
        if isinstance(field_value, Subfactory):
            return (
                cls._get_or_create_factory(field_value.model)
                .create_factory(field_value.model, **field_value.kwargs)
                .build(**(field_build_parameters if field_build_parameters else {}))
            )

        return super()._handle_factory_field(field_value, field_build_parameters)


@dataclass
class B:
    attr: str


@dataclass
class A:
    b: B


class AFactory(DataclassFactory[A]):
    __model__ = A

    b = Subfactory(B, attr="value")


print(AFactory.build(**{"b": {"attr": "passed value"}}).b.attr)  # "passed value"
print(AFactory.build().b.attr)  # "value"
#

Something like that would work without changing lib itself but still would require user passing in type on required submodels (and some issues with regenerating factories). Having model type being omittable would be a nicer API but think would require lib changes

summer smelt
#

@visual hollow any thoughts on adding support for this? Also as a separate thing using factories as attributes could be documented

neat oriole
#

@visual hollow is this the way to go?

class StringLengthLimitFactory(ModelFactory[StringLengthLimit]):
    __model__ = StringLengthLimit

    DiagnosisType = Require()

class DiagnosisFactory(ModelFactory[Diagnosis]):
    __model__ = Diagnosis

    #StringNoLimit
    #StringEnumLimit
    StringLengthLimit = StringLengthLimitFactory

class DataFactory(ModelFactory[Data]):
    __model__ = Data
    Diagnosis = DiagnosisFactory

class ReportFactory(ModelFactory[Report]):
    __model__ = Report
    Data = DataFactory

class StringFactory(ModelFactory[INTERNTEST_v1_0_0]):
    __model__ = INTERNTEST_v1_0_0
    Report = ReportFactory
    test = {"Report": {"Data": {"Diagnosis": {"StringLengthLimit": {"DiagnosisType": "OtteTegn"}}}}}
    test_data = StringFactory.build(False, **test)

It is working, because if i remove the test data dict, it is saying DiagnosisType is required

But I noticed that the factory will always create Nonefor any xxx: Optional[str] in the pydantic shema
is this the intended behaviour??

summer smelt
visual hollow
visual hollow
neat oriole