fancy-dict

Build Status Build Status Coverage Status Test Status

Extends python dictionaries with merge, load and filter functions

Key Features

  • Load data from various sources (dicts, Files, HTTP)
  • Customize the merging behavior on update()
  • Filter data of dictionaries

Currently only tested on Python 3.6

Installation

$ pip install git+https://github.com/stefanhoelzl/fancy-dict.git

Usage

Basics

>>> from fancy_dict import FancyDict

# Load data form GitHub-API
>>> repo = FancyDict.load("https://api.github.com/repos/stefanhoelzl/fancy-dict")

# Access like plain python dicts
>>> repo["owner"]["avatar_url"]
'https://avatars0.githubusercontent.com/u/1478183?v=4'

# Nested updates
>>> repo.update({"owner": {"avatar_url": "https://avatars0.githubusercontent.com/u/254659"}})

# Other values in repo["owner"] are still present
>>> repo["owner"]["html_url"]
'https://github.com/stefanhoelzl'

Load and merge annotated yaml/json files.

# Create directories later needed
>>> import os
>>> os.makedirs("inc")

# Import used fancy_dict classes
>>> from fancy_dict import FancyDict
>>> from fancy_dict.loader import KeyAnnotationsConverter

# write settings defaults
>>> with open("inc/base.yml", "w+") as base_file:
...     base_file.write('{"counter[add]": 0, "settings": {"skip": True}}')
47

# write custom settings
>>> with open("config.yml", "w+") as config_file:
...     config_file.write('{"include": ["base.yml"], "counter": 1, "settings": {"+skip": False, "?merge": True}}')
85

# merge custom and default settings
>>> FancyDict.load("config.yml", include_paths=("inc",), include_key="include", annotations_decoder=KeyAnnotationsConverter)
{'counter': 1, 'settings': {'skip': True}}

Annotate keys to control updating behavior

>>> from fancy_dict import FancyDict
>>> from fancy_dict.merger import add
>>> from fancy_dict.conditions import if_existing, if_not_existing

# Set a custom merge method (defines how old and new value get merged)
>>> annotated_dict = FancyDict({"counter": 0})

# sets an annotation that the key "counter" should be updated by adding old and new value
>>> annotated_dict.annotate("counter", merge_method=add)
>>> annotated_dict.update({"counter": 1})
>>> annotated_dict["counter"]
1
>>> annotated_dict.update({"counter": 1})
>>> annotated_dict["counter"]
2

# Finalizes a value for a given key, so updates dont change this value
>>> annotated_dict.annotate("counter", finalized=True)
>>> annotated_dict.update({"counter": 1})
>>> annotated_dict["counter"]
2

# direct changes of this key are still possible
>>> annotated_dict["counter"] = 0
>>> annotated_dict["counter"]
0

# set annotations so that updates only apply under certain conditions
>>> annotated_dict.annotate("not_existing", condition=if_existing)
>>> annotated_dict.update({"not_existing": False})
>>> annotated_dict.keys()
dict_keys(['counter'])

>>> annotated_dict["not_existing"] = False
>>> annotated_dict.update({"not_existing": True})
>>> annotated_dict["not_existing"]  # value was updated, because it was existing before
True

# same for if_not_existing condition
>>> annotated_dict.annotate("existing", condition=if_not_existing)
>>> annotated_dict["existing"] = False
>>> annotated_dict.update({"existing": True})
>>> annotated_dict["existing"]
False
>>> del annotated_dict["existing"]
>>> annotated_dict.update({"existing": True})
>>> annotated_dict["existing"]
True

Development status

Alpha

working towards the first release and better documentation!

Documentation

http://fancy-dict.readthedocs.io/

Contribution

  • There is a bug? Write an Issue
  • Feedback or Questions? Write an Issue
  • You like it? Great!! Spread the news :)

Submit changes

tested on macOS and Linux (will be different on Windows)

Fork it and check out:

$ git checkout https://github.com/<YourUsername>/fancy-dict.git

setup the development environment

$ cd fancy-dict
$ virtualenv venv
$ source venv/bin/activate
$ make env.install

Write a failing test, make your changes until all tests pass

$ make tests           # runs all tests
$ make tests.unit      # runs only unit tests
$ make tests.lint       # runs only linter
$ make tests.coverage  # runs only code coverage

Before making a pull request, check if still everythinig builds

$ make       # runs all tests, builds the docs and creates a package
$ make clean # cleans the repository

Create a pull request!

License

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