gelidum
Freeze your objects in python.
Latin | English |
---|---|
Caelum est hieme frigidum et gelidum; myrtos oleas quaeque alia assiduo tepore laetantur, aspernatur ac respuit; laurum tamen patitur atque etiam nitidissimam profert, interdum sed non saepius quam sub urbe nostra necat. | In winter the air is cold and frosty: myrtles, olives and all other trees which require constant warmth for them to do well, the climate rejects and spurns, though it allows laurel to grow, and even brings it to a luxuriant leaf. Occasionally, however, it kills it, but that does not happen more frequently than in the neighbourhood of Rome. |
The Letters of the Younger Pliny, First Series — Volume 1 by the Younger Pliny, translated to English by John Benjamin Firth.
Introduction
Inspired by the method freeze found in other languages like Javascript, this package tries to make immutable objects to make it easier avoid accidental modifications in your code.
Major highlights
- freeze method creates objects with the same attributes of inputs that cannot be expanded or modified.
- Frozen object creation is thread-safe.
How it works
In case of the builtin types (int, float, str, etc) it makes nothing, as they are already immutable.
For the list type, a tuple with frozen items is returned.
Tuples are already immutable, so a new tuple with frozen items is returned.
For sets, frozensets of frozen items are returned.
For dicts, it creates a new frozendict with the keys and frozen values of the original dict.
This package, change the methods __setattr__, __delattr__, __set__, __setitem__, __delitem__, and __reversed__.
of the object argument and all of its attributed recursively, making them raise an exception if the developer tries to call them to modify the attributes of the instance.
How to use it
Freeze in the same object
from gelidum import freeze
your_frozen_object = freeze(your_object, inplace=True)
assert(id(your_frozen_object), id(your_object))
# Both raise exception
your_object.attr1 = new_value
your_frozen_object.attr1 = new_value
Freeze in a new object
Basic use
from gelidum import freeze
# inplace=False by default
your_frozen_object = freeze(your_object, inplace=False)
# It doesn't raise an exception, mutable object
your_object.attr1 = new_value
# Raises exception, immutable object
your_frozen_object.attr1 = new_value
What to do when trying to update an attribute
from gelidum import freeze
class SharedState(object):
def __init__(self, count: int):
self.count = count
shared_state = SharedState(1)
# on_update="exception": raises an exception when an update is tried
frozen_shared_state = freeze(shared_state, on_update="exception")
frozen_shared_state.count = 4 # Raises exception
# on_update="warning": shows a warning in console exception when an update is tried
frozen_shared_state = freeze(shared_state, on_update="warning")
frozen_shared_state.count = 4 # Shows a warning in console
# on_update="nothing": does nothing when an update is tried
frozen_shared_state = freeze(shared_state, on_update="nothing")
frozen_shared_state.count = 4 # Does nothing, as this update did not exist
Freeze input params
Use the decorator freeze_params to freeze the input parameters and avoid non-intended modifications:
from typing import List
from gelidum import freeze_params
@freeze_params()
def append_to_list(a_list: List, new_item: int):
a_list.append(new_item)
If freeze_params is called without arguments, all input parameters will be frozen. Otherwise, passing a set of parameters will inform the decorator of which named parameters must be frozen.
from typing import List
from gelidum import freeze_params
@freeze_params(params={"list1", "list2"})
def concat_lists_in(dest: List, list1: List, list2: List):
dest = list1 + list2
# Freeze dest, list1 and list2
concat_lists_in([], list1=[1, 2, 3], list2=[4, 5, 6])
# Freeze list1 and list2
concat_lists_in(dest=[], list1=[1, 2, 3], list2=[4, 5, 6])
Always use kwargs unless you want to freeze the args params. A good way to enforce this is by making the function have keyword-only arguments:
from typing import List
from gelidum import freeze_params
@freeze_params(params={"list1", "list2"})
def concat_lists_in(*, dest: List, list1: List, list2: List):
dest = list1 + list2
Take in account that all freezing is done in a new object (i.e. freeze with inplace=False). It makes no sense to freeze a parameter of a function that could be used later, outside said function.
Check original (i.e. "hot") class
- get_gelidum_hot_class_name: returns the name of hot class.
- get_gelidum_hot_class_module returns the module reference where the hot class was.
Limitations
- dict, list, tuple and set cannot be modified inplace although the flag inplace is set.
- file handler attributes are not supported. An exception is raised when trying to freeze an object with them
- frozen objects cannot be serialized with marshal.
Dependencies
Right now this package uses frozendict.
Roadmap
- @freeze_final decorator (use final typehints in params to freeze only those parameters).
- Include on_update: Callable. Add some examples of callables.
- Freeze only when attributes are modified?
- Include timestamp when freezing objects.
- Include some RELEASE_NOTES.md with information about each release.
- Make some use-cases with threading/async module (i.e. server)
- Add version of object when freezing.
Collaborations
This project is open to collaborations. Make a PR or an issue, and I'll take a look to it.