Skip to main content

Data storage

Stores are key-value databases. A store can be attached to any number of bots. Attachment happens during bot creation. A store that is attached to multiple bots can act as a shared database between them.

A store is persistent and bot deletions have no effect on it. It will keep existing and retain data until you delete it.

Create a store

A store can be created from the UI and via REST API and Python SDK.

Here's how to create a store from the UI:

Creating a persistent data store on BotFleet

Access a store

Store that is attached to a bot can be accessed via the store argument of the main function:

def main(request, store):
print(store.items())

Use a store

store is a dict-like object. Here's how it can be used:

def main(request, store):
# get the value of "color"
color = store["color"]

# set the value of "color" to "red"
store["color"] = "red"

# delete "color"
del store["color"]

# iterate over all keys
for key in store
print(key)

# get the number of keys in store
length = len(store)

# check if "color" is in store
if "color" in store:
print("color is in store")

# get the value of "color", or the default value "red" if "color" is not in store
color = store.get("color", "red")

# get all key-value pairs in store
for key, value in store.items():
print(f"{key}: {value}")

# get all keys in store
for key in store.keys():
print(key)

# get all values in store
for value in store.values():
print(value)

# delete all keys in store
store.clear()

Supported types

When working with store, the following rules need to be followed:

  • The root key must be a string:

      store["key"] = "value"
    store[1] = "value" # error
  • Values must be JSON-serializable:

      store["key"] = "value"
    store["key"] = 1
    store["key"] = 1.1
    store["key"] = False
    store["key"] = {"key": 1}
    store["key"] = [1, 2, 3]
    store["key"] = None
    store["key"] = object() # error

Transactions

Stores also have transaction support. Transactions are used to ensure that a set of operations are performed atomically.

def main(request, store):
store["color"] = "red"
store["shape"] = "circle"
with store.transaction():
store["color"] = "green"
some_helper_function()
store["shape"] = "rectangle"

def some_helper_function():
raise ValueError()

In the above example, when some_helper_function raises an exception, the store will be rolled back to its previous state. This means that the value of color will be red and the value of shape will be circle. The value of color will not be green.

Transactions can be used to ensure that either all the operations in a transaction are performed or none of them are. This is useful in cases when the operations are dependent on each other. For example, in banking, when transferring money from one account to another, there are two operations that need to be performed: debiting the sender's account and crediting the receiver's account. Either both of these operations need to be performed or none of them should be performed to avoid inconsistencies.

Locking

Locking is also supported. Locking makes sure that only one execution of bot can access the store at a time.

Locking is useful in cases when there are a lot of concurrent requests to a store. This can happen when a single bot is executed in very quick succession or when multiple bots are frequently accessing the same store.

def main(request, store):
with store.transaction(lock=True):
# keep track of the number of times the bot has been run
store["run"] = store.get("run", 0) + 1
print(f"run: {store['run']}")

In the above example, if we hadn't used locking, it would have been possible for two bots executed at the same time to read the same value of run and increment it, which would have resulted in the value of run being incremented in total by 1 instead of 2.

For example, consider the following sequence of events:

  1. Bot 1 reads the value of run (reads 0)
  2. Bot 2 reads the value of run (reads 0)
  3. Bot 1 increments the value of run (writes 1)
  4. Bot 2 increments the value of run (writes 1)

Locking makes sure that the first bot acquires the lock, increments the value of run and releases the lock. Only then can the second bot acquire the lock and increment the value of run, which was already incremented by the first bot. This ensures that in total, the value of run is incremented by 2, as expected.