[](https://github.com/hristokbonev/MagiDict)
[](https://pypi.org/project/magidict/)
[](https://pypi.org/project/magidict/)
[](https://github.com/hristokbonev/MagiDict/actions/workflows/ci.yml)
[](https://codecov.io/gh/hristokbonev/MagiDict)
[](https://hristokbonev.github.io/magidict/)
[](https://opensource.org/licenses/MIT)
<p align="center">
<img src="http://raw.githubusercontent.com/hristokbonev/MagiDict/refs/heads/main/docs/assets/MagiDictLogo.png" alt="MagiDict Logo" width="200">
</p>
<h1 align="center">MagiDict</h1>
Do you find yourself chaining `.get()`'s like there's no tomorrow, then praying to the Gods of Safety that you didn't miss a single `{}`?<br>
Has your partner left you because whenever they ask you to do something, you always reply, "I'll try, except `KeyError` as e"?<br>
Do your kids get annoyed with you because you've called them "`None`" one too many times.<br>
And did your friends stop hanging out with you because every time you're together, you keep going to the bathroom to check your production logs for any TypeErrors named "`real_friends`"?<br>
How often do you seek imaginary guidance from Guido, begging him to teach you the mystical ways of safely navigating nested Python dictionaries?<br>
When you're out in public, do you constantly have the feeling that Keanu Reeves is judging you from behind the corner for your inability to elegantly access nested dictionary keys?<br>
And when you go to sleep at night, do you lie awake thinking about how much better your life would be if you took that course in JavaScript that your friend gave you a voucher for, before they moved to a different country and you lost contact with them, so you could finally use optional chaining and nullish coalescing operators to safely access nested properties without all the drama?
If you answered "yes" to any of these questions, you're not alone!
But don't worry anymore, because there's finally a solution that doesn't involve learning a whole new programming language or changing your religion to JavaScript! It's called ✨MagiDict✨ and it's here to save your sanity!
MagiDict is a powerful Python dictionary subclass that provides simple, safe and convenient attribute-style access to nested data structures, with recursive conversion and graceful failure handling. Designed to ease working with complex, deeply nested dictionaries, it reduces errors and improves code readability. Optimized and memoized for better performance.
Stop chaining `get()`'s and brackets like it's 2003 and start living your best life, where `Dicts.Just.Work`!
## Overview
`MagiDict` extends Python's built-in `dict` to offer a more convenient and forgiving way to work with nested dictionaries. It's particularly useful when working with JSON data, API responses, configuration files, or any deeply nested data structures where safe navigation is important.
## Installation
You can install MagiDict via pip:
```bash
pip install magidict
```
## Quick Start
```python
from magidict import MagiDict
# Create from dict
md = MagiDict({
'user': {
'name': 'Alice',
'profile': {
'bio': 'Software Engineer',
'location': 'NYC'
}
}
})
# Dot notation access
print(md.user.name) # 'Alice'
print(md.user.profile.bio) # 'Software Engineer'
# Safe chaining - no errors!
print(md.user.settings.theme) # MagiDict({}) - not a KeyError!
print(md.user.email or 'no-email') # 'no-email'
# Works with None values too
md = MagiDict({'value': None})
print(md.value.nested.key) # MagiDict({}) - safe!
```
## Documentation
Full documentation available in the GitHub [Wiki](https://github.com/hristokbonev/MagiDict/wiki)
## Key Features
```ascii
┌───────────────────┐
│ Access Styles │
└─────────┬─────────┘
│
┌─────────┴─────────┐
│ │
▼ ▼
┌─────────────────┐ ┌────────────────┐
│ Attribute Style │ │ Bracket Style │
│ . │ │ [] ├──────┐
└─────────┬───────┘ └───────┬────────┘ │
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌─────────┐
│ Safe │ │ Safe │ │ Strict ├──────────┐
└─────┬────┘ └─────┬────┘ └────┬────┘ │
│ │ │ │
▼ ▼ ▼ ▼
┌───────┐ ┌────────────────┐ ┌──────────┐ ┌──────────────┐
│ d.bar │ │ d["foo","bar"] │ │ d["foo"] │ │ d["foo.bar"] │
└───────┘ └────────────────┘ └──────────┘ └──────────────┘
*Safe* returns empty MagiDict for missing keys or None values.
*Strict* raises KeyError for missing keys and returns None for None values.
```
### 1. Attribute-Style Access
Access dictionary keys using dot notation instead of bracket notation. Missing keys and keys with `None` values return an empty `MagiDict`:
```python
md = MagiDict({'user': {'name': 'Alice', 'age': 30, 'nickname': None}})
md.user.name # 'Alice'
md.user.age # 30
md.user.nickname # MagiDict({})
md.user.email # MagiDict({})
```
### 2. Dot Notation in Brackets
Use dot-separated strings for deep access, including list indices.
Missing keys raise `KeyError` and out-of-bounds indices raise `IndexError` as expected from standard dict/list behavior:
```python
md = MagiDict({
'users': [
{'name': 'Alice', 'id': 1},
{'name': 'Keanu', 'id': 2}
]
})
md['users.0.name'] # 'Alice'
md['users.1.id'] # 2
md['users.2.name'] # IndexError
md['users.0.email'] # KeyError
```
### 3. List or Tuple of Keys in Brackets
Use a list or tuple of keys for deep sefe access.
Missing keys and keys with `None` values return an empty `MagiDict`. If the entire tuple is an actual key in the dict, it prioritizes and returns that value. The caveat of this is that you cannot strictly access tuple keys that don't exist, as it will return an empty `MagiDict` instead of raising `KeyError`. For that, use the strict `strict_get()` method.
```python
md = MagiDict({
'users': [
{'name': 'Alice', 'id': 1},
{'name': 'Keanu', 'id': 2}
]
})
md['users', 0, 'name'] # 'Alice'
md['users', 1, 'id'] # 2
md['users', 2, 'name'] # MagiDict({})
md['users', 0, 'email'] # MagiDict({})
md['users', 0, 'name'] = "Overridden"
# It returns the the actual tuple key value
md['users', 0, 'name'] # 'Overridden'
```
### 4. Recursive Conversion
Nested dictionaries are automatically converted to `MagiDict` instances:
```python
data = {
'company': {
'departments': {
'engineering': {
'employees': 50
}
}
}
}
md = MagiDict(data)
md.company.departments.engineering.employees # 50
```
### 5. Graceful Failure
Accessing non-existent keys or keys with `None` values via dot notation or tuple/list of keys returns an empty `MagiDict` instead of raising errors:
```python
md = MagiDict({'user': {'name': 'Alice'}})
# No error, returns empty MagiDict
md.user.email.address.street # MagiDict({})
md["user", "email", "address", "street"] # MagiDict({})
# Safe chaining
if md.settings.theme.dark_mode:
# This won't cause an error even if 'settings' doesn't exist
pass
```
### 6. Safe None Handling
Keys with `None` values can be safely chained:
```python
md = MagiDict({'user': {'nickname': None}})
md.user.nickname.stage_name # MagiDict({})
# Bracket access returns the actual None value
md.user['nickname'] # None
# Safe access with conversion back to None
none(md.user.nickname) # None
```
### 7. Standard Dictionary Behavior Preserved
All standard `dict` methods and behaviors work as expected. For example missing keys with brackets raise `KeyError` as expected
### 8. Safe `mget()` Method
`mget` is MagiDict's native `get` method. Unless a custom default is provided, it returns an empty `MagiDict` for missing keys or `None` values:
```python
md = MagiDict({'1-invalid': 'value', 'valid': None})
# Works with invalid identifiers
md.mget('1-invalid') # 'value'
# Returns empty MagiDict for missing keys
md.mget('missing') # MagiDict({})
# Shorthand version
md.mg('1-invalid') # 'value'
# Provide custom default
md.mget('missing', 'default') # 'default'
```
### 9. Strict `strict_get()` Method
`strict_get` is a strict version of `mget` that behaves like standard `dict` bracket access, raising `KeyError` for missing keys and returning `None` for keys with `None` values.
The main usage is to strictly access tuple keys, where if they don't exist, it raises a `KeyError`, instead of returning an empty `MagiDict`.
```python
md = MagiDict({'user': {'name': 'Alice', ('tuple', 'key'): 'value'}, 'valid': None})
# Raises KeyError for missing keys
md.sg('missing') # KeyError
md.sg('valid') # None
# Raises KeyError for missing nested keys
md.sg('user').sg('email') # KeyError
# Raises KeyError for missing tuple keys
md.sg(('tuple', 'missing')) # KeyError
md.sg(('tuple', 'key')) # 'value'
```
### 10. Convert Back to Standard Dict
Use `disenchant()` to convert back to a standard Python `dict`:
```python
md = MagiDict({'user': {'name': 'Alice'}})
standard_dict = md.disenchant()
type(standard_dict) # <class 'dict'>
```
### 11. Convert empty MagiDict to None
Use `none()` to convert empty `MagiDict` instances that were created from `None` or missing keys back to `None`:
```python
md = MagiDict({'user': None, 'age': 25})
none(md.user) # None
none(md.user.name) # None
none(md.age) # 25
```
## API Reference
### Constructor
```python
MagiDict(*args, **kwargs)
```
Creates a new `MagiDict` instance. Accepts the same arguments as the built-in `dict`.
**Examples:**
```python
MagiDict(*args, **kwargs)
```
or
```python
d = {"key": "value"}
md = MagiDict(d)
```
### Methods
#### `mget(key, default=...)`
Safe get method that mimics `dict`'s `get()`, but returns an empty `MagiDict` for missing keys or `None` values unless a custom default is provided.
**Parameters:**
- `key`: The key to retrieve
- `default`: Value to return if key doesn't exist (optional)
**Returns:**
- The value if key exists and is not `None`
- Empty `MagiDict` if key doesn't exist (unless custom default provided)
- Empty `MagiDict` if value is `None` (unless default explicitly set to `None`)
#### `mg(key, default=...)`
Shorthand alias for `mget()`.
#### `strict_get(key)`
Strict get method that raises `KeyError` for missing keys and returns `None` for keys with `None` values, mimicking standard `dict` bracket access behavior.
**Parameters:**
- `key`: The key to retrieve
**Returns:**
- The value if key exists (including `None`)
- Raises `KeyError` if key doesn't exist
#### `sg(key)` and `sget(key)`
Shorthand aliasese for `strict_get()`.
#### `disenchant()`
Converts the `MagiDict` and all nested `MagiDict` instances back to standard Python dictionaries. Handles circular references gracefully.
**Returns:** A standard Python `dict`
**Example:**
```python
md = MagiDict({'nested': {'data': [1, 2, 3]}})
regular_dict = md.disenchant()
type(regular_dict) # <class 'dict'>
```
#### `filter(function, drop_empty=False)`
Returns a new `MagiDict` containing only the items for which the provided function returns `True`.
**Parameters:**
- `function`: A function that takes either one argument (value) or two arguments (key, value) and returns `True` or `False`. If function is `None`,
it defaults to filtering out items with `None` values.
- `drop_empty`: If `True`, removes empty data structures from the result (default: `False`)
**Returns:** A new `MagiDict` with filtered items
**Example:**
```python
md = MagiDict({'a': 1, 'b': 2, 'c': 3})
filtered_md = md.filter(lambda k, v: v % 2 == 1)
# filtered_md is MagiDict({'a': 1, 'c': 3})
```
#### `search_key(key)`
Searches for the first occurrence of the specified key in the `MagiDict` and its nested structures, returning the corresponding value if found.
**Parameters:**
- `key`: The key to search for
**Returns:** The value associated with the key or `None` if not found
**Example:**
```python
md = MagiDict({'level1': {'level2': {'target_key': 'found_value'}}})
value = md.search_key('target_key') # 'found_value'
```
#### `search_keys(keys)`
Searches for all occurrences of the specified key in the `MagiDict` and its nested structures, returning a list of values corresponding to the found keys.
**Parameters:**
- `keys`: The key to search for
**Returns:** A list of values associated with the keys or an empty list if none found
**Example:**
```python
md = MagiDict({
'level1': {'target_key': 'value1'},
'level2': {'nested': {'target_key': 'value2'}}
})
values = md.search_keys('target_key') # ['value1', 'value2']
```
### Standard Dict Methods
All standard dictionary methods are supported:
- `update()` - Update with key-value pairs
- `copy()` - Return a shallow copy
- `setdefault()` - Get value or set default
- `fromkeys()` - Create dict from sequence of keys
- `pop()` - Remove and return value
- `popitem()` - Remove and return arbitrary item
- `clear()` - Remove all items
- `keys()` - Return dict keys
- `values()` - Return dict values
- `items()` - Return dict items
- `get()` - Get value with optional default
- `__contains__()` - Check if key exists (via `in`)
- and more
## Utility Functions
### `enchant(d)`
Converts a standard dictionary into a `MagiDict`.
**Parameters:**
- `d`: A standard Python dictionary
**Returns:** A `MagiDict` instance
### `magi_loads(s, **kwargs)`
Deserializes a JSON string directly into a `MagiDict` instead of a standard dict.
**Parameters:**
- `s`: JSON string to parse
- `**kwargs`: Additional arguments passed to `json.loads()`
**Returns:** A `MagiDict` instance
**Example:**
```python
json_string = '{"user": {"name": "Alice", "age": 30}}'
md = magi_loads(json_string)
md.user.name # 'Alice'
```
### `magi_load(fp, **kwargs)`
Deserializes a JSON file-like object into a `MagiDict` instead of a standard dict.
**Parameters:**
- `fp`: A file-like object containing JSON data
- `**kwargs`: Additional arguments passed to `json.load()`
**Returns:** A `MagiDict` instance
### `none(obj)`
Converts an empty `MagiDict` that was created from a `None` or missing key into `None`. Otherwise, returns the object as is.
**Parameters:**
- `obj`: The object to check
**Returns:**
- `None` if `obj` is an empty `MagiDict` created from `None` or missing key
- `obj` otherwise
## Important Caveats
### 1. Key Conflicts with Dict Methods
Keys that conflict with standard `dict` methods must be accessed using brackets, `mget` or `get`:
```python
md = MagiDict({'keys': 'my_value', 'items': 'another_value'})
# These return dict methods, not your values
md.keys # <built-in method keys...>
md.items # <built-in method items...>
# Use bracket access instead
md['keys'] # 'my_value'
md['items'] # 'another_value'
# Or use mget()
md.mget('keys') # 'my_value'
```
**Common conflicting keys:** `keys`, `values`, `items`, `get`, `pop`, `update`, `clear`, `copy`, `setdefault`, `fromkeys`
### 2. Invalid Python Identifiers
Keys that aren't valid Python identifiers must use bracket access or `mget()`:
```python
md = MagiDict({
'1-key': 'value1',
'my key': 'value2',
'my-key': 'value3'
})
# Must use brackets or mget()
md['1-key'] # 'value1'
md.mget('my key') # 'value2'
md['my-key'] # 'value3'
# These won't work
print(md.1-key) # SyntaxError
print(md.my key) # SyntaxError
```
### 3. Non-String Keys
Non-string keys can only be accessed using standard bracket notation or `mget()`:
```python
md = MagiDict({1: 'one', (2, 3): 'tuple_key'})
md[1] # 'one'
md[(2, 3)] # 'tuple_key'
md.mget(1) # 'one'
print(md.1) # SyntaxError
```
### 4. Protected Empty MagiDicts
Empty `MagiDict` instances returned from missing keys or `None` values are protected from modification:
```python
md = MagiDict({'user': None})
md.user["name"] = 'Alice' # TypeError
# Same for missing keys
md["missing"]["key"] = 'value' # TypeError
```
This protection prevents silent bugs where you might accidentally try to modify a non-existent path.
### 5. Setting attributes
Setting or updating keys using dot notation is not supported. Use bracket notation instead. As with standard dicts, this is purposely restricted to avoid confusion and potential bugs.
```python
md = MagiDict({'user': {'name': 'Alice'}})
md.user.name = 'Keanu' # AttributeError
md.user.age = 30 # AttributeError
# Use bracket notation instead
md['user']['name'] = 'Keanu'
md['user']['age'] = 30
```
### 6. Accessing Tuple Keys
When accessing tuple keys, if the tuple does not exist as a key in the dictionary, it will return an empty `MagiDict` instead of raising a `KeyError`. To strictly access tuple keys and raise `KeyError` if they don't exist, use the `strict_get()` method.
```python
md = MagiDict({('tuple', 'key'): 'value'})
md[('tuple', 'key')] # 'value'
md[('tuple', 'missing')] # MagiDict({}) - does not raise KeyError
md.sg(('tuple', 'missing')) # KeyError - raises KeyError
```
## Advanced Features
### Pickle Support
`MagiDict` supports pickling and unpickling:
```python
md = MagiDict({'data': {'nested': 'value'}})
pickled = pickle.dumps(md)
restored = pickle.loads(pickled)
restored.data.nested # 'value'
```
### Deep Copy Support
```python
md1 = MagiDict({'user': {'name': 'Alice'}})
md2 = deepcopy(md1)
md2.user.name = 'Keanu'
md1.user.name # 'Alice' (unchanged)
md2.user.name # 'Keanu'
```
### In-Place Updates with `|=` Operator
Python 3.9+ dict merge operator is supported:
```python
md = MagiDict({'a': 1})
md |= {'b': 2, 'c': 3}
md # MagiDict({'a': 1, 'b': 2, 'c': 3})
```
### Circular Reference Handling
`MagiDict` gracefully handles circular references:
```python
md = MagiDict({'name': 'root'})
md['self'] = md # Circular reference
# Access works
md.self.name # 'root'
md.self.self.name # 'root'
# Safely converts back to dict
regular = md.disenchant()
```
### Auto-completion Support
`MagiDict` provides intelligent auto-completion in IPython, Jupyter notebooks and IDE's.
## Performance Considerations
### Tested:
- All standard and custom functionality
- Circular and self references through pickle/deepcopy/disenchant
- Concurrent access patterns (multi-threaded reads/writes)
- Protected MagiDict mutation attempts
- Deep nesting with recursion limits and stack overflow prevention
- Type preservation through operations
### Performance
Magidict's initialization and recursive conversion are very fast due to the core hooks being implemented in C.
[Benchmarks](https://hristokbonev.github.io/magidict/)
### Best Practices
**Good use cases:**
- Configuration files
- API response processing
- Data exploration
- One-time data transformations
- Interactive development
**Avoid for:**
- High-performance inner loops
- Large-scale data processing
- Memory-constrained environments
- When you need maximum speed
### Optimization Tips
```python
# If you need standard dict for performance-critical code
if need_speed:
regular_dict = md.disenchant()
# Use regular_dict in hot loop
# Convert back when done
md = enchant(regular_dict)
```
## Comparison with Alternatives
### vs. Regular Dict
```python
# Regular dict - verbose and error-prone
regular = {'user': {'profile': {'name': 'Alice'}}}
name = regular.get('user', {}).get('profile', {}).get('name', 'Unknown')
# MagiDict - clean and safe
md = MagiDict({'user': {'profile': {'name': 'Alice'}}})
name = md.user.profile.name or 'Unknown'
```
### vs. DotDict, Bunch, AttrDict and Similar Libraries
MagiDict provides additional features:
- Safe chaining with missing keys (returns empty `MagiDict`)
- Safe chaining with None values
- Dot notation in bracket access
- List/tuple of keys in bracket access with safe chaining
- Built-in `mget()` and `strict_get()` methods
- Protected empty instances
- Circular reference handling
- Memoization
- Type preservation for all non-dict values
- In-place mutation
## Troubleshooting
### `KeyError` on Dot Notation Access
```python
md = MagiDict({'user': {'name': 'Alice'}})
email = md['user']['email'] #KeyError
email = md['user.email'] #KeyError
# This is safe
email = md.user.email or 'no-email'
email = md['user', 'email'] or 'no-email'
```
### Cannot Modify Error
```python
md = MagiDict({'user': None})
md.user.name = 'Alice' #TypeError
```
### Unexpected Empty `MagiDict`
```python
md = MagiDict({'value': None})
md.value # MagiDict({})
# Use bracket access to get actual None
md['value'] # None
```
### Empty `MagiDict` on Missing Tuple Key
```python
md = MagiDict({('tuple', 'key'): 'value'})
md[('tuple', 'missing')] # MagiDict({}) - does not raise KeyError
md.sg(('tuple', 'missing')) # KeyError - raises KeyError
```
---
## Contributing
Contributions are welcome and appreciated! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
## License
MagiDict is licensed under the [MIT License](https://github.com/hristokbonev/MagiDict/blob/main/LICENSE).
## Links
For documentation and source code, visit the project on GitHub: <br>
Documentation: [GitHub Wiki](https://github.com/hristokbonev/MagiDict/wiki)<br>
PyPI: [magidict](https://pypi.org/project/magidict/)<br>
Source Code: [MagiDict](https://github.com/hristokbonev/MagiDict)<br>
Issue Tracker: [GitHub Issues](https://github.com/hristokbonev/MagiDict/issues)<br>
Benchmarks: [Performance Results](https://hristokbonev.github.io/magidict/)
Raw data
{
"_id": null,
"home_page": "https://github.com/hristokbonev/magidict",
"name": "magidict",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "dictionary, nested dict, magic dict, magidict, safe access, dot notation, attribute access, dotdict, recursive dict, json, data structure, python utility, mapping, dynamic object, flexible dict, auto convert, dict wrapper, smart dict, object-like dict, data helper, config parser, config manager, attribute dict, namespace dict, chainable access, python tools",
"author": "Hristo Bonev",
"author_email": "Hristo Bonev <chkbonev@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/f4/0c/4945f0103cce61b95b63b1ebc16e5007a56469f6e489110190835dfce9ac/magidict-0.1.9.tar.gz",
"platform": null,
"description": "[](https://github.com/hristokbonev/MagiDict)\r\n[](https://pypi.org/project/magidict/)\r\n[](https://pypi.org/project/magidict/)\r\n[](https://github.com/hristokbonev/MagiDict/actions/workflows/ci.yml)\r\n[](https://codecov.io/gh/hristokbonev/MagiDict)\r\n[](https://hristokbonev.github.io/magidict/)\r\n[](https://opensource.org/licenses/MIT)\r\n\r\n<p align=\"center\">\r\n <img src=\"http://raw.githubusercontent.com/hristokbonev/MagiDict/refs/heads/main/docs/assets/MagiDictLogo.png\" alt=\"MagiDict Logo\" width=\"200\">\r\n</p>\r\n\r\n<h1 align=\"center\">MagiDict</h1>\r\n\r\nDo you find yourself chaining `.get()`'s like there's no tomorrow, then praying to the Gods of Safety that you didn't miss a single `{}`?<br>\r\nHas your partner left you because whenever they ask you to do something, you always reply, \"I'll try, except `KeyError` as e\"?<br>\r\nDo your kids get annoyed with you because you've called them \"`None`\" one too many times.<br>\r\nAnd did your friends stop hanging out with you because every time you're together, you keep going to the bathroom to check your production logs for any TypeErrors named \"`real_friends`\"?<br>\r\nHow often do you seek imaginary guidance from Guido, begging him to teach you the mystical ways of safely navigating nested Python dictionaries?<br>\r\nWhen you're out in public, do you constantly have the feeling that Keanu Reeves is judging you from behind the corner for your inability to elegantly access nested dictionary keys?<br>\r\nAnd when you go to sleep at night, do you lie awake thinking about how much better your life would be if you took that course in JavaScript that your friend gave you a voucher for, before they moved to a different country and you lost contact with them, so you could finally use optional chaining and nullish coalescing operators to safely access nested properties without all the drama?\r\n\r\nIf you answered \"yes\" to any of these questions, you're not alone!\r\nBut don't worry anymore, because there's finally a solution that doesn't involve learning a whole new programming language or changing your religion to JavaScript! It's called \u2728MagiDict\u2728 and it's here to save your sanity!\r\n\r\nMagiDict is a powerful Python dictionary subclass that provides simple, safe and convenient attribute-style access to nested data structures, with recursive conversion and graceful failure handling. Designed to ease working with complex, deeply nested dictionaries, it reduces errors and improves code readability. Optimized and memoized for better performance.\r\n\r\nStop chaining `get()`'s and brackets like it's 2003 and start living your best life, where `Dicts.Just.Work`!\r\n\r\n## Overview\r\n\r\n`MagiDict` extends Python's built-in `dict` to offer a more convenient and forgiving way to work with nested dictionaries. It's particularly useful when working with JSON data, API responses, configuration files, or any deeply nested data structures where safe navigation is important.\r\n\r\n## Installation\r\n\r\nYou can install MagiDict via pip:\r\n\r\n```bash\r\npip install magidict\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\nfrom magidict import MagiDict\r\n\r\n# Create from dict\r\nmd = MagiDict({\r\n 'user': {\r\n 'name': 'Alice',\r\n 'profile': {\r\n 'bio': 'Software Engineer',\r\n 'location': 'NYC'\r\n }\r\n }\r\n})\r\n\r\n# Dot notation access\r\nprint(md.user.name) # 'Alice'\r\nprint(md.user.profile.bio) # 'Software Engineer'\r\n\r\n# Safe chaining - no errors!\r\nprint(md.user.settings.theme) # MagiDict({}) - not a KeyError!\r\nprint(md.user.email or 'no-email') # 'no-email'\r\n\r\n# Works with None values too\r\nmd = MagiDict({'value': None})\r\nprint(md.value.nested.key) # MagiDict({}) - safe!\r\n```\r\n\r\n## Documentation\r\n\r\nFull documentation available in the GitHub [Wiki](https://github.com/hristokbonev/MagiDict/wiki)\r\n\r\n## Key Features\r\n\r\n```ascii\r\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2502 Access Styles \u2502\r\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n \u2502\r\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2502 \u2502\r\n \u25bc \u25bc\r\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n\u2502 Attribute Style \u2502 \u2502 Bracket Style \u2502\r\n\u2502 . \u2502 \u2502 [] \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\r\n \u2502 \u2502 \u2502\r\n \u25bc \u25bc \u25bc\r\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2502 Safe \u2502 \u2502 Safe \u2502 \u2502 Strict \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2514\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2518 \u2502\r\n \u2502 \u2502 \u2502 \u2502\r\n \u25bc \u25bc \u25bc \u25bc\r\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2502 d.bar \u2502 \u2502 d[\"foo\",\"bar\"] \u2502 \u2502 d[\"foo\"] \u2502 \u2502 d[\"foo.bar\"] \u2502\r\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n\r\n\r\n*Safe* returns empty MagiDict for missing keys or None values.\r\n*Strict* raises KeyError for missing keys and returns None for None values.\r\n```\r\n\r\n### 1. Attribute-Style Access\r\n\r\nAccess dictionary keys using dot notation instead of bracket notation. Missing keys and keys with `None` values return an empty `MagiDict`:\r\n\r\n```python\r\nmd = MagiDict({'user': {'name': 'Alice', 'age': 30, 'nickname': None}})\r\nmd.user.name # 'Alice'\r\nmd.user.age # 30\r\nmd.user.nickname # MagiDict({})\r\nmd.user.email # MagiDict({})\r\n```\r\n\r\n### 2. Dot Notation in Brackets\r\n\r\nUse dot-separated strings for deep access, including list indices.\r\nMissing keys raise `KeyError` and out-of-bounds indices raise `IndexError` as expected from standard dict/list behavior:\r\n\r\n```python\r\nmd = MagiDict({\r\n 'users': [\r\n {'name': 'Alice', 'id': 1},\r\n {'name': 'Keanu', 'id': 2}\r\n ]\r\n})\r\n\r\nmd['users.0.name'] # 'Alice'\r\nmd['users.1.id'] # 2\r\nmd['users.2.name'] # IndexError\r\nmd['users.0.email'] # KeyError\r\n```\r\n\r\n### 3. List or Tuple of Keys in Brackets\r\n\r\nUse a list or tuple of keys for deep sefe access.\r\nMissing keys and keys with `None` values return an empty `MagiDict`. If the entire tuple is an actual key in the dict, it prioritizes and returns that value. The caveat of this is that you cannot strictly access tuple keys that don't exist, as it will return an empty `MagiDict` instead of raising `KeyError`. For that, use the strict `strict_get()` method.\r\n\r\n```python\r\nmd = MagiDict({\r\n 'users': [\r\n {'name': 'Alice', 'id': 1},\r\n {'name': 'Keanu', 'id': 2}\r\n ]\r\n})\r\nmd['users', 0, 'name'] # 'Alice'\r\nmd['users', 1, 'id'] # 2\r\nmd['users', 2, 'name'] # MagiDict({})\r\nmd['users', 0, 'email'] # MagiDict({})\r\n\r\nmd['users', 0, 'name'] = \"Overridden\"\r\n# It returns the the actual tuple key value\r\nmd['users', 0, 'name'] # 'Overridden'\r\n```\r\n\r\n### 4. Recursive Conversion\r\n\r\nNested dictionaries are automatically converted to `MagiDict` instances:\r\n\r\n```python\r\ndata = {\r\n 'company': {\r\n 'departments': {\r\n 'engineering': {\r\n 'employees': 50\r\n }\r\n }\r\n }\r\n}\r\nmd = MagiDict(data)\r\nmd.company.departments.engineering.employees # 50\r\n```\r\n\r\n### 5. Graceful Failure\r\n\r\nAccessing non-existent keys or keys with `None` values via dot notation or tuple/list of keys returns an empty `MagiDict` instead of raising errors:\r\n\r\n```python\r\nmd = MagiDict({'user': {'name': 'Alice'}})\r\n\r\n# No error, returns empty MagiDict\r\nmd.user.email.address.street # MagiDict({})\r\nmd[\"user\", \"email\", \"address\", \"street\"] # MagiDict({})\r\n\r\n# Safe chaining\r\nif md.settings.theme.dark_mode:\r\n # This won't cause an error even if 'settings' doesn't exist\r\n pass\r\n```\r\n\r\n### 6. Safe None Handling\r\n\r\nKeys with `None` values can be safely chained:\r\n\r\n```python\r\nmd = MagiDict({'user': {'nickname': None}})\r\n\r\nmd.user.nickname.stage_name # MagiDict({})\r\n\r\n# Bracket access returns the actual None value\r\nmd.user['nickname'] # None\r\n# Safe access with conversion back to None\r\nnone(md.user.nickname) # None\r\n```\r\n\r\n### 7. Standard Dictionary Behavior Preserved\r\n\r\nAll standard `dict` methods and behaviors work as expected. For example missing keys with brackets raise `KeyError` as expected\r\n\r\n### 8. Safe `mget()` Method\r\n\r\n`mget` is MagiDict's native `get` method. Unless a custom default is provided, it returns an empty `MagiDict` for missing keys or `None` values:\r\n\r\n```python\r\nmd = MagiDict({'1-invalid': 'value', 'valid': None})\r\n\r\n# Works with invalid identifiers\r\nmd.mget('1-invalid') # 'value'\r\n\r\n# Returns empty MagiDict for missing keys\r\nmd.mget('missing') # MagiDict({})\r\n\r\n# Shorthand version\r\nmd.mg('1-invalid') # 'value'\r\n\r\n# Provide custom default\r\nmd.mget('missing', 'default') # 'default'\r\n```\r\n\r\n### 9. Strict `strict_get()` Method\r\n\r\n`strict_get` is a strict version of `mget` that behaves like standard `dict` bracket access, raising `KeyError` for missing keys and returning `None` for keys with `None` values.\r\nThe main usage is to strictly access tuple keys, where if they don't exist, it raises a `KeyError`, instead of returning an empty `MagiDict`.\r\n\r\n```python\r\nmd = MagiDict({'user': {'name': 'Alice', ('tuple', 'key'): 'value'}, 'valid': None})\r\n# Raises KeyError for missing keys\r\nmd.sg('missing') # KeyError\r\nmd.sg('valid') # None\r\n# Raises KeyError for missing nested keys\r\nmd.sg('user').sg('email') # KeyError\r\n# Raises KeyError for missing tuple keys\r\nmd.sg(('tuple', 'missing')) # KeyError\r\nmd.sg(('tuple', 'key')) # 'value'\r\n```\r\n\r\n### 10. Convert Back to Standard Dict\r\n\r\nUse `disenchant()` to convert back to a standard Python `dict`:\r\n\r\n```python\r\nmd = MagiDict({'user': {'name': 'Alice'}})\r\nstandard_dict = md.disenchant()\r\ntype(standard_dict) # <class 'dict'>\r\n```\r\n\r\n### 11. Convert empty MagiDict to None\r\n\r\nUse `none()` to convert empty `MagiDict` instances that were created from `None` or missing keys back to `None`:\r\n\r\n```python\r\nmd = MagiDict({'user': None, 'age': 25})\r\nnone(md.user) # None\r\nnone(md.user.name) # None\r\nnone(md.age) # 25\r\n```\r\n\r\n## API Reference\r\n\r\n### Constructor\r\n\r\n```python\r\nMagiDict(*args, **kwargs)\r\n```\r\n\r\nCreates a new `MagiDict` instance. Accepts the same arguments as the built-in `dict`.\r\n\r\n**Examples:**\r\n\r\n```python\r\nMagiDict(*args, **kwargs)\r\n```\r\n\r\nor\r\n\r\n```python\r\nd = {\"key\": \"value\"}\r\n\r\nmd = MagiDict(d)\r\n```\r\n\r\n### Methods\r\n\r\n#### `mget(key, default=...)`\r\n\r\nSafe get method that mimics `dict`'s `get()`, but returns an empty `MagiDict` for missing keys or `None` values unless a custom default is provided.\r\n\r\n**Parameters:**\r\n\r\n- `key`: The key to retrieve\r\n- `default`: Value to return if key doesn't exist (optional)\r\n\r\n**Returns:**\r\n\r\n- The value if key exists and is not `None`\r\n- Empty `MagiDict` if key doesn't exist (unless custom default provided)\r\n- Empty `MagiDict` if value is `None` (unless default explicitly set to `None`)\r\n\r\n#### `mg(key, default=...)`\r\n\r\nShorthand alias for `mget()`.\r\n\r\n#### `strict_get(key)`\r\n\r\nStrict get method that raises `KeyError` for missing keys and returns `None` for keys with `None` values, mimicking standard `dict` bracket access behavior.\r\n\r\n**Parameters:**\r\n\r\n- `key`: The key to retrieve\r\n\r\n**Returns:**\r\n\r\n- The value if key exists (including `None`)\r\n- Raises `KeyError` if key doesn't exist\r\n\r\n#### `sg(key)` and `sget(key)`\r\n\r\nShorthand aliasese for `strict_get()`.\r\n\r\n#### `disenchant()`\r\n\r\nConverts the `MagiDict` and all nested `MagiDict` instances back to standard Python dictionaries. Handles circular references gracefully.\r\n\r\n**Returns:** A standard Python `dict`\r\n\r\n**Example:**\r\n\r\n```python\r\nmd = MagiDict({'nested': {'data': [1, 2, 3]}})\r\nregular_dict = md.disenchant()\r\ntype(regular_dict) # <class 'dict'>\r\n```\r\n\r\n#### `filter(function, drop_empty=False)`\r\n\r\nReturns a new `MagiDict` containing only the items for which the provided function returns `True`.\r\n\r\n**Parameters:**\r\n\r\n- `function`: A function that takes either one argument (value) or two arguments (key, value) and returns `True` or `False`. If function is `None`,\r\n it defaults to filtering out items with `None` values.\r\n- `drop_empty`: If `True`, removes empty data structures from the result (default: `False`)\r\n\r\n**Returns:** A new `MagiDict` with filtered items\r\n\r\n**Example:**\r\n\r\n```python\r\nmd = MagiDict({'a': 1, 'b': 2, 'c': 3})\r\nfiltered_md = md.filter(lambda k, v: v % 2 == 1)\r\n# filtered_md is MagiDict({'a': 1, 'c': 3})\r\n```\r\n\r\n#### `search_key(key)`\r\n\r\nSearches for the first occurrence of the specified key in the `MagiDict` and its nested structures, returning the corresponding value if found.\r\n\r\n**Parameters:**\r\n\r\n- `key`: The key to search for\r\n\r\n**Returns:** The value associated with the key or `None` if not found\r\n\r\n**Example:**\r\n\r\n```python\r\nmd = MagiDict({'level1': {'level2': {'target_key': 'found_value'}}})\r\nvalue = md.search_key('target_key') # 'found_value'\r\n```\r\n\r\n#### `search_keys(keys)`\r\n\r\nSearches for all occurrences of the specified key in the `MagiDict` and its nested structures, returning a list of values corresponding to the found keys.\r\n\r\n**Parameters:**\r\n\r\n- `keys`: The key to search for\r\n\r\n**Returns:** A list of values associated with the keys or an empty list if none found\r\n\r\n**Example:**\r\n\r\n```python\r\nmd = MagiDict({\r\n 'level1': {'target_key': 'value1'},\r\n 'level2': {'nested': {'target_key': 'value2'}}\r\n})\r\nvalues = md.search_keys('target_key') # ['value1', 'value2']\r\n```\r\n\r\n### Standard Dict Methods\r\n\r\nAll standard dictionary methods are supported:\r\n\r\n- `update()` - Update with key-value pairs\r\n- `copy()` - Return a shallow copy\r\n- `setdefault()` - Get value or set default\r\n- `fromkeys()` - Create dict from sequence of keys\r\n- `pop()` - Remove and return value\r\n- `popitem()` - Remove and return arbitrary item\r\n- `clear()` - Remove all items\r\n- `keys()` - Return dict keys\r\n- `values()` - Return dict values\r\n- `items()` - Return dict items\r\n- `get()` - Get value with optional default\r\n- `__contains__()` - Check if key exists (via `in`)\r\n- and more\r\n\r\n## Utility Functions\r\n\r\n### `enchant(d)`\r\n\r\nConverts a standard dictionary into a `MagiDict`.\r\n\r\n**Parameters:**\r\n\r\n- `d`: A standard Python dictionary\r\n\r\n**Returns:** A `MagiDict` instance\r\n\r\n### `magi_loads(s, **kwargs)`\r\n\r\nDeserializes a JSON string directly into a `MagiDict` instead of a standard dict.\r\n\r\n**Parameters:**\r\n\r\n- `s`: JSON string to parse\r\n- `**kwargs`: Additional arguments passed to `json.loads()`\r\n\r\n**Returns:** A `MagiDict` instance\r\n\r\n**Example:**\r\n\r\n```python\r\n\r\njson_string = '{\"user\": {\"name\": \"Alice\", \"age\": 30}}'\r\nmd = magi_loads(json_string)\r\nmd.user.name # 'Alice'\r\n```\r\n\r\n### `magi_load(fp, **kwargs)`\r\n\r\nDeserializes a JSON file-like object into a `MagiDict` instead of a standard dict.\r\n\r\n**Parameters:**\r\n\r\n- `fp`: A file-like object containing JSON data\r\n- `**kwargs`: Additional arguments passed to `json.load()`\r\n\r\n**Returns:** A `MagiDict` instance\r\n\r\n### `none(obj)`\r\n\r\nConverts an empty `MagiDict` that was created from a `None` or missing key into `None`. Otherwise, returns the object as is.\r\n\r\n**Parameters:**\r\n\r\n- `obj`: The object to check\r\n\r\n**Returns:**\r\n\r\n- `None` if `obj` is an empty `MagiDict` created from `None` or missing key\r\n- `obj` otherwise\r\n\r\n## Important Caveats\r\n\r\n### 1. Key Conflicts with Dict Methods\r\n\r\nKeys that conflict with standard `dict` methods must be accessed using brackets, `mget` or `get`:\r\n\r\n```python\r\nmd = MagiDict({'keys': 'my_value', 'items': 'another_value'})\r\n\r\n# These return dict methods, not your values\r\nmd.keys # <built-in method keys...>\r\nmd.items # <built-in method items...>\r\n\r\n# Use bracket access instead\r\nmd['keys'] # 'my_value'\r\nmd['items'] # 'another_value'\r\n\r\n# Or use mget()\r\nmd.mget('keys') # 'my_value'\r\n```\r\n\r\n**Common conflicting keys:** `keys`, `values`, `items`, `get`, `pop`, `update`, `clear`, `copy`, `setdefault`, `fromkeys`\r\n\r\n### 2. Invalid Python Identifiers\r\n\r\nKeys that aren't valid Python identifiers must use bracket access or `mget()`:\r\n\r\n```python\r\nmd = MagiDict({\r\n '1-key': 'value1',\r\n 'my key': 'value2',\r\n 'my-key': 'value3'\r\n})\r\n\r\n# Must use brackets or mget()\r\nmd['1-key'] # 'value1'\r\nmd.mget('my key') # 'value2'\r\nmd['my-key'] # 'value3'\r\n\r\n# These won't work\r\nprint(md.1-key) # SyntaxError\r\nprint(md.my key) # SyntaxError\r\n```\r\n\r\n### 3. Non-String Keys\r\n\r\nNon-string keys can only be accessed using standard bracket notation or `mget()`:\r\n\r\n```python\r\nmd = MagiDict({1: 'one', (2, 3): 'tuple_key'})\r\n\r\nmd[1] # 'one'\r\nmd[(2, 3)] # 'tuple_key'\r\nmd.mget(1) # 'one'\r\n\r\nprint(md.1) # SyntaxError\r\n```\r\n\r\n### 4. Protected Empty MagiDicts\r\n\r\nEmpty `MagiDict` instances returned from missing keys or `None` values are protected from modification:\r\n\r\n```python\r\nmd = MagiDict({'user': None})\r\n\r\nmd.user[\"name\"] = 'Alice' # TypeError\r\n\r\n# Same for missing keys\r\nmd[\"missing\"][\"key\"] = 'value' # TypeError\r\n```\r\n\r\nThis protection prevents silent bugs where you might accidentally try to modify a non-existent path.\r\n\r\n### 5. Setting attributes\r\n\r\nSetting or updating keys using dot notation is not supported. Use bracket notation instead. As with standard dicts, this is purposely restricted to avoid confusion and potential bugs.\r\n\r\n```python\r\nmd = MagiDict({'user': {'name': 'Alice'}})\r\n\r\nmd.user.name = 'Keanu' # AttributeError\r\nmd.user.age = 30 # AttributeError\r\n# Use bracket notation instead\r\nmd['user']['name'] = 'Keanu'\r\nmd['user']['age'] = 30\r\n```\r\n\r\n### 6. Accessing Tuple Keys\r\n\r\nWhen accessing tuple keys, if the tuple does not exist as a key in the dictionary, it will return an empty `MagiDict` instead of raising a `KeyError`. To strictly access tuple keys and raise `KeyError` if they don't exist, use the `strict_get()` method.\r\n\r\n```python\r\nmd = MagiDict({('tuple', 'key'): 'value'})\r\nmd[('tuple', 'key')] # 'value'\r\nmd[('tuple', 'missing')] # MagiDict({}) - does not raise KeyError\r\nmd.sg(('tuple', 'missing')) # KeyError - raises KeyError\r\n```\r\n\r\n## Advanced Features\r\n\r\n### Pickle Support\r\n\r\n`MagiDict` supports pickling and unpickling:\r\n\r\n```python\r\n\r\nmd = MagiDict({'data': {'nested': 'value'}})\r\npickled = pickle.dumps(md)\r\nrestored = pickle.loads(pickled)\r\nrestored.data.nested # 'value'\r\n```\r\n\r\n### Deep Copy Support\r\n\r\n```python\r\n\r\nmd1 = MagiDict({'user': {'name': 'Alice'}})\r\nmd2 = deepcopy(md1)\r\nmd2.user.name = 'Keanu'\r\n\r\nmd1.user.name # 'Alice' (unchanged)\r\nmd2.user.name # 'Keanu'\r\n```\r\n\r\n### In-Place Updates with `|=` Operator\r\n\r\nPython 3.9+ dict merge operator is supported:\r\n\r\n```python\r\nmd = MagiDict({'a': 1})\r\nmd |= {'b': 2, 'c': 3}\r\nmd # MagiDict({'a': 1, 'b': 2, 'c': 3})\r\n```\r\n\r\n### Circular Reference Handling\r\n\r\n`MagiDict` gracefully handles circular references:\r\n\r\n```python\r\nmd = MagiDict({'name': 'root'})\r\nmd['self'] = md # Circular reference\r\n\r\n# Access works\r\nmd.self.name # 'root'\r\nmd.self.self.name # 'root'\r\n\r\n# Safely converts back to dict\r\nregular = md.disenchant()\r\n```\r\n\r\n### Auto-completion Support\r\n\r\n`MagiDict` provides intelligent auto-completion in IPython, Jupyter notebooks and IDE's.\r\n\r\n## Performance Considerations\r\n\r\n### Tested:\r\n\r\n- All standard and custom functionality\r\n- Circular and self references through pickle/deepcopy/disenchant\r\n- Concurrent access patterns (multi-threaded reads/writes)\r\n- Protected MagiDict mutation attempts\r\n- Deep nesting with recursion limits and stack overflow prevention\r\n- Type preservation through operations\r\n\r\n### Performance\r\n\r\nMagidict's initialization and recursive conversion are very fast due to the core hooks being implemented in C.\r\n\r\n[Benchmarks](https://hristokbonev.github.io/magidict/)\r\n\r\n### Best Practices\r\n\r\n**Good use cases:**\r\n\r\n- Configuration files\r\n- API response processing\r\n- Data exploration\r\n- One-time data transformations\r\n- Interactive development\r\n\r\n**Avoid for:**\r\n\r\n- High-performance inner loops\r\n- Large-scale data processing\r\n- Memory-constrained environments\r\n- When you need maximum speed\r\n\r\n### Optimization Tips\r\n\r\n```python\r\n# If you need standard dict for performance-critical code\r\nif need_speed:\r\n regular_dict = md.disenchant()\r\n # Use regular_dict in hot loop\r\n\r\n# Convert back when done\r\nmd = enchant(regular_dict)\r\n```\r\n\r\n## Comparison with Alternatives\r\n\r\n### vs. Regular Dict\r\n\r\n```python\r\n# Regular dict - verbose and error-prone\r\nregular = {'user': {'profile': {'name': 'Alice'}}}\r\nname = regular.get('user', {}).get('profile', {}).get('name', 'Unknown')\r\n\r\n# MagiDict - clean and safe\r\nmd = MagiDict({'user': {'profile': {'name': 'Alice'}}})\r\nname = md.user.profile.name or 'Unknown'\r\n```\r\n\r\n### vs. DotDict, Bunch, AttrDict and Similar Libraries\r\n\r\nMagiDict provides additional features:\r\n\r\n- Safe chaining with missing keys (returns empty `MagiDict`)\r\n- Safe chaining with None values\r\n- Dot notation in bracket access\r\n- List/tuple of keys in bracket access with safe chaining\r\n- Built-in `mget()` and `strict_get()` methods\r\n- Protected empty instances\r\n- Circular reference handling\r\n- Memoization\r\n- Type preservation for all non-dict values\r\n- In-place mutation\r\n\r\n## Troubleshooting\r\n\r\n### `KeyError` on Dot Notation Access\r\n\r\n```python\r\nmd = MagiDict({'user': {'name': 'Alice'}})\r\n\r\nemail = md['user']['email'] #KeyError\r\nemail = md['user.email'] #KeyError\r\n\r\n# This is safe\r\nemail = md.user.email or 'no-email'\r\nemail = md['user', 'email'] or 'no-email'\r\n```\r\n\r\n### Cannot Modify Error\r\n\r\n```python\r\nmd = MagiDict({'user': None})\r\n\r\nmd.user.name = 'Alice' #TypeError\r\n```\r\n\r\n### Unexpected Empty `MagiDict`\r\n\r\n```python\r\nmd = MagiDict({'value': None})\r\n\r\nmd.value # MagiDict({})\r\n\r\n# Use bracket access to get actual None\r\nmd['value'] # None\r\n```\r\n\r\n### Empty `MagiDict` on Missing Tuple Key\r\n\r\n```python\r\nmd = MagiDict({('tuple', 'key'): 'value'})\r\nmd[('tuple', 'missing')] # MagiDict({}) - does not raise KeyError\r\nmd.sg(('tuple', 'missing')) # KeyError - raises KeyError\r\n```\r\n\r\n---\r\n\r\n## Contributing\r\n\r\nContributions are welcome and appreciated! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) for more information.\r\n\r\n## License\r\n\r\nMagiDict is licensed under the [MIT License](https://github.com/hristokbonev/MagiDict/blob/main/LICENSE).\r\n\r\n## Links\r\n\r\nFor documentation and source code, visit the project on GitHub: <br>\r\nDocumentation: [GitHub Wiki](https://github.com/hristokbonev/MagiDict/wiki)<br>\r\nPyPI: [magidict](https://pypi.org/project/magidict/)<br>\r\nSource Code: [MagiDict](https://github.com/hristokbonev/MagiDict)<br>\r\nIssue Tracker: [GitHub Issues](https://github.com/hristokbonev/MagiDict/issues)<br>\r\nBenchmarks: [Performance Results](https://hristokbonev.github.io/magidict/)\r\n",
"bugtrack_url": null,
"license": null,
"summary": "A recursive dictionary with safe attribute access and automatic conversion.",
"version": "0.1.9",
"project_urls": {
"Homepage": "https://github.com/hristokbonev/magidict",
"Issues": "https://github.com/hristokbonev/magidict/issues"
},
"split_keywords": [
"dictionary",
" nested dict",
" magic dict",
" magidict",
" safe access",
" dot notation",
" attribute access",
" dotdict",
" recursive dict",
" json",
" data structure",
" python utility",
" mapping",
" dynamic object",
" flexible dict",
" auto convert",
" dict wrapper",
" smart dict",
" object-like dict",
" data helper",
" config parser",
" config manager",
" attribute dict",
" namespace dict",
" chainable access",
" python tools"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "f521f93a73641f731e6907dad402ba79c77eb96c5fdf9c307bc665e9b64e821d",
"md5": "be673ea76ba638aaa8654e9d55e665ff",
"sha256": "922872f5e7e7a7ab6c6a9cd0ecf570b3a0a6564ab061d4ae545177f437c4c91c"
},
"downloads": -1,
"filename": "magidict-0.1.9-cp39-cp39-win_amd64.whl",
"has_sig": false,
"md5_digest": "be673ea76ba638aaa8654e9d55e665ff",
"packagetype": "bdist_wheel",
"python_version": "cp39",
"requires_python": ">=3.8",
"size": 26304,
"upload_time": "2025-10-21T15:25:41",
"upload_time_iso_8601": "2025-10-21T15:25:41.220624Z",
"url": "https://files.pythonhosted.org/packages/f5/21/f93a73641f731e6907dad402ba79c77eb96c5fdf9c307bc665e9b64e821d/magidict-0.1.9-cp39-cp39-win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "f40c4945f0103cce61b95b63b1ebc16e5007a56469f6e489110190835dfce9ac",
"md5": "0bab48709000fe121d90f1b385359c28",
"sha256": "dfec22bebb7868deacd76e9bb0461ef73d5dbd7ea1a4166b2bcf1e834cdd9007"
},
"downloads": -1,
"filename": "magidict-0.1.9.tar.gz",
"has_sig": false,
"md5_digest": "0bab48709000fe121d90f1b385359c28",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 75100,
"upload_time": "2025-10-21T15:25:42",
"upload_time_iso_8601": "2025-10-21T15:25:42.581226Z",
"url": "https://files.pythonhosted.org/packages/f4/0c/4945f0103cce61b95b63b1ebc16e5007a56469f6e489110190835dfce9ac/magidict-0.1.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-21 15:25:42",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "hristokbonev",
"github_project": "magidict",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "magidict"
}