{"id":25902999,"url":"https://github.com/iann838/nullsafe-python","last_synced_at":"2025-03-03T03:18:02.712Z","repository":{"id":49295868,"uuid":"377987433","full_name":"iann838/nullsafe-python","owner":"iann838","description":"Null safe support for Python","archived":false,"fork":false,"pushed_at":"2021-12-20T01:02:43.000Z","size":24,"stargazers_count":19,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-27T01:18:18.169Z","etag":null,"topics":["none-aware","null-safe","null-safety","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iann838.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-17T23:53:59.000Z","updated_at":"2024-08-17T19:21:02.000Z","dependencies_parsed_at":"2022-09-05T05:10:34.581Z","dependency_job_id":null,"html_url":"https://github.com/iann838/nullsafe-python","commit_stats":null,"previous_names":["iann838/nullsafe-python","paaksing/nullsafe-python"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iann838%2Fnullsafe-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iann838%2Fnullsafe-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iann838%2Fnullsafe-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iann838%2Fnullsafe-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iann838","download_url":"https://codeload.github.com/iann838/nullsafe-python/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241600463,"owners_count":19988716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["none-aware","null-safe","null-safety","python"],"created_at":"2025-03-03T03:18:02.233Z","updated_at":"2025-03-03T03:18:02.700Z","avatar_url":"https://github.com/iann838.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Null Safe Python\n\nNull safe support for Python.\n\n## Installation\n\n```bash\npip install nullsafe\n```\n\n## Quick Start\n\nDummy Class\n\n```python\nclass Dummy:\n    pass\n```\n\nNormal Python code:\n\n```python\no = Dummy()\n\ntry:\n    value = o.inexistent\nexcept AttributeError:\n    print(\"oops\")\n```\n\nWith nullsafe:\n\n```python\nfrom nullsafe import undefined, _\n\no = Dummy()\n\nvalue = _(o).inexistent\n\nif value is undefined:\n    print(\"oops\")\n```\n\nNote that `if not value` and `if value == None` also works.\n\n# Documentation\n\n## Basics\n\nThere are 5 values importable in nullsafe root:\n\n### class `NullSafeProxy: (o: T)`\n\nReceives an object `o` on instantiation.\n\nProxy class for granting nullsafe abilities to an object.\n\n### class `NullSafe: ()`\n\nNo argument needed.\n\nNullish class with nullsafe abilities. Instances will have a falsy boolean evaluation, equity comparison (`==`) to `None` and `isinstance` of `NullSafe` returns `True`, otherwise `False`. Identity comparison (`is`) to `None` will return `False`. It also has a `__call__` method that always returns `undefined`.\n\n### variable `undefined: NullSafe`\n\nInstance of `Nullsafe`, this instance will be returned for all nullish access in a proxied object, enabling identity comparison `value is undefined` for code clarity.\n\n### function `nullsafe: (o: T) -\u003e T | NullSafe | NullSafeProxy[T]`\n\nReceives an object `o` as argument.\n\nHelper function that checks if object is nullish and return the proxied object.\n\nreturns `undefined` if `o` is `None` or `undefined`, otherwise returns the proxied object `NullSafeProxy[T]`.\n\nThis function is **generic typed** (`(o: T) -\u003e T`), code autocompletions and linters functionalities will remain. Disclaimer: If the object is not typed before proxying, it obviously won't come out typed out of the blue.\n\n### function `_: (o: T) -\u003e T | NullSafe | NullSafeProxy[T]` (alias to `nullsafe`)\n\nAlias to `nullsafe`, used for better code clarity.\n\n## Implementation\n\nNullsafe 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 will \"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`.\n\n## Import\n\n```python\nfrom nullsafe import undefined, _\n```\n\n## Usage\n\nVarious ways of using nullsafe.\n\nThe examples shown will be using the `_` import instead of `nullsafe` for code clarity. For better understanding, the Javascript equivalents will be shown as comments.\n\n### Null safe attribute access\n\nProxied object doing a possibly `AttributeError` access.\n\n```python\no = Dummy()\n\n# o.inexistent\nassert _(o).inexistent is undefined\nassert _(o).inexistent == None   # undefined == None\nassert not _(o).inexistent       # bool(undefined) == False\n\n# o.inexistent?.nested\nassert _(o).inexistent.nested is undefined\n\n# o.existent.inexistent?.nested\nassert _(o.existent).inexistent.nested is undefined\n\n# o.maybe?.inexistent?.nested\nassert _(_(o).maybe).inexistent.nested is undefined\n\n# o.inexistent?.inexistcall(\"anything\").inexistent.nested().final\nassert _(o).inexistent.inexistcall(\"anything\").inexistent.nested().final is undefined\n```\n\n### Null safe item access\n\nProxied object doing a possibly `KeyError` access.\n\n```python\no = Dummy() # dict works too !\n\n# o.inexistent\nassert _(o)[\"inexistent\"] is undefined\nassert _(o)[\"inexistent\"] == None    # undefined == None\nassert not _(o)[\"inexistent\"]        # bool(undefined) == False\n\n# o.inexistent?.nested\nassert _(o)[\"inexistent\"][\"nested\"] is undefined\n\n# o.existent.inexistent?.nested\nassert _(o[\"existent\"])[\"inexistent\"][\"nested\"] is undefined\n\n# o.maybe?.inexistent?.nested\nassert _(_(o)[\"maybe\"])[\"inexistent\"][\"nested\"] is undefined\n\n# o.inexistent?.inexistcall(\"anything\").inexistent.nested().final\nassert _(o)[\"inexistent\"][\"inexistcall\"](\"anything\")[\"inexistent\"][\"nested\"]()[\"final\"] is undefined\n```\n\n### Null safe post evaluation\n\nPossibly `None` or `undefined` object doing possibly `AttributeError` or `KeyError` access.\n\nNote: This only works if the seeking value is accessible, see [limitations](#post-evaluation)\n\n```python\no = Dummy() # dict works too !\no.nay = None\n\n# o.nay?.inexistent\nassert _(o.nay).inexistent is undefined\nassert _(o.nay).inexistent == None   # undefined == None\nassert not _(o.nay).inexistent       # bool(undefined) == False\n\n# o.nay?.inexistent.nested\nassert _(o.nay).inexistent.nested is undefined\n\n# o.nay?.inexistent().nested\nassert _(o.nay).inexistent().nested is undefined\n```\n\n```python\no = Dummy() # dict works too !\no[\"nay\"] = None\n\n# o.nay?.inexistent\nassert _(o[\"nay\"])[\"inexistent\"] is undefined\n\n# o.nay?.inexistent.nested\nassert _(o[\"nay\"])[\"inexistent\"][\"nested\"] is undefined\n\n# o.nay?.inexistent().nested\nassert _(o[\"nay\"])[\"inexistent\"]()[\"nested\"] is undefined\n```\n\n### Combined usage\n\nOf course you can combine different styles.\n\n```python\nassert _(o).inexistent[\"inexistent\"].inexistent.inexistent[\"inexistent\"][\"inexistent\"] is undefined\n```\n\n## Limitations\n\nList of limitations that you may encounter.\n\n### `undefined` behavior\n\n`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.\n\n```python\nval = _(o).inexistent\n\nassert val.another_inexistent is undefined\n```\n\n### Post evaluation\n\nIn 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](#null-safe-post-evaluation)), so it must proxy the instance that may or may not contain the attribute or key before accessing.\n\n```python\ntry:\n    val = _(o.inexistent).nested # AttributeError: '\u003ctype\u003e' object has no attribute 'inexistent'\nexcept AttributeError:\n    assert True\nassert _(o).inexistent.nested is undefined\n```\n\n## Contributing\n\nContributions welcomed ! Make sure it passes current tests tho.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiann838%2Fnullsafe-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiann838%2Fnullsafe-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiann838%2Fnullsafe-python/lists"}