An easy-to-use seeder manager to keep seed data in sync across multiple environments.
Project description
Django Synced Seeds
An easy-to-use seeder manager to keep seed data in sync across multiple environments. Perfect for managing reference data, initial configurations, and test data across development, staging, and production environments.
Features
✨ Version Control for Seeds - Track and manage seed versions with automatic revision tracking 🔄 Environment Sync - Keep data consistent across development, staging, and production 📦 Export & Import - Easy data export from any environment and import to others 🎯 Selective Loading - Load only the seeds you need with intelligent version checking 🏗️ Django Integration - Built specifically for Django with full ORM support 🧪 Test-Friendly - Comprehensive test suite with function-based tests 🔧 Extensible - Easy to extend with custom seeders for your specific needs
Quick Start
Installation
pip install django-synced-seeders
Add to Django Settings
# settings.py
INSTALLED_APPS = [
# ... your apps
'seeders',
]
# Optional: Custom seed metadata path
SEEDS_META_PATH = BASE_DIR / "seeds_meta.json"
Run Migrations
python manage.py migrate
Create Your First Seeder
# myapp/seeders.py
from seeds.registries import seeder_registry
from seeds.seeders import Seeder
from .models import Category, Tag
@seeder_registry.register()
class CategorySeeder(Seeder):
"""Seeder for category reference data."""
seed_slug = "categories"
exporting_querysets = (Category.objects.all(),)
# Optional: whether to delete existing data before loading
delete_existing = True
@seeder_registry.register()
class TagSeeder(Seeder):
"""Seeder for tag data."""
seed_slug = "tags"
exporting_querysets = (Tag.objects.all(),)
Export Data
# Export data from current environment
python manage.py exportseed categories
python manage.py exportseed tags
Sync Data to Another Environment
# Import all available seeds
python manage.py syncseeds
Core Concepts
Seeders
A Seeder is a Python class that defines how to export and import specific data. Each seeder:
- Has a unique
seed_slugidentifier - Defines which data to export via
exporting_querysets - Manages the seed file path and format
- Controls whether to delete existing data before importing
Version Management
Seeds are automatically versioned using a metadata file (seeds_meta.json). Each time you export:
- The revision number increments
- Only newer versions are imported during sync
- Already up-to-date seeds are skipped
Registry System
Seeders are automatically discovered and registered using the @seeder_registry.register() decorator. The registry:
- Auto-discovers seeders in
*/seeders.pyfiles - Provides access to all registered seeders
- Enables the management commands to find your seeders
Usage Examples
Development Workflow
# 1. Create some reference data in your dev environment
# (through admin, fixtures, or manual creation)
# 2. Export the data
python manage.py exportseed categories
python manage.py exportseed user_roles
# 3. Commit the generated seed files and metadata
git add seeds/ seeds_meta.json
git commit -m "Add category and user role seeds"
# 4. Deploy to staging/production
git pull
python manage.py syncseeds
Custom Seeder with Filtering
from seeds.registries import seeder_registry
from seeds.seeders import Seeder
from myapp.models import User, UserProfile
@seeder_registry.register()
class AdminUserSeeder(Seeder):
"""Seeder for admin users only."""
seed_slug = "admin_users"
# Custom queryset filtering
exporting_querysets = (
User.objects.filter(is_superuser=True),
UserProfile.objects.filter(user__is_superuser=True),
)
# Don't delete existing admins
delete_existing = False
Environment-Specific Seeds
from django.conf import settings
from seeds.registries import seeder_registry
from seeds.seeders import Seeder
@seeder_registry.register()
class ConfigSeeder(Seeder):
"""Environment-specific configuration seeder."""
seed_slug = "app_config"
def __init__(self):
super().__init__()
# Different seed file per environment
env = getattr(settings, 'ENVIRONMENT', 'dev')
self.seed_path = f"seeds/config_{env}.json"
@property
def exporting_querysets(self):
from myapp.models import AppConfig
return (AppConfig.objects.filter(environment=settings.ENVIRONMENT),)
Complex Data Relationships
@seeder_registry.register()
class BlogSeeder(Seeder):
"""Seeder for blog content with relationships."""
seed_slug = "blog_content"
exporting_querysets = (
# Export in dependency order
Category.objects.all(),
Tag.objects.all(),
Author.objects.all(),
Post.objects.all(),
PostTag.objects.all(), # Many-to-many through table
)
def get_export_objects(self):
"""Custom export logic with proper ordering."""
from itertools import chain
# Ensure categories and tags are first
categories = Category.objects.all()
tags = Tag.objects.all()
authors = Author.objects.filter(posts__isnull=False).distinct()
posts = Post.objects.select_related('category', 'author')
post_tags = PostTag.objects.select_related('post', 'tag')
return chain(categories, tags, authors, posts, post_tags)
Management Commands
exportseed
Export data for a specific seeder:
python manage.py exportseed <seed_slug>
# Examples
python manage.py exportseed categories
python manage.py exportseed user_permissions
What it does:
- Finds the seeder by slug
- Exports data using the seeder's
exporting_querysets - Saves data to JSON file
- Increments version number in metadata file
syncseeds
Import all available seeds that need updating:
python manage.py syncseeds
What it does:
- Discovers all registered seeders
- Compares local vs. available versions
- Imports only newer versions
- Skips already up-to-date seeds
- Creates revision records for tracking
Example Output:
[Synced Seeders] Syncing seeds...
[Synced Seeders] Fixture categories is installed (Not installed -> v3).
[Synced Seeders] Fixture tags is already synced, skipped.
[Synced Seeders] Fixture user_roles is installed (v1 -> v2).
[Synced Seeders] Synced 2 seeds.
File Structure
After using django-synced-seeds, your project structure will look like:
your_project/
├── seeds/ # Seed data files
│ ├── categories.json
│ ├── tags.json
│ └── user_roles.json
├── seeds_meta.json # Version metadata
├── myapp/
│ ├── seeders.py # Your seeder definitions
│ └── models.py
└── manage.py
seeds_meta.json Example
{
"categories": 3,
"tags": 1,
"user_roles": 2
}
Seed File Example
[
{
"model": "myapp.category",
"pk": 1,
"fields": {
"name": "Technology",
"slug": "technology",
"description": "Tech-related content"
}
},
{
"model": "myapp.category",
"pk": 2,
"fields": {
"name": "Business",
"slug": "business",
"description": "Business and finance content"
}
}
]
Advanced Configuration
Custom Seed Paths
# settings.py
SEEDS_META_PATH = BASE_DIR / "custom_seeds" / "metadata.json"
# Custom seeder with specific path
class CustomSeeder(Seeder):
seed_slug = "custom_data"
seed_path = "custom_seeds/my_data.json"
exporting_querysets = (MyModel.objects.all(),)
Conditional Seeding
class ConditionalSeeder(Seeder):
seed_slug = "conditional_data"
def load_seed(self):
"""Override to add custom logic."""
if settings.DEBUG:
# Only load in development
super().load_seed()
else:
self.stdout.write("Skipping conditional seed in production")
@property
def exporting_querysets(self):
# Dynamic querysets based on environment
if settings.DEBUG:
return (TestData.objects.all(),)
return (ProductionData.objects.all(),)
Integration with Django Fixtures
Django Synced Seeds works alongside Django's built-in fixtures:
# You can still use regular fixtures
python manage.py loaddata initial_data.json
# And use synced seeds for environment-specific data
python manage.py syncseeds
Testing
Django Synced Seeds includes a comprehensive test suite with function-based tests:
# Install test dependencies
pip install django-synced-seeders[testing]
# Run all tests
python -m pytest
# Run specific test categories
python -m pytest -m "not slow" # Skip slow tests
python -m pytest tests/test_models.py # Just model tests
python -m pytest tests/test_integration.py # Integration tests
# With coverage
python -m pytest --cov=seeders --cov-report=html
Writing Tests for Your Seeders
import pytest
from django.test import override_settings
from myapp.models import Category
from myapp.seeders import CategorySeeder
@pytest.mark.django_db
def test_category_seeder_export():
"""Test exporting category data."""
# Create test data
Category.objects.create(name="Test Category", slug="test")
seeder = CategorySeeder()
seeder.seed_path = "/tmp/test_categories.json"
# Test export
seeder.export()
# Verify file was created
assert Path(seeder.seed_path).exists()
@pytest.mark.django_db
def test_category_seeder_load():
"""Test loading category data."""
seeder = CategorySeeder()
seeder.seed_path = "fixtures/test_categories.json"
# Load seed data
seeder.load_seed()
# Verify data was imported
assert Category.objects.filter(name="Test Category").exists()
Best Practices
1. Organize Seeders by Domain
# users/seeders.py - User-related seeds
class UserRoleSeeder(Seeder): ...
class PermissionSeeder(Seeder): ...
# content/seeders.py - Content-related seeds
class CategorySeeder(Seeder): ...
class TagSeeder(Seeder): ...
2. Use Descriptive Seed Slugs
# Good
seed_slug = "user_permissions"
seed_slug = "blog_categories"
seed_slug = "payment_providers"
# Avoid
seed_slug = "data"
seed_slug = "stuff"
seed_slug = "seed1"
3. Order Dependencies Properly
class BlogSeeder(Seeder):
# Export dependencies first
exporting_querysets = (
Author.objects.all(), # No dependencies
Category.objects.all(), # No dependencies
Post.objects.all(), # Depends on Author, Category
Comment.objects.all(), # Depends on Post
)
4. Handle Sensitive Data
class UserSeeder(Seeder):
"""Seeder that excludes sensitive data."""
@property
def exporting_querysets(self):
# Exclude sensitive fields or users
return (
User.objects.exclude(is_superuser=True)
.only('username', 'email', 'first_name', 'last_name'),
)
5. Environment-Specific Configuration
# settings/development.py
SEEDS_META_PATH = BASE_DIR / "seeds" / "dev_meta.json"
# settings/production.py
SEEDS_META_PATH = BASE_DIR / "seeds" / "prod_meta.json"
6. Version Control Best Practices
# Always commit seeds and metadata together
git add seeds/ seeds_meta.json
git commit -m "Update user role seeds to v3"
# Use .gitignore for environment-specific seeds if needed
echo "seeds/local_*.json" >> .gitignore
7. Deployment Integration
# In your deployment script
python manage.py migrate # Run migrations first
python manage.py syncseeds # Then sync seed data
python manage.py collectstatic # Then static files
Troubleshooting
Common Issues
Q: Seeds aren't being discovered
# Make sure your seeders.py file is in the right location
myapp/
├── seeders.py # ✓ Will be discovered
└── models.py
# And uses the registry decorator
@seeder_registry.register()
class MySeeder(Seeder): ...
Q: "Seed path is not set" error
# Either set seed_path explicitly
class MySeeder(Seeder):
seed_path = "seeds/my_data.json"
# Or let it use the default (seeds/{seed_slug}.json)
class MySeeder(Seeder):
seed_slug = "my_data" # Will use seeds/my_data.json
Q: Data not loading correctly
# Check your exporting_querysets
class MySeeder(Seeder):
exporting_querysets = (
MyModel.objects.all(), # Make sure this returns data
)
# Debug what's being exported
def get_export_objects(self):
objects = super().get_export_objects()
print(f"Exporting {len(list(objects))} objects")
return super().get_export_objects()
Q: Version conflicts
# Check current versions
cat seeds_meta.json
# Force re-export if needed
python manage.py exportseed my_seeder
# Or manually edit seeds_meta.json to reset versions
Debug Mode
# settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'seeders': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
API Reference
Seeder Class
class Seeder:
"""Base seeder class."""
# Configuration attributes
seed_slug: str = "base-seed"
seed_path: str | None = None
delete_existing: bool = True
exporting_querysets: tuple = ()
# Methods
def __init__(self) -> None: ...
def load_seed(self) -> None: ...
def export(self) -> None: ...
def get_export_objects(self) -> Iterable: ...
Registry Functions
from seeds.registries import seeder_registry
# Register a seeder
@seeder_registry.register()
class MySeeder(Seeder): ...
# Access registered seeders
all_seeders = seeder_registry.registry
my_seeder_class = seeder_registry.registry["my_slug"]
Utility Functions
from seeds.utils import get_seed_meta_path
# Get metadata file path
meta_path = get_seed_meta_path()
Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
Development Setup
# Clone the repository
git clone https://github.com/Starscribers/python-packages.git
cd python-packages/django-synced-seeds
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install development dependencies
pip install -e .[dev,testing]
# Run tests
python -m pytest
# Run with coverage
python -m pytest --cov=seeders --cov-report=html
# Code formatting
black src/ tests/
isort src/ tests/
# Type checking
mypy src/
Happy coding! 🎉 Join our discord open source community: https://discord.gg/ngE8JxjDx7
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_synced_seeders-0.2.0.tar.gz.
File metadata
- Download URL: django_synced_seeders-0.2.0.tar.gz
- Upload date:
- Size: 19.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
238ca0dad30875d70fd2a5b0be6ce17162f6c4b9baa909a660809874a4814dd8
|
|
| MD5 |
db65b4c584158f0bf6ecde56b1032b20
|
|
| BLAKE2b-256 |
33697d39d36994a10db31e9b1bb7dbae308e1287446d490dd78701a40eca8220
|
File details
Details for the file django_synced_seeders-0.2.0-py3-none-any.whl.
File metadata
- Download URL: django_synced_seeders-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e3b1acb4cf9fd9281257d834e32c14e8697a60e2146581b09736c0133cda1e2
|
|
| MD5 |
749922d5e440f36751e1599e5ed12a88
|
|
| BLAKE2b-256 |
6be619fbe2b1b4f498a2d9126e8a7a46639240c6d1bd07f2cca5eea6b588914b
|