# ๐ฆ Duckdantic
<div align="center">
[](https://pypi.org/project/duckdantic/)
[](https://pypi.org/project/duckdantic/)
[](https://pypi.org/project/duckdantic/)
[](https://github.com/pr1m8/duckdantic/blob/main/LICENSE)
[](https://github.com/pr1m8/duckdantic/actions)
[](https://duckdantic.readthedocs.io/en/latest/?badge=latest)
**๐ Structural typing and runtime validation for Python**
*If it walks like a duck and quacks like a duck, then it's probably a duck*
[๐ Documentation](https://duckdantic.readthedocs.io/) โข [๐ฏ Examples](https://github.com/pr1m8/duckdantic/tree/main/docs/examples) โข [๐ฆ PyPI](https://pypi.org/project/duckdantic/) โข [๐ GitHub](https://github.com/pr1m8/duckdantic)
</div>
---
## ๐ What is Duckdantic?
**Duckdantic** brings true **structural typing** to Python runtime! Check if objects satisfy interfaces without inheritance, validate data shapes dynamically, and build flexible APIs that work with **any** compatible object.
Perfect for **microservices**, **plugin architectures**, and **polyglot systems** where you need structural compatibility without tight coupling.
## โจ Key Features
| Feature | Description |
|---------|-------------|
| ๐ฆ **True Duck Typing** | Runtime structural validation without inheritance |
| โก **High Performance** | Intelligent caching and optimized field normalization |
| ๐ **Universal Compatibility** | Works with [Pydantic](https://pydantic.dev/), [dataclasses](https://docs.python.org/3/library/dataclasses.html), [TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict), [attrs](https://www.attrs.org/), and plain objects |
| ๐ฏ **Flexible Policies** | Customize validation behavior (strict, lenient, coercive) |
| ๐๏ธ **Protocol Integration** | Drop-in compatibility with Python's [typing.Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol) |
| ๐ **ABC Support** | Use with `isinstance()` and `issubclass()` |
| ๐จ **Ergonomic API** | Clean, intuitive interface with excellent IDE support |
## ๐ฆ Installation
```bash
# Basic installation
pip install duckdantic
# With Pydantic support (recommended)
pip install "duckdantic[pydantic]"
# Development installation
pip install "duckdantic[all]"
```
## ๐ Quick Start
### The Duck API (Most Popular)
Perfect for **Pydantic** users and modern Python development:
```python
from pydantic import BaseModel
from duckdantic import Duck
# Define your models
class User(BaseModel):
name: str
email: str
age: int
is_active: bool = True
class Person(BaseModel):
name: str
age: int
# Create a structural type
PersonShape = Duck(Person)
# โ
Structural validation - no inheritance needed!
user = User(name="Alice", email="alice@example.com", age=30)
assert isinstance(user, PersonShape) # True! User has required Person fields
# ๐ Convert between compatible types
person = PersonShape.convert(user) # Person(name="Alice", age=30)
```
### Universal Compatibility
Works with **any** Python object:
```python
from dataclasses import dataclass
from typing import TypedDict
from duckdantic import Duck
# Works with dataclasses
@dataclass
class DataPerson:
name: str
age: int
# Works with TypedDict
class DictPerson(TypedDict):
name: str
age: int
# Works with plain objects
class PlainPerson:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
# One duck type validates them all!
PersonShape = Duck.from_fields({"name": str, "age": int})
# โ
All of these work
assert isinstance(DataPerson("Bob", 25), PersonShape)
assert isinstance(DictPerson(name="Charlie", age=35), PersonShape)
assert isinstance(PlainPerson("Diana", 28), PersonShape)
```
### Advanced: Trait-Based Validation
For complex structural requirements:
```python
from duckdantic import TraitSpec, FieldSpec, satisfies
# Define a complex trait
APIResponse = TraitSpec(
name="APIResponse",
fields=(
FieldSpec("status", int, required=True),
FieldSpec("data", dict, required=True),
FieldSpec("message", str, required=False),
FieldSpec("timestamp", str, required=False),
)
)
# Validate any object structure
response1 = {"status": 200, "data": {"users": []}}
response2 = {"status": 404, "data": {}, "message": "Not found"}
assert satisfies(response1, APIResponse) # โ
assert satisfies(response2, APIResponse) # โ
```
### Method Validation
Ensure objects implement required methods:
```python
from duckdantic import MethodSpec, methods_satisfy
# Define method requirements (like typing.Protocol)
Drawable = [
MethodSpec("draw", params=[int, int], returns=None),
MethodSpec("get_bounds", params=[], returns=tuple),
]
class Circle:
def draw(self, x: int, y: int) -> None:
print(f"Drawing circle at ({x}, {y})")
def get_bounds(self) -> tuple:
return (0, 0, 10, 10)
assert methods_satisfy(Circle, Drawable) # โ
```
## ๐ฏ Common Use Cases
### ๐ Microservices & APIs
```python
from duckdantic import Duck
from fastapi import FastAPI
from pydantic import BaseModel
class CreateUserRequest(BaseModel):
name: str
email: str
class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: str
# Ensure response compatibility across services
ResponseShape = Duck(UserResponse)
app = FastAPI()
@app.post("/users/")
def create_user(request: CreateUserRequest):
# Any object with the right shape works
response = build_user_response(request) # From any source
assert isinstance(response, ResponseShape) # Validation
return response
```
### ๐ Plugin Systems
```python
from duckdantic import Duck
# Define plugin interface
class PluginInterface:
name: str
version: str
def execute(self, data: dict) -> dict: ...
PluginShape = Duck(PluginInterface)
# Load plugins from anywhere
def load_plugin(plugin_class):
if isinstance(plugin_class(), PluginShape):
return plugin_class()
raise ValueError("Invalid plugin structure")
```
### ๐งช Testing & Mocking
```python
from duckdantic import Duck
ProductionModel = Duck(YourProductionClass)
# Create test doubles that satisfy production interfaces
class MockService:
def __init__(self):
self.required_field = "test"
self.another_field = 42
mock = MockService()
assert isinstance(mock, ProductionModel) # โ
Valid test double
```
## ๐ Related Projects
| Project | Relationship | When to Use |
|---------|--------------|-------------|
| [**Pydantic**](https://pydantic.dev/) | ๐ค **Perfect Companion** | Use Pydantic for data validation, Duckdantic for structural typing |
| [**typing.Protocol**](https://docs.python.org/3/library/typing.html#typing.Protocol) | ๐ **Runtime Alternative** | Protocols are static, Duckdantic is runtime + dynamic |
| [**attrs**](https://www.attrs.org/) | โ
**Fully Compatible** | Define classes with attrs, validate with Duckdantic |
| [**dataclasses**](https://docs.python.org/3/library/dataclasses.html) | โ
**Fully Compatible** | Built-in support for dataclass validation |
| [**TypedDict**](https://docs.python.org/3/library/typing.html#typing.TypedDict) | โ
**Fully Compatible** | Validate dictionary structures dynamically |
## ๐ฌ Type Algebra & Relationships
Duckdantic implements a **complete algebraic type system** with mathematical properties that enable powerful type composition and analysis:
### Set-Theoretic Operations
```python
from duckdantic import Duck, TraitSpec, FieldSpec
# Define base types
Person = Duck.from_fields({"name": str, "age": int})
Employee = Duck.from_fields({"name": str, "age": int, "employee_id": str})
Manager = Duck.from_fields({"name": str, "age": int, "employee_id": str, "team": list})
# Subtyping relationships (Employee โ Person)
assert Employee <= Person # Employee is a subtype of Person
assert Manager <= Employee # Manager is a subtype of Employee
assert Manager <= Person # Transitivity holds!
# Type intersection (A โฉ B)
HasName = Duck.from_fields({"name": str})
HasAge = Duck.from_fields({"age": int})
PersonIntersect = HasName & HasAge # Creates intersection type
# Type union (A โช B)
EmailUser = Duck.from_fields({"email": str})
PhoneUser = Duck.from_fields({"phone": str})
ContactableUser = EmailUser | PhoneUser # Either email OR phone
# Type algebra satisfies mathematical laws
assert (A <= B and B <= C) implies (A <= C) # Transitivity
assert (A & B) <= A and (A & B) <= B # Intersection property
assert A <= (A | B) and B <= (A | B) # Union property
```
### Structural Width & Depth Subtyping
```python
# Width subtyping: More fields = more specific
Basic = Duck.from_fields({"id": int})
Extended = Duck.from_fields({"id": int, "name": str})
assert Extended <= Basic # Extended is a subtype (has more fields)
# Depth subtyping: More specific field types
Generic = Duck.from_fields({"items": list})
Specific = Duck.from_fields({"items": list[str]})
assert Specific <= Generic # Specific is a subtype (more precise type)
# Combined width + depth
BaseAPI = Duck.from_fields({"status": int})
DetailedAPI = Duck.from_fields({"status": int, "data": dict[str, Any], "meta": dict})
assert DetailedAPI <= BaseAPI # Satisfies both width and depth
```
### Algebraic Properties
Duckdantic's type system satisfies important algebraic properties:
| Property | Definition | Example |
|----------|------------|---------|
| **Reflexivity** | A โ A | `Person <= Person` is always true |
| **Antisymmetry** | A โ B โง B โ A โน A = B | If types satisfy each other, they're equivalent |
| **Transitivity** | A โ B โง B โ C โน A โ C | Subtyping chains work as expected |
| **Join Existence** | โA,B โ(A โจ B) | Union types always exist |
| **Meet Existence** | โA,B โ(A โง B) | Intersection types always exist |
### Practical Applications
```python
# Type-safe function composition
def process_person(obj: Person) -> dict:
"""Accepts any object satisfying Person shape"""
return {"name": obj.name.upper(), "adult": obj.age >= 18}
def process_employee(obj: Employee) -> dict:
"""More specific - requires employee_id too"""
result = process_person(obj) # Safe! Employee <= Person
result["emp_id"] = obj.employee_id
return result
# Automatic type narrowing
manager = Manager(name="Alice", age=35, employee_id="M001", team=["Bob", "Charlie"])
assert isinstance(manager, Person) # โ
True
assert isinstance(manager, Employee) # โ
True
assert isinstance(manager, Manager) # โ
True
# Use in generic contexts
from typing import TypeVar
T = TypeVar('T', bound=Person)
def birthday(person: T) -> T:
"""Works with any Person-like type"""
person.age += 1
return person
# Works with all subtypes!
birthday(manager) # Manager in, Manager out
```
### Type Lattice Visualization
```
โค (Top - Empty type)
|
Manager
|
Employee
|
Person
|
HasName โฉ HasAge
/ \
HasName HasAge
\ /
โฅ (Bottom - Any type)
```
This algebraic foundation enables:
- **Type inference**: Automatically determine most general valid types
- **Type checking**: Mathematically prove type safety
- **Type optimization**: Find minimal type representations
- **Composition**: Build complex types from simple ones
## ๐๏ธ Architecture Patterns
### Hexagonal Architecture
```python
# Define port interfaces with Duck types
UserRepositoryPort = Duck.from_methods({
"save": (User,) -> User,
"find_by_id": (int,) -> Optional[User],
})
# Any implementation that matches the structure works
class SQLUserRepository:
def save(self, user: User) -> User: ...
def find_by_id(self, user_id: int) -> Optional[User]: ...
assert isinstance(SQLUserRepository(), UserRepositoryPort) # โ
```
### CQRS Pattern
```python
# Command/Query interfaces as Duck types
Command = Duck.from_fields({"command_id": str, "timestamp": datetime})
Query = Duck.from_fields({"query_id": str, "filters": dict})
# Any object with the right shape is valid
CreateUserCommand = {"command_id": "123", "timestamp": datetime.now(), "user_data": {...}}
assert isinstance(CreateUserCommand, Command) # โ
```
## ๐ Type Relationships & Operators
Duckdantic provides rich comparison operators for type analysis:
### Comparison Operators
```python
from duckdantic import Duck
# Define a type hierarchy
Animal = Duck.from_fields({"name": str})
Dog = Duck.from_fields({"name": str, "breed": str})
Poodle = Duck.from_fields({"name": str, "breed": str, "fluffy": bool})
# Subtype checking (<=, >=)
assert Poodle <= Dog <= Animal # Poodle is most specific
assert Animal >= Dog >= Poodle # Animal is most general
# Strict subtype (<, >)
assert Poodle < Dog < Animal # Strictly more specific
assert Animal > Dog > Poodle # Strictly more general
# Type equality (==)
Dog2 = Duck.from_fields({"name": str, "breed": str})
assert Dog == Dog2 # Same structure = equal types
# Type compatibility (~)
class MyDog:
name: str = "Fido"
breed: str = "Labrador"
assert MyDog() ~ Dog # MyDog instance satisfies Dog shape
```
### Advanced Type Operations
```python
# Type difference
RequiredFields = Employee - Person # Fields in Employee but not Person
# Result: {"employee_id": str}
# Type complement
NotPerson = ~Person # Types that don't satisfy Person
# Conditional types
Adult = Person.where(lambda p: p.age >= 18)
Senior = Person.where(lambda p: p.age >= 65)
# Type guards
def process_contact(contact: EmailUser | PhoneUser):
if contact ~ EmailUser:
send_email(contact.email)
elif contact ~ PhoneUser:
send_sms(contact.phone)
```
## ๐ Performance
Duckdantic is built for **production performance**:
- **Intelligent caching**: Field analysis cached per type
- **Lazy evaluation**: Only validates what's needed
- **Zero-copy operations**: Minimal object creation overhead
- **Optimized for common patterns**: Special handling for Pydantic/dataclasses
```python
# Benchmark: 1M validations
import timeit
from duckdantic import Duck
PersonShape = Duck.from_fields({"name": str, "age": int})
test_obj = {"name": "test", "age": 25}
time_taken = timeit.timeit(
lambda: isinstance(test_obj, PersonShape),
number=1_000_000
)
print(f"1M validations: {time_taken:.2f}s") # ~0.5s on modern hardware
```
## ๐ ๏ธ Advanced Configuration
### Custom Validation Policies
```python
from duckdantic import Duck, ValidationPolicy
# Strict policy: exact type matching
strict_duck = Duck(MyModel, policy=ValidationPolicy.STRICT)
# Lenient policy: duck typing with coercion
lenient_duck = Duck(MyModel, policy=ValidationPolicy.LENIENT)
# Custom policy: your own rules
class CustomPolicy(ValidationPolicy):
def validate_field(self, value, expected_type):
# Your custom validation logic
return custom_validation(value, expected_type)
custom_duck = Duck(MyModel, policy=CustomPolicy())
```
### Integration with Type Checkers
```python
from typing import TYPE_CHECKING
from duckdantic import Duck
if TYPE_CHECKING:
# Static type checking with Protocol
from typing import Protocol
class PersonProtocol(Protocol):
name: str
age: int
else:
# Runtime validation with Duck
PersonProtocol = Duck.from_fields({"name": str, "age": int})
# Works with both mypy and runtime!
def process_person(person: PersonProtocol) -> str:
return f"{person.name} is {person.age} years old"
```
## ๐ Documentation
| Resource | Description |
|----------|-------------|
| [๐ **Getting Started**](https://duckdantic.readthedocs.io/en/latest/getting-started/) | Complete setup and basic usage guide |
| [๐ฏ **Examples**](https://duckdantic.readthedocs.io/en/latest/examples/) | Real-world usage patterns and recipes |
| [๐ง **API Reference**](https://duckdantic.readthedocs.io/en/latest/api/) | Complete API documentation |
| [๐๏ธ **Architecture Guide**](https://duckdantic.readthedocs.io/en/latest/guide/architecture/) | Design patterns and best practices |
| [โก **Performance Guide**](https://duckdantic.readthedocs.io/en/latest/guide/performance/) | Optimization tips and benchmarks |
## ๐ค Contributing
We love contributions! Duckdantic is built by the community, for the community.
```bash
# Quick setup
git clone https://github.com/pr1m8/duckdantic.git
cd duckdantic
pip install -e ".[dev]"
pytest # Run tests
```
See our [**Contributing Guide**](CONTRIBUTING.md) for detailed instructions.
## ๐ Community
- ๐ **Found a bug?** [Open an issue](https://github.com/pr1m8/duckdantic/issues)
- ๐ก **Have an idea?** [Start a discussion](https://github.com/pr1m8/duckdantic/discussions)
- ๐ **Need help?** Check our [documentation](https://duckdantic.readthedocs.io/)
- ๐ **Like the project?** Give us a star on [GitHub](https://github.com/pr1m8/duckdantic)!
- ๐ฆ **Install it!** Get it from [PyPI](https://pypi.org/project/duckdantic/)
## ๐ License
Licensed under the **MIT License** - see [LICENSE](LICENSE.txt) for details.
## ๐ Acknowledgments
- **TypeScript** and **Go interfaces** for structural typing inspiration
- **[Pydantic](https://pydantic.dev/)** for showing how beautiful Python validation can be
- **[Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol)** for static structural typing patterns
- **The Python community** for embracing duck typing as a core principle
---
<div align="center">
**"In the face of ambiguity, refuse the temptation to guess."**
*โ The Zen of Python*
**Duckdantic: Where structure meets flexibility** ๐ฆโจ
</div>
Raw data
{
"_id": null,
"home_page": null,
"name": "duckdantic",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "duck-typing, pydantic, runtime-types, structural-typing, validation",
"author": null,
"author_email": "duckdantic <william.astley@algebraicwealth.com>",
"download_url": "https://files.pythonhosted.org/packages/06/36/33fbb60a525d254b54e7e4ca37bc77fd0de649fc65cee7ef556c415c0d65/duckdantic-1.1.1.tar.gz",
"platform": null,
"description": "# \ud83e\udd86 Duckdantic\n\n<div align=\"center\">\n\n[](https://pypi.org/project/duckdantic/)\n[](https://pypi.org/project/duckdantic/)\n[](https://pypi.org/project/duckdantic/)\n[](https://github.com/pr1m8/duckdantic/blob/main/LICENSE)\n[](https://github.com/pr1m8/duckdantic/actions)\n[](https://duckdantic.readthedocs.io/en/latest/?badge=latest)\n\n**\ud83d\ude80 Structural typing and runtime validation for Python**\n\n*If it walks like a duck and quacks like a duck, then it's probably a duck*\n\n[\ud83d\udcda Documentation](https://duckdantic.readthedocs.io/) \u2022 [\ud83c\udfaf Examples](https://github.com/pr1m8/duckdantic/tree/main/docs/examples) \u2022 [\ud83d\udce6 PyPI](https://pypi.org/project/duckdantic/) \u2022 [\ud83d\udc19 GitHub](https://github.com/pr1m8/duckdantic)\n\n</div>\n\n---\n\n## \ud83c\udf1f What is Duckdantic?\n\n**Duckdantic** brings true **structural typing** to Python runtime! Check if objects satisfy interfaces without inheritance, validate data shapes dynamically, and build flexible APIs that work with **any** compatible object.\n\nPerfect for **microservices**, **plugin architectures**, and **polyglot systems** where you need structural compatibility without tight coupling.\n\n## \u2728 Key Features\n\n| Feature | Description |\n|---------|-------------|\n| \ud83e\udd86 **True Duck Typing** | Runtime structural validation without inheritance |\n| \u26a1 **High Performance** | Intelligent caching and optimized field normalization | \n| \ud83d\udd0c **Universal Compatibility** | Works with [Pydantic](https://pydantic.dev/), [dataclasses](https://docs.python.org/3/library/dataclasses.html), [TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict), [attrs](https://www.attrs.org/), and plain objects |\n| \ud83c\udfaf **Flexible Policies** | Customize validation behavior (strict, lenient, coercive) |\n| \ud83c\udfd7\ufe0f **Protocol Integration** | Drop-in compatibility with Python's [typing.Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol) |\n| \ud83d\udd0d **ABC Support** | Use with `isinstance()` and `issubclass()` |\n| \ud83c\udfa8 **Ergonomic API** | Clean, intuitive interface with excellent IDE support |\n\n## \ud83d\udce6 Installation\n\n```bash\n# Basic installation\npip install duckdantic\n\n# With Pydantic support (recommended)\npip install \"duckdantic[pydantic]\"\n\n# Development installation\npip install \"duckdantic[all]\"\n```\n\n## \ud83d\ude80 Quick Start\n\n### The Duck API (Most Popular)\n\nPerfect for **Pydantic** users and modern Python development:\n\n```python\nfrom pydantic import BaseModel\nfrom duckdantic import Duck\n\n# Define your models\nclass User(BaseModel):\n name: str\n email: str\n age: int\n is_active: bool = True\n\nclass Person(BaseModel):\n name: str\n age: int\n\n# Create a structural type\nPersonShape = Duck(Person)\n\n# \u2705 Structural validation - no inheritance needed!\nuser = User(name=\"Alice\", email=\"alice@example.com\", age=30)\nassert isinstance(user, PersonShape) # True! User has required Person fields\n\n# \ud83d\udd04 Convert between compatible types\nperson = PersonShape.convert(user) # Person(name=\"Alice\", age=30)\n```\n\n### Universal Compatibility\n\nWorks with **any** Python object:\n\n```python\nfrom dataclasses import dataclass\nfrom typing import TypedDict\nfrom duckdantic import Duck\n\n# Works with dataclasses\n@dataclass\nclass DataPerson:\n name: str\n age: int\n\n# Works with TypedDict\nclass DictPerson(TypedDict):\n name: str\n age: int\n\n# Works with plain objects\nclass PlainPerson:\n def __init__(self, name: str, age: int):\n self.name = name\n self.age = age\n\n# One duck type validates them all!\nPersonShape = Duck.from_fields({\"name\": str, \"age\": int})\n\n# \u2705 All of these work\nassert isinstance(DataPerson(\"Bob\", 25), PersonShape)\nassert isinstance(DictPerson(name=\"Charlie\", age=35), PersonShape) \nassert isinstance(PlainPerson(\"Diana\", 28), PersonShape)\n```\n\n### Advanced: Trait-Based Validation\n\nFor complex structural requirements:\n\n```python\nfrom duckdantic import TraitSpec, FieldSpec, satisfies\n\n# Define a complex trait\nAPIResponse = TraitSpec(\n name=\"APIResponse\",\n fields=(\n FieldSpec(\"status\", int, required=True),\n FieldSpec(\"data\", dict, required=True),\n FieldSpec(\"message\", str, required=False),\n FieldSpec(\"timestamp\", str, required=False),\n )\n)\n\n# Validate any object structure\nresponse1 = {\"status\": 200, \"data\": {\"users\": []}}\nresponse2 = {\"status\": 404, \"data\": {}, \"message\": \"Not found\"}\n\nassert satisfies(response1, APIResponse) # \u2705 \nassert satisfies(response2, APIResponse) # \u2705\n```\n\n### Method Validation\n\nEnsure objects implement required methods:\n\n```python\nfrom duckdantic import MethodSpec, methods_satisfy\n\n# Define method requirements (like typing.Protocol)\nDrawable = [\n MethodSpec(\"draw\", params=[int, int], returns=None),\n MethodSpec(\"get_bounds\", params=[], returns=tuple),\n]\n\nclass Circle:\n def draw(self, x: int, y: int) -> None:\n print(f\"Drawing circle at ({x}, {y})\")\n \n def get_bounds(self) -> tuple:\n return (0, 0, 10, 10)\n\nassert methods_satisfy(Circle, Drawable) # \u2705\n```\n\n## \ud83c\udfaf Common Use Cases\n\n### \ud83c\udf10 Microservices & APIs\n\n```python\nfrom duckdantic import Duck\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\nclass CreateUserRequest(BaseModel):\n name: str\n email: str\n\nclass UserResponse(BaseModel):\n id: int\n name: str\n email: str\n created_at: str\n\n# Ensure response compatibility across services\nResponseShape = Duck(UserResponse)\n\napp = FastAPI()\n\n@app.post(\"/users/\")\ndef create_user(request: CreateUserRequest):\n # Any object with the right shape works\n response = build_user_response(request) # From any source\n assert isinstance(response, ResponseShape) # Validation\n return response\n```\n\n### \ud83d\udd0c Plugin Systems\n\n```python\nfrom duckdantic import Duck\n\n# Define plugin interface\nclass PluginInterface:\n name: str\n version: str\n def execute(self, data: dict) -> dict: ...\n\nPluginShape = Duck(PluginInterface)\n\n# Load plugins from anywhere\ndef load_plugin(plugin_class):\n if isinstance(plugin_class(), PluginShape):\n return plugin_class()\n raise ValueError(\"Invalid plugin structure\")\n```\n\n### \ud83e\uddea Testing & Mocking\n\n```python\nfrom duckdantic import Duck\n\nProductionModel = Duck(YourProductionClass)\n\n# Create test doubles that satisfy production interfaces\nclass MockService:\n def __init__(self):\n self.required_field = \"test\"\n self.another_field = 42\n\nmock = MockService()\nassert isinstance(mock, ProductionModel) # \u2705 Valid test double\n```\n\n## \ud83d\udd17 Related Projects\n\n| Project | Relationship | When to Use |\n|---------|--------------|-------------|\n| [**Pydantic**](https://pydantic.dev/) | \ud83e\udd1d **Perfect Companion** | Use Pydantic for data validation, Duckdantic for structural typing |\n| [**typing.Protocol**](https://docs.python.org/3/library/typing.html#typing.Protocol) | \ud83d\udd04 **Runtime Alternative** | Protocols are static, Duckdantic is runtime + dynamic |\n| [**attrs**](https://www.attrs.org/) | \u2705 **Fully Compatible** | Define classes with attrs, validate with Duckdantic |\n| [**dataclasses**](https://docs.python.org/3/library/dataclasses.html) | \u2705 **Fully Compatible** | Built-in support for dataclass validation |\n| [**TypedDict**](https://docs.python.org/3/library/typing.html#typing.TypedDict) | \u2705 **Fully Compatible** | Validate dictionary structures dynamically |\n\n## \ud83d\udd2c Type Algebra & Relationships\n\nDuckdantic implements a **complete algebraic type system** with mathematical properties that enable powerful type composition and analysis:\n\n### Set-Theoretic Operations\n\n```python\nfrom duckdantic import Duck, TraitSpec, FieldSpec\n\n# Define base types\nPerson = Duck.from_fields({\"name\": str, \"age\": int})\nEmployee = Duck.from_fields({\"name\": str, \"age\": int, \"employee_id\": str})\nManager = Duck.from_fields({\"name\": str, \"age\": int, \"employee_id\": str, \"team\": list})\n\n# Subtyping relationships (Employee \u2286 Person)\nassert Employee <= Person # Employee is a subtype of Person\nassert Manager <= Employee # Manager is a subtype of Employee\nassert Manager <= Person # Transitivity holds!\n\n# Type intersection (A \u2229 B)\nHasName = Duck.from_fields({\"name\": str})\nHasAge = Duck.from_fields({\"age\": int})\nPersonIntersect = HasName & HasAge # Creates intersection type\n\n# Type union (A \u222a B)\nEmailUser = Duck.from_fields({\"email\": str})\nPhoneUser = Duck.from_fields({\"phone\": str})\nContactableUser = EmailUser | PhoneUser # Either email OR phone\n\n# Type algebra satisfies mathematical laws\nassert (A <= B and B <= C) implies (A <= C) # Transitivity\nassert (A & B) <= A and (A & B) <= B # Intersection property\nassert A <= (A | B) and B <= (A | B) # Union property\n```\n\n### Structural Width & Depth Subtyping\n\n```python\n# Width subtyping: More fields = more specific\nBasic = Duck.from_fields({\"id\": int})\nExtended = Duck.from_fields({\"id\": int, \"name\": str})\nassert Extended <= Basic # Extended is a subtype (has more fields)\n\n# Depth subtyping: More specific field types\nGeneric = Duck.from_fields({\"items\": list})\nSpecific = Duck.from_fields({\"items\": list[str]})\nassert Specific <= Generic # Specific is a subtype (more precise type)\n\n# Combined width + depth\nBaseAPI = Duck.from_fields({\"status\": int})\nDetailedAPI = Duck.from_fields({\"status\": int, \"data\": dict[str, Any], \"meta\": dict})\nassert DetailedAPI <= BaseAPI # Satisfies both width and depth\n```\n\n### Algebraic Properties\n\nDuckdantic's type system satisfies important algebraic properties:\n\n| Property | Definition | Example |\n|----------|------------|---------|\n| **Reflexivity** | A \u2286 A | `Person <= Person` is always true |\n| **Antisymmetry** | A \u2286 B \u2227 B \u2286 A \u27f9 A = B | If types satisfy each other, they're equivalent |\n| **Transitivity** | A \u2286 B \u2227 B \u2286 C \u27f9 A \u2286 C | Subtyping chains work as expected |\n| **Join Existence** | \u2200A,B \u2203(A \u2228 B) | Union types always exist |\n| **Meet Existence** | \u2200A,B \u2203(A \u2227 B) | Intersection types always exist |\n\n### Practical Applications\n\n```python\n# Type-safe function composition\ndef process_person(obj: Person) -> dict:\n \"\"\"Accepts any object satisfying Person shape\"\"\"\n return {\"name\": obj.name.upper(), \"adult\": obj.age >= 18}\n\ndef process_employee(obj: Employee) -> dict:\n \"\"\"More specific - requires employee_id too\"\"\"\n result = process_person(obj) # Safe! Employee <= Person\n result[\"emp_id\"] = obj.employee_id\n return result\n\n# Automatic type narrowing\nmanager = Manager(name=\"Alice\", age=35, employee_id=\"M001\", team=[\"Bob\", \"Charlie\"])\nassert isinstance(manager, Person) # \u2705 True\nassert isinstance(manager, Employee) # \u2705 True\nassert isinstance(manager, Manager) # \u2705 True\n\n# Use in generic contexts\nfrom typing import TypeVar\n\nT = TypeVar('T', bound=Person)\n\ndef birthday(person: T) -> T:\n \"\"\"Works with any Person-like type\"\"\"\n person.age += 1\n return person\n\n# Works with all subtypes!\nbirthday(manager) # Manager in, Manager out\n```\n\n### Type Lattice Visualization\n\n```\n \u22a4 (Top - Empty type)\n |\n Manager\n |\n Employee\n |\n Person\n |\n HasName \u2229 HasAge\n / \\\n HasName HasAge\n \\ /\n \u22a5 (Bottom - Any type)\n```\n\nThis algebraic foundation enables:\n- **Type inference**: Automatically determine most general valid types\n- **Type checking**: Mathematically prove type safety\n- **Type optimization**: Find minimal type representations\n- **Composition**: Build complex types from simple ones\n\n## \ud83c\udfd7\ufe0f Architecture Patterns\n\n### Hexagonal Architecture\n```python\n# Define port interfaces with Duck types\nUserRepositoryPort = Duck.from_methods({\n \"save\": (User,) -> User,\n \"find_by_id\": (int,) -> Optional[User],\n})\n\n# Any implementation that matches the structure works\nclass SQLUserRepository:\n def save(self, user: User) -> User: ...\n def find_by_id(self, user_id: int) -> Optional[User]: ...\n\nassert isinstance(SQLUserRepository(), UserRepositoryPort) # \u2705\n```\n\n### CQRS Pattern\n```python\n# Command/Query interfaces as Duck types\nCommand = Duck.from_fields({\"command_id\": str, \"timestamp\": datetime})\nQuery = Duck.from_fields({\"query_id\": str, \"filters\": dict})\n\n# Any object with the right shape is valid\nCreateUserCommand = {\"command_id\": \"123\", \"timestamp\": datetime.now(), \"user_data\": {...}}\nassert isinstance(CreateUserCommand, Command) # \u2705\n```\n\n## \ud83d\udd17 Type Relationships & Operators\n\nDuckdantic provides rich comparison operators for type analysis:\n\n### Comparison Operators\n\n```python\nfrom duckdantic import Duck\n\n# Define a type hierarchy\nAnimal = Duck.from_fields({\"name\": str})\nDog = Duck.from_fields({\"name\": str, \"breed\": str})\nPoodle = Duck.from_fields({\"name\": str, \"breed\": str, \"fluffy\": bool})\n\n# Subtype checking (<=, >=)\nassert Poodle <= Dog <= Animal # Poodle is most specific\nassert Animal >= Dog >= Poodle # Animal is most general\n\n# Strict subtype (<, >)\nassert Poodle < Dog < Animal # Strictly more specific\nassert Animal > Dog > Poodle # Strictly more general\n\n# Type equality (==)\nDog2 = Duck.from_fields({\"name\": str, \"breed\": str})\nassert Dog == Dog2 # Same structure = equal types\n\n# Type compatibility (~)\nclass MyDog:\n name: str = \"Fido\"\n breed: str = \"Labrador\"\n\nassert MyDog() ~ Dog # MyDog instance satisfies Dog shape\n```\n\n### Advanced Type Operations\n\n```python\n# Type difference\nRequiredFields = Employee - Person # Fields in Employee but not Person\n# Result: {\"employee_id\": str}\n\n# Type complement\nNotPerson = ~Person # Types that don't satisfy Person\n\n# Conditional types\nAdult = Person.where(lambda p: p.age >= 18)\nSenior = Person.where(lambda p: p.age >= 65)\n\n# Type guards\ndef process_contact(contact: EmailUser | PhoneUser):\n if contact ~ EmailUser:\n send_email(contact.email)\n elif contact ~ PhoneUser:\n send_sms(contact.phone)\n```\n\n## \ud83d\udcca Performance\n\nDuckdantic is built for **production performance**:\n\n- **Intelligent caching**: Field analysis cached per type\n- **Lazy evaluation**: Only validates what's needed\n- **Zero-copy operations**: Minimal object creation overhead\n- **Optimized for common patterns**: Special handling for Pydantic/dataclasses\n\n```python\n# Benchmark: 1M validations\nimport timeit\nfrom duckdantic import Duck\n\nPersonShape = Duck.from_fields({\"name\": str, \"age\": int})\ntest_obj = {\"name\": \"test\", \"age\": 25}\n\ntime_taken = timeit.timeit(\n lambda: isinstance(test_obj, PersonShape),\n number=1_000_000\n)\nprint(f\"1M validations: {time_taken:.2f}s\") # ~0.5s on modern hardware\n```\n\n## \ud83d\udee0\ufe0f Advanced Configuration\n\n### Custom Validation Policies\n\n```python\nfrom duckdantic import Duck, ValidationPolicy\n\n# Strict policy: exact type matching\nstrict_duck = Duck(MyModel, policy=ValidationPolicy.STRICT)\n\n# Lenient policy: duck typing with coercion\nlenient_duck = Duck(MyModel, policy=ValidationPolicy.LENIENT)\n\n# Custom policy: your own rules\nclass CustomPolicy(ValidationPolicy):\n def validate_field(self, value, expected_type):\n # Your custom validation logic\n return custom_validation(value, expected_type)\n\ncustom_duck = Duck(MyModel, policy=CustomPolicy())\n```\n\n### Integration with Type Checkers\n\n```python\nfrom typing import TYPE_CHECKING\nfrom duckdantic import Duck\n\nif TYPE_CHECKING:\n # Static type checking with Protocol\n from typing import Protocol\n \n class PersonProtocol(Protocol):\n name: str\n age: int\nelse:\n # Runtime validation with Duck\n PersonProtocol = Duck.from_fields({\"name\": str, \"age\": int})\n\n# Works with both mypy and runtime!\ndef process_person(person: PersonProtocol) -> str:\n return f\"{person.name} is {person.age} years old\"\n```\n\n## \ud83d\udcda Documentation\n\n| Resource | Description |\n|----------|-------------|\n| [\ud83d\udcd6 **Getting Started**](https://duckdantic.readthedocs.io/en/latest/getting-started/) | Complete setup and basic usage guide |\n| [\ud83c\udfaf **Examples**](https://duckdantic.readthedocs.io/en/latest/examples/) | Real-world usage patterns and recipes |\n| [\ud83d\udd27 **API Reference**](https://duckdantic.readthedocs.io/en/latest/api/) | Complete API documentation |\n| [\ud83c\udfd7\ufe0f **Architecture Guide**](https://duckdantic.readthedocs.io/en/latest/guide/architecture/) | Design patterns and best practices |\n| [\u26a1 **Performance Guide**](https://duckdantic.readthedocs.io/en/latest/guide/performance/) | Optimization tips and benchmarks |\n\n## \ud83e\udd1d Contributing\n\nWe love contributions! Duckdantic is built by the community, for the community.\n\n```bash\n# Quick setup\ngit clone https://github.com/pr1m8/duckdantic.git\ncd duckdantic\npip install -e \".[dev]\"\npytest # Run tests\n```\n\nSee our [**Contributing Guide**](CONTRIBUTING.md) for detailed instructions.\n\n## \ud83c\udf89 Community\n\n- \ud83d\udc1b **Found a bug?** [Open an issue](https://github.com/pr1m8/duckdantic/issues)\n- \ud83d\udca1 **Have an idea?** [Start a discussion](https://github.com/pr1m8/duckdantic/discussions)\n- \ud83d\udcd6 **Need help?** Check our [documentation](https://duckdantic.readthedocs.io/)\n- \ud83c\udf1f **Like the project?** Give us a star on [GitHub](https://github.com/pr1m8/duckdantic)!\n- \ud83d\udce6 **Install it!** Get it from [PyPI](https://pypi.org/project/duckdantic/)\n\n## \ud83d\udcc4 License\n\nLicensed under the **MIT License** - see [LICENSE](LICENSE.txt) for details.\n\n## \ud83d\ude4f Acknowledgments\n\n- **TypeScript** and **Go interfaces** for structural typing inspiration\n- **[Pydantic](https://pydantic.dev/)** for showing how beautiful Python validation can be\n- **[Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol)** for static structural typing patterns\n- **The Python community** for embracing duck typing as a core principle\n\n---\n\n<div align=\"center\">\n\n**\"In the face of ambiguity, refuse the temptation to guess.\"** \n*\u2014 The Zen of Python*\n\n**Duckdantic: Where structure meets flexibility** \ud83e\udd86\u2728\n\n</div>",
"bugtrack_url": null,
"license": null,
"summary": "Flexible structural typing and runtime validation for Python",
"version": "1.1.1",
"project_urls": {
"Changelog": "https://github.com/pr1m8/duckdantic/blob/main/CHANGELOG.md",
"Documentation": "https://duckdantic.readthedocs.io/",
"Issues": "https://github.com/pr1m8/duckdantic/issues",
"PyPI": "https://pypi.org/project/duckdantic/",
"Repository": "https://github.com/pr1m8/duckdantic"
},
"split_keywords": [
"duck-typing",
" pydantic",
" runtime-types",
" structural-typing",
" validation"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "1d6adfeb0051a94d9180ed4e170ae29320d2c5b726b49d7ae3ea6b0dee6dc930",
"md5": "6a3660bfee9ea555d24f76fbef254d34",
"sha256": "401e5aff66dfd0c9270e6c752e47a00682c7518535f12092e8416f15cd9b5aa6"
},
"downloads": -1,
"filename": "duckdantic-1.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6a3660bfee9ea555d24f76fbef254d34",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 35888,
"upload_time": "2025-08-22T21:40:16",
"upload_time_iso_8601": "2025-08-22T21:40:16.856626Z",
"url": "https://files.pythonhosted.org/packages/1d/6a/dfeb0051a94d9180ed4e170ae29320d2c5b726b49d7ae3ea6b0dee6dc930/duckdantic-1.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "063633fbb60a525d254b54e7e4ca37bc77fd0de649fc65cee7ef556c415c0d65",
"md5": "61d99f0582f72d540d314def59aef4a0",
"sha256": "3ebe64d5347a50e2df17304d8b2315cfcddeabe6a0c959415d54fa8ba3f09f86"
},
"downloads": -1,
"filename": "duckdantic-1.1.1.tar.gz",
"has_sig": false,
"md5_digest": "61d99f0582f72d540d314def59aef4a0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 488269,
"upload_time": "2025-08-22T21:40:18",
"upload_time_iso_8601": "2025-08-22T21:40:18.330993Z",
"url": "https://files.pythonhosted.org/packages/06/36/33fbb60a525d254b54e7e4ca37bc77fd0de649fc65cee7ef556c415c0d65/duckdantic-1.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-22 21:40:18",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "pr1m8",
"github_project": "duckdantic",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "duckdantic"
}