From 4fcaf6f167532fa753da1e7f8750da550c6f72b5 Mon Sep 17 00:00:00 2001 From: Ritvik Nag Date: Sun, 24 Nov 2024 23:03:21 -0500 Subject: [PATCH] Update docs --- README.rst | 87 +++++++++++ .../serialization_options.rst | 143 ++++++++++++++++++ tests/unit/test_load.py | 2 +- 3 files changed, 231 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index edf89629..27f847db 100644 --- a/README.rst +++ b/README.rst @@ -1044,6 +1044,93 @@ Additionally, here is an example to demonstrate usage of both these approaches: assert out_dict == {'myStr': 'my string'} +Conditional Field Skipping +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dataclass Wizard now supports **conditional skipping** of fields during serialization using global settings, per-field annotations, or field wrappers. + +Quick Examples +############## + +1. **Globally Skip Fields Matching a Condition** + + Use the ``Meta.skip_if`` option to define a global rule for skipping fields: + + .. code-block:: python3 + + from dataclasses import dataclass + from dataclass_wizard import JSONWizard, IS_NOT + + + @dataclass + class Example(JSONWizard): + class _(JSONWizard.Meta): + skip_if = IS_NOT(True) # Skip fields if not `True`. + + my_bool: bool + my_str: 'str | None' + + + print(Example(my_bool=True, my_str=None).to_dict()) + # Output: {'myBool': True} + +2. **Skip Defaults Based on a Condition** + Use ``Meta.skip_defaults_if`` to skip fields with default values matching a condition. + + .. note:: + Using ``skip_defaults_if`` automatically enables ``skip_defaults=True``. + + .. code-block:: python3 + + from dataclasses import dataclass + + from dataclass_wizard import JSONWizard, IS + + + @dataclass + class Example(JSONWizard): + class _(JSONWizard.Meta): + key_transform_with_dump = 'NONE' + skip_defaults_if = IS(None) # Skip default `None` values. + + my_str: 'str | None' = None + my_bool: bool = False + + + print(Example(my_str=None).to_dict()) + # Output: {'my_bool': False} + +3. **Per-Field Conditional Skipping** + Use type annotations or ``skip_if_field`` for fine-grained control: + + .. code-block:: python3 + + from __future__ import annotations # can be removed in Python 3.10+ + + from dataclasses import dataclass + from typing import Annotated + + from dataclass_wizard import JSONWizard, SkipIfNone, skip_if_field, EQ + + + @dataclass + class Example(JSONWizard): + my_str: Annotated[str | None, SkipIfNone] # Skip if `None`. + other_str: str | None = skip_if_field(EQ(''), default=None) # Skip if empty. + + print(Example(my_str=None, other_str='').to_dict()) + # Output: {} + +Special Cases +############# + +- **SkipIfNone**: Alias for ``SkipIf(IS(None))``, skips fields with a value of ``None``. +- **Condition Helpers**: + + - ``IS``, ``IS_NOT``: Identity checks. + - ``EQ``, ``NE``, ``GT``, etc.: Comparison operators. + - Combine these for flexible serialization rules. + Field Properties ---------------- diff --git a/docs/common_use_cases/serialization_options.rst b/docs/common_use_cases/serialization_options.rst index 8e04b605..2bd58243 100644 --- a/docs/common_use_cases/serialization_options.rst +++ b/docs/common_use_cases/serialization_options.rst @@ -102,3 +102,146 @@ Additionally, here is an example to demonstrate usage of both these approaches: print(out_dict) assert out_dict == {'myStr': 'my string'} + +"Skip If" Functionality +~~~~~~~~~~~~~~~~~~~~~~~ + +The **Dataclass Wizard** now offers powerful, configurable options to **skip serializing fields** under certain conditions. This functionality is available both **globally** (via the `Meta` class) and **per-field** (using type annotations or `dataclasses.Field` wrappers). + +Overview +-------- + +You can: +- **Globally skip** fields that match a condition using ``Meta.skip_if`` or ``Meta.skip_defaults_if``. +- **Conditionally skip fields individually** using type annotations with ``SkipIf``, or the ``skip_if_field`` wrapper for ``dataclasses.Field``. + +1. Global Field Skipping +------------------------ + +1.1 Skip Any Field Matching a Condition +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use the ``skip_if`` option in your dataclass's ``Meta`` configuration to skip fields that meet a specific condition during serialization. + +.. code-block:: python + + from dataclasses import dataclass + from dataclass_wizard import JSONWizard, IS_NOT + + @dataclass + class Example(JSONWizard): + class _(JSONWizard.Meta): + skip_if = IS_NOT(True) # Skip if the field is not `True`. + + my_str: 'str | None' + my_bool: bool + other_bool: bool = False + + ex = Example(my_str=None, my_bool=True) + assert ex.to_dict() == {'my_bool': True} # Only `my_bool` is serialized. + +1.2 Skip Fields with Default Values Matching a Condition +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use the ``skip_defaults_if`` option to skip serializing **fields with default values** that match a condition. + +.. code-block:: python + + from __future__ import annotations # can be removed in Python 3.10+ + + from dataclasses import dataclass + from dataclass_wizard import JSONWizard, IS + + + @dataclass + class Example(JSONWizard): + class _(JSONWizard.Meta): + key_transform_with_dump = 'NONE' + skip_defaults_if = IS(None) # Skip fields with default value `None`. + + my_str: str | None + other_str: str | None = None + third_str: str | None = None + my_bool: bool = False + + + ex = Example(my_str=None, other_str='') + assert ex.to_dict() == { + 'my_str': None, # Not skipped because it was explicitly set. + 'other_str': '', # Explicitly set values are always serialized. + 'my_bool': False # Default values are serialized if not matching the condition. + } + +2. Per-Field Skipping +--------------------- + +For finer control, fields can be skipped **individually** using type annotations with ``SkipIf`` or by wrapping ``dataclasses.Field`` with ``skip_if_field``. + +2.1 Using Type Annotations +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can use ``SkipIf`` in conjunction with ``Annotated`` to conditionally skip individual fields during serialization. + +.. code-block:: python + + from dataclasses import dataclass + from typing import Annotated + from dataclass_wizard import JSONWizard, SkipIf, IS + + @dataclass + class Example(JSONWizard) + + my_str: Annotated['str | None', SkipIf(IS(True))] # Skip if `my_str is True`. + +2.2 Using ``skip_if_field`` Wrapper +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use ``skip_if_field`` to add conditions directly to ``dataclasses.Field``: + +.. code-block:: python + + from dataclasses import dataclass + from dataclass_wizard import JSONWizard, skip_if_field, EQ + + @dataclass + class Example(JSONWizard): + class _(JSONWizard.Meta): + pass + + third_str: 'str | None' = skip_if_field(EQ(''), default=None) # Skip if field is empty string. + +2.3 Combined Example +^^^^^^^^^^^^^^^^^^^^ + +Both approaches can be used together to achieve granular control: + +.. code-block:: python + + from dataclasses import dataclass + from typing import Annotated + from dataclass_wizard import JSONWizard, SkipIf, skip_if_field, IS, EQ + + @dataclass + class Example(JSONWizard): + class _(JSONWizard.Meta): + pass + + my_str: Annotated['str | None', SkipIf(IS(None))] # Skip if `my_str is None`. + third_str: 'str | None' = skip_if_field(EQ(''), default=None) # Skip if `third_str` is ''. + + ex = Example(my_str='test', third_str='') + assert ex.to_dict() == {'my_str': 'test'} + +Key Classes and Utilities +------------------------- + +- **``SkipIf``**: Adds skipping logic to a field via type annotations. +- **``skip_if_field``**: Wraps ``dataclasses.Field`` for inline skipping logic. +- **Condition Helpers**: + - ``IS``, ``IS_NOT``: Skip based on identity. + - ``EQ``, ``NE``, ``LT``, ``LE``, ``GT``, ``GE``: Skip based on comparison. + +Performance and Clarity +----------------------- + +This design ensures both **performance** and **self-documenting code**, while enabling complex serialization rules effortlessly. diff --git a/tests/unit/test_load.py b/tests/unit/test_load.py index 4c9f1b6f..a786209b 100644 --- a/tests/unit/test_load.py +++ b/tests/unit/test_load.py @@ -2346,7 +2346,7 @@ class _(JSONWizard.Meta): assert ex.to_dict() == {'my_str': None} -def test_skip_if_with_condition_in_annotation_and_skip_if_field(): +def test_per_field_skip_if(): """ Test per-field `skip_if` functionality, with the ``SkipIf`` condition in type annotation, and also specified in