Skip to content

Commit e25a938

Browse files
committed
Ref #61 -- Introduce generators for each integer field
# Conflicts: # model_bakery/random_gen.py diff --git c/model_bakery/generators.py i/model_bakery/generators.py index 087355e..619736a 100644 --- c/model_bakery/generators.py +++ i/model_bakery/generators.py @@ -2,7 +2,6 @@ from collections.abc import Callable from decimal import Decimal from typing import Any -from django.db.backends.base.operations import BaseDatabaseOperations from django.db.models import ( AutoField, BigAutoField, @@ -82,29 +81,20 @@ except ImportError: IntegerRangeField = None -def _make_integer_gen_by_range(field_type: Any) -> Callable: - min_int, max_int = BaseDatabaseOperations.integer_field_ranges[field_type.__name__] - - def gen_integer(): - return random_gen.gen_integer(min_int=min_int, max_int=max_int) - - return gen_integer - - default_mapping = { ForeignKey: random_gen.gen_related, OneToOneField: random_gen.gen_related, ManyToManyField: random_gen.gen_m2m, BooleanField: random_gen.gen_boolean, - AutoField: _make_integer_gen_by_range(AutoField), - BigAutoField: _make_integer_gen_by_range(BigAutoField), - IntegerField: _make_integer_gen_by_range(IntegerField), - SmallAutoField: _make_integer_gen_by_range(SmallAutoField), - BigIntegerField: _make_integer_gen_by_range(BigIntegerField), - SmallIntegerField: _make_integer_gen_by_range(SmallIntegerField), - PositiveBigIntegerField: _make_integer_gen_by_range(PositiveBigIntegerField), - PositiveIntegerField: _make_integer_gen_by_range(PositiveIntegerField), - PositiveSmallIntegerField: _make_integer_gen_by_range(PositiveSmallIntegerField), + AutoField: random_gen.gen_auto_field, + BigAutoField: random_gen.gen_positive_big_integer, + IntegerField: random_gen.gen_regular_integer, + SmallAutoField: random_gen.gen_positive_small_integer, + BigIntegerField: random_gen.gen_big_integer, + SmallIntegerField: random_gen.gen_small_integer, + PositiveBigIntegerField: random_gen.gen_positive_big_integer, + PositiveIntegerField: random_gen.gen_positive_integer, + PositiveSmallIntegerField: random_gen.gen_positive_small_integer, FloatField: random_gen.gen_float, DecimalField: random_gen.gen_decimal, BinaryField: random_gen.gen_byte_string, diff --git c/model_bakery/random_gen.py i/model_bakery/random_gen.py index 278e604..59f9bdf 100644 --- c/model_bakery/random_gen.py +++ i/model_bakery/random_gen.py @@ -86,9 +86,87 @@ def gen_from_choices( def gen_integer(min_int: int = -MAX_INT, max_int: int = MAX_INT) -> int: + warnings.warn( + "gen_integer() may cause overflow errors with Django integer fields due to " + "large default MAX_INT value. Consider using field-specific generators instead:\n" + "- gen_positive_small_integer() for PositiveSmallIntegerField\n" + "- gen_small_integer() for SmallIntegerField\n" + "- gen_regular_integer() for IntegerField\n" + "- gen_positive_integer() for PositiveIntegerField\n" + "- gen_big_integer() for BigIntegerField\n" + "- gen_positive_big_integer() for PositiveBigIntegerField\n" + "See model_bakery.random_gen documentation for more details.", + DeprecationWarning, + stacklevel=2, + ) return baker_random.randint(min_int, max_int) +def _get_field_range(field_name: str): + """Get field range from Django's BaseDatabaseOperations.""" + from django.db.backends.base.operations import BaseDatabaseOperations + + return BaseDatabaseOperations.integer_field_ranges.get( + field_name, (-MAX_INT, MAX_INT) + ) + + +def gen_small_integer(min_int: int = None, max_int: int = None) -> int: + """Generate integer for SmallIntegerField.""" + field_min, field_max = _get_field_range("SmallIntegerField") + actual_min = min_int if min_int is not None else field_min + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + +def gen_positive_small_integer(min_int: int = None, max_int: int = None) -> int: + """Generate integer for PositiveSmallIntegerField.""" + field_min, field_max = _get_field_range("PositiveSmallIntegerField") + actual_min = min_int if min_int is not None else max(field_min, 1) + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + +def gen_positive_integer(min_int: int = None, max_int: int = None) -> int: + """Generate integer for PositiveIntegerField.""" + field_min, field_max = _get_field_range("PositiveIntegerField") + actual_min = min_int if min_int is not None else max(field_min, 1) + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + +def gen_big_integer(min_int: int = None, max_int: int = None) -> int: + """Generate integer for BigIntegerField.""" + field_min, field_max = _get_field_range("BigIntegerField") + actual_min = min_int if min_int is not None else field_min + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + +def gen_positive_big_integer(min_int: int = None, max_int: int = None) -> int: + """Generate integer for PositiveBigIntegerField.""" + field_min, field_max = _get_field_range("PositiveBigIntegerField") + actual_min = min_int if min_int is not None else max(field_min, 1) + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + +def gen_regular_integer(min_int: int = None, max_int: int = None) -> int: + """Generate integer for IntegerField.""" + field_min, field_max = _get_field_range("IntegerField") + actual_min = min_int if min_int is not None else field_min + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + +def gen_auto_field(min_int: int = None, max_int: int = None) -> int: + """Generate integer for AutoField.""" + field_min, field_max = _get_field_range("AutoField") + actual_min = min_int if min_int is not None else max(field_min, 1) + actual_max = max_int if max_int is not None else field_max + return baker_random.randint(actual_min, actual_max) + + def gen_float(min_float: float = -1000000.0, max_float: float = 1000000.0) -> float: """ Generate a random float uniformly distributed between `min_float` and `max_float`. diff --git c/tests/test_baker.py i/tests/test_baker.py index 71786bf..e1df809 100644 --- c/tests/test_baker.py +++ i/tests/test_baker.py @@ -1186,3 +1186,47 @@ class TestAutoNowFields: assert instance.created == created assert instance.updated == updated assert instance.sent_date == sent_date + + +class TestFieldSpecificIntegerGenerators: + @pytest.mark.django_db + def test_gen_positive_small_integer_works_safely(self): + obj = baker.make( + models.DummyPositiveIntModel, + positive_small_int_field=random_gen.gen_positive_small_integer(min_int=1), + ) + + assert 1 <= obj.positive_small_int_field <= 32767 + + def test_field_specific_generators_respect_constraints(self): + obj = baker.make( + models.DummyPositiveIntModel, + positive_small_int_field=random_gen.gen_positive_small_integer( + min_int=100, max_int=200 + ), + ) + + assert 100 <= obj.positive_small_int_field <= 200 + + def test_gen_integer_shows_deprecation_warning(self): + import warnings + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + + # This should trigger the deprecation warning + value = random_gen.gen_integer(min_int=1, max_int=100) + + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "gen_integer() may cause overflow errors" in str(w[0].message) + assert "gen_positive_small_integer()" in str(w[0].message) + + assert 1 <= value <= 100 + + def test_automatic_generation_still_works(self): + obj = baker.make(models.DummyPositiveIntModel) + + assert 1 <= obj.positive_small_int_field <= 32767 + assert isinstance(obj.positive_int_field, int) + assert isinstance(obj.positive_big_int_field, int)
1 parent 92c6a9b commit e25a938

File tree

3 files changed

+131
-19
lines changed

3 files changed

+131
-19
lines changed

model_bakery/generators.py

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from decimal import Decimal
33
from typing import Any
44

5-
from django.db.backends.base.operations import BaseDatabaseOperations
65
from django.db.models import (
76
AutoField,
87
BigAutoField,
@@ -82,29 +81,20 @@
8281
IntegerRangeField = None
8382

8483

85-
def _make_integer_gen_by_range(field_type: Any) -> Callable:
86-
min_int, max_int = BaseDatabaseOperations.integer_field_ranges[field_type.__name__]
87-
88-
def gen_integer():
89-
return random_gen.gen_integer(min_int=min_int, max_int=max_int)
90-
91-
return gen_integer
92-
93-
9484
default_mapping = {
9585
ForeignKey: random_gen.gen_related,
9686
OneToOneField: random_gen.gen_related,
9787
ManyToManyField: random_gen.gen_m2m,
9888
BooleanField: random_gen.gen_boolean,
99-
AutoField: _make_integer_gen_by_range(AutoField),
100-
BigAutoField: _make_integer_gen_by_range(BigAutoField),
101-
IntegerField: _make_integer_gen_by_range(IntegerField),
102-
SmallAutoField: _make_integer_gen_by_range(SmallAutoField),
103-
BigIntegerField: _make_integer_gen_by_range(BigIntegerField),
104-
SmallIntegerField: _make_integer_gen_by_range(SmallIntegerField),
105-
PositiveBigIntegerField: _make_integer_gen_by_range(PositiveBigIntegerField),
106-
PositiveIntegerField: _make_integer_gen_by_range(PositiveIntegerField),
107-
PositiveSmallIntegerField: _make_integer_gen_by_range(PositiveSmallIntegerField),
89+
AutoField: random_gen.gen_auto_field,
90+
BigAutoField: random_gen.gen_positive_big_integer,
91+
IntegerField: random_gen.gen_regular_integer,
92+
SmallAutoField: random_gen.gen_positive_small_integer,
93+
BigIntegerField: random_gen.gen_big_integer,
94+
SmallIntegerField: random_gen.gen_small_integer,
95+
PositiveBigIntegerField: random_gen.gen_positive_big_integer,
96+
PositiveIntegerField: random_gen.gen_positive_integer,
97+
PositiveSmallIntegerField: random_gen.gen_positive_small_integer,
10898
FloatField: random_gen.gen_float,
10999
DecimalField: random_gen.gen_decimal,
110100
BinaryField: random_gen.gen_byte_string,

model_bakery/random_gen.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,87 @@ def gen_from_choices(
8686

8787

8888
def gen_integer(min_int: int = -MAX_INT, max_int: int = MAX_INT) -> int:
89+
warnings.warn(
90+
"gen_integer() may cause overflow errors with Django integer fields due to "
91+
"large default MAX_INT value. Consider using field-specific generators instead:\n"
92+
"- gen_positive_small_integer() for PositiveSmallIntegerField\n"
93+
"- gen_small_integer() for SmallIntegerField\n"
94+
"- gen_regular_integer() for IntegerField\n"
95+
"- gen_positive_integer() for PositiveIntegerField\n"
96+
"- gen_big_integer() for BigIntegerField\n"
97+
"- gen_positive_big_integer() for PositiveBigIntegerField\n"
98+
"See model_bakery.random_gen documentation for more details.",
99+
DeprecationWarning,
100+
stacklevel=2,
101+
)
89102
return baker_random.randint(min_int, max_int)
90103

91104

105+
def _get_field_range(field_name: str):
106+
"""Get field range from Django's BaseDatabaseOperations."""
107+
from django.db.backends.base.operations import BaseDatabaseOperations
108+
109+
return BaseDatabaseOperations.integer_field_ranges.get(
110+
field_name, (-MAX_INT, MAX_INT)
111+
)
112+
113+
114+
def gen_small_integer(min_int: int = None, max_int: int = None) -> int:
115+
"""Generate integer for SmallIntegerField."""
116+
field_min, field_max = _get_field_range("SmallIntegerField")
117+
actual_min = min_int if min_int is not None else field_min
118+
actual_max = max_int if max_int is not None else field_max
119+
return baker_random.randint(actual_min, actual_max)
120+
121+
122+
def gen_positive_small_integer(min_int: int = None, max_int: int = None) -> int:
123+
"""Generate integer for PositiveSmallIntegerField."""
124+
field_min, field_max = _get_field_range("PositiveSmallIntegerField")
125+
actual_min = min_int if min_int is not None else max(field_min, 1)
126+
actual_max = max_int if max_int is not None else field_max
127+
return baker_random.randint(actual_min, actual_max)
128+
129+
130+
def gen_positive_integer(min_int: int = None, max_int: int = None) -> int:
131+
"""Generate integer for PositiveIntegerField."""
132+
field_min, field_max = _get_field_range("PositiveIntegerField")
133+
actual_min = min_int if min_int is not None else max(field_min, 1)
134+
actual_max = max_int if max_int is not None else field_max
135+
return baker_random.randint(actual_min, actual_max)
136+
137+
138+
def gen_big_integer(min_int: int = None, max_int: int = None) -> int:
139+
"""Generate integer for BigIntegerField."""
140+
field_min, field_max = _get_field_range("BigIntegerField")
141+
actual_min = min_int if min_int is not None else field_min
142+
actual_max = max_int if max_int is not None else field_max
143+
return baker_random.randint(actual_min, actual_max)
144+
145+
146+
def gen_positive_big_integer(min_int: int = None, max_int: int = None) -> int:
147+
"""Generate integer for PositiveBigIntegerField."""
148+
field_min, field_max = _get_field_range("PositiveBigIntegerField")
149+
actual_min = min_int if min_int is not None else max(field_min, 1)
150+
actual_max = max_int if max_int is not None else field_max
151+
return baker_random.randint(actual_min, actual_max)
152+
153+
154+
def gen_regular_integer(min_int: int = None, max_int: int = None) -> int:
155+
"""Generate integer for IntegerField."""
156+
field_min, field_max = _get_field_range("IntegerField")
157+
actual_min = min_int if min_int is not None else field_min
158+
actual_max = max_int if max_int is not None else field_max
159+
return baker_random.randint(actual_min, actual_max)
160+
161+
162+
def gen_auto_field(min_int: int = None, max_int: int = None) -> int:
163+
"""Generate integer for AutoField."""
164+
field_min, field_max = _get_field_range("AutoField")
165+
actual_min = min_int if min_int is not None else max(field_min, 1)
166+
actual_max = max_int if max_int is not None else field_max
167+
return baker_random.randint(actual_min, actual_max)
168+
169+
92170
def gen_float(min_float: float = -1000000.0, max_float: float = 1000000.0) -> float:
93171
"""
94172
Generate a random float uniformly distributed between `min_float` and `max_float`.

tests/test_baker.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,3 +1186,47 @@ def test_make_with_auto_now_and_fill_optional(self):
11861186
assert instance.created == created
11871187
assert instance.updated == updated
11881188
assert instance.sent_date == sent_date
1189+
1190+
1191+
class TestFieldSpecificIntegerGenerators:
1192+
@pytest.mark.django_db
1193+
def test_gen_positive_small_integer_works_safely(self):
1194+
obj = baker.make(
1195+
models.DummyPositiveIntModel,
1196+
positive_small_int_field=random_gen.gen_positive_small_integer(min_int=1),
1197+
)
1198+
1199+
assert 1 <= obj.positive_small_int_field <= 32767
1200+
1201+
def test_field_specific_generators_respect_constraints(self):
1202+
obj = baker.make(
1203+
models.DummyPositiveIntModel,
1204+
positive_small_int_field=random_gen.gen_positive_small_integer(
1205+
min_int=100, max_int=200
1206+
),
1207+
)
1208+
1209+
assert 100 <= obj.positive_small_int_field <= 200
1210+
1211+
def test_gen_integer_shows_deprecation_warning(self):
1212+
import warnings
1213+
1214+
with warnings.catch_warnings(record=True) as w:
1215+
warnings.simplefilter("always")
1216+
1217+
# This should trigger the deprecation warning
1218+
value = random_gen.gen_integer(min_int=1, max_int=100)
1219+
1220+
assert len(w) == 1
1221+
assert issubclass(w[0].category, DeprecationWarning)
1222+
assert "gen_integer() may cause overflow errors" in str(w[0].message)
1223+
assert "gen_positive_small_integer()" in str(w[0].message)
1224+
1225+
assert 1 <= value <= 100
1226+
1227+
def test_automatic_generation_still_works(self):
1228+
obj = baker.make(models.DummyPositiveIntModel)
1229+
1230+
assert 1 <= obj.positive_small_int_field <= 32767
1231+
assert isinstance(obj.positive_int_field, int)
1232+
assert isinstance(obj.positive_big_int_field, int)

0 commit comments

Comments
 (0)