Metadata-Version: 2.1
Name: varvault
Version: 1.1.0
Summary: A package that sets up a key-value vault to store and access variables in a global context.
Home-page: https://github.com/data-ductus/varvault
Author: Calle Holst
Author-email: calle.holst@dataductus.se
License: Apache 2.0
Platform: UNKNOWN
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Description-Content-Type: text/markdown
License-File: LICENSE

# README

## How to install?
```
pip3 install varvault
```

## What is this? 
This is a package that allows you to create a key-value vault for storing variables in a global context. It allows 
you to set up a keyring with pre-defined constants which act as keys for the vault. These constants are then what is 
stored inside the vault. A key is just a string, but the value that the key is mapped to can be assigned to any type of 
object in Python. If the object is serializable (like a list or a dict), it can also be writen to a JSON file    
You can then use a decorator to annotate functions that you want to have use this vault to either store return variables 
in or to extract variables to be used as input for the function.  

## How does it work? 
The way this works is that when you write a function, you annotate it with a special decorator (`varvault.Vault.vaulter`)
that takes some arguments. This decorator will then handle any input arguments and return variables for you.
The decorator takes some arguments that defines certain keys.

### How about an example?
The best examples can be found in the test suites which can give a very good idea how it works and is guaranteed to be up-to-date. 
```
import varvault


class Keyring(varvault.Keyring):
    arg1 = varvault.Key("arg1")
    arg2 = varvault.Key("arg2")


vault = varvault.create_vault(Keyring, "example-vault", "~/.vault/vault.json", varvault.VaultFlags.return_values_cannot_be_none()))


@vault.vaulter(return_keys=[Keyring.arg1, Keyring.arg2])
def create_args(arg1, arg2):
    return arg1, arg2


@vault.vaulter(input_keys=[Keyring.arg1, Keyring.arg2])
def use_args(**kwargs):
    arg1 = kwargs.get(Keyring.arg1)
    arg2 = kwargs.get(Keyring.arg2)

    print(f"{Keyring.arg1}: {arg1}, {Keyring.arg2}: {arg2}")


def run_create_args():
    create_args(1, 2)
    

def run_use_args():
    use_args()


if __name__ == "__main__":
    run_create_args()
    
    run_use_args()    
```
1. In this example, we start by creating a class that defines a keyring. This keyring will be the keys used
   in the vault. Any key you use for storing variables or take variables out should be defined as a constant 
   in this keyring (by default, this is the way to use it, but it is possible to be more flexible).

2. Then we create the actual Vault-object. It's entirely possible to create a Vault without using the factory function,
   but the factory function will do some things for you to make it slightly easier. Creating the vault requires only
   two arguments and that is a class that inherits from the `varvault.Keyring` (a class based on the Keyring class here), and a name.
   Optionally, you can define some flags to further tweak the behavior of the vault. These tweaked behaviors include
   allowing for existing key-value pairs to be modified (this is not allowed by default), allowing return variables from
   functions defined with return keys to be None, and setting a flag to write some additional debug logs.
   You can also define a .JSON file to be used as a vault file to store all the arguments in.

3. We create a function called `create_args` that takes some arguments (we have to insert variables
   into the vault somehow, right?) that we annotate with the vault decorator. We pass an argument to the
   decorator called `return_keys`. This argument tells the vault which keys this function will assign its
   return variables to. Note that the order of the return keys matter. In this case, the ingoing argument `arg1` will
   be assigned to `Keyring.arg1`, and the ingoing argument `arg2` will be assigned to `Keyring.arg2`. It's very possible
   to set return_var_key to a single string as well if you only have one variable to return. If you want more control
   over how return variables are handled, please see `varvault.MiniVault` and make use of that to ensure that
   return-variables are handled exactly as you want. 
   
   **Note:** When this function is called and it finishes, the decorator here will capture the return variables and then store 
   those return variables in the vault with the keys that were passed to the decorator. These variables can then be 
   accessed by another function that uses the same vault-object as this one does.

4. We then create a new function called `use_args` that we also annotate with the vault decorator. We pass a different
   argument to the decorator this time called `input_keys`. This argument tells the vault which keys in the vault
   we want passed to this function. The order of the keys doesn't really matter here, the order is mostly aesthetic.
   
   **Note:** What ends up happening when this function is called, is that the decorator will try to extract keys defined in
   `input_keys` from the vault and then pass those variables to the function as a dictionary (this is what `**kwargs`
   essentially is). It is possible to write arguments in the signature of the function itself (in this case the signature
   would be `def use_args(arg1=None, arg2=None)`, but one of the purposes of the Keyring is that the constants
   defined in the keyring can be used to easily find where a key is being used. It's recommended to write the function
   like this but it is possible to write it with pre-defined arguments as well. Do note that the arguments have to have
   a default value, like None. Otherwise, when you call the function, you have to call the function with the arguments
   fulfilled as well.

5. We create a very simple function called `run_create_args` which doesn't get annotated. This function is simply made
   to demonstrate what makes this vault so useful. When this function is called, it will obviously call `create_args`,
   which will create  Keyring.arg1` and `Keyring.arg2` which will then be stored in the vault.

6. A final function called `run_use_args` is then created which calls the `use_args` function. This function
   is able to use the arguments defined in `create_args` because with this vault, the context for where a function runs
   doesn't really matter as long as the input variables it needs exists in the vault-object already, and the function
   exists in that scope.

7. Lastly, the variables that were involved in the execution of this code can be viewed by simply checking the contents 
   of the file `~/.vault/vault.json`. In this example the file would simply contain: 
   ```
   {
     "arg1": 1,
     "arg2": 2
   }
   ```
8. When a file such as this (see above) exists, it's very possible to re-create the same vault again from this file. 
   In order to re-create the same vault again simply do this: 
   ```
   vault = varvault.from_vault_file(Keyring,
                                    "example-vault",
                                    "~/.vault/vault.json",
                                    varvault.VaultFlags.permit_modifications()))
   ```
   When re-creating a vault from an existing file it's recommended to allow modifications 
   (see `varvault.VaultFlags.permit_modifications`) in-case you are planning to write the same
   arguments to the vault again. 

Conclusion. This flow demonstrates what this functionality can be used for. With this vault, the context for where
a function executes doesn't matter as long as the keys the function needs have been assigned in the vault and the
functions exists in the scope. The functions become building blocks that you can call regardless of context provided
the above criteria have been met. You don't need to clutter your function calls with tons of input variables because
all of that is handled for you by the vault and the decorator. If you use it correctly, you can end up with
functions that on the surface appears to not use any arguments or pass any return variables at all. This makes the main
body of your code clean and easy to follow. You can then use the Keyring to see where your keys are
actually being used. By saving arguments to a file, it allows you to keep parts of the context the code ran 
in previously. This can be very useful when deploying something which then has to be un-deployed at a later time that 
isn't necessarily running in the same process as before. This adds an extra layer similar to environment variables 
that works slightly differently and exclusively in Python. Since the arguments are saved to a JSON file, anything that
can parse JSON can obviously use the data as they see fit. 


