Freeze your objects in python

Overview

gelidum

License Maintenance made-with-python PyPI pyversions PyPI version gelidum PyPI status Maintainability Test Coverage

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.

License

MIT

Comments
  • Non-free bits in release

    Non-free bits in release

    I am currently trying to package gelidum for fedora, however I noticed that the package is non-free, as it contains a CC BY-NC 2.0 image.

    This is listed in fedora license list as a non-acceptable license. While I could remove the image always from the release, not being forced to do this would be greatly appreciated.

    The NC part might also violate the PYPI terms of use but I am not a lawyer ...

    opened by dschwoerer 6
  • frozendict is unable to be instantiated with an iterator or generator

    frozendict is unable to be instantiated with an iterator or generator

    https://github.com/diegojromerolopez/gelidum/blob/850c061395ea2d2a04193b236fd3862092faa7df/gelidum/collections/frozendict.py#L26

    The __init__ method of frozendict assumes that the class will only ever be called with a parameter of subtype collections.abc.Mapping (has an items() method).

    An iteratable/iterator or generator type parameter should be handled as well. Something along the lines of:

    if isinstance(mapping, Mapping):
        super().__init__(
            {key: freeze_func(value) for key, value in mapping.items()}
        )
    else:
        super().__init__(
            {key: freeze_func(value) for key, value in mapping}
        )
    

    I'm not up on typing yet, so am hesitant to make a pull request.

    opened by nolsto 4
  • Make available on conda-forge?

    Make available on conda-forge?

    It would be nice to make gelidum available through conda-forge - I personally prefer to manage the dependencies of my own package with conda, and would like gelidum to be available there so I can use it easily. I'm happy to set up the 'feedstock'. Maintenance after that is usually pretty easy. When a new version appears on PyPi, conda-forge will pick it up and automatically create an update. A line or two might need editing if the dependencies change, but other than that you just need to click 'merge' to release the new version on conda-forge.

    @diegojromerolopez would it be OK for me to go ahead and add gelidum to conda-forge?

    opened by johnomotani 4
  • Fix frozendict __init__ method

    Fix frozendict __init__ method

    Allow creation of frozendict by passing a generator or kwargs.

    Requested and devised by nolsto in https://github.com/diegojromerolopez/gelidum/issues/28.

    opened by diegojromerolopez 2
  • Move 'tests' package to be a sub-package of 'gelidum'

    Move 'tests' package to be a sub-package of 'gelidum'

    At the moment, when gelidum is pip-installed, it also installs a package called tests, because the tests subdirectory is picked up automatically by find_packages() in setup.py. This is not best practice, because it means after gelidum is pip-installed, running import tests in Python will successfully import a package which sounds generic, but actually only contains gelidum's tests.

    I think the nicest solution to this is to move the tests to a sub-package (by moving tests to gelidum/tests), as proposed in this PR. This means the tests are installed with gelidum, so the installed package can be tested, by running python -m unittest discover -v gelidum (which as it happens would be helpful for the conda-forge set-up #15). An alternative would be to exclude the tests directory from the install (something like find_packages(exclude=["tests"])).

    Fingers crossed I didn't break the CI with this change...

    opened by johnomotani 2
  • Artwork for project

    Artwork for project

    This project needs a logo and a header image. Gelidum means "frozen" in Latin (the language of the Ancient Romans).

    Any artist that would like to make a simple logo is greatly appreciated. License of the artwork must be Creative Commons with attribution or Public Domain.

    If there is any CC or Public Domain artwork that could match the aim of this project, a comment pointing to it is also appreciated.

    help wanted good first issue artwork 
    opened by diegojromerolopez 1
  • New module: on_freeze

    New module: on_freeze

    This PR includes adds more fine-grained control of the freezing process. To be more precise, to the on_freeze function. There are two main on_freeze posibilities: deep-copy and inplace (pass the reference directly). There was another option: passing a callable. This PR adds some basing callable classes to allow tracking what objects are going to be frozen.

    By creating a OnFreezeOriginalObjTracker object, it is possible to store a reference to the original object before being frozen.

    The following test test_on_freeze_original_obj_tracking is a good example of this use-case:

    import logging
    import unittest
    from unittest import mock
    from typing import List, Any
    from unittest.mock import call
    from gelidum import OnFreezeCopier, OnFreezeOriginalObjTracker, OnFreezeIdentityFunc
    from gelidum import freeze
    from gelidum.frozen import FrozenBase, clear_frozen_classes
    
    
    class TestOnFreeze(unittest.TestCase):
    
        def setUp(self) -> None:
            clear_frozen_classes()
    
        def test_on_freeze_original_obj_tracking(self):
            class DummyAttr1(object):
                def __init__(self, value: int):
                    self.attr = value
    
            class DummyAttr2(object):
                def __init__(self, value: int):
                    self.attr = value
    
            class Dummy(object):
                def __init__(self, value1: int, value2: int):
                    self.attr1 = DummyAttr1(value1)
                    self.attr2 = DummyAttr2(value2)
    
            dummy1 = Dummy(value1=1, value2=2)
    
            freezer = OnFreezeOriginalObjTracker()
    
            frozen_dummy1 = freeze(dummy1, on_freeze=freezer)
            original_obj = freezer.original_obj
    
            self.assertEqual(id(dummy1), id(original_obj))
            self.assertNotEqual(id(dummy1), id(frozen_dummy1))
    
    opened by diegojromerolopez 0
  • Minor refactor: break frozen module in small modules

    Minor refactor: break frozen module in small modules

    • Break frozen module in small modules.
    • Prepare .gitignore for graalpython support.
    • Make an exception when a class with __slots__ is passed as argument.
    opened by diegojromerolopez 0
  • No numpy support?

    No numpy support?

    Dear @diegojromerolopez I noticed that your code does not support numpy instances.

    import numpy as np
    data = np.empty(10)
    frozen_data = freeze(data)
    

    results in

    Traceback (most recent call last):
      File "/home/volker/workspace/PROGRAMS/pycharm-2021.2.2/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
        pydev_imports.execfile(file, globals, locals)  # execute the script
      File "/home/volker/workspace/PROGRAMS/pycharm-2021.2.2/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
        exec(compile(contents+"\n", file, 'exec'), glob, loc)
      File "/home/volker/workspace/PYTHON4/ELDAmwl/src/ELDAmwl/sandbox/freezing.py", line 31, in <module>
        frozen_dummy1 = freeze(data)
      File "/home/volker/workspace/PYTHON4/gelidum/gelidum/freeze.py", line 41, in freeze
        return __freeze(obj=obj, on_update=on_update_func, on_freeze=on_freeze_func)
      File "/home/volker/workspace/PYTHON4/gelidum/gelidum/freeze.py", line 61, in __freeze
        return __freeze_object(obj, on_update=on_update, on_freeze=on_freeze)
      File "/home/volker/workspace/PYTHON4/gelidum/gelidum/freeze.py", line 113, in __freeze_object
        for attr, value in frozen_obj.__dict__.items():
    AttributeError: 'numpy.ndarray' object has no attribute '__dict__'
    
    

    I think that this feature is not hard to implement since with

    array.flags.writeable=False

    a readonly functionality is already available for numpy objects.

    If you like I may come up with PR for that?

    Probably some kind of plugin structure would be helpful to support the freezing of arbitrary data structures.

    Cheers, Volker

    opened by volkerjaenisch 4
  • Is there a way to unfreeze a frozen instance?

    Is there a way to unfreeze a frozen instance?

    Dear @diegojromerolopez

    Thank you for this nice piece of software! I need a frozen instance that I later can make a unfrozen (deep-)copy of. This is used for IMHO typical (scientific) data processing chains:

    1. Calculate a instance of an intermediate result A and store it frozen (e.g. load a dataset)
    2. Calculate a result B that depend on A but shares the same metadata (e.g. b = filter(A), or B= smooth(A))
    3. Store B frozen ...

    In your documentation you stated that (deep-)copy of a frozen instance is not possible.

    So I see two ways to achieve this goal:

    1. There may be a way to unfreeze a frozen instance?
    2. There may be a way to access the original instance below the frozen structural sharing?

    Any help appreciated

    Cheers, Volker

    opened by volkerjaenisch 7
  • Documentation

    Documentation

    While this project is easy to use and some documentation can be found in the README.md file, it needs some documentation.

    Maybe create the folder docs and use sphinx. Thus, upload to readthedocs.io

    Some use-cases and examples would be good too.

    documentation help wanted good first issue 
    opened by diegojromerolopez 0
Owner
Diego J.
Software Engineer working at Telefonica always learning new things.
Diego J.
Py4J enables Python programs to dynamically access arbitrary Java objects

Py4J Py4J enables Python programs running in a Python interpreter to dynamically access Java objects in a Java Virtual Machine. Methods are called as

Barthelemy Dagenais 1k Jan 2, 2023
Utility to play with ADCS, allows to request tickets and collect information about related objects

certi Utility to play with ADCS, allows to request tickets and collect information about related objects. Basically, it's the impacket copy of Certify

Eloy 185 Dec 29, 2022
py-js: python3 objects for max

Simple (and extensible) python3 externals for MaxMSP

Shakeeb Alireza 39 Nov 20, 2022
A way to write regex with objects instead of strings.

Py Idiomatic Regex (AKA iregex) Documentation Available Here An easier way to write regex in Python using OOP instead of strings. Makes the code much

Ryan Peach 18 Nov 15, 2021
Kivy program for identification & rotation sensing of objects on multi-touch tables.

ObjectViz ObjectViz is a multitouch object detection solution, enabling you to create physical markers out of any reliable multitouch solution. It's e

TangibleDisplay 8 Apr 4, 2022
dynamically create __slots__ objects with less code

slots_factory Factory functions and decorators for creating slot objects Slots are a python construct that allows users to create an object that doesn

Michael Green 2 Sep 7, 2021
TB Set color display - Add-on for Blender to set multiple objects and material Display Color at once.

TB_Set_color_display Add-on for Blender with operations to transfer name between object, data, materials and action names Set groups of object's or ma

null 1 Jun 1, 2022
🗽 Like yarn outdated/upgrade, but for pip. Upgrade all your pip packages and automate your Python Dependency Management.

pipupgrade The missing command for pip Table of Contents Features Quick Start Usage Basic Usage Docker Environment Variables FAQ License Features Upda

Achilles Rasquinha 529 Dec 31, 2022
A python tool that creates issues in your repos based on TODO comments in your code

Krypto A neat little sidekick python script to create issues on your repo based on comments left in the code on your behalf Convert todo comments in y

Alex Antoniou 4 Oct 26, 2021
edgetest is a tox-inspired python library that will loop through your project's dependencies, and check if your project is compatible with the latest version of each dependency

Bleeding edge dependency testing Full Documentation edgetest is a tox-inspired python library that will loop through your project's dependencies, and

Capital One 16 Dec 7, 2022
Purge your likes and wall comments from VKontakte. Set yourself free from your digital footprint.

vk_liberator Regain liberty in the cruel social media world. This program assists you with purging your metadata from Russian social network VKontakte

null 20 Jun 11, 2021
switching computer? changing your setup? You need to automate the download of your current setup? This is the right tool for you :incoming_envelope:

?? setup_shift(SS.py) switching computer? changing your setup? You need to automate the download of your current setup? This is the right tool for you

Mohamed Elfaleh 15 Aug 26, 2022
Add your recently blog and douban states in your GitHub Profile

Add your recently blog and douban states in your GitHub Profile

Bingjie Yan 4 Dec 12, 2022
An app that mirrors your phone to your compute and maps controller input to the screen

What is 'Dragalia Control'? An app that mirrors your phone to your compute and maps controller input to the screen. Inputs are mapped specifically for

null 1 May 3, 2022
An assistant to guess your pip dependencies from your code, without using a requirements file.

Pip Sala Bim is an assistant to guess your pip dependencies from your code, without using a requirements file. Pip Sala Bim will tell you which packag

Collage Labs 15 Nov 19, 2022
Helper to organize your windows on your desktop.

The script of positionsing windows on the screen. How does it work? Select your window to move/res

Andrii D. 1 Jul 9, 2021
Its a simple and fun to use application. You can make your own quizes and send the lik of the quiz to your friends.

Quiz Application Its a simple and fun to use application. You can make your own quizes and send the lik of the quiz to your friends. When they would a

Atharva Parkhe 1 Feb 23, 2022
Display your data in an attractive way in your notebook!

Bloxs Bloxs is a simple python package that helps you display information in an attractive way (formed in blocks). Perfect for building dashboards, re

MLJAR 192 Dec 28, 2022
Python Projects is an Open Source to enhance your python skills

Welcome! ???? Python Project is Open Source to enhance your python skills. You're free to contribute. ?? You just need to give us your scripts written

Tristán 6 Nov 28, 2022