Skip to content

Commit b1a3bc0

Browse files
authored
Improve README.md
1 parent 66dac60 commit b1a3bc0

File tree

1 file changed

+86
-51
lines changed

1 file changed

+86
-51
lines changed

README.md

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
![example workflow](https://github.com/RamonGiovane/py-data-digger/actions/workflows/python-app.yml/badge.svg?branch=main)
22

33
# py-data-digger
4-
Safely navigate through unsafe data: dicts, tuples, lists, strings and objects
54

6-
Inspired on Ruby's [dig](https://www.rubydoc.info/stdlib/core/Hash:dig).
5+
Safely navigate through unsafe data structures: dictionaries, tuples, lists, strings, and even custom objects.
76

8-
```
7+
Inspired by Ruby’s [`dig`](https://www.rubydoc.info/stdlib/core/Hash:dig).
8+
9+
```bash
910
pip install py-data-digger
1011
```
1112

12-
## Why?
13-
### TLDR:
14-
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**...
13+
---
1514

16-
**No ifs, no try-excepts!**
15+
## 💡 Why?
16+
### TL;DR:
17+
Sometimes, you just don’t want to deal with Python exceptions when accessing nested lists and dictionaries.
18+
If the data isn’t there, you just want to **move on** — no `if`s, no `try`-`except`s!
1719

1820
```python
1921
from py_data_digger import dig
2022

2123
components: list | None = dig(nasty_dict, "machines", 0, "engine", "components")
2224
```
2325

24-
### Detailed explanation
25-
In some occasions (like when web scrapping) you need to grab some data from a deeply nested structure like this:
26+
---
27+
28+
### 🧠 A more detailed example
29+
30+
When web scraping or working with APIs, you often deal with deeply nested structures like this:
31+
2632
```python
2733
nasty_dict = {
2834
"machines": [
@@ -42,82 +48,111 @@ nasty_dict = {
4248
}
4349
```
4450

45-
Suppose we want to take the list of components of the engine of a machine (the only present).
51+
Let’s say we want to extract the list of engine components.
52+
53+
---
54+
55+
### 🚨 The unsafe way:
4656

47-
### 🚨 The unsafe strategy:
4857
```python
49-
components: list = nasty_dict["machines"][0]["engine"]["components"]
58+
components = nasty_dict["machines"][0]["engine"]["components"]
5059
```
5160

52-
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.
61+
This can raise `KeyError`, `IndexError`, or `TypeError` if any part of the structure is missing or malformed.
62+
63+
---
64+
65+
### 😴 The safe (but verbose) way:
5366

54-
### 😴 The safe (but boring) strategy:
5567
```python
56-
machines: list | None = nasty_dict.get("machines")
57-
machine: dict | None = next(iter(machines), None) if machines else None
58-
engine: dict | None = machine.get("engine") if machine is not None else None
59-
components: list | None: engine.get("components") if engine is not None else None
60-
68+
machines = nasty_dict.get("machines")
69+
machine = next(iter(machines), None) if machines else None
70+
engine = machine.get("engine") if machine else None
71+
components = engine.get("components") if engine else None
6172
```
6273

63-
This is not only tedious but labourious!
64-
At least, it's safe. We would not raise errors to break our code.
74+
Safe, yes — but tedious and hard to read.
6575

76+
---
6677

67-
## Introducing `dig`
68-
With this tool we may quickly and securely navigate through all sorts of nested data.
78+
## ✅ Enter `dig()`
79+
80+
With `dig()`, you can safely access deeply nested data in a concise and readable way:
6981

70-
Let's consider the `nasty_dict` from the past section and that we also want to access the list of components.
7182
```python
7283
from py_data_digger import dig
7384

74-
components: list | None = dig(nasty_dict, "machines", 0, "engine", "components")
85+
components = dig(nasty_dict, "machines", 0, "engine", "components")
7586
```
7687

77-
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**!
88+
If any part of the path is missing, `dig()` returns `None` — and you move on:
89+
7890
```python
79-
components: list | None = dig(nasty_dict, "machines", 0, "engine_2", "components")
91+
components = dig(nasty_dict, "machines", 0, "engine_2", "components")
8092
if components is None:
81-
return None
93+
return None
8294
```
8395

84-
## Introducing `seek`
85-
Not satisfied with `None` returns?
96+
---
97+
98+
## 🚀 Need strict behavior? Use `seek()`
8699

87-
The `seek` function works just like `dig`, but it will raise an error if the path informed could not be found.
100+
If you want stricter error handling, use `seek()`. It behaves like `dig()`, but **raises an error** if the path doesn’t exist.
88101

89102
```python
90103
from py_data_digger import seek
91104

105+
components = seek(nasty_dict, "machines", 0, "engine_2", "components")
106+
```
92107

93-
components: list = seek(nasty_dict, "machines", 0, "engine_2", "components")
94-
>>> SeekError: Data digger can't go any further: KeyError
108+
```text
109+
SeekError: Data digger can't go any further: KeyError
95110
Path traveled: dict -> machines -> 0 -> engine_2
96111
```
97112

98-
The cool thing is, you would need to handle just one exception (`SeekError`). It also shows where it failed to seek 😎
113+
No more dealing with multiple exception types — just catch `SeekError`.
114+
115+
---
116+
117+
## 🔍 Accessing object attributes
118+
119+
You can also dig through **custom object attributes**, such as `dataclasses`, Pydantic models, or any plain Python objects.
120+
121+
Use the `dig_objects=True` or `seek_objects=True` flag:
99122

100-
## Seeking/digging objects
101-
And there is more!
102-
If you also want to look inside object attributes, you may do it by passing a special flag.
103-
This way it will be compatible with any nested objects like **Pydantic** models and **dataclasses**!
104123
```python
105-
person = Person(name='John Doe')
106-
my_dict = {
107-
'item_with_object': person
108-
}
124+
from py_data_digger import dig, seek
125+
126+
person = Person(name='John Doe')
127+
my_dict = {
128+
'item_with_object': person
129+
}
130+
131+
dig(my_dict, 'item_with_object', 'name', dig_objects=True)
132+
# → 'John Doe'
109133

110-
dig(my_dict, 'item_with_object', 'name', dig_objects=True)
111-
>>> 'John Doe'
134+
dig(my_dict, 'item_with_object', 'age', dig_objects=True)
135+
# → None
112136

113-
dig(my_dict, 'item_with_object', 'age', dig_objects=True)
114-
>>> None
137+
seek(my_dict, 'item_with_object', 'name', seek_objects=True)
138+
# → 'John Doe'
115139

116-
seek(my_dict, 'item_with_object', 'name', seek_objects=True)
117-
>>> 'John Doe'
140+
seek(my_dict, 'item_with_object', 'age', seek_objects=True)
141+
# → SeekError
142+
```
143+
144+
⚠️ Note: Use the object-access flags with caution, as attribute names may conflict with dictionary keys.
145+
146+
---
147+
148+
## 📦 Installation
118149

119-
seek(my_dict, 'item_with_object', 'age', seek_objects=True)
120-
>>> SeekError
150+
```bash
151+
pip install py-data-digger
121152
```
122153

123-
⚠️ The special flag is required because attribute names may conflict with other mapped keys. Use with caution.
154+
---
155+
156+
## 🙌 Contributions
157+
158+
Feel free to open issues or pull requests. Feedback is always welcome!

0 commit comments

Comments
 (0)