Skip to main content

CloneCat is a Python framework that helps you create perfect clones of your objects along with all their relationships.

Project description

🐱➕🐱️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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clonecat-0.1.15.tar.gz (106.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clonecat-0.1.15-py3-none-any.whl (13.3 kB view details)

Uploaded Python 3

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

Hashes for clonecat-0.1.15.tar.gz
Algorithm Hash digest
SHA256 ebdcdc769476f487b3971eb451b6ac9d923db59444ad21d2b25c7c15d8efeb10
MD5 4953a95aed9b70de5075c6c83f5f02e3
BLAKE2b-256 d61a52e5aae4e600067fb533ec9a986eac97372f1a462ea8ceac62cc609d27dd

See more details on using hashes here.

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

Hashes for clonecat-0.1.15-py3-none-any.whl
Algorithm Hash digest
SHA256 9d0c41cd7b3e9cfcd240b4b9bce11783edc8235696155252f488de6a8f6816c1
MD5 d34bf7ac0d3e45a753ecb5a2ee33dd0c
BLAKE2b-256 06afa2679eba07f7462442f042cd4114d1ecb7a6c590058a64d7dae33c1eecd9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page