Metadata-Version: 2.1
Name: lockup
Version: 1.0.2
Summary: Immutable and concealed attributes for classes, modules, and namespaces.
Home-page: https://github.com/emcd/python-lockup
Author: Eric McDonald
Author-email: python-lockup@googlegroups.com
Maintainer: Eric McDonald
Maintainer-email: python-lockup@googlegroups.com
License: Apache-2.0
Project-URL: Documentation, https://emcd.github.io/python-lockup
Project-URL: Source Code, https://github.com/emcd/python-lockup
Project-URL: Google Group, https://groups.google.com/g/python-lockup
Keywords: api attribute concealment immutability namespace
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Software Development
Description-Content-Type: text/x-rst
License-File: LICENSE.txt

.. 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
*******************************************************************************

.. image:: https://img.shields.io/pypi/pyversions/lockup
   :alt: Python Versions

.. image:: https://img.shields.io/pypi/v/lockup
   :alt: Project Version
   :target: https://pypi.org/project/lockup/

.. image:: https://github.com/emcd/python-lockup/actions/workflows/test.yaml/badge.svg?branch=master&event=push
   :alt: Tests Status
   :target: https://github.com/emcd/python-lockup/actions/workflows/test.yaml

.. image:: https://codecov.io/gh/emcd/python-lockup/branch/master/graph/badge.svg?token=PA9QI9RL63
   :target: https://app.codecov.io/gh/emcd/python-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-lockup/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 all
attributes are **immutable** and for which non-public attributes are
**concealed**. Immutability increases code safety by discouraging
monkey-patching and preventing accidental or deliberate changes to state.
Concealment means that functions, such as `dir
<https://docs.python.org/3/library/functions.html#dir>`_, can report a subset
of attributes that are intended for programmers to use without exposing
internals.

Salient Features
-------------------------------------------------------------------------------

* A module class, which enforces immutability and concealment 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
  concealment upon their attributes. (Just attributes on the classes,
  themsleves, are immutable and concealed and not attributes on the instances
  of the classes.)

* A factory that creates namespaces, enforcing immutability and concealment
  upon their attributes.

Quick Tour
===============================================================================

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:

	>>> 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
<https://docs.python.org/3/library/types.html#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'>]

.. 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
===============================================================================

v1.0.2
-------------------------------------------------------------------------------

Documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Provide direct link in README to stable API documentation.

v1.0.1
-------------------------------------------------------------------------------

Documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Improve wording of introduction in README.
* Add badges in README for supported Python versions, current release, code
  coverage, and license.
* Provide direct links in README to current API documentation, code of conduct,
  and contribution guide.


