Name | py-data-digger JSON |
Version |
0.2.0
JSON |
| download |
home_page | None |
Summary | Safely navigate through unsafe data: dicts, tuples, lists, strings and objects |
upload_time | 2024-10-09 14:34:23 |
maintainer | None |
docs_url | None |
author | Ramon G. Dias |
requires_python | <4.0,>=3.9 |
license | MIT |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|

# py-data-digger
Safely navigate through unsafe data: dicts, tuples, lists, strings and objects
Inspired on Ruby's [dig](https://www.rubydoc.info/stdlib/core/Hash:dig).
```
pip install py-data-digger
```
## Why?
### TLDR:
Sometimes you don't want to deal with Python exceptions when accessing lists and dicts. If the data you need isn't there, you just want to **move on**...
**No ifs, no try-excepts!**
```python
from py_data_digger import dig
components: list | None = dig(nasty_dict, "machines", 0, "engine", "components")
```
### Detailed explanation
In some occasions (like when web scrapping) you need to grab some data from a deeply nested structure like this:
```python
nasty_dict = {
"machines": [
{
"machine_id": "1234567890",
"engine": {
"id": "321abcde",
"name": "Motor XPTO",
"components": [
{"id": "0942323", "name": "Cog"},
{"id": "1642723", "name": "Piston"},
{"id": "8412321", "name": "Bar", "extras": ["Foo"]},
],
},
}
]
}
```
Suppose we want to take the list of components of the engine of a machine (the only present).
### 🚨 The unsafe strategy:
```python
components: list = nasty_dict["machines"][0]["engine"]["components"]
```
This is unsafe because it is highly prone to raise `IndexError`, `KeyError`, `TypeError` if you use the wrong key/index or if the data just isn't there.
### 😴 The safe (but boring) strategy:
```python
machines: list | None = nasty_dict.get("machines", None)
machine: dict | None = next(iter(machines), None) if machines else None
engine: dict | None = machine.get("engine", None) if machine is not None else None
components: list | None: engine.get("components", None) if engine is not None else None
```
This is not only tedious but labourious!
At least, it's safe. We would not raise errors to break our code.
## Introducing `dig`
With this tool we may quickly and securely navigate through all sorts of nested data.
Let's consider the `nasty_dict` from the past section and that we also want to access the list of components.
```python
from py_data_digger import dig
components: list | None = dig(nasty_dict, "machines", 0, "engine", "components")
```
That's it! All the access problems are solved. If the data you want isn't there, it returns `None` and you can just **move on**!
```python
components: list | None = dig(nasty_dict, "machines", 0, "engine_2", "components")
if components is None:
return None
```
## Introducing `seek`
Not satisfied with `None` returns?
The `seek` function works just like `dig`, but it will raise an error if the path informed could not be found.
```python
from py_data_digger import seek
components: list = seek(nasty_dict, "machines", 0, "engine_2", "components")
>>> SeekError: Data digger can't go any further: KeyError
Path traveled: dict -> machines -> 0 -> engine_2
```
The cool thing is, you would need to handle just one exception (`SeekError`). It also shows where it failed to seek 😎
## Seeking/digging objects
And there is more!
If you also want to look inside object attributes, you may do it by passing a special flag.
This way it will be compatible with any nested objects like **Pydantic** models and **dataclasses**!
```python
person = Person(name='John Doe')
my_dict = {
'item_with_object': person
}
dig(my_dict, 'item_with_object', 'name', dig_objects=True)
>>> 'John Doe'
dig(my_dict, 'item_with_object', 'age', dig_objects=True)
>>> None
seek(my_dict, 'item_with_object', 'name', seek_objects=True)
>>> 'John Doe'
seek(my_dict, 'item_with_object', 'age', seek_objects=True)
>>> SeekError
```
⚠️ The special flag is required because attribute names may conflict with other mapped keys. Use with caution.
Raw data
{
"_id": null,
"home_page": null,
"name": "py-data-digger",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.9",
"maintainer_email": null,
"keywords": null,
"author": "Ramon G. Dias",
"author_email": "ramon.giovane@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/65/21/c78aaf63759acb7b44a81ec92bbad23a27ddb58db3fefc72b52b9f9fdf6a/py_data_digger-0.2.0.tar.gz",
"platform": null,
"description": "\n\n# py-data-digger\nSafely navigate through unsafe data: dicts, tuples, lists, strings and objects\n\nInspired on Ruby's [dig](https://www.rubydoc.info/stdlib/core/Hash:dig).\n\n```\npip install py-data-digger\n```\n\n## Why?\n### TLDR: \nSometimes you don't want to deal with Python exceptions when accessing lists and dicts. If the data you need isn't there, you just want to **move on**...\n\n**No ifs, no try-excepts!**\n\n```python\nfrom py_data_digger import dig\n\ncomponents: list | None = dig(nasty_dict, \"machines\", 0, \"engine\", \"components\")\n```\n\n### Detailed explanation\nIn some occasions (like when web scrapping) you need to grab some data from a deeply nested structure like this:\n```python\nnasty_dict = {\n \"machines\": [\n {\n \"machine_id\": \"1234567890\",\n \"engine\": {\n \"id\": \"321abcde\",\n \"name\": \"Motor XPTO\",\n \"components\": [\n {\"id\": \"0942323\", \"name\": \"Cog\"},\n {\"id\": \"1642723\", \"name\": \"Piston\"},\n {\"id\": \"8412321\", \"name\": \"Bar\", \"extras\": [\"Foo\"]},\n ],\n },\n }\n ]\n}\n```\n\nSuppose we want to take the list of components of the engine of a machine (the only present).\n\n### \ud83d\udea8 The unsafe strategy:\n```python\ncomponents: list = nasty_dict[\"machines\"][0][\"engine\"][\"components\"]\n```\n\nThis is unsafe because it is highly prone to raise `IndexError`, `KeyError`, `TypeError` if you use the wrong key/index or if the data just isn't there.\n\n### \ud83d\ude34 The safe (but boring) strategy:\n```python\nmachines: list | None = nasty_dict.get(\"machines\", None)\nmachine: dict | None = next(iter(machines), None) if machines else None\nengine: dict | None = machine.get(\"engine\", None) if machine is not None else None\ncomponents: list | None: engine.get(\"components\", None) if engine is not None else None\n \n```\n\nThis is not only tedious but labourious!\nAt least, it's safe. We would not raise errors to break our code.\n\n\n## Introducing `dig`\nWith this tool we may quickly and securely navigate through all sorts of nested data.\n\nLet's consider the `nasty_dict` from the past section and that we also want to access the list of components.\n```python\nfrom py_data_digger import dig\n\ncomponents: list | None = dig(nasty_dict, \"machines\", 0, \"engine\", \"components\")\n```\n\nThat's it! All the access problems are solved. If the data you want isn't there, it returns `None` and you can just **move on**!\n```python\ncomponents: list | None = dig(nasty_dict, \"machines\", 0, \"engine_2\", \"components\")\nif components is None:\n return None\n```\n\n## Introducing `seek`\nNot satisfied with `None` returns?\n\nThe `seek` function works just like `dig`, but it will raise an error if the path informed could not be found.\n\n```python\nfrom py_data_digger import seek\n\n\ncomponents: list = seek(nasty_dict, \"machines\", 0, \"engine_2\", \"components\")\n>>> SeekError: Data digger can't go any further: KeyError\nPath traveled: dict -> machines -> 0 -> engine_2\n```\n\nThe cool thing is, you would need to handle just one exception (`SeekError`). It also shows where it failed to seek \ud83d\ude0e\n\n## Seeking/digging objects\nAnd there is more!\nIf you also want to look inside object attributes, you may do it by passing a special flag.\nThis way it will be compatible with any nested objects like **Pydantic** models and **dataclasses**!\n```python\n person = Person(name='John Doe')\n my_dict = {\n 'item_with_object': person\n }\n\n dig(my_dict, 'item_with_object', 'name', dig_objects=True)\n >>> 'John Doe'\n\n dig(my_dict, 'item_with_object', 'age', dig_objects=True)\n >>> None\n\n seek(my_dict, 'item_with_object', 'name', seek_objects=True)\n >>> 'John Doe'\n\n seek(my_dict, 'item_with_object', 'age', seek_objects=True)\n >>> SeekError\n```\n\n\u26a0\ufe0f The special flag is required because attribute names may conflict with other mapped keys. Use with caution.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Safely navigate through unsafe data: dicts, tuples, lists, strings and objects",
"version": "0.2.0",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4fa428adc00cf2d8e7152e9395db74deb219276d77effbb3e55bc6a35741ad78",
"md5": "85531ce0c70b072470b53058a24042d8",
"sha256": "67460d16c8d6a73c4d374687b8afa2d4b4c03b31a01cfba121c13a0178d24cc7"
},
"downloads": -1,
"filename": "py_data_digger-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "85531ce0c70b072470b53058a24042d8",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.9",
"size": 5068,
"upload_time": "2024-10-09T14:34:21",
"upload_time_iso_8601": "2024-10-09T14:34:21.596985Z",
"url": "https://files.pythonhosted.org/packages/4f/a4/28adc00cf2d8e7152e9395db74deb219276d77effbb3e55bc6a35741ad78/py_data_digger-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6521c78aaf63759acb7b44a81ec92bbad23a27ddb58db3fefc72b52b9f9fdf6a",
"md5": "5155000c32307da1784513c781edf7a9",
"sha256": "b1e641b07244a075143991d70f3e4cb58b7235d8df24144f3c7806787e86dfb4"
},
"downloads": -1,
"filename": "py_data_digger-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "5155000c32307da1784513c781edf7a9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.9",
"size": 4445,
"upload_time": "2024-10-09T14:34:23",
"upload_time_iso_8601": "2024-10-09T14:34:23.277237Z",
"url": "https://files.pythonhosted.org/packages/65/21/c78aaf63759acb7b44a81ec92bbad23a27ddb58db3fefc72b52b9f9fdf6a/py_data_digger-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-09 14:34:23",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "py-data-digger"
}