diff --git a/docs/changelog.rst b/docs/changelog.rst index 8985587a..76fafee7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,7 @@ ChangeLog ------------------ - Add support for Django 5.2 +- Export :data:`~factory.declarations.SKIP` sentinel to public API for excluding keys from :class:`~factory.DictFactory` 3.3.3 (2025-02-03) diff --git a/docs/reference.rst b/docs/reference.rst index 2122b9f7..d3410eb2 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -1553,6 +1553,22 @@ with the :class:`Dict` and :class:`List` attributes: The actual factory to use for generating the dict can be set as a keyword argument, if an exotic dictionary-like object (SortedDict, ...) is required. + To exclude a key from the generated dictionary, use :data:`factory.SKIP`: + + .. code-block:: python + + class ConfigFactory(factory.DictFactory): + host = "localhost" + port = 8080 + debug = factory.SKIP # This key will be omitted + + .. code-block:: pycon + + >>> ConfigFactory() + {'host': 'localhost', 'port': 8080} + >>> ConfigFactory(debug=True) # Override to include the key + {'host': 'localhost', 'port': 8080, 'debug': True} + .. class:: List(items[, list_factory=factory.ListFactory]) diff --git a/factory/__init__.py b/factory/__init__.py index 62042a2a..258d294d 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -12,6 +12,7 @@ use_strategy, ) from .declarations import ( + SKIP, ContainerAttribute, Dict, Iterator, diff --git a/factory/declarations.py b/factory/declarations.py index f835f0d2..01a4935d 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -157,7 +157,7 @@ def evaluate_pre(self, instance, step, overrides): step=step, overrides=overrides, ) - if bypass_transform: + if bypass_transform or value is SKIP: return value return self.transform(value) diff --git a/tests/test_skip.py b/tests/test_skip.py new file mode 100644 index 00000000..7a107088 --- /dev/null +++ b/tests/test_skip.py @@ -0,0 +1,89 @@ +# Copyright: See the LICENSE file. + +import unittest + +import factory +from factory import SKIP + + +class SkipSentinelTestCase(unittest.TestCase): + """Tests for the SKIP sentinel object.""" + + def test_skip_basic(self): + """Test basic SKIP exclusion from DictFactory.""" + + class MyDictFactory(factory.DictFactory): + name = "John" + email = SKIP + phone = "123-456" + + result = MyDictFactory() + self.assertEqual(result, {"name": "John", "phone": "123-456"}) + self.assertNotIn("email", result) + + def test_skip_override(self): + """Test SKIP can be overridden per-instance.""" + + class MyDictFactory(factory.DictFactory): + name = "John" + email = "default@example.com" + + result = MyDictFactory(email=SKIP) + self.assertEqual(result, {"name": "John"}) + self.assertNotIn("email", result) + + def test_skip_all(self): + """Test all fields can be SKIP.""" + + class MyDictFactory(factory.DictFactory): + field1 = SKIP + field2 = SKIP + + result = MyDictFactory() + self.assertEqual(result, {}) + + def test_skip_with_lazy_function(self): + """Test SKIP works with declarations.""" + + class MyDictFactory(factory.DictFactory): + name = factory.LazyFunction(lambda: "Generated") + skipped = SKIP + value = 42 + + result = MyDictFactory() + self.assertEqual(result["name"], "Generated") + self.assertEqual(result["value"], 42) + self.assertNotIn("skipped", result) + + def test_skip_singleton(self): + """Test SKIP is a singleton.""" + another_skip = factory.SKIP + self.assertIs(SKIP, another_skip) + + def test_skip_bool(self): + """Test SKIP is falsy.""" + self.assertFalse(SKIP) + self.assertFalse(bool(SKIP)) + + def test_skip_with_transformer(self): + """Test SKIP works with Transformer declarations.""" + + class MyDictFactory(factory.DictFactory): + timeout = factory.Transformer(30.0, transform=int) + + result = MyDictFactory() + self.assertEqual(result, {"timeout": 30}) + + result = MyDictFactory(timeout=SKIP) + self.assertEqual(result, {}) + self.assertNotIn("timeout", result) + + def test_skip_transformer_default(self): + """Test SKIP as Transformer default excludes without error.""" + + class MyDictFactory(factory.DictFactory): + timeout = factory.Transformer(SKIP, transform=int) + + result = MyDictFactory() + self.assertEqual(result, {}) + self.assertNotIn("timeout", result)