Package: pyprotect-package
Restrict visibility or mutability of Python object attributespyprotect 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()andprotect()wrap the python object (like aProxy) to restrict visibility or mutability of selected attributes of the wrapped object, while allowing thewrappingobject to behave virtually identical to thewrappedobject.FeaturesCan wrap virtually any Python object - instances, classes (types), modules, methods, classmethods, instancemethods, staticmethods, partials, lambdas.Tested on Python 2.7 and Python 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11Tested on the following distributions with the latest versions of python2, python3, pypy3, pypy shipped by the respective distributions:Ubuntu Jammy 22.04Arch linux (20220101)Fedora 37Alpine Linux 3.15 (Alpine 3.16 does not have python2-dev)Has extensive unit (functional) tests - intestsdirectory.Table of ContentsQuick startClassesFeatures of key classesWrappedFrozenPrivateFrozenPrivateProtectedFrozenProtectedAPIWrapping APIfreezeprivateprotectwrapChecking types of wrapped objectsisfrozenisimmutableisprivateisprotectedChecking properties of objects inside wrapped objectscontainshelp_protectedid_protectedisinstance_protectedisreadonlyinstance_of_protectedisvisiblesame_class_protectedsubclass_of_protectedpyprotect module metadataimmutable_builtin_attributesalways_delegated_attributesattribute_protectedhidden_pickle_attributesnever_writeablenever_writeable_privateCalling wrap operations multiple timesPython rules for attributes of type 'property':What kind of python objects can be wrapped?Work in progressChangelogJan-20-2022Jan-15-2022Dec-08-2022Quick startfreeze(o:object)->Frozen:Ifois immutable (e.g. int , string), returnsoUNCHANGEDIfois Wrapped, returnsoUNCHANGED if object WRAPPPED INSIDEois immutable, returns Frozen otherwiseIfois Frozen, returnsoUNCHANGEDIfois FrozenPrivate, FrozenProtected or FrozenPrivacyDict, returnsoUNCHANGEDIfois Private, returns FrozenPrivateIfois Protected, returns FrozenProtectedOtherwise, returns FrozenObject returned prevents modification of ANY attributeprivate(o:object,frozen:bool=False)->object:Iffrozenis False:Ifois an instance of Private, returnsoUNCHANGEDIf 'o' is an instance of Protected, returnsoUNCHANGEDIffrozenis True:Ifois an instance of Private, returnsfreeze(o)(FrozenPrivate)Ifois an instance of Protected, returnsfreeze(o)(FrozenProtected)Otherwise:
Iffrozenis True, returns FrozenPrivate; returns Private otherwiseprotect(o:objectfrozen: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 wrappedReturns-->Instance ofFrozenProtectediffrozen; Instance ofProtectedotherwiseIfprotect()is called on an object 'o' that is an instance of Protected,protect()will merge theprotect()rules, enforcing the most restrictive combination among the two sets of protect() options:hideandhide_privateare OR-edro_method,ro_dataandroare OR-edrwis AND-ed, butrwof second protect overridesro*ofsecondprotect butnotthefirstprotect.In short, by calling protect() a second time (or multiple times):Additoinal attributes can be hiddenAdditional attributes can be made read-onlyNo previously hidden attribute will become visibleNo previously read-only attribute will become mutableOptions: protect method argumentsOptionTypeDefaultDescriptionOverridesfrozenboolFalseIf True, no attributes can be changed, added or deletedhide_privateboolFalseIf True, private vars of the form_varwill be hiddenro_databoolFalseData (non-method) attributes will be immutableCan override selectively withrwro_methodboolTrueMethod (callable) attributes will be immutableCan override selectively withrwrolist of str[ ]Attributes that will be immutableCan override selectively withrwrwlist of str[ ]Attributes that will be mutablero_dataro_methodrohidelist of str[ ]Visibility and mutability of attributes with protect() methodOptionAttribute TypeRestricts VisibilityRestricts MutabilityfrozenAnyNOYEShide_privatePrivate attributesYESYES (Indirect)ro_dataData attributesNOYESro_methodMethod attributesNOYESroANYNOYESrwANYNOYEShideANYYESYES (Indirect)ClassesFeatures of key classesWrappedVisibility: No additional restrictionsMutability: No additional restrictionsFrozenVisibility: Does notadditionallyrestrict visibility of any attributes inwrappedobject accessed throughwrappingobjectMutability: Prevents modification of ANY attributePrivateVisibility:Cannot access traditionally 'private' mangled python attributesCannot modify traditionally private attributes (form 'var')Attributes not part of dir(wrapped_object) are not visibleMutability:The following attributes of wrapped object are NEVER writeable:__class__,__dict__,__delattr__,__setattr__,__slots__,__getattribute__Traditional (mangled) Python private vars are ALWAYS hiddenPrivate vars (form _var) will be read-onlyAttributes cannot be added or removedAttributes that are properties are ALWAYS visible AND WRITABLE (except if 'frozen' is used)Properties indicate an intention of class author to expose themWhether they are actually writable depends on whether class author implemented property.setterFrozenPrivateVisibility: Same as PrivateMutability: Prevents modification of ANY attributeProtectedFeatures of Private PLUS allowsfurther restrictionof:Which attributes are VISIBLEWhich attributes are WRITEABLEDefault settings:Features of Private - see abovedynamic == True
Attribute additions, deletions, type changes automatically visiblero_method == True: Method attributes will be read-onlyAll other non-private data attributes are read-writeFrozenProtectedFeatures of Protected PLUS prevents modification of ANY attributeAPIWrapping APIfreezefreeze(o:object)->Frozen:Ifois immutable (e.g. int , string), returnsoUNCHANGEDIfois Wrapped, returnsoUNCHANGED if object WRAPPPED INSIDEois immutable, returns Frozen otherwiseIfois Frozen, returnsoUNCHANGEDIfois FrozenPrivate, FrozenProtected or FrozenPrivacyDict, returnsoUNCHANGEDIfois Private, returns FrozenPrivateIfois Protected, returns FrozenProtectedOtherwise, returns FrozenObject returned prevents modification of ANY attributeprivateprivate(o:object,frozen:bool=False)->object:Iffrozenis False:Ifois an instance of Private, returnsoUNCHANGEDIf 'o' is an instance of Protected, returnsoUNCHANGEDIffrozenis True:Ifois an instance of Private, returnsfreeze(o)(FrozenPrivate)Ifois an instance of Protected, returnsfreeze(o)(FrozenProtected)Otherwise:
Iffrozenis True, returns FrozenPrivate; returns Private otherwiseprotectprotect(o:objectfrozen: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 wrappedReturns-->Instance ofFrozenProtectediffrozen; Instance ofProtectedotherwiseIfprotect()is called on an object 'o' that is an instance of Protected,protect()will merge theprotect()rules, enforcing the most restrictive combination among the two sets of protect() options:hideandhide_privateare OR-edro_method,ro_dataandroare OR-edrwis AND-ed, butrwof second protect overridesro*ofsecondprotect butnotthefirstprotect.In short, by calling protect() a second time (or multiple times):Additoinal attributes can be hiddenAdditional attributes can be made read-onlyNo previously hidden attribute will become visibleNo previously read-only attribute will become mutableOptions: protect method argumentsOptionTypeDefaultDescriptionOverridesfrozenboolFalseIf True, no attributes can be changed, added or deletedhide_privateboolFalseIf True, private vars of the form_varwill be hiddenro_databoolFalseData (non-method) attributes will be immutableCan override selectively withrwro_methodboolTrueMethod (callable) attributes will be immutableCan override selectively withrwrolist of str[ ]Attributes that will be immutableCan override selectively withrwrwlist of str[ ]Attributes that will be mutablero_dataro_methodrohidelist of str[ ]Visibility and mutability of attributes with protect() methodOptionAttribute TypeRestricts VisibilityRestricts MutabilityfrozenAnyNOYEShide_privatePrivate attributesYESYES (Indirect)ro_dataData attributesNOYESro_methodMethod attributesNOYESroANYNOYESrwANYNOYEShideANYYESYES (Indirect)wrapwrap(o:object)->Wrapped:Should behave just like the wrapped object, except following attributes cannot be modified:__getattribute__,__delattr__,__setattr__,__slots__Explicitly does NOT support pickling, and will raisepickle.PicklingErrorDoes NOT protect CLASS (or__class__) of wrapped object from modificationDoes NOT protect__dict__or__slots__Useful for testing if wrapping is failing for a particular type of objectChecking types of wrapped objectsisfrozenisfrozen(x:object)->boolxwas created usingfreeze()orprivate(o, frozen=True)orprotect(o, frozen=True)isimmutableisimmutable(x:object)->boolxis known to be immutableisprivateisprivate(x:object)->boolxwas created usingprivate()isprotectedisprotected(x:object)->boolxwas created usingprotect()Checking properties of objects inside wrapped objectscontainscontains(w:object,o:object)->boolIfwis a wrapped object (iswrapped(w)is True), returns whetherwwrapsoOtherwise unconditionally returns Falsehelp_protectedhelp_protected(x:object)->NoneIfxwrapso, executeshelp(o)Otherwise executes h_elp(x)_id_protectedid_protected(x:object)->intifxis a wrapped object (iswrapped(x)is True) andxwrapso, returnsid(o)Otherwise returnsid(x)isinstance_protectedisinstance_protected(x:object,t:type)->boolIfxis a wrapped object (iswrapped(x)is True) andxwrapso, returnsisinstance(o, t)Otherwise returnsisinstance(x, t)isreadonlyisreadonly(x:object,a:str)->boolIfxis a wrapped object - withiswrapped(x)== True - andxwrapso,isreadonly(x, a)returns whether rules ofwrappermake attributearead-only when accessed throughxThis representsruleof wrapped object - does not guarantee that_o_ has attribute_a_ or that setting attributeain objectowill not raise any exception
Ifxisnota wrapped object (iswrapped(x)is False) , unconditionally returns Falseinstance_of_protectedinstance_of_protected(x:object,o:object)->boolIfxis a wrapped object - withiswrapped(x)== True - andxwrapso,instance_of_protected(x, o)returns True if and only ifisinstance(x, type(o))Ifxis not a wrapped object -iswrapped(x)== False -instance_of_protected(x, o)returnsisinstance(x, o)isvisibleisvisible(x:object,a:str)->boolReturns False if and only ifiswrapped(x)is True ANDxmakes attributeainvisible if present in wrapped objectThis representsruleof wrapped object - does not guarantee thatwrapped objecthas attributeaor that accessing attributeain objectxwill not raise any exceptionIfxis not a wrapped object, unconditionally returns Falsesame_class_protectedsame_class_protected(c:type,w:object)->boolIfiswrapped(w)andwwrapso: Returns(c is type(o))Otherwise: returns(c is type(w))subclass_of_protectedsubclass_of_protected(x:object,w:object)->boolIfiswrapped(w)andwwrapso: Returnsissubclass(x, type(o))Otherwise: returnsissubclass(x, w)pyprotect module metadataimmutable_builtin_attributesimmutable_builtin_attributes()->Set[str]Returns-->set of str: attributes in builtins that are immutable
Used in unit testsalways_delegated_attributesalways_delegated_attributes()->set(str)Attributes that are always delegated to wrapped objectattribute_protectedattribute_protected()->strName of special attribute in Wrapped objectshidden_pickle_attributeshidden_pickle_attributes()->set(str)Attributes that are never visible in object 'o' if iswrapped(o) - to disallow picklingnever_writeablenever_writeable()->set(str)Attributes that are never writeable in objectoifiswrapped(o)never_writeable_privatenever_writeable_private()->set(str)Attributes that are never writeable in objectoifisprivate(o)Calling wrap operations multiple timesIn the table below:The left-most column shows starting state.The top row shows operation applied to the starting state.The intersecting cell shows the result.UNCHrepresents operation returning the starting state unchangedOperation  🡆On type 🡇wrapfreezeprivateprivate+ frozenprotectprotect+ frozenOrdinary objectiswrapped(x)is FalseWrappedFrozen(2)PrivateFrozenPrivateProtectedFrozenProtectedWrappedUNCHFrozen(2)PrivateFrozenPrivateProtectedFrozenProtectedFrozenWrapped(2)UNCH(2)FrozenPrivateFrozenPrivateFrozenProtectedFrozenProtectedPrivateUNCHFrozenPrivateUNCHFrozenPrivateProtectedFrozenProtectedFrozenPrivateUNCHUNCHUNCHUNCHFrozenProtectedFrozenProtectedProtectedUNCHFrozenProtectedUNCHFrozenProtectedProtected(1)FrozenProtected(1)FrozenProtectedUNCHUNCHUNCHUNCHFrozenProtected(1)FrozenProtected(1)protect()applied twice will merge theprotect()rules, enforcing the most restrictive combination among the two sets of protect() options:hideandhide_privateare OR-edro_method,ro_dataandroare OR-edrwis AND-ed, butrwof second protect overridesro*ofsecondprotect butnotthefirstprotect.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 mutableIfxis an immutable object (e.g. int, str ...) havingisimmutable(x) == True,freeze(x)returnsxandiswrapped(freeze(x))will be False.For all other objectsx, havingisimmutable(x) == False,freeze(x)will return a Frozen object havingiswrapped(freeze(x)) == TrueFor all other wrapped objectsw, created withprivate(x)orprotect(x),freeze(w)will always return a Wrapped object withiswrapped(w) == TruePython rules for attributes of type 'property':Properties are defined in the CLASS, and cannot be changed in the object INSTANCEProperties cannot be DELETEDProperties cannot be WRITTEN to unless property has a 'setter' method defined in the CLASSThese rules are implemented by the python language (interpreter) and Protected class does not enforce or checkWhat kind of python objects can be wrapped?Pretty much anything. pyprotect only mediates attribute access usingobject.__getattribute__,object.__setattr__andobject.__delatr__. If these methods work on your object, your object can be wrappedWork in progressUploading to pypi.orgTest cases requiredChangelogJan-20-2022Project name on github changed topyprotectto match the name of the module. This has been long-pending. Theold github linkredirects to the new project name.Jan-15-2022Started signing commits with my GPG key and displaying 'verified' for signed commitsDec-08-2022A 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_dunderSo-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_dunderwould hide all such special methods.hide_methodwould in addition hide all methods in the objest.hide_allwould hide all object attributes, making the object virtually useless, and the option useful in testing (if at all).hide_dataIn 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_dunderSeems 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_allIs unnecessary, since 'frozen' can be used instead.addIn 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.showIt 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