MagicO - enabling attribute notation and JSONPath
MagicO (Magic Object) allows you to access a dict, list, or tuple Python object using the attribute notation or a JSONPath.
For example, given the following data object:
my_data = {
"a": 1,
"b": {
"c": 3,
"d": (4, 5)
},
"e": [
{"f": 6},
"xyz",
],
}
to access attribute "f", you would need to use a series of subscripts, such as my_data["e"][0]["f"]).
As a programmer, you probably would find it more natural to use the attribute notation, such as my_data.e[0].f, or the JSONPath notation, such as my_data["$.e[0].f"].
This is what MagicO enables you to do.
To install MagicO:
pip install magico
To use MagicO:
from magico import MagicO
my_magic = MagicO(my_data)
Attribute notation
To access an attribute using the attribute notation:
print(my_magic) # Original data
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}
print(my_magic.e[0].f)
# Output: 6
You may create new attributes, change them, and delete them using the attribute notation.
print(my_magic) # Original data
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}
my_magic.b.g = 7
print(my_magic) # b.g is created
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5), 'g': 7}, 'e': [{'f': 6}, 'xyz']}
my_magic.b.g = 8
print(my_magic) # b.g is updated
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5), 'g': 8}, 'e': [{'f': 6}, 'xyz']}
del my_magic.b.g
print(my_magic) # b.g is deleted
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}
JSONPath notation
There are times when the attribute to access is programmatically formulated as a JSONPath, such as "$.e[0].f".
In this case, you may use the JSONPath as a subscript to the MagicO object, as in the following example:
print(my_magic) # Original data
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}
print(my_magic["$.e[0].f"])
# Output: 6
# The root element of the JSONPath can be omitted
print(my_magic["e[0].f"])
# Output: 6
With the MagicO subscript notation, you can create a "deep" attribute simply by assigning a value to it, and all missing parent attributes along the path will be created automatically. For example:
my_magic["$.b.g.h.i"] = 9 # Creating a "deep" attribute b.g.h.i
print(my_magic) # Attribute "b" is added with "g.h" to get to "i"
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5), 'g': {'h': {'i': 9}}}, 'e': [{'f': 6}, 'xyz']}
del my_magic["$.b.g"] # Deleting the parent will delete its tree
print(my_magic) # Attribute "b.g" is deleted
# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}
Data types
The data type MagicO returns depends on how you access it:
- Attribute notation:
dict,list,tupleandMagicOobjects: Returns as aMagicOobject.to_data(): Returns the data
- Scalar (
str,int,bool, etc.): Returns the data
- JSONPath notation:
- Returns the data
print("MagicO object")
print(f" {type(my_magic)}: {my_magic}") # <class 'magico.magico.MagicO'>: ...
print(f" {type(my_magic.to_data())}: {my_magic.to_data()}") # <class 'dict'>: ...
print(f" {type(my_magic.data_type())}: {my_magic.data_type()}") # <class 'type'>: <class 'dict'>
print("dict object")
print(f" {type(my_magic.e[0])}: {my_magic.e[0]}") # <class 'magico.magico.MagicO'>: {'f': 6}
print(f" {type(my_magic.e[0].to_data())}: {my_magic.e[0].to_data()}") # <class 'dict'>: {'f': 6}
print(f" {type(my_magic.e[0].data_type())}: {my_magic.e[0].data_type()}") # <class 'type'>: <class 'dict'>
print("list object")
print(f" {type(my_magic.e)}: {my_magic.e}") # <class 'magico.magico.MagicO'>: [{'f': 6}, 'xyz']
print(f" {type(my_magic.e.to_data())}: {my_magic.e.to_data()}") # <class 'list'>: [{'f': 6}, 'xyz']
print(f" {type(my_magic.e.data_type())}: {my_magic.e.data_type()}") # <class 'type'>: <class 'list'>
print("tuple object")
print(f" {type(my_magic.b.d)}: {my_magic.b.d}") # <class 'magico.magico.MagicO'>: (4, 5)
print(f" {type(my_magic.b.d.to_data())}: {my_magic.b.d.to_data()}") # <class 'tuple'>: (4, 5)
print(f" {type(my_magic.b.d.data_type())}: {my_magic.b.d.data_type()}") # <class 'type'>: <class 'tuple'>
print("Scalar")
print(f" {type(my_magic.e[0].f)}: {my_magic.e[0].f}") # <class 'int'>: 6
print("JSONPath access")
print(f" {type(my_magic['$.e[0].f'])}: {my_magic['$.e[0].f']}") # <class 'int'>: 6
print(f" {type(my_magic[''])}: {my_magic['']}") # <class 'dict'>: ...
MagicO supports all dict, list, and tuple behaviours: you may use dict methods, list methods, and tuple methods on a MagicO object, as if it is the underlying dict, list, or tuple.
For example,
# Iterable
for m in my_magic:
print(f"{m}: {my_magic[m]}")
# Output:
# a: 1
# b: {'c': 3, 'd': (4, 5)}
# e: [{'f': 6}, 'xyz']
# Sortable
my_magic.e.append([8, 6, 7, 5])
print(my_magic)
my_magic.e[-1].sort()
print(my_magic)
# Output:
# {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz', [8, 6, 7, 5]]}
# {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz', [5, 6, 7, 8]]}
Referential pointers
Access to a MagicO object returns a pointer to the original data.
Updating the returned object will affect the original data object as well.
In short, MagicO is a wrapper of the original data you created it with.
They all share the same storage.
print(my_data) # Original: {..., 'e': [{'f': 6}, 'xyz'], ...}
my_magic_data = my_magic.to_data()
# Update the data object
my_magic_data["e"][1] = "abc"
print(my_data) # Output: {..., 'e': [{'f': 6}, 'abc'], ...}
# Update the MagicO object
my_magic.e[1] = "xyz"
print(my_data) # Output: {..., 'e': [{'f': 6}, 'xyz'], ...}
Another example with JSONPath and delete. The deletion on the returned object my_magic_attr affects the original data my_data.
my_magic_attr = my_magic["$.e"]
print(my_magic_attr) # Output: [{'f': 6}, 'xyz', [5, 6, 7, 8]]
del my_magic_attr[-1]
print(my_data) # Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}
Here is a Jupyter version of this document.
If you have any questions or experience any issues, please log a MagicO ticket on GitHub.
Metadata
Release files for magico 1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| magico-1.0.tar.gz | 11.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| magico-1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.1 kB
Release files / magico-1.0.tar.gz
| Download URL | magico-1.0.tar.gz |
|---|---|
| Size | 11.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
16d4791f1280714dc1278c225d30b1dc5d4577d711be407ca3ccd0988a790d1d
|
|
BLAKE2b-256 checksum How to use checksums |
85ec97b947f535ec99691933c0df85ab306a1ed16036272b55f5ed41a280932c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/4.0.2 CPython/3.9.17
|
Release files / magico-1.0-py3-none-any.whl
| Download URL | magico-1.0-py3-none-any.whl |
|---|---|
| Size | 9.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
990e067088bb7b34beb249dfa4a22979d84d744e9bc565935484af16f9a9e065
|
|
BLAKE2b-256 checksum How to use checksums |
b5787139afba9ede78c220c1f4ea7bdee447b485cdf1cdbf6304e40c915e3463
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/4.0.2 CPython/3.9.17
|
Security for magico 1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.