Skip to main content

🐱➕🐱️CloneCat

CI Release PyPI version Python versions Downloads License Code coverage

"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:

✨ 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 from CloneCat.
  • inspector_class = DataclassInspector: This tells CloneCat how to interspect the dataclass, revealing its attributes.
  • class Meta: A Meta-class is required, with at least the model-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

Metadata

Release files for clonecat 0.1.15

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clonecat 0.1.15
File Size Uploaded
clonecat-0.1.15.tar.gz 106.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clonecat 0.1.15
File Interpreter ABI Platform
clonecat-0.1.15-py3-none-any.whl Python 3 none any Details

Total release size: 120.0 kB

Release files / clonecat-0.1.15.tar.gz

Download URL clonecat-0.1.15.tar.gz
Size 106.7 kB
Tags Source
SHA-256 checksum
How to use checksums
ebdcdc769476f487b3971eb451b6ac9d923db59444ad21d2b25c7c15d8efeb10
BLAKE2b-256 checksum
How to use checksums
d61a52e5aae4e600067fb533ec9a986eac97372f1a462ea8ceac62cc609d27dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.9

Release files / clonecat-0.1.15-py3-none-any.whl

Download URL clonecat-0.1.15-py3-none-any.whl
Size 13.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9d0c41cd7b3e9cfcd240b4b9bce11783edc8235696155252f488de6a8f6816c1
BLAKE2b-256 checksum
How to use checksums
06afa2679eba07f7462442f042cd4114d1ecb7a6c590058a64d7dae33c1eecd9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.9

Release history Release notifications | RSS feed

This release

0.1.15 This release

2 release files

0.1.14

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page