Tiny library for key-value single-file application data storage


License
MIT
Install
pip install tiny-storage==1.1.0

Documentation

Summary

Library for application data storage. It is:

  • tiny
  • key-value
  • single-file
  • YAML based

Example

from tiny_storage import Unit, Type
import sys

# matches the file /etc/example-app/yaml or %PROGRAMDATA%\example-app\config.yaml
config = Unit('example-app', Type.global_config)

if sys.argv[1] == 'set-greeting':
  # changes greeting only if does not exist
  if not config('lines.greeting').try_put(sys.argv[2]):
    print('Greeting already exists. It should not be changed.')
else:
  # prints greeting if it exists or given string
  print(config('lines.greeting').pull('Hello, world!'))

Installation

pip install tiny_storage

Full guide as a tiny_storage iceberg

1. Hello world

Suppose you want to store a configuration of your application between sessions.

from tiny_storage import Unit

# create a storage unit
config = Unit('application-name')

# write the data
config('the-best-number-ever').push(42)

The code above will create a YAML configuration file in the appropriate place and save the number 42 as the-best-number-ever entry.

2. Accessing data

Syntax for accessing data is the following:

config('<dot separated path in config>').<access method>(<alternative value>)

There are 5 access methods in total, but the get/set functionality is realized in pull/push methods.

config('essential.greeting').pull('hello')  # get a greeting from config or 'hello'
config('essential.greeting').push('hi')     # set a greeting as 'hi'

3. Storing config in different places

You can define what type of config do you need and tiny_storage will choose the place according to standard of your OS. For example, to store data in global configuration file you pass Type.global_config to your storage unit definition, and it will go to /etc in linux and to %PROGRAM_DATA% in windows.

from tiny_storage import Unit, Type

config = Unit('application-name', Type.global_config)

The list of all config types:

Data type Windows Linux
Type.local {name}.yaml {name}.yaml
Type.user %APPDATA%/{name}/{name}.yaml $HOME/.{name}.yaml
Type.user_config %APPDATA%/{name}/config.yaml $HOME/.config/{name}.yaml
Type.global_data %PROGRAMDATA%/{name}/data.yaml /var/lib/{name}.yaml
Type.global_config %PROGRAMDATA%/{name}/config.yaml /etc/{name}.yaml

You can also pass your own config type as a (str) -> Path function that constructs a path to config from the name.

config = Unit('application-name', lambda name: Path(f"/root/.{name}.yaml"))

4. Access methods

There are 5 most common cases of configuration data access and they are encapsulated into 5 access methods.

.push(value) to forcefully set an entry

print(f'Greeting was updated to {config("greeting").push("hi")}')

.pull(value) to get an entry or default value

print(f'Current greeting is {config("greeting").pull("<none>")}')

.put(value) to get the value or set it if it doesn't exist

print(f'{config("greeting").put("Hello")}, world')

.try_push(value) to push and know whether something changes

if not config("greeting").try_push(input()):
    print("You input the same greeting. Why are you doing that?")

.try_put(value) to put and know whether something changes

if config("greeting").try_put("Hello"):
    print("There was no greeting, so I put hello as one.")

5. Laziness

You can also pass a callable with no arguments as a value and it will be interpreted as a lazy value and be called only if it was used. For example, this hello world program will ask for a greeting only on the first launch:

print(config('greeting').put(input("greeting: ")), "world!")