diff --git a/docs/node/clients/python_wrapper.md b/docs/node/clients/python_wrapper.md
new file mode 100644
index 0000000..a4b3c47
--- /dev/null
+++ b/docs/node/clients/python_wrapper.md
@@ -0,0 +1,931 @@
+# Python-Wrapper Documentation:
+
+>Important Preface:
+All of the documentation and testing done regarding the VILLASnode Python-Wrapper
+is based upon the signal_v2 node provided by VILLASnode.
+Node specific functions are implemented on a node to node basis and
+therefore may exert different behavior.
+
+1. [VILLASnode functions exposed by the C-API](#1.-villasnode-functions-exposed-by-the-c-api)
+ - [Functions to set up a node or modify its state](#functions-to-set-up-a-node-or-modify-its-state)
+ - [Functions to extract node specific information](#functions-to-extract-node-specific-information)
+ - [Functions related to data transfer](#functions-related-to-data-transfer)
+2. [Using the C-API with the Python-Wrapper](#2.-using-the-c-api-with-the-python-wrapper)
+3. [Bugs](#3.-bugs)
+4. [Improvements](#4.-improvements)
+5. [Contributing to the Python-Wrapper](#5.-contributing-to-the-python-wrapper)
+6. [Installation](#6.-installation)
+
+## 1. VILLASnode functions exposed by the C-API
+```
+#Functions to set up up a node or modify its state
+
+
+vnode * node_new(const char *id_str, const char *json_str);
+
+int node_check(vnode *n);
+int node_prepare(vnode *n);
+int node_start(vnode *n);
+int node_stop(vnode *n);
+int node_pause(vnode *n);
+int node_resume(vnode *n);
+int node_restart(vnode *n);
+int node_destroy(vnode *n);
+
+#Functions to extract node specific information
+
+bool node_is_valid_name(const char *name);
+bool node_is_enabled(const vnode *n);
+const char *node_name(vnode *n);
+const char *node_name_short(vnode *n);
+const char *node_name_full(vnode *n);
+const char *node_details(vnode *n);
+unsigned node_input_signals_max_cnt(vnode *n);
+unsigned node_output_signals_max_cnt(vnode *n);
+const char *node_to_json_str(vnode *n);
+unsigned sample_length(vsample *smp);
+
+#Functions related to data transfer
+
+int node_reverse(vnode *n);
+int node_read(vnode *n, vsample **smps, unsigned cnt);
+int node_write(vnode *n, vsample **smps, unsigned cnt);
+int node_poll_fds(vnode *n, int fds[]);
+int node_netem_fds(vnode *n, int fds[]);
+vsample *sample_alloc(unsigned len);
+void sample_decref(vsample *smp);
+vsample *sample_pack(unsigned seq, struct timespec *ts_origin,
+ struct timespec *ts_received, unsigned len,
+ double *values);
+void sample_unpack(vsample *s, unsigned *seq, struct timespec *ts_origin,
+ struct timespec *ts_received, int *flags, unsigned *len,
+ double *values);
+int memory_init(int hugepages);
+```
+---
+### Functions to set up a node or modify its state
+
+
+`node_new(const char *id_str, const char *json_str)` takes two strings as input parameters.
+- `id_str:` identification string (uuid - 36 characters long + 1 character for null termination).
+If not provided, resulting in the nullptr, a random uuid is created and assigned to the node by VILLASnode.
+It can be retrieved by different functions like [node_name_full()](#node_name_full()).
+
+- `json_str:` the string containing a valid json configuration
+
+Invalid json configurations will throw an error.
+
+**Further:** valid VILLASnode configurations can be either checked for with `node_check()`
+if the configuration has been accepted by VILLASnode ***or*** may fail completely when
+a wrong configurations is used to create a node, resulting in a runtime error.
+The required json format to configure nodes can be found [here](https://villas.fein-aachen.org/docs/node/nodes/).
+
+***Paths or the VILLASdaemon are not used for the Python-Wrapper instance.
+Therefore only the node configurations need to be considered.***
+
+
+ Creating a Node
+
+```
+import uuid
+import villas_node as vn
+
+# some valid json config
+config = {
+ ...
+}
+
+# config is a singular node configuration
+# creating an uuid - optional
+id = str(uuid.uuid4())
+
+#invalid uuid's are discarded and a new random one is generated by VILLASnode
+#node = vn.node_new(config) does not work, some input for the uuid is necessary
+#node = vn.node_new("", config)
+#node = vn.node_new("0", config)
+
+node = vn.node_new(id, config)
+
+# config is a list of node configurations
+nodes = {}
+# creating new nodes, accessible by name
+for name, content in data.items():
+ #dictionary to extract the name of each node
+ obj = {name: content}
+
+ # read inner configuration/json object to create a node
+ config = json.dumps(obj, indent=2)
+ id = str(uuid.uuid4())
+
+ nodes[name] = vn.node_new(id, config)
+
+```
+
+
+---
+
+`int node_check(vnode *n)` checks the in and output signals of a node and sets the node state to **checked**
+
+`int node_prepare(vnode *n)` sets up the in and output signals of a node and sets the node state **prepared**
+
+`int node_start(vnode *n)` starts a node and sets the node state **started**
+
+`int node_stop(vnode *n)` stops a node, can be (re-)started and but resumed
+
+`int node_pause(vnode *n)` pauses a node, can be resumed and stopped
+
+`int node_resume(vnode *n)` resumes a paused node, does not work if the node stopped
+
+`int node_restart(vnode *n)` restarts a stopped node
+
+
+`int node_destroy(vnode *n)` deletes/destroys a node, can not be used again leaving the node pointer [dangling](#dangling_pointer_comment) (do not use (the pointer))
+
+- `vnode *n` a node pointer that can be created by [node_new()](#node_new())
+
+The functions have return codes on success or failure.
+In other words either `-1`, `0`, `1` is returned depending on either:
+- unchanged `-1`
+- success `0`
+- failure `1`
+
+An example: in case node_start() is used on a node that has already been started `1` is returned.
+This can be used for branching.
+
+ All of these functions can be used like this
+
+```
+# assuming node is a valid node
+node = ...
+
+node_prepare(node)
+node_check(node)
+status = node_start(node)
+node_stop(node)
+node_pause(node)
+node_resume(node)
+node_restart(node)
+node_destroy(node)
+
+# branching
+if (status == -1):
+ print("Node already started!")
+if (status == 0):
+ print("Node started!")
+if (status == 1):
+ print("Starting the node failed!")
+```
+
+
+---
+### Functions to extract node specific information
+
+`bool node_is_valid_name(const char *name)`
+
+`bool node_is_enabled(const vnode *n)`
+
+`const char *node_name(vnode *n)` returns the node name
+
+`const char *node_name_short(vnode *n)` [not working](#node_name_short)
+
+`const char *node_name_full(vnode *n)` returns name and details of the node
+>output structure of the details:
+node_name\: uuid=\,
+#in.signals=<.../...>,
+#out.signals<.../...>,
+#in.hooks=...,
+#out.hooks=...,
+in.vectorize=...,
+out.vectorize=...,
+out.netem=...,
+layer=...,
+in.address=,
+out.address
+
+`const char *node_details(vnode *n)` returns less details than node_name_full()
+>layer=...,
+in.address=\,
+out.address=\
+
+`unsigned node_input_signals_max_cnt(vnode *n)` returns input signal count
+
+`unsigned node_output_signals_max_cnt(vnode *n)` returns output signal count
+
+`const char *node_to_json_str(vnode *n)` returns node config in string format
+
+`unsigned sample_length(vsample *smp)` returns the length of the samples stored in a sample object
+
+
+ All of these functions can be used like this
+
+```
+#assuming node is a valid node
+name = "some name"
+node = ...
+
+node_is_valid_name(name)
+node_is_enabled(node)
+node_name(node)
+node_name_short(node)
+nodfe_name_full(node)
+node_details(node)
+node_input_signals_max_cnt(node)
+node_output_signals_max_cnt(node)
+node_to_json_str(node)
+
+#sample_length() requires a sample handle
+#this can be a sample stored in an array
+samples = smps_array(1)
+samples[0] = sample_alloc(i) #i should be the sample length
+...
+
+sample_length(samples[0])
+
+#or a sample created manually with sample_pack()
+
+sample = sample_pack(...)
+
+sample_length(sample)
+```
+
+
+---
+`json_t *node_to_json(const vnode *n):` returns a [node configuration](#node_to_json_wrong_format) of the node
+
+The node configuration returned is either of type:
+- `none`
+- `int`
+- `float`
+- `bool`
+- `string`
+
+the following returned types contain the ones above:
+- `dictionary`
+- `list`
+
+---
+### Functions related to data transfer
+
+`int node_reverse(vnode *n)` swap in and output signals of a node
+
+`int node_read(vnode *n, vsample **smps, unsigned cnt)` reads from the node's storage/buffer
+
+- `vnode *n` the pointer to a node
+- `vsample **smps` is a pointer to a data structure that can hold samples
+
+Since this is impossible without a wrapper class such as the [sample holding array](#2.-implementation-notes) to do natively with a python data structure, the samples array has to be used for this.
+
+- `unsigned cnt` the amount of samples to read or write
+
+Some nodes like the signal generator (v2) node can only read one sample at a time. Especially when using things such as rt (realtime) mode.
+
+`int node_write(vnode *n, vsample **smps, unsigned cnt)` writes to a node's storage/buffer
+
+The same as `node_read()` above.
+
+`int node_poll_fds(vnode *n, int fds[])`
+
+`int node_netem_fds(vnode *n, int fds[])`
+
+`vsample *sample_alloc(unsigned len)` allocates a single sample
+
+`void sample_decref(vsample *smp)` decrement and delete sample pointer
+
+The sample is deleted and deallocated if it has no pointers pointing to it.
+
+`vsample *sample_pack(unsigned seq, struct timespec *ts_origin,
+ struct timespec *ts_received, unsigned len,
+ double *values)` creates a sample manually
+
+`void sample_unpack(vsample *s, unsigned *seq, struct timespec *ts_origin,
+ struct timespec *ts_received, int *flags, unsigned *len,
+ double *values)`
+
+`int memory_init(int hugepages)` initializes memory system with **hugepages**
+
+All of these functions can be used like in [this example](#rw_test).
+
+---
+
+### 2. Using the C-API with the Python-Wrapper
+
+The wrapper exposes all of the C-API functions of VILLASnode.
+
+All functions except for:
+```
+int node_poll_fds()
+int node_netem_fds()
+vsample *sample_pack()
+void sample_unpack()
+int memory_init()
+```
+
+have been tested and work, except for:
+
+```
+node_name_short()
+node_restart()
+node_stop()
+```
+
+which may cause unexpected behavior.
+
+
+---
+### An example creating a signal, socket and file nodes to read from a configuration file, write the data to a file and send over a socket.
+
+This will be moved to a Lab in the future.
+Further there will be also another Lab of the Python-Wrapper that leverages a VILLASnode openDSS node type, which is still being implemented.
+
+
+ The configuration file may look like this
+
+```
+{
+ "send_socket": {
+ "type": "socket",
+ "format": "protobuf",
+ "layer": "udp",
+ "in": {
+ "address": "127.0.0.1:65532",
+ "signals": [
+ {
+ "name": "voltage",
+ "type": "float",
+ "unit": "V"
+ },
+ {
+ "name": "current",
+ "type": "float",
+ "unit": "A"
+ }
+ ]
+ },
+ "out": {
+ "address": "127.0.0.1:65533",
+ "netem": {
+ "enabled": false
+ },
+ "multicast": {
+ "enabled": false
+ }
+
+ }
+ },
+ "intmdt_socket": {
+ "type": "socket",
+ "format": "protobuf",
+ "layer": "udp",
+ "in": {
+ "address": "127.0.0.1:65533",
+ "signals": [
+ {
+ "name": "voltage",
+ "type": "float",
+ "unit": "V"
+ },
+ {
+ "name": "current",
+ "type": "float",
+ "unit": "A"
+ }
+ ]
+ },
+ "out": {
+ "address": "127.0.0.1:65534",
+ "netem": {
+ "enabled": false
+ },
+ "multicast": {
+ "enabled": false
+ }
+ }
+ },
+ "recv_socket": {
+ "type": "socket",
+ "format": "protobuf",
+ "layer": "udp",
+ "in": {
+ "address": "127.0.0.1:65534",
+ "signals": [
+ {
+ "name": "voltage",
+ "type": "float",
+ "unit": "V"
+ },
+ {
+ "name": "current",
+ "type": "float",
+ "unit": "A"
+ }
+ ]
+ },
+ "out": {
+ "address": "127.0.0.1:65535",
+ "netem": {
+ "enabled": false
+ },
+ "multicast": {
+ "enabled": false
+ }
+ }
+ },
+ "sig_gen_file" :{
+ "type": "file",
+ "format": "villas.human",
+"uri": "/path/to/sig_gen.log",
+ "in": {
+ "epoch_mode": "wait",
+ "signals": [
+ {
+ "name": "voltage",
+ "type": "float",
+ "unit": "V"
+ },
+ {
+ "name": "current",
+ "type": "float",
+ "unit": "A"
+ }
+ ]
+ }
+ },
+ "recv_socket_file" :{
+ "type": "file",
+ "format": "villas.human",
+ "uri": "/path/to/recv_socket.log",
+ "in": {
+ "epoch_mode": "wait",
+ "signals": [
+ {
+ "name": "voltage",
+ "type": "float",
+ "unit": "V"
+ },
+ {
+ "name": "current",
+ "type": "float",
+ "unit": "A"
+ }
+ ],
+ "hooks": [
+ {
+ "type": "print",
+ "format": "villas.human"
+ }
+ ]
+ }
+ },
+ "signal_generator": {
+ "type": "signal.v2",
+ "limit": 100,
+ "rate": 10,
+ "in": {
+ "signals": [
+ {
+ "amplitude": 2,
+ "name": "voltage",
+ "phase": 90,
+ "signal": "sine",
+ "type": "float",
+ "unit": "V"
+ },
+ {
+ "amplitude": 1,
+ "name": "current",
+ "phase": 0,
+ "signal": "sine",
+ "type": "float",
+ "unit": "A"
+ }
+ ],
+ "hooks": [
+ {
+ "type": "print",
+ "format": "villas.human"
+ }
+ ]
+ }
+ }
+}
+```
+
+
+
+**Example code taken from the Wrapper Unit tests and slightly modified:**
+```
+import json
+import uuid
+import villas_node as vn
+
+# the configuration comprises nodes with the type and name:
+#
+# signal generator node (v2): "signal_generator"
+# socket nodes: "send_socket", "intmdt_socket", "recv_socket"
+# file nodes: "sig_gen_file", "recv_socket_file"
+
+with open('/path/to/config/file.json', 'r') as f:
+ data = json.load(f)
+ f.close()
+
+# list to read and create multiple nodes from a file
+test_nodes = {}
+for name, content in data.items():
+ #dictionary to extract the name of each node
+ obj = {name: content}
+
+ # forward inner configuration to create a node
+ config = json.dumps(obj, indent=2)
+ id = str(uuid.uuid4())
+
+ #creating new nodes, accesible by name
+ test_nodes[name] = vn.node_new(id, config)
+
+# verifying the node configurations and starting them
+for node in test_nodes.values():
+ if (vn.node_check(node)):
+ raise RuntimeError(f"Failed to verify node configuration")
+ if (vn.node_prepare(node)):
+ raise RuntimeError(f"Failed to verify {vn.node_name(node)} node configuration")
+ vn.node_start(node)
+
+# declare Arrays with that can hold 1, 100 and 100 samples respectively
+send_smpls = vn.smps_array(1)
+intmdt_smpls = vn.smps_array(100)
+recv_smpls = vn.smps_array(100)
+
+for i in range(0,100):
+ # allocate memory for samples to be stored with two signal values per Sample
+ send_smpls[0] = vn.sample_alloc(2)
+ intmdt_smpls[i] = vn.sample_alloc(2)
+ recv_smpls[i] = vn.sample_alloc(2)
+
+ # generate signals and send over send socket, write to file
+ # signal nodes can only create one Sample at a time
+ vn.node_read(test_nodes["signal_generator"], send_smpls, 1)
+ vn.node_write(test_nodes["send_socket"], send_smpls, 1)
+ vn.node_write(test_nodes["sig_gen_file"], send_smpls, 1)
+
+# write intermediary signals to file (100 at once)
+vn.node_read(test_nodes["intmdt_socket"], intmdt_smpls, 100)
+vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls, 100)
+
+# write receive socket signals to file (100 at once)
+vn.node_read(test_nodes["recv_socket"], recv_smpls, 100)
+vn.node_write(test_nodes["recv_socket_file"], recv_smpls, 100)
+```
+
+___
+## 3. Bugs
+
+This is a small collection of known bugs. If any other ones are encountered, you are encouraged to open an [issue](#https://github.com/VILLASframework/node/issues).
+
+---
+Hooks are a big issue.
+Not every hook works properly, some cause undefined behavior or even segmentation faults.
+
+Hooks within the json config have no exclusive place to be defined in.
+Let's consider what would happen if you defined the same hook for print in:
+- `"in":{...}`
+- `"out": {...}`
+- `"hooks": {[...]}`
+
+ Hooks example
+
+```
+{
+ "some_node": {
+ ...
+ "in":{
+ "...": {
+ ...
+ },
+ ...
+ "hooks": {[
+ {
+ #some hook
+ },
+ ...
+ ]}
+ },
+ "out":{
+ "...": {
+ ...
+ },
+ ...
+ "hooks": {[
+ {
+ #some hook
+ },
+ ...
+ ]}
+ },
+ "hooks": {[
+ {
+ #some hook
+ },
+ ...
+ ]}
+ }
+}
+```
+
+
+---
+
+node_write_short() bugged, appears to be trying to print a free()'d (json?)object.
+
+---
+## 4. Improvements
+
+This is a small collection of known improvements that could be made. If any other suggestions arise, you are encouraged
+to either open an [issue](#https://github.com/VILLASframework/node/issues) or contact the code authors.
+
+---
+Fix [bugs](#-bugs).
+
+Convenience functions/bindings in the wrapper for ease of use (one example would be the automatic sample_decref()).
+
+Implement sample_alloc() for an array slice? for loops in python are terribly slow.
+
+Reduce the amount of calls between Python/C/C++.
+
+
+Ensure that the [dangling pointer](#dangling_pointer) is a nullptr.
+
+
+It does not seem like vectorize has much sensible use...
+
+---
+## 5. Contributing to the Python-Wrapper
+
+This is a small guide about what was used to create the Python-Wrapper to help with understanding how the wrapper works
+and therefore also help with developing for it.
+
+### Implementation Notes
+
+For Samples to be able to be used and stored in Python, it was necessary to
+create a data structure that can store them.
+This was done with a simple Array implementation that can hold the type
+`void ***` equivalent to `vsample **`, which is cast to `vsample *` when used by bindings for singular samples. These are then cast to `villas::node::Sample *`.
+- `void ***:` list to a set of samples stored in a data structure
+- `vsample **:` indicative of samples to be used with this data structure
+- `vsample *:` indicative of a singular sample to be used
+- `villas::node::Sample ` or `Sample *:` cast to the sample
+
+
+The Sample Array automatically uses sample_decref() on a sample, if its entry is overwritten or for all the Samples it holds as long as they do exist (differ from the nullptr) and the Array is deleted/destroyed.
+This makes the use easier and less annoying in python.
+Further this reduces the API calls between C/C++ and Python.
+
+For node_to_json() to return a json object, as the json library in Python itself would do, a wrapper function had to be created to translate the possible json return types to the same ones [Python would translate them to](#json_wrapper).
+This function is implemented recursively and unfortunately const can not be used, as the for each function expects a non const json_t *.
+Using a recursive function should be fine, as json objects are not typically that deep.
+Further it is only used by the tool itself to generate a valid json_t translated python object, thus it is not exposed to the wrapper and can not be used to create a "configuration bomb".
+
+
+ Json-Wrapper for C/C++/Python
+
+```
+#include
+#include
+
+namespace py = pybind11;
+
+py::object json_to_py_json(json_t *json) {
+ switch(json_typeof(json)) {
+ case JSON_NULL:
+ return py::none();
+
+ case JSON_INTEGER:
+ return py::int_(json_integer_value(json));
+
+ case JSON_REAL:
+ return py::float_(json_real_value(json));
+
+ case JSON_TRUE:
+ return py::bool_(json_string_value(json));
+
+ case JSON_FALSE:
+ return py::bool_(json_boolean_value(json));
+
+ case JSON_STRING:
+ return py::str(json_string_value(json));
+
+ case JSON_OBJECT: {
+ py::dict dict;
+ const char *key;
+ json_t *value;
+
+ json_object_foreach(json, key, value) {
+ dict[py::str(key)] = json_to_py_json(value);
+ }
+ return dict;
+ }
+
+ case JSON_ARRAY: {
+ py::list list(json_array_size(json));
+ size_t index;
+ json_t *value;
+
+ json_array_foreach(json, index, value) {
+ list[index] = json_to_py_json(value);
+ }
+ return list;
+ }
+
+ default:
+ throw std::runtime_error("Unsupported JSON type");
+ }
+}
+```
+
+
+---
+### Pybind11 (and why not SIP)
+
+Late in the process, I've realized that not declaring
+```
+extern "C"{
+#include
+}
+```
+but rather
+```
+#include
+```
+has been the cause for not being able to link bindings properly to libvillas.
+This may be, next to some shared issues with Pybind11, the main cause that caused me to abandon SIP.
+Other problems include Pybind11 or SIP not being able to deal with pointers of type `void **` or `void ***`.
+Pybind11 as well as SIP can only handle `void *` pointers natively.
+Nevertheless this does not directly pose an issue, as void pointers can simply be cast to any other type within the implemented functions.
+
+**An example:**
+
+```
+typedef void *vnode;
+
+PYBIND11_MODULE(villas_node, m) {
+ ...
+ #binding for 'node_start()'
+ m.def("node_start",
+
+ #lambda function using a pointer of type 'void *'
+ [](void *n) -> int {
+ return node_start((vnode *)n); #casting pointer to type 'vnode *'
+ });
+ ...
+}
+
+#C-API of VILLASnode called by the node_start() binding
+int node_start(vnode *n) {
+ auto *nc = (Node *)n; #'vnode *' cast to the Node type
+ return nc->start();
+}
+```
+
+SIP being originally developed to be used with PyQt may be a mighty tool, but there is too much unneccessary overhead required to learn in comparison to Pybind11, causing SIP to be not as straight forward and "barebones" as Pybind11.
+Since writing bindings with SIP does not directly leverage the Python C API, but rather uses tools to translate and at the same time optimize everything, you have to rely on SIP being flawless.
+
+This is where Pybind11 has the advantage of being a header only library, essentially being Macros that directly use the Python C API. This gives you more direct control as for how your code works and interacts with C/C++/Python.
+Especially Pybind11 being the more lightweight solution, considering dependencies, installation and integration, makes it more favorable to use.
+
+---
+### Some Pybind11 basics:
+
+The [example above](#node_start()) already shows how Pybind11 is used for the most part.
+For convenience: everything used to create the Wrapper bindings with Pybind11 is in the following documented briefly.
+The full Pybind11 Documentation can be found [here](https://pybind11.readthedocs.io/en/stable/index.html).
+
+### Module Definitions
+
+`PYBIND11_MODULE(module_name, m)`
+
+- `module_name:` module_name
+- `m:` module identifier to define module bindings
+
+`m.doc() = "":` docstring for the Python module
+
+`m.def()` takes different inputs to define Bindings separated by `,`
+
+- `"":` defines the binding name as found in the module imported into Python
+
+- `(param1, param2, ...)[] -> { #code }:` lambda function to define the implementation if not already done elsewhere.
+If already defined `&function_name` automatically uses this implementation for the binding.
+Return types can be of C/C++ standard or STL-Types and will be translated to corresponding Python Types.
+However you can also define Python return types [directly](#node_to_json()),
+this can be as plain as a generic Python Object `pybind11::object`.
+Pointers are returned within [capsules](https://docs.python.org/3/c-api/capsule.html) to Python and can therefore not be used directly within Python, for the most part, without having a wrapped function that can make use of them.
+
+- `optional: pybind11::return_value_policy:::` automatically determined, but not always as to the desired behaviour.
+Can be explicitly defined with `:`
+`take_ownership`, `copy`, `move`, `reference`, `reference_internal`, `automatic`, `automatic_reference`
+Further documentation can be found [here](https://pybind11.readthedocs.io/en/stable/advanced/functions.html).
+
+- `optional: :` docstring for a binding
+### Classes
+Can be implemented like you would implement them in C++.
+These are extended by the Python object types through Pybind11.
+Classes exposed to Python have to be defined with [bindings](#class_definition).
+
+**These are as follows:**
+`pybind11::class_(module_identifier, "Python object name")`
+- class_name has to match the C++ defined class name
+- module identifier can be chosen freely, [in this given example](#node_start()) it would be `m`
+- `"Python object name"` sets the name of the class becoming available as a Python object
+
+Further definitions are extended by `.`.
+
+`.def()` for classes follows the same pattern as `module_identifier.def()` for the bindings.
+It is however important to define e.g. the **constructor**.
+**Set** and **get functions can be implemented, which will always be
+called upon writing or reading to or from an object of the class.
+
+- `pybind::init()` is an essential argument for the constructor
+- `.def("__getitem__", ...)`
+- `.def("__setitem__", ...)`
+
+`"__getitem__"` and `"__setitem__"` are predefined by Python.
+In other words: these names have to be used in order to define the
+get and set functions for the Python object.
+
+
+ Array Class Bindings as example
+
+```
+py::class_(m, "SamplesArray")
+ .def(py::init(), py::arg("len"))
+ .def("size", &Array::size)
+ .def("__getitem__", [](Array &a, unsigned int idx) {
+ if (idx >= a.size()) {
+ throw py::index_error("Index out of bounds");
+ }
+ return a[idx];
+ })
+ .def("__setitem__", [](Array &a, unsigned int idx, void *smp) {
+ if (idx >= a.size()) {
+ throw py::index_error("Index out of bounds");
+ }
+ if (a[idx]) {
+ sample_decref(a[idx]);
+ }
+ a[idx] = (vsample *)smp;
+ });
+```
+
+
+---
+Pointer return types are returned to Python as [capsules](https://docs.python.org/3/c-api/capsule.html) (addresses can be seen and dereferenced, but are essentially useless in Python).
+Capsules are a "safe" way of having pointers in Python and using them with wrapped C++ functions.
+
+Further, capsules can be used to ensure lifetime constraints, if you use them as a return object. You can use a lambda function to return a capsule with a defined destructor that is executed when the object is garbage collected and thus deleted by Python.
+
+
+ Example for a capsule return
+
+```
+#node_new() if it just accepted proper configurations as per VILLASnode documentation
+m.def("node_new", [](const char *id_str, const char *json_str) -> py::capsule {
+ json_error_t err;
+ uuid_t id;
+
+ uuid_parse(id_str, id);
+ auto *json = json_loads(json_str, 0, &err);
+
+ void *it = json_object_iter(json);
+ json_t *inner = json_object_iter_value(it);
+
+ // create node with name
+ auto node = (vnode *)villas::node::NodeFactory::make(json_object_iter_value(it), id, json_object_iter_key(it));
+
+ return py::capsule(node, [](void *p) {
+ auto *vnode = (vnode *)(p);
+ if (vnode != nullptr){
+ node_destroy(vnode);
+ }
+ })
+ });
+```
+
+
+Function parameters in Pybind11 are strict, a wrong type will cause a function call to fail and a runtime error to be caused.
+
+---
+### 6. Installation
+
+There are two recommended methods to install the VILLASnode Python-Wrapper.
+
+
+1. Using one of the compatible Docker Containers which can be found [in the VILLASnode repository](https://github.com/VILLASframework/node/tree/python-wrapper/packaging/docker)
+ or can be installed [as is described here](../../installation.md). The Fedora container, which is also the development
+ container would be recommended first and foremost.
+2. Build VILLASnode from source [as is described here](../installation.md) and make sure to have all of the necessary
+ dependencies installed.
+
+**The requirements for the Python-Wrapper differ from the Versions listed in 2.**
+
+| Package | Version | Purpose | License |
+| --- | --- | --- | --- |
+| [CMake](http://cmake.org/) | >= 3.15 | for generating the build-system | BSD 3 |
+| [pybind11](https://github.com/pybind/pybind11) | >= 2.13 | for building the Python-Wrapper | BSD 3 |
+| [python](https://python.org/) | >= 3.7 | building and using the Python-Wrapper | PSFL |