Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-model-bakery for openSUSE:Factory checked in at 2025-04-11 19:27:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-model-bakery (Old) and /work/SRC/openSUSE:Factory/.python-model-bakery.new.1907 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-model-bakery" Fri Apr 11 19:27:23 2025 rev:7 rq:1268609 version:1.20.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-model-bakery/python-model-bakery.changes 2024-11-20 17:00:54.215124138 +0100 +++ /work/SRC/openSUSE:Factory/.python-model-bakery.new.1907/python-model-bakery.changes 2025-04-11 19:27:24.963120168 +0200 @@ -1,0 +2,17 @@ +Fri Apr 11 12:46:10 UTC 2025 - Dirk Müller <dmuel...@suse.com> + +- update to 1.20.4: + * Fix regression introduced in 1.20.3 that prevented using + `auto_now` and `auto_now_add` fields with seq or callable. + * Fix support of `auto_now` and `auto_now_add` fields in + combination with `_fill_optional` + * Isolate Recipe defaults to prevent modification via instances + * Fix setting GFK parameter by a callable + * Fix regression forbidding using Proxy models as GFK + * docs: Add missing doc on `_refresh_after_create` option + * Fix `Recipe.prepare` without `_quantity` (on one-to-one + relation) + * Remove deprecation warning of + `datetime.datetime.utcfromtimestamp`. + +------------------------------------------------------------------- Old: ---- model-bakery-1.20.0-gh.tar.gz New: ---- model-bakery-1.20.4-gh.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-model-bakery.spec ++++++ --- /var/tmp/diff_new_pack.o2nVmr/_old 2025-04-11 19:27:25.599146801 +0200 +++ /var/tmp/diff_new_pack.o2nVmr/_new 2025-04-11 19:27:25.603146969 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-model-bakery # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-model-bakery -Version: 1.20.0 +Version: 1.20.4 Release: 0 Summary: Smart object creation facility for Django License: Apache-2.0 ++++++ model-bakery-1.20.0-gh.tar.gz -> model-bakery-1.20.4-gh.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/.readthedocs.yaml new/model_bakery-1.20.4/.readthedocs.yaml --- old/model_bakery-1.20.0/.readthedocs.yaml 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/.readthedocs.yaml 2025-02-26 21:08:35.000000000 +0100 @@ -11,3 +11,6 @@ path: . extra_requirements: - docs + +sphinx: + configuration: docs/conf.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/CHANGELOG.md new/model_bakery-1.20.4/CHANGELOG.md --- old/model_bakery-1.20.0/CHANGELOG.md 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/CHANGELOG.md 2025-02-26 21:08:35.000000000 +0100 @@ -13,6 +13,34 @@ ### Removed +## [1.20.4](https://pypi.org/project/model-bakery/1.20.4/) + +### Changed +- Fix regression introduced in 1.20.3 that prevented using `auto_now` and `auto_now_add` fields with seq or callable. + +## [1.20.3](https://pypi.org/project/model-bakery/1.20.3/) + +### Changed +- Fix support of `auto_now` and `auto_now_add` fields in combination with `_fill_optional` +- Isolate Recipe defaults to prevent modification via instances + +## [1.20.2](https://pypi.org/project/model-bakery/1.20.2/) + +### Changed +- Fix setting GFK parameter by a callable +- Fix regression forbidding using Proxy models as GFK + +## [1.20.1](https://pypi.org/project/model-bakery/1.20.1/) + +### Added +- docs: Add missing doc on `_refresh_after_create` option + +### Changed +- Fix `Recipe.prepare` without `_quantity` (on one-to-one relation) + +### Removed +- Remove deprecation warning of `datetime.datetime.utcfromtimestamp`. + ## [1.20.0](https://pypi.org/project/model-bakery/1.20.0/) ### Added diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/docs/basic_usage.md new/model_bakery-1.20.4/docs/basic_usage.md --- old/model_bakery-1.20.0/docs/basic_usage.md 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/docs/basic_usage.md 2025-02-26 21:08:35.000000000 +0100 @@ -242,6 +242,24 @@ **Important**: the lib does not do any kind of file clean up, so it's up to you to delete the files created by it. +## Refreshing Instances After Creation + +By default, Model Bakery does not refresh the instance after it is created and saved. +If you want to refresh the instance after it is created, +you can pass the flag `_refresh_after_create=True` to either `baker.make` or `baker.make_recipe`. +This ensures that any changes made by the database or signal handlers are reflected in the instance. + +```python +from model_bakery import baker + +# default behavior +customer = baker.make('shop.Customer', birthday='1990-01-01', _refresh_after_create=False) +assert customer.birthday == '1990-01-01' + +customer = baker.make('shop.Customer', birthday='1990-01-01', _refresh_after_create=True) +assert customer.birthday == datetime.date(1990, 1, 1) +``` + ## Non persistent objects If you don't need a persisted object, Model Bakery can handle this for you as well with the **prepare** method: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/model_bakery/__about__.py new/model_bakery-1.20.4/model_bakery/__about__.py --- old/model_bakery-1.20.0/model_bakery/__about__.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/model_bakery/__about__.py 2025-02-26 21:08:35.000000000 +0100 @@ -1 +1 @@ -__version__ = "1.20.0" +__version__ = "1.20.4" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/model_bakery/baker.py new/model_bakery-1.20.4/model_bakery/baker.py --- old/model_bakery-1.20.0/model_bakery/baker.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/model_bakery/baker.py 2025-02-26 21:08:35.000000000 +0100 @@ -70,6 +70,10 @@ return quantity is not None and (not isinstance(quantity, int) or quantity < 1) +def _is_auto_datetime_field(field: Field) -> bool: + return getattr(field, "auto_now_add", False) or getattr(field, "auto_now", False) + + def seed(seed: Union[int, float, str, bytes, bytearray, None]) -> None: Baker.seed(seed) @@ -513,10 +517,7 @@ if isinstance(field, ForeignRelatedObjectsDescriptor): one_to_many_keys[k] = attrs.pop(k) - if hasattr(field, "field") and ( - getattr(field.field, "auto_now_add", False) - or getattr(field.field, "auto_now", False) - ): + if hasattr(field, "field") and _is_auto_datetime_field(field.field): auto_now_keys[k] = attrs[k] if BAKER_CONTENTTYPES and isinstance(field, GenericForeignKey): @@ -643,8 +644,13 @@ if not attrs: return + # use .update() to force update auto_now fields instance.__class__.objects.filter(pk=instance.pk).update(**attrs) + # to make the resulting instance has the specified values + for k, v in attrs.items(): + setattr(instance, k, v) + def _handle_one_to_many(self, instance: Model, attrs: Dict[str, Any]): for key, values in attrs.items(): manager = getattr(instance, key) @@ -699,6 +705,8 @@ ct_field_name = data["content_type_field"] oid_field_name = data["object_id_field"] value = data["value"] + if callable(value): + value = value() if is_iterator(value): value = next(value) if value is None: @@ -714,7 +722,9 @@ setattr( instance, ct_field_name, - contenttypes_models.ContentType.objects.get_for_model(value), + contenttypes_models.ContentType.objects.get_for_model( + value, for_concrete_model=False + ), ) setattr(instance, oid_field_name, value.pk) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/model_bakery/recipe.py new/model_bakery-1.20.4/model_bakery/recipe.py --- old/model_bakery-1.20.0/model_bakery/recipe.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/model_bakery/recipe.py 2025-02-26 21:08:35.000000000 +0100 @@ -1,4 +1,5 @@ import collections +import copy import itertools from typing import ( Any, @@ -79,6 +80,8 @@ mapping[k] = v.recipe.prepare(_using=_using, **recipe_attrs) elif isinstance(v, related): mapping[k] = v.make + elif isinstance(v, collections.abc.Container): + mapping[k] = copy.deepcopy(v) mapping.update(new_attrs) mapping.update(rel_fields_attrs) @@ -164,9 +167,11 @@ **attrs: Any, ) -> Union[M, List[M]]: defaults = { - "_quantity": _quantity, "_save_related": _save_related, } + if _quantity is not None: + defaults["_quantity"] = _quantity # type: ignore[assignment] + defaults.update(attrs) return baker.prepare( self._model, _using=_using, **self._mapping(_using, defaults) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/model_bakery/timezone.py new/model_bakery-1.20.4/model_bakery/timezone.py --- old/model_bakery-1.20.0/model_bakery/timezone.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/model_bakery/timezone.py 2025-02-26 21:08:35.000000000 +0100 @@ -8,6 +8,6 @@ def tz_aware(value: datetime) -> datetime: """Return an UTC-aware datetime in case of USE_TZ=True.""" if settings.USE_TZ: - value = value.replace(tzinfo=timezone.utc) + return value.replace(tzinfo=timezone.utc) - return value + return value.replace(tzinfo=None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/model_bakery/utils.py new/model_bakery-1.20.4/model_bakery/utils.py --- old/model_bakery-1.20.0/model_bakery/utils.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/model_bakery/utils.py 2025-02-26 21:08:35.000000000 +0100 @@ -86,7 +86,10 @@ start = (date - epoch_datetime).total_seconds() increment_by = increment_by.total_seconds() for n in itertools.count(increment_by, increment_by): - series_date = tz_aware(datetime.datetime.utcfromtimestamp(start + n)) + series_date = tz_aware( + datetime.datetime.fromtimestamp(start + n, tz=datetime.timezone.utc) + ) + if type(value) is datetime.time: yield series_date.time() elif type(value) is datetime.date: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/tests/test_baker.py new/model_bakery-1.20.4/tests/test_baker.py --- old/model_bakery-1.20.0/tests/test_baker.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/tests/test_baker.py 2025-02-26 21:08:35.000000000 +0100 @@ -19,7 +19,7 @@ ModelNotFound, ) from model_bakery.timezone import tz_aware -from tests.generic import models +from tests.generic import baker_recipes, models from tests.generic.forms import DummyGenericIPAddressFieldForm @@ -286,6 +286,12 @@ assert lonely_person.pk is None assert lonely_person.only_friend.pk + def test_recipe_prepare_model_with_one_to_one_and_save_related(self): + lonely_person = baker_recipes.lonely_person.prepare(_save_related=True) + + assert lonely_person.pk is None + assert lonely_person.only_friend.pk + @pytest.mark.django_db class TestBakerCreatesAssociatedModels(TestCase): @@ -629,6 +635,38 @@ assert isinstance(dummy, models.DummyGenericForeignKeyModel) assert isinstance(dummy.content_type, ContentType) + def test_create_model_with_contenttype_with_content_object(self): + """Test creating model with contenttype field and populating that field by function.""" + from django.contrib.contenttypes.models import ContentType + + def get_dummy_key(): + return baker.make("Person") + + dummy = baker.make( + models.DummyGenericForeignKeyModel, content_object=get_dummy_key + ) + assert isinstance(dummy, models.DummyGenericForeignKeyModel) + assert isinstance(dummy.content_type, ContentType) + assert isinstance(dummy.content_object, models.Person) + + def test_create_model_with_contenttype_field_and_proxy_model(self): + from django.contrib.contenttypes.models import ContentType + + class ProxyPerson(models.Person): + class Meta: + proxy = True + app_label = "generic" + + dummy = baker.make( + models.DummyGenericForeignKeyModel, + content_object=baker.make(ProxyPerson, name="John Doe"), + ) + dummy.refresh_from_db() + assert isinstance(dummy, models.DummyGenericForeignKeyModel) + assert isinstance(dummy.content_type, ContentType) + assert isinstance(dummy.content_object, ProxyPerson) + assert dummy.content_object.name == "John Doe" + @pytest.mark.skipif( not BAKER_CONTENTTYPES, reason="Django contenttypes framework is not installed" @@ -1119,8 +1157,30 @@ sent_date=now, ) - instance.refresh_from_db() + assert instance.created == now + assert instance.updated == now + assert instance.sent_date == now + # Should not update after refreshing from the db + instance.refresh_from_db() assert instance.created == now assert instance.updated == now assert instance.sent_date == now + + @pytest.mark.django_db + def test_make_with_auto_now_and_fill_optional(self): + instance = baker.make( + models.ModelWithAutoNowFields, + _fill_optional=True, + ) + created, updated, sent_date = ( + instance.created, + instance.updated, + instance.sent_date, + ) + + # Should not update after refreshing from the db + instance.refresh_from_db() + assert instance.created == created + assert instance.updated == updated + assert instance.sent_date == sent_date diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/model_bakery-1.20.0/tests/test_recipes.py new/model_bakery-1.20.4/tests/test_recipes.py --- old/model_bakery-1.20.0/tests/test_recipes.py 2024-10-10 10:08:17.000000000 +0200 +++ new/model_bakery-1.20.4/tests/test_recipes.py 2025-02-26 21:08:35.000000000 +0100 @@ -4,6 +4,7 @@ from random import choice # noqa from unittest.mock import patch +from django.db import connection from django.utils.timezone import now import pytest @@ -19,6 +20,7 @@ DummyBlankFieldsModel, DummyNumbersModel, LonelyPerson, + ModelWithAutoNowFields, Person, Profile, User, @@ -34,6 +36,7 @@ "blog": "https://joe.example.com", "days_since_last_login": 4, "birth_time": now(), + "data": {"one": 1}, } person_recipe = Recipe(Person, **recipe_attrs) user_recipe = Recipe(User) @@ -68,6 +71,8 @@ assert person.appointment == recipe_attrs["appointment"] assert person.blog == recipe_attrs["blog"] assert person.days_since_last_login == recipe_attrs["days_since_last_login"] + assert person.data is not recipe_attrs["data"] + assert person.data == recipe_attrs["data"] assert person.id is not None def test_flat_model_prepare_recipe_with_the_correct_attributes(self): @@ -80,6 +85,8 @@ assert person.appointment == recipe_attrs["appointment"] assert person.blog == recipe_attrs["blog"] assert person.days_since_last_login == recipe_attrs["days_since_last_login"] + assert person.data is not recipe_attrs["data"] + assert person.data == recipe_attrs["data"] assert person.id is None def test_accepts_callable(self): @@ -171,6 +178,34 @@ except AttributeError as e: pytest.fail(f"{e}") + def test_recipe_dict_attribute_isolation(self): + person1 = person_recipe.make() + person2 = person_recipe.make() + person2.data["two"] = 2 + person3 = person_recipe.make() + + # Mutation on instances must have no side effect on their recipe definition, + # or on other instances of the same recipe. + assert person1.data == {"one": 1} + assert person2.data == {"one": 1, "two": 2} + assert person3.data == {"one": 1} + + @pytest.mark.skipif( + connection.vendor != "postgresql", reason="PostgreSQL specific tests" + ) + def test_recipe_list_attribute_isolation(self): + pg_person_recipe = person_recipe.extend(acquaintances=[1, 2, 3]) + person1 = pg_person_recipe.make() + person2 = pg_person_recipe.make() + person2.acquaintances.append(4) + person3 = pg_person_recipe.make() + + # Mutation on instances must have no side effect on their recipe definition, + # or on other instances of the same recipe. + assert person1.acquaintances == [1, 2, 3] + assert person2.acquaintances == [1, 2, 3, 4] + assert person3.acquaintances == [1, 2, 3] + @pytest.mark.django_db class TestExecutingRecipes: @@ -635,3 +670,37 @@ DummyBlankFieldsModel, blank_text_field="not an iterator, so don't iterate!" ) assert r.make().blank_text_field == "not an iterator, so don't iterate!" + + +class TestAutoNowFields: + @pytest.mark.django_db + def test_make_with_auto_now_using_datetime_generator(self): + delta = timedelta(minutes=1) + + def gen(): + idx = 0 + while True: + idx += 1 + yield tz_aware(TEST_TIME) + idx * delta + + r = Recipe( + ModelWithAutoNowFields, + created=gen(), + ) + + assert r.make().created == tz_aware(TEST_TIME + 1 * delta) + assert r.make().created == tz_aware(TEST_TIME + 2 * delta) + + @pytest.mark.django_db + def test_make_with_auto_now_using_datetime_seq(self): + delta = timedelta(minutes=1) + r = Recipe( + ModelWithAutoNowFields, + created=seq( + tz_aware(TEST_TIME), + increment_by=delta, + ), + ) + + assert r.make().created == tz_aware(TEST_TIME + 1 * delta) + assert r.make().created == tz_aware(TEST_TIME + 2 * delta)