Transform Pydantic models from one structure to another with declarative field mapping.
β¨ Reshape data between APIs
π― Flatten nested structures
π Aggregate complex models
All while maintaining Pydantic's validation and type safety.
- π Quick Start
- β¨ Features
- π¦ Installation
- π§ Requirements
- π οΈ Development
- π€ Contributing
- π License
Map data from a source Pydantic model to a target model with a different structure, using simple field declarations:
from pydantic import BaseModel, Field
from pymapme.models.mapping import MappingModel
# Source models
class PersonalInfo(BaseModel):
first_name: str
last_name: str
class JobInfo(BaseModel):
title: str
company: str
class UserProfile(BaseModel):
personal: PersonalInfo
job: JobInfo
# Target model with flattened structure
class UserSummary(MappingModel):
name: str = Field(json_schema_extra={"source": "personal.first_name"})
title: str = Field(json_schema_extra={"source": "job.title"})
# Transform
profile = UserProfile(
personal=PersonalInfo(first_name="John", last_name="Smith"),
job=JobInfo(title="Developer", company="Acme")
)
summary = UserSummary.build_from_model(profile)
# UserSummary(name="John", title="Developer")
Map deeply nested fields using dot notation:
class OrderSummary(MappingModel):
customer_name: str = Field(json_schema_extra={"source": "customer.profile.name"})
payment_total: float = Field(json_schema_extra={"source": "payment.amount"})
shipping_city: str = Field(json_schema_extra={"source": "shipping.address.city"})
Transform data using custom functions with access to the source model:
class UserDisplay(MappingModel):
full_name: str = Field(json_schema_extra={"source_func": "_build_full_name"})
@staticmethod
def _build_full_name(source_model, default):
return f"{source_model.first_name} {source_model.last_name}".strip()
Inject additional data during transformation:
class EnrichedUser(MappingModel):
name: str = Field(json_schema_extra={"source": "name"})
is_premium: bool = Field(json_schema_extra={"source_func": "_check_premium"})
@staticmethod
def _check_premium(source_model, default, user_tier: str = "basic"):
return user_tier == "premium"
# Usage with context
user = User(name="John")
enriched = EnrichedUser.build_from_model(user, context={"user_tier": "premium"})
# EnrichedUser(name="John", is_premium=True)
Fields without explicit mapping use the same field name from source:
class SimpleMapping(MappingModel):
# These map automatically by name
name: str
email: str
# This uses explicit mapping
user_id: int = Field(json_schema_extra={"source": "id"})
# Using pip
pip install pymapme
# Using Poetry
poetry add pymapme
- Python 3.13+
- Pydantic 2.0+
# Clone the repository
git clone https://github.com/funnydman/pymapme.git
cd pymapme
# Install dependencies
poetry install
# Run tests with coverage
make run-unit-tests
# Run static analysis (Ruff + mypy)
make run-static-analysis
# Auto-format code
make format
# Build package
make build-package
We welcome contributions! Please follow these steps:
git clone https://github.com/funnydman/pymapme.git
cd pymapme
git checkout -b feature/your-feature-name
- Write tests for new functionality
- Follow existing code patterns
- Update documentation if needed
# Run full test suite
make run-unit-tests
# Check code quality
make run-static-analysis
# Format code
make format
- Ensure all tests pass
- Include clear description of changes
- Reference any related issues
- Tests: Write tests for all new features and bug fixes
- Type hints: Use modern Python type annotations
- Documentation: Update examples and docstrings as needed
- Commit messages: Use clear, descriptive commit messages
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.