Ports & Adapters foundation library for Python applications
taew-py is a Python foundation library that supports rapid development of evolvable MVPs without premature technology lock-in. By implementing the Ports & Adapters (Hexagonal Architecture) pattern, it enables you to build applications where core business logic remains independent of external technologies and frameworks.
The name taew comes from the Elvish word for "socket" - a programmable port that adapts to different needs.
When building MVPs, developers face a dilemma:
- Move fast by coupling directly to specific technologies (databases, frameworks, cloud providers)
- Move carefully by building extensive abstractions upfront
The first approach leads to technology lock-in that becomes increasingly painful to change. The second creates upfront complexity that slows initial development.
taew-py provides a third path: write business logic against protocol-based ports or ABCs, then plug in adapters as needed. This enables:
- Rapid prototyping with simple adapters (in-memory, standard library)
- Gradual evolution by swapping adapters without changing core logic
- Technology freedom through clean separation of concerns
- Type safety via Python protocols and strict type checking
Start with Python standard library adapters (this repository), then add technology-specific adapters (AWS, databases, web frameworks) from separate repositories as your needs evolve.
The taew-py library organizes code into three fundamental layers:
Domain Data Structures
- Pure data classes and types
- No behavior or logic - just data representation
- Foundation for all operations across the system
Ports
- Interfaces describing operations on domain structures
- Defined as Protocols (preferred) or ABCs when inheritance is needed
- No implementation - only contracts
- A port is a group of related interfaces bound to the same technology concern (e.g., read/write operations, send/receive operations)
Adapters
- Concrete implementations of ports built on specific technologies
- Bridge between abstract ports and real-world tools (databases, APIs, file systems, etc.)
- Each adapter targets a specific technology stack
Domain Layer (taew/domain/)
- Pure domain data structures
- Configuration types for port and application setup
Ports Layer (taew/ports/)
- Protocol-based or ABC-based interface definitions
- Examples: binding interfaces, browsing code trees, building parsers, serializing objects
Adapters Layer (taew/adapters/)
- Python standard library-based implementations
- CLI adapters for command-line interface support
- Launch-time adapters for dependency injection and instantiation
Utils (taew/utils/)
- Minimal common utilities - kept as small as possible
Adapters are selected at runtime through Python data structures (AppConfiguration and PortsMapping):
from taew.domain.configuration import AppConfiguration, PortConfiguration
config = AppConfiguration(
ports={
ports.for_serializing_objects: PortConfiguration(
adapter_module="taew.adapters.python.pprint.for_serializing_objects"
),
ports.for_browsing_code_tree: PortConfiguration(
adapter_module="taew.adapters.python.inspect.for_browsing_code_tree"
),
}
)Configuration is encoded in Python data structures for maximum flexibility. Helper Configure classes are provided for all taew adapters to enable automatic generation when needed.
This design enables:
- Zero code changes when swapping implementations
- Testing flexibility using lightweight adapters (e.g., in-memory vs. database)
- Gradual migration from simple to sophisticated technologies
taew-py promotes clean architecture through strict import boundaries:
-
Application Domain - No
taewimports- Pure domain data structures and business logic
- Completely independent of the framework
-
Application Ports - No
taewimports (only application domain)- Interface definitions using Protocols or ABCs
- May reference domain data structures
-
Application Workflows - No specific adapters or
taewimports- Orchestrates domain logic through port interfaces
- Only depends on application domain and ports
-
Application Adapters -
taewimports allowed- Customizations of generic
taewadapters - Implements application port interfaces
- Customizations of generic
-
Application Configuration -
taewimports allowed- Wires adapters to ports
- Python data structures for maximum flexibility
- Helper
Configureclasses provided for alltaewadapters
- Not Opinionated -
taewdoes not enforce any specific interpretation of Ports & Adapters - Good Practices Made Easy - Aims to make sound architectural patterns straightforward to apply
- Standard Library First - Prefers Python stdlib interfaces (collections, protocols) whenever possible; in many ways,
taew-pyextends them for Ports & Adapters development - Type Safety - Python 3.14+ with full utilization of strong type annotations
- AI-Friendly from Day One - Once domain structures and ports are defined (can be brainstormed with AI), developing specific technology adapters becomes a straightforward process that's easy and safe to delegate completely to AI agents
- CLI First MVP - close to zero code conversion of application workflows to CLI commands
See bz-taew-py - a complete CLI application for parking zone payment validation demonstrating real-world usage of taew-py's ports and adapters architecture.
Start with simple adapters, evolve as needed:
- Prototype - Use in-memory adapters for quick validation
- MVP - Switch to SQLite or local file storage
- Scale - Migrate to cloud databases without changing business logic
- Optimize - Add caching layers or specialized storage
Each transition requires only adapter changes, not core logic rewrites.
While this repository contains Python standard library adapters, technology-specific adapters are developed in separate repositories:
- Cloud adapters (Buckets, Functions, etc.) - e.g.
taew-adapters-aws - 3rd Party Database adapters (PostgreSQL, MySQL, MongoDB) - e.g.
taew-adapters-pg - Web framework adapters (FastAPI, Flask, Django) -
taew-adapters-flask
This separation enables:
- Minimal dependencies - Only include what you need
- Independent evolution - Adapters update on their own schedules
- Technology-specific testing - Each adapter suite tests against real services
taew-py requires Python 3.14+ and is currently distributed via GitHub. We recommend using uv for dependency management.
curl -LsSf https://astral.sh/uv/install.sh | sh# Initialize a new project
mkdir my-app
cd my-app
uv init my-app
# Add taew-py as a dependency
uv add "taew @ git+https://github.com/asterkin/taew-py.git@main"[project]
name = "my-app"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
"taew",
]
[tool.uv.sources]
taew = { git = "https://github.com/asterkin/taew-py.git", branch = "main" }Create configuration.py with minimal setup:
from taew.utils.cli import configure
adapters = configure()Create executable bin/my-app:
#!/usr/bin/env python3
import sys
from collections.abc import Sequence
from pathlib import Path
# Add project root to PYTHONPATH
sys.path.insert(0, str(Path(__file__).parent.parent))
from taew.ports.for_starting_programs import Main
from taew.adapters.launch_time.for_binding_interfaces import bind
from configuration import adapters
def main(cmd_args: Sequence[str]) -> None:
"""CLI entry point for testing and production use.
Args:
cmd_args: Command line arguments (typically sys.argv)
"""
try:
# Dynamically bind the Main interface
_main = bind(Main, adapters=adapters)
# Run with command line arguments
_main(cmd_args)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main(sys.argv)Make it executable: chmod +x bin/my-app
For workflow packages, create workflows/<package>/for_configuring_adapters.py:
from dataclasses import dataclass
from taew.adapters.python.dataclass.for_configuring_adapters import (
Configure as ConfigureBase,
)
@dataclass(eq=False, frozen=True)
class Configure(ConfigureBase):
_root_marker: str = "workflows"
_ports: str = "ports"
def __post_init__(self) -> None:
object.__setattr__(self, "_package", __package__)
object.__setattr__(self, "_file", __file__)my-app/
├── domain/ # Pure business data structures
├── ports/ # Protocol interfaces for your capabilities
├── workflows/ # Business logic orchestrating ports
│ └── <package>/
│ ├── __init__.py
│ ├── for_configuring_adapters.py
│ └── <workflow>.py
├── adapters/ # Port implementations
│ ├── cli/ # CLI commands (auto-discovered)
│ ├── ram/ # In-memory adapters (for prototyping)
│ └── <technology>/ # Technology-specific adapters
├── configuration.py # Dependency injection wiring
├── bin/
│ └── my-app # CLI entry point
└── pyproject.toml
taew-py is designed for AI-assisted development. The best way to learn is through hands-on experience with an AI assistant like Claude Code CLI.
See HELLO_TAEW_PY.md for a step-by-step guided tutorial. Send the proposed prompts one by one to Claude Code CLI and observe the results as you build a complete "Hello World" application through 8 progressive steps:
- Project bootstrapping - Initialize project, add dependencies, create minimal configuration and CLI shim
- Simple command - Add a basic CLI command without full architecture
- Full architecture - Implement ports, workflows, and adapters pattern
- Extension practice - Add new functionality following established patterns
- Initial documentation - Generate architecture documentation
- Template repository - Add data abstraction layer with repository pattern using string.Template
- Base class and logging - Extract shared dependencies and add cross-cutting concerns
- Final documentation - Update architecture docs with advanced patterns
Each prompt includes verification steps to confirm correct behavior.
See the results: docs/HELLO_TAEW_CALUDE.md contains the complete CLAUDE.md file generated by executing all 8 prompts with Claude Code CLI. This comprehensive architecture document demonstrates how well AI assistants can understand and document the Ports & Adapters pattern, including:
- Four-layer architecture breakdown (domain, ports, workflows, adapters)
- Complete flow from CLI entry point through dependency injection to workflow execution
- Detailed explanation of Repository Pattern, Template Method Pattern, and base class patterns
- Guidelines for adding new functionality
- Testing strategies and development workflows
See bz-taew-py for a complete application demonstrating:
- Complex domain models (parking tickets, payment cards, zones)
- Multiple workflows (car drivers, parking inspectors)
- Various adapters (RAM, directory-based storage, CLI)
- Configuration with variants (date formatting)
- Full test suite with 100% coverage
- Study bz-taew-py for real-world patterns
- Read CLAUDE.md for system architecture details
- Review CONTRIBUTING.md for AI-native development workflow
- Explore adapter implementations in
taew/adapters/python/
taew-py/
├── taew/ # Core library
│ ├── domain/ # Pure data structures
│ │ ├── configuration.py # Port and adapter configuration types
│ │ ├── argument.py # Function argument metadata
│ │ └── function.py # Function metadata structures
│ ├── ports/ # Protocol-based interfaces
│ │ ├── for_binding_interfaces.py
│ │ ├── for_browsing_code_tree.py
│ │ ├── for_building_command_parsers.py
│ │ ├── for_stringizing_objects.py
│ │ ├── for_marshalling_objects.py
│ │ └── ... # Additional port definitions
│ ├── adapters/ # Concrete implementations
│ │ ├── python/ # Python stdlib adapters
│ │ │ ├── argparse/ # CLI argument parsing
│ │ │ ├── dataclass/ # Dataclass support
│ │ │ ├── json/ # JSON serialization
│ │ │ ├── pprint/ # Pretty printing
│ │ │ ├── pickle/ # Binary serialization
│ │ │ ├── inspect/ # Code introspection
│ │ │ └── ... # 30+ stdlib adapters
│ │ ├── cli/ # CLI command framework
│ │ │ └── for_starting_programs/
│ │ └── launch_time/ # Dependency injection
│ │ └── for_binding_interfaces/
│ └── utils/ # Minimal utilities
│ ├── cli.py # CLI bootstrap helpers
│ └── configure.py # Configuration utilities
├── test/ # Test suite
├── bin/ # Sample CLI applications
├── CLAUDE.md # System architecture (AI context)
├── GEMINI.md # Quick reference (AI context)
├── AGENTS.md # Cross-agent patterns (AI context)
├── CONTRIBUTING.md # Contribution guidelines
└── README.md # This file
Key Directories:
- domain/ - Pure data classes with no behavior or dependencies
- ports/ - Protocol definitions named by capability (e.g.,
for_stringizing_objects) - adapters/python/ - 30+ adapters built on Python standard library (argparse, json, pickle, dataclass, inspect, typing, etc.)
- adapters/cli/ - Command-line interface framework with automatic command discovery
- adapters/launch_time/ - Stateless dependency injection via
bind()andcreate_instance() - utils/ - Minimal helper functions for configuration and bootstrapping
See CLAUDE.md for detailed system architecture and CONTRIBUTING.md for development guidance.
We welcome contributions! This project is designed as an AI-Native codebase, optimized for development with AI assistants like Claude Code CLI.
Please read CONTRIBUTING.md for detailed guidelines on:
- AI-native development workflow
- Using Claude Code CLI slash commands (
/issue-new,/issue-close) - Code quality standards and testing requirements
- Architectural patterns and the configurator system
- Pull request process
This project follows strict type checking and formatting standards:
- Python 3.14+ - Full utilization of modern type annotations
- All code must pass
mypyandpyrightwith zero errors - Use
rufffor formatting (runmake ruff-format) - Maintain 100% test coverage for new features
- Write tests against protocols, not implementations
This project uses uv for dependency management and make for task automation.
# Clone the repository
git clone https://github.com/asterkin/taew-py.git
cd taew-py
# Create and activate virtual environment
uv venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
# Install dependencies
uv sync
# Run the full verification suite
make allmake all- Run complete pipeline (static analysis + tests with coverage)make static- Run ruff, mypy, and pyrightmake coverage- Run tests with coverage analysismake test-unit- Run unit tests onlymake ruff-format- Format code
Tests are written against port protocols, not concrete implementations:
from taew.ports.for_stringizing_objects import Dumps as DumpsProtocol
def _get_stringizer() -> DumpsProtocol:
from taew.adapters.python.pprint.for_stringizing_objects import Dumps
return Dumps()
class TestStringizing(unittest.TestCase):
def test_stringation_dict(self):
dumps = _get_stringizer()
result = dumps({"key": "value"})
self.assertIn("key", result)This approach ensures adapters are truly interchangeable.
This project is licensed under the MIT License - see the LICENSE file for details.
- Ports & Adapters Pattern: Alistair Cockburn's original article
- Focus on Core Value and Keep Cloud Infrastructure Flexible: Asher Sterkin's article on applying Ports & Adapters in cloud environments
- Dependency Inversion Principle: Part of SOLID principles
- Protocol-based programming in Python: PEP 544