py-data-digger


Namepy-data-digger JSON
Version 0.2.0 PyPI version JSON
download
home_pageNone
SummarySafely navigate through unsafe data: dicts, tuples, lists, strings and objects
upload_time2024-10-09 14:34:23
maintainerNone
docs_urlNone
authorRamon G. Dias
requires_python<4.0,>=3.9
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![example workflow](https://github.com/RamonGiovane/py-data-digger/actions/workflows/python-app.yml/badge.svg?branch=main)

# 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": "![example workflow](https://github.com/RamonGiovane/py-data-digger/actions/workflows/python-app.yml/badge.svg?branch=main)\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"
}
        
Elapsed time: 0.30269s