
Security News
Software Engineering Daily Podcast: Feross on AI, Open Source, and Supply Chain Risk
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.
pydantic-mini
Advanced tools
Pydantic-mini is a lightweight Python library that extends the functionality of Python's native dataclass by providing built-in validation, serialisation, and support for custom validators. It is designed to be simple, minimalistic, and based entirely on Python's standard library, making it perfect for projects requiring data validation and object-relational mapping (ORM) without relying on third-party dependencies.
pip install pydantic-mini
git clone https://github.com/nshaibu/pydantic-mini.git
cd pydantic-mini
# Use the code directly in your project
Here's a simple example to get you started:
from pydantic_mini import BaseModel
class Person(BaseModel):
name: str
age: int
# Create an instance
person = Person(name="Alice", age=30)
print(person) # Person(name='Alice', age=30)
# Validation happens automatically
try:
invalid_person = Person(name="Bob", age="not_a_number")
except ValidationError as e:
print(f"Validation failed: {e}")
BaseModel is the foundation class that all your data models should inherit from. It provides:
MiniAnnotated is used to add metadata and validation rules to fields:
from pydantic_mini import BaseModel, MiniAnnotated, Attrib
class User(BaseModel):
username: MiniAnnotated[str, Attrib(max_length=20)]
age: MiniAnnotated[int, Attrib(gt=0, default=18)]
Attrib defines field attributes and validation rules:
default: Default value for the fielddefault_factory: Function to generate default valuemax_length: Maximum length for stringsgt: Greater than validation for numberspattern: Regex pattern for string validationvalidators: List of custom validator functionsThe base class for all data models.
loads(data, _format="dict")Load data from various formats into model instances.
Parameters:
data: Input data (string, dict, or other format-specific data)_format: Format of input data ("json", "dict", "csv")Returns: Model instance or list of instances (for CSV)
Example:
# From JSON string
json_data = '{"name": "John", "age": 30}'
person = Person.loads(json_data, _format="json")
# From dictionary
dict_data = {"name": "Alice", "age": 25}
person = Person.loads(dict_data, _format="dict")
# From CSV
csv_data = "name,age\nJohn,30\nAlice,25"
people = Person.loads(csv_data, _format="csv")
dump(_format="dict")Serialize the model instance to various formats.
Parameters:
_format: Output format ("json", "dict", "csv")Returns: Serialized data in the specified format
Example:
person = Person(name="John", age=30)
# To JSON string
json_output = person.dump(_format="json")
# To dictionary
dict_output = person.dump(_format="dict")
__model_init__(self, **kwargs)Optional method for custom initialization logic.
Example:
from typing import Optional
from dataclasses import InitVar
class DatabaseModel(BaseModel):
id: int
name: str
database: InitVar[Optional[object]] = None
def __model_init__(self, database):
if database is not None:
# Custom initialization logic
self.id = database.get_next_id()
Pydantic-mini automatically validates field types based on annotations:
class Product(BaseModel):
name: str
price: float
quantity: int
is_available: bool
Use Attrib to add built-in validation rules:
class User(BaseModel):
username: MiniAnnotated[str, Attrib(max_length=20)]
age: MiniAnnotated[int, Attrib(gt=18)]
email: MiniAnnotated[str, Attrib(pattern=r"^\S+@\S+\.\S+$")]
score: MiniAnnotated[float, Attrib(default=0.0)]
Define custom validation functions:
from pydantic_mini.exceptions import ValidationError
def validate_not_kofi(instance, value: str):
if value.lower() == "kofi":
raise ValidationError("Kofi is not a valid name")
return value.upper() # Transform the value
class Employee(BaseModel):
name: MiniAnnotated[str, Attrib(validators=[validate_not_kofi])]
department: str
Define validators as methods with the pattern validate_<field_name>:
class School(BaseModel):
name: str
students_count: int
def validate_name(self, value, field):
if len(value) > 50:
raise ValidationError("School name too long")
return value
def validate_students_count(self, value, field):
if value < 0:
raise ValidationError("Students count cannot be negative")
return value
Apply validation rules to all fields:
class StrictModel(BaseModel):
field1: str
field2: str
field3: str
def validate(self, value, field):
if isinstance(value, str) and len(value) > 100:
raise ValidationError(f"Field {field.name} is too long")
return value
ValidationError when validation failsPydantic-mini supports three serialization formats:
person = Person(name="John", age=30)
# Serialize to JSON
json_str = person.dump(_format="json")
print(json_str) # '{"name": "John", "age": 30}'
# Deserialize from JSON
person = Person.loads('{"name": "Alice", "age": 25}', _format="json")
# Serialize to dictionary
person_dict = person.dump(_format="dict")
print(person_dict) # {'name': 'John', 'age': 30}
# Deserialize from dictionary
person = Person.loads({"name": "Bob", "age": 35}, _format="dict")
# Deserialize from CSV (returns list of instances)
csv_data = "name,age\nJohn,30\nAlice,25\nBob,35"
people = Person.loads(csv_data, _format="csv")
for person in people:
print(person)
Configure model behavior using the Config class:
import os
from datetime import datetime
import typing
class EventResult(BaseModel):
error: bool
task_id: str
event_name: str
content: typing.Any
init_params: typing.Optional[typing.Dict[str, typing.Any]]
call_params: typing.Optional[typing.Dict[str, typing.Any]]
process_id: MiniAnnotated[int, Attrib(default_factory=lambda: os.getpid())]
creation_time: MiniAnnotated[float, Attrib(default_factory=lambda: datetime.now().timestamp())]
class Config:
unsafe_hash = False
frozen = False
eq = True
order = False
disable_typecheck = False
disable_all_validation = False
| Field | Type | Default | Description |
|---|---|---|---|
init | bool | True | Whether the __init__ method is generated for the dataclass |
repr | bool | True | Whether a __repr__ method is generated |
eq | bool | True | Enables the generation of __eq__ for comparisons |
order | bool | False | Enables ordering methods (__lt__, __gt__, etc.) |
unsafe_hash | bool | False | Allows an unsafe implementation of __hash__ |
frozen | bool | False | Makes the dataclass instances immutable |
disable_typecheck | bool | False | Disable runtime type checking in models |
disable_all_validation | bool | False | Disable all validation logic (type + custom rules) |
For fields that are only used during initialization:
from dataclasses import InitVar
import typing
class DatabaseRecord(BaseModel):
id: int
name: str
database: InitVar[typing.Optional[object]] = None
def __model_init__(self, database):
if database is not None and self.id is None:
self.id = database.get_next_id()
Use default_factory for dynamic default values:
import uuid
from datetime import datetime
class Task(BaseModel):
id: MiniAnnotated[str, Attrib(default_factory=lambda: str(uuid.uuid4()))]
created_at: MiniAnnotated[float, Attrib(default_factory=lambda: datetime.now().timestamp())]
title: str
completed: MiniAnnotated[bool, Attrib(default=False)]
Create lightweight ORMs for in-memory data management:
class PersonORM:
def __init__(self):
self.people_db = []
def create(self, **kwargs):
person = Person(**kwargs)
self.people_db.append(person)
return person
def find_by_age(self, min_age):
return [p for p in self.people_db if p.age >= min_age]
def find_by_name(self, name):
return [p for p in self.people_db if p.name == name]
# Usage
orm = PersonORM()
orm.create(name="John", age=30)
orm.create(name="Alice", age=25)
orm.create(name="Bob", age=35)
adults = orm.find_by_age(18)
johns = orm.find_by_name("John")
import re
from typing import Optional, List
from pydantic_mini import BaseModel, MiniAnnotated, Attrib
from pydantic_mini.exceptions import ValidationError
def validate_strong_password(instance, password: str):
"""Validate password strength."""
if len(password) < 8:
raise ValidationError("Password must be at least 8 characters long")
if not re.search(r"[A-Z]", password):
raise ValidationError("Password must contain at least one uppercase letter")
if not re.search(r"[a-z]", password):
raise ValidationError("Password must contain at least one lowercase letter")
if not re.search(r"\d", password):
raise ValidationError("Password must contain at least one digit")
return password
def validate_username(instance, username: str):
"""Validate username format."""
if not re.match(r"^[a-zA-Z0-9_]+$", username):
raise ValidationError("Username can only contain letters, numbers, and underscores")
return username.lower()
class User(BaseModel):
username: MiniAnnotated[str, Attrib(
max_length=30,
validators=[validate_username]
)]
email: MiniAnnotated[str, Attrib(
pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
)]
password: MiniAnnotated[str, Attrib(validators=[validate_strong_password])]
age: MiniAnnotated[int, Attrib(gt=13, default=18)]
is_active: MiniAnnotated[bool, Attrib(default=True)]
roles: Optional[List[str]] = None
def validate_age(self, value, field):
if value > 120:
raise ValidationError("Age seems unrealistic")
return value
class Config:
frozen = False
eq = True
# Usage example
try:
user = User(
username="JohnDoe123",
email="john@example.com",
password="SecurePass123",
age=25,
roles=["user", "admin"]
)
# Serialize user
user_json = user.dump(_format="json")
print("User JSON:", user_json)
# Load from JSON
loaded_user = User.loads(user_json, _format="json")
print("Loaded user:", loaded_user)
except ValidationError as e:
print(f"Validation error: {e}")
from decimal import Decimal
from typing import Optional, List
from enum import Enum
class ProductStatus(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
DISCONTINUED = "discontinued"
def validate_price(instance, price: float):
if price < 0:
raise ValidationError("Price cannot be negative")
if price > 1000000:
raise ValidationError("Price too high")
return round(price, 2)
def validate_sku(instance, sku: str):
if not re.match(r"^[A-Z]{2,3}-\d{4,6}$", sku):
raise ValidationError("SKU must be in format XX-NNNN or XXX-NNNNNN")
return sku.upper()
class Product(BaseModel):
name: MiniAnnotated[str, Attrib(max_length=100)]
sku: MiniAnnotated[str, Attrib(validators=[validate_sku])]
price: MiniAnnotated[float, Attrib(validators=[validate_price])]
description: Optional[str] = None
category: str
tags: Optional[List[str]] = None
stock_quantity: MiniAnnotated[int, Attrib(default=0)]
status: str = ProductStatus.ACTIVE.value
def validate_stock_quantity(self, value, field):
if value < 0:
raise ValidationError("Stock quantity cannot be negative")
return value
def validate_category(self, value, field):
valid_categories = ["electronics", "clothing", "books", "home", "sports"]
if value.lower() not in valid_categories:
raise ValidationError(f"Category must be one of: {valid_categories}")
return value.lower()
# Create products
products = [
Product(
name="Wireless Headphones",
sku="EL-1234",
price=99.99,
description="High-quality wireless headphones",
category="electronics",
tags=["wireless", "audio", "bluetooth"],
stock_quantity=50
),
Product(
name="Python Programming Book",
sku="BK-5678",
price=29.99,
category="books",
stock_quantity=25
)
]
# Serialize to JSON
products_json = [p.dump(_format="json") for p in products]
print("Products JSON:", products_json)
All validation failures raise ValidationError:
from pydantic_mini.exceptions import ValidationError
try:
user = User(username="", email="invalid-email", age=-5)
except ValidationError as e:
print(f"Validation failed: {e}")
# Handle the error appropriately
def create_user_safely(user_data):
try:
user = User.loads(user_data, _format="dict")
return {"success": True, "user": user}
except ValidationError as e:
return {"success": False, "error": str(e)}
except Exception as e:
return {"success": False, "error": f"Unexpected error: {e}"}
# Usage
result = create_user_safely({
"username": "testuser",
"email": "test@example.com",
"password": "SecurePass123"
})
if result["success"]:
print("User created:", result["user"])
else:
print("Error:", result["error"])
For performance-critical scenarios, you can disable validation:
class FastModel(BaseModel):
field1: str
field2: int
class Config:
disable_typecheck = True
disable_all_validation = True
Choose the appropriate serialization format based on your needs:
dict format for Python-to-Python communicationjson format for API responses and storagecsv format for data export and reportingContributions are welcome! To contribute to pydantic-mini:
git clone https://github.com/nshaibu/pydantic-mini.git
cd pydantic-mini
# Set up your development environment
# Run tests
python -m pytest tests/
Pydantic-mini is open-source and available under the GPL License.
This documentation is for pydantic-mini, a lightweight alternative to Pydantic with zero external dependencies.
FAQs
Dataclass with validation
We found that pydantic-mini demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.

Security News
GitHub has revoked npm classic tokens for publishing; maintainers must migrate, but OpenJS warns OIDC trusted publishing still has risky gaps for critical projects.

Security News
Rust’s crates.io team is advancing an RFC to add a Security tab that surfaces RustSec vulnerability and unsoundness advisories directly on crate pages.