python-benedict
python-benedict is a dict subclass with keylist/keypath/keyattr support, I/O shortcuts (base64
, cli
, csv
, html
, ini
, json
, pickle
, plist
, query-string
, toml
, xls
, xml
, yaml
) and many utilities... for humans, obviously.
Features
- 100% backward-compatible, you can safely wrap existing dictionaries.
NEW
Keyattr support for get/set items using keys as attributes.- Keylist support using list of keys as key.
- Keypath support using keypath-separator (dot syntax by default).
- Keypath list-index support (also negative) using the standard
[n]
suffix. - Normalized I/O operations with most common formats:
base64
, cli
, csv
, html
, ini
, json
, pickle
, plist
, query-string
, toml
, xls
, xml
, yaml
. - Multiple I/O operations backends:
file-system
(read/write), url
(read-only), s3
(read/write). - Many utility and parse methods to retrieve data as needed (check the API section).
- Well tested. ;)
Index
Installation
If you want to install everything:
- Run
pip install "python-benedict[all]"
alternatively you can install the main package:
Optional Requirements
Here the hierarchy of possible installation targets available when running pip install "python-benedict[...]"
(each target installs all its sub-targets):
[all]
[io]
[html]
[toml]
[xls]
[xml]
[yaml]
[parse]
[s3]
Usage
Basics
benedict
is a dict
subclass, so it is possible to use it as a normal dictionary (you can just cast an existing dict).
from benedict import benedict
d = benedict()
d = benedict(existing_dict)
d = benedict("https://localhost:8000/data.json", format="json")
params = benedict(request.GET.items())
page = params.get_int("page", 1)
Keyattr
It is possible to get/set items using keys as attributes (dotted notation).
d = benedict(keyattr_dynamic=True)
d.profile.firstname = "Fabio"
d.profile.lastname = "Caccamo"
print(d)
By default, if the keyattr_dynamic
is not explicitly set to True
, this functionality works for get/set only already existing items.
Disable keyattr functionality
You can disable the keyattr functionality passing keyattr_enabled=False
option in the constructor.
d = benedict(existing_dict, keyattr_enabled=False)
or using the getter/setter
property.
d.keyattr_enabled = False
Dynamic keyattr functionality
You can enable the dynamic attributes access functionality passing keyattr_dynamic=True
in the constructor.
d = benedict(existing_dict, keyattr_dynamic=True)
or using the getter/setter
property.
d.keyattr_dynamic = True
Warning - even if this feature is very useful, it has some obvious limitations: it works only for string keys that are unprotected (not starting with an _
) and that don't clash with the currently supported methods names.
Keylist
Wherever a key is used, it is possible to use also a list of keys.
d = benedict()
d[["profile", "firstname"]] = "Fabio"
d[["profile", "lastname"]] = "Caccamo"
print(d)
print(d["profile"])
print([["profile", "lastname"]] in d)
del d[["profile", "lastname"]]
print(d["profile"])
Keypath
.
is the default keypath separator.
If you cast an existing dict and its keys contain the keypath separator a ValueError
will be raised.
In this case you should use a custom keypath separator or disable keypath functionality.
d = benedict()
d["profile.firstname"] = "Fabio"
d["profile.lastname"] = "Caccamo"
print(d)
print(d["profile"])
print("profile.lastname" in d)
del d["profile.lastname"]
Custom keypath separator
You can customize the keypath separator passing the keypath_separator
argument in the constructor.
If you pass an existing dict to the constructor and its keys contain the keypath separator an Exception
will be raised.
d = benedict(existing_dict, keypath_separator="/")
Change keypath separator
You can change the keypath_separator
at any time using the getter/setter
property.
If any existing key contains the new keypath_separator
an Exception
will be raised.
d.keypath_separator = "/"
Disable keypath functionality
You can disable the keypath functionality passing keypath_separator=None
option in the constructor.
d = benedict(existing_dict, keypath_separator=None)
or using the getter/setter
property.
d.keypath_separator = None
List index support
List index are supported, keypaths can include indexes (also negative) using [n]
, to perform any operation very fast:
loc = d["results[0].locations[-1].coordinates"]
lat = loc.get_decimal("latitude")
lng = loc.get_decimal("longitude")
I/O
For simplifying I/O operations, benedict
supports a variety of input/output methods with most common formats: base64
, cli
, csv
, html
, ini
, json
, pickle
, plist
, query-string
, toml
, xls
, xml
, yaml
.
Input via constructor
It is possible to create a benedict
instance directly from data-source (filepath
, url
, s3
or data
string) by passing the data source and the data format (optional, default "json") in the constructor.
d = benedict("/root/data.yml", format="yaml")
d = benedict("https://localhost:8000/data.xml", format="xml")
d = benedict("s3://my-bucket/data.xml", s3_options={"aws_access_key_id": "...", "aws_secret_access_key": "..."})
d = benedict('{"a": 1, "b": 2, "c": 3, "x": 7, "y": 8, "z": 9}')
Input methods
- All input methods can be accessed as class methods and are prefixed by
from_*
followed by the format name. - In all input methods, the first argument can represent a source: file path, url, s3 url, or data string.
Input sources
All supported sources (file, url, s3, data) are allowed by default, but in certains situations when the input data comes from untrusted sources it may be useful to restrict the allowed sources using the sources
argument:
d = benedict("https://localhost:8000/data.json", sources=["url"])
d = benedict.from_json("https://localhost:8000/data.json", sources=["url"])
d = benedict("s3://my-bucket/data.json", sources=["url"])
d = benedict.from_json("s3://my-bucket/data.json", sources=["url"])
Output methods
- All output methods can be accessed as instance methods and are prefixed by
to_*
followed by the format name. - In all output methods, if
filepath="..."
kwarg is specified, the output will be also saved at the specified filepath.
Supported formats
Here are the details of the supported formats, operations and extra options docs.
format | input | output | extra options docs |
---|
base64 | :white_check_mark: | :white_check_mark: | - |
cli | :white_check_mark: | :x: | argparse |
csv | :white_check_mark: | :white_check_mark: | csv |
html | :white_check_mark: | :x: | bs4 (Beautiful Soup 4) |
ini | :white_check_mark: | :white_check_mark: | configparser |
json | :white_check_mark: | :white_check_mark: | json |
pickle | :white_check_mark: | :white_check_mark: | pickle |
plist | :white_check_mark: | :white_check_mark: | plistlib |
query-string | :white_check_mark: | :white_check_mark: | - |
toml | :white_check_mark: | :white_check_mark: | toml |
xls | :white_check_mark: | :x: | openpyxl - xlrd |
xml | :white_check_mark: | :white_check_mark: | xmltodict |
yaml | :white_check_mark: | :white_check_mark: | PyYAML |
API
-
Utility methods
-
I/O methods
-
Parse methods
Utility methods
These methods are common utilities that will speed up your everyday work.
Utilities that accept key argument(s) also support keypath(s).
Utilities that return a dictionary always return a new benedict
instance.
clean
d.clean(strings=True, collections=True)
clone
c = d.clone()
dump
s = benedict.dump(d.keypaths())
print(s)
d = benedict()
print(d.dump())
filter
predicate = lambda k, v: v is not None
f = d.filter(predicate)
find
keys = ["a.b.c", "m.n.o", "x.y.z"]
f = d.find(keys, default=0)
flatten
f = d.flatten(separator="_")
groupby
g = d.groupby("cities", by_key="country_code")
invert
i = d.invert(flat=False)
items_sorted_by_keys
items = d.items_sorted_by_keys(reverse=False)
items_sorted_by_values
items = d.items_sorted_by_values(reverse=False)
keypaths
k = d.keypaths(indexes=False, sort=True)
match
m = d.match(pattern, indexes=True)
merge
d.merge(a, b, c, overwrite=True, concat=False)
move
d.move("a", "b", overwrite=True)
nest
d.nest("values", id_key="id", parent_id_key="parent_id", children_key="children")
remove
d.remove(["firstname", "lastname", "email"])
rename
d.rename("first_name", "firstname")
search
r = d.search("hello", in_keys=True, in_values=True, exact=False, case_sensitive=False)
standardize
d.standardize()
subset
s = d.subset(["firstname", "lastname", "email"])
swap
d.swap("firstname", "lastname")
traverse
def f(d, key, value):
print(f"dict: {d} - key: {key} - value: {value}")
d.traverse(f)
unflatten
u = d.unflatten(separator="_")
unique
d.unique()
I/O methods
These methods are available for input/output operations.
from_base64
d = benedict.from_base64(s, subformat="json", encoding="utf-8", **kwargs)
from_cli
d = benedict.from_cli(s, **kwargs)
from_csv
d = benedict.from_csv(s, columns=None, columns_row=True, **kwargs)
from_html
d = benedict.from_html(s, **kwargs)
from_ini
d = benedict.from_ini(s, **kwargs)
from_json
d = benedict.from_json(s, **kwargs)
from_pickle
d = benedict.from_pickle(s, **kwargs)
from_plist
d = benedict.from_plist(s, **kwargs)
from_query_string
d = benedict.from_query_string(s, **kwargs)
from_toml
d = benedict.from_toml(s, **kwargs)
from_xls
d = benedict.from_xls(s, sheet=0, columns=None, columns_row=True, **kwargs)
from_xml
d = benedict.from_xml(s, **kwargs)
from_yaml
d = benedict.from_yaml(s, **kwargs)
to_base64
s = d.to_base64(subformat="json", encoding="utf-8", **kwargs)
to_csv
s = d.to_csv(key="values", columns=None, columns_row=True, **kwargs)
to_ini
s = d.to_ini(**kwargs)
to_json
s = d.to_json(**kwargs)
to_pickle
s = d.to_pickle(**kwargs)
to_plist
s = d.to_plist(**kwargs)
to_query_string
s = d.to_query_string(**kwargs)
to_toml
s = d.to_toml(**kwargs)
to_xml
s = d.to_xml(**kwargs)
to_yaml
s = d.to_yaml(**kwargs)
Parse methods
These methods are wrappers of the get
method, they parse data trying to return it in the expected type.
get_bool
d.get_bool(key, default=False)
get_bool_list
d.get_bool_list(key, default=[], separator=",")
get_date
d.get_date(key, default=None, format=None, choices=[])
get_date_list
d.get_date_list(key, default=[], format=None, separator=",")
get_datetime
d.get_datetime(key, default=None, format=None, choices=[])
get_datetime_list
d.get_datetime_list(key, default=[], format=None, separator=",")
get_decimal
d.get_decimal(key, default=Decimal("0.0"), choices=[])
get_decimal_list
d.get_decimal_list(key, default=[], separator=",")
get_dict
d.get_dict(key, default={})
get_email
d.get_email(key, default="", choices=None, check_blacklist=True)
get_float
d.get_float(key, default=0.0, choices=[])
get_float_list
d.get_float_list(key, default=[], separator=",")
get_int
d.get_int(key, default=0, choices=[])
get_int_list
d.get_int_list(key, default=[], separator=",")
get_list
d.get_list(key, default=[], separator=",")
get_list_item
d.get_list_item(key, index=0, default=None, separator=",")
get_phonenumber
d.get_phonenumber(key, country_code=None, default=None)
get_slug
d.get_slug(key, default="", choices=[])
get_slug_list
d.get_slug_list(key, default=[], separator=",")
get_str
d.get_str(key, default="", choices=[])
get_str_list
d.get_str_list(key, default=[], separator=",")
get_uuid
d.get_uuid(key, default="", choices=[])
get_uuid_list
d.get_uuid_list(key, default=[], separator=",")
Testing
git clone https://github.com/fabiocaccamo/python-benedict.git && cd python-benedict
python -m venv venv && . venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-test.txt
pre-commit install --install-hooks
tox
python -m unittest
License
Released under MIT License.
Supporting
- :star: Star this project on GitHub
- :octocat: Follow me on GitHub
- :blue_heart: Follow me on Twitter
- :moneybag: Sponsor me on Github
See also