Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions documentation/docs/library_services/ciphertext.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,21 @@ application — you do not need to set it yourself.

## Imports and servicers

The `ciphertext` library depends on [`OrderedMap`](./ordered_map), so include
both when starting your `Application`.
The `ciphertext` library depends on [`OrderedMap`](./ordered_map). In
Python, listing `ciphertext_library()` brings it along; in TypeScript,
list both.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

```py

from reboot.std.ciphertext.v1.ciphertext import ciphertext_library
from reboot.std.collections.ordered_map.v1.ordered_map import (
ordered_map_library,
)

async def main():
application = Application(
servicers=[MyServicer],
libraries=[ciphertext_library(), ordered_map_library()],
libraries=[ciphertext_library()],
)
await application.run()
```
Expand Down
16 changes: 4 additions & 12 deletions documentation/docs/library_services/oauth_token_manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,20 @@ service without one (e.g. `"slack.com"`).

## Imports and servicers

The `oauth` library builds on [`Ciphertext`](./ciphertext) (which builds
on [`OrderedMap`](./ordered_map)), so include all three when starting
your `Application`; omitting any of them fails fast at startup.
The `oauth` library builds on [`Ciphertext`](./ciphertext), which builds
on [`OrderedMap`](./ordered_map). In Python, listing `oauth_library()`
brings both along; in TypeScript, list all three.

<Tabs groupId="language">
<TabItem value="python" label="Python" default>

```py
from reboot.std.oauth.v1.oauth import oauth_library
from reboot.std.ciphertext.v1.ciphertext import ciphertext_library
from reboot.std.collections.ordered_map.v1.ordered_map import (
ordered_map_library,
)

async def main():
application = Application(
servicers=[MyServicer],
libraries=[
oauth_library(),
ciphertext_library(),
ordered_map_library(),
],
libraries=[oauth_library()],
)
await application.run()
```
Expand Down
5 changes: 5 additions & 0 deletions documentation/docs/library_services/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ new Application({
</TabItem>
</Tabs>

In Python, a library's own requirements come along automatically:
listing `queue_library()` also mounts the `SortedMap` library it builds
on. List a required library yourself only to customize it, for example
with an authorizer.

## Future libraries and integrations

[Reach out to us](https://discord.gg/cRbdcS94Nr) if there are any integrations or standard library features you want us to prioritize. Or we can help you build them, if that's more your speed!
16 changes: 6 additions & 10 deletions documentation/docs/library_services/pubsub.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,21 @@ import { Topic } from "@reboot-dev/reboot-std/pubsub/v1";
</TabItem>
</Tabs>

Also make sure to include the pub/sub library and the dependent
[`Queue`](./queue) and [`SortedMap`](./sorted_map) libraries when starting up
your `Application`. (Note: this import is different from above.)
Also make sure to include the pub/sub library when starting up your
`Application`. In Python, listing `pubsub_library()` brings along the
[`Queue`](./queue) and [`SortedMap`](./sorted_map) libraries it depends
on; in TypeScript, list all three. (Note: this import is different from
above.)

<Tabs groupId="language">
<TabItem value="python" label="Python" default>
```py
from reboot.std.collections.queue.v1.queue import queue_library
from reboot.std.collections.v1.sorted_map import sorted_map_library
from reboot.std.pubsub.v1.pubsub import pubsub_library

async def main():
application = Application(
servicers=[MyServicer],
libraries=[
pubsub_library(),
queue_library(),
sorted_map_library(),
],
libraries=[pubsub_library()],
)
await application.run()
```
Expand Down
10 changes: 5 additions & 5 deletions documentation/docs/library_services/queue.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,20 @@ import { Queue } from "@reboot-dev/reboot-std/collections/queue/v1";
</TabItem>
</Tabs>

Also make sure to include the `Queue` library and the dependent [`SortedMap`](./sorted_map)
library when starting up your `Application`. (Note: this import is different
from above.)
Also make sure to include the `Queue` library when starting up your
`Application`. In Python, listing `queue_library()` brings along the
[`SortedMap`](./sorted_map) library it depends on; in TypeScript, list
both. (Note: this import is different from above.)

<Tabs groupId="language">
<TabItem value="python" label="Python" default>
```py
from reboot.std.collections.queue.v1.queue import queue_library
from reboot.std.collections.v1.sorted_map import sorted_map_library

async def main():
application = Application(
servicers=[MyServicer],
libraries=[queue_library(), sorted_map_library()],
libraries=[queue_library()],
)
await application.run()
```
Expand Down
103 changes: 73 additions & 30 deletions reboot/aio/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@
from reboot.version import REBOOT_VERSION
from reboot.versioning import version_less_than
from starlette.staticfiles import StaticFiles
from typing import Any, Awaitable, Callable, Mapping, NoReturn, Optional
from typing import (
Any,
Awaitable,
Callable,
ClassVar,
Mapping,
NoReturn,
Optional,
)

logger = get_logger(__name__)

Expand Down Expand Up @@ -184,6 +192,12 @@ class Library(AbstractLibrary):
any checks on subclasses.
"""

# Every library class by its `name`, so that a library's
# `requirements()` can be constructed without the application
# listing them. A class defined later under the same name replaces
# the earlier one, which keeps reloading a module harmless.
_registry: ClassVar[dict[str, type['Library']]] = {}

def __init_subclass__(cls, **kwargs):
# `name` checked here because this class expects its subclasses
# to define it.
Expand All @@ -193,6 +207,7 @@ def __init_subclass__(cls, **kwargs):
"Please set `name` as a class variable. For best practices, "
"please define the name as a constant in your library module."
)
Library._registry[cls.name] = cls

async def pre_run(self, application: Application) -> None:
"""
Expand Down Expand Up @@ -231,6 +246,25 @@ async def initialize(self, context: InitializeContext) -> None:
await self._initialize(context)


def _construct_library(name: str, required_by: str) -> Library:
"""Constructs, with its defaults, the library registered as `name`."""
library_type = Library._registry.get(name)
if library_type is None:
raise ValueError(
f"Library `{required_by}` requires library `{name}`, which is "
"not one Reboot can construct itself. Please construct it and "
"pass it in the `libraries` parameter."
)
try:
return library_type()
except TypeError as error:
raise ValueError(
f"Library `{required_by}` requires library `{name}`, which can "
"not be constructed without arguments. Please construct it and "
"pass it in the `libraries` parameter."
) from error


class Application:
"""Entry point for all Reboot applications."""

Expand Down Expand Up @@ -258,7 +292,10 @@ def __init__(
:param legacy_grpc_servicers: the types of legacy gRPC servicers
(not using Reboot libraries) that this Application will
serve.
:param libraries: the libraries this Application will use.
:param libraries: the libraries this Application will use. A
library's own requirements are added automatically; list a
required library yourself only to customize it, e.g. with
an authorizer.
:param initialize: will be called after the Application's
servicers have started for the first time, so that it can
perform initialization logic (e.g., creating some well-known
Expand Down Expand Up @@ -321,23 +358,30 @@ def __init__(
"library once in `libraries`."
)

# Verify we have all the requirements for libraries.
requirements_names = set(
requirement for library in libraries
for requirement in library.requirements()
)
needed_requirements = requirements_names - library_names

if len(needed_requirements) > 0:
raise ValueError(
"Missing required libraries: "
f"{', '.join(needed_requirements)}. "
"Please add these libraries and pass them to the "
"`libraries` parameter."
)
# Add each library's requirements, and theirs in turn, so
# that listing a library is enough to run it. A library the
# application lists itself takes precedence over one
# constructed here, which is how an application customizes
# a dependency, e.g. with an authorizer.
libraries_by_name = {
library.name: library for library in libraries
}
pending = list(libraries)
while len(pending) > 0:
library = pending.pop()
for requirement in library.requirements():
if requirement in libraries_by_name:
continue
required = _construct_library(
requirement, required_by=library.name
)
libraries_by_name[requirement] = required
pending.append(required)

# Sort libraries by name for guaranteed ordering.
libraries = sorted(libraries, key=lambda library: library.name)
libraries = sorted(
libraries_by_name.values(), key=lambda library: library.name
)

# Add the library servicers to the list of servicers.
library_servicers = [
Expand Down Expand Up @@ -958,27 +1002,26 @@ def _frontend_project_root(self) -> Path:

def _require_oauth_libraries(self) -> None:
"""Fail fast if an OAuth provider with `store_tokens=True` is used
without the `oauth` (and its `ciphertext`) library mounted — they
encrypt and persist the identity provider's tokens.
without the `oauth` library mounted — it (with the `ciphertext`
library it builds on) encrypts and persists the identity
provider's tokens.
"""
# Imported lazily: both libraries import `reboot.aio.applications`,
# Imported lazily: the library imports `reboot.aio.applications`,
# so a module-level import would be circular.
from reboot.std.ciphertext.v1.ciphertext import CIPHERTEXT_LIBRARY_NAME
from reboot.std.oauth.v1.oauth import OAUTH_LIBRARY_NAME
names = {library.name for library in (self._libraries or [])}
if OAUTH_LIBRARY_NAME in names and CIPHERTEXT_LIBRARY_NAME in names:
if OAUTH_LIBRARY_NAME in names:
return
raise InputError(
reason=(
"An OAuth provider with `store_tokens=True` needs the "
"`oauth` and `ciphertext` libraries to encrypt and persist "
"the identity provider's tokens, but they aren't all "
"mounted. Add them to your `Application`, e.g. "
"`Application(..., libraries=[oauth_library(), "
"ciphertext_library(), ordered_map_library()])` (import "
"`oauth_library` from `reboot.std.oauth.v1.oauth` and "
"`ciphertext_library` from "
"`reboot.std.ciphertext.v1.ciphertext`)."
"`oauth` library to encrypt and persist the identity "
"provider's tokens, but it isn't mounted. Add it to your "
"`Application`, e.g. `Application(..., "
"libraries=[oauth_library()])` (import `oauth_library` "
"from `reboot.std.oauth.v1.oauth`); the `ciphertext` and "
"`ordered_map` libraries it builds on come along "
"automatically."
),
)

Expand Down
9 changes: 4 additions & 5 deletions reboot/plugin/skills/mcp-ui/references/auth-store-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ def _google() -> Google:
async def main() -> None:
application = Application(
servicers=[UserServicer],
# `store_tokens=True` persists tokens via the `oauth` library,
# which encrypts them via `ciphertext`, which in turn needs
# `ordered_map`. Without all three the app fails fast at startup.
libraries=[oauth_library(), ciphertext_library(),
ordered_map_library()],
# `store_tokens=True` persists tokens via the `oauth` library;
# the `ciphertext` and `ordered_map` libraries it builds on come
# along automatically.
libraries=[oauth_library()],
oauth=OAuth(
provider=OAuthProviderByEnvironment(
# The calendar needs a real provider token even in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,9 @@ fails fast at startup:

```python
from reboot.std.oauth.v1.oauth import oauth_library
from reboot.std.ciphertext.v1.ciphertext import ciphertext_library
from reboot.std.collections.ordered_map.v1.ordered_map import (
ordered_map_library,
)

# libraries=[oauth_library(), ciphertext_library(), ordered_map_library()]
# libraries=[oauth_library()]; the `ciphertext` and `ordered_map`
# libraries it builds on come along automatically.
```

`REBOOT_CRYPTO_ROOT_KEYS` backs the encryption; it is auto-provisioned
Expand Down Expand Up @@ -310,7 +307,7 @@ await KeyManager.ref(_key_manager_id(GOOGLE)).shred(context, scope=user_id)

## Checklist

- [ ] `libraries=[oauth_library(), ciphertext_library(), ordered_map_library()]` on the `Application` (Path C needs only the latter two).
- [ ] `libraries=[oauth_library()]` on the `Application` (Path C needs only `ciphertext_library()`).
- [ ] **Capture** — Path A (identity provider's own API):
`scopes=[...]` (least privilege) + `store_tokens=True`. Path B
(any other service): your own authorize + callback routes, callback registered `app_internal=True`, the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,17 @@ async def main():

## See Also

If you're using any stdlib state type, the wiring lives in **two**
places: its `servicers()` list goes into `servicers=[...]`, and its
`<name>_library()` factory goes into `libraries=[...]`. Forgetting
either gives a runtime "unknown actor type" error. The references for
each type call out exactly what to register:

- `stdlib-ordered-map.md` — `ordered_map.servicers()` + `ordered_map_library()`
- `stdlib-queue.md` — `queue.servicers()` + the stdlib map library
(`Queue` uses a stdlib sorted-map actor under the hood — see the
reference for the exact import)
- `stdlib-pubsub.md` — `pubsub.servicers()` (transitively pulls
`queue.servicers()`) + the stdlib map library
If you're using any stdlib state type, its `<name>_library()` factory
goes into `libraries=[...]`; that registers its servicers and mounts
whatever the library itself depends on. Forgetting it gives a runtime
"unknown actor type" error. The references for each type call out
exactly what to register:

- `stdlib-ordered-map.md` — `ordered_map_library()`
- `stdlib-queue.md` — `queue_library()` (brings along the stdlib
sorted-map library `Queue` uses under the hood)
- `stdlib-pubsub.md` — `pubsub_library()` (brings along `Queue` and
the sorted-map library)
- `stdlib-presence.md` — `presence.servicers()` (returns three
Servicers; no library factory)

Expand Down
12 changes: 5 additions & 7 deletions reboot/plugin/skills/python/references/stdlib-ciphertext.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ tags: stdlib, ciphertext, encryption, envelope, crypto-shred, gdpr, right-to-era

## Use `Ciphertext` for Envelope Encryption and Crypto-Shredding

> **Critical:** register **both** `ciphertext_library()` **and** > `ordered_map_library()` in `Application(libraries=[...])` —
> `Ciphertext` depends on `OrderedMap`; forgetting either fails at boot
> with "unknown actor type." `associated_data` must be supplied
> **Critical:** register `ciphertext_library()` in
> `Application(libraries=[...])`; it brings along the `OrderedMap`
> library it depends on. Forgetting it fails at boot with "unknown
> actor type." `associated_data` must be supplied
> **byte-for-byte identical** at decrypt as at encrypt — build it with
> `make_associated_data`, never an ad-hoc string. These methods are
> **app-internal by default** (no authorizer); call them from within
Expand Down Expand Up @@ -47,15 +48,12 @@ root KEK — derived from REBOOT_CRYPTO_ROOT_KEYS (auto-provisioned); never st

```python
from reboot.std.ciphertext.v1.ciphertext import ciphertext_library
from reboot.std.collections.ordered_map.v1.ordered_map import (
ordered_map_library,
)


async def main():
await Application(
servicers=[VaultServicer],
libraries=[ciphertext_library(), ordered_map_library()],
libraries=[ciphertext_library()],
).run()
```

Expand Down
Loading
Loading