Classno
Classno is a lightweight and extensible Python library for data modeling, schema definition, and validation. It provides a clean and intuitive way to define data classes with various features like type validation, immutability, private fields, and automatic type casting.
Key Features
- Type hints validation - Runtime type checking for all fields
- Immutable objects - Create frozen, hashable instances
- Private fields - Write-once, read-only field access patterns
- Automatic type casting - Lossy autocast between compatible types
- Customizable comparison - Control equality, hashing, and ordering behavior
- Default values and factories - Safe handling of mutable defaults
- Nested object support - Deep validation and casting of complex structures
- Slots optimization - Reduced memory footprint
- Rich comparison methods - Full ordering support
- Union and Optional types - First-class support for
Optional[T]andUnion[T1, T2] - Extended collection types - Support for
frozenset,deque,defaultdict, and more
Installation
pip install classno
Basic Usage
Simple Data Class
from classno import Classno, field
class User(Classno):
name: str
age: int = 0
email: str = field(default="")
# Create an instance
user = User(name="John", age=30)
Features Configuration
Features can be enabled by setting the __features__ class attribute:
from classno import Classno, Features
class Config(Classno):
__features__ = Features.VALIDATION | Features.FROZEN
host: str
port: int = 8080
Available features:
Features.EQ- Enable equality comparisonFeatures.ORDER- Enable ordering operationsFeatures.HASH- Make instances hashableFeatures.SLOTS- Use slots for memory optimizationFeatures.FROZEN- Make instances immutableFeatures.PRIVATE- Enable private field accessFeatures.VALIDATION- Enable type validationFeatures.LOSSY_AUTOCAST- Enable automatic type casting
Field Configuration
Fields can be configured using the field() function:
from classno import Classno, field
from datetime import datetime
class Post(Classno):
title: str
content: str = ""
created_at: datetime = field(default_factory=datetime.now)
metadata: dict = field(default_factory=dict, metadata={"indexed": True})
Important: Always use default_factory for mutable defaults (list, dict, set) to avoid shared state issues:
# ❌ WRONG - This will raise an error
class Wrong(Classno):
tags: list = []
# ✅ CORRECT - Use default_factory
class Correct(Classno):
tags: list = field(default_factory=list)
Type Validation
class ValidatedModel(Classno):
__features__ = Features.VALIDATION
numbers: list[int]
mapping: dict[str, float]
# This will raise TypeError if types don't match
model = ValidatedModel(
numbers=[1, 2, 3],
mapping={"a": 1.0, "b": 2.0}
)
Optional and Union Types
Full support for Optional and Union types with proper validation:
from typing import Optional, Union
class FlexibleModel(Classno):
__features__ = Features.VALIDATION
# Optional field - can be None
optional_name: Optional[str] = None
# Union type - can be str or int
identifier: Union[str, int]
# Complex unions
data: Union[list[str], dict[str, int], None] = None
model = FlexibleModel(optional_name=None, identifier=42)
Extended Collection Types
Support for Python's extended collection types:
from collections import deque, defaultdict, Counter
from typing import Optional
class AdvancedCollections(Classno):
__features__ = Features.VALIDATION | Features.LOSSY_AUTOCAST
# Immutable set
unique_ids: frozenset[int] = field(default_factory=frozenset)
# Double-ended queue
history: deque[str] = field(default_factory=deque)
# Dict with default values
counters: defaultdict[str, int] = field(
default_factory=lambda: defaultdict(int)
)
# Counter for frequency tracking
word_counts: Counter[str] = field(default_factory=Counter)
Immutable Objects
class ImmutableConfig(Classno):
__features__ = Features.IMMUTABLE # Combines FROZEN, SLOTS, and HASH
host: str
port: int = 8080
config = ImmutableConfig(host="localhost")
# Attempting to modify will raise an exception
config.port = 9000 # Raises Exception
Private Fields
class PrivateFields(Classno):
__features__ = Features.PRIVATE
name: str
secret: str # Can only be accessed with _secret prefix for rw, secret for ro
obj = PrivateFields(name="public")
obj._secret = "hidden" # OK
obj.secret # OK
obj.secret = "hidden" # Raises Exception
Nested Objects
class Address(Classno):
street: str
city: str
class Person(Classno):
name: str
address: Address
# Create nested structure
person = Person(
name="John",
address=Address(street="123 Main St", city="Boston")
)
Automatic Type Casting
Enable automatic type conversion with LOSSY_AUTOCAST:
class AutoCastModel(Classno):
__features__ = Features.LOSSY_AUTOCAST
count: int
price: float
active: bool
# Automatic casting from compatible types
model = AutoCastModel(
count="42", # str → int
price="19.99", # str → float
active="true" # str → bool
)
# model.count == 42 (int)
# model.price == 19.99 (float)
# model.active == True (bool)
Combining Features
Features can be combined for powerful data modeling:
class RobustModel(Classno):
# First cast, then validate
__features__ = Features.LOSSY_AUTOCAST | Features.VALIDATION | Features.FROZEN
user_id: int
tags: list[str] = field(default_factory=list)
# This works: "42" is cast to 42, validated, then frozen
model = RobustModel(user_id="42", tags=["python", "classno"])
Feature execution order:
LOSSY_AUTOCAST- Values are automatically cast to target typesVALIDATION- Type validation is performed on cast valuesFROZEN- Object becomes immutable after validation
Customization
Custom Comparison Keys
Control which fields participate in equality, hashing, and ordering:
class CustomCompare(Classno):
__features__ = Features.EQ | Features.HASH | Features.ORDER
# Use tuples for ordered keys (order matters for comparison)
__hash_keys__ = ("id",) # Only id used for hashing
__eq_keys__ = ("id", "name") # id and name for equality
__order_keys__ = ("priority", "name") # Sort by priority, then name
id: int
name: str
priority: int = 0
description: str = ""
# Two objects are equal if id and name match
obj1 = CustomCompare(id=1, name="A", description="First")
obj2 = CustomCompare(id=1, name="A", description="Second")
assert obj1 == obj2 # True - description is ignored
# Ordering uses priority first, then name
items = [
CustomCompare(id=1, name="B", priority=2),
CustomCompare(id=2, name="A", priority=1),
]
sorted_items = sorted(items) # Sorted by priority, then name
Best Practices
- Use type hints for all fields - Enables validation and better IDE support
- Use
default_factoryfor mutable defaults - Always usefield(default_factory=list)instead offield(default=[]) - Enable appropriate features - Only enable features you need (e.g.,
VALIDATIONin development,FROZENfor config) - Use
Features.SLOTS- Reduces memory usage by ~40% for classes with many instances - Combine
LOSSY_AUTOCASTwithVALIDATION- Cast first, then validate for robust data handling - Use tuples for comparison keys -
__eq_keys__ = ("id", "name")not["id", "name"] - Leverage
OptionalandUnion- Use proper type hints for flexible APIs
Error Handling
Classno provides clear, actionable error messages:
# Validation error
class Model(Classno):
__features__ = Features.VALIDATION
age: int
try:
Model(age="not a number")
except ValidationError as e:
# "Validation error for field 'age': expected int, got 'not a number' of type str"
pass
# Mutable default error
try:
class Wrong(Classno):
tags: list = [] # ❌ Error at class definition time
except ValueError as e:
# "Mutable default values are not allowed. Found list [].
# Use default_factory=lambda: [] instead to avoid shared state issues."
pass
# Frozen modification error
class Frozen(Classno):
__features__ = Features.FROZEN
value: int
obj = Frozen(value=42)
try:
obj.value = 100
except AttributeError as e:
# "Cannot modify attributes of frozen class Frozen"
pass
The library raises appropriate exceptions for:
- ValidationError - Type validation failures
- TypeError - Type mismatch during casting or validation
- ValueError - Invalid field configurations (e.g., mutable defaults)
- AttributeError - Immutability violations (FROZEN), invalid field access (PRIVATE)
Advanced Usage
Inheritance
Features and fields are properly inherited:
class BaseModel(Classno):
__features__ = Features.VALIDATION
id: int
class UserModel(BaseModel):
# Inherits VALIDATION feature and id field
name: str
email: str
user = UserModel(id=1, name="John", email="john@example.com")
Serialization
Convert objects to dictionaries for JSON serialization:
class User(Classno):
name: str
age: int
user = User(name="John", age=30)
data = user.as_dict() # {"name": "John", "age": 30}
Copying
Objects support both shallow and deep copying:
import copy
original = User(name="John", age=30)
shallow = copy.copy(original)
deep = copy.deepcopy(original)
Testing
Run the comprehensive test suite:
# Install development dependencies
poetry install
# Run all tests
poetry run poe tests
# Run fast tests (skip slow tests)
poetry run poe test-fast
# Run with coverage
poetry run poe test-all
# Run specific test categories
poetry run poe test-unit # Unit tests
poetry run poe test-integration # Integration tests
poetry run poe test-edge-cases # Edge case tests
Authors
- Dmitriy Kudryavtsev - author - kuderr
Metadata
Release files for classno 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| classno-0.1.0.tar.gz | 18.4 kB | Without provenance | Details for classno-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| classno-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for classno-0.1.0-py3-none-any.whl |
Total release size: 38.6 kB
Release files / classno-0.1.0.tar.gz
| Download URL | classno-0.1.0.tar.gz |
|---|---|
| Size | 18.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d40279eb91235caa6b6196152b88af605fcd4ad64fe255b029d7a2705c76feaf
|
|
BLAKE2b-256 checksum How to use checksums |
efb6b18828929de0f99bcf2a43e7bad5e4eff1473acc71087204853143f3a751
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.13.3 Darwin/22.6.0
|
Release files / classno-0.1.0-py3-none-any.whl
| Download URL | classno-0.1.0-py3-none-any.whl |
|---|---|
| Size | 20.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f76e6441a805d1d8451c7d6f8a20e5e8fa7d9bd6c7fe61f0ca05765f42522e7b
|
|
BLAKE2b-256 checksum How to use checksums |
f420510ad4893a7b089c4157baeb16cabde11724711cbcdec13794adc5dd0a80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.13.3 Darwin/22.6.0
|
Security for classno 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.