Package: nullsafe
Null Safe PythonNull safe support for Python.InstallationpipinstallnullsafeQuick StartDummy ClassclassDummy:passNormal Python code:o=Dummy()try:value=o.inexistentprint("accessed")exceptAttributeError:value=NoneWith nullsafe:fromnullsafeimportundefined,_o=Dummy()value=_(o).inexistentifvalueisnotundefined:print("accessed")DocumentationBasicsThere are 5 values importable in nullsafe root:classNullSafeProxy: (o: T)Receives an objectoon instantiation.Proxy class for granting nullsafe abilities to an object.classNullSafe: ()No argument needed.Nullish class with with nullsafe abilities. Instances will have a falsy boolean evaluation, equity comparison (==) toNoneand instance ofNullSafereturnsTrue, otherwiseFalse. Identity comparison (is) toNonewill returnFalse. It also has a__call__method that always returnsundefined.variableundefined: NullSafeInstance ofNullsafe, this instance will be returned for all nullish access in a proxied object, enabling identity comparisonvalue is undefinedfor code clarity.functionnullsafe: (o: T) -> T | NullSafe | NullSafeProxy[T]Receives an objectoas argument.Helper function that checks if object is nullish and return the proxied object.returnundefinedifoisNoneorundefined, otherwise return the proxied objectNullSafeProxy[T].This function isgeneric typed((o: T) -> T), code autocompletions and linters functionalities will remain. Disclaimer: If the object was not typed before proxy, it obviously won't come out typed out of the blue.function_: (o: T) -> T | NullSafe | NullSafeProxy[T](alias tonullsafe)Alias tonullsafe, used for better code clarity.The examples shown will be using_instead ofnullsafefor code clarity. For better understanding, the Javascript equivalents will be shown as comments.ImplementationNullsafe abilities are granted after proxying an object throughNullSafeProxy. To proxy an object pass it through_()ornullsafe(). Due to language limitation, the implementation does not follow the "return the first nullish value in chain", instead it "extendundefined(custom nullish value) until the end of chain". Inexistent values of a proxied object and its subsequent values in chain will returnundefined.Importfromnullsafeimportundefined,_UsageThere are various way to get a nullsafe proxied object.Null safe attribute accessProxied object doing a possiblyAttributeErroraccess.o=Dummy()# o.inexistentassert_(o).inexistentisundefinedassert_(o).inexistent==None# undefined == Noneassertnot_(o).inexistent# bool(undefined) == False# o.inexistent?.nestedassert_(o).inexistent.nestedisundefined# o.existent.inexistent?.nestedassert_(o.existent).inexistent.nestedisundefined# o.maybe?.inexistent?.nestedassert_(_(o).maybe).inexistent.nestedisundefined# o.inexistent?.inexistcall("anything").inexistent.nested().finalassert_(o).inexistent.inexistcall("anything").inexistent.nested().finalisundefinedNull safe item accessProxied object doing a possiblyKeyErroraccess.o=Dummy()# dict works too !# o.inexistentassert_(o)["inexistent"]isundefinedassert_(o)["inexistent"]==None# undefined == Noneassertnot_(o)["inexistent"]# bool(undefined) == False# o.inexistent?.nestedassert_(o)["inexistent"]["nested"]isundefined# o.existent.inexistent?.nestedassert_(o["existent"])["inexistent"]["nested"]isundefined# o.maybe?.inexistent?.nestedassert_(_(o)["maybe"])["inexistent"]["nested"]isundefined# o.inexistent?.inexistcall("anything").inexistent.nested().finalassert_(o)["inexistent"]["inexistcall"]("anything")["inexistent"]["nested"]()["final"]isundefinedNull safe post evaluationPossiblyNoneorundefinedobject doing possiblyAttributeErrororKeyErroraccess.Note: This only works if the seeking value is accessible, seelimitationso=Dummy()# dict works too !o.nay=None# o.nay?.inexistentassert_(o.nay).inexistentisundefinedassert_(o.nay).inexistent==None# undefined == Noneassertnot_(o.nay).inexistent# bool(undefined) == False# o.nay?.inexistent.nestedassert_(o.nay).inexistent.nestedisundefined# o.nay?.inexistent().nestedassert_(o.nay).inexistent().nestedisundefinedo=Dummy()# dict works too !o["nay"]=None# o.nay?.inexistentassert_(o["nay"])["inexistent"]isundefined# o.nay?.inexistent.nestedassert_(o["nay"])["inexistent"]["nested"]isundefined# o.nay?.inexistent().nestedassert_(o["nay"])["inexistent"]()["nested"]isundefinedCombined usageOf course you can combine different styles.assert_(o).inexistent["inexistent"].inexistent.inexistent["inexistent"]["inexistent"]isundefinedLimitationsList of limitations that you may encounter.undefinedbehaviorundefinedis actually an instance ofNullSafe, the actual mechanism used for nullsafe chaining, it cannot self rip the nullsafe functionality when the chain ends (because it doesn't know), so the following instruction is technically correct but probably not the wanted behavior.val=_(o).inexistentassertval.another_inexistentisundefinedPost evaluationIn other languages like Javascript, it checks for each item in the chain and returnundefinedon the first nullish value, which in fact is post-evaluated. This is not possible in python because it raises anAttributeErrororKeyErroron access attempt, unless it returnsNone(seeone of the available usage), so it must proxy the instance that may contain the attr or key before accessing.try:val=_(o.inexistent).nested# AttributeError: '<type>' object has no attribute 'inexistent'exceptAttributeError:assertTrueassert_(o).inexistent.nestedisundefinedContributingContributions welcomed ! Make sure it passes current tests tho.