{"id":26246274,"url":"https://github.com/emcd/python-lockup","last_synced_at":"2025-03-13T13:17:47.607Z","repository":{"id":38233496,"uuid":"428091224","full_name":"emcd/python-lockup","owner":"emcd","description":"Immutable and concealed attributes for Python classes, modules, and namespaces.","archived":false,"fork":false,"pushed_at":"2024-10-24T01:47:50.000Z","size":3531,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-24T17:49:32.258Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emcd.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-15T01:56:56.000Z","updated_at":"2024-09-15T22:11:18.000Z","dependencies_parsed_at":"2024-02-08T02:26:58.412Z","dependency_job_id":"b1865180-875f-4d66-b83a-f068ac10af8f","html_url":"https://github.com/emcd/python-lockup","commit_stats":{"total_commits":309,"total_committers":1,"mean_commits":309.0,"dds":0.0,"last_synced_commit":"d77241d2daa20b807eaaa34f6209c202b6a5b9e5"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emcd%2Fpython-lockup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emcd%2Fpython-lockup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emcd%2Fpython-lockup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emcd%2Fpython-lockup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emcd","download_url":"https://codeload.github.com/emcd/python-lockup/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243410450,"owners_count":20286403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-13T13:17:46.928Z","updated_at":"2025-03-13T13:17:47.601Z","avatar_url":"https://github.com/emcd.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":".. vim: set fileencoding=utf-8:\n.. -*- coding: utf-8 -*-\n.. +--------------------------------------------------------------------------+\n   |                                                                          |\n   | Licensed under the Apache License, Version 2.0 (the \"License\");          |\n   | you may not use this file except in compliance with the License.         |\n   | You may obtain a copy of the License at                                  |\n   |                                                                          |\n   |     http://www.apache.org/licenses/LICENSE-2.0                           |\n   |                                                                          |\n   | Unless required by applicable law or agreed to in writing, software      |\n   | distributed under the License is distributed on an \"AS IS\" BASIS,        |\n   | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |\n   | See the License for the specific language governing permissions and      |\n   | limitations under the License.                                           |\n   |                                                                          |\n   +--------------------------------------------------------------------------+\n\n*******************************************************************************\n                                    lockup\n*******************************************************************************\n\n\n\n⚠️ **Project Status: Unmaintained**\n\n    This project is no longer actively maintained. The replacement is `frigid\n    \u003chttps://pypi.org/project/frigid/\u003e`_ which provides similar functionality\n    and is actively maintained.\n\n    No further updates will be provided for this package.\n\n\n\n.. image:: https://img.shields.io/pypi/v/lockup\n   :alt: Project Version\n   :target: https://pypi.org/project/lockup/\n\n.. image:: https://img.shields.io/pypi/status/lockup\n   :alt: PyPI - Status\n   :target: https://pypi.org/project/lockup/\n\n.. image:: https://github.com/emcd/python-lockup/actions/workflows/tester.yaml/badge.svg?branch=master\u0026event=push\n   :alt: Tests Status\n   :target: https://github.com/emcd/python-lockup/actions/workflows/tester.yaml\n\n.. image:: https://codecov.io/gh/emcd/python-lockup/graph/badge.svg?token=PA9QI9RL63\n   :alt: Code Coverage\n   :target: https://codecov.io/gh/emcd/python-lockup\n\n.. image:: https://img.shields.io/pypi/pyversions/lockup\n   :alt: Python Versions\n   :target: https://pypi.org/project/lockup/\n\n.. image:: https://img.shields.io/pypi/l/lockup\n   :alt: Project License\n   :target: https://github.com/emcd/python-lockup/blob/master/LICENSE.txt\n\n`API Documentation (stable)\n\u003chttps://python-lockup.readthedocs.io/en/stable/api.html\u003e`_\n|\n`API Documentation (current) \u003chttps://emcd.github.io/python-lockup/api.html\u003e`_\n|\n`Code of Conduct\n\u003chttps://emcd.github.io/python-devshim/contribution.html#code-of-conduct\u003e`_\n|\n`Contribution Guide \u003chttps://emcd.github.io/python-lockup/contribution.html\u003e`_\n\nOverview\n===============================================================================\n\nEnables the creation of classes, modules, and namespaces on which the following\nproperties are true:\n\n* All attributes are **immutable**. Immutability increases code safety by\n  discouraging monkey-patching and preventing changes to state, accidental or\n  otherwise.\n\n  .. code-block:: python\n\n    \u003e\u003e\u003e import getpass\n    \u003e\u003e\u003e def steal_password( prompt = 'Password: ', stream = None ):\n    ...     pwned = getpass.getpass( prompt = prompt, stream = stream )\n    ...     # Send host address, username, and password to Dark Web collector.\n    ...     return pwned\n    ...\n    \u003e\u003e\u003e import lockup\n    \u003e\u003e\u003e lockup.reclassify_module( getpass )\n    \u003e\u003e\u003e getpass.getpass = steal_password\n    Traceback (most recent call last):\n    ...\n    lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'getpass' on module 'getpass'.\n\n  .. code-block:: python\n\n    \u003e\u003e\u003e import lockup\n    \u003e\u003e\u003e ns = lockup.create_namespace( some_constant = 6 )\n    \u003e\u003e\u003e ns.some_constant = 13\n    Traceback (most recent call last):\n    ...\n    lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'some_constant' on class 'lockup.Namespace'.\n\n* Non-public attributes are **concealed**. Concealment means that the\n  `dir \u003chttps://docs.python.org/3/library/functions.html#dir\u003e`_ function will\n  report a subset of attributes that are intended for programmers to use...\n  without exposing internals.\n\n  .. code-block:: python\n\n    \u003e\u003e\u003e import lockup\n    \u003e\u003e\u003e class Demo( metaclass = lockup.Class ):\n    ...     _foo = 'Semi-private class variable.'\n    ...     hello = 'Public class variable.'\n    ...     def __len__( self ): return 1\n    ...\n    \u003e\u003e\u003e dir( Demo )\n    ['hello']\n\nIn addition to the above, the package also provides the ability to apprehend\n\"fugitive\" exceptions attempting to cross API boundaries. Various auxiliary\nfunctionalities are provided as well; these are used internally within the\npackage but are deemed useful enough for public consumption. Please see the\ndocumentation for more details.\n\nQuick Tour\n===============================================================================\n\n.. _`Class Factory`: https://python-lockup.readthedocs.io/en/stable/api.html#lockup.Class\n.. _Module: https://python-lockup.readthedocs.io/en/stable/api.html#lockup.Module\n.. _`Namespace Factory`: https://python-lockup.readthedocs.io/en/stable/api.html#lockup.NamespaceClass\n\nModule_\n-------------------------------------------------------------------------------\n\nLet us consider the mutable `os \u003chttps://docs.python.org/3/library/os.html\u003e`_\nmodule from the Python standard library and how we can alter \"constants\" that\nmay be used in many places:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e import os\n    \u003e\u003e\u003e type( os )\n    \u003cclass 'module'\u003e\n    \u003e\u003e\u003e os.O_RDONLY\n    0\n    \u003e\u003e\u003e os.O_RDONLY = os.O_RDWR\n    \u003e\u003e\u003e os.O_RDONLY\n    2\n    \u003e\u003e\u003e os.O_RDONLY = 0\n\nNow, let us see what protection it gains from becoming immutable:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e import os\n    \u003e\u003e\u003e import lockup\n    \u003e\u003e\u003e lockup.reclassify_module( os )\n    \u003e\u003e\u003e type( os )\n    \u003cclass 'lockup.module.Module'\u003e\n    \u003e\u003e\u003e # How? https://docs.python.org/3/reference/datamodel.html#customizing-module-attribute-access\n    \u003e\u003e\u003e os.O_RDONLY = os.O_RDWR\n    Traceback (most recent call last):\n    ...\n    lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'O_RDONLY' on module 'os'.\n    \u003e\u003e\u003e del os.O_RDONLY\n    Traceback (most recent call last):\n    ...\n    lockup.exceptions.ImpermissibleAttributeOperation: Attempt to delete indelible attribute 'O_RDONLY' on module 'os'.\n\n`Class Factory`_\n-------------------------------------------------------------------------------\n\nLet us monkey-patch a mutable class:\n\n.. code-block:: python\n\n\t\u003e\u003e\u003e class A:\n\t...     def expected_functionality( self ): return 42\n\t...\n\t\u003e\u003e\u003e a = A( )\n\t\u003e\u003e\u003e a.expected_functionality( )\n\t42\n\t\u003e\u003e\u003e def monkey_patch( self ):\n\t...     return 'I selfishly change behavior upon which other consumers depend.'\n\t...\n\t\u003e\u003e\u003e A.expected_functionality = monkey_patch\n\t\u003e\u003e\u003e a = A( )\n\t\u003e\u003e\u003e a.expected_functionality( )\n\t'I selfishly change behavior upon which other consumers depend.'\n\nNow, let us try to monkey-patch an immutable class:\n\n.. code-block:: python\n\n\t\u003e\u003e\u003e import lockup\n\t\u003e\u003e\u003e class B( metaclass = lockup.Class ):\n\t...     def expected_functionality( self ): return 42\n\t...\n\t\u003e\u003e\u003e b = B( )\n\t\u003e\u003e\u003e b.expected_functionality( )\n\t42\n\t\u003e\u003e\u003e def monkey_patch( self ):\n\t...     return 'I selfishly change behavior upon which other consumers depend.'\n\t...\n\t\u003e\u003e\u003e B.expected_functionality = monkey_patch\n\tTraceback (most recent call last):\n\t...\n\tlockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'expected_functionality' on class ...\n\t\u003e\u003e\u003e del B.expected_functionality\n\tTraceback (most recent call last):\n\t...\n\tlockup.exceptions.ImpermissibleAttributeOperation: Attempt to delete indelible attribute 'expected_functionality' on class ...\n\n.. note::\n   Only class attributes are immutable. Instances of immutable classes will\n   have mutable attributes without additional intervention beyond the scope of\n   this package.\n\n`Namespace Factory`_\n-------------------------------------------------------------------------------\n\nAn alternative to `types.SimpleNamespace\n\u003chttps://docs.python.org/3/library/types.html#types.SimpleNamespace\u003e`_ is\nprovided. First, let us observe the behaviors on a standard namespace:\n\n.. code-block:: python\n\n\t\u003e\u003e\u003e import types\n\t\u003e\u003e\u003e sn = types.SimpleNamespace( run = lambda: 42 )\n\t\u003e\u003e\u003e sn\n\tnamespace(run=\u003cfunction \u003clambda\u003e at ...\u003e)\n\t\u003e\u003e\u003e sn.run( )\n\t42\n\t\u003e\u003e\u003e type( sn )\n\t\u003cclass 'types.SimpleNamespace'\u003e\n\t\u003e\u003e\u003e sn.__dict__\n\t{'run': \u003cfunction \u003clambda\u003e at ...\u003e}\n\t\u003e\u003e\u003e type( sn.run )\n\t\u003cclass 'function'\u003e\n\t\u003e\u003e\u003e sn.run = lambda: 666\n\t\u003e\u003e\u003e sn.run( )\n\t666\n\t\u003e\u003e\u003e sn( )  # doctest: +SKIP\n\tTraceback (most recent call last):\n\t...\n\tTypeError: 'types.SimpleNamespace' object is not callable\n\nNow, let us compare those behaviors to an immutable namespace:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e import lockup\n    \u003e\u003e\u003e ns = lockup.create_namespace( run = lambda: 42 )\n    \u003e\u003e\u003e ns\n    NamespaceClass( 'Namespace', ('object',), { ... } )\n    \u003e\u003e\u003e ns.run( )\n    42\n    \u003e\u003e\u003e type( ns )\n    \u003cclass 'lockup.factories.NamespaceClass'\u003e\n    \u003e\u003e\u003e ns.__dict__\n    mappingproxy({...})\n    \u003e\u003e\u003e type( ns.run )\n    \u003cclass 'function'\u003e\n    \u003e\u003e\u003e ns.run = lambda: 666\n    Traceback (most recent call last):\n    ...\n    lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'run' on class 'lockup.Namespace'.\n    \u003e\u003e\u003e ns.__dict__[ 'run' ] = lambda: 666\n    Traceback (most recent call last):\n    ...\n    TypeError: 'mappingproxy' object does not support item assignment\n    \u003e\u003e\u003e ns( )\n    Traceback (most recent call last):\n    ...\n    lockup.exceptions.ImpermissibleOperation: Impermissible instantiation of class 'lockup.Namespace'.\n\nAlso of note is that we can define namespace classes directly, allowing us to\ncapture imports for internal use in a module without publicly exposing them as\npart of the module API, for example:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e import lockup\n    \u003e\u003e\u003e class __( metaclass = lockup.NamespaceClass ):\n    ...     from os import O_RDONLY, O_RDWR\n    ...\n    \u003e\u003e\u003e __.O_RDONLY\n    0\n\nThe above technique is used internally within this package itself.\n\nInterception\n-------------------------------------------------------------------------------\n\nIf a particular exceptional condition is not anticipated in Python code, a\n\"fugitive\" exception can escape across the boundary of a published API. If you\nhave told the consumers of the API that it will only emit certain classes of\nexceptions, then consumers might not handle exceptions outside of the expected\nclasses, i.e., fugitive exceptions. If you apprehend all fugitives at the API\nboundary, then you can guarantee to your consumers that they will only need to\nanticipate certain classes of exceptions.\n\nHere is an example with an interceptor, which includes fugitive exception\napprehension, that this package uses internally:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e from lockup.exceptions import InvalidState\n    \u003e\u003e\u003e from lockup.interception import our_interceptor\n    \u003e\u003e\u003e @our_interceptor\n    ... def divide_by_zero( number ): return number / 0\n    ...\n    \u003e\u003e\u003e try: divide_by_zero( 42 )\n    ... except InvalidState as exc:\n    ...     type( exc ), type( exc.__cause__ ), str( exc )\n    ...\n    (\u003cclass 'lockup.exceptions.InvalidState'\u003e, \u003cclass 'ZeroDivisionError'\u003e, \"Apprehension of fugitive exception of class 'builtins.ZeroDivisionError' at boundary of function 'divide_by_zero' on module '__main__'.\")\n\nAs can be seen, the ``ZeroDivisionError`` is in the custody of an exception\nthat is of an expected class.\n\nYou can create your own interceptors with custom fugitive apprehension\nbehaviors using the ``create_interception_decorator`` function.\n\nCompatibility\n===============================================================================\n\nThis package has been verified to work on the following Python implementations:\n\n* `CPython \u003chttps://github.com/python/cpython\u003e`_\n\n  - Complete functionality.\n\n  - Support for interpreters compiled with ``Py_TRACE_REFS`` definition.\n\n* `PyPy \u003chttps://www.pypy.org/\u003e`_\n\n  - Complete functionality except for reflection.\n\n  - Reflection is a no-op if ``assert_implementation`` is ``False``.\n\n* `Pyston \u003chttps://www.pyston.org/\u003e`_\n\n  - Complete functionality.\n\n  .. warning::\n\n     Support for Pyston may disappear in the future as the maintainers have\n     decided to invest in a JIT module for CPython rather than a separate\n     implementation.\n\nIt likely works on others as well, but please report if it does not.\n\n.. TODO: https://github.com/facebookincubator/cinder\n.. TODO: https://github.com/oracle/graalpython\n.. TODO: https://github.com/IronLanguages/ironpython3\n.. TODO: https://pyodide.org/en/stable\n.. TODO: https://github.com/RustPython/RustPython\n\n.. TODO: https://pypi.org/project/cindervm/\n.. TODO: https://pypi.org/project/Cython/\n.. TODO: https://mypyc.readthedocs.io/en/latest/\n.. TODO: https://pypi.org/project/Nuitka/\n.. TODO: https://pypi.org/project/numba/\n.. TODO: https://pypi.org/project/pyston-lite/\n.. TODO: https://pypi.org/project/taichi/\n\n`More Flair \u003chttps://www.imdb.com/title/tt0151804/characters/nm0431918\u003e`_\n===============================================================================\n...than the required minimum\n\n.. image:: https://img.shields.io/github/last-commit/emcd/python-lockup\n   :alt: GitHub last commit\n   :target: https://github.com/emcd/python-lockup\n\n.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit\n   :alt: pre-commit\n   :target: https://github.com/pre-commit/pre-commit\n\n.. image:: https://img.shields.io/badge/security-bandit-yellow.svg\n   :alt: Security Status\n   :target: https://github.com/PyCQA/bandit\n\n.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen\n   :alt: Static Analysis Status\n   :target: https://github.com/PyCQA/pylint\n\n.. image:: https://img.shields.io/pypi/implementation/lockup\n   :alt: PyPI - Implementation\n   :target: https://pypi.org/project/lockup/\n\n.. image:: https://img.shields.io/pypi/wheel/lockup\n   :alt: PyPI - Wheel\n   :target: https://pypi.org/project/lockup/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femcd%2Fpython-lockup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femcd%2Fpython-lockup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femcd%2Fpython-lockup/lists"}