Skip to content

Commit c2a001d

Browse files
authored
Reset Polyfactory's default random state on each test (#735)
Polyfactory keeps its own default `random.Random()` instance (`polyfactory.constants.DEFAULT_RANDOM`) separate from Faker's, so it wasn't being reseeded along with the other libraries pytest-randomly already supports. This adds Polyfactory to the same try/except reseed pattern used for factory_boy, Faker, Model Bakery, and NumPy, so factories using the default random generator get deterministic, per-test values under `--randomly-seed`. Factories that call `seed_random()` themselves are unaffected, same as before. Closes #720.
1 parent cc4762b commit c2a001d

6 files changed

Lines changed: 94 additions & 7 deletions

File tree

‎CHANGELOG.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Unreleased
77

88
* Support Python 3.15.
99

10+
* Reset `Polyfactory <https://polyfactory.litestar.dev/>`__\’s default random state at the start of every test, if it is installed.
11+
12+
Thanks to Rahul Kumar in `PR #735 <https://github.com/pytest-dev/pytest-randomly/issues/735>`__.
13+
1014
* Switch package build backend from setuptools to `uv_build <https://docs.astral.sh/uv/concepts/build-backend/>`__.
1115
This makes builds with uv about nine times faster, since uv runs the backend natively, without creating a build environment or spawning a Python process.
1216
Additionally, source distributions no longer include test files, which setuptools previously included incompletely, missing the files needed to actually run them.

‎README.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ All of these features are on by default but can be disabled with flags.
6464

6565
Only its `legacy random state <https://numpy.org/doc/stable/reference/random/legacy.html>`__ is affected.
6666

67+
* `Polyfactory <https://polyfactory.litestar.dev/>`__
68+
69+
Only the default random generator is affected.
70+
Factories that have called ``seed_random()`` with their own seed are unaffected.
71+
6772
* If additional random generators are used, they can be registered under the
6873
``pytest_randomly.random_seeder``
6974
`entry point <https://packaging.python.org/specifications/entry-points/>`_ and

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ test = [
5151
"faker",
5252
"model-bakery>=1.13",
5353
"numpy",
54+
"polyfactory",
5455
"pytest>=9",
5556
"pytest-xdist",
5657
]

‎src/pytest_randomly/__init__.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@
5454
except ImportError: # pragma: no cover
5555
have_numpy = False
5656

57+
# polyfactory
58+
try:
59+
from polyfactory.constants import DEFAULT_RANDOM as polyfactory_random
60+
61+
have_polyfactory = True
62+
except ImportError: # pragma: no cover
63+
# old versions
64+
try:
65+
from polyfactory.factories.base import BaseFactory
66+
67+
polyfactory_random = BaseFactory.__random__
68+
have_polyfactory = True
69+
except ImportError:
70+
have_polyfactory = False
71+
5772

5873
def make_seed() -> int:
5974
return random.Random().getrandbits(32)
@@ -157,6 +172,9 @@ def _reseed(config: Config, offset: int = 0) -> int:
157172
if have_numpy: # pragma: no branch
158173
np_random.seed(seed % 2**32)
159174

175+
if have_polyfactory: # pragma: no branch
176+
polyfactory_random.setstate(random_state)
177+
160178
if entrypoint_reseeds is None:
161179
eps = entry_points(group="pytest_randomly.random_seeder")
162180
entrypoint_reseeds = [e.load() for e in eps]

‎tests/test_pytest_randomly.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,50 @@ def test_one():
696696
out.assert_outcomes(passed=1)
697697

698698

699+
def test_polyfactory(ourtester):
700+
"""
701+
Check that Polyfactory's default random generator is reset between tests.
702+
703+
Uses a Union-typed field so the assertion depends on
704+
DEFAULT_RANDOM.choice() picking which type to generate. A plain ``int``
705+
field wouldn't do: Polyfactory generates those via its Faker instance,
706+
whose random generator pytest-randomly already reseeds separately, so
707+
such a test would pass even without resetting DEFAULT_RANDOM.
708+
"""
709+
ourtester.makepyfile(
710+
test_one="""
711+
from dataclasses import dataclass
712+
from typing import Union
713+
714+
from polyfactory.factories import DataclassFactory
715+
716+
717+
@dataclass
718+
class Foo:
719+
x: Union[int, str, float, bytes]
720+
721+
722+
class FooFactory(DataclassFactory[Foo]):
723+
pass
724+
725+
726+
def test_a():
727+
value = FooFactory.build().x
728+
assert isinstance(value, str)
729+
assert value == 'jKdWckZoDYuPmqHSsjBh'
730+
731+
732+
def test_b():
733+
value = FooFactory.build().x
734+
assert isinstance(value, int)
735+
assert value == 1908
736+
"""
737+
)
738+
739+
out = ourtester.runpytest("--randomly-seed=1")
740+
out.assert_outcomes(passed=2)
741+
742+
699743
def test_failing_import(testdir):
700744
"""Test with pytest raising CollectError or ImportError.
701745

‎uv.lock‎

Lines changed: 22 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)