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
InitConfigoptions - 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 comparisonsmin_length(),max_length(),length()- Length validationrange_()- Value range validationpattern()- Regex matchinginstance_of()- Type checkingmember_of()- Membership validationand_(),or_(),not_()- Logical compositionoptional()- Allow None valuesiterable(),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 typecoercion_failed- Failed to convert value to target typevalidation_failed- Validator rejected the valuerequired_field- Required field is missingnull_not_allowed- None provided butallow_null=Falseinvalid_format- String format doesn't match patternvalue_too_small- Number belowmin_valuevalue_too_large- Number abovemax_valuelength_too_short- String/collection belowmin_lengthlength_too_long- String/collection abovemax_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:
- Deserialize: Convert strings like
["42", "43"]into an actual list of integers - Validate: Check that the value is actually an int
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad3f0d545d54d19f23c7d2298b58cb6c83ddcc20a8faeb54d3d01d67930bb66b
|
|
| MD5 |
6372a41550bd532168f265188c4510db
|
|
| BLAKE2b-256 |
193244dbfc5721f8e41c707c870bd027155b45c3ac478ad7b20bf42c06d2e564
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86d45d87f9829c8471eba92cfd48eb62bdbb6971317fdb73bcfe3c55218733e6
|
|
| MD5 |
efb858f040c45077b89b173727c59ad4
|
|
| BLAKE2b-256 |
71dece259ba49ddc49f9fb642ddce46aa39b6a4cfa15701c4cc983c35f66279e
|