CodeSampleX

샘플

attrs 26.1.0: Programmatically generate classes, resolve string forward-reference type annotations, introspect fields, and enforce attribute-level immutability in attrs 26.1.0

검증된 샘플 — pypi attrs 26.1.0: Programmatically generate classes, resolve string forward-reference type annotations, introspect fields, and enforce…

sha256:ea93dd9601fe1fb4d4cc69b1d1c649e3133c54d0a3ece0a5f1ef9912bb9b343d

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
2
빌드한 서명 키
2
선언된 환경 python linux x64 python python pip

검증 실행 환경

환경 컨트랙트 단계 실행일
python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-17
python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-18

케이스

HOW
목표
Programmatically generate classes, resolve string forward-reference type annotations, introspect fields, and enforce attribute-level immutability in attrs 26.1.0
패키지
심벌
  • attrs.resolve_types
  • attrs.make_class
  • attrs.fields_dict
  • attrs.fields
  • attrs.has
  • attrs.validate
  • attrs.astuple
  • attrs.filters.exclude
  • attrs.setters.frozen
  • attrs.exceptions.FrozenAttributeError
  • attrs.exceptions.FrozenInstanceError
  • attrs.exceptions.NotAnAttrsClassError
환경
python
생성일
2026-08-17T13:04:07Z

컨트랙트

  1. attrs.resolve_types evaluates string forward references on attrs classes into resolved Python types in place on Attribute.type, while raising NotAnAttrsClassError on non-attrs classes
  2. assert attrs.make_class dynamically constructs slotted, frozen, and validated attrs classes with full field definition support
  3. assert attrs.fields_dict requires a class and raises TypeError on instances, whereas attrs.fields accepts both classes and instances
  4. assert attrs.has returns True for attrs classes and instances and False for standard Python objects
  5. assert on_setattr=setters.frozen raises FrozenAttributeError on field mutation while @frozen raises FrozenInstanceError
  6. assert attrs.validate manually re-evaluates all field validators against current instance state on demand
  7. assert attrs.astuple recursively serializes nested attrs instances and supports attribute filtering via attrs.filters.exclude
  8. assert initialization lifecycle sequence executes __attrs_pre_init__, converters, validators with converted values, and __attrs_post_init__ deterministically

파일

  • NOTES.md
  • csx.json
  • requirements.txt
  • src/__init__.py
  • src/models.py
  • test/__init__.py
  • test/contract.py

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# Notes on attrs 26.1.0 Behavior

## Search Results
Calling `search_known_solution` returned existing samples:
- `sha256:89a1110228934559fed8711f85b6f59e237474ccaa0a73797a4d02be4b9d4404` (evaluating validator disabling contexts and field transformer aliases)
- `sha256:a94157e4aed7876259ed8efef84afdec66ce469f4a0e2d632e81d3e9fe282e43` (evaluating equality comparisons and keyword-only inheritance)
- `sha256:04534868bcf83a1c9437d56575f5b38eea5b61ac3a126103d53fa34f2fbd7310` (evaluating authoring custom validators and converters)

This sample tests a distinct set of programmatic generation, type resolution, inspection, and lifecycle behaviors in `attrs 26.1.0`.

## Pinned Release
- Package: `attrs`
- Release: `26.1.0` (pinned in `requirements.txt`)

## Observed Behaviors
1. **Type Annotation Resolution (`attrs.resolve_types`)**:
   - Under deferred evaluation (e.g. `from __future__ import annotations`), field type hints initially remain raw strings.
   - `attrs.resolve_types(cls)` resolves string forward references in place on `Attribute.type` and returns the class.
   - Passing a non-attrs class raises `attrs.exceptions.NotAnAttrsClassError`.
   - Forward references unresolvable in the global/local namespace raise `NameError` unless supplied via `localns`.

2. **Programmatic Class Creation (`attrs.make_class`)**:
   - `attrs.make_class` dynamically produces attrs classes with full support for field definitions, validators, defaults, slots, and frozen immutability.

3. **Field Introspection Discrepancy (`attrs.fields` vs `attrs.fields_dict`)**:
   - `attrs.fields` accepts both class objects and instances directly.
   - `attrs.fields_dict` requires a class object and raises `TypeError: Passed object must be a class` when passed an instance.

4. **Class and Instance Introspection (`attrs.has`)**:
   - `attrs.has` returns `True` for both attrs classes and attrs instances, and `False` for standard Python classes or primitive instances.

5. **Field Immutability vs Class Immutability**:
   - `on_setattr=attrs.setters.frozen` locks individual fields on otherwise mutable classes, raising `attrs.exceptions.FrozenAttributeError` on mutation attempts.
   - Whole-class `@frozen` (or `@define(frozen=True)`) locks the entire instance and raises `attrs.exceptions.FrozenInstanceError`.

6. **On-Demand Validation (`attrs.validate`)**:
   - `attrs.validate(instance)` re-runs all field validators against the current instance state, catching invalid values that bypassed standard attribute setters.

7. **Tuple Serialization and Filtering (`attrs.astuple`)**:
   - `attrs.astuple` recursively transforms nested attrs instances into nested tuples while supporting attribute filtering through `attrs.filters.exclude`.

8. **Deterministic Lifecycle Ordering**:
   - Initialization executes deterministically in the following order: `__attrs_pre_init__` (with raw constructor arguments) -> default factories -> converters (transforming raw inputs) -> validators (receiving converted values) -> `__attrs_post_init__`.
csx.json
{"case":{"caseId":"case:sha256:80908c224518a738fc7350008feba23150a2c2088f52df4ffa3a903ba9737fc5","contract":["attrs.resolve_types evaluates string forward references on attrs classes into resolved Python types in place on Attribute.type, while raising NotAnAttrsClassError on non-attrs classes","assert attrs.make_class dynamically constructs slotted, frozen, and validated attrs classes with full field definition support","assert attrs.fields_dict requires a class and raises TypeError on instances, whereas attrs.fields accepts both classes and instances","assert attrs.has returns True for attrs classes and instances and False for standard Python objects","assert on_setattr=setters.frozen raises FrozenAttributeError on field mutation while @frozen raises FrozenInstanceError","assert attrs.validate manually re-evaluates all field validators against current instance state on demand","assert attrs.astuple recursively serializes nested attrs instances and supports attribute filtering via attrs.filters.exclude","assert initialization lifecycle sequence executes __attrs_pre_init__, converters, validators with converted values, and __attrs_post_init__ deterministically"],"goal":"Programmatically generate classes, resolve string forward-reference type annotations, introspect fields, and enforce attribute-level immutability in attrs 26.1.0","kind":"HOW","packages":["pkg:pypi/attrs@26.1.0"],"schemaVersion":1,"symbols":["attrs.resolve_types","attrs.make_class","attrs.fields_dict","attrs.fields","attrs.has","attrs.validate","attrs.astuple","attrs.filters.exclude","attrs.setters.frozen","attrs.exceptions.FrozenAttributeError","attrs.exceptions.FrozenInstanceError","attrs.exceptions.NotAnAttrsClassError"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/attrs@26.1.0"],"schemaVersion":1,"symbols":["attrs.resolve_types","attrs.make_class","attrs.fields_dict","attrs.fields","attrs.has","attrs.validate","attrs.astuple","attrs.filters.exclude","attrs.setters.frozen","attrs.exceptions.FrozenAttributeError","attrs.exceptions.FrozenInstanceError","attrs.exceptions.NotAnAttrsClassError"],"verifierAdapter":"python@1"}
requirements.txt
attrs==26.1.0
src/__init__.py
"""attrs behavioral contracts package."""
src/models.py
from __future__ import annotations

from typing import Any, List, Optional
import attrs
from attrs import define, field, frozen, setters, validators

LIFECYCLE_STEPS: List[str] = []


def record_converter(val: int) -> int:
    LIFECYCLE_STEPS.append(f"converter:{val}")
    return val * 2


def record_validator(inst: Any, attr: attrs.Attribute, val: int) -> None:
    LIFECYCLE_STEPS.append(f"validator:{val}")
    if val <= 0:
        raise ValueError(f"{attr.name} must be positive")


@define
class LifecycleTracker:
    """Demonstrates attrs initialization sequence and hook execution order."""

    value: int = field(
        converter=record_converter,
        validator=record_validator,
    )

    def __attrs_pre_init__(self, value: int) -> None:
        LIFECYCLE_STEPS.append(f"pre_init:{value}")

    def __attrs_post_init__(self) -> None:
        LIFECYCLE_STEPS.append(f"post_init:{self.value}")


@define
class GraphNode:
    """Demonstrates type annotation forward references resolved via attrs.resolve_types."""

    name: str
    parent: Optional[GraphNode] = None
    neighbors: List[GraphNode] = field(factory=list)


@define
class AuditRecord:
    """Demonstrates field-level immutability using on_setattr=setters.frozen."""

    record_id: str = field(on_setattr=setters.frozen)
    status: str = "pending"


@frozen
class ImmutableConfig:
    """Demonstrates whole-class immutability with @frozen."""

    key: str
    value: str


@define
class ValidatedRange:
    """Demonstrates on-demand manual validation with attrs.validate()."""

    lower: int = field(validator=validators.ge(0))
    upper: int = field(validator=validators.ge(0))


@define
class NestedCoordinate:
    """Demonstrates nested data extraction with attrs.astuple."""

    x: int
    y: int


@define
class LocationMarker:
    """Demonstrates nested composite models serialized via attrs.astuple."""

    label: str
    coords: NestedCoordinate
    internal_tag: str = "internal"
test/__init__.py
"""attrs behavioral contract tests."""
test/contract.py
import importlib.metadata
import sys
from pathlib import Path
from typing import List, Optional

# Add project root to sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import attrs
from attrs import exceptions, filters, validators
from src.models import (
    LIFECYCLE_STEPS,
    AuditRecord,
    GraphNode,
    ImmutableConfig,
    LifecycleTracker,
    LocationMarker,
    NestedCoordinate,
    ValidatedRange,
)


def test_resolve_types_forward_references():
    """Verify attrs.resolve_types evaluates string forward references on attrs classes."""
    # Before resolve_types, annotations remain strings under postponed evaluation
    fields_before = attrs.fields(GraphNode)
    assert fields_before.name.type == "str"
    assert fields_before.parent.type == "Optional[GraphNode]"
    assert fields_before.neighbors.type == "List[GraphNode]"

    # resolve_types returns the class and resolves Attribute.type in place
    ret = attrs.resolve_types(GraphNode)
    assert ret is GraphNode

    fields_after = attrs.fields(GraphNode)
    assert fields_after.name.type is str
    assert fields_after.parent.type == Optional[GraphNode]
    assert fields_after.neighbors.type == List[GraphNode]

    # Calling resolve_types on a non-attrs class raises NotAnAttrsClassError
    class OrdinaryClass:
        x: int

    try:
        attrs.resolve_types(OrdinaryClass)
        raise AssertionError("resolve_types on non-attrs class should raise NotAnAttrsClassError")
    except exceptions.NotAnAttrsClassError as exc:
        assert "is not an attrs-decorated class" in str(exc)

    # Calling resolve_types with unresolved forward references raises NameError without localns
    @attrs.define
    class CustomLookupNode:
        data: "UndefinedExternalType"

    try:
        attrs.resolve_types(CustomLookupNode)
        raise AssertionError("Undefined annotation without localns should raise NameError")
    except NameError as exc:
        assert "UndefinedExternalType" in str(exc)

    # Supplying localns allows resolving custom types explicitly
    class UndefinedExternalType:
        pass

    attrs.resolve_types(CustomLookupNode, localns={"UndefinedExternalType": UndefinedExternalType})
    assert attrs.fields(CustomLookupNode).data.type is UndefinedExternalType


def test_make_class_programmatic_generation():
    """Verify attrs.make_class dynamically constructs slotted, frozen, and validated classes."""
    DynamicPoint = attrs.make_class(
        "DynamicPoint",
        {
            "x": attrs.field(validator=validators.instance_of(int)),
            "y": attrs.field(validator=validators.instance_of(int), default=0),
        },
        frozen=True,
        slots=True,
    )

    pt = DynamicPoint(10, 20)
    assert pt.x == 10
    assert pt.y == 20
    assert attrs.has(DynamicPoint) is True
    assert attrs.has(pt) is True

    # Default value works as expected
    pt_default = DynamicPoint(5)
    assert pt_default.x == 5
    assert pt_default.y == 0

    # Validator runs on dynamic class constructor
    try:
        DynamicPoint("invalid", 10)  # type: ignore[arg-type]
        raise AssertionError("make_class with invalid type should fail validation")
    except TypeError as exc:
        assert "must be <class 'int'>" in str(exc)

    # Frozen behavior enforced on dynamic class
    try:
        pt.x = 99
        raise AssertionError("Modifying frozen dynamic class instance should raise FrozenInstanceError")
    except exceptions.FrozenInstanceError as exc:
        assert "can't set attribute" in str(exc)


def test_fields_dict_vs_fields_inspection():
    """Verify attrs.fields_dict requires a class while attrs.fields accepts instances."""
    node = GraphNode(name="root")

    # attrs.fields accepts both class and instance
    fields_from_cls = attrs.fields(GraphNode)
    fields_from_inst = attrs.fields(node)
    assert fields_from_cls == fields_from_inst
    assert [f.name for f in fields_from_cls] == ["name", "parent", "neighbors"]

    # attrs.fields_dict returns a name-to-Attribute mapping for classes
    field_map = attrs.fields_dict(GraphNode)
    assert list(field_map.keys()) == ["name", "parent", "neighbors"]
    assert field_map["name"] == fields_from_cls.name

    # attrs.fields_dict strictly rejects instances with TypeError
    try:
        attrs.fields_dict(node)  # type: ignore[arg-type]
        raise AssertionError("fields_dict(instance) should raise TypeError")
    except TypeError as exc:
        assert "Passed object must be a class" in str(exc)


def test_has_introspection():
    """Verify attrs.has distinguishes attrs classes and instances from standard objects."""
    class PlainPythonClass:
        pass

    plain_inst = PlainPythonClass()
    node = GraphNode(name="test")

    assert attrs.has(GraphNode) is True
    assert attrs.has(node) is True
    assert attrs.has(PlainPythonClass) is False
    assert attrs.has(plain_inst) is False
    assert attrs.has(int) is False
    assert attrs.has(123) is False


def test_frozen_field_vs_frozen_instance():
    """Verify on_setattr=setters.frozen raises FrozenAttributeError while @frozen raises FrozenInstanceError."""
    # Field-level frozen allows mutating other fields but blocks frozen field modification
    record = AuditRecord(record_id="REC-001", status="pending")
    assert record.record_id == "REC-001"
    assert record.status == "pending"

    record.status = "processed"
    assert record.status == "processed"

    try:
        record.record_id = "REC-002"
        raise AssertionError("Modifying field with on_setattr=setters.frozen should raise FrozenAttributeError")
    except exceptions.FrozenAttributeError as exc:
        assert "can't set attribute" in str(exc)

    # Class-level frozen blocks any attribute modification
    cfg = ImmutableConfig(key="timeout", value="30s")
    try:
        cfg.value = "60s"
        raise AssertionError("Modifying @frozen class attribute should raise FrozenInstanceError")
    except exceptions.FrozenInstanceError as exc:
        assert "can't set attribute" in str(exc)


def test_manual_validation():
    """Verify attrs.validate runs all field validators on demand to catch out-of-band mutations."""
    rng = ValidatedRange(lower=5, upper=10)
    assert rng.lower == 5
    assert rng.upper == 10

    # Mutate attribute out-of-band bypassing setter hooks
    object.__setattr__(rng, "lower", -10)
    assert rng.lower == -10

    # attrs.validate detects the invalid state
    try:
        attrs.validate(rng)
        raise AssertionError("validate() should fail when field invariant is violated")
    except ValueError as exc:
        assert "'lower' must be >= 0: -10" in str(exc)


def test_astuple_serialization_and_filtering():
    """Verify attrs.astuple recursively serializes nested attrs instances into tuples with filtering."""
    marker = LocationMarker(
        label="central",
        coords=NestedCoordinate(x=100, y=200),
        internal_tag="debug_meta",
    )

    # Full recursive extraction
    tup = attrs.astuple(marker)
    assert tup == ("central", (100, 200), "debug_meta")
    assert isinstance(tup[1], tuple)

    # Filtered extraction omitting internal_tag
    filtered_tup = attrs.astuple(
        marker,
        filter=filters.exclude(attrs.fields(LocationMarker).internal_tag),
    )
    assert filtered_tup == ("central", (100, 200))


def test_initialization_lifecycle_sequence():
    """Verify deterministic execution order of pre_init, converters, validators, and post_init."""
    LIFECYCLE_STEPS.clear()
    tracker = LifecycleTracker(value=7)

    assert tracker.value == 14
    # Execution order:
    # 1. __attrs_pre_init__ runs with raw constructor arguments
    # 2. converter runs with raw argument and transforms it (7 -> 14)
    # 3. validator runs with converted value (14)
    # 4. __attrs_post_init__ runs after all attributes are set on self
    assert LIFECYCLE_STEPS == [
        "pre_init:7",
        "converter:7",
        "validator:14",
        "post_init:14",
    ]


if __name__ == "__main__":
    test_resolve_types_forward_references()
    test_make_class_programmatic_generation()
    test_fields_dict_vs_fields_inspection()
    test_has_introspection()
    test_frozen_field_vs_frozen_instance()
    test_manual_validation()
    test_astuple_serialization_and_filtering()
    test_initialization_lifecycle_sequence()
    print("contract ok:", importlib.metadata.version("attrs"))

오리진 시더

csx-seed