Dict
Dicts: a key-value store for small state that code and agents share.
seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
if "o_1841" not in seen:
seen["o_1841"] = {"status": "refunded"}
print(seen.increment("refunds-today"))Keys are strings and values are JSON (a dict, list, string, number, bool or
None), never pickled, so a value your Python code writes reads the same from
TypeScript, an agent or the console. A dict works like Python's own: d[key],
d[key] = value, key in d, del d[key], len(d) and iterating its
keys, plus expiry (ttl_s), locks (put_if_absent) and atomic counters
(increment). Each of those reads or writes the API, so the .aio forms of
get, put and pop are how async code does the same.
class DictA key-value store: string keys, JSON values, optional expiry per key.
Read and write it like a Python dict (d[key], d[key] = value,
key in d, del d[key], len(d), for key in d); a missing key
raises KeyError there and get returns a default instead. Each of those
is a call to the API, never a local copy, so two processes see each other's
writes at once. A dict object is always truthy, so if seen: never makes a
call. key_count and size_bytes are as of the last load; len(d)
reads the count fresh.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| name | str | |
| key_count | int | None | How many keys it holds (expired keys are gone). |
| size_bytes | int | None | The size of its keys and values as JSON. |
| used_by | list[dict[str, Any]] | The apps that bind it: |
| region | str | None | The region it keeps its data in ( |
| user_id | str | None | |
| created_at | Any | |
| console_url | str | None | This dict in the developer console, with its key browser (None off the hosted API). |
createclassmethodasync
noclick.Dict.create(name, *, region=None, user=..., client=None) -> DictCreate a dict called name (1-64 letters, digits and . _ -),
unique to its user (a taken name raises noclick.Conflict with
existing_id). region (noclick.regions(kind="dict")) is where its
keys are kept, for good; without one, your account's default region, else
us-east. Dicts are kept in us-east only for now: another region, or
a default region elsewhere, raises noclick.InvalidRequest naming it.
NoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The new dict, empty.
A dict of the account's own:
seen = noclick.Dict.create("processed-orders")Created inside as_user, it belongs to (and bills) that user:
with noclick.as_user("acme"):
flags = noclick.Dict.create("feature-flags")from_nameclassmethod
noclick.Dict.from_name(
name, *, create_if_missing=False, region=None, user=..., client=None
) -> DictA lazy reference to the dict called name in the current (or given)
user. With create_if_missing=True it's created the first time it's used,
in region (as create).
Inside a deployed app (signed in by its deployment) the name is looked up in the app's own
namespace for the environment the code runs in, and one made there is the app's: a name bound to
the app by hand wins, and in a preview of an app whose preview_resources is branch the
branch gets its own copy of the keys the first time it uses it, so a preview never changes
production's data.
FalseNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A dict that loads (or is created) on first use. Nothing is sent yet.
Make your code the definition, so the first run creates it:
seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
if "o_1841" not in seen:
seen["o_1841"] = {"status": "refunded"}A user's own, for per-customer settings:
with noclick.as_user("acme"):
flags = noclick.Dict.from_name("feature-flags", create_if_missing=True)
print(flags.get("new-checkout", default=False))from_idclassmethod
noclick.Dict.from_id(dict_id, *, client=None, user=...) -> DictA lazy reference to a dict by id. There's no Dict.get(id), since
get reads a key: call hydrate() on this to load it now.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A dict that loads the first time you use it. Nothing is sent yet.
seen = noclick.Dict.from_id("5b8e2c71-4d9a-4f36-a0e2-7c1b9d3f6a58").hydrate()
print(seen.name, seen.key_count)listclassmethodasync
noclick.Dict.list(*, limit=None, user_filter=None, user=..., client=None) -> list[Dict]The current user's dicts (the account's own outside as_user, or
those user_filter covers).
Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching dict, newest first, fetched page by page.
Your account's dicts:
for store in noclick.Dict.list():
print(store.name, store.key_count, store.size_bytes)Every user's:
every = noclick.Dict.list(user_filter=noclick.ALL_USERS)list_pageclassmethodasync
noclick.Dict.list_page(
*, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> PageOne page of dicts; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the dicts and whose next_cursor is None on the last page.
page = noclick.Dict.list_page(limit=50, user_filter=noclick.ALL_USERS)
for store in page.data:
print(store.user_id, store.name, store.key_count)__getitem__
d[key] -> AnyRead a key; a missing (or expired) one raises KeyError.
The value, as the JSON it was stored as.
flags = noclick.Dict.from_name("feature-flags")
if flags["new-checkout"]:
print("New checkout is on")getasync
d.get(key, default=None) -> AnyRead a key, or default when it's missing or expired.
NoneThe value as the JSON it was stored as, or default.
flags = noclick.Dict.from_name("feature-flags")
if flags.get("new-checkout", default=False):
print("New checkout is on")keysasync
d.keys(prefix=None, *, limit=None) -> list[str]The keys, in order, or those that start with prefix.
NoneReturn at most this many.
None)Every matching key (or the first limit), fetched page by page.
seen = noclick.Dict.from_name("processed-orders")
for key in seen.keys(prefix="lock:"):
print(key)itemsasync
d.items(prefix=None, *, limit=None) -> list[DictItem]The keys and their values, in key order, or those whose key starts with prefix.
NoneReturn at most this many.
None)A DictItem per key (or for the first limit): it unpacks as key, value and
also carries expires_at, updated_at and size_bytes.
Read them like a Python dict's items:
seen = noclick.Dict.from_name("processed-orders")
for key, value in seen.items(prefix="o_", limit=100):
print(key, value)See what's about to expire:
seen = noclick.Dict.from_name("processed-orders")
for item in seen.items(prefix="lock:"):
print(item.key, item.expires_at)items_pageasync
d.items_page(prefix=None, *, cursor=None, limit=None) -> PageOne page of items in key order; pass next_cursor back as cursor.
NoneThe next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)seen = noclick.Dict.from_name("processed-orders")
page = seen.items_page(prefix="o_", limit=50)
for key, value in page.data:
print(key, value)putasync
d.put(key, value, *, ttl_s=None) -> NoneWrite a key. With ttl_s it expires that many seconds from now;
without, it stays until it's removed (and loses any expiry it had).
d[key] = value does the same with no expiry.
NoneRemember an order you've handled:
seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
seen.put("o_1841", {"status": "refunded", "by": "support-bot"})A cache entry for a day:
cache = noclick.Dict.from_name("rates-cache", create_if_missing=True)
cache.put("usd:eur", 0.92, ttl_s=86400)put_if_absentasync
d.put_if_absent(key, value, *, ttl_s=None) -> boolWrite a key only if it isn't there (an expired key counts as absent), in
one atomic step: of any number of callers racing, exactly one wins. That
makes it a lock (with ttl_s, so a crashed holder's lock frees itself)
or a check that something runs once.
NoneTrue if this call wrote it, False if the key was already there.
Only one worker runs the nightly job; the lock frees itself after five minutes:
locks = noclick.Dict.from_name("locks", create_if_missing=True)
if locks.put_if_absent("nightly-report", "worker-3", ttl_s=300):
print("This worker runs it")Handle each webhook delivery once, however often it's retried:
seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
if seen.put_if_absent("evt_9f2c", True, ttl_s=7 * 86400):
print("First time: handle it")incrementasync
d.increment(key, by=1, *, ttl_s=None) -> intAdd by (which may be negative) to a counter, atomically: concurrent
increments never lose a count. A missing key starts at 0; ttl_s sets
its expiry only when this call creates it, so a counter per window
(visits:2026-09-28) cleans itself up. A key holding anything but an
integer raises noclick.InvalidRequest.
1NoneThe counter's value after this increment.
Count, and read the total in the same call:
stats = noclick.Dict.from_name("stats", create_if_missing=True)
total = stats.increment("refunds-today")A rate limit: at most 100 calls per customer per hour:
limits = noclick.Dict.from_name("rate-limits", create_if_missing=True)
if limits.increment("calls:acme:2026-09-28T14", ttl_s=3600) > 100:
print("Over the limit; try again next hour")updateasync
d.update(items, *, ttl_s=None) -> NoneWrite up to 1,000 keys in one call, all of them or none; ttl_s
applies to each.
Noneflags = noclick.Dict.from_name("feature-flags", create_if_missing=True)
flags.update({"new-checkout": True, "beta-search": False, "max-upload-mb": 50})popasync
d.pop(key, default=...) -> AnyRemove a key and return its value. A missing key returns default,
or raises KeyError when you gave none, like a Python dict's pop.
...The value the key held, or default.
jobs = noclick.Dict.from_name("pending-exports", create_if_missing=True)
job = jobs.pop("acme", None)
if job is not None:
print("Exporting", job)removeasync
d.remove(key) -> boolRemove a key if it's there. del d[key] does the same and raises
KeyError on a missing key; this is its form that doesn't, with an
.aio for async code.
Whether the key was there to remove.
locks = noclick.Dict.from_name("locks")
locks.remove("nightly-report") # done: let the next run take itclearasync
d.clear() -> OperationRemove every key. The dict itself stays, empty.
The clear's operation: undo() puts back every key it removed that nothing has written since.
cache = noclick.Dict.from_name("rates-cache")
cleared = cache.clear()
cleared.undo() # the keys are backhistoryasync
d.history(key=None, *, limit=None) -> list[DictChange]Its writes of the last 30 days, newest first, each with the value it replaced; key keeps one key's.
NoneReturn at most this many.
None)Each change.
prefs = noclick.Dict.from_name("prefs")
for change in prefs.history("theme"):
print(change.at, change.op, change.previous)restore_changeasync
d.restore_change(change) -> NonePut the key back as change found it: the value it replaced, or gone, when the change created
the key. A value that has expired since raises noclick.Conflict.
Undo a bad write:
prefs = noclick.Dict.from_name("prefs")
prefs["theme"] = "neon"
prefs.restore_change(prefs.history("theme")[0])exportasync
d.export() -> OperationExport every key as it stands at one moment, as JSON Lines: one
{"key", "value", "expires_at"} per line, in key order.
An operation whose wait() gives an ExportBundle: a zip at url holding the file.
seen = noclick.Dict.from_name("processed-orders")
bundle = seen.export().wait()
print(bundle.url)deleteasync
d.delete() -> TrashItemDelete the dict and every key in it: it goes to the trash, restorable for 30 days. A dict an app
still binds raises noclick.Conflict until you unbind it.
cache = noclick.Dict.from_name("rates-cache")
item = cache.delete()
item.restore() # it's backDictItem
class DictItemOne key of a dict as items() reads it: its key and value, when it
expires_at (None if it doesn't), when it was last written (updated_at)
and size_bytes, the size of its value as JSON. It unpacks like a pair, so
for key, value in d.items() and dict(d.items()) work as they do on a
Python dict. A listing from keys() carries no values.
| key | str | |
| value | Any | |
| expires_at | Any | |
| updated_at | Any | |
| size_bytes | int | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
DictChange
class DictChangeOne write to a dict, from its history: op is created (the key was new), changed or
deleted, and previous the value it replaced (None for a new key), which expired at
previous_expires_at. A key changed again within a minute adds none, so a busy key keeps one a minute.
| id | str | |
| key | str | |
| op | str | |
| previous | Any | |
| previous_expires_at | Any | |
| at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |