Package: magico
MagicO - enabling attribute notation and JSONPathMagicO(Magic Object) allows you to access adict,list, ortuplePython 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 asmy_data["e"][0]["f"]).
As a programmer, you probably would find it more natural to use the attribute notation, such asmy_data.e[0].f, or the JSONPath notation, such asmy_data["$.e[0].f"].
This is whatMagicOenables you to do.To installMagicO:pipinstallmagicoTo useMagicO:frommagicoimportMagicOmy_magic=MagicO(my_data)Attribute notationTo 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: 6You 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=7print(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=8print(my_magic)# b.g is updated# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5), 'g': 8}, 'e': [{'f': 6}, 'xyz']}delmy_magic.b.gprint(my_magic)# b.g is deleted# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}JSONPath notationThere are times when the attribute to access is programmatically formulated as aJSONPath, such as "$.e[0].f".
In this case, you may use the JSONPath as a subscript to theMagicOobject, 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 omittedprint(my_magic["e[0].f"])# Output: 6With theMagicOsubscript 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.iprint(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']}delmy_magic["$.b.g"]# Deleting the parent will delete its treeprint(my_magic)# Attribute "b.g" is deleted# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}Data typesThe data typeMagicOreturns depends on how you access it:Attribute notation:dict,list,tupleandMagicOobjects: Returns as aMagicOobject.to_data(): Returns the dataScalar (str,int,bool, etc.): Returns the dataJSONPath notation:Returns the dataprint("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'>: 6print("JSONPath access")print(f"{type(my_magic['$.e[0].f'])}:{my_magic['$.e[0].f']}")# <class 'int'>: 6print(f"{type(my_magic[''])}:{my_magic['']}")# <class 'dict'>: ...MagicOsupports alldict,list, andtuplebehaviours: you may usedict methods,list methods, andtuple methodson aMagicOobject, as if it is the underlyingdict,list, ortuple.For example,# Iterableforminmy_magic:print(f"{m}:{my_magic[m]}")# Output:# a: 1# b: {'c': 3, 'd': (4, 5)}# e: [{'f': 6}, 'xyz']# Sortablemy_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 pointersAccess to aMagicOobject returns a pointer to the original data.
Updating the returned object will affect the original data object as well.
In short,MagicOis 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 objectmy_magic_data["e"][1]="abc"print(my_data)# Output: {..., 'e': [{'f': 6}, 'abc'], ...}# Update the MagicO objectmy_magic.e[1]="xyz"print(my_data)# Output: {..., 'e': [{'f': 6}, 'xyz'], ...}Another example with JSONPath and delete. The deletion on the returned objectmy_magic_attraffects the original datamy_data.my_magic_attr=my_magic["$.e"]print(my_magic_attr)# Output: [{'f': 6}, 'xyz', [5, 6, 7, 8]]delmy_magic_attr[-1]print(my_data)# Output: {'a': 1, 'b': {'c': 3, 'd': (4, 5)}, 'e': [{'f': 6}, 'xyz']}Here is aJupyter versionof this document.If you have any questions or experience any issues, please log aMagicO ticket on GitHub.