The unreleased `Transformer` has a conflicting API with the overall design
See original GitHub issueDescription
For most declarations, I can override the default mechanism by passing an explicit value:
>>> UserFactory().email
"john.doe@example.org"
>>> UserFactory(email="jane.doe@example.org").email
"jane.doe@example.org"
If the factory uses a factory.Transformer
field, I can’t force a value:
>>> UserFactory().password
"pbkdf2_sha256$216000$Gj2b9f0Ln1ms$1dBlLEYrtGiwEA219JU831VAdscD2f9nFY37PrNfCDU="
>>> UserFactory(
... password="pbkdf2_sha256$216000$Gj2b9f0Ln1ms$1dBlLEYrtGiwEA219JU831VAdscD2f9nFY37PrNfCDU="
... ).password
"pbkdf2_sha256$216000$EUKlFdNViyB5$vcfPaK6H7pDiQAa9ZIbFoj5oj55tUyHKHDUfxI4GIeY="
As a user, it is quite confusing to have very different behaviours for fields; if I have a specific password hash (in this example), I need to have a way to set it.
Next steps
I’m not sure we can officially release the factory.Transformer
(and related code) without addressing this topic first; how can a user bypass the transform?
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:8 (5 by maintainers)
Top Results From Across the Web
Unreleased toy - Transformers Wiki
Unicron wanted a toy, but his bargaining posture was highly dubious. Two Unicron prototypes were created during G1, neither saw full release. Diaclone...
Read more >docs/advanced-development.md · dev · subugoe / Theology ...
Changes to third party libraries / unreleased dependencies. Some requirements could be only ... This API has some simple design flaws, it's not...
Read more >Plux: A Higher-level Plugin Mechanism Around Python's Entry Point ...
PluginManager : manages the run time lifecycle of a Plugin, which has three states: resolved: the entrypoint pointing to the PluginSpec was imported...
Read more >Q7A: Questions and Answers
What do you expect from audits of API suppliers by drug product manufacturers? With Q7A we now have a GMP guidance document that...
Read more >Mental conflict - Imaginations and scary | OpenSea
... permeated car despite having conditioning recycled tenth time ive arrested selling deepfried cigars encountered maize thought incredibly corny miniature ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Thanks for sharing the detailed analysis!
I agree that changing
factory.Transformer
to accept a pre- declaration would be an improvement, happy to give that a shot.However, I am against the post- declaration part: the
Transformer
is meant to transform the value before to instantiate the object. This declaration was specifically introduced to deal with #366, which was a blocker for #316. My concern with a post-generation would be to keep encouraging the (bad) practice of saving the generated object twice with theCREATE
strategy. If a post- declaration is allowed, an additional save is needed for the object in database to match the Python instance, going against #316. Unless a clear use case is presented, whereTransformer
would be a real gain, I’m convinced it’s better not to implement it.This syntax is very unclear to me. It’s also not documented, not tested and does not appear a single time in the library repo. I would rather consider it an implementation detail of the library, and not start advertising it or even commit to supporting it. A GitHub search (although difficult because the
=
sign is ignored) does not yield usage either.Raw
(orForce
) could be declared inner toTransformer
, so that users need to specifyTransformer.Raw
to wrap the value, clarifying its scope and preventing confusion forTrait
orMaybe
:What do you think?
I’d like to move this forward; here are a couple of additional considerations we should take into account 😉
Requirements
With the following code (mixing examples from @francoisfreitag and @n1ngu):
I believe we want the following options to be possible:
transform
(in @n1ngu’s example, can we keep track of the pre-upper()
name?).Solving these would also address the topic discussed in #963.
Current behaviour
With the
3.2.1
code, the user can rely onassert MyFactory(field=value).field == value
, except whe the factory performs some mangling throughFactory._meta.rename
.In that situation, achieving the goals of
Transformer
would require:The user would then use:
UserFactory(name_raw="value")
to force a specific pre-transformation value;UserFactory(name="value")
to force a specific final valueAlternative implementations
Other options, for instance suggested in #963, would be to declare the transformation as part of the
LazyAttribute
orSelfAttribute
:However, that pattern would require the addition of a
transform=
keyword argument to all declarations, which would be cumbersome and might conflict with named arguments expected by other declarations (e.g.SubFactory
).Declarations with similar issues
The following declarations exhibit a similar issue to the proposed
<dl> <dt>factory.Trait</dt> <dd>Once the class declaration of a factory using a `Trait` has been parsed by Python, the `Trait` is converted into a set of `Maybe`; callers cannot provide a new value for the trait afterwards, or "delete" the `Maybe` portion.</dd> <dt>factory.PostGeneration</dt> <dd>Those declarations have specific handling: if the caller provides a non-declaration value, it is provided to the function. If, instead, a factory post-generation declaration is provided, it overrides the declaration. </dd> <dt>factory>RelatedFactory</dt> <dd>Similar to `factory.PostGeneration`, a user can bypass a call to a related factory by providing a nake value to the factory: `UserFactory(main_group=SomeGroup)`. If, instead, they provide another `RelatedFactory` declaration, that value would override the existing `RelatedFactory` generation: `UserFactory(main_group=factory.RelatedFactory(LimitedGroupFactory))` </dd> </dl>factory.Transformer
:Possible options
Special declaration
As proposed in #888, and similarly to the implementation for
factory.PostGeneration
, we could special-case a declaration:Specific keyword argument
We could support an explicit keyword argument to force the raw value:
Discussion
The “special declaration” case requires adding a new kwarg, which is only useful for the
factory.Transformer
case.factory.Trait
— as theTrait
declaration has already been parsed at class declaration time.factory.PostGeneration
, as those declarations are supposed to alter the instance, not provide a field.factory.RelatedFactory
, where one could call eitherUserFactory(main_group=SomeGroup)
orUserFactory(main_group=factory.Force(SomeGroup))
to bypass the related factory — although the latter might be clearer.The “specific keyword” pattern is similar to the behaviour used by
@post_generation
andPostGenerationMethodCall
:UserFactory(main_group__=SomeGroup)
to bypass amain_group = factory.RelatedFactory(...)
declaration,;UserFactory(make_password__="hunter2")
.Additional considerations
What happens with the following factories?
Conclusion
I believe the best way forward is:
factory.Transformer
declaration to be able to accept either a pre- or post- declaration, asMaybe
andTrait
do;name = factory.Transformer(...)
withname__ = x