Skip to content

Commit

Permalink
fix: random seed configuration (litestar-org#321)
Browse files Browse the repository at this point in the history
Co-authored-by: Jacob Coffee <jacob@z7x.org>
  • Loading branch information
guacs and JacobCoffee committed Aug 2, 2023
1 parent 5543e66 commit 61f1e2e
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 11 deletions.
5 changes: 3 additions & 2 deletions docs/examples/configuration/test_example_1.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from dataclasses import dataclass

from polyfactory import Use
from polyfactory.factories import DataclassFactory


Expand All @@ -16,7 +15,9 @@ class PersonFactory(DataclassFactory[Person]):
__model__ = Person
__random_seed__ = 1

name = Use(DataclassFactory.__random__.choice, ["John", "Alice", "George"])
@classmethod
def name(cls) -> str:
return cls.__random__.choice(["John", "Alice", "George"])


def test_random_seed() -> None:
Expand Down
7 changes: 4 additions & 3 deletions docs/examples/configuration/test_example_2.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from dataclasses import dataclass
from random import Random

from polyfactory import Use
from polyfactory.factories import DataclassFactory


Expand All @@ -17,11 +16,13 @@ class PersonFactory(DataclassFactory[Person]):
__model__ = Person
__random__ = Random(10)

name = Use(DataclassFactory.__random__.choice, ["John", "Alice", "George"])
@classmethod
def name(cls) -> str:
return cls.__random__.choice(["John", "Alice", "George"])


def test_setting_random() -> None:
# the outcome of 'factory.__random__.choice' is deterministic, because Random is configured with a set value.
assert PersonFactory.build().name == "Alice"
assert PersonFactory.build().name == "George"
assert PersonFactory.build().name == "John"
assert PersonFactory.build().name == "Alice"
2 changes: 1 addition & 1 deletion docs/examples/configuration/test_example_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ def name(cls) -> str:

def test_setting_faker() -> None:
# the outcome of faker deterministic because we seeded random, and it uses a spanish locale.
assert PersonFactory.build().name == "Lucas Barroso Jara"
assert PersonFactory.build().name == "Jessica Lane"
13 changes: 8 additions & 5 deletions polyfactory/factories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from os.path import realpath
from pathlib import Path
from random import Random
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -79,8 +80,6 @@
)

if TYPE_CHECKING:
from random import Random

from typing_extensions import TypeGuard

from polyfactory.field_meta import Constraints, FieldMeta
Expand Down Expand Up @@ -255,7 +254,8 @@ def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
else:
BaseFactory._base_factories.append(cls)

if random_seed := getattr(cls, "__random_seed__", None) is not None:
random_seed = getattr(cls, "__random_seed__", None)
if random_seed is not None:
cls.seed_random(random_seed)

if cls.__set_as_default_factory_for_type__:
Expand Down Expand Up @@ -401,8 +401,11 @@ def seed_random(cls, seed: int) -> None:
:returns: 'None'
"""
cls.__random__.seed(seed, version=3)
cls.__faker__.seed_instance(seed)
cls.__random__ = Random(seed)

faker = Faker()
faker.seed_instance(seed)
cls.__faker__ = faker

@classmethod
def is_ignored_type(cls, value: Any) -> bool:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ include = ["polyfactory", "tests", "examples"]
omit = ["*/tests/*"]

[tool.pytest.ini_options]
addopts = "tests docs/examples"
asyncio_mode = "auto"
filterwarnings = [
"ignore:.*pkg_resources.declare_namespace\\('sphinxcontrib'\\).*:DeprecationWarning",
Expand Down
92 changes: 92 additions & 0 deletions tests/test_random_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from dataclasses import dataclass
from random import Random
from typing import cast

import pytest

from polyfactory.factories.dataclass_factory import DataclassFactory

RANDINT_MAP = {i: Random(i).randint(0, 10) for i in range(3)}


@pytest.mark.parametrize("seed", RANDINT_MAP.keys())
def test_setting_random(seed: int) -> None:
@dataclass
class Foo:
foo: int

class FooFactory(DataclassFactory[Foo]):
__model__ = Foo
__random__ = Random(seed)

@classmethod
def foo(cls) -> int:
return cls.__random__.randint(0, 10)

assert FooFactory.build().foo == RANDINT_MAP[seed]


@pytest.mark.parametrize("seed", RANDINT_MAP.keys())
def test_setting_random_seed_on_random(seed: int) -> None:
@dataclass
class Foo:
foo: int

class FooFactory(DataclassFactory[Foo]):
__model__ = Foo
__random_seed__ = seed

@classmethod
def foo(cls) -> int:
return cls.__random__.randint(0, 10)

assert FooFactory.build().foo == RANDINT_MAP[seed]


@pytest.mark.parametrize("seed", RANDINT_MAP.keys())
def test_setting_random_seed_on_faker(seed: int) -> None:
@dataclass
class Foo:
foo: int

class FooFactory(DataclassFactory[Foo]):
__model__ = Foo
__random_seed__ = seed

@classmethod
def foo(cls) -> int:
return cast(int, cls.__faker__.random_digit())

assert FooFactory.build().foo == RANDINT_MAP[seed]


def test_setting_random_seed_on_multiple_factories() -> None:
foo_seed = 0
bar_seed = 1

@dataclass
class Foo:
foo: int

@dataclass
class Bar:
bar: int

class FooFactory(DataclassFactory[Foo]):
__model__ = Foo
__random_seed__ = foo_seed

@classmethod
def foo(cls) -> int:
return cls.__random__.randint(0, 10)

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

@classmethod
def bar(cls) -> int:
return cls.__random__.randint(0, 10)

assert FooFactory.build().foo == RANDINT_MAP[foo_seed]
assert BarFactory.build().bar == RANDINT_MAP[bar_seed]

0 comments on commit 61f1e2e

Please sign in to comment.