CloneCat is a Python framework that helps you create perfect clones of your objects along with all their relationships.
Project description
🐱➕🐱️CloneCat
"Two heads are better than one, but two objects are just right!"
CloneCat is a Python framework that helps you create perfect clones
of your objects along with all their relationships.
Think copy.deepcopy() but with superpowers.
🚀 Why CloneCat?
Ever tried to clone a complex object only to find that half its relationships went on vacation? CloneCat keeps the family together! It's like a family reunion, but for your data structures.
CloneCat has a clear interface to determine which relations should be copied, which relations should be cloned, and which relations should be ignored.
Additionally, CloneCat has built in validation that verifies that all relations in a dataclass are specified. When any attribute is left out, its corresponding CloneCat class cannot be used.
CopyCat works great with:
- dataclasses
- SQLAlchemy
- Django models
- Pydantic
- More frameworks can be added easily. See documentation below.
✨ Features
- 🔗 Relationship Preservation: Keeps all your object relationships intact
- 🏃♂️ Blazing Fast: Optimized for performance (okay, we tried our best)
- 🧠 Smart Detection: Automatically handles circular references without breaking a sweat
- 🎯 Type Safe: Full type hints because we're not animals
- 🛡️ Battle Tested: Comprehensive test suite (translation: we broke it many times)
- 🎭 Customizable: Supports custom cloning strategies for picky objects
📦 Installation
Using uv (because you're cool like that):
uv add clonecat
Or with pip (if you must):
pip install clonecat
🎮 Quick Start
Basic Cloning
Given the following dataclasses:
import dataclasses
@dataclasses.dataclass
class Person:
id: int
first_name: str
last_name: str
When a person must be cloned (don't try this at home),
use the following CloneCat class:
from clonecat import CloneCat
from clonecat.inspectors.dataclass import DataclassInspector
class ClonePerson(CloneCat):
inspector_class = DataclassInspector
class Meta:
model = Person
ignore = {"id"}
copy = {"first_name", "last_name"}
Let's break down the snippet above into several chunks:
class ClonePerson(CloneCat): All copy classes must inherit fromCloneCat.inspector_class = DataclassInspector: This tellsCloneCathow to interspect the dataclass, revealing its attributes.class Meta: AMeta-class is required, with at least themodel-parameter set.ignore = {"id"}: Optionally: specify keys that should NOT be copied.copy = {"first_name", "last_name"}: Optionally specify keys that should be copied.
Given an instance of Person (yes, also Elon Musk is human), clone this using:
elon_musk = Person(first_name="Elon", last_name="Musk")
elon_musk_clone = ClonePerson.clone(elon_musk, CloneCatRegistry())
Forgot to say, but CloneCatRegistry() can be used
to keep track which new instances are created out of the existing instances.
It's nothing more than a glorified dictionary.
After cloning, the following assertions hold:
assert elon_musk.first_name == elon_musk_clone.first_name
assert elon_musk.last_name == elon_musk_clone.last_name
# ID is not copied, and therefore different:
assert elon_musk.id != elon_musk_clone.id
Advanced Cloning
Ignoring keys and copying are two options, but there is another one: cloning. This implies that a new instance is created out of the old instance. The relation remains intact.
Let's say Elon Musk favorite food is Pineapple Pizza. This is encoded with the following dataclasses:
import dataclasses
@dataclasses.dataclass
class Food:
name: str
@dataclasses.dataclass
class Person:
id: int
first_name: str
last_name: str
And the corresponding CloneCat models:
from clonecat import CloneCat
from clonecat.inspectors.dataclass import DataclassInspector
class CloneFood(CloneCat):
inspector_class = DataclassInspector
class Meta:
model = Food
copy = {"name"}
class ClonePerson(CloneCat):
inspector_class = DataclassInspector
class Meta:
model = Person
ignore = {"id"}
copy = {"first_name", "last_name"}
favorite_food: Food
Now, it's time to show how this can be cloned:
pineapple_pizza = Food(name="Pineapple pizza")
elon_musk = Person(first_name="Elon", last_name="Musk", favorite_food=pineapple_pizza)
elon_musk_clone = ClonePerson.clone(elon_musk, CloneCatRegistry())
Trust, but verify:
assert elon_musk.first_name == elon_musk_clone.first_name
assert elon_musk.last_name == elon_musk_clone.last_name
# Food is a different instance
assert elon_musk.favorite_food is not elon_musk_clone.favorite_food
# But they both love Pineapple Pizza
assert elon_musk.favorite_food.name == elon_musk_clone.favorite_food.name
Handling Circular References
TODO: complete section
🎪 Advanced Features
TODO: complete section
Performance Monitoring
This section is a work in progress.
TODO: complete section
🧪 Testing
Run the test suite:
uv run pytest
With coverage:
uv run pytest --cov=clonecat --cov-report=html
🤝 Contributing
We love contributions! Whether it's:
- 🐛 Bug reports
- 💡 Feature requests
- 📖 Documentation improvements
- 🧪 Test cases
- 🎨 Code improvements
Check out our Contributing Guide to get started.
📄 License
MIT License - see LICENSE file for details.
🙏 Acknowledgments
- Inspired by the frustrations of
copy.deepcopy() - Built with love, coffee, and questionable life choices
- Special thanks to all the objects that sacrificed themselves during testing
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 clonecat-0.1.15.tar.gz.
File metadata
- Download URL: clonecat-0.1.15.tar.gz
- Upload date:
- Size: 106.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebdcdc769476f487b3971eb451b6ac9d923db59444ad21d2b25c7c15d8efeb10
|
|
| MD5 |
4953a95aed9b70de5075c6c83f5f02e3
|
|
| BLAKE2b-256 |
d61a52e5aae4e600067fb533ec9a986eac97372f1a462ea8ceac62cc609d27dd
|
File details
Details for the file clonecat-0.1.15-py3-none-any.whl.
File metadata
- Download URL: clonecat-0.1.15-py3-none-any.whl
- Upload date:
- Size: 13.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d0c41cd7b3e9cfcd240b4b9bce11783edc8235696155252f488de6a8f6816c1
|
|
| MD5 |
d34bf7ac0d3e45a753ecb5a2ee33dd0c
|
|
| BLAKE2b-256 |
06afa2679eba07f7462442f042cd4114d1ecb7a6c590058a64d7dae33c1eecd9
|