🗺️ PyMapMe
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.
📋 Table of Contents
🚀 Quick Start
Map data from a source Pydantic model to a target model with a different structure, using simple field declarations.
Common Use Case: Third-Party API Integration
Convert camelCase third-party API responses with nested structures to snake_case Python models:
from pydantic import BaseModel, Field
from pymapme.models.mapping import MappingModel
# Third-party API response models (camelCase with nesting)
class ThirdPartyAddress(BaseModel):
streetName: str
cityName: str
zipCode: str
class ThirdPartyUserProfile(BaseModel):
firstName: str
lastName: str
userEmail: str
homeAddress: ThirdPartyAddress
isActive: bool
# Your application's Python model (snake_case, flattened)
class User(MappingModel):
first_name: str = Field(json_schema_extra={"source": "firstName"})
last_name: str = Field(json_schema_extra={"source": "lastName"})
email: str = Field(json_schema_extra={"source": "userEmail"})
street: str = Field(json_schema_extra={"source": "homeAddress.streetName"})
city: str = Field(json_schema_extra={"source": "homeAddress.cityName"})
zip_code: str = Field(json_schema_extra={"source": "homeAddress.zipCode"})
is_active: bool = Field(json_schema_extra={"source": "isActive"})
# Transform third-party API response to your application model
third_party_data = ThirdPartyUserProfile(
firstName="John",
lastName="Doe",
userEmail="john@example.com",
homeAddress=ThirdPartyAddress(
streetName="123 Main St",
cityName="New York",
zipCode="10001"
),
isActive=True
)
user = User.build_from_model(third_party_data)
# User(first_name="John", last_name="Doe", email="john@example.com",
# street="123 Main St", city="New York", zip_code="10001", is_active=True)
Basic Structure Mapping
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")
✨ Features
🎯 Nested Field Mapping
Map deeply nested fields using dot notation:
from pydantic import Field
from pymapme.models.mapping import MappingModel
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"})
🔧 Custom Transformation Functions
Transform data using custom functions with access to the source model:
from pydantic import Field
from pymapme.models.mapping import MappingModel
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()
📊 Context Data Injection
Inject additional data during transformation:
from pydantic import BaseModel, Field
from pymapme.models.mapping import MappingModel
class User(BaseModel):
name: str
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)
⚡ Automatic Field Mapping
Fields without explicit mapping use the same field name from source:
from pydantic import Field
from pymapme.models.mapping import MappingModel
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"})
📦 Installation
# Using pip
pip install pymapme
# Using Poetry
poetry add pymapme
🔧 Requirements
- Python 3.13+
- Pydantic 2.0+
🛠️ Development
Setup
# Clone the repository
git clone https://github.com/funnydman/pymapme.git
cd pymapme
# Install dependencies
poetry install
Commands
# 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
🤝 Contributing
We welcome contributions! Please follow these steps:
1. Fork and Clone
git clone https://github.com/funnydman/pymapme.git
cd pymapme
2. Create Feature Branch
git checkout -b feature/your-feature-name
3. Make Changes
- Write tests for new functionality
- Follow existing code patterns
- Update documentation if needed
4. Verify Quality
# Run full test suite
make run-unit-tests
# Check code quality
make run-static-analysis
# Format code
make format
5. Submit Pull Request
- Ensure all tests pass
- Include clear description of changes
- Reference any related issues
Development Guidelines
- 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
📄 License
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Metadata
Release files for pymapme 1.0.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 | |
|---|---|---|---|---|
| pymapme-1.0.0.tar.gz | 16.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pymapme-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.7 kB
Release files / pymapme-1.0.0.tar.gz
| Download URL | pymapme-1.0.0.tar.gz |
|---|---|
| Size | 16.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5cf46c031994df53a01029fdf7f7ba85eee880b12fe4c19b901671bd2ad4a16
|
|
BLAKE2b-256 checksum How to use checksums |
40e69d313db1bb97f4f1c2eddcdd5bd6f3766ae25e2edc5f485713c0e30e2337
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.0.1 CPython/3.13.3 Linux/6.12.33-1-lts
|
Release files / pymapme-1.0.0-py3-none-any.whl
| Download URL | pymapme-1.0.0-py3-none-any.whl |
|---|---|
| Size | 17.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
98ea959d6a406d86bf927daafdd48b2ab246c17419d45acd739dcb6fd4db6550
|
|
BLAKE2b-256 checksum How to use checksums |
6ba194999a88bc679529403c7b78babcaffda5f28e6841207d2be34ac1cbc7e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.0.1 CPython/3.13.3 Linux/6.12.33-1-lts
|
Security for pymapme 1.0.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.