09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
288 lines
14 KiB
Markdown
288 lines
14 KiB
Markdown
# Naming Conventions
|
|
|
|
**Status:** Active
|
|
**Last Updated:** 2026-05-21
|
|
|
|
This document is the canonical reference for naming patterns that recur across
|
|
the `notebooklm-py` codebase. It catalogues three families that an internal
|
|
architecture audit (findings CC2 / CC3 / CC5) called out as inconsistent enough
|
|
to need a written tiebreaker:
|
|
|
|
1. [Waiting / polling verbs](#1-waiting--polling-verbs-cc2) — `poll_X` vs
|
|
`wait_for_X` vs `wait_until_X` vs `await_X` vs `_wait_for_X`.
|
|
2. [RPC-callable Protocol names](#2-rpc-callable-protocol-names-cc3) —
|
|
`NextCall` / `RpcCallback` / `RpcCaller`.
|
|
3. [Metrics method verbs](#3-metrics-method-verbs-cc5) — `record_X` vs `emit_X`.
|
|
|
|
Examples below cite **symbol names only** (no file:line refs). Use
|
|
`rg '<symbol>' src/notebooklm/` to locate the current home — line numbers drift
|
|
faster than this file can keep up with.
|
|
|
|
---
|
|
|
|
## 1. Waiting / polling verbs (CC2)
|
|
|
|
Five distinct verbs are intentional. They are not interchangeable. Pick the
|
|
shape that matches what the function actually does and the loop will document
|
|
itself.
|
|
|
|
### `poll_X` — one-shot status read, no loop, no sleep
|
|
|
|
A `poll_X` function performs **a single** status / readiness check and returns
|
|
immediately. It never sleeps and never iterates. Use this when the *loop* lives
|
|
in the caller (or in a `wait_*` wrapper) and the function is just the
|
|
per-iteration probe.
|
|
|
|
Examples:
|
|
|
|
- `ArtifactPollingService.poll_status` — single RPC list + scan for one task ID.
|
|
- `ArtifactsAPI.poll_status` — public single-shot facade over the service.
|
|
- `ResearchAPI.poll` — single status read for a research plan.
|
|
- `artifact_poll` (CLI command) — one shot, then exit. Use the separate
|
|
`artifact wait` command for the blocking / looping variant; `artifact poll`
|
|
itself has no `--wait` flag.
|
|
|
|
> **Test name:** "if I call this twice in a row without a sleep, does that make
|
|
> sense?" If yes → it's a `poll_X`.
|
|
|
|
### `wait_for_X` — bounded loop with a **timeout**
|
|
|
|
A `wait_for_X` function loops until either the awaited condition holds **or** a
|
|
deadline expires. Timeouts are required (default or explicit); the function
|
|
raises a typed `*TimeoutError` on expiry rather than returning a sentinel.
|
|
|
|
Examples:
|
|
|
|
- `ArtifactPollingService.wait_for_completion` — loops `poll_status` until the
|
|
artifact is terminal or `timeout` elapses.
|
|
- `ArtifactsAPI.wait_for_completion` — public facade over the service loop.
|
|
- `ResearchAPI.wait_for_completion` — loops `poll` until research is terminal,
|
|
pinning a discovered `task_id` between iterations.
|
|
- `SourcePoller.wait_for_sources` (and `SourcesAPI.wait_for_sources`) — batch
|
|
wait across N source IDs with a shared deadline.
|
|
- `RetryMiddleware._wait_for_rate_limit` / `_wait_for_server_error` — private
|
|
variant; see the underscore-prefix subsection below.
|
|
|
|
### `wait_until_X` — loop on a **predicate** (also bounded)
|
|
|
|
`wait_until_X` reads like English: "wait until `X` is true". Same loop+timeout
|
|
contract as `wait_for_X`, but the verb signals that the awaited condition is a
|
|
**state predicate** on a specific resource, not the *arrival* of a value.
|
|
|
|
Examples:
|
|
|
|
- `SourcePoller.wait_until_ready` / `SourcesAPI.wait_until_ready` — block until
|
|
`source.is_ready`.
|
|
- `SourcePoller.wait_until_registered` / `SourcesAPI.wait_until_registered` —
|
|
block until a freshly-added source appears in the notebook listing.
|
|
|
|
> **`wait_for_X` vs `wait_until_X`:** both loop with a timeout. The difference
|
|
> is naming ergonomics. Prefer `wait_until_X` when the awaited condition is a
|
|
> boolean property of an existing resource (`is_ready`, `is_registered`).
|
|
> Prefer `wait_for_X` when you're waiting on an external arrival or a *set* of
|
|
> items (`wait_for_sources`, `wait_for_completion`). Neither form is "more
|
|
> correct"; pick the one that reads naturally at the call site.
|
|
|
|
### `await_X` — coalesced single-flight join
|
|
|
|
`await_X` is reserved for **single-flight coalescing** primitives: many
|
|
concurrent callers join one shared in-flight operation. The function name
|
|
matches the user-facing verb ("await the refresh"), and the implementation
|
|
guarantees deduplication (typically via `asyncio.shield` + a stored task).
|
|
|
|
Examples:
|
|
|
|
- `AuthRefreshCoordinator.await_refresh` — thundering-herd-safe token refresh;
|
|
all 401-bouncing callers join one refresh task.
|
|
|
|
Do **not** use `await_X` for ordinary `async def` functions just because they
|
|
get `await`-ed. The verb signals coalescing semantics, not async-ness.
|
|
|
|
### `_wait_for_X` — module-private backoff helper
|
|
|
|
The leading underscore + `wait_for_` shape is used inside middlewares to
|
|
indicate **"this is the bounded backoff helper I extracted from one specific
|
|
retry leg"**. It is not a public coordination primitive; it is a private
|
|
implementation detail of a larger retry loop.
|
|
|
|
Examples:
|
|
|
|
- `RetryMiddleware._wait_for_rate_limit` — honors `Retry-After`, falls back to
|
|
exponential backoff. Called from inside the rate-limit branch of the retry
|
|
loop; never called externally.
|
|
- `RetryMiddleware._wait_for_server_error` — same shape for the 5xx branch.
|
|
|
|
If you extract a backoff helper from a middleware, follow this pattern. If you
|
|
extract a *public* waiting primitive, drop the underscore and use one of the
|
|
four verbs above.
|
|
|
|
### Summary table
|
|
|
|
| Verb | Loop? | Timeout? | Predicate or arrival? | Shared single-flight? | Public? |
|
|
|---|---|---|---|---|---|
|
|
| `poll_X` | no (one-shot) | n/a | either | n/a | yes |
|
|
| `wait_for_X` | yes | required | arrival of value(s) | no | yes |
|
|
| `wait_until_X` | yes | required | state predicate | no | yes |
|
|
| `await_X` | yes (joins one task) | inherits from task | n/a | yes | yes |
|
|
| `_wait_for_X` | yes | required | arrival | no | no (module-private) |
|
|
|
|
---
|
|
|
|
## 2. RPC-callable Protocol names (CC3)
|
|
|
|
Most feature modules type their RPC dependency as the shared
|
|
`RpcCaller` object Protocol from `_runtime/contracts.py`. Only middleware-chain
|
|
callables and upload's keyword-injected registration callback keep local
|
|
callable shapes. These names are NOT interchangeable — the divergence is
|
|
structural, not stylistic. This section explains what each name signals so new
|
|
code picks the right shape.
|
|
|
|
### The three names in use
|
|
|
|
| Name | Defined in | Protocol shape | Used by |
|
|
|---|---|---|---|
|
|
| `NextCall` | `_middleware/core.py` | **type alias**, not a class: `Callable[[RpcRequest], Awaitable[RpcResponse]]` | Every `Middleware.__call__` — the "call the next link" function passed into around-style middlewares |
|
|
| `RpcCallback` | `_source/upload.py` | **Callable** Protocol: `async def __call__(method, params, ...)` | `SourceUploadPipeline.register_file_source` — RPC entrypoint passed as a **keyword argument** at call time |
|
|
| `RpcCaller` | `_runtime/contracts.py` | **Object** Protocol: `async def rpc_call(method, params, ...)` (i.e. `obj.rpc_call(...)`) | The canonical shared capability Protocol for pure-RPC feature APIs and helper services (`NotesAPI`, `SourceLister`, `ShareManager`, etc.) |
|
|
|
|
### Why they diverge
|
|
|
|
Two axes do the actual work:
|
|
|
|
1. **Callable Protocol vs Object Protocol.** `NextCall` and `RpcCallback` are
|
|
*callable* shapes — the conformer is itself directly invokable
|
|
(`rpc(method, params)`). `RpcCaller` is an
|
|
*object* shape — the conformer exposes an `.rpc_call(...)` method
|
|
(`executor.rpc_call(method, params)`).
|
|
These are NOT interchangeable to mypy: a callable Protocol matches a bare
|
|
function or `__call__`, while `RpcCaller` requires the named method. An
|
|
`RpcExecutor` instance satisfies `RpcCaller` because it defines `rpc_call`; the
|
|
bound method `executor.rpc_call` satisfies `RpcCallback` because it is a
|
|
callable Protocol.
|
|
2. **Type alias vs Protocol class.** `NextCall` is a `Callable[...]` alias, not
|
|
a class. It exists because the middleware chain is built from a list of
|
|
wrapped callables (`functools.reduce`-style composition); a Protocol class
|
|
would not buy anything over the alias and would make the middleware
|
|
constructor signatures noisier.
|
|
|
|
`RpcCallback` exists separately from `RpcCaller` for one remaining reason:
|
|
it is a **keyword-only callback** passed into `register_file_source`, and
|
|
keeping it as a structural Protocol (instead of a bare `Callable[...]` alias)
|
|
lets mypy flag keyword-name typos at the call site.
|
|
|
|
### Choosing a name in new code
|
|
|
|
- New pure-RPC feature API? Type the dependency as
|
|
**`RpcCaller`** from `_runtime/contracts.py`. This is the shared capability
|
|
Protocol; see [`docs/architecture.md`](./architecture.md) for the protocol
|
|
catalogue. The concrete `RpcExecutor` and `NotebookLMClient` satisfy it
|
|
structurally.
|
|
- New middleware? Use **`NextCall`** from `_middleware/core.py` for the chain
|
|
callable — do not invent a new alias.
|
|
- New feature that takes the RPC entrypoint as a **keyword argument** at call
|
|
time? Define a local Protocol named **`RpcCallback`** so the keyword-typo
|
|
detection kicks in at every call site.
|
|
|
|
> **Why not collapse the last callback too?** `SourceUploadPipeline` accepts
|
|
> `rpc_call=` as a keyword override inside `register_file_source(...)`; keeping
|
|
> a callable Protocol there preserves mypy's keyword-name checking at the
|
|
> override seam. Ordinary constructor-injected feature services should use
|
|
> `RpcCaller`.
|
|
|
|
This convention is guarded by
|
|
`tests/_guardrails/test_no_legacy_rpc_callable_aliases.py`: `RpcCall` and `ShareRpc`
|
|
must stay deleted, and `RpcCallback` must stay local to `_source/upload.py`.
|
|
|
|
---
|
|
|
|
## 3. Metrics method verbs (CC5)
|
|
|
|
`ClientMetrics` exposes two verb families. They have different threading and
|
|
back-pressure contracts; the verb is the contract.
|
|
|
|
### `record_X` — sync, mutates counter state under a lock
|
|
|
|
`record_X` methods are **synchronous**, take a numeric measurement (typically
|
|
seconds), and update an in-memory `ClientMetricsSnapshot` field under
|
|
`_metrics_lock`. They never call user code, never schedule tasks, and never
|
|
block on I/O. Callers do not need to be inside an event loop.
|
|
|
|
Examples (all on `ClientMetrics`):
|
|
|
|
- `record_rpc_queue_wait` — time waiting for the RPC semaphore.
|
|
- `record_upload_queue_wait` — time waiting for the upload semaphore.
|
|
- `record_lock_wait` — time waiting on `_reqid_lock` (or a similar internal
|
|
lock).
|
|
|
|
Shared backend: `ClientMetrics._record_wait` (private; the three public
|
|
methods are typed wrappers around it).
|
|
|
|
> Upload queue waits are recorded through the `ClientMetrics` instance injected
|
|
> into the upload pipeline. The verb stays `record_*` because the contract is
|
|
> still sync + lock-protected counter mutation.
|
|
|
|
### `emit_X` — async, fires the user-supplied callback
|
|
|
|
`emit_X` methods are **`async def`** and `await` the user-configured telemetry
|
|
callback (`on_rpc_event=...`). They are the back-pressure seam: the awaited
|
|
callback can hold up the calling RPC if it does I/O, and that is intentional
|
|
(rate-limit feedback flows backwards into the producer).
|
|
|
|
Examples:
|
|
|
|
- `ClientMetrics.emit_rpc_event` — awaits the `on_rpc_event` callback with a
|
|
`RpcTelemetryEvent` payload; swallows + logs callback exceptions so a
|
|
misbehaving callback can't corrupt the RPC path.
|
|
|
|
Exceptions inside the callback are caught and logged (observability must not
|
|
alter behavior), but the `await` itself is load-bearing — *don't* fire-and-forget
|
|
the callback with `asyncio.create_task(...)`, because that would defeat the
|
|
back-pressure contract.
|
|
|
|
### Choosing a verb in new code
|
|
|
|
- The new method updates a counter / gauge / histogram bucket synchronously?
|
|
Use **`record_X`** and document the unit (seconds, bytes, count).
|
|
- The new method dispatches an event to user code (callback, queue, log
|
|
sink) and the producer should `await` it? Use **`emit_X`** and make it
|
|
`async def`.
|
|
- If both apply (record *and* emit), do them as two calls: `record_*` first
|
|
(cheap, lock-protected), then `await emit_*` (potentially slow, can raise —
|
|
though `ClientMetrics.emit_rpc_event` swallows). Keep the verbs separate so
|
|
the contract at each call site stays one line of code.
|
|
|
|
---
|
|
|
|
## 4. Historical-work vocabulary (glossary)
|
|
|
|
Comments, ADRs, `docs/refactor-history.md`, and `CLAUDE.md` use a handful of
|
|
recurring labels to reference past refactoring work. They are accurate but
|
|
opaque without a key. When you write a *new* comment, prefer a one-line
|
|
behavioral rationale over one of these labels — but when you read an existing
|
|
one, this is what it means:
|
|
|
|
| Token | Meaning |
|
|
|-------|---------|
|
|
| **`#NNNN`** | A GitHub pull request or issue number in `teng-lin/notebooklm-py`, e.g. <https://github.com/teng-lin/notebooklm-py/pull/1205>. Bare numbers are not clickable in most renderers; resolve them on GitHub. |
|
|
| **ADR-NNNN** | An Architecture Decision Record under [`docs/adr/`](./adr/) (zero-padded, e.g. ADR-0014 -> `docs/adr/0014-*.md`). The authoritative source for a design decision and its trade-offs. |
|
|
| **Wave N** | A sequenced step of a larger multi-PR migration (e.g. the session-decoupling / host-protocol-removal effort). Waves are *planning* units, not releases; the durable record of what a wave did is the ADR it closed and the merged PRs. |
|
|
| **Phase N** | A coarser planning grouping than a Wave, used by some older migrations. |
|
|
| **Tier N** | Yet another planning grouping (e.g. "tier-12-13" migration); interchangeable with Phase in intent. |
|
|
| **`Pn.Tn`** (e.g. `P3.T0`) | "Phase *n*, Task *n*" task codes from a numbered refactor plan; used in `CLAUDE.md` to date module renames. |
|
|
| **`.sisyphus/plans/…`** | Internal planning notes that are **not checked into the repo**. References to them are unfollowable by outside readers; treat them as provenance only. New code should cite the public ADR or doc instead. |
|
|
|
|
When historical labels are the *only* explanation for why code exists, that is a
|
|
smell — promote the reasoning into a plain-language comment or an ADR so the
|
|
rationale survives without the label.
|
|
|
|
## Related documents
|
|
|
|
- [`docs/architecture.md`](./architecture.md) — the current runtime graph,
|
|
capability Protocols, and the `RpcCaller` catalogue entry.
|
|
- [`docs/development.md`](./development.md) — contributor on-ramp; this
|
|
conventions doc is linked from its "Key Design Decisions" section.
|
|
- The architecture audit that motivated this catalogue lives in internal
|
|
planning notes that are not checked into the repo. Future codebase audits
|
|
with naming-convention findings should
|
|
extend this document rather than spawn parallel tiebreaker files.
|