dattrs 0.0.1a1
A dataclass library
Requires Python: >=3.8

# dattrs

`dattrs` is a dataclass library. It is built on Python's descriptor protocol, it lets you define structured data with type enforcement, validation, and serialization—all without hidden runtime magic.

## Quick Setup

Clone the repository and ensure you have `uv` installed. If not, visit [uv's installation guide](https://docs.astral.sh/uv/getting-started/installation/).

Check available Python versions:

```bash
uv python list
```

Install Python (if needed):

```bash
uv python install 3.10
```

Sync dependencies:

```bash
uv sync --dev

uv add tzdata
```

## Getting Started

### Your First Dataclass

Let's start simple. Here's how you define a dataclass with `dattrs`:

```python
import dattrs

class Person(dattrs.Dataclass):
    name = dattrs.field(str)
    age = dattrs.field(int)
    email = dattrs.field(str, allow_null=True, default=None)
```

That's it. Now you can deserialize data into it:

```python
person = dattrs.deserialize(Person, {
    "name": "Alice",
    "age": 30,
    "email": "alice@example.com"
})

print(person.name)  # Alice
print(person.age)   # 30
```

### Ways to Define Fields

`dattrs` gives you two ways to define fields, depending on what feels right for your use case:

**1. Using `dattrs.field()` (recommended for most cases)**

```python
class User(dattrs.Dataclass):
    username = dattrs.field(str, max_length=50)
    age = dattrs.field(int, min_value=0, max_value=120)
```

**2. Using Field classes directly**

```python
class User(dattrs.Dataclass):
    username = dattrs.String(max_length=50)
    age = dattrs.Integer(min_value=0, max_value=120)
```

Both approaches work identically - `dattrs.field()` is just a smart factory that picks the right Field class for you. Use direct Field classes when you want to be explicit or need specialized fields like `dattrs.Email`, `dattrs.IPAddress`, or `dattrs.DateTime`.

### Field Validation

Fields validate on assignment, not just during deserialization:

```python
class Product(dattrs.Dataclass):
    name = dattrs.field(str, min_length=3, max_length=100)
    price = dattrs.field(float, min_value=0.0)
    stock = dattrs.field(int, min_value=0)

product = Product(name="Widget", price=9.99, stock=100)
product.price = -5.0  # Raises ValidationError!
```

### Default Values and Factories

```python
from datetime import datetime
import random

class Article(dattrs.Dataclass):
    title = dattrs.field(str)
    content = dattrs.field(str)
    published = dattrs.field(bool, default=False)
    created_at = dattrs.field(datetime, default=datetime.now)
    view_count = dattrs.field(int, default=0)
    rating = dattrs.field(
        float, 
        default=dattrs.Factory(random.random)  # Generate random rating
    )
```

Use `dattrs.Factory()` when your default value needs to be computed or when you need to pass arguments to a callable. For simple immutable defaults like `0`, `False`, or `None`, just use them directly.

### Instantiation: Direct vs Deserialize

You can create dataclass instances in two ways:

```python
class User(dattrs.Dataclass):
    name = dattrs.field(str)
    age = dattrs.field(int)
    email = dattrs.field(str, allow_null=True, default=None)

# Option 1: Direct instantiation (like regular classes)
user = User(name="Alice", age=30, email="alice@example.com")

# Or pass a dict
user = User({"name": "Alice", "age": 30})

# Or mix both
user = User({"name": "Alice"}, age=30)

# Option 2: Using deserialize (recommended)
user = dattrs.deserialize(User, {"name": "Alice", "age": 30})
```

While both work, `deserialize()` is preferred because:

- It gives you more control with `InitConfig` options
- It's more explicit about data transformation
- The intent is clearer when reading code

Use direct instantiation for simple cases and when creating instances programmatically. Use `deserialize()` when loading external data (JSON, APIs, config files, etc.).

### Nested Dataclasses

Nested structures just work:

```python
class Address(dattrs.Dataclass):
    street = dattrs.field(str)
    city = dattrs.field(str)
    country = dattrs.field(str, default="USA")

class Company(dattrs.Dataclass):
    name = dattrs.field(str)
    address = dattrs.field(Address)

company_data = {
    "name": "Tech Corp",
    "address": {
        "street": "123 Main St",
        "city": "San Francisco"
    }
}

company = dattrs.deserialize(Company, company_data)
print(company.address.city)  # San Francisco
```

### Enums and Choices

```python
import enum

class Status(enum.Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    PENDING = "pending"

class Task(dattrs.Dataclass):
    title = dattrs.field(str)
    status = dattrs.field(dattrs.Choice[Status], default=Status.PENDING)

task = dattrs.deserialize(Task, {"title": "Deploy", "status": "active"})
print(task.status)  # Status.ACTIVE
```

### Lists and Collections

```python
from typing import List

class Team(dattrs.Dataclass):
    name = dattrs.field(str)
    members = dattrs.field(List[Person])
    tags = dattrs.field(List[str], default=list)

team_data = {
    "name": "Engineering",
    "members": [
        {"name": "Alice", "age": 30},
        {"name": "Bob", "age": 25}
    ]
}

team = dattrs.deserialize(Team, team_data)
print(len(team.members))  # 2
```

## Validation Deep Dive

### Built-in Validators

`dattrs` comes with a rich set of validators:

```python
import dattrs.validators as v

class User(dattrs.Dataclass):
    username = dattrs.field(
        str,
        validator=v.and_(
            v.min_length(3),
            v.max_length(20),
            v.pattern(r"^[a-zA-Z0-9_]+$")
        )
    )
    age = dattrs.field(int, validator=v.range_(18, 65))
    email = dattrs.field(str, validator=v.pattern(r".+@.+\..+"))
```

Available validators include:

- `gt()`, `gte()`, `lt()`, `lte()`, `eq()` - Numeric comparisons
- `min_length()`, `max_length()`, `length()` - Length validation
- `range_()` - Value range validation
- `pattern()` - Regex matching
- `instance_of()` - Type checking
- `member_of()` - Membership validation
- `and_()`, `or_()`, `not_()` - Logical composition
- `optional()` - Allow None values
- `iterable()`, `mapping()` - Collection validation

### Custom Validators

Write your own validators easily:

```python
def is_even(value, adapter=None, *args, **kwargs):
    if value % 2 != 0:
        raise ValueError(f"{value} is not even")

class EvenNumbers(dattrs.Dataclass):
    value = dattrs.field(int, validator=is_even)
```

### Composing Validators

Chain validators together:

```python
import dattrs.validators as v

class SecurePassword(dattrs.Dataclass):
    password = dattrs.field(
        str,
        validator=v.and_(
            v.min_length(8),
            v.pattern(r".*[A-Z].*"),  # Must have uppercase
            v.pattern(r".*[0-9].*"),  # Must have number
        )
    )
```

## Special Field Types

### DateTime Fields

```python
from datetime import datetime, date

class Event(dattrs.Dataclass):
    name = dattrs.field(str)
    date = dattrs.field(date, input_formats=["%Y-%m-%d", "%d/%m/%Y"])
    start_time = dattrs.field(datetime)
    created_at = dattrs.field(datetime, default=datetime.now)
```

### Email Fields

```python
class Contact(dattrs.Dataclass):
    name = dattrs.field(str)
    email = dattrs.Email()
```

## Serialization

### Basic Serialization

```python
person = Person(name="Alice", age=30, email="alice@example.com")

# To Python dict
data = dattrs.serialize(person, fmt="python")

# To JSON-compatible dict
json_data = dattrs.serialize(person, fmt="json")
```

### Serialization Aliases

Control field names in serialized output:

```python
class User(dattrs.Dataclass):
    internal_id = dattrs.field(int, serialization_alias="id")
    user_name = dattrs.field(str, serialization_alias="username")

user = User(internal_id=1, user_name="alice")
data = dattrs.serialize(user, fmt="python", by_alias=True)
# {"id": 1, "username": "alice"}
```

### Controlling Serialization with Options

Fine-tune what gets serialized:

```python
# Exclude specific fields
student_options = dattrs.Options(
    dattrs.Option(Student, exclude={"internal_notes"}),
    dattrs.Option(Course, recurse=False)  # Don't serialize nested courses
)

serialized = dattrs.serialize(student, options=student_options)
```

### Exclude Unset Fields

Only serialize fields that were explicitly set:

```python
person = Person(name="Alice", age=30)  # email not set
data = dattrs.serialize(person, exclude_unset=True)
# {"name": "Alice", "age": 30}  # email excluded
```

## Dataclass Configuration

### Class-level Config

```python
class ImmutableUser(dattrs.Dataclass, frozen=True, hash=True):
    id = dattrs.field(int)
    username = dattrs.field(str)

# This will raise FrozenInstanceError
user = ImmutableUser(id=1, username="alice")
user.username = "bob"  # Error!
```

### Meta Configuration

```python
class Student(dattrs.Dataclass):
    name = dattrs.field(str)
    age = dattrs.field(int)
    
    __config__ = dattrs.MetaConfig(
        sort=True,      # Sort fields alphabetically
        repr=True,      # Generate __repr__
        frozen=False    # Mutable instances
    )
```

### Inheritance

```python
class Person(dattrs.Dataclass):
    name = dattrs.field(str)
    age = dattrs.field(int)

class Employee(Person):
    employee_id = dattrs.field(int)
    department = dattrs.field(str)

# Employee has all Person fields plus its own
```

## Deserialization Options

### InitConfig

Control how data is deserialized:

```python
# Use field names instead of aliases
person = dattrs.deserialize(
    Person,
    data,
    config=dattrs.InitConfig(by_name=True)
)

# Fail fast on first error
person = dattrs.deserialize(
    Person,
    data,
    config=dattrs.InitConfig(fail_fast=True)
)

# Skip validation (data already validated)
person = dattrs.deserialize(
    Person,
    data,
    config=dattrs.InitConfig(is_valid=True)
)
```

## Utility Functions

### Copy with Updates

```python
person = Person(name="Alice", age=30)
updated = dattrs.copy(person, update={"age": 31})
```

### Evolve (Immutable Update)

This works similarly to `copy` with updates.

```python
updated = dattrs.evolve(person, age=31)
```

### Field Introspection

```python
# Get a specific field
field = dattrs.get_field(Person, "name")

# Get all fields
fields = dattrs.get_fields(Person)

# Check if it's a dataclass
if dattrs.is_dataclass(Person):
    print("It's a dataclass!")
```

## Error Handling

`dattrs` provides detailed error information to help you debug deserialization and validation issues quickly.

### Exception Types

**`dattrsException`** - Base exception for all dattrs errors

**`ConfigurationError`** - Raised when there's an issue with dataclass or field configuration

```python
# Example: Using both include and exclude in Options
dattrs.Option(MyClass, include={"a"}, exclude={"b"})  # ConfigurationError!
```

**`FieldError`** - Raised for field-specific errors like invalid configuration

```python
# Example: Invalid field type
class Bad(dattrs.Dataclass):
    value = dattrs.field("not a type")  # Will raise FieldError during build
```

**`ValidationError`** - Raised when field validation fails

```python
class User(dattrs.Dataclass):
    age = dattrs.field(int, min_value=0)

user = User(age=30)
user.age = -5  # ValidationError: Value must be >= 0
```

**`DeserializationError`** - Raised when deserialization fails (wraps multiple errors)

```python
try:
    person = dattrs.deserialize(Person, {
        "name": "A",    # Too short (min_length=3)
        "age": -5       # Negative (min_value=0)
    })
except dattrs.DeserializationError as e:
    print(f"Parent: {e.parent_name}")
    print(f"Total errors: {len(e.error_list)}")
    
    for error in e.error_list:
        print(f"\nLocation: {'.'.join(map(str, error.location))}")
        print(f"Message: {error.message}")
        print(f"Code: {error.code}")
        print(f"Expected: {error.expected_type}")
        print(f"Got: {error.input_type}")
```

**`SerializationError`** - Raised when serialization fails

**`FrozenInstanceError`** - Raised when trying to modify a frozen dataclass

```python
class Immutable(dattrs.Dataclass, frozen=True):
    value = dattrs.field(int)

obj = Immutable(value=10)
obj.value = 20  # FrozenInstanceError!
```

### Error Details

Every error comes with rich context via the `ErrorDetail` named tuple:

```python
error = ErrorDetail(
    location=["user", "address", "zipcode"],  # Path to error
    message="Invalid zipcode format",          # Human-readable message
    expected_type=str,                         # What was expected
    input_type=int,                            # What was received
    code="invalid_format",                     # Machine-readable error code
    context={"pattern": r"\d{5}"},            # Additional context
    origin=ValueError("...")                   # Original exception
)

# Get formatted string
print(error.as_string())
# user.address.zipcode
#   Invalid zipcode format [input_type='int', expected_type='str', code='invalid_format', origin=ValueError]

# Get JSON representation
error_json = error.as_json()
```

### Error Codes

Common error codes you'll encounter:

- `invalid_type` - Value doesn't match expected type
- `coercion_failed` - Failed to convert value to target type
- `validation_failed` - Validator rejected the value
- `required_field` - Required field is missing
- `null_not_allowed` - None provided but `allow_null=False`
- `invalid_format` - String format doesn't match pattern
- `value_too_small` - Number below `min_value`
- `value_too_large` - Number above `max_value`
- `length_too_short` - String/collection below `min_length`
- `length_too_long` - String/collection above `max_length`

### Collecting vs Failing Fast

By default, `dattrs` collects all errors. Use `fail_fast=True` to stop at the first error:

```python
# Collect all errors (default)
try:
    data = dattrs.deserialize(ComplexClass, bad_data)
except dattrs.DeserializationError as e:
    print(f"Found {len(e.error_list)} errors")

# Fail on first error
try:
    data = dattrs.deserialize(
        ComplexClass, 
        bad_data,
        config=dattrs.InitConfig(fail_fast=True)
    )
except dattrs.DeserializationError as e:
    print(f"First error: {e.error_list[0].message}")
```

## Type Adapters

Type adapters are `dattrs`'s way of handling any Python type - not just the built-in ones. Think of them as translators that know how to deserialize, validate, and serialize a specific type.

### Why Type Adapters?

When you use `dattrs.field(list[int])`, behind the scenes `dattrs` creates a `TypeAdapter[list[int]]` that knows how to:

1. **Deserialize**: Convert strings like `["42", "43"]` into an actual list of integers
2. **Validate**: Check that the value is actually an int
3. **Serialize**: Convert the int back to regular and JSON-compatible formats

For custom types or complex generic types, you can create your own adapters.

### Basic Usage

```python
from dattrs import TypeAdapter
import dattrs.validators as v

# Simple adapter with validation
age_adapter = TypeAdapter(
    int,
    name="Age",
    validator=v.range_(0, 150),
    strict=False  # Allow coercion from strings
)

# Use it
value = age_adapter.adapt("25")  # Deserializes and validates
print(value)  # 25 (int)

# Or validate without deserialization
age_adapter.validate(25)  # OK
age_adapter.validate(200)  # ValidationError!
```

### Complex Types with Adapters

Adapters shine with complex generic types:

```python
from typing import List, Dict, Optional, Tuple
from collections import namedtuple

PersonTuple = namedtuple("PersonTuple", ["name", "age", "friends"])

# Adapter for complex nested structure
adapter = TypeAdapter(
    Tuple[
        List[Optional[PersonTuple]],
        Dict[str, List[int]],
        Optional[str]
    ],
    defer_build=True  # Resolve forward references later
)

# Build it when ready
adapter.build(globalns=globals(), depth=10)

# Now adapt complex data
raw_data = (
    [
        {"name": "Alice", "age": 30, "friends": []},
        {"name": "Bob", "age": 25, "friends": []},
        None
    ],
    {"scores": [10, 20, 30]},
    "metadata"
)

adapted = adapter.adapt(raw_data)
print(type(adapted[0][0]))  # PersonTuple
```

### Using Adapters in Fields

You can pass adapters directly to fields:

```python
# Create a reusable adapter
email_adapter = TypeAdapter(
    str,
    validator=v.pattern(r".+@.+\..+"),
    deserializer=lambda v, f: v.lower().strip()
)

class User(dattrs.Dataclass):
    email = dattrs.field(email_adapter)
    # Or inline
    verified = dattrs.field(
        TypeAdapter(
            bool,
            deserializer=lambda v, f: str(v).lower() in ("1", "true", "yes")
        )
    )
```

### Adapter Methods

**`adapt(value)`** - Full pipeline: deserialize → validate → return

```python
result = adapter.adapt("42")  # Returns int(42)
```

**`deserialize(value)`** - Convert to target type without validation

```python
result = adapter.deserialize("42")  # Returns int(42), no validation
```

**`validate(value)`** - Validate an already-typed value

```python
adapter.validate(42)  # OK
adapter.validate("42")  # ValidationError
```

**`serialize(value, fmt)`** - Convert to output format

```python
json_val = adapter.serialize(42, "json")  
python_val = adapter.serialize(42, "python")  # Usually unchanged
```

**`build(...)`** - Build/resolve the adapter (for forward references)

```python
adapter = TypeAdapter(
    "MyClass",  # Forward reference
    defer_build=True
)
# Later, when MyClass is defined
adapter.build(globalns=globals())
```

### Custom Deserializers & Serializers

```python
from datetime import datetime

def parse_timestamp(value, field):
    """Custom deserializer"""
    if isinstance(value, int):
        return datetime.fromtimestamp(value)
    return datetime.fromisoformat(value)

def format_timestamp(value, field, context):
    """Custom serializer"""
    return int(value.timestamp())

timestamp_adapter = TypeAdapter(
    datetime,
    deserializer=parse_timestamp,
    serializers={
        "json": format_timestamp,
        "python": lambda v, f, ctx: v  # Keep as datetime
    }
)

class Event(dattrs.Dataclass):
    created_at = dattrs.field(timestamp_adapter)
```

### Strict Mode

Strict mode disables type coercion:

```python
strict_int = TypeAdapter(int, strict=True)
strict_int.adapt(42)    # OK
strict_int.adapt("42")  # ValidationError: expected int, got str

lenient_int = TypeAdapter(int, strict=False)
lenient_int.adapt("42")  # OK, returns 42
```

## Field Configuration Reference

Every field accepts these parameters (from `FieldKwargs`):

### Type and Validation

**`field_type`** - The expected Python type (required)

```python
dattrs.field(int)
dattrs.field(List[str])
dattrs.field(Optional[datetime])
```

**`strict`** (bool, default=`False`) - Only accept exact type, no coercion

```python
value = dattrs.field(int, strict=True)  # "42" will fail
```

**`validator`** (callable, optional) - Custom validation function

```python
value = dattrs.field(int, validator=v.range_(0, 100))
```

**`allow_null`** (bool, default=`False`) - Allow None values

```python
email = dattrs.field(str, allow_null=True)  # Can be None
```

**`required`** (bool, default=`False`) - Must be explicitly provided

```python
id = dattrs.field(int, required=True)  # Can't use default
```

### Defaults

**`default`** (value or Factory) - Default value when not provided

```python
active = dattrs.field(bool, default=False)
created = dattrs.field(datetime, default=datetime.now)
items = dattrs.field(list, default=dattrs.Factory(list))
```

**`validate_default`** (bool, default=`False`) - Validate the default value

```python
# Useful to catch config errors early
value = dattrs.field(int, default=-5, min_value=0, validate_default=True)  # Error!
```

### Serialization & Deserialization

**`alias`** (str, optional) - Alternative name for deserialization

```python
user_id = dattrs.field(int, alias="userId")
# {"userId": 123} deserializes to user_id=123
```

**`serialization_alias`** (str, optional) - Alternative name for serialization

```python
internal_id = dattrs.field(int, serialization_alias="id")
# Serializes as {"id": 123} instead of {"internal_id": 123}
```

**`deserializer`** (callable, optional) - Custom deserialization function

```python
def parse_date(value, field):
    return datetime.strptime(value, "%Y-%m-%d")

date = dattrs.field(datetime, deserializer=parse_date)
```

**`serializers`** (dict, optional) - Format-specific serializers

```python
timestamp = dattrs.field(
    datetime,
    serializers={
        "json": lambda v, f, ctx: v.isoformat(),
        "python": lambda v, f, ctx: v
    }
)
```

**`always_coerce`** (bool, default=`False`) - Always run deserializer

```python
# Even if value is already correct type
lower_str = dattrs.field(
    str, 
    deserializer=lambda v, f: v.lower(),
    always_coerce=True  # Always lowercase
)
```

**`check_coerced`** (bool, default=`False`) - Verify deserializer output type

```python
# Safety check for custom deserializers
value = dattrs.field(int, deserializer=my_parser, check_coerced=True)
```

**`skip_validator`** (bool, default=`False`) - Skip validation after deserialization

```python
# Use when you trust the deserializer output
value = dattrs.field(int, skip_validator=True)
```

### Behavior Control

**`fail_fast`** (bool, default=`False`) - Stop on first validation error

```python
strict_field = dattrs.field(int, validator=v.range_(0, 100), fail_fast=True)
```

**`init`** (bool, default=`True`) - Include in `__init__` parameters

```python
computed = dattrs.field(int, init=False)  # Not in __init__
```

**`repr`** (bool, default=`True`) - Include in `__repr__` output

```python
password = dattrs.field(str, repr=False)  # Hidden in repr
```

**`hash`** (bool, default=`True`) - Include in `__hash__` calculation

```python
id = dattrs.field(int, hash=True)
metadata = dattrs.field(dict, hash=False)  # Not hashable anyway
```

**`eq`** (bool, default=`True`) - Include in equality comparison

```python
id = dattrs.field(int, eq=True)
timestamp = dattrs.field(datetime, eq=False)  # Ignored in ==
```

**`order`** (int >= 0, optional) - Ordering priority for comparisons

```python
priority = dattrs.field(int, order=0)  # Compared first
name = dattrs.field(str, order=1)      # Compared second
```

### Example

```python
class Article(dattrs.Dataclass):
    # Minimal
    title = dattrs.field(str)
    
    # With constraints
    word_count = dattrs.field(int, min_value=0, max_value=100000)
    
    # With validation
    slug = dattrs.field(
        str,
        validator=v.pattern(r"^[a-z0-9-]+$"),
        deserializer=lambda v, f: v.lower().replace(" ", "-")
    )
    
    # With defaults
    published = dattrs.field(bool, default=False)
    views = dattrs.field(int, default=0)
    
    # With aliases
    author_id = dattrs.field(
        int,
        alias="authorId",
        serialization_alias="author"
    )
    
    # Complex
    tags = dattrs.field(
        List[str],
        default=dattrs.Factory(list),
        validator=v.and_(
            v.min_length(1),
            v.max_length(10)
        )
    )
    
    # Internal use only
    internal_notes = dattrs.field(
        str,
        allow_null=True,
        default=None,
        repr=False,
        eq=False,
        hash=False
    )
```

## Dataclass Configuration Reference

Configure dataclass behavior with class parameters or `MetaConfig`:

### Class Parameters (Inline)

```python
class MyClass(dattrs.Dataclass, frozen=True, hash=True, repr=True):
    pass
```

### MetaConfig (Explicit)

```python
class MyClass(dattrs.Dataclass):
    field1 = dattrs.field(str)
    
    __config__ = dattrs.MetaConfig(
        frozen=True,
        hash=True,
        repr=True
    )
```

### Available Options

**`frozen`** (bool, default=`False`) - Make instances immutable

```python
class ImmutableUser(dattrs.Dataclass, frozen=True):
    id = dattrs.field(int)

user = ImmutableUser(id=1)
user.id = 2  # FrozenInstanceError!
```

**`slots`** (bool or tuple, default=`False`) - Use `__slots__` for memory efficiency

```python
# Boolean: automatic slots
class Compact(dattrs.Dataclass, slots=True):
    pass

# Tuple: add custom slots
class Custom(dattrs.Dataclass, slots=("_cache", "_state")):
    pass
```

**`repr`** (bool, default=`False`) - Generate `__repr__` method

```python
class User(dattrs.Dataclass, repr=True):
    name = dattrs.field(str)
    age = dattrs.field(int)

print(User(name="Alice", age=30))
# User(name='Alice', age=30)
```

**`str`** (bool, default=`False`) - Generate `__str__` method

```python
class User(dattrs.Dataclass, str=True):
    name = dattrs.field(str)
```

**`hash`** (bool, default=`False`) - Generate `__hash__` method

```python
# Usually paired with frozen=True
class HashableUser(dattrs.Dataclass, frozen=True, hash=True):
    id = dattrs.field(int)

users = {HashableUser(id=1), HashableUser(id=2)}  # Can use in sets
```

**`eq`** (bool, default=`True`) - Generate `__eq__` method

```python
class User(dattrs.Dataclass, eq=True):
    id = dattrs.field(int)

User(id=1) == User(id=1)  # True
```

**`order`** (bool, default=`False`) - Generate comparison methods (`__lt__`, `__le__`, etc.)

```python
class Priority(dattrs.Dataclass, order=True):
    level = dattrs.field(int, order=0)
    name = dattrs.field(str, order=1)

Priority(level=1, name="Low") < Priority(level=2, name="High")  # True
```

**`sort`** (bool or callable, default=`False`) - Sort fields

```python
# Sort alphabetically
class Sorted(dattrs.Dataclass, sort=True):
    pass

# Custom sort key
class CustomSort(dattrs.Dataclass, sort=lambda item: item[1].order):
    pass
```

**`getitem`** (bool, default=`False`) - Enable `__getitem__` access

```python
class User(dattrs.Dataclass, getitem=True):
    name = dattrs.field(str)

user = User(name="Alice")
print(user["name"])  # Alice
```

**`setitem`** (bool, default=`False`) - Enable `__setitem__` assignment

```python
class User(dattrs.Dataclass, setitem=True):
    name = dattrs.field(str)

user = User(name="Alice")
user["name"] = "Bob"
```

**`pickleable`** (bool, default=`True`) - Add pickle support methods

```python
import pickle

class Pickleable(dattrs.Dataclass, pickleable=True):
    data = dattrs.field(dict)

obj = Pickleable(data={"key": "value"})
pickled = pickle.dumps(obj)
restored = pickle.loads(pickled)
```

## Contributing

Contributions are welcome! Please fork the repository and submit a pull request with your changes. Make sure to include tests for any new features or bug fixes.