Skip to content

FlyingBird95/CloneCat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

57 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿฑโž•๐Ÿฑ๏ธ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

About

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

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages