샘플
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
컨트랙트
- 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
파일
- NOTES.md
- csx.json
- requirements.txt
- src/__init__.py
- src/models.py
- test/__init__.py
- test/contract.py
소스
# 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__`.
{"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"}
attrs==26.1.0
"""attrs behavioral contracts package."""
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"
"""attrs behavioral contract tests."""
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"))