Package: basicco
OverviewBase ClassesandUtilitiesfor compatibility, features and validation.MotivationWhile developing Python software for Visual Effects pipelines, I found myself having to
write the same boiler-plate code over and over again, as well as struggling with
compatibility issues and feature gaps between Python 2.7 and Python 3.7+.So I decided to implement solutions for those issues at theBase, andbasiccowas born.Base ClassesCompatBaseThe goal with theCompatBaseMetametaclass and theCompatBaseclass is to bridge
some of the feature gaps between Python 2.7 and Python 3.7+.This includes adding Python 2.7 workarounds for:Abstract properties:
Betterabstractmethoddecorator support for property-like descriptors.
See alsoabstract_class.PEP 487:
Support for__init_subclass__and__set_name__.
See alsoinit_subclassandset_name.object.__dir__:
Base__dir__method.
See alsodefault_dir.__eq__ override:
Overriding__eq__will set__hash__to None. See alsoimplicit_hash.PEP 307:
Support for pickling objects with__slots__.
See alsoobj_state.PEP 3155:
Qualified name__qualname__for nested classes.
See alsoqualname.__ne__ behavior:
By default,__ne__should negate the result of__eq__.
See alsosafe_not_equalsPEP 0560:
Better handling of Generic classes.
See alsotippo.BaseIn addition to the compatibility solutions, the goal with theBaseMetametaclass and
theBaseclass is to add useful low-level features that hopefully yield better code
readability and validation.This includes:__weakref__slot: Added by default.locked_class: Public class attributes are read-only by default.explicit_hash: Overriding__eq__without overriding__hash__will error.namespace: Adds a protected__namespaceunique to each class.runtime_final: Runtime checking for classes and methods decorated withfinal.SlottedBaseTheSlottedBaseclass and theSlottedBaseMetametaclass offer all features fromBaseandBaseMetaplus implicit__slots__declaration.
Seeslottedfor more information.UtilitiesApart from the features integrated into the base classes,basiccoprovides many
general utility modules.abstract_classBetter support forabstract classes.Provides abstract decorators that can be used directly on methods but also on
property getters, classmethods, and staticmethods (even in Python 2.7).>>>fromsiximportwith_metaclass>>>frombasicco.abstract_classimportAbstractMeta,abstract>>>classAsset(with_metaclass(AbstractMeta,object)):...@abstract...defmethod(self):...pass......@property...@abstract...defprop(self):...returnNone...>>>Asset()Traceback(mostrecentcalllast):TypeError:Can't instantiate abstract class Asset...caller_moduleRetrieve the caller’s module name.>>>frombasicco.caller_moduleimportcaller_module>>>defdo_something():...return"I was called by{}".format(caller_module())...>>>do_something()'I was called by __main__'context_varsBackport of thecontextvarsmodule for Python 2.7, based onMagicStack/contextvars.When imported from Python 3, it simply redirects to the nativecontextvarsmodule.>>>frombasicco.context_varsimportContextVar>>>my_var=ContextVar("my_var",default="bar")>>>token=my_var.set("foo")>>>my_var.get()'foo'>>>my_var.reset(token)>>>my_var.get()'bar'custom_reprCustom representation functions for mappings, items, and iterables.>>>frombasicco.custom_reprimportmapping_repr>>>dct={"a":1,"b":2}>>>mapping_repr(...dct,...prefix="<",...suffix=">",...template="{key}={value}",...sorting=True...)"<'a'=1, 'b'=2>">>>frombasicco.custom_reprimportmapping_repr>>>items=[("a",1),("b",2)]>>>mapping_repr(...items,...prefix="[",suffix="]",...template=(lambdai,key,value:key+" -> "+value),...)"['a' -> 1, 'b' -> 2]">>>frombasicco.custom_reprimportiterable_repr>>>tup=("a","b","c",1,2,3)>>>iterable_repr(tup,prefix="<",suffix=">",value_repr=str)'<a, b, c, 1, 2, 3>'default_dirBackport of Python 3’s implementation ofobject.__dir__.This allows for callingsuper().__dir__()from a subclass to leverage the default
implementation.>>>fromsiximportwith_metaclass>>>frombasicco.default_dirimportDefaultDir>>>classClass(DefaultDir):...def__dir__(self):...returnsuper(Class,self).__dir__()...>>>obj=Class()>>>dir(obj)[...]descriptorsConfigurable descriptors.>>>fromsiximportwith_metaclass>>>frombasicco.descriptorsimportREMOVE,Descriptor,Owner>>>classSlotDescriptor(Descriptor):...def__get_required_slots__(self):...return(self.name,)# request a slot with the same name as this...def__get_replacement__(self):...returnREMOVE# remove this descriptor from the class body...>>>classPropDescriptor(Descriptor):...__slots__=("_slot_desc",)...def__init__(self,slot_desc):...super(PropDescriptor,self).__init__()...self._slot_desc=slot_desc...def__get__(self,instance,owner):...ifinstanceisnotNone:...returngetattr(instance,self._slot_desc.name)...returnself...def__set__(self,instance,value):...setattr(instance,self._slot_desc.name,value)...>>>classStuff(Owner):..._foo=SlotDescriptor()..._bar=SlotDescriptor()...foo=PropDescriptor(_foo)...bar=PropDescriptor(_bar)...>>>stuff=Stuff()>>>stuff.foo="foo">>>stuff.bar="bar">>>stuff.foo'foo'>>>stuff.bar'bar'dynamic_classEasily generate classes on the fly. This works best with aBaseclass.
If provided a valid qualified name and module (usescaller_moduleby default), the
class will be pickable/importable.>>>frombasiccoimportBase>>>frombasicco.dynamic_classimportmake_cls>>>classMyClass(object):...DynClass=make_cls("MyClass.DynClass",bases=(Base,),dct={"foo":"bar"})...>>>repr(MyClass.DynClass)"<class '__main__.MyClass.DynClass'>"dynamic_codeGenerate debuggable code on the fly that supports line numbers on tracebacks.>>>frombasicco.dynamic_codeimportmake_function,generate_unique_filename>>>classMyClass(object):...pass...>>>bar='bar'>>># Prepare the script and necessary data.>>>script="\n".join(...(..."def __init__(self):",..."    self.foo = 'bar'",...)...)>>># Gather information.>>>name="__init__">>>owner_name=MyClass.__name__>>>module=MyClass.__module__>>>filename=generate_unique_filename(name,module,owner_name)>>>globs={"bar":bar}>>># Make function and attach it as a method.>>>MyClass.__init__=make_function(name,script,globs,filename,module)>>>obj=MyClass()>>>obj.foo'bar'explicit_hashMetaclass that forces__hash__to be declared whenever__eq__is declared.>>>fromsiximportwith_metaclass>>>frombasicco.explicit_hashimportExplicitHashMeta>>>classAsset(with_metaclass(ExplicitHashMeta,object)):...def__eq__(self,other):...pass...Traceback(mostrecentcalllast):TypeError:declared'__eq__'in'Asset'butdidn't declare '__hash__'fabricate_valueRun a value through a callable factory (or None).>>>frombasicco.fabricate_valueimportfabricate_value>>>fabricate_value(None,3)# no factory, value passthrough3>>>fabricate_value(str,3)# callable factory'3'>>>fabricate_value("str",3)# use an import path'3'>>>fabricate_value(int)# no input value, just the factory itself0func_toolsBackport offunctools.cache,functools.lru_cache, andfunctools.update_wrapperfor Python 2.7.>>>frombasicco.func_toolsimportcache>>>@cache...defcalculate(a,b):...print("calculating...")...returna**b...>>>calculate(2,2)calculating...4>>>calculate(2,2)4get_mroGet consistent MRO amongst different python versions. This works even with generic
classes in Python 2.7.>>>fromsiximportwith_metaclass>>>fromtippoimportGeneric,TypeVar>>>frombasicco.get_mroimportget_mro>>>T=TypeVar("T")>>>classMyGeneric(Generic[T]):...pass...>>>classSubClass(MyGeneric[T]):...pass...>>>classMixed(SubClass[T],MyGeneric[T]):...pass...>>>[c.__name__forcinget_mro(Mixed)]['Mixed','SubClass','MyGeneric','Generic','object']hash_cache_wrapperAn integer subclass that pickles/copies as None. This can be used to avoid serializing
a cached hash value.>>>fromcopyimportcopy>>>frombasicco.hash_cache_wrapperimportHashCacheWrapper>>>hash_cache=HashCacheWrapper(12345)>>>print(hash_cache)12345>>>print(copy(hash_cache))Noneimplicit_hashMetaclass that forces__hash__to None when__eq__is declared.
This is a backport of the default behavior in Python 3.>>>fromsiximportwith_metaclass>>>frombasicco.implicit_hashimportImplicitHashMeta>>>classAsset(with_metaclass(ImplicitHashMeta,object)):...def__eq__(self,other):...pass...>>>Asset.__hash__isNoneTrueimport_pathGenerate importable dot paths and import from them.>>>importitertools>>>frombasicco.import_pathimportget_path,import_path>>>get_path(itertools.chain)'itertools.chain'>>>import_path("itertools.chain")<...'itertools.chain'>>>>frombasicco.import_pathimportextract_generic_paths>>>extract_generic_paths("Tuple[int, str]")('Tuple',('int','str'))init_subclassBackport of the functionality of__init_subclass__from PEP 487 to Python 2.7.
This works for both Python 2 (using__kwargs__) and 3 (using the new class
parameters).>>>frombasicco.init_subclassimportInitSubclass>>>classFoo(InitSubclass):...def__init_subclass__(cls,foo=None,**kwargs):...cls.foo=foo...>>>classBar(Foo):...__kwargs__={"foo":"bar"}# you can specify cls kwargs on py2 like this...>>>Bar.foo'bar'lazy_tupleLazily-evaluated tuple-like structure.>>>frombasicco.lazy_tupleimportLazyTuple>>>defexpensive_generator():...foriinrange(100):...yieldi...>>>lazy_tuple=LazyTuple(expensive_generator())>>>lazy_tuple[4]4locked_classPrevents changing public class attributes.>>>fromsiximportwith_metaclass>>>frombasicco.locked_classimportLockedClassMeta>>>classFoo(with_metaclass(LockedClassMeta,object)):...bar="foo"...>>>Foo.bar="bar"Traceback(mostrecentcalllast):AttributeError:can't set read-only class attribute 'bar'manglingFunctions to mangle/unmangle/extract private names.>>>frombasicco.manglingimportmangle,unmangle,extract>>>mangle("__member","Foo")'_Foo__member'>>>unmangle("_Foo__member","Foo")'__member'>>>extract("_Foo__member")('__member','Foo')mapping_proxyMapping Proxy type (read-only dictionary) for older Python versions.>>>frombasicco.mapping_proxyimportMappingProxyType>>>internal_dict={"foo":"bar"}>>>proxy_dict=MappingProxyType(internal_dict)>>>proxy_dict["foo"]'bar'namespaceWraps a dictionary/mapping and provides attribute-style access to it.>>>frombasicco.namespaceimportNamespace>>>ns=Namespace({"bar":"foo"})>>>ns.bar'foo'>>>frombasicco.namespaceimportMutableNamespace>>>ns=MutableNamespace({"bar":"foo"})>>>ns.foo="bar">>>ns.foo'bar'>>>ns.bar'foo'Also provides aNamespacedMetametaclass that adds a__namespaceprotected class
attribute that is unique to each class.>>>fromsiximportwith_metaclass>>>frombasicco.namespaceimportNamespacedMeta>>>classAsset(with_metaclass(NamespacedMeta,object)):...@classmethod...defset_class_value(cls,value):...cls.__namespace.value=value......@classmethod...defget_class_value(cls):...returncls.__namespace.value...>>>Asset.set_class_value("foobar")>>>Asset.get_class_value()'foobar'null_contextBackport ofcontextlib.nullcontextfor Python 2.7.>>>frombasicco.null_contextimportnull_context>>>frombasicco.suppress_exceptionimportsuppress_exception>>>defmyfunction(arg,ignore_exceptions=False):...ifignore_exceptions:...# Use suppress_exception to ignore all exceptions....cm=suppress_exception(Exception)...else:...# Do not ignore any exceptions, cm has no effect....cm=null_context()...withcm:...pass# Do something...obj_stateGet/update the state of an object, slotted or not (works even in Python 2.7).>>>frombasicco.obj_stateimportget_state>>>classSlotted(object):...__slots__=("foo","bar")...def__init__(self,foo,bar):...self.foo=foo...self.bar=bar...>>>slotted=Slotted("a","b")>>>sorted(get_state(slotted).items())[('bar','b'),('foo','a')]Also provides aReducibleMetametaclass that allows for pickling instances of slotted
classes in Python 2.7.qualnamePython 2.7 compatible way of getting the qualified name. Based onwbolster/qualname.
Also provides aQualnamedMetametaclass with a__qualname__class property for
Python 2.7.recursive_reprDecorator that prevents infinite recursion for__repr__methods.>>>frombasicco.recursive_reprimportrecursive_repr>>>classMyClass(object):......@recursive_repr...def__repr__(self):...return"MyClass<{!r}>".format(self)...>>>my_obj=MyClass()>>>repr(my_obj)'MyClass<...>'runtime_finalRuntime-checked version of thetyping.finaldecorator.Can be used on methods, properties, classmethods, staticmethods, and classes that haveRuntimeFinalMetaas a metaclass. It is also recognized by static type checkers and
prevents subclassing and/or member overriding during runtime:>>>fromsiximportwith_metaclass>>>frombasicco.runtime_finalimportRuntimeFinalMeta,final>>>@final...classAsset(with_metaclass(RuntimeFinalMeta,object)):...pass...>>>classSubAsset(Asset):...pass...Traceback(mostrecentcalllast):TypeError:can't subclass final class 'Asset'>>>fromsiximportwith_metaclass>>>frombasicco.runtime_finalimportRuntimeFinalMeta,final>>>classAsset(with_metaclass(RuntimeFinalMeta,object)):...@final...defmethod(self):...pass...>>>classSubAsset(Asset):...defmethod(self):...passTraceback(mostrecentcalllast):TypeError:'SubAsset'overridesfinalmember'method'definedby'Asset'>>>fromsiximportwith_metaclass>>>frombasicco.runtime_finalimportRuntimeFinalMeta,final>>>classAsset(with_metaclass(RuntimeFinalMeta,object)):...@property...@final...defprop(self):...pass...>>>classSubAsset(Asset):...@property...defprop(self):...passTraceback(mostrecentcalllast):TypeError:'SubAsset'overridesfinalmember'prop'definedby'Asset'safe_not_equalsBackport of the default Python 3 behavior of__ne__behavior for Python 2.7.>>>fromsiximportwith_metaclass>>>frombasicco.safe_not_equalsimportSafeNotEqualsMeta>>>classClass(with_metaclass(SafeNotEqualsMeta,object)):...pass...>>>obj_a=Class()>>>obj_b=Class()>>>assert(obj_a==obj_a)isnot(obj_a!=obj_a)>>>assert(obj_b==obj_b)isnot(obj_b!=obj_b)>>>assert(obj_a==obj_b)isnot(obj_a!=obj_b)safe_reprDecorator that prevents__repr__methods from raising exceptions and return a default
representation instead.>>>frombasicco.safe_reprimportsafe_repr>>>classClass(object):...@safe_repr...def__repr__(self):...raiseRuntimeError("oh oh")...>>>obj=Class()>>>repr(obj)"<__main__.Class object at ...; repr failed due to 'RuntimeError: oh oh'>"sentinelEasily define singleton sentinel values and their type (for type hinting).>>>frombasicco.sentinelimportSentinelType>>>classMissingType(SentinelType):...def__repr__(self):...return"MISSING"...>>>MISSING=MissingType()>>>MISSINGMISSING>>>MissingType()isMISSINGTrue>>>isinstance(MISSING,MissingType)Trueset_nameBackport of the functionality of__set_name__from PEP 487 to Python 2.7.>>>frombasicco.set_nameimportSetName>>>classAttribute(object):...def__set_name__(self,owner,name):...self.owner=owner...self.name=name...>>>classCollection(SetName):...foo=Attribute()...>>>Collection.foo.ownerisCollectionTrue>>>Collection.foo.name'foo'suppress_exceptionBackport ofcontextlib.suppressfor Python 2.7.
Seenull_contextfor an example usage.type_checkingRuntime type checking with support for import paths and type hints.>>>fromtippoimportMapping,Literal>>>fromitertoolsimportchain>>>frombasicco.type_checkingimportis_instance>>>classSubChain(chain):...pass...>>>is_instance(3,int)True>>>is_instance(3,(chain,int))True>>>is_instance(3,())False>>>is_instance(SubChain(),"itertools.chain")True>>>is_instance(chain(),"itertools.chain",subtypes=False)True>>>is_instance(SubChain(),"itertools.chain",subtypes=False)False>>>is_instance({"a":1,"b":2},Mapping[str,int])True>>>is_instance("PRE",Literal["PRE","POST"])Trueunique_iteratorIterator that yields unique values.>>>frombasicco.unique_iteratorimportunique_iterator>>>list(unique_iterator([1,2,3,3,4,4,5]))[1,2,3,4,5]