Skip to content

Commit 54e5eee

Browse files
committed
Siva | prepare for v2.0.0 release
1 parent 19bef0e commit 54e5eee

7 files changed

Lines changed: 294 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.0.0] - 2025-02-06
9+
10+
### Breaking Changes
11+
12+
- **`get_at` now raises `PathError` by default** for missing paths instead of returning `None` silently. Use the `default` parameter for optional/nullable access: `get_at(data, "path", default=None)`
13+
- **`get_at` and `set_at` parameters are now keyword-only** - The `default` and `create` parameters must be passed as keyword arguments (e.g., `get_at(data, "path", default=None)`, not `get_at(data, "path", None)`)
14+
- **`set_at` parameter change** - The `fill_strategy` parameter has been replaced with a simpler `create` boolean parameter. Replace `fill_strategy=FillStrategy.AUTO` with `create=True`
15+
- **No more sparse lists** - `set_at` no longer allows creating lists with gaps. Lists must be built sequentially (index 0, then 1, then 2, etc.). Attempting to set at index > len(list) raises `PathError`
16+
- **Removed `FillStrategy` enum** - Use `create=True/False` instead
17+
- **Implicit `None` returns from `get_at`** - Now raises `PathError` by default instead of returning `None` silently
18+
19+
### Added
20+
21+
- **Introspection module** with new functions for analyzing nested structures:
22+
- `get_depth(data)` - Returns maximum nesting depth
23+
- `count_leaves(data)` - Counts total leaf values
24+
- `get_all_paths(data)` - Returns all paths to leaf values
25+
- **`default` parameter for `get_at`** - Explicit way to handle missing paths: `get_at(data, "path", default="fallback")`
26+
- **`create` parameter for `set_at`** - Simple boolean to control auto-creation of intermediate containers
27+
- **New error codes**:
28+
- `OPERATION_DISABLED` - For operations blocked by configuration (e.g., list deletion without `allow_list_mutation=True`)
29+
- `NON_NAVIGABLE_TYPE` - For attempts to navigate into non-container types (e.g., int, str, set)
30+
31+
### Changed
32+
33+
- Improved error messages with more context about what went wrong and how to fix it
34+
- Refactored internal helpers for better maintainability and testability
35+
- Enhanced path validation with clearer error codes
36+
37+
For detailed migration instructions, see the [Migration Guide](https://ysskrishna.github.io/nestedutils/migration-v1-to-v2/).
38+
839
## [1.1.7]
940

1041
### Added
@@ -13,7 +44,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1344
- Radio button selection for three example datasets (User Profile, E-commerce Data, API Response) and custom JSON input
1445
- Consolidated path validation tests into `test_normalize_path.py` with new test cases for complex keys and None value handling
1546

16-
1747
### Fixed
1848

1949
- Fixed `normalize_path()` converting all list path keys to strings, now preserves integer and other key types.
@@ -133,6 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
133163
- Immutable container protection (tuples cannot be modified)
134164
- Safe list deletion (requires explicit `allow_list_mutation=True` flag)
135165

166+
[2.0.0]: https://github.com/ysskrishna/nestedutils/compare/v1.1.7...v2.0.0
136167
[1.1.7]: https://github.com/ysskrishna/nestedutils/compare/v1.1.6...v1.1.7
137168
[1.1.6]: https://github.com/ysskrishna/nestedutils/compare/v1.1.5...v1.1.6
138169
[1.1.5]: https://github.com/ysskrishna/nestedutils/compare/v1.1.4...v1.1.5

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ The library includes built-in safety limits to prevent excessive resource usage:
375375
These limits help protect against accidental memory exhaustion or performance issues. If you hit these limits, you'll receive a `PathError` with a clear message.
376376

377377

378+
## Migration from v1.x to v2.0
379+
380+
Version 2.0 introduces breaking changes to make the library safer and more predictable. If you're upgrading from v1.x, please see the [Migration Guide](https://ysskrishna.github.io/nestedutils/migration-v1-to-v2/) for detailed upgrade instructions.
381+
378382
## Contributing
379383

380384
Contributions are welcome! Please read our [Contributing Guide](https://github.com/ysskrishna/nestedutils/blob/main/CONTRIBUTING.md) for details on our code of conduct, development setup, and the process for submitting pull requests.

docs/migration-v1-to-v2.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Migration Guide - v1 to v2
3+
description: "Guide for migrating from nestedutils v1.x to v2.0. Covers breaking changes, API updates, and code migration examples."
4+
keywords:
5+
- nestedutils
6+
- migration
7+
- upgrade guide
8+
- v2
9+
- breaking changes
10+
---
11+
12+
--8<-- "migration-v1-to-v2.md"

migration-v1-to-v2.md

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Migration Guide: v1.x to v2.0
2+
3+
This guide helps you upgrade your code from nestedutils v1.x to v2.0. Version 2.0 introduces breaking changes that make the library safer and more predictable.
4+
5+
## Quick Summary of Breaking Changes
6+
7+
| Change | v1.x Behavior | v2.0 Behavior |
8+
|--------|---------------|---------------|
9+
| `get_at` missing path | Returns `None` silently | Raises `PathError` |
10+
| `get_at` default parameter | Could be positional | Must be keyword-only (`default=...`) |
11+
| `set_at` auto-create | `fill_strategy` parameter | `create=False` parameter |
12+
| `set_at` create parameter | N/A (didn't exist) | Must be keyword-only (`create=...`) |
13+
| Sparse lists | Allowed with `None` fill | Not allowed (strict sequential) |
14+
| `FillStrategy` enum | Available | Removed |
15+
16+
---
17+
18+
## 1. `get_at` Now Raises by Default
19+
20+
### The Change
21+
22+
In v1.x, `get_at` silently returned `None` when a path didn't exist. In v2.0, it raises `PathError` by default, making missing data explicit rather than hidden.
23+
24+
### v1.x Code
25+
26+
```python
27+
from nestedutils import get_at
28+
29+
data = {"user": {"name": "Alice"}}
30+
31+
# v1.x: Returns None silently - bugs can hide here!
32+
email = get_at(data, "user.email") # None
33+
age = get_at(data, "user.profile.age") # None
34+
35+
# v1.x: default could be passed positionally
36+
value = get_at(data, "missing.path", None) # None (positional argument)
37+
```
38+
39+
### v2.0 Migration
40+
41+
**Option A: Use `default` parameter for optional values**
42+
43+
```python
44+
from nestedutils import get_at
45+
46+
data = {"user": {"name": "Alice"}}
47+
48+
# Explicit default - clear intent
49+
email = get_at(data, "user.email", default=None) # None
50+
email = get_at(data, "user.email", default="unknown@example.com") # fallback value
51+
```
52+
53+
**Option B: Use `exists_at` to check first**
54+
55+
```python
56+
from nestedutils import get_at, exists_at
57+
58+
if exists_at(data, "user.email"):
59+
email = get_at(data, "user.email")
60+
else:
61+
email = "default@example.com"
62+
```
63+
64+
**Option C: Use try/except for error handling**
65+
66+
```python
67+
from nestedutils import get_at, PathError
68+
69+
try:
70+
email = get_at(data, "user.email")
71+
except PathError:
72+
email = "default@example.com"
73+
```
74+
75+
### Why This Change?
76+
77+
Silent `None` returns masked bugs. Consider:
78+
79+
```python
80+
# v1.x - Bug hidden: typo in path returns None, not an error
81+
user_name = get_at(data, "usr.name") # None (typo: "usr" vs "user")
82+
83+
# v2.0 - Bug exposed immediately
84+
user_name = get_at(data, "usr.name") # Raises PathError!
85+
```
86+
87+
---
88+
89+
## 2. `set_at` Parameter Changes
90+
91+
### The Change
92+
93+
The `fill_strategy` parameter has been replaced with a simpler `create` boolean parameter. Sparse list creation is no longer supported. **Important**: Both `get_at`'s `default` and `set_at`'s `create` are now keyword-only parameters (must use `default=...` and `create=...`, not positional arguments).
94+
95+
### v1.x Code
96+
97+
```python
98+
from nestedutils import set_at, FillStrategy
99+
100+
data = {}
101+
102+
# v1.x: Various fill strategies
103+
set_at(data, "user.name", "Alice", fill_strategy=FillStrategy.AUTO)
104+
set_at(data, "items.0", "first", fill_strategy=FillStrategy.AUTO)
105+
set_at(data, "items.5", "sixth", fill_strategy=FillStrategy.NONE) # Sparse list!
106+
107+
# v1.x: fill_strategy could be passed as string positionally
108+
set_at(data, "user.name", "Alice", "auto") # Positional argument
109+
```
110+
111+
### v2.0 Migration
112+
113+
```python
114+
from nestedutils import set_at
115+
116+
data = {}
117+
118+
# v2.0: Simple create=True for auto-creation
119+
set_at(data, "user.name", "Alice", create=True)
120+
set_at(data, "items.0", "first", create=True)
121+
122+
# Sparse lists are NO LONGER ALLOWED
123+
# set_at(data, "items.5", "sixth", create=True) # Raises PathError!
124+
125+
# Must build sequentially
126+
set_at(data, "items.1", "second", create=True) # OK - appends
127+
```
128+
129+
### FillStrategy Migration Table
130+
131+
| v1.x `fill_strategy` | v2.0 Equivalent |
132+
|----------------------|-----------------|
133+
| `FillStrategy.AUTO` | `create=True` |
134+
| `FillStrategy.NONE` | Not supported (no sparse lists) |
135+
| `FillStrategy.DICT` | `create=True` (inferred from key type) |
136+
| `FillStrategy.LIST` | `create=True` (inferred from key type) |
137+
| Not specified | `create=False` (default, raises on missing) |
138+
139+
### Why This Change?
140+
141+
- Sparse lists (`[None, None, None, "value"]`) caused confusion and bugs
142+
- The `FillStrategy` enum added complexity without proportional benefit
143+
- `create=True/False` is more intuitive and covers most use cases
144+
145+
---
146+
147+
## 3. No More Sparse Lists
148+
149+
### The Change
150+
151+
v1.x allowed creating lists with gaps filled by `None`. v2.0 enforces sequential list building only.
152+
153+
### v1.x Code
154+
155+
```python
156+
from nestedutils import set_at
157+
158+
data = {}
159+
set_at(data, "items.5", "value", fill_strategy=FillStrategy.NONE)
160+
# Result: {"items": [None, None, None, None, None, "value"]}
161+
```
162+
163+
### v2.0 Migration
164+
165+
```python
166+
from nestedutils import set_at
167+
168+
data = {}
169+
170+
# Build lists sequentially
171+
set_at(data, "items.0", "first", create=True)
172+
set_at(data, "items.1", "second", create=True)
173+
set_at(data, "items.2", "third", create=True)
174+
# Result: {"items": ["first", "second", "third"]}
175+
176+
# Or use plain Python for sparse/pre-sized lists
177+
data = {"items": [None] * 6}
178+
set_at(data, "items.5", "value") # OK - index exists
179+
```
180+
181+
### Why This Change?
182+
183+
Sparse lists often indicate logic errors. If you truly need sparse data, a dict with integer keys is more appropriate:
184+
185+
```python
186+
# Instead of sparse list
187+
data = {"items": {5: "value", 10: "another"}}
188+
```
189+
190+
---
191+
192+
## 4. FillStrategy Enum Removed
193+
194+
### The Change
195+
196+
The `FillStrategy` enum has been removed from the public API.
197+
198+
### v1.x Code
199+
200+
```python
201+
from nestedutils import FillStrategy
202+
203+
strategy = FillStrategy.AUTO
204+
```
205+
206+
### v2.0 Migration
207+
208+
Remove all `FillStrategy` imports and usages. Use `create=True/False` instead.
209+
210+
```python
211+
# v2.0: No FillStrategy import needed
212+
from nestedutils import set_at
213+
214+
set_at(data, "path", value, create=True)
215+
```
216+
217+
---
218+
219+
## 5. New Introspection Functions
220+
221+
v2.0 adds new introspection functions that don't exist in v1.x:
222+
223+
```python
224+
from nestedutils import get_depth, count_leaves, get_all_paths
225+
226+
data = {"a": {"b": 1, "c": 2}, "d": [3, 4]}
227+
228+
get_depth(data) # 2
229+
count_leaves(data) # 4
230+
get_all_paths(data) # [["a", "b"], ["a", "c"], ["d", 0], ["d", 1]]
231+
```
232+
233+
---
234+
235+
## Migration Checklist
236+
237+
- [ ] **Search for `get_at` calls without `default`** - Add `default=None` if silent failure is intended
238+
- [ ] **Update positional `default` arguments** - Change `get_at(data, "path", None)` to `get_at(data, "path", default=None)`
239+
- [ ] **Remove `FillStrategy` imports** - Replace with `create=True/False`
240+
- [ ] **Remove `fill_strategy` parameters** - Replace with `create=True`
241+
- [ ] **Update positional `fill_strategy` arguments** - Change `set_at(data, "path", value, "auto")` to `set_at(data, "path", value, create=True)`
242+
- [ ] **Check for sparse list creation** - Refactor to sequential building or use dicts
243+
- [ ] **Run your test suite** - v2.0's stricter behavior will expose hidden bugs

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nav:
1313
- Home: index.md
1414
- Interactive Demo: demo.md
1515
- API Reference: api-reference.md
16+
- Migration Guide (v1 → v2): migration-v1-to-v2.md
1617
- Contributing: CONTRIBUTING.md
1718
- Changelog: CHANGELOG.md
1819
- License: LICENSE.md

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "nestedutils"
3-
version = "1.1.7"
3+
version = "2.0.0"
44
description = "The lightweight Python library for safe, simple, dot-notation access to nested dicts and lists. Effortlessly get, set, and delete values deep in your complex JSON, API responses, and config files without verbose error-checking or handling KeyError exceptions."
55
readme = "README.md"
66
requires-python = ">=3.8"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)