.. vim: set fileencoding=utf-8:
.. -*- coding: utf-8 -*-
.. +--------------------------------------------------------------------------+
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
+--------------------------------------------------------------------------+
*******************************************************************************
lockup
*******************************************************************************
⚠️ **Project Status: Unmaintained**
This project is no longer actively maintained. The replacement is `frigid
<https://pypi.org/project/frigid/>`_ which provides similar functionality
and is actively maintained.
No further updates will be provided for this package.
.. image:: https://img.shields.io/pypi/v/lockup
:alt: Project Version
:target: https://pypi.org/project/lockup/
.. image:: https://img.shields.io/pypi/status/lockup
:alt: PyPI - Status
:target: https://pypi.org/project/lockup/
.. image:: https://github.com/emcd/python-lockup/actions/workflows/tester.yaml/badge.svg?branch=master&event=push
:alt: Tests Status
:target: https://github.com/emcd/python-lockup/actions/workflows/tester.yaml
.. image:: https://codecov.io/gh/emcd/python-lockup/graph/badge.svg?token=PA9QI9RL63
:alt: Code Coverage
:target: https://codecov.io/gh/emcd/python-lockup
.. image:: https://img.shields.io/pypi/pyversions/lockup
:alt: Python Versions
:target: https://pypi.org/project/lockup/
.. image:: https://img.shields.io/pypi/l/lockup
:alt: Project License
:target: https://github.com/emcd/python-lockup/blob/master/LICENSE.txt
`API Documentation (stable)
<https://python-lockup.readthedocs.io/en/stable/api.html>`_
|
`API Documentation (current) <https://emcd.github.io/python-lockup/api.html>`_
|
`Code of Conduct
<https://emcd.github.io/python-devshim/contribution.html#code-of-conduct>`_
|
`Contribution Guide <https://emcd.github.io/python-lockup/contribution.html>`_
Overview
===============================================================================
Enables the creation of classes, modules, and namespaces on which the following
properties are true:
* All attributes are **immutable**. Immutability increases code safety by
discouraging monkey-patching and preventing changes to state, accidental or
otherwise.
.. code-block:: python
>>> import getpass
>>> def steal_password( prompt = 'Password: ', stream = None ):
... pwned = getpass.getpass( prompt = prompt, stream = stream )
... # Send host address, username, and password to Dark Web collector.
... return pwned
...
>>> import lockup
>>> lockup.reclassify_module( getpass )
>>> getpass.getpass = steal_password
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'getpass' on module 'getpass'.
.. code-block:: python
>>> import lockup
>>> ns = lockup.create_namespace( some_constant = 6 )
>>> ns.some_constant = 13
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'some_constant' on class 'lockup.Namespace'.
* Non-public attributes are **concealed**. Concealment means that the
`dir <https://docs.python.org/3/library/functions.html#dir>`_ function will
report a subset of attributes that are intended for programmers to use...
without exposing internals.
.. code-block:: python
>>> import lockup
>>> class Demo( metaclass = lockup.Class ):
... _foo = 'Semi-private class variable.'
... hello = 'Public class variable.'
... def __len__( self ): return 1
...
>>> dir( Demo )
['hello']
In addition to the above, the package also provides the ability to apprehend
"fugitive" exceptions attempting to cross API boundaries. Various auxiliary
functionalities are provided as well; these are used internally within the
package but are deemed useful enough for public consumption. Please see the
documentation for more details.
Quick Tour
===============================================================================
.. _`Class Factory`: https://python-lockup.readthedocs.io/en/stable/api.html#lockup.Class
.. _Module: https://python-lockup.readthedocs.io/en/stable/api.html#lockup.Module
.. _`Namespace Factory`: https://python-lockup.readthedocs.io/en/stable/api.html#lockup.NamespaceClass
Module_
-------------------------------------------------------------------------------
Let us consider the mutable `os <https://docs.python.org/3/library/os.html>`_
module from the Python standard library and how we can alter "constants" that
may be used in many places:
.. code-block:: python
>>> import os
>>> type( os )
<class 'module'>
>>> os.O_RDONLY
0
>>> os.O_RDONLY = os.O_RDWR
>>> os.O_RDONLY
2
>>> os.O_RDONLY = 0
Now, let us see what protection it gains from becoming immutable:
.. code-block:: python
>>> import os
>>> import lockup
>>> lockup.reclassify_module( os )
>>> type( os )
<class 'lockup.module.Module'>
>>> # How? https://docs.python.org/3/reference/datamodel.html#customizing-module-attribute-access
>>> os.O_RDONLY = os.O_RDWR
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'O_RDONLY' on module 'os'.
>>> del os.O_RDONLY
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to delete indelible attribute 'O_RDONLY' on module 'os'.
`Class Factory`_
-------------------------------------------------------------------------------
Let us monkey-patch a mutable class:
.. code-block:: python
>>> class A:
... def expected_functionality( self ): return 42
...
>>> a = A( )
>>> a.expected_functionality( )
42
>>> def monkey_patch( self ):
... return 'I selfishly change behavior upon which other consumers depend.'
...
>>> A.expected_functionality = monkey_patch
>>> a = A( )
>>> a.expected_functionality( )
'I selfishly change behavior upon which other consumers depend.'
Now, let us try to monkey-patch an immutable class:
.. code-block:: python
>>> import lockup
>>> class B( metaclass = lockup.Class ):
... def expected_functionality( self ): return 42
...
>>> b = B( )
>>> b.expected_functionality( )
42
>>> def monkey_patch( self ):
... return 'I selfishly change behavior upon which other consumers depend.'
...
>>> B.expected_functionality = monkey_patch
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'expected_functionality' on class ...
>>> del B.expected_functionality
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to delete indelible attribute 'expected_functionality' on class ...
.. note::
Only class attributes are immutable. Instances of immutable classes will
have mutable attributes without additional intervention beyond the scope of
this package.
`Namespace Factory`_
-------------------------------------------------------------------------------
An alternative to `types.SimpleNamespace
<https://docs.python.org/3/library/types.html#types.SimpleNamespace>`_ is
provided. First, let us observe the behaviors on a standard namespace:
.. code-block:: python
>>> import types
>>> sn = types.SimpleNamespace( run = lambda: 42 )
>>> sn
namespace(run=<function <lambda> at ...>)
>>> sn.run( )
42
>>> type( sn )
<class 'types.SimpleNamespace'>
>>> sn.__dict__
{'run': <function <lambda> at ...>}
>>> type( sn.run )
<class 'function'>
>>> sn.run = lambda: 666
>>> sn.run( )
666
>>> sn( ) # doctest: +SKIP
Traceback (most recent call last):
...
TypeError: 'types.SimpleNamespace' object is not callable
Now, let us compare those behaviors to an immutable namespace:
.. code-block:: python
>>> import lockup
>>> ns = lockup.create_namespace( run = lambda: 42 )
>>> ns
NamespaceClass( 'Namespace', ('object',), { ... } )
>>> ns.run( )
42
>>> type( ns )
<class 'lockup.factories.NamespaceClass'>
>>> ns.__dict__
mappingproxy({...})
>>> type( ns.run )
<class 'function'>
>>> ns.run = lambda: 666
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'run' on class 'lockup.Namespace'.
>>> ns.__dict__[ 'run' ] = lambda: 666
Traceback (most recent call last):
...
TypeError: 'mappingproxy' object does not support item assignment
>>> ns( )
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleOperation: Impermissible instantiation of class 'lockup.Namespace'.
Also of note is that we can define namespace classes directly, allowing us to
capture imports for internal use in a module without publicly exposing them as
part of the module API, for example:
.. code-block:: python
>>> import lockup
>>> class __( metaclass = lockup.NamespaceClass ):
... from os import O_RDONLY, O_RDWR
...
>>> __.O_RDONLY
0
The above technique is used internally within this package itself.
Interception
-------------------------------------------------------------------------------
If a particular exceptional condition is not anticipated in Python code, a
"fugitive" exception can escape across the boundary of a published API. If you
have told the consumers of the API that it will only emit certain classes of
exceptions, then consumers might not handle exceptions outside of the expected
classes, i.e., fugitive exceptions. If you apprehend all fugitives at the API
boundary, then you can guarantee to your consumers that they will only need to
anticipate certain classes of exceptions.
Here is an example with an interceptor, which includes fugitive exception
apprehension, that this package uses internally:
.. code-block:: python
>>> from lockup.exceptions import InvalidState
>>> from lockup.interception import our_interceptor
>>> @our_interceptor
... def divide_by_zero( number ): return number / 0
...
>>> try: divide_by_zero( 42 )
... except InvalidState as exc:
... type( exc ), type( exc.__cause__ ), str( exc )
...
(<class 'lockup.exceptions.InvalidState'>, <class 'ZeroDivisionError'>, "Apprehension of fugitive exception of class 'builtins.ZeroDivisionError' at boundary of function 'divide_by_zero' on module '__main__'.")
As can be seen, the ``ZeroDivisionError`` is in the custody of an exception
that is of an expected class.
You can create your own interceptors with custom fugitive apprehension
behaviors using the ``create_interception_decorator`` function.
Compatibility
===============================================================================
This package has been verified to work on the following Python implementations:
* `CPython <https://github.com/python/cpython>`_
- Complete functionality.
- Support for interpreters compiled with ``Py_TRACE_REFS`` definition.
* `PyPy <https://www.pypy.org/>`_
- Complete functionality except for reflection.
- Reflection is a no-op if ``assert_implementation`` is ``False``.
* `Pyston <https://www.pyston.org/>`_
- Complete functionality.
.. warning::
Support for Pyston may disappear in the future as the maintainers have
decided to invest in a JIT module for CPython rather than a separate
implementation.
It likely works on others as well, but please report if it does not.
.. TODO: https://github.com/facebookincubator/cinder
.. TODO: https://github.com/oracle/graalpython
.. TODO: https://github.com/IronLanguages/ironpython3
.. TODO: https://pyodide.org/en/stable
.. TODO: https://github.com/RustPython/RustPython
.. TODO: https://pypi.org/project/cindervm/
.. TODO: https://pypi.org/project/Cython/
.. TODO: https://mypyc.readthedocs.io/en/latest/
.. TODO: https://pypi.org/project/Nuitka/
.. TODO: https://pypi.org/project/numba/
.. TODO: https://pypi.org/project/pyston-lite/
.. TODO: https://pypi.org/project/taichi/
`More Flair <https://www.imdb.com/title/tt0151804/characters/nm0431918>`_
===============================================================================
...than the required minimum
.. image:: https://img.shields.io/github/last-commit/emcd/python-lockup
:alt: GitHub last commit
:target: https://github.com/emcd/python-lockup
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit
:alt: pre-commit
:target: https://github.com/pre-commit/pre-commit
.. image:: https://img.shields.io/badge/security-bandit-yellow.svg
:alt: Security Status
:target: https://github.com/PyCQA/bandit
.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
:alt: Static Analysis Status
:target: https://github.com/PyCQA/pylint
.. image:: https://img.shields.io/pypi/implementation/lockup
:alt: PyPI - Implementation
:target: https://pypi.org/project/lockup/
.. image:: https://img.shields.io/pypi/wheel/lockup
:alt: PyPI - Wheel
:target: https://pypi.org/project/lockup/
.. vim: set fileencoding=utf-8:
.. -*- coding: utf-8 -*-
.. +--------------------------------------------------------------------------+
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
+--------------------------------------------------------------------------+
Changelog
===============================================================================
v2.2.0
-------------------------------------------------------------------------------
Python Support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Remove PyPy 3.7 because it is no longer maintained.
v2.1.0
-------------------------------------------------------------------------------
Python Support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Remove Pyston because its maintainers have decided upon another direction.
The ``reflection`` module still recognizes Pyston, but there is no support
for this Python implementation, going forward.
* Add CPython 3.11.
v2.0.0
-------------------------------------------------------------------------------
API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* No more separate API for package-internal development. Everything is now
exposed as part of an auxiliary public API as opposed to the primary public
API.
.. note::
Some parts of the auxiliary public API may be refactored into separate
packages at a later point.
* Provide ``create_interception_decorator`` function which creates function
decorators that can apprehend "fugitive" exceptions before they escape across
the boundary of a public API. Fugitive exceptions are exceptions which are
unexpected and which should have been caught internally.
* Provide ``reassign_class_factory`` function which allows for a class to be
assigned a new factory class ("metaclass"). This can even be used on a class
factory class itself, resulting in a factory class similar to how `type
<https://docs.python.org/3/library/functions.html#type>`_ behaves. This
package uses it internally, when possible, to allow class factory classes to
enforce attribute concealment and immutability on themselves and not just
their instances. But, it can be put to other purposes too.
* Provide exception management utilities, including factories which can inject
labels into instances of a single omniexception class as an alternative to
working with a class hierarchy. This package internally uses the utilities to
create exceptions with descriptive messages and labels.
* Provide nomenclatural utilities which determine the classification of objects
that are provided to them. These are useful for the creation of more helpful
exception messages or log entries. This package internally uses the utilities
to create descritpive exception messages. A suite of exception factories,
which use these utilities, is also exposed.
* Provide validation utilities which return back their argument if the
validation is successful. Otherwise, they raise a validation error. This
allows for multiple validators to be fluently applied in succession. This
package internally uses the validators on arguments to functions that are
part of its public API.
* Provide visibility utilities which determine if an attribute is considered
public or non-public and what attributes should be concealed on an object.
This package uses the utilities internally to conceal non-public attributes
on classes, modules, and namespaces. But, they can be put to other purposes
as well.
Python Support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Remove CPython 3.6 because it is past end-of-life.
* Deprecate Pyston because of its new development direction.
* Add PyPy 3.9.
v1.1.0
-------------------------------------------------------------------------------
Documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Officially verify and mention PyPy and Pyston support.
Raw data
{
"_id": null,
"home_page": null,
"name": "lockup",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "api, attribute, concealment, immutability, namespace",
"author": null,
"author_email": "Eric McDonald <python-lockup@googlegroups.com>",
"download_url": "https://files.pythonhosted.org/packages/28/02/e109eb1dfba505dd39fa641a62a2660c7f91973be95026a6900504c5f308/lockup-2.2.0.tar.gz",
"platform": null,
"description": ".. 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\u26a0\ufe0f **Project Status: Unmaintained**\n\n This project is no longer actively maintained. The replacement is `frigid\n <https://pypi.org/project/frigid/>`_ 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&event=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<https://python-lockup.readthedocs.io/en/stable/api.html>`_\n|\n`API Documentation (current) <https://emcd.github.io/python-lockup/api.html>`_\n|\n`Code of Conduct\n<https://emcd.github.io/python-devshim/contribution.html#code-of-conduct>`_\n|\n`Contribution Guide <https://emcd.github.io/python-lockup/contribution.html>`_\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 >>> import getpass\n >>> 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 >>> import lockup\n >>> lockup.reclassify_module( getpass )\n >>> 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 >>> import lockup\n >>> ns = lockup.create_namespace( some_constant = 6 )\n >>> 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 <https://docs.python.org/3/library/functions.html#dir>`_ 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 >>> import lockup\n >>> class Demo( metaclass = lockup.Class ):\n ... _foo = 'Semi-private class variable.'\n ... hello = 'Public class variable.'\n ... def __len__( self ): return 1\n ...\n >>> 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 <https://docs.python.org/3/library/os.html>`_\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 >>> import os\n >>> type( os )\n <class 'module'>\n >>> os.O_RDONLY\n 0\n >>> os.O_RDONLY = os.O_RDWR\n >>> os.O_RDONLY\n 2\n >>> os.O_RDONLY = 0\n\nNow, let us see what protection it gains from becoming immutable:\n\n.. code-block:: python\n\n >>> import os\n >>> import lockup\n >>> lockup.reclassify_module( os )\n >>> type( os )\n <class 'lockup.module.Module'>\n >>> # How? https://docs.python.org/3/reference/datamodel.html#customizing-module-attribute-access\n >>> 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 >>> 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>>> class A:\n\t... def expected_functionality( self ): return 42\n\t...\n\t>>> a = A( )\n\t>>> a.expected_functionality( )\n\t42\n\t>>> def monkey_patch( self ):\n\t... return 'I selfishly change behavior upon which other consumers depend.'\n\t...\n\t>>> A.expected_functionality = monkey_patch\n\t>>> a = A( )\n\t>>> 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>>> import lockup\n\t>>> class B( metaclass = lockup.Class ):\n\t... def expected_functionality( self ): return 42\n\t...\n\t>>> b = B( )\n\t>>> b.expected_functionality( )\n\t42\n\t>>> def monkey_patch( self ):\n\t... return 'I selfishly change behavior upon which other consumers depend.'\n\t...\n\t>>> 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>>> 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<https://docs.python.org/3/library/types.html#types.SimpleNamespace>`_ is\nprovided. First, let us observe the behaviors on a standard namespace:\n\n.. code-block:: python\n\n\t>>> import types\n\t>>> sn = types.SimpleNamespace( run = lambda: 42 )\n\t>>> sn\n\tnamespace(run=<function <lambda> at ...>)\n\t>>> sn.run( )\n\t42\n\t>>> type( sn )\n\t<class 'types.SimpleNamespace'>\n\t>>> sn.__dict__\n\t{'run': <function <lambda> at ...>}\n\t>>> type( sn.run )\n\t<class 'function'>\n\t>>> sn.run = lambda: 666\n\t>>> sn.run( )\n\t666\n\t>>> 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 >>> import lockup\n >>> ns = lockup.create_namespace( run = lambda: 42 )\n >>> ns\n NamespaceClass( 'Namespace', ('object',), { ... } )\n >>> ns.run( )\n 42\n >>> type( ns )\n <class 'lockup.factories.NamespaceClass'>\n >>> ns.__dict__\n mappingproxy({...})\n >>> type( ns.run )\n <class 'function'>\n >>> 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 >>> ns.__dict__[ 'run' ] = lambda: 666\n Traceback (most recent call last):\n ...\n TypeError: 'mappingproxy' object does not support item assignment\n >>> 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 >>> import lockup\n >>> class __( metaclass = lockup.NamespaceClass ):\n ... from os import O_RDONLY, O_RDWR\n ...\n >>> __.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 >>> from lockup.exceptions import InvalidState\n >>> from lockup.interception import our_interceptor\n >>> @our_interceptor\n ... def divide_by_zero( number ): return number / 0\n ...\n >>> try: divide_by_zero( 42 )\n ... except InvalidState as exc:\n ... type( exc ), type( exc.__cause__ ), str( exc )\n ...\n (<class 'lockup.exceptions.InvalidState'>, <class 'ZeroDivisionError'>, \"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 <https://github.com/python/cpython>`_\n\n - Complete functionality.\n\n - Support for interpreters compiled with ``Py_TRACE_REFS`` definition.\n\n* `PyPy <https://www.pypy.org/>`_\n\n - Complete functionality except for reflection.\n\n - Reflection is a no-op if ``assert_implementation`` is ``False``.\n\n* `Pyston <https://www.pyston.org/>`_\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 <https://www.imdb.com/title/tt0151804/characters/nm0431918>`_\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\n.. 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\nChangelog\n===============================================================================\n\nv2.2.0\n-------------------------------------------------------------------------------\n\nPython Support\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Remove PyPy 3.7 because it is no longer maintained.\n\nv2.1.0\n-------------------------------------------------------------------------------\n\nPython Support\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Remove Pyston because its maintainers have decided upon another direction.\n The ``reflection`` module still recognizes Pyston, but there is no support\n for this Python implementation, going forward.\n\n* Add CPython 3.11.\n\nv2.0.0\n-------------------------------------------------------------------------------\n\nAPI\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* No more separate API for package-internal development. Everything is now\n exposed as part of an auxiliary public API as opposed to the primary public\n API.\n\n .. note::\n\n Some parts of the auxiliary public API may be refactored into separate\n packages at a later point.\n\n* Provide ``create_interception_decorator`` function which creates function\n decorators that can apprehend \"fugitive\" exceptions before they escape across\n the boundary of a public API. Fugitive exceptions are exceptions which are\n unexpected and which should have been caught internally.\n\n* Provide ``reassign_class_factory`` function which allows for a class to be\n assigned a new factory class (\"metaclass\"). This can even be used on a class\n factory class itself, resulting in a factory class similar to how `type\n <https://docs.python.org/3/library/functions.html#type>`_ behaves. This\n package uses it internally, when possible, to allow class factory classes to\n enforce attribute concealment and immutability on themselves and not just\n their instances. But, it can be put to other purposes too.\n\n* Provide exception management utilities, including factories which can inject\n labels into instances of a single omniexception class as an alternative to\n working with a class hierarchy. This package internally uses the utilities to\n create exceptions with descriptive messages and labels.\n\n* Provide nomenclatural utilities which determine the classification of objects\n that are provided to them. These are useful for the creation of more helpful\n exception messages or log entries. This package internally uses the utilities\n to create descritpive exception messages. A suite of exception factories,\n which use these utilities, is also exposed.\n\n* Provide validation utilities which return back their argument if the\n validation is successful. Otherwise, they raise a validation error. This\n allows for multiple validators to be fluently applied in succession. This\n package internally uses the validators on arguments to functions that are\n part of its public API.\n\n* Provide visibility utilities which determine if an attribute is considered\n public or non-public and what attributes should be concealed on an object.\n This package uses the utilities internally to conceal non-public attributes\n on classes, modules, and namespaces. But, they can be put to other purposes\n as well.\n\nPython Support\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Remove CPython 3.6 because it is past end-of-life.\n\n* Deprecate Pyston because of its new development direction.\n\n* Add PyPy 3.9.\n\nv1.1.0\n-------------------------------------------------------------------------------\n\nDocumentation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* Officially verify and mention PyPy and Pyston support.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Immutable and concealed attributes for classes, modules, and namespaces.",
"version": "2.2.0",
"project_urls": {
"Documentation": "https://emcd.github.io/python-lockup",
"Download": "https://pypi.org/project/lockup/#files",
"Google Group": "https://groups.google.com/g/python-lockup",
"Homepage": "https://github.com/emcd/python-lockup",
"Issue Tracker": "https://github.com/emcd/python-lockup/issues",
"Source Code": "https://github.com/emcd/python-lockup"
},
"split_keywords": [
"api",
" attribute",
" concealment",
" immutability",
" namespace"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "392e6f5320272408b1d0bb18469b2d3c2222a72570d4c039fc1d9958feed5887",
"md5": "ce73950bf85005f23fdf8b1fb27b5f36",
"sha256": "5b3fb672c7dbc7bd1abfcc600cae1b4db97d4684cf48d1dd254556550141a44e"
},
"downloads": -1,
"filename": "lockup-2.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ce73950bf85005f23fdf8b1fb27b5f36",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 33067,
"upload_time": "2024-12-17T04:45:35",
"upload_time_iso_8601": "2024-12-17T04:45:35.284400Z",
"url": "https://files.pythonhosted.org/packages/39/2e/6f5320272408b1d0bb18469b2d3c2222a72570d4c039fc1d9958feed5887/lockup-2.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "2802e109eb1dfba505dd39fa641a62a2660c7f91973be95026a6900504c5f308",
"md5": "9adb700e4e398d774e2d11379662114c",
"sha256": "f87735038c5a622a25269bec4440b8c294ba48ba7845c931ce1a824aa57e2eef"
},
"downloads": -1,
"filename": "lockup-2.2.0.tar.gz",
"has_sig": false,
"md5_digest": "9adb700e4e398d774e2d11379662114c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 28861,
"upload_time": "2024-12-17T04:45:38",
"upload_time_iso_8601": "2024-12-17T04:45:38.962463Z",
"url": "https://files.pythonhosted.org/packages/28/02/e109eb1dfba505dd39fa641a62a2660c7f91973be95026a6900504c5f308/lockup-2.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-17 04:45:38",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "emcd",
"github_project": "python-lockup",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "lockup"
}