Skip to main content

Immutable and concealed attributes for classes, modules, and namespaces.

Project description

Enables the creation of classes, modules, and namespaces on which all attributes are immutable and for which non-public attributes are concealed (visibility restriction). Immutability increases code safety by discouraging monkey-patching and preventing accidental or deliberate changes to state. Visibility restriction means that functions, like dir, can report a subset of attributes that are intended for programmers to use without exposing internals.

Contents of this package are:

  • A module class, which enforces immutability and visibility restriction upon module attributes. This module class can replace the standard Python module class with a single line of code in a module definition.

  • A factory (metaclass) that creates classes, enforcing immutability and visibility restriction upon their attributes. (Just attributes on the classes, themsleves, are immutable and visibility-restricted and not attributes on the instances of the classes.)

  • A factory that creates namespaces, enforcing immutability and visibility restriction upon their attributes.

Full Documentation: https://emcd.github.io/python-lockup/

Quick Tour

Module

Let us consider the mutable os module from the Python standard library and how we can alter “constants” that may be used in many places:

>>> import os
>>> os.EX_OK
0
>>> del os.EX_OK
>>> os.EX_OK
Traceback (most recent call last):
...
AttributeError: module 'os' has no attribute 'EX_OK'
>>> os.EX_OK = 0
>>> type( os )
<class 'module'>

Now, let us see what protection it gains from becoming immutable:

>>> import os
>>> import lockup
>>> lockup.reclassify_module( os )
>>> del os.EX_OK
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to delete indelible attribute 'EX_OK' on module 'os'.
>>> os.EX_OK = 255
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to assign immutable attribute 'EX_OK' on module 'os'.
>>> type( os )
<class 'lockup.Module'>

Class Factory

Let us monkey-patch a mutable class:

>>> import lockup
>>> 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:

>>> 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 ...
>>> type( B )
<class 'lockup.Class'>
>>> del type( B ).__setattr__
Traceback (most recent call last):
...
lockup.exceptions.ImpermissibleAttributeOperation: Attempt to delete indelible attribute '__setattr__' on class 'lockup.Class'.
>>> issubclass( type( B ), type )
True

Namespace Factory

An alternative to types.SimpleNamespace is provided. First, let us observe the behaviors on a standard namespace:

>>> import types
>>> sn = types.SimpleNamespace( run = lambda: 42 )
>>> sn
namespace(run=<function <lambda> at ...>)
>>> sn.run( )
42
>>> type( sn )
<class 'types.SimpleNamespace'>
>>> dir( sn )
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'run']
>>> sn.__dict__
{'run': <function <lambda> at ...>}
>>> type( sn.run )
<class 'function'>
>>> sn.run = lambda: 666
>>> sn.run( )
666
>>> sn( )
Traceback (most recent call last):
...
TypeError: 'types.SimpleNamespace' object is not callable

Now, let us compare those behaviors to an immutable namespace:

>>> import lockup
>>> ns = lockup.create_namespace( run = lambda: 42 )
>>> ns
NamespaceClass( 'Namespace', ('object',), { ... } )
>>> ns.run( )
42
>>> type( ns )
<class 'lockup.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:

>>> class __( metaclass = lockup.NamespaceClass ):
...     from os import O_RDONLY, O_RDWR
...
>>> __.O_RDONLY
0

The above technique is used internally within this package itself.

Exceptions

Exceptions can be intercepted with appropriate builtin exception classes or with package exception classes:

>>> import os
>>> import lockup
>>> from lockup.exceptions import InvalidOperation
>>> os.O_RDONLY
0
>>> lockup.reclassify_module( os )
>>> try: os.O_RDONLY = 15
... except AttributeError as exc:
...     type( exc ).mro( )
...
[<class 'lockup.exceptions.ImpermissibleAttributeOperation'>, <class 'lockup.exceptions.ImpermissibleOperation'>, <class 'lockup.exceptions.InvalidOperation'>, <class 'lockup.exceptions.Exception0'>, <class 'TypeError'>, <class 'AttributeError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>]
>>> try: os.does_not_exist
... except InvalidOperation as exc:
...     type( exc ).mro( )
...
[<class 'lockup.exceptions.InaccessibleAttribute'>, <class 'lockup.exceptions.InaccessibleEntity'>, <class 'lockup.exceptions.InvalidOperation'>, <class 'lockup.exceptions.Exception0'>, <class 'AttributeError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>]

Changelog

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lockup-1.0rc7.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

lockup-1.0rc7-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

Details for the file lockup-1.0rc7.tar.gz.

File metadata

  • Download URL: lockup-1.0rc7.tar.gz
  • Upload date:
  • Size: 18.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.6.0 importlib_metadata/4.8.2 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.6.15

File hashes

Hashes for lockup-1.0rc7.tar.gz
Algorithm Hash digest
SHA256 bb4eee07d3f39db8ff9cdf2fc09bef0bd4fadd1a3fb0d31f4aaebbbd7fb40eb5
MD5 9edbb39467864a1b179aa0b50040fb0b
BLAKE2b-256 1dd4bc9aa73b31e9bf1c8835be321107da7994c77f32bb46a3a159321b6b4f9c

See more details on using hashes here.

File details

Details for the file lockup-1.0rc7-py3-none-any.whl.

File metadata

  • Download URL: lockup-1.0rc7-py3-none-any.whl
  • Upload date:
  • Size: 16.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.6.0 importlib_metadata/4.8.2 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.6.15

File hashes

Hashes for lockup-1.0rc7-py3-none-any.whl
Algorithm Hash digest
SHA256 9e21cd84c40f77ec0ed7887cab34dfe6bdbc01dca742edc5ba84455f3ef2db8e
MD5 825b66a0c810237afa85e73bcfb26ab4
BLAKE2b-256 4b9a9d3e4d0165e3dbaac2c5a20b611c4e82ec0251d138ba117091b217ac6378

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page