{"id":47331357,"url":"https://github.com/amirouche/asyncio-foundationdb","last_synced_at":"2026-04-01T01:00:54.534Z","repository":{"id":340488146,"uuid":"1160458528","full_name":"amirouche/asyncio-foundationdb","owner":"amirouche","description":"FoundationDB for asyncio Python","archived":false,"fork":false,"pushed_at":"2026-03-07T04:32:50.000Z","size":755,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-07T12:51:31.897Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amirouche.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-18T00:47:34.000Z","updated_at":"2026-03-07T04:32:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/amirouche/asyncio-foundationdb","commit_stats":null,"previous_names":["amirouche/asyncio-foundationdb"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/amirouche/asyncio-foundationdb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirouche%2Fasyncio-foundationdb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirouche%2Fasyncio-foundationdb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirouche%2Fasyncio-foundationdb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirouche%2Fasyncio-foundationdb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amirouche","download_url":"https://codeload.github.com/amirouche/asyncio-foundationdb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirouche%2Fasyncio-foundationdb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31260117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T00:21:46.390Z","status":"ssl_error","status_checked_at":"2026-04-01T00:09:28.497Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-17T21:00:25.192Z","updated_at":"2026-04-01T01:00:54.443Z","avatar_url":"https://github.com/amirouche.png","language":"Python","funding_links":[],"categories":["Bindings"],"sub_categories":[],"readme":"# [asyncio-foundationdb](https://github.com/amirouche/asyncio-foundationdb/)\n\nFoundationDB drivers for asyncio tested with CPython and PyPy 3.9+.\n\n[![Library Database](https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-1.2.1\u0026ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026auto=format\u0026fit=crop\u0026w=1021\u0026q=80)](https://unsplash.com/photos/lRoX0shwjUQ)\n\n**One language. One API. Your data model.**\n\nMost Python applications are secretly two programs: the Python part\nyou write, and the SQL part you also write, connected by an ORM that\npretends the gap doesn't exist. When something breaks at the boundary\n— and it will — you need to hold two mental models at once, or find\nsomeone who speaks both.\n\nasyncio-foundationdb is a bet that this doesn't have to be true.\n\nFoundationDB gives you a foundation (pun intended) that scales from a\nweekend project on a single box to a distributed system handling real\nproduction load — without changing your data layer. On top of that,\nthis library gives you Python-native building blocks: tuple stores,\nblob stores, pattern-matching queries, versioned knowledge graphs. No\nSQL. No ORM. No impedance mismatch.\n\nYou choose your domain model. You express it in Python. You own the\nfull stack, in one language, with one place to look when things go\nwrong.\n\nThis is not about avoiding complexity. It's about choosing which\ncomplexity you live with — and keeping it visible. [Why I built\nthis.](WHY.md)\n\n## Links\n\n- [FoundationDB Website](https://www.foundationdb.org/)\n- [FoundationDB Forum](https://forums.foundationdb.org/)\n- [FoundationDB Documentation](https://apple.github.io/foundationdb/)\n\n## Table of Contents\n\n| Section | Summary |\n|---------|---------|\n| [Installation](#installation) | pip install instructions, optional server extras |\n| [Example](#example) | Minimal get/set/query snippet to get started |\n| [ChangeLog](#changelog) | Release history and migration notes |\n| [`import found`](#import-found) | Core API: open, transactional, get, set, query, tuple layer, atomic ops |\n| [`bstore`](#from-foundext-import-bstore) | Content-addressable blob store with blake2b deduplication |\n| [`nstore`](#from-foundext-import-nstore) | N-tuple store with pattern-matching queries and automatic indexing |\n| [`eavstore`](#from-foundext-import-eavstore) | Entity-Attribute-Value store for schema-free dicts |\n| [`pstore`](#from-foundext-import-pstore) | Inverted index for relevance-ranked keyword search |\n| [`vnstore`](#from-foundext-import-vnstore) | Versioned N-tuple store with auditable change-sets |\n| [`vnstore.server`](#from-foundextvnstore-import-server) | ASGI HTTP server exposing a vnstore over a browser UI |\n| [`pool`](#from-foundext-import-pool) | Thread-pool fan-out utility for parallel async/blocking work |\n\n## Installation\n\nIn a minute, install FoundationDB 7.3+, getting the latest stable release\nfrom the official release page: https://github.com/apple/foundationdb/releases/tag/7.3.69\n\nThen install asyncio drivers `asyncio-foundationdb`:\n\n```\npip install asyncio-foundationdb\n```\n\nTo use the built-in HTTP server (powered by [uvicorn](https://www.uvicorn.org/)):\n\n```\npip install asyncio-foundationdb[server]\n```\n\nThis pulls in `uvicorn`, `jinja2`, and `zstandard`. It also registers the\n`found-vnstore` console script:\n\n```\nfound-vnstore   # starts the vnstore ASGI server on 127.0.0.1:8000\n```\n\n## Example\n\n```python\nasync def readme():\n\n    async def get(tx, key):\n        out = await found.get(tx, key)\n        return out\n\n    async def set(tx, key, value):\n        await found.set(tx, key, value)\n\n    db = await found.open()\n    out = await found.transactional(db, get, b'hello')\n    assert out is None\n\n    await found.transactional(db, set, b'hello', b'world')\n    out = await found.transactional(db, get, b'hello')\n    assert out == b'world'\n\n    await found.transactional(db, set, b'azul', b'world')\n    out = await found.transactional(db, get, b'azul')\n    assert out == b'world'\n\n    async def query(tx, key, other):\n        out = await found.all(found.query(tx, key, other))\n        return out\n\n    out = await found.transactional(db, query, b'', b'\\xFF')\n    assert out == [(b'azul', b'world'), (b'hello', b'world')]\n\nasyncio.run(readme())\n```\n\n## ChangeLog\n\n### v0.13.2\n\n#### CI\n\n- Fix PyPI release: run `auditwheel repair` after building Linux wheels to produce\n  `manylinux_2_17_*` platform tags instead of the rejected plain `linux_*` tags\n\n### v0.13.1\n\n#### CI\n\n- Drop deprecated `macos-13` (x86_64) runner from release workflow; `macos-latest` (arm64) only\n\n### v0.13.0\n\nRequires Python 3.9+. Upgrade to FoundationDB 7.3 (API version 730).\n\n#### Breaking changes\n\n- `found.get_range` removed — use `found.query(tx, begin, end)` (async generator)\n- `nstore.v` alias removed — use `nstore.var`\n- `foundationdb` package is no longer a runtime dependency; install it manually\n  if you rely on `fdb.tuple` directly\n\n#### New features\n\n- Native tuple layer: `found.pack` / `found.unpack`, byte-for-byte compatible with\n  `fdb.tuple.pack` / `fdb.tuple.unpack`; keys written by `found ≤ 0.12` remain\n  readable without any migration\n- Transaction lifecycle hooks: `on_begin`, `on_commit`, `on_post_commit` on `db.hooks`\n- `found.TransactionStats` (retries, elapsed, commit_bytes) passed to `on_post_commit`\n- `async with found.transaction(db) as tx:` — non-retrying context manager; commits on\n  clean exit, fires the same `on_begin` / `on_commit` / `on_post_commit` hooks as\n  `transactional()`; caller is responsible for retry logic\n- New public APIs: `get_key`, `commit`, `on_error`, `reset`, `cancel`,\n  `get_committed_version`, `get_approximate_size`, `get_versionstamp`,\n  `add_conflict_range`, `set_option`, `get_range_split_points`, `append_if_fits`,\n  `compare_and_clear`, `get_client_version`, `get_addresses_for_key`,\n  `database_set_option`, `network_set_option`, `add_network_thread_completion_hook`,\n  `error_predicate`\n- Split `found.ext.vnstore` into store (`__init__.py`) and ASGI server (`server.py`)\n  — store can be imported without `jinja2`\n- `found-vnstore` console script (`pip install asyncio-foundationdb[server]`)\n- Docstrings on all public functions and module docstrings on all modules\n\n#### Fixes\n\n- Fix `gte()` ignoring the `offset` parameter\n- Fix `watch()` must be awaited from a running event loop (made async)\n- Fix `transactional()` not clearing `tx.vars` between retries\n- Fix `_cb_int64` result extraction not guarded behind error check\n- Fix broken ASGI lifespan handler — now sends `lifespan.startup.complete` /\n  `lifespan.shutdown.complete` correctly\n- Fix `set_read_version()` overly restrictive snapshot assertion\n- Replace global `CACHE` with `scope[\"state\"]` in vnstore ASGI server\n- Eliminate repeated UUID validation with shared `with_change` helper\n- Check `fdb_create_database()` return code for errors\n- Make ffibuild.py portable via `FDB_INCLUDE_DIR` / `FDB_LIB_DIR` env vars\n- Use `asyncio.get_running_loop()` instead of deprecated `asyncio.get_event_loop()`\n\n#### CI / Infrastructure\n\n- Binding tester correctness suite in two concurrency modes: POSIX threads\n  (`tester_pthread.py`) and asyncio tasks (`tester_aio.py`)\n- CI matrix: Python 3.9–3.14t + PyPy 3.9–3.11 across both unit tests and binding tester\n- Free-threaded Python (3.14t) validated with `PYTHON_GIL=0`\n- Release workflow: multi-arch wheels (Linux x86_64+aarch64, macOS x86_64+arm64)\n  plus sdist, triggered only on tags from `main`\n- Replace Poetry with uv; replace black/isort/pylama/bandit with ruff\n- Remove `immutables` dependency\n\n### v0.12.0\n\n- Move back to GitHub;\n- Add versioned generic tuple store (code name `vnstore`)\n\n### v0.10.x\n\n- Almost full rewrite\n- Remove hooks for the time being\n- Port Generic Tuple Store aka. `nstore`\n- Add blob store aka. `bstore`\n- Add Entity-Attribute-Value store aka. `eavstore`\n- Add inverted index store aka. `pstore`\n\n## `import found`\n\n### `found.BaseFoundException`\n\nAll `found` exceptions inherit that class.\n\n### `found.FoundException`\n\nException raised when there is an error foundationdb client driver, or\nfoundationdb server side.\n\n### `await found.open(cluster_file=None)`\n\nOpen database.\n\nCoroutine that will open a connection with the cluster specified in\nthe file `cluster_file`. If `cluster_file` is not provided the default\nis `/etc/foundationdb/fdb.cluster`. Returns a database object.\n\n### `await found.transactional(db, func, *args, snapshot=False, **kwargs)`\n\nOperate a transaction for `func`.\n\nCoroutine that will operate a transaction against `db` for `func`. If\n`snapshot=True` then the transaction is read-only. `func` will receive\nan appropriate transaction object as first argument, then `args`, then\n`kwargs`. Because of errors `transactional` might run `func` several\ntimes, hence `func` should be idempotent.\n\nThe function `func` receive transaction object that should be passed\nto other database functions. It has a property `vars` that is a\ndictionary that can be used to cache objects for the extent of the\ntransaction.\n\n### `async with found.transaction(db, snapshot=False) as tx:`\n\nNon-retrying context manager for a single transaction.\n\nOn clean exit the transaction is committed. On exception the transaction\nis abandoned without committing. The caller is responsible for retry\nlogic if needed. Fires the same lifecycle hooks as `transactional`.\n\n### `found.Hooks`\n\nNamedtuple with three lists of async callables:\n\n- `on_begin` — fired after the transaction is created and after each retry\n  (i.e. after `tx.vars.clear()`). Hook signature: `async def hook(tx)`.\n- `on_commit` — fired before commit, still inside the transaction. Any\n  write made by an `on_commit` hook is atomic with the rest of the\n  transaction. Re-runs on retry. Hook signature: `async def hook(tx)`.\n- `on_post_commit` — fired once after a successful durable commit, never\n  on retry. Hook signature: `async def hook(tx, stats)` where `stats` is\n  a `found.TransactionStats`.\n\n### `found.make_hooks()`\n\nReturn a fresh `Hooks` instance with empty lists for each lifecycle slot.\n`open()` calls this automatically; you normally interact with the lists on\n`db.hooks` directly.\n\n```python\ndb = await found.open()\n\nasync def observe(tx, stats):\n    print(stats.retries, stats.elapsed, stats.commit_bytes)\n\ndb.hooks.on_post_commit.append(observe)\n```\n\n### `found.TransactionStats`\n\nNamedtuple populated after each successful commit and passed to every\n`on_post_commit` hook:\n\n- `retries` — number of retries before the successful commit (0 on first attempt)\n- `elapsed` — wall time in seconds from `make_transaction` to commit\n- `commit_bytes` — approximate serialized size of the transaction in bytes\n\n### `await found.get(tx, key)`\n\nGet the value associated with `key`.\n\nCoroutine that will fetch the value associated with `key` inside the\ndatabase associated with `tx`. `key` must be `bytes`. In case of\nsuccess, returns `bytes`. Otherwise, if there is no value associated\nwith `key`, returns the object `None`.\n\n### `await found.set(tx, key, value)`\n\nSet `key` to `value`.\n\nIn the database associated with `tx`, associate `key` with\n`value`. Both `key` and `value` must be `bytes`.\n\n### `found.pack(tuple)`\n\nSerialize python objects `tuple` into bytes.\n\n### `found.pack_with_versionstamp(tuple)`\n\nSerialize python objects `tuple` into bytes. `tuple` may contain\n`found.Versionstamp` objects.\n\n### `found.unpack(bytes)`\n\nDeserialize bytes into python objects.\n\n### `found.has_incomplete_versionstamp(tuple)`\n\nReturn `True` if `tuple` contains at least one incomplete\n`Versionstamp`. Useful to validate input before calling\n`found.pack_with_versionstamp`.\n\n### `found.Versionstamp(...)`\n\nRepresents a FoundationDB versionstamp. Used with\n`found.pack_with_versionstamp` to create keys or values that will be\nfilled in by FoundationDB with a unique, monotonically increasing\nversion at commit time.\n\n### `await found.clear(tx, key, other=None)`\n\nRemove key or keys.\n\nIn the database associated with `tx`, clear the specified `key` or\nrange of keys.\n\n`key` and `other` if provided must be `bytes`.\n\nIf `other=None`, then clear the association that might exists with\n`key`. Otherwise, if `other` is provided, `found.clear` will remove\nany association between `key` and `other` but not the association with\n`other` if any (that is `other` is excluded from the range).\n\n### `await found.query(tx, key, other, *, limit=0, mode=STREAMING_MODE_ITERATOR)`\n\nFetch key-value pairs.\n\nIn the database associated with `tx`, generate at most `limit`\nkey-value pairs inside the specified range, with the specified order.\n\nIf `key \u003c other` then `found.query` generates key-value pairs in\nlexicographic order. Otherwise, if `key \u003e other` then `found.query`\ngenerates key-value pairs in reverse lexicographic order, that is\nstarting at `other` until `key`.\n\nIf `limit=0`, then `found.query` generates all key-value pairs in the\nspecified bounds. Otherwise if `limit \u003e 0` then, it generates at most\n`limit` pairs.\n\nThe keyword `mode` can be one the following constant:\n\n- `found.STREAMING_MODE_WANT_ALL`\n- `found.STREAMING_MODE_ITERATOR`\n- `found.STREAMING_MODE_EXACT`\n- `found.STREAMING_MODE_SMALL`\n- `found.STREAMING_MODE_MEDIUM`\n- `found.STREAMING_MODE_LARGE`\n- `found.STREAMING_MODE_SERIAL`\n\n### `await found.get_key(tx, key_selector)`\n\nResolve a key selector to a key.\n\nIn the database associated with `tx`, resolve the given\n`key_selector` and return the resulting key as `bytes`. The\n`key_selector` should be created with `found.lt`, `found.lte`,\n`found.gt`, or `found.gte`.\n\n### `await found.commit(tx)`\n\nCommit the transaction.\n\nExplicitly commit the transaction `tx`. This is done automatically\nby `found.transactional`, but is useful when managing transactions\nmanually with `found.make_transaction`.\n\n### `await found.on_error(tx, code)`\n\nHandle a transaction error.\n\nPass error `code` to FoundationDB's conflict resolution logic. If\nthe error is retryable, the transaction is reset and the coroutine\nreturns. If the error is not retryable, raises `FoundException`.\n\n### `found.reset(tx)`\n\nReset the transaction.\n\nReset `tx` to its initial state, as if it had just been created.\nThis allows the transaction object to be reused for a new operation.\n\n### `found.cancel(tx)`\n\nCancel the transaction.\n\nCancel `tx`, causing any pending or future operations on it to fail\nwith an error.\n\n### `found.get_committed_version(tx)`\n\nReturn the committed version of the transaction.\n\nAfter a successful commit, returns the version at which the\ntransaction was committed as an integer. Must be called after\n`found.commit`.\n\n### `await found.get_approximate_size(tx)`\n\nReturn the approximate size of the transaction in bytes.\n\nReturns the approximate byte size of the transaction so far,\nincluding all keys, values, and conflict ranges. Useful for\nmonitoring whether a transaction is approaching the 10 MB limit.\n\n### `await found.get_versionstamp(tx)`\n\nReturn the versionstamp of a committed transaction.\n\nReturns the 10-byte versionstamp as `bytes`. The future must be\ncreated before commit and awaited after commit completes.\n\n### `found.add_conflict_range(tx, begin, end, conflict_type)`\n\nAdd a conflict range to the transaction.\n\nManually add a read or write conflict range to `tx`. `begin` and\n`end` must be `bytes`. `conflict_type` must be\n`found.CONFLICT_RANGE_TYPE_READ` or\n`found.CONFLICT_RANGE_TYPE_WRITE`.\n\n### `found.set_option(tx, option, value=None)`\n\nSet a transaction option.\n\nSet an option on `tx`. `option` must be one of the\n`FDB_TR_OPTION_*` integer constants. `value` is optional and must\nbe `bytes` when provided.\n\n### `found.watch(tx, key)`\n\nWatch a key for changes.\n\nRegisters a watch on `key` (synchronous C call) and returns an\n`asyncio.Future` that resolves to `None` when the key is modified by another\ntransaction. The watch only detects external changes after the transaction that\ncreated it has been committed. `key` must be `bytes`.\n\n```python\n# Register the watch on a fresh transaction\ntx = found.make_transaction(db)\nwatch_future = found.watch(tx, key)\nawait found.commit(tx)         # activates the watch for external changes\n\n# ... in another task, modify the key ...\nawait watch_future             # resolves when the key changes\n```\n\nUnused watch futures should be cancelled (`watch_future.cancel()`) to avoid\nresource exhaustion (default limit: 10,000 active watches per connection).\n\n### `await found.get_range_split_points(tx, begin, end, chunk_size)`\n\nGet split points for a key range.\n\nIn the database associated with `tx`, return a list of `bytes` keys\nthat divide the range from `begin` to `end` into chunks of approximately\n`chunk_size` bytes each. `begin` and `end` must be `bytes`.\n`chunk_size` is an integer in bytes. Returns an empty list if the range\nis empty or smaller than `chunk_size`.\n\n### `await found.estimated_size_bytes(tx, begin, end)`\n\nEstimate the byte size of a key range.\n\nIn the database associated with `tx`, return an estimate of the\nbyte size of the range from `begin` to `end`. Both `begin` and `end`\nmust be `bytes`. The estimate is approximate, especially for ranges\nsmaller than 3 MB.\n\n### `found.next_prefix(key)`\n\nReturns the immediately next byte sequence that is not a prefix of\n`key`. Raises `ValueError` if `key` is made entirely of `0xFF` bytes.\n\n### `found.lt(key, offset=0)`\n\nCreate a key selector that resolves to the last key lexicographically\nless than `key`. `key` must be `bytes`. Use with `found.query`.\n\n### `found.lte(key, offset=0)`\n\nCreate a key selector that resolves to the last key lexicographically\nless than or equal to `key`. `key` must be `bytes`. Use with\n`found.query`.\n\n### `found.gt(key, offset=1)`\n\nCreate a key selector that resolves to the first key lexicographically\ngreater than `key`. `key` must be `bytes`. Use with `found.query`.\n\n### `found.gte(key, offset=1)`\n\nCreate a key selector that resolves to the first key lexicographically\ngreater than or equal to `key`. `key` must be `bytes`. Use with\n`found.query`.\n\n### `await found.read_version(tx)`\n\nReturn the read version of the transaction `tx` as an integer.\n\n### `await found.set_read_version(tx, version)`\n\nSet the read version of the transaction `tx` to `version`. The\ntransaction must not be a snapshot transaction.\n\n### `found.co(func)`\n\nDecorator that wraps a synchronous function into a coroutine. The\nwrapped function can then be used with `await`.\n\n### `await found.all(aiogenerator)`\n\nCollect all items from an async generator into a list and return it.\n\n### `found.limit(iterator, length)`\n\nAsync generator that yields at most `length` items from `iterator`.\n\n### `await found.add(tx, key, param)`\n\nPerform an atomic add of `param` to the value at `key`.\n\n### `await found.bit_and(tx, key, param)`\n\nPerform an atomic bitwise AND of `param` with the value at `key`.\n\n### `await found.bit_or(tx, key, param)`\n\nPerform an atomic bitwise OR of `param` with the value at `key`.\n\n### `await found.bit_xor(tx, key, param)`\n\nPerform an atomic bitwise XOR of `param` with the value at `key`.\n\n### `await found.max(tx, key, param)`\n\nAtomically set the value at `key` to the larger of the existing value\nand `param`, compared as unsigned integers.\n\n### `await found.byte_max(tx, key, param)`\n\nAtomically set the value at `key` to the lexicographically larger of\nthe existing value and `param`.\n\n### `await found.min(tx, key, param)`\n\nAtomically set the value at `key` to the smaller of the existing value\nand `param`, compared as unsigned integers.\n\n### `await found.byte_min(tx, key, param)`\n\nAtomically set the value at `key` to the lexicographically smaller of\nthe existing value and `param`.\n\n### `await found.set_versionstamped_key(tx, key, param)`\n\nSet `key` with an embedded versionstamp to `param`. The key must\ncontain an incomplete versionstamp.\n\n### `await found.set_versionstamped_value(tx, key, param)`\n\nSet `key` to `param` where `param` contains an embedded versionstamp.\nThe value must contain an incomplete versionstamp.\n\n### `await found.append_if_fits(tx, key, param)`\n\nAtomically append `param` to the value at `key`. If the resulting value\nwould exceed the maximum value size, the operation has no effect.\n\n### `await found.compare_and_clear(tx, key, param)`\n\nAtomically clear `key` if its current value equals `param`. If the\nvalue does not match, the key is left unchanged.\n\n### `found.get_client_version()`\n\nReturn the FDB client library version string (synchronous).\n\n### `await found.get_addresses_for_key(tx, key)`\n\nReturn a list of strings representing the storage server addresses\nresponsible for `key`. `key` must be `bytes`.\n\n### `found.database_set_option(db, option, value=None)`\n\nSet a database-level option. `option` must be one of the\n`FDB_DB_OPTION_*` integer constants. `value` is optional and must\nbe `bytes` when provided.\n\n### `found.network_set_option(option, value=None)`\n\nSet a network-level option. `option` must be one of the\n`FDB_NET_OPTION_*` integer constants. `value` is optional and must\nbe `bytes` when provided. Must be called before the network thread\nstarts (i.e., before the first `found.open()` call).\n\n### `found.add_network_thread_completion_hook(callback)`\n\nRegister a callback to be invoked when the FDB network thread exits.\n`callback` must be a callable taking no arguments.\n\n### `found.error_predicate(predicate, code)`\n\nTest whether an error `code` matches `predicate`. Returns `True` or\n`False`. Predicate constants:\n\n- `found.ERROR_PREDICATE_RETRYABLE` (50000)\n- `found.ERROR_PREDICATE_MAYBE_COMMITTED` (50001)\n- `found.ERROR_PREDICATE_RETRYABLE_NOT_COMMITTED` (50002)\n\n## `from found.ext import bstore`\n\n`bstore` is a content-addressable blob store. You hand it an arbitrary\nbinary payload and it returns a stable uid; store the same bytes twice\nand you get the same uid back without writing a second copy, because\nevery blob is hashed with blake2b before storage. Reach for `bstore`\nwhen your data includes large or repeated binary objects — files,\nimages, serialized artifacts — that you want to reference by identity\nrather than by the content itself.\n\n### `bstore.BStoreException`\n\nException specific to `bstore`.\n\n### `bstore.make(name, prefix)`\n\nHandle over a `bstore` called `name` with `prefix`.\n\n### `await bstore.get_or_create(tx, bstore, blob)`\n\nStore `blob` and return its uid. If a blob with the same content\nalready exists, return the existing uid without storing a duplicate.\n\n### `await bstore.get(tx, bstore, uid)`\n\nRetrieve the blob associated with `uid`. Raises `BStoreException`\nif not found.\n\n## `from found.ext import nstore`\n\n`nstore` is a generic N-tuple store with pattern-matching queries. You\ndefine a store of fixed width N and add tuples to it; you then query by\nsupplying a pattern where any position can be a concrete value or a\n`var`, and the store yields all bindings that satisfy the pattern. It\nmaintains a minimal set of index permutations automatically so that any\nquery pattern resolves in a single ordered range scan. Reach for\n`nstore` when your data is naturally relational and you want to express\nqueries as patterns — think Datalog or a triple store — rather than\nbuilding explicit indexes by hand.\n\n**Index count.** By Dilworth's theorem, covering the boolean lattice of\nall query patterns by the minimal number of maximal chains requires\nexactly C(n, n//2) permutations — the central binomial coefficient.\nIn practice: a triplestore (`n=3`) needs **3 indices**; a quadstore\n(`n=4`) needs **6 indices**. Every `add` writes to all of them; every\n`select` uses exactly one. See\n[the derivation](https://math.stackexchange.com/questions/3146568/)\nfor details.\n\n### `nstore.NStoreException`\n\nException specific to `nstore`.\n\n### `nstore.make(name, prefix, n)`\n\nCreate a handle over a `nstore` called `name` with `prefix` and `n`\ncolumns.\n\nThe argument `name` should be a string, it is really meant to ease\ndebugging. `prefix` should be a tuple that can be packed with\n`found.pack`. Last but not least, `n` is the number of columns in the\nreturned tuple store (or, if you prefer, the number of tuple items).\n\nIt is preferable to store the returned value.\n\n### `await nstore.add(tx, nstore, *items, *, value=b'')`\n\nIn the database associated with `tx`, as part of `nstore`, add\n`items` associated with `value`.\n\n### `await nstore.remove(tx, nstore, *items)`\n\nIn the database associated with `tx`, as part of `nstore`, remove\n`items` and the associated value.\n\n### `await nstore.get(tx, nstore, *items)`\n\nIn the database associated with `tx`, as part of `nstore`, get the\nvalue associated with `items`. If there is no such items in `nstore`,\nreturns `None`.\n\n### `nstore.var(name)`\n\nCreate a variable called `name` for use with `nstore.query`.\n\n### `nstore.select(tx, nstore, *pattern, seed=None)`\n\nYield dict bindings that match `pattern`. Each element of\n`pattern` is either a value or a `nstore.var`. This is the\nlow-level primitive used by `nstore.query`.\n\n### `nstore.where(tx, nstore, iterator, *pattern)`\n\nFor each binding from `iterator`, bind `pattern` and yield matching\nbindings from `nstore`. Used to chain queries together.\n\n### `nstore.query(tx, nstore, pattern, *patterns)`\n\nIn the database associated with `tx`, as part of `nstore`, generate\nmappings that match `pattern` and `patterns`. Both `pattern` and\n`patterns` may contain `nstore.var` that will be replaced with\nmatching values in the generic tuple store.\n\n## `from found.ext import eavstore`\n\n`eavstore` is an entity-attribute-value store for Python dictionaries.\nEach call to `create` stores a dict under a generated uid, and the store\nautomatically maintains a reverse index on every attribute-value pair so\nyou can look up all entities that share a given key-value combination.\nReach for `eavstore` when your records have irregular shapes — different\nkeys per entity, optional fields — or when you need attribute-level\nlookup without defining a schema up front.\n\n### `eavstore.make(name, prefix)`\n\nCreate a handle over an eavstore called `name` with `prefix`.\n\nThe argument `name` should be a string, it is really meant to ease\ndebugging. `prefix` should be a tuple that can be packed with\n`found.pack`.\n\n### `await eavstore.create(tx, eavstore, dict, uid=None)`\n\nStore a dictionary.\n\nIn the database associated with `tx`, as part of `eavstore`, save\n`dict` and returns its unique identifier. If `uid` is provided,\nuse it instead of generating a new one.\n\n### `await eavstore.get(tx, eavstore, uid)`\n\nFetch a dictionary.\n\nIn the database associated with `tx`, as part of `eavstore`, retrieve\nthe dictionary associated with `uid`. If there is no such dictionary,\nreturns an empty dictionary.\n\n### `await eavstore.remove(tx, eavstore, uid)`\n\nClear a dictionary.\n\nIn the database associated with `tx`, as part of `eavstore`, remove\nthe dictionary associated with `uid`.\n\n### `await eavstore.update(tx, eavstore, uid, dict)`\n\nUpdate a dictionary.\n\nIn the database associated with `tx`, as part of `eavstore`, replace\nthe dictionary associated with `uid` with `dict`.\n\n### `await eavstore.query(tx, eavstore, key, value)`\n\nLookup dictionaries according to specification.\n\nIn the database associated with `tx`, as part of `eavstore`, generates\nunique identifier for dictionaries that have `key` equal to `value`.\n\n## `from found.ext import pstore`\n\n`pstore` is an inverted index for keyword search. You index each\ndocument as a mapping of string terms to positive integer counts; later\nyou query with a set of keywords and get back the top-scoring document\nuids, ranked by how well the terms match. Reach for `pstore` when you\nneed relevance-ranked full-text or keyword search over documents whose\nprimary content lives elsewhere in the database.\n\n### `pstore.PStoreException`\n\nException specific to `pstore`.\n\n### `pstore.make(name, prefix)`\n\nCreate a handle over a `pstore` called `name` with `prefix`.\n\n### `await pstore.index(tx, store, docuid, counter)`\n\nAssociates `docuid` with `counter`.\n\nCoroutine that associates the identifier `docuid` with the dict-like\n`counter` inside the database associated with `tx` at `store` for\nlater retrieval with `pstore.search`.\n\n`counter` must be a dict-like mapping string to integers bigger than\nzero.\n\n### `await pstore.search(tx, store, keywords, limit)`\n\nReturn a sorted list of at most `limit` documents matching `keywords`.\n\n## `from found.ext import vnstore`\n\n`vnstore` is a versioned N-tuple store. It wraps the same pattern-\nmatching model as `nstore` but groups every addition and removal into a\nnamed change-set; a change is invisible until you explicitly apply it,\nat which point it receives a uuid7 significance that defines its place\nin history. Reach for `vnstore` when you need an auditable log of\nmodifications, or when you want to stage a batch of changes for review\nbefore making them visible to other readers.\n\n### `vnstore.make(name, prefix, items)`\n\nCreate a handle over a `vnstore` called `name` with the prefix tuple\n`prefix`, and `items` as column names.\n\nThe argument name should be a string, it is really meant to ease\ndebugging. prefix should be a tuple that can be packed with\nfound.pack. Last but not least, `items` is the columns in the returned\ntuple store (or, if you prefer, the name of tuple items).\n\nIt is preferable to store the returned value.\n\n### `await vnstore.change_create(tr, vnstore)`\n\nReturn the unique idenifier of a new change in database.  Its initial\nsignifiance is `None` which means it is invisible to other\ntransactions, and its message `None`.\n\n### `await vnstore.change_list(tr, vnstore)`\n\nReturn a list of all changes in `vnstore`. Each change is a\ndictionary with keys `uid`, `type`, `significance`, and `message`.\n\n### `await vnstore.change_get(tr, vnstore, changeid)`\n\nReturn the change as a dictionary with keys `uid`, `type`,\n`significance`, and `message`. Returns `None` if the change does\nnot exist.\n\n### `vnstore.change_continue(tr, vnstore, changeid)`\n\nAgainst transaction `tr`, and `vnstore`, continue a change `changeid`.\nThis sets the active change on the transaction so that subsequent\n`vnstore.add` and `vnstore.remove` calls are associated with it.\n\n### `await vnstore.change_message(tr, vnstore, changeid, message)`\n\nReplace the existing message of `changeid` with `message`.\n\n### `await vnstore.change_changes(tr, vnstore, changeid)`\n\nReturn a list of all tuple modifications (additions and removals)\nassociated with `changeid`.\n\n### `await vnstore.change_apply(tr, vnstore, changeid)`\n\nApply the change `changeid` against `vnstore`, setting the next\n`uuid7` as significance.\n\n#### Known issue: Weak serializability\n\nThe use of `uuid7` instead of versionstamps can break things when\nchanges happen over overlapping versioned triples. Strict ordering,\nserializability is not guaranteed, hence one transaction may write, a\nvalue based on a value that was overwritten by another change that\nappears to be commited after according to its `uuid7` significance.\nEven if changes are commited in the correct order uuid7 does not\nguarantee serializability.\n\nIn other words, as long as we rely `uuid7` we can't consider changes\ncommited with `vnstore_change_apply` happen as if all changes were\ncommited after the other, that is, there is no serializability\nguarantee.\n\n[Contact me for workarounds](mailto:amirouche@hyper.dev)\n\n#### Known issue: consistency\n\nSince changes may be constructed with several transactions, it is\npossible that two changes introduce consistency bugs.\n\n[Contact me for workarounds](mailto:amirouche@hyper.dev)\n\n### `vnstore.select(tr, vnstore, *pattern, seed=None)`\n\nYield dict bindings that match `pattern` against alive tuples in\n`vnstore`. Each element of `pattern` is either a value or a\n`nstore.var`. This is the low-level primitive used by\n`vnstore.query`.\n\n### `await vnstore.ask(tr, vnstore, *items)`\n\nReturn `True` if `items` is alive in the space `vnstore`.\n\n### `await vnstore.add(tr, vnstore, *items)`\n\nAdd `items` to `vnstore` under the current active change (set via\n`vnstore.change_continue`). Returns `True`.\n\n### `await vnstore.remove(tr, vnstore, *items)`\n\nRemove `items` from `vnstore` under the current active change. Returns\n`True` if the items existed and were removed, `False` otherwise.\n\n### `await vnstore.where(tr, vnstore, iterator, *pattern)`\n\nBind `pattern` against each binding from `iterator`, then yield\nmatching bindings from `vnstore`. Used to chain queries together.\n\n### `await vnstore.query(tr, vnstore, pattern, *patterns)`\n\nReturn immutable mappings where `vnstore.var` from `pattern`, and\n`patterns` are replaced with objects from `vnstore`.\n\n## `from found.ext.vnstore import server`\n\n`server` is an ASGI application that exposes a `vnstore` instance over HTTP.\nIt requires the `server` optional-dependency group (`jinja2`, `uvicorn`).\n\n### Running the server\n\n```\npip install asyncio-foundationdb[server]\nfound-vnstore\n```\n\nThe server listens on `127.0.0.1:8000` by default.  The store is initialised\nduring the ASGI lifespan startup event and shared across all requests through\n`scope[\"state\"]`.\n\n### `server(scope, receive, send)`\n\nStandard three-argument ASGI callable.  Pass it directly to any ASGI runner:\n\n```\nuvicorn found.ext.vnstore.server:server --lifespan on\n```\n\n### `server.main()`\n\nEntry point used by the `found-vnstore` console script.  Calls\n`uvicorn.run(\"found.ext.vnstore.server:server\", host=\"127.0.0.1\", port=8000,\nlifespan=\"on\")`.\n\n### Routes\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/` | Index page |\n| `GET` | `/history/` | List all changes |\n| `GET` | `/history/change/` | New-change form |\n| `POST` | `/history/change/` | Create a change |\n| `GET` | `/history/u/{hex}/` | Change detail |\n| `GET` | `/history/u/{hex}/add/` | Add-tuple form |\n| `POST` | `/history/u/{hex}/add/` | Add a tuple to the change |\n| `GET` | `/history/u/{hex}/remove/` | Remove-tuple form |\n| `POST` | `/history/u/{hex}/remove/` | Remove a tuple from the change |\n| `GET` | `/history/u/{hex}/apply/` | Apply-change confirmation |\n| `POST` | `/history/u/{hex}/apply/` | Apply the change |\n| `GET` | `/navigate/` | Pattern-match query (up to 42 results) |\n\n### Value encoding\n\nForm fields use a compact text encoding recognised by `server.fromstring` /\n`server.tostring`:\n\n| Prefix | Python type | Example |\n|--------|-------------|---------|\n| *(none)* | `str` | `hello` |\n| `_` | `uuid4()` (fresh) | `_` |\n| `#none` | `None` | `#none` |\n| `#true` / `#false` | `bool` | `#true` |\n| `#u…` | `UUID` | `#uDEADBEEF…` |\n| `#i…` | `int` | `#i42` |\n| `#f…` | `float` | `#f3.14` |\n\n## `from found.ext import pool`\n\n`pool` is a low-level utility, not a domain abstraction. It provides a\nsingle helper that fans an async iterator out to a thread-pool executor\nand streams results back as each completes. Reach for it when you need\nto parallelize CPU-bound or blocking work alongside async database\noperations, and none of the domain stores above is the right layer for\nthat parallelism.\n\n### `await pool.pool_for_each_par_map(loop, pool, f, p, iterator)`\n\nApply `p` in `pool` threads over `iterator`, calling `f` on each\nresult as it completes. `loop` is the asyncio event loop, `pool` is\na `concurrent.futures.ThreadPoolExecutor`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famirouche%2Fasyncio-foundationdb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famirouche%2Fasyncio-foundationdb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famirouche%2Fasyncio-foundationdb/lists"}