Skip to main content

OrderedMap

A larger than memory map / dictionary, sorted by its keys. It supports insertion, removal, sorted range queries, and point-lookup queries.

Keys are strings and can be associated with a Value, bytes, or Any object when inserted into an OrderedMap.

  • Value represents a dynamically typed value which can be either null, a number, a string, a boolean, a recursive struct value, or a list of values. A producer of value is expected to set one of that variants, absence of any variant indicates an error. This type is meant to represent all possible JSON values.
  • bytes is used to store a string of bytes.
  • Any refers to the Any protobuf and is the best choice if you are using protobufs to structure your data.

OrderedMaps are frequently used to create indexes of other state types, but can also be used to efficiently store large collections of primitive values.

For example:

  • To index a data type in its natural state-ID order, you can create a OrderedMap with the type's ID as keys and empty values. This is effectively a sorted set.
  • To index a data type by creation time, you can use time-ordered UUIDs (such as UUIDv1 or UUIDv7) as keys and the data type's state-ID as values.
  • To index a large collection of Protobuf message types, you can use a key of your choice and the serialized value of each Protobuf message as a value.

To use a OrderedMap in your application, you typically want to choose an ID for the map and store it somewhere (e.g., as a field in the appropriate state data type). To use the map, first get a ref() to the map using its ID and then invoke the appropriate operations. The map will be implicitly constructed when the first writer method is invoked (e.g., insert()) or can be explicitly constructed with create().

Imports and servicers

To use a OrderedMap, import the library where you would like to use it.

from reboot.std.collections.ordered_map.v1.ordered_map import OrderedMap

Also make sure to include the OrderedMap servicers when starting up your Application. (Note: this import is different from above.)

from reboot.std.collections.ordered_map.v1 import ordered_map

async def main():
application = Application(
servicers=[MyServicer] + ordered_map.servicers(),
).run()

OrderedMap

Each OrderedMap you create will need a unique ID. Using a reference to that OrderedMap, you will be able to perform operations on keys and associated data.

Referencing OrderedMaps

This creates a reference to a OrderedMap with ID "my-map".

my_map = OrderedMap.ref("my-map")

Methods

Insert

Insert a key and its associated data into the OrderedMap.

Keys which are already in the map will be overwritten.

Only one of value, bytes, or any can be set.

The examples below show the different ways data can be associated with a key, but in practice, we recommend you use a uniform structure for your value. For more information on how to format data, check out the Value and Any docs for Items.

from reboot.protobuf import as_str

await my_map.insert(
context,
key="key-a",
value=from_str("a value!"),
)

await my_map.insert(
context,
key="key-b",
bytes=b"some bytes",
)

await my_map.insert(
context,
key="key-c",
any=<Any>,
)

Search for if a given key is in the OrderedMap.

Returns whether or not the key exists. If the key exists, this will also return the associated Value, bytes, or Any.

# Search for key with associated `Value`.
response = await my_map.search(context, key="key-a")
print(response.value)

# Search for key with associated `bytes`.
response = await my_map.search(context, key="key-b")
print(response.bytes)

# Search for key with associated `Any`.
response = await my_map.search(context, key="key-c")
print(response.any)

# Search for key with no associated data.
missing_response = await my_map.search(context, key="missing-key")
assert missing_response.found == False

Remove

Remove a key and its associated data from the OrderedMap.

Any key that is not present will be ignored.

await my_map.remove(context, key="key-a")

Range

Read a range of data in ascending key order from the OrderedMap.

You can optionally specify a start_key (Python) / startKey (TypeScript) to limit the lower bound (inclusive) of the returned entries. If start_key (Python) / startKey (TypeScript) is not set, the range will start from the smallest key.

You will receive up to limit items in the response. The limit argument is always required to avoid exhausting memory in the client or server.

This returns entries that have a key and one of value, bytes, or any set dependent on how you inserted the data.

range1 = await my_map.range(
context,
start_key="key-b",
end_key="key-z",
limit=2,
)

for entry in range1.entries:
print(entry.key, entry.value, entry.bytes, entry.any)

# Returns entries associated with the 3 smallest keys.
range2 = await my_map.range(context, limit=3)

If you have an error in your range request (i.e. you don't specify a limit), you'll receive an InvalidRangeError explaining why your range is not valid.

ReverseRange

Read a range of data in descending key order from the OrderedMap. This is similar to Range but reads data in reverse order.

You can optionally specify a start_key (Python) / startKey (TypeScript) to limit the upper bound (inclusive) of the returned entries. If start_key (Python) / startKey (TypeScript) is not set, the range will start from the largest key.

You will receive up to limit items in the response. The limit argument is always required to avoid exhausting memory in the client or server.

This returns entries that have a key and one of value, bytes, or any set dependent on how you inserted the data.

range1 = await my_map.reverse_range(
context,
start_key="key-z",
end_key="key-b",
limit=2,
)

for entry in range1.entries:
print(entry.key, entry.value, entry.bytes, entry.any)

# Returns entries associated with the 3 largest keys.
range2 = await my_map.reverse_range(context, limit=3)

If you have an error in your range request (i.e. you don't specify a limit), you'll receive an InvalidRangeError explaining why your range is not valid.

Errors

InvalidRangeError

See Range and ReverseRange. Raised when the requested range is not valid.

The message field of the error contains the string explaining why the range is not valid.

from reboot.std.collections.v1.ordered_map import (
InvalidRangeError,
OrderedMap,
)

try:
await my_map.range(context)
except OrderedMap.RangeAborted as e:
# isinstance(e.error, InvalidRangeError) == True
print(e.error.message)