Protect class attributes in any python object instance
BSD-3-CLAUSE License
pyprotect is a python module that provides API to restrict visibility or mutability of selected Python object attributes in a robust manner.
The key functions in the pyprotect module API - private() and protect() wrap the python object (like a Proxy) to restrict visibility or mutability of selected attributes of the wrapped object, while allowing the wrapping object to behave virtually identical to the wrapped object.
freeze(o: object) -> Frozen:
Object returned prevents modification of ANY attribute
private(o: object, frozen: bool = False) -> object:
protect(
o: object frozen: bool = False,
dynamic: bool = True,
hide_private: bool = False,
ro_data: bool = False,
ro_method: bool = True,
ro: List[str] = [],
rw: List[str] = [],
hide: List[str] = []
) -> object:
# o-->object to be wrapped
Returns-->Instance of FrozenProtected if frozen; Instance of Protected otherwise
If protect() is called on an object 'o' that is an instance of Protected, protect() will merge the protect() rules, enforcing the most restrictive combination among the two sets of protect() options:
In short, by calling protect() a second time (or multiple times):
Options: protect method arguments
Option | Type | Default | Description | Overrides |
---|---|---|---|---|
frozen | bool | False | If True, no attributes can be changed, added or deleted | |
hide_private | bool | False | If True, private vars of the form _var will be hidden |
|
ro_data | bool | False | Data (non-method) attributes will be immutableCan override selectively with rw | |
ro_method | bool | True | Method (callable) attributes will be immutableCan override selectively with rw | |
ro | list of str | [ ] | Attributes that will be immutableCan override selectively with rw | |
rw | list of str | [ ] | Attributes that will be mutable | ro_dataro_methodro |
hide | list of str | [ ] |
Visibility and mutability of attributes with protect() method
Option | Attribute Type | Restricts Visibility | Restricts Mutability |
---|---|---|---|
frozen | Any | NO | YES |
hide_private | Private attributes | YES | YES (Indirect) |
ro_data | Data attributes | NO | YES |
ro_method | Method attributes | NO | YES |
ro | ANY | NO | YES |
rw | ANY | NO | YES |
hide | ANY | YES | YES (Indirect) |
__class__
, __dict__
, __delattr__
, __setattr__
, __slots__
, __getattribute__
freeze(o: object) -> Frozen:
Object returned prevents modification of ANY attribute
private(o: object, frozen: bool = False) -> object:
protect(
o: object frozen: bool = False,
dynamic: bool = True,
hide_private: bool = False,
ro_data: bool = False,
ro_method: bool = True,
ro: List[str] = [],
rw: List[str] = [],
hide: List[str] = []
) -> object:
# o-->object to be wrapped
Returns-->Instance of FrozenProtected if frozen; Instance of Protected otherwise
If protect() is called on an object 'o' that is an instance of Protected, protect() will merge the protect() rules, enforcing the most restrictive combination among the two sets of protect() options:
In short, by calling protect() a second time (or multiple times):
Options: protect method arguments
Option | Type | Default | Description | Overrides |
---|---|---|---|---|
frozen | bool | False | If True, no attributes can be changed, added or deleted | |
hide_private | bool | False | If True, private vars of the form _var will be hidden |
|
ro_data | bool | False | Data (non-method) attributes will be immutableCan override selectively with rw | |
ro_method | bool | True | Method (callable) attributes will be immutableCan override selectively with rw | |
ro | list of str | [ ] | Attributes that will be immutableCan override selectively with rw | |
rw | list of str | [ ] | Attributes that will be mutable | ro_dataro_methodro |
hide | list of str | [ ] |
Visibility and mutability of attributes with protect() method
Option | Attribute Type | Restricts Visibility | Restricts Mutability |
---|---|---|---|
frozen | Any | NO | YES |
hide_private | Private attributes | YES | YES (Indirect) |
ro_data | Data attributes | NO | YES |
ro_method | Method attributes | NO | YES |
ro | ANY | NO | YES |
rw | ANY | NO | YES |
hide | ANY | YES | YES (Indirect) |
wrap(o: object) -> Wrapped:
__getattribute__
, __delattr__
, __setattr__
, __slots__
__class__
) of wrapped object from modification__dict__
or __slots__
Useful for testing if wrapping is failing for a particular type of object
isfrozen(x: object) -> bool
x was created using freeze() or private(o, frozen=True) or protect(o, frozen=True)
isimmutable(x: object) -> bool
x is known to be immutable
isprivate(x: object) -> bool
x was created using private()
isprotected(x: object) -> bool
x was created using protect()
contains(w: object, o: object) -> bool
If w is a wrapped object (iswrapped(w) is True), returns whether w wraps o Otherwise unconditionally returns False
help_protected(x: object) -> None
If x wraps o, executes help(o) Otherwise executes h_elp(x)_
id_protected(x: object) -> int
if x is a wrapped object (iswrapped(x) is True) and x wraps o, returns id(o) Otherwise returns id(x)
isinstance_protected(x: object, t: type) -> bool
If x is a wrapped object (iswrapped(x) is True) and x wraps o, returns isinstance(o, t) Otherwise returns isinstance(x, t)
isreadonly(x: object, a: str) -> bool
If x is a wrapped object - with iswrapped(x) == True - and x wraps o, isreadonly(x, a) returns whether rules of wrapper make attribute a read-only when accessed through x This represents rule of wrapped object - does not guarantee that_o_ has attribute_a_ or that setting attribute a in object o will not raise any exception If x is not a wrapped object (iswrapped(x) is False) , unconditionally returns False
instance_of_protected(x: object, o: object) -> bool
If x is a wrapped object - with iswrapped(x) == True - and x wraps o, instance_of_protected(x, o) returns True if and only if isinstance(x, type(o)) If x is not a wrapped object - iswrapped(x) == False - instance_of_protected(x, o) returns isinstance(x, o)
isvisible(x: object, a: str) -> bool
Returns False if and only if iswrapped(x) is True AND x makes attribute a invisible if present in wrapped object This represents rule of wrapped object - does not guarantee that wrapped object has attribute a or that accessing attribute a in object x will not raise any exception If x is not a wrapped object, unconditionally returns False
same_class_protected(c: type, w: object) -> bool
If iswrapped(w) and w wraps o: Returns (c is type(o)) Otherwise: returns (c is type(w))
subclass_of_protected(x: object, w: object) -> bool
If iswrapped(w) and w wraps o: Returns issubclass(x, type(o)) Otherwise: returns issubclass(x, w)
immutable_builtin_attributes() -> Set[str]
Returns-->set of str: attributes in builtins that are immutable Used in unit tests
always_delegated_attributes() -> set(str)
Attributes that are always delegated to wrapped object
attribute_protected() -> str
Name of special attribute in Wrapped objects
hidden_pickle_attributes() -> set(str)
Attributes that are never visible in object 'o' if iswrapped(o) - to disallow pickling
never_writeable() -> set(str)
Attributes that are never writeable in object o if iswrapped(o)
never_writeable_private() -> set(str)
Attributes that are never writeable in object o if isprivate(o)
In the table below:
Operation 🡆On type 🡇 | wrap | freeze | private | private+ frozen | protect | protect+ frozen |
---|---|---|---|---|---|---|
Ordinary objectiswrapped(x) is False | Wrapped | Frozen(2) | Private | FrozenPrivate | Protected | FrozenProtected |
Wrapped | UNCH | Frozen(2) | Private | FrozenPrivate | Protected | FrozenProtected |
Frozen | Wrapped(2) | UNCH(2) | FrozenPrivate | FrozenPrivate | FrozenProtected | FrozenProtected |
Private | UNCH | FrozenPrivate | UNCH | FrozenPrivate | Protected | FrozenProtected |
FrozenPrivate | UNCH | UNCH | UNCH | UNCH | FrozenProtected | FrozenProtected |
Protected | UNCH | FrozenProtected | UNCH | FrozenProtected | Protected(1) | FrozenProtected(1) |
FrozenProtected | UNCH | UNCH | UNCH | UNCH | FrozenProtected(1) | FrozenProtected(1) |
In short, by calling protect() a second time (or multiple times): - Additoinal attributes can be hidden - Additional attributes can be made read-only but: - No previously hidden attribute will become visible - No previously read-only attribute will become mutable
For all other objects x, having isimmutable(x) == False, freeze(x) will return a Frozen object having iswrapped(freeze(x)) == True
For all other wrapped objects w, created with private(x) or protect(x), freeze(w) will always return a Wrapped object with iswrapped(w) == True
Pretty much anything. pyprotect only mediates attribute access using object.__getattribute__
, object.__setattr__
and object.__delatr__
. If these methods work on your object, your object can be wrapped
pyprotect
to match the name of the module. This has been long-pending. The old github link redirects to the new project name.A number of parameters to protect() have been discontinued. See list and reasons below, as well as how to achieve the same effect without thos parameters (sometimes, it takes more work). Most of them would be realistically useful very rarely, and / or do not align with what I call 'idiomatic python'.
hide_all, hide_method, hide_dunder
So-called 'special methods' in Python serve an important functional roles - especially in emulating containers (tuples, lists, sets, dicts), emulating numeric types supporting arithmetic operators, numeric comparisons etc. If such specific 'dunder methods' were hidden, it would definitely affect the behavior of the wrapped object. hide_dunder
would hide all such special methods. hide_method
would in addition hide all methods in the objest. hide_all
would hide all object attributes, making the object virtually useless, and the option useful in testing (if at all).
hide_data In most cases, hiding all non-method data attributes will make the object less useful / cripple the expected usage of the object. Specific use-cases can be achieved using the 'hide' parameter.
ro_dunder Seems like it can be replaced by using ro_method' Mostly, in 'idiomatic python', methods of a class / instance are not mutated from outside the class / instance. This expected 'idiomatic python' behavior can be achieved with 'ro_method'.
ro_all Is unnecessary, since 'frozen' can be used instead.
add In my opinion, 'add' does not align with 'idiomatic python'. While Python allows users of a class / instance adding attributes to the class / instance, that is not the expected style. Based on this, I have decided to promote that 'idiomatic style', and prevent adding / deleting any attributes in a Private or Protected object.
show It is unnecessary, since all attributes are visible by default in Python. Only 'hide' or 'hide_private' will hide any attributes.
This leaves the following (incidentally also reducing testing load):
- Visibility:
- hide_private
- hide
- Mutability:
- ro_data
- ro_method
- ro
- rw
- frozen
- Behavior:
- dynamic