Null safe support for Python
Project description
Null Safe Python
Null safe support for Python.
Installation
pip install nullsafe
Quick Start
Dummy Class
class Dummy:
pass
Normal Python code:
o = Dummy()
try:
value = o.inexistent
print("accessed")
except AttributeError:
value = None
With nullsafe:
from nullsafe import undefined, _
o = Dummy()
value = _(o).inexistent
if value is not undefined:
print("accessed")
Documentation
Basics
There are 5 values importable in nullsafe root:
class NullSafeProxy: (o: T)
Receives an object o on instantiation.
Proxy class for granting nullsafe abilities to an object.
class NullSafe: ()
No argument needed.
Nullish class with with nullsafe abilities. Instances will have a falsy boolean evaluation, equity comparison (==) to None and instance of NullSafe returns True, otherwise False. Identity comparison (is) to None will return False. It also has a __call__ method that always returns undefined.
variable undefined: NullSafe
Instance of Nullsafe, this instance will be returned for all nullish access in a proxied object, enabling identity comparison value is undefined for code clarity.
function nullsafe: (o: T) -> T | NullSafe | NullSafeProxy[T]
Receives an object o as argument.
Helper function that checks if object is nullish and return the proxied object.
return undefined if o is None or undefined, otherwise return the proxied object NullSafeProxy[T].
This function is generic 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 to nullsafe)
Alias to nullsafe, used for better code clarity.
The examples shown will be using _ instead of nullsafe for code clarity. For better understanding, the Javascript equivalents will be shown as comments.
Implementation
Nullsafe abilities are granted after proxying an object through NullSafeProxy. To proxy an object pass it through _() or nullsafe(). Due to language limitation, the implementation does not follow the "return the first nullish value in chain", instead it "extend undefined (custom nullish value) until the end of chain". Inexistent values of a proxied object and its subsequent values in chain will return undefined.
Import
from nullsafe import undefined, _
Usage
There are various way to get a nullsafe proxied object.
Null safe attribute access
Proxied object doing a possibly AttributeError access.
o = Dummy()
# o.inexistent
assert _(o).inexistent is undefined
assert _(o).inexistent == None # undefined == None
assert not _(o).inexistent # bool(undefined) == False
# o.inexistent?.nested
assert _(o).inexistent.nested is undefined
# o.existent.inexistent?.nested
assert _(o.existent).inexistent.nested is undefined
# o.maybe?.inexistent?.nested
assert _(_(o).maybe).inexistent.nested is undefined
# o.inexistent?.inexistcall("anything").inexistent.nested().final
assert _(o).inexistent.inexistcall("anything").inexistent.nested().final is undefined
Null safe item access
Proxied object doing a possibly KeyError access.
o = Dummy() # dict works too !
# o.inexistent
assert _(o)["inexistent"] is undefined
assert _(o)["inexistent"] == None # undefined == None
assert not _(o)["inexistent"] # bool(undefined) == False
# o.inexistent?.nested
assert _(o)["inexistent"]["nested"] is undefined
# o.existent.inexistent?.nested
assert _(o["existent"])["inexistent"]["nested"] is undefined
# o.maybe?.inexistent?.nested
assert _(_(o)["maybe"])["inexistent"]["nested"] is undefined
# o.inexistent?.inexistcall("anything").inexistent.nested().final
assert _(o)["inexistent"]["inexistcall"]("anything")["inexistent"]["nested"]()["final"] is undefined
Null safe post evaluation
Possibly None or undefined object doing possibly AttributeError or KeyError access.
Note: This only works if the seeking value is accessible, see limitations
o = Dummy() # dict works too !
o.nay = None
# o.nay?.inexistent
assert _(o.nay).inexistent is undefined
assert _(o.nay).inexistent == None # undefined == None
assert not _(o.nay).inexistent # bool(undefined) == False
# o.nay?.inexistent.nested
assert _(o.nay).inexistent.nested is undefined
# o.nay?.inexistent().nested
assert _(o.nay).inexistent().nested is undefined
o = Dummy() # dict works too !
o["nay"] = None
# o.nay?.inexistent
assert _(o["nay"])["inexistent"] is undefined
# o.nay?.inexistent.nested
assert _(o["nay"])["inexistent"]["nested"] is undefined
# o.nay?.inexistent().nested
assert _(o["nay"])["inexistent"]()["nested"] is undefined
Combined usage
Of course you can combine different styles.
assert _(o).inexistent["inexistent"].inexistent.inexistent["inexistent"]["inexistent"] is undefined
Limitations
List of limitations that you may encounter.
undefined behavior
undefined is actually an instance of NullSafe, 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).inexistent
assert val.another_inexistent is undefined
Post evaluation
In other languages like Javascript, it checks for each item in the chain and return undefined on the first nullish value, which in fact is post-evaluated. This is not possible in python because it raises an AttributeError or KeyError on access attempt, unless it returns None (see one 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'
except AttributeError:
assert True
assert _(o).inexistent.nested is undefined
Contributing
Contributions welcomed ! Make sure it passes current tests tho.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nullsafe-0.2.1.tar.gz.
File metadata
- Download URL: nullsafe-0.2.1.tar.gz
- Upload date:
- Size: 6.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.5.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.1 CPython/3.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8377d21eed984e983e31d278cc6e586d53780bbb570c9272ca4114970c7405be
|
|
| MD5 |
5189c9a83805866532a2d68cb4dd82ea
|
|
| BLAKE2b-256 |
9f9a26f1de6bfdae5be85f557c4182ca73e8674ccbaf38ee941cb67c9c06a6fd
|
File details
Details for the file nullsafe-0.2.1-py3-none-any.whl.
File metadata
- Download URL: nullsafe-0.2.1-py3-none-any.whl
- Upload date:
- Size: 6.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.5.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.1 CPython/3.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0822487ff17902bf883125172b42575d475d75ed9e8c7ac73a8d8550d3f6db8
|
|
| MD5 |
13fa65574c593f2091e9d24df45587a1
|
|
| BLAKE2b-256 |
a02cb0065f31bdf35a0b68e3518004b0ad6fab6ca1df02faf6a474801fa5ce88
|