Skip to content

Conversation

@amureki
Copy link
Member

@amureki amureki commented Jul 25, 2025

Describe the change
An attempt to address #61

Add field-specific integer generators that use Django's actual field ranges, and introduce dedicated generators for auto-incrementing primary keys.

  • Add gen_auto_field(), gen_big_auto_field(), gen_small_auto_field() (start from 1)
  • Add field-specific generators for all integer field types using Django's actual database limits
  • Deprecate gen_integer() to avoid overflow issues

PR Checklist

  • Change is covered with tests
  • CHANGELOG.md is updated if needed

@amureki amureki self-assigned this Jul 25, 2025
Copy link
Member

@berinhard berinhard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the idea of defining per-field generators methods. I'm only leaving the comment since this is on draft, but I think it's a good path to follow.

# 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)
@amureki amureki force-pushed the 61/int-specific-generators branch from 5d9d6ce to e25a938 Compare January 1, 2026 17:39
@amureki amureki force-pushed the 61/int-specific-generators branch from 3a0c7f9 to 251e52f Compare January 1, 2026 18:05
@amureki amureki marked this pull request as ready for review January 1, 2026 18:30
…ators

* origin/main:
  Add type hints to `seq()`'s `increment_by` argument (#560)
  Update seq import from model_bakery.recipe to model_bakery.utils in basic usage docs (#559)
@amureki amureki merged commit 20b3532 into main Jan 15, 2026
74 checks passed
@amureki amureki deleted the 61/int-specific-generators branch January 15, 2026 12:33
amureki added a commit that referenced this pull request Jan 15, 2026
…t-markers

* origin/main: (52 commits)
  Ref #61 -- Introduce generators for each integer field (#528)
  Add type hints to `seq()`'s `increment_by` argument (#560)
  Update seq import from model_bakery.recipe to model_bakery.utils in basic usage docs (#559)
  Remove mentions of the old project (#558)
  Bump 1.21.0
  Only select valid choices from model field choices (#556)
  Improve numeric generators and drop internal `gen_integer` usage (#557)
  Fix link to Django supported versions document
  Drop Python 3.9 support (reached end of file) (#548)
  Bump actions/checkout from 5 to 6 (#549)
  Bump actions/upload-artifact from 5 to 6 (#553)
  Bump actions/download-artifact from 6 to 7 (#554)
  Standardize licenses to Apache License 2.0 (#552)
  Configure generic foreign key relations respecting Django internals (#544)
  Bump actions/upload-artifact from 4 to 5 (#546)
  Bump actions/download-artifact from 5 to 6 (#545)
  Add Django 6.0 support (#540)
  Add Python 3.14 support (#539)
  Modernize superseded typing features
  Drop Python 3.8 support
  ...
amureki added a commit that referenced this pull request Jan 15, 2026
* origin/main: (210 commits)
  Fix `baker.prepare()` with `GenericForeignKey` to work without database access (#501)
  Ref #61 -- Introduce generators for each integer field (#528)
  Add type hints to `seq()`'s `increment_by` argument (#560)
  Update seq import from model_bakery.recipe to model_bakery.utils in basic usage docs (#559)
  Remove mentions of the old project (#558)
  Bump 1.21.0
  Only select valid choices from model field choices (#556)
  Improve numeric generators and drop internal `gen_integer` usage (#557)
  Fix link to Django supported versions document
  Drop Python 3.9 support (reached end of file) (#548)
  Bump actions/checkout from 5 to 6 (#549)
  Bump actions/upload-artifact from 5 to 6 (#553)
  Bump actions/download-artifact from 6 to 7 (#554)
  Standardize licenses to Apache License 2.0 (#552)
  Configure generic foreign key relations respecting Django internals (#544)
  Bump actions/upload-artifact from 4 to 5 (#546)
  Bump actions/download-artifact from 5 to 6 (#545)
  Add Django 6.0 support (#540)
  Add Python 3.14 support (#539)
  Modernize superseded typing features
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants