CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-syrupy

Pytest snapshot testing utility that enables developers to write tests asserting immutability of computed results.

Overall
score

80%

Overview
Eval results
Files

filters.mddocs/

Filters

Property inclusion and exclusion system for controlling serialization scope in snapshots. Filters allow you to include or exclude specific properties from snapshot serialization, useful for ignoring dynamic fields, private data, or focusing on specific aspects of complex objects.

Capabilities

Property-Based Filtering

Filter properties by their names, supporting both inclusion and exclusion patterns.

def props(*prop_names: str) -> PropertyFilter:
    """
    Create filter that includes only specified property names.
    
    Parameters:
    - *included: Property names to include in serialization
    
    Returns:
    PropertyFilter: Function that returns True for included properties
    """

Usage examples:

def test_include_specific_props(snapshot):
    from syrupy.filters import props
    
    user_data = {
        "id": 123,
        "name": "Alice",
        "email": "alice@example.com",
        "password_hash": "secret_hash_value",
        "created_at": "2023-12-01T10:00:00Z",
        "last_login": "2023-12-01T15:30:00Z",
        "internal_id": "internal_abc123"
    }
    
    # Only include public fields in snapshot
    assert user_data == snapshot(include=props("id", "name", "email"))

def test_exclude_sensitive_props(snapshot):
    from syrupy.filters import props
    
    config = {
        "app_name": "MyApp",
        "version": "1.0.0",
        "database_url": "postgresql://localhost/mydb",
        "secret_key": "super_secret_key_123",
        "api_token": "token_abc123def456",
        "debug": True
    }
    
    # Exclude sensitive configuration
    assert config == snapshot(exclude=props("secret_key", "api_token", "database_url"))

def test_nested_object_filtering(snapshot):
    from syrupy.filters import props
    
    response = {
        "user": {
            "id": 123,
            "name": "Alice", 
            "email": "alice@example.com",
            "private_notes": "Internal notes",
            "password_hash": "secret"
        },
        "session": {
            "id": "sess_123",
            "token": "secret_token",
            "expires": "2023-12-01T16:00:00Z"
        }
    }
    
    # Include only public user fields
    # Note: This filters at each level independently
    assert response == snapshot(include=props("user", "id", "name", "email", "session", "expires"))

Path-Based Filtering

Filter properties using full path strings delimited with dots, allowing precise control over nested structures.

def paths(*included: str) -> PropertyFilter:
    """
    Create filter using full path strings for precise property targeting.
    
    Parameters:
    - *included: Dot-delimited path strings to include
    
    Returns:
    PropertyFilter: Function that returns True for included paths
    """

Usage examples:

def test_precise_path_filtering(snapshot):
    from syrupy.filters import paths
    
    complex_data = {
        "user": {
            "profile": {
                "name": "Alice",
                "email": "alice@example.com",
                "private_info": "secret"
            },
            "settings": {
                "theme": "dark",
                "notifications": True,
                "private_key": "secret_key"
            }
        },
        "metadata": {
            "version": "1.0",
            "debug_info": "internal_data"
        }
    }
    
    # Include only specific nested paths
    assert complex_data == snapshot(include=paths(
        "user.profile.name",
        "user.profile.email", 
        "user.settings.theme",
        "metadata.version"
    ))

def test_exclude_specific_paths(snapshot):
    from syrupy.filters import paths
    
    api_response = {
        "data": {
            "users": [
                {"id": 1, "name": "Alice", "internal_id": "int_123"},
                {"id": 2, "name": "Bob", "internal_id": "int_456"}
            ]
        },
        "meta": {
            "total_count": 2,
            "request_id": "req_789",
            "debug_trace": "trace_data"
        }
    }
    
    # Exclude internal/debug paths
    assert api_response == snapshot(exclude=paths(
        "data.users.*.internal_id",  # Exclude internal_id from all users
        "meta.request_id",
        "meta.debug_trace"
    ))

Nested Path Inclusion

Advanced filtering that automatically includes parent paths when targeting nested properties.

def paths_include(*path_parts: Union[Tuple[str, ...], List[str]]) -> PropertyFilter:
    """
    Create include filter with automatic parent path inclusion.
    
    When including a nested path, automatically includes all parent paths
    necessary to reach the target property.
    
    Parameters:
    - *nested_paths: Dot-delimited paths to include with parents
    
    Returns:
    PropertyFilter: Function that includes paths and their parents
    """

Usage examples:

def test_nested_path_inclusion(snapshot):
    from syrupy.filters import paths_include
    
    deep_structure = {
        "level1": {
            "level2": {
                "level3": {
                    "target_field": "important_data",
                    "other_field": "not_needed"
                },
                "sibling": "also_not_needed"
            },
            "other_branch": "ignore_this"
        },
        "root_field": "ignore_this_too"
    }
    
    # Include only level1.level2.level3.target_field and necessary parents
    # This automatically includes: level1, level1.level2, level1.level2.level3
    assert deep_structure == snapshot(include=paths_include(
        "level1.level2.level3.target_field"
    ))

def test_multiple_nested_inclusions(snapshot):
    from syrupy.filters import paths_include
    
    config = {
        "database": {
            "host": "localhost",
            "port": 5432,
            "credentials": {
                "username": "admin",
                "password": "secret"
            }
        },
        "cache": {
            "redis": {
                "host": "cache-host",
                "port": 6379
            }
        },
        "logging": {
            "level": "INFO",
            "file": "/var/log/app.log"
        }
    }
    
    # Include multiple nested paths - parents automatically included
    assert config == snapshot(include=paths_include(
        "database.host",
        "database.port", 
        "cache.redis.host",
        "logging.level"
    ))

Combined Filter Usage

Using filters together with matchers and extensions for comprehensive snapshot control.

# Filters can be combined with matchers and extensions
PropertyFilter = Callable[[PropertyName, PropertyPath], bool]

Usage examples:

def test_filter_with_matcher(snapshot):
    from syrupy.filters import props
    from syrupy.matchers import path_type
    
    user_activity = {
        "user_id": 12345,
        "username": "alice",
        "last_login": "2023-12-01T10:30:00Z",
        "login_count": 42,
        "internal_tracking_id": "track_abc123",
        "debug_info": {"trace": "internal_data"}
    }
    
    # Exclude internal fields AND replace dynamic values
    assert user_activity == snapshot(
        exclude=props("internal_tracking_id", "debug_info"),
        matcher=path_type({
            "user_id": (int, "<user_id>"),
            "last_login": (str, "<timestamp>")
        })
    )

def test_filter_with_extension(snapshot):
    from syrupy.filters import paths
    from syrupy.extensions.json import JSONSnapshotExtension
    
    api_data = {
        "public_api": {
            "users": [{"id": 1, "name": "Alice"}],
            "total": 1
        },
        "internal_meta": {
            "query_time": 0.05,
            "cache_hit": True,
            "debug_trace": "internal"
        }
    }
    
    # Include only public API data and save as clean JSON
    assert api_data == snapshot(
        include=paths("public_api.users", "public_api.total")
    ).use_extension(JSONSnapshotExtension)

Custom Filter Development

Create custom filter functions for specialized filtering needs.

PropertyFilter = Callable[[PropertyName, PropertyPath], bool]

# PropertyPath structure for custom filters
PropertyName = Hashable
PropertyValueType = Type[SerializableData] 
PropertyPathEntry = Tuple[PropertyName, PropertyValueType]
PropertyPath = Tuple[PropertyPathEntry, ...]

Usage examples:

def test_custom_type_filter(snapshot):
    def exclude_private_types(prop_name, path):
        """Exclude properties containing sensitive types"""
        # Get the property value type from the path
        if path and len(path) > 0:
            prop_type = path[-1][1]  # Last entry's type
            
            # Exclude certain types
            if prop_type in (type(lambda: None), type):  # Functions and type objects
                return False
                
            # Exclude properties with "private" or "secret" in name
            if isinstance(prop_name, str):
                if "private" in prop_name.lower() or "secret" in prop_name.lower():
                    return False
        
        return True  # Include by default
    
    test_data = {
        "public_value": "visible",
        "private_key": "hidden",
        "secret_token": "hidden",
        "normal_field": "visible",
        "callback_func": lambda x: x,  # Function type - excluded
        "metadata": {"version": "1.0"}
    }
    
    assert test_data == snapshot(include=exclude_private_types)

def test_depth_based_filter(snapshot):
    def limit_depth(prop_name, path):
        """Limit serialization depth to prevent infinite recursion"""
        max_depth = 3
        return len(path) <= max_depth
    
    # Deeply nested structure
    deep_data = {
        "level1": {
            "level2": {
                "level3": {
                    "level4": "too deep - excluded",
                    "value": "included"
                },
                "value": "included"
            },
            "value": "included"
        }
    }
    
    assert deep_data == snapshot(include=limit_depth)

def test_conditional_filter(snapshot):
    def filter_by_value_type(prop_name, path):
        """Custom logic based on property name patterns"""
        prop_str = str(prop_name)
        
        # Include all numeric IDs
        if prop_str.endswith("_id") or prop_str == "id":
            return True
            
        # Include all timestamps
        if "time" in prop_str.lower() or "date" in prop_str.lower():
            return True
            
        # Include names and titles
        if prop_str in ["name", "title", "description"]:
            return True
            
        # Exclude everything else
        return False
    
    mixed_data = {
        "user_id": 123,
        "name": "Alice",
        "email": "alice@example.com",  # Excluded
        "created_time": "2023-12-01",
        "password": "secret",  # Excluded
        "description": "User account"
    }
    
    assert mixed_data == snapshot(include=filter_by_value_type)

Install with Tessl CLI

npx tessl i tessl/pypi-syrupy

docs

cli-integration.md

core-assertions.md

extensions.md

filters.md

index.md

matchers.md

tile.json