Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default_factory being run prior to object init #1491

Closed
xlotlu opened this issue May 8, 2020 · 11 comments · Fixed by #1504
Closed

default_factory being run prior to object init #1491

xlotlu opened this issue May 8, 2020 · 11 comments · Fixed by #1504
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@xlotlu
Copy link
Contributor

xlotlu commented May 8, 2020

Bug

             pydantic version: 1.5.1

default_factory gets run twice before any object instantiation. This is obvious for callables with side-effects:

>>> from pydantic import BaseModel, Field
>>> class Seq():
...     def __init__(self):
...         self.v = 0 
...     def __call__(self):
...         self.v += 1
...         return self.v
...   
>>> class MyModel(BaseModel):
...     id: int = Field(default_factory=Seq())
...   
>>> MyModel()
MyModel(id=3)
@xlotlu xlotlu added the bug V1 Bug related to Pydantic V1.X label May 8, 2020
PrettyWood added a commit to PrettyWood/pydantic that referenced this issue May 11, 2020
@samuelcolvin
Copy link
Member

I'm not sure I would call this a bug.

Since it'll be consistent, you should be able to just start with v = -2 or whatever - I know it looks odd but as long as you include a comment I think it should be fine.

I can't think of another safe workaround.

PrettyWood added a commit to PrettyWood/pydantic that referenced this issue May 26, 2020
- by calling it only once if possible (fix pydantic#1491)
- by not setting the default value in the schema (fix pydantic#1520)
@bharel
Copy link

bharel commented Jun 8, 2020

@samuelcolvin Unfortunately our team also encountered this bug. Starting with v = -2 is just not possible in some cases, for factories that cause side effects e.g. writing to files, loading env vars which don't exist as of yet...

@samuelcolvin
Copy link
Member

@bharel if you're writing to a file or messing with the environment in a factory function you're doing something very wrong.

@aviramha
Copy link
Contributor

aviramha commented Jun 8, 2020

What is the purpose of running default factory function on class creation?

@bharel
Copy link

bharel commented Jun 8, 2020

@samuelcolvin I'm sorry but I don't think this is wrong:

class RedisSettings(BaseSettings):
    host: str = Field(..., env='RedisHost')
    
class Settings(BaseSettings):
    redis: RedisSettings = Field(default_factory=RedisSettings)

All I'm using is pydantic, and it'll fail on import time if the env doesn't exist.
Which it doesn't while unittesting etc.

@samuelcolvin
Copy link
Member

What is the purpose of running default factory function on class creation?

See #1504, it's called to get the field type - I think this is fixed on #1504 by making type annotations required for fields with a factory.

But I haven't looked through #1504 completely I think it might require some more work.

@samuelcolvin I'm sorry but I don't think this is wrong:

class RedisSettings(BaseSettings):
    host: str = Field(..., env='RedisHost')
    
class Settings(BaseSettings):
    redis: RedisSettings = Field(default_factory=RedisSettings)

All I'm using is pydantic, and it'll fail on import time if the env doesn't exist.
Which it doesn't while unittesting etc.

This is an interesting use case, I'll think more about how best to work around the problem or solve it completely.

@aviramha
Copy link
Contributor

@samuelcolvin
Have you had the chance to think about it? If you have an idea but no time to implement it I'll be happy to send a PR.

@samuelcolvin
Copy link
Member

see #1504, is there anything you think is wrong with that? It seems a good fix to me.

@aviramha
Copy link
Contributor

@samuelcolvin I'm asking regarding the example @bharel posted. Would that PR fix that also? (i.e will not call the factory on class definition?)

@samuelcolvin
Copy link
Member

Why don't you try the PR and find out? Then you could report back here how it works or comment on the PR with suggestions.

@aviramha
Copy link
Contributor

@samuelcolvin sorry, I think I read in the PR that it makes it run only once (I assumed it meant instead of twice, but actually it meant only on initialization, not class deifintion).
I tested it now and works as expected!
I'll follow the PR.

samuelcolvin pushed a commit that referenced this issue Jun 27, 2020
* Avoid some side effects of default factory

- by calling it only once if possible (fix #1491)
- by not setting the default value in the schema (fix #1520)

* refactor: ensure type is set when using default_factory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants