Package: worktoy
WorkToy v0.40.28pip install worktoyTable of ContentsWorkToyClassmaybemaybeTypemaybeTypessearchKeysearchKeysmaybeKeymaybeKeysWorkToyClass.GuardsnoneGuardsomeGuardoverRideGuardfunctionGuardintGuardstrGuardDescriptorsAttributeMetaclasstypeNameSpaceWait A Minute!MetaXceptMetaTypeSupportErrorFieldDecoderExceptionFieldEncoderExceptionMissingArgumentExceptionRecursiveCreateGetErrorTypeSupportErrorUnavailableNameExceptionUnexpectedEventExceptionUnsupportedSubclassExceptionCoreWorkToyClassParent class providing general utility functions on the class itself.fromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""def__init__(self,*args,**kwargs)->None:WorkToyClass.__init__(self,*args,**kwargs)By inheriting from theWorkToyClass, instances now have access to a
collection of utility functions:WorkToyClass.maybefromtypingimportAnyfromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*args)->Any:"""Instance method using ``maybe`` use a default argument value."""returnself.maybe(*args)myInstance=MyClass()myInstance.instanceMethod(None,[],)# >>> []WorkToyClass.maybeTypefromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*args,**kwargs)->int:"""Instance method using ``maybeType`` to extract an integer from thepositional arguments. """returnself.maybeType(int,*args)myInstance=MyClass()myInstance.instanceMethod('one',2,'3',4,5)# >>> 2WorkToyClass.maybeTypesfromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*args,**kwargs)->int:"""Instance method using 'maybeTypes' to extract every integer from thepositional arguments."""out=self.maybeTypes(int,*args)ifisinstance(out,int):returnoutmyInstance=MyClass()myInstance.instanceMethod('one',2,'3',4,5)# >>> [2, 4, 5]WorkToyClass.searchKeyfromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*keys,**kwargs)->int:"""Instance method using ``searchKey`` to search for keyword argumentvalue."""returnself.searchKey(*keys,**kwargs)myInstance=MyClass()myInstance.instanceMethod('count','Count','amount',count=7)# >>> 7WorkToyClass.searchKeysfromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*keys,**kwargs)->int:"""Instance method using ``searchKeys`` to search for every keywordargument."""returnself.searchKeys(*keys,**kwargs)myInstance=MyClass()myInstance.instanceMethod('a','d','e',a=1,b=2,c=3,d=4)# >>> [1, 4]WorkToyClass.maybeKeyfromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*args,**kwargs)->int:"""Instance method using ``maybeKey`` to search for a keyword argumentvalue with a type restriction argument."""returnself.maybeKey(*args,**kwargs)myInstance=MyClass()myInstance.instanceMethod('a','b',int,a='1',b=2,c=3,d=4)# >>> 2WorkToyClass.maybeKeysfromworktoy.worktoyclassimportWorkToyClassclassMyClass(WorkToyClass):"""Example class"""definstanceMethod(self,*args,**kwargs)->int:"""Instance method using ``maybeKeys`` to search for every keywordargument restricted to a certain type."""returnself.maybeKeys(*args,**kwargs)myInstance=MyClass()myInstance.instanceMethod('a','b',int,a=1,b=2,c=3,d=4)# >>> [1, 2]WorkToyClass - GuardsThe following methods are various type guards.WorkToyClass.noneGuardRaisesUnavailableNameExceptionif the given object is not None.WorkToyClass.someGuardRaisesMissingArgumentExceptionif given object is NoneWorkToyClass.overRideGuardRaisesUnavailableNameExceptionif given object is not NoneWorkToyClass.functionGuardRaisesTypeSupportErrorif given object is not a functionWorkToyClass.intGuardRaisesTypeSupportErrorif given object is None or not an integerWorkToyClass.floatGuardRaisesTypeSupportErrorif given object is None or not a floatWorkToyClass.strGuardRaisesTypeSupportErrorif given object is None or not a stringDescriptorsTheAttributeclass implements flexible descriptors.classAttribute(WorkToyClass):"""WorkToy - Fields - FieldBasic descriptor implementation."""def__init__(self,defVal:Any=None,*args,**kwargs)->None:WorkToyClass.__init__(self,*args,**kwargs)self._defaultValue=Noneself._defaultType=Nonecase 1:Receives a default value that is not a typeifdefValisnotNoneandnotisinstance(defVal,type):self._setDefaultValue(defVal)self._defaultType=type(self._defaultValue)case 2:Receives a default value that is a type. This is taken to
mean that the attribute is to hold a value of that type. The type
is then called without argument to attempt to create a default value.ifisinstance(defVal,type):self._setDefaultValue(defVal)self._defaultValue=defVal()case 3Receives no argument. In this case, the instance is
responsible for providing a default value. For example in the
instance__init__method using the public setter.
Please note, that fields must do one of the following:Provide a default value in the Field creation call (RECOMMENDED)Define a Field type in the creation call. (NOT RECOMMENDED)Have the owning class defer default instance creation to the
first call to the getter-function. This advanced use case allows
the owning class to control specifically when and how to create
instances. This is supported and encouraged, for advanced owning
classes. (ADVANCED USE CASE)Subclass attribute to the specific type. (ADVANCED USE CASE)ATTENTION!Doing none of the above leads to HIGHLY UNDEFINED BEHAVIOUR!
The Attribute is intended to raise MissingArgumentException in such
cases.self._fieldName=Noneself._fieldOwner=Noneself._getterFunctionName=Noneself._setterFunctionName=Noneself._deleterFunctionName=NoneMetaClassMetaclasses are certainly the most powerful tool available in Python
development. The WorkToy package provides a basic skeleton for
implementing custom metaclasses in the form ofAbstractMetaClassandAbstractNameSpace. Before explaining the merits of these, a
examination of how metaclasses work seem appropriate.Introduction to metaclassesYou are already familiar with the default baseclass:type. In a
somewhat unfortunate choice of nomenclature, we face an ambiguity here:
do we meantypeas in:isinstance(int, type)or do we mean:type(int)? The first treatstypeas atype, but the second
treatstypeas a function. To illustrate how unfortunate this
nomenclature is, consider this expression:type(type) is typeorisinstance(type, type) >>> TrueAmetaclassis a customtype. ConsiderTestClassdefined below:fromworktoy.worktoyclassimportWorkToyClassclassTestClass(WorkToyClass):"""Created with traditional class body."""def__init__(self,*args,**kwargs)->None:WorkToyClass.__init__(self,*args,**kwargs)definstanceMethod(self,*args,**kwargs)->int:"""Instance method"""returnself.maybeType(int,*args)The above is entirely equivalent to:fromtypingimportAnyfromworktoy.worktoyclassimportWorkToyClassdefinitFunc(instance:Any,*args,**kwargs)->None:"""Init function"""WorkToyClass.__init__(instance,*args,**kwargs)defsomeFunc(self,*args,**kwargs)->int:"""Instance Method"""returnself._maybeType(int,*args)name='TestClass'bases=(WorkToyClass,)nameSpace=dict(__init__=initFunc,instanceMethod=someFunc)TestClass=type(name,bases,nameSpace)typeThetypeobject used above specifies the creation of new classes. By
creating a custommetaclass, we are able to define our own class
creation. Below we define ametaclassthat behaves entirely liketypeallowing us to recognize the class creation we are familiar with
and see how we can change this behaviour.(Please note, the naming convention:mcls: metaclass,cls: new
class,selfnew instance).fromtypingimportAnyclassBaseMeta(type):# metaclasses inherit from type"""Base metaclass behaving line ``type``"""@classmethoddef__prepare__(mcls,name:str,bases:tuple[type],**kwargs)->dict:"""The prepare method creates the empty mapping object providing thenamespace for the newly created class. The base implementationreturns an empty instance of ``dict``."""return{}def__new__(mcls,name:str,bases:tuple[type],nameSpace:dict,**kwargs)->type:"""The ``__new__`` method createds the new class,"""cls=type.__new__(mcls,name,bases,nameSpace,**kwargs)returnclsdef__init__(cls,name:str,bases:tuple[type],nameSpace:dict,**kwargs)->None:"""Once the new class is created it is initialised by this method. """type.__init__(cls,name,bases,nameSpace,**kwargs)def__call__(cls:type,*args,**kwargs)->Any:"""This method specifies how the newly creatd class creates instancesof itself. The default behaviour is as shown below: The instance iscreated with the __new__ method on the newly created class, and thenit is initialized with the __init__ on the newly created class."""self=cls.__new__(cls,)cls.__init__(self,*args,**kwargs)returnselfBy introducing custom metaclasses, we are free to customize the above
steps to achieve any imaginable functionality. People say that Python
does not support function overloading. What they mean is that function
overloading in Python must be implemented at the metaclass level. (That
is dumb, function overloading should not require custom metaclasses, but
the point stands).NameSpaceCustomizing the__prepare__method gives the greatest
opportunity to customize the class creation. Let us examine the
requirements for the namespace object returned by the__prepare__method. When attempting to use a custom class for this purppose, one is
likely to encounter errors like:"""TypeError: type.__new__() argument 3 must be dict, not NameSpace""""""TypeError: META.__prepare__() must return a mapping, not NameSpace""""""TypeError: ``NameSpace`` object does not support item assignment"""It is possible to create a custom class that does not trigger any suchTypeError, which is able to create classes without any problem. Until
one day, you introduce astaticmethodand then receive:"""@staticmethod^^^^^^^^^^^^TypeError: ``NoneType`` object is not callable"""What even is that error message? The above happens if the__getitem__method on the namespace object does not raise a KeyError when receiving a
missing key. The expected behaviour from the namespace object receiving a
missing key is to raise a KeyError with the missing key as the message.
For example:fromtypingimportAnydef__getitem__(self,key:str,)->Any:try:dict.__getitem__(self,key)exceptKeyErrorase:print(key)raiseeBy including the print statement, we can see that the problems occur
where the class body has a valid expression without an equal sign. For
example when decorating a function. Consider the following example:fromtypingimportAny,CallableclassNameSpace(dict,):"""NameSpace custom class"""def__getitem__(self,key:str,)->Any:"""Prints missing keys that are encountered."""try:returndict.__getitem__(self,key)exceptKeyErrorase:print(key)raiseeclassMETA(type):"""Metaclass implementing the __prepare__ method which returns aninstance of the NameSpace class."""@classmethoddef__prepare__(mcls,name,bases,**kwargs)->Any:nameSpace=NameSpace()returnnameSpacedeffunc(f:Callable)->None:"""Decorator"""returnfclassTestClass(metaclass=META):"""TestClass """@staticmethod@funcdefa(self)->None:passif__name__=='__main__':TestClass()When running the above script, we see the following printed to the console:'__name__''staticmethod''func'Fortunately, WorkToy provides theAbstractNameSpaceclass which
implements all required mapping functionality. Besides implementingdictmethods, it logs every line in the class body.Wait A Minute!In this module, WorkToy provides the custom exceptions used throughout
the entire package.MetaXceptJust like the SYM module, the custom exceptions
implement a custom metaclass inheriting fromAbstractMetaClass. This
metaclassMetaXceptuses a custom namespace class inheriting from theAbstractNameSpacein its__prepare__method.Below is a reference list of the custom exceptions currently implemented:MetaTypeSupportErrorDescriptionIndicates that an instance is not a member of class derived from the
correct metaclass.SignaturefromtypingimportAnyexpMetaClass:type# The expected metaclassactualValue:Any# The actual value receivedargName:str# Argument nameFieldDecoderExceptionCustom exception raised when an instance ofDataFieldattempts to decode
with default JSON decoder. The exception catches theJSONDecodeErrorand
brings additional information.FieldEncoderExceptionCustom exception raised when an instance ofDataFieldattempts to
serialize its value toJSONformat, but where the value is not
serializable.MissingArgumentExceptionDescriptionInvoked when a function is called before it is ready.SignaturefromtypingimportAnymissingArgument:str# Argument nameRecursiveCreateGetErrorDescriptionRaised when a getter function calls a creator function a second time.Signaturefromworktoy.coreimportFunctionfromtypingimportAnycreator:Function# The expected typevariableType:type# The type of the variablevariableName:str# Argument nameTypeSupportErrorDescriptionThis exception should be raised when the argument type is not supported.SignaturefromtypingimportAnyexpectedType:type# The expected typeactualValue:Any# The actual value receivedargName:str# Argument nameUnavailableNameExceptionDescriptionException raised when a name is already occupied. Meaning that the named
argument were expected to beNone.SignaturefromtypingimportAnyargName:str# The unavailable nameexistingValue:Any# The present value at the namenewValue:str# The new value attempted to setUnexpectedEventExceptionDescriptionRaised when receiving aQEventof the wrongQEvent.Type. (Please note
that this exception is intended for use with the companionWorkSidemodule.)SignaturefromtypingimportAnyexpectedQEventType:str# The expected QEvent.TypeactualEvent:Any# The actual instance of QEvent receivedargumentName:str# The argument nameUnsupportedSubclassExceptionDescriptionThis exception should be raised when encountering a variable of correct
type, but of incorrect subclass.SignaturefromtypingimportAnyargumentName:str# The argument nameexpectedParent:type# The actual instance of QEvent receivedactualValue:str# The actual value of the variableCoreThis module provides common types and constants.