Skip to main content

A custom wrapper object around dict that allows attribute-style access to dictionary items and support for nested JSON/TOML data.

Project description

objdict-bf

objdict-bf is a Python module that provides a wrapper class to conveniently manipulate dictionaries or dict-based json/toml nested structures using attribute-like syntax. It is intended mostly to ease manipulation of json/toml data, web requests and responses, configuration files, dynamic prototyping...

Features

  • Attribute-style access to dictionary items (e.g., obj.key instead of obj['key']).
  • Synchronization with the original dictionary if passed at instantiation.
  • Utility methods for recursive conversion of nested structures to and from objdict and dict.
  • Serialization and deserialization methods for both strings and files with json, toml and jsonpickle backend support.
  • Advanced default value attribution features for missing keys.
  • optional object-like behavior, by auto-passing the objdict instance as 'self' to callable attributes having 'self' in their signature.

Installation

pip install objdict-bf

Signature of the constructor

objdict(*args,_use_default=False,_default=None,_file=None,_backend=None,_auto_self=False,**kwargs)

Parameters:

  • *args: either dicts, objdicts or iterables on key:value pairs. If the first arg is a dict, it will serve as the internal _data_dict of the objdict instance.
  • _use_default: boolean, determines if a default value is attributed to missing keys
  • _default: can be any value or callable. If it is callable with adequate signature, this callable will be used to handle default values generation.
  • _file: reference to a json file path for dumping
  • _backend: either 'json', 'toml' or 'jsonpickle'. Determines the backend used for serialization/deserialization when dumping/loading (None defaults to 'json').
  • _auto_self: boolean. Determines if the instance is auto-passed a 'self' to its callable callable with 'self' in their signature (mocked object behavior).
  • **kwargs: key value pairs passed as kwargs to update the objdict

Usage

from objdict_bf import objdict

# Create an objdict with some initial data
data = objdict(
    name='John',
    age=30,
    location='New York'
)

#Or synchronize with an existing dict
d={'name': 'John', 'age': 30, 'location': 'New York'}
data = objdict(d)

#Access data using attribute-style access
print(data.name)  # Output: John
print(data.age)   # Output: 30

#Modify data
data.age = 31

#Create a new key:value pair
data.job='developer'

#Changes are reflected on the original dict
print(d['age']) #Ouput: 31
print(d['job']) #Ouput: 'developer'

#Chaining attributes is supported for nested structures involving lists
d={
    'profile':{
        'name':'John',
        'hobbies':[
            {'type':'sport','title':'tennis'},
            {'type':'music','title':'guitar playing'}
        ]
    }
}
data = objdict(d)

print(data) #Output: the repr of the above dict
print(data.profile.hobbies[1].title) #Output: guitar playing

#Conversion of dict items to their objdict version is automatic upon access. 
#The created objdicts will inherit the parent objdict settings, namely: _backend,_use_default, _default, _auto_self).
#The objdict being essentially a wrapper interface on the initial dict,  
#this conversion is reflected in the initial dict content as well

print(isinstance(data.profile.hobbies[1],objdict)) #Output: True
print(isinstance(d['profile']['hobbies'][1],objdict)) #Output: True

#to_dict returns the underlying dict, converting recursively all objdicts found in the nested structure back to dicts
print(d is data.to_dict()) #Ouptut: True
print(isinstance(d['profile']['hobbies'][1], dict)) #Output: True 

#-----------------------------Serialization-------------------------------

# Serialize to JSON string
json_string = data.dumps()
print(json_string)
#or use another backend for serialization 
toml_string=data.dumps(_backend='toml')
print(toml_string)

#dump to a file
data.dump("my_json_file.json")
#or
data.dump("my_toml_file.toml",_backend='toml')

#make some more changes
data.email="dummy.email@gmail.com"

#the reference to the file and backend preference from the last dump is kept in the objdict instance so you don't have to pass them again
data.dump()

# Deserialize from a string (new instance keeping reference to the chosen backend)
data = objdict.loads(json_string)
#or
data = objdict.loads(toml_string,_backend='toml')


# Deserialize from a file (new instance keeping reference to the chosen file and backend)
data = objdict.load("my_json_file.json")
#or
data = objdict.load("my_toml_file.toml",_backend='toml')

#class methods creating new instances can be passed parameters accepted in the objdict constructor to control the properties of the created instance:
data = objdict.loads(string,_backend='json',_use_default=True,_default=None,_auto_self=False)
data = objdict.load(file,_backend='toml',_use_default=False,_auto_self=True)

#update data
data.email="dummy.email@gmail.com"
data.user="dummy_username"

#dump changes to the file using the previously chosen backend 
data.dump()

#-------------------Working with default value generators-------------------


#Default value (None) when accessing a missing key
obj=objdict(_use_default=True)

#Will set the value to None and won't raise a KeyError
print(obj.a) #Output: None

#Or, choose a default value
obj=objdict(_use_default=True,_default=3)
#Missing key will be initialized to 3 and returned
print(obj.a) #Output: 3

#Or pass a default value generator depending on the key (must have 'key' in its signature)
default_gen=lambda key: f"Missing key: {key}" 
obj=objdict(_use_default=True,_default=default_gen)
print(obj.a) #Output: "Missing key: a"
print(obj.b) #Output: "Missing key: b"

#Or pass a default value generator whose output depends on the current state/content of the objdict
#Must have 'self' in its signature
#Will use 'self' as the keyword refering to the current objdict instance
def default_gen(self):
    if 'a' in self:
        return self.a.value
    else:
        return objdict(value=5)
        
obj=objdict(_use_default=True,_default=default_gen)
print(obj.a) #Output: {'value':5}
print(obj.b) #Output: 5

#Accepted signature of default value generators are () ; (self,); (key,) ; (self,key)
#This allows implementing context-aware and key-dependant logic for default value attribution. 
#Any other signature will be considered invalid and will fall back to assign the callable itself as the default value for all keys.

#Example: Using a default value generator to automatically create new child objdict instances inheriting the parent's settings when accessing missing keys
def child_instance(self):
    return objdict(_use_default=True,_default=child_instance,_backend=self._backend,_auto_self=self._auto_self)

obj=objdict(_use_default=True,_default=child_instance,_backend='toml',_auto_self=True)
obj.a.b.c=3
print(obj) #Output: {'a':{'b':{'c':3}}}
#child elements inherit the chosen parent properties
print(obj.a.b._backend) #Output: 'toml'
print(obj.a.b._auto_self) #Output: True

#The child_instance generator hard-coded above is already implemented as the objdict.child_instance static method which you may pass as _default parameter
obj=objdict(_use_default=True,_default=objdict.child_instance)
obj.a.b.c=3
print(obj) #Output: {'a':{'b':{'c':3}}}

#--------------------------------Mock objects-------------------------------

#Using the objdict as a mocked object with context aware methods thanks to the _auto_self parameter which automatically passes the objdict instance as 'self' to callable attributes having 'self' as first parameter in their signature.

obj=objdict(_auto_self=True)
obj.a=2

#create a function with 'self' as first parameter (any other name won't receive the instance)
def add_to_a(self,b):
    self.a+=b

#attach the function as attribute
obj.add_to_a=add_to_a
obj.add_to_a(3)
print(obj.a) #output 5

License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

objdict_bf-0.1.22.tar.gz (11.7 kB view details)

Uploaded Source

Built Distribution

objdict_bf-0.1.22-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

Details for the file objdict_bf-0.1.22.tar.gz.

File metadata

  • Download URL: objdict_bf-0.1.22.tar.gz
  • Upload date:
  • Size: 11.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.10.12

File hashes

Hashes for objdict_bf-0.1.22.tar.gz
Algorithm Hash digest
SHA256 793b99bd3a57d7198fe065ad9f795f86770e90a181e1eddb4dd2f31a3fd6654e
MD5 b91ec911e227068b1ec8cd3335efddf8
BLAKE2b-256 e55d635e0f47457fc5081bc32a57889db754c40990987745e880d285a0ffded9

See more details on using hashes here.

File details

Details for the file objdict_bf-0.1.22-py3-none-any.whl.

File metadata

  • Download URL: objdict_bf-0.1.22-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.10.12

File hashes

Hashes for objdict_bf-0.1.22-py3-none-any.whl
Algorithm Hash digest
SHA256 a3da28358aebe1c69ab9f9158fe9a3ea7b0b0ba5c34f437e3463f2597c9796f7
MD5 29c59b6454896f6ad6138760c703ae43
BLAKE2b-256 f9de75808bbaeb73d736a8b408ceb7af3f331212669c50ebb113289ee60ce72a

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page