Skip to main content

A dataclass library

Project description

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.

Check available Python versions:

uv python list

Install Python (if needed):

uv python install 3.10

Sync dependencies:

uv sync --dev

uv add tzdata

Getting Started

Your First Dataclass

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

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:

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)

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

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:

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

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:

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:

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

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

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:

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:

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:

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

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

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

Serialization

Basic Serialization

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:

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:

# 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:

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

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

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

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:

# 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

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

Evolve (Immutable Update)

This works similarly to copy with updates.

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

Field Introspection

# 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

# 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

# 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

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)

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

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:

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:

# 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

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:

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:

# 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

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

deserialize(value) - Convert to target type without validation

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

validate(value) - Validate an already-typed value

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

serialize(value, fmt) - Convert to output format

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

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

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

Custom Deserializers & Serializers

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:

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)

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

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

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

validator (callable, optional) - Custom validation function

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

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

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

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

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

Defaults

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

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

# 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

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

serialization_alias (str, optional) - Alternative name for serialization

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

deserializer (callable, optional) - Custom deserialization function

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

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

# 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

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

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

# 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

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

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

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

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

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

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

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

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

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

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

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

Example

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)

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

MetaConfig (Explicit)

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

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

# 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

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

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

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

# 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

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.)

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

# 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

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

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

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dattrs-0.0.1a1.tar.gz (73.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dattrs-0.0.1a1-py3-none-any.whl (66.0 kB view details)

Uploaded Python 3

File details

Details for the file dattrs-0.0.1a1.tar.gz.

File metadata

  • Download URL: dattrs-0.0.1a1.tar.gz
  • Upload date:
  • Size: 73.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for dattrs-0.0.1a1.tar.gz
Algorithm Hash digest
SHA256 ad3f0d545d54d19f23c7d2298b58cb6c83ddcc20a8faeb54d3d01d67930bb66b
MD5 6372a41550bd532168f265188c4510db
BLAKE2b-256 193244dbfc5721f8e41c707c870bd027155b45c3ac478ad7b20bf42c06d2e564

See more details on using hashes here.

File details

Details for the file dattrs-0.0.1a1-py3-none-any.whl.

File metadata

  • Download URL: dattrs-0.0.1a1-py3-none-any.whl
  • Upload date:
  • Size: 66.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for dattrs-0.0.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 86d45d87f9829c8471eba92cfd48eb62bdbb6971317fdb73bcfe3c55218733e6
MD5 efb858f040c45077b89b173727c59ad4
BLAKE2b-256 71dece259ba49ddc49f9fb642ddce46aa39b6a4cfa15701c4cc983c35f66279e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page