attrs
Python classes without boilerplate — generates __init__, __repr__, __eq__, __hash__ and more from class definitions with validators and converters. attrs features: @define (modern API), @attrs.define for dataclass-like syntax, attr.ib() / attrs.field() for field definition, validators (instance_of, in_, optional, and_, or_, deep_iterable), converters (str, int, custom), Factory for default factories, frozen=True for immutable instances, slots=True for memory efficiency, on_setattr for post-set hooks, __attrs_post_init__ hook, evolve() for copying with changes, asdict()/astuple() for serialization, and cattrs integration for complex serialization.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
Pure data class library with no network calls. validators.instance_of() and custom validators enforce input types. frozen=True prevents tampering with immutable data. No security concerns beyond standard input validation best practices.
⚡ Reliability
Best When
Data classes with validation, converters, and immutability requirements — attrs provides more features than stdlib dataclasses including validators, converters, and slots support.
Avoid When
Simple data containers without validation (use dataclasses), DB models (use SQLAlchemy), or when Pydantic's automatic JSON/API integration is needed.
Use Cases
- • Agent data class — import attrs; @attrs.define; class Config: host: str = attrs.field(validator=attrs.validators.instance_of(str)); port: int = attrs.field(default=8080, validator=attrs.validators.in_(range(1, 65536))); debug: bool = False — validated class; agent creates data classes with built-in validation; @define generates __init__ with validation
- • Agent frozen immutable — @attrs.define(frozen=True); class Point: x: float; y: float; p = Point(1.0, 2.0); evolved = attrs.evolve(p, x=3.0) — immutable; agent creates immutable value objects; frozen=True raises FrozenInstanceError on modification; evolve() creates new instance with changed fields
- • Agent validators — @attrs.define; class User: email: str = attrs.field(); @email.validator; def check_email(self, attribute, value): if '@' not in value: raise ValueError('Invalid email'); name: str = attrs.field(validator=attrs.validators.min_len(1)) — custom validator; agent validates with inline validator functions
- • Agent converters — @attrs.define; class HTTPConfig: timeout: int = attrs.field(converter=int); url: str = attrs.field(converter=str.strip) — type coercion; agent accepts string input and converts to correct types automatically; converters run before validators; useful for config parsing from strings
- • Agent slots for memory — @attrs.define; class Record: id: int; value: float; name: str — slots=True by default in @define; agent creates memory-efficient classes for large collections; slots classes are ~30% smaller than dict-based; cannot add new attributes dynamically
Not For
- • Simple data containers — Python 3.7+ dataclasses cover basic use cases without deps; attrs adds value with validators and converters
- • Serialization to JSON/dict — attrs provides asdict() but cattrs is the companion for complex serialization/deserialization
- • ORM models — attrs is for in-memory data; for DB-backed models use SQLAlchemy or Django ORM
Interface
Authentication
No auth — data class library.
Pricing
attrs is MIT licensed. Created by Hynek Schlawack. Free for all use.
Agent Metadata
Known Gotchas
- ⚠ Two APIs: @attr.s vs @attrs.define — legacy API uses @attr.s(auto_attribs=True) with attr.ib(); modern API uses @attrs.define with type annotations; agent code: use modern @attrs.define (slots=True by default, better performance); avoid mixing APIs in same codebase; @define is PEP 526 style
- ⚠ Slots classes cannot have dynamic attributes — @attrs.define generates __slots__; obj.new_attr = 'x' raises AttributeError; agent code needing dynamic attributes: use @attrs.define(slots=False) — slower but dict-based; or plan all attributes at class definition time; __attrs_post_init__ runs after init for computed attributes
- ⚠ Validators run at __init__ only — validator only runs during construction; mutating a non-frozen instance bypasses validators: obj.port = 99999 — no validation; agent code needing mutation validation: use frozen=True + evolve(), or use on_setattr=attrs.setters.validate in field definition: attrs.field(on_setattr=attrs.setters.validate)
- ⚠ evolve() requires attrs class — attrs.evolve(obj, field=new_value) only works on attrs instances; if obj is a dict or regular class, evolve() raises TypeError; agent code: verify object is attrs class with attrs.has(type(obj)) before using evolve(); use copy.replace() for dataclasses
- ⚠ Factory vs default for mutable defaults — attrs.field(default=[]) raises ValueError (mutable default); use attrs.field(factory=list) for mutable defaults; Factory(dict) for dict; agent code: never use default=[] or default={} in attrs fields; always use factory= for lists/dicts/sets
- ⚠ asdict() recurses into nested attrs — attrs.asdict(obj) converts nested attrs instances to dicts recursively; non-attrs values (list of non-attrs objects) are returned as-is; agent code serializing to JSON: nested attrs become dicts; lists of attrs become lists of dicts; datetimes are NOT converted — need custom filter or converter
Alternatives
Full Evaluation Report
Comprehensive deep-dive: security analysis, reliability audit, agent experience review, cost modeling, competitive positioning, and improvement roadmap for attrs.
AI-powered analysis · PDF + markdown · Delivered within 30 minutes
Package Brief
Quick verdict, integration guide, cost projections, gotchas with workarounds, and alternatives comparison.
Delivered within 10 minutes
Score Monitoring
Get alerted when this package's AF, security, or reliability scores change significantly. Stay ahead of regressions.
Continuous monitoring
Scores are editorial opinions as of 2026-03-06.