# dicta

A dict subclass that observes a nested dict and listens for changes in its data structure. If a data change is registered, Dicta reacts with a callback or a data-export to a JSON file.

## Core Functionality

- Throw a callback, when the nested data structure changes
- Write data to a JSON file, when the nested data structure changes

## Features

- Behaves like a regular `dict` and supports all `dict`, `list`, `tuple` and `set` methods.
- Supports nesting of all possible datatypes like `dict`, `list`, `tuple`, `set` and other objects like custom classes.
- Encodes non-serializable objects to a binary-string when writing data to a file (Optional / deactivated by default).
- Reading data from a file will decode a binary-string back to a non-serializable object (Optional / deactivated by default).
- Import/Insert additional data from json files.
- Export data to json files.


## Install

```
pip3 install dicta
```

## How to use

```python
import dicta

# Declare the 'Dicta' class.
dicta = dicta.Dicta()

# Activate binary serialization
dicta.setBinarySerializer(True)

# Set a synch file path.
dicta.synchFile("data.json")

# Define the callback method
def callback():
    print("Data changed!")
    print(dicta)

# Bind the callback method to dicta
dicta.bind(callback)

# Add data
dicta["entities"] = {}
dicta["entities"]["persons"] = []

dicta["entities"]["persons"].append({"name":"john", "age":23})
dicta["entities"]["persons"].append({"name":"peter", "age":13})

# Update a key in a dict
dicta["entities"]["persons"][1]["age"] = 42

# Add another nested list to the dict
dicta["entities"]["animals"] = []
dicta["entities"]["animals"].append("lion")
dicta["entities"]["animals"].append("elephant")

# Slice item from list
del dicta["entities"]["animals"][0:1]

# Remove item from dict
dicta["entities"].pop("persons")

# and so forth…
# Should support all regular dict behaviours and 
# list methods (pop(), append(), slice(), insert() …)

# Import additional data from another file. 
# (New data will be added. Old data remains but will 
# be overwritten if dict keys match.)
dicta.insert("additional_data_file.json")

# Export the data to another file
dicta.export("data_backup.json")

# Get string representation of the Dicta
dicta.stringify()
```

## Reference

---

#### Dicta()

```python
Dicta(*args, **kwargs)
```

A dict subclass.

###### **Parameter**

- ***args** *(Optional)*
- ****kwargs** *(Optional)*

###### **Return**

- **Dicta Class**

---

### Methods

#### Dicta Methods

---

##### Dicta.bind()

```python
Dicta.bind(callback, response=False, *args, *kwargs)
```

Sets the callback method for the Dicta Class. If `response=False` (default) the callback method only gets the `*args, *kwargs` as parameters you define. If `response=True` the callback method gets response from the Dicta Class. You should define your callback function with a `*kwargs` parameter or with three positional parameters:

`def my_callback(**kwargs)`

or

`def my_callback(modifed_object, modify_info, modify_trace)`

###### **Parameter**

- **callback** *(method)*
- **default_response** *(bool) (optional / default = False)*

###### **Callback**

- **args** as defined in setCallback *(optional / default: None)*
- **kwargs** as defined in setCallback *(optional / default: None)*
- **modifed_object** *(object)*
- ***modify_info*** *(json_string)*: Contains info about the data mod
- **modify_trace** *(list)*: Contains the dict-tree-path to the modified object as a list starting from root*

---

##### Dicta.syncFile()

```python
Dicta.syncFile(path, reset=False)
```

Sets the sync file to automatically store the data on data change. If `reset=False` (default) old data will remain and will be updated with new data . If `reset=True` the data wil be cleared when `syncFile()` is called.

**This will fail if your dict contains non-serializable objects and binary serialization is not activated.** For security reasons this is deactivated by default. You can activate binary serialization manually with `Dicta.useBinarySerializer(True)`.

If you activate the binary-serializer all non-serializable objects will be encoded to a binary string and packed into a `dict` labeled with the key `'<serialized-object>'`. See the reference for `Dicta.useBinarySerializer()`.

###### **Parameter**

- **path** *(string)*
- **reset** *(bool) (optional / default = False)*

---

##### Dicta.importFile()

```python
Dicta.importFile(path)
```

Import data from a file. New data will be added to the DictObsercer, old data remains but will be overwritten if dict keys match.

###### **Parameter**

- **path** *(string)*

---

##### Dicta.exportFile()

```python
Dicta.exportFile(path, reset=True)
```

Export data to a file. If `reset=True` the data wil be cleared when `exportFile()` (default) is called . If `reset=False` the data will be updated.

**This will fail if your dict contains non-serializable objects and binary serialization is not activated.** For security reasons this is deactivated by default. You can activate binary serialization by calling `Dicta.useBinarySerializer(True)` before.

If you activate the binary-serializer all non-serializable objects will be encoded to a binary string and packed into a `dict` labeled with the key `'<serialized-object>'`. See the reference for `Dicta.useBinarySerializer()`.

###### **Parameter**

- **path** (string)
- **reset** *(bool) (optional / default = True)*

---

##### Dicta.clearFile()

```python
Dicta.clearFile(path)
```

Clear a file.

###### **Parameter**

- **path** *(string)*

---

##### Dicta.removeFile()

```python
Dicta.removeFile(path)
```

Remove a data file.

###### **Parameter**

- **path** *(string)*

---

##### Dicta.dictify()

```python
Dicta.dictify()
```

Returns a plain dict representation of the data without Dicta functionality.

###### **Parameter**

- None

###### **Return**

- **dict**

---

##### Dicta.stringify()

```python
Dicta.stringify(returnBinaries=False)
```

Returns a string representation of the data in Dicta.

**This will fail if your dict contains non-serializable objects and binary serialization is not activated.** For security reasons this is deactivated by default. You can activate binary serialization by calling `Dicta.useBinarySerializer(True)` before.

If you activate the binary-serializer all non-serializable objects will be encoded to a binary string and packed into a `dict` labeled with the key `'<serialized-object>'`. See the reference for `Dicta.useBinarySerializer()`.

For better readability serialized objects won´t be returned by default and are replaced by a the `'<serialized-object>'` hook. If you want to return the binaries set the `return_binaries`parameter to `True`.

###### **Parameter**

- **return_binaries** *(bool) (default = False)*

###### **Return**

- **string**

---

##### Dicta.setBinarySerializer()

```python
Dicta.setBinarySerializer(binary_serializer=False, serializer_hook='<serialized-object>')
```

For security reasons binary serialization of non-serializable objects is deactivated by default. You can activate or deactivate binary serialization with this method (default=False).

If you activate the binary-serializer all non-serializable objects will be encoded to a binary string and packed into a dict labeled with the key `'<serialized-object>'`. In case you need this key for your data structure, define a custom serializer-hook by using the `serializer_hook` parameter (optional). If you don´t use the `serializer_hook` parameter the default hook `'<serialized-object>'` will be used.

###### Parameter

- **use_binary_serializer** *(bool) (default = False)*
- **serializer_hook** *(string) (optional / default = '\<serialized-object>')*

###### Example

```python
myDictObserver.useBinarySerializer(True, '<my_serialzer_hook>')
```

---

#### Data Type Methods

Behaves like a regular nested dict and supports all data type methods. Adding, removing, modifiying and accessing of nested elements should work out of the box. For example:

---

##### NestedDict.update()

```python
NestedDict.update(*args, *kwargs)
```

---

##### NestedDict.clear()

```python
NestedDict.clear()
```

---

##### NestedDict.pop()

```python
NestedDict.pop(key)
```

---

##### NestedDict.popitem()

```python
NestedDict.popitem(key)
```

---

##### NestedDict.setdefault()

```python
NestedDict.setdefault(key, default=None)
```

*and so forth: keys(), iter() …*

---

##### NestedList.append()

```python
NestedList.append(item)
```

*and so forth: pop()…*

---

## Dependencies

- os
- re
- json
- pickle
