Skip to content

Try to find a better API for notary - #455

Draft
Carreau wants to merge 5 commits into
jupyter:mainfrom
Carreau:main
Draft

Try to find a better API for notary#455
Carreau wants to merge 5 commits into
jupyter:mainfrom
Carreau:main

Conversation

@Carreau

@Carreau Carreau commented Aug 12, 2026

Copy link
Copy Markdown
Member

Notably, I think the main error of notary is to open the store at creation and not make it obvious it needs to be closed.

We can't break user too much, and give a progressive migration path.

Originally I wanted the notary context manager to return a new object (which I do here with Claude), returning a Session object which does open and close its store.

The tough part:

  • how to make the migration easy ?
  • are we ok opening/closing the store often ?
  • Do we allow nested context managers, to not re-close the store, or something else ?

#456 should be merged first as it's pure refactor to make it easier to review the code in depth here, right now I'm more trying to think at high level API and concepts/guaranties we want

@Carreau Carreau changed the title fix steps to skip Try to find a better API for notary Aug 12, 2026
claude added 4 commits August 12, 2026 11:21
`compute_signature`, `mark_cells`, `_check_cell` and `check_cells` do not
touch the signature store: they only need the notebook, and in one case
the secret and the digest. Move their bodies to private module-level
functions and have `NotebookNotary` delegate, so the logic is available
without a notary.

Pure refactor: no public name is added or removed, no behaviour changes,
and the test suite is untouched. `check_cells` still dispatches through
`self._check_cell` so subclasses overriding it keep working.
Signing needs an open signature store, and nothing in the API said whose
job it was to close it. Give that job to an object:

    with notary.open_session() as session:
        session.sign(nb)

A session owns the store it opens and closes it when the block exits, so
the store's lifetime is the session's lifetime. `open_session()` is a
context manager, so the session has no public `close()` -- it is not
something you can keep alive past the block. Each call opens its own
store, so sessions are independent: nestable, sequential, or per-thread.

The notary keeps its configuration (secret, algorithm, store_factory) and
becomes the thing that hands out sessions. Its own signing methods keep
working unchanged, delegating to a session over the shared store, and
still honour a subclass's compute_signature override.

`NotebookNotaryContext` stays the protocol it was in 5.11; both classes
satisfy it structurally. `TrustNotebookApp` signs through a session,
taking it as a required keyword-only argument, and docs/api.rst
documents the new API.
The notary opened its store in `__init__` and closed it in `close()`,
which made a closed notary unusable and made `with notary:` a one-shot.
Create the store lazily instead, and split the two kinds of close:

- `close()` is the caller saying they are done. The store is closed and
  not re-created: the direct API then raises RuntimeError pointing at
  open_session(). Sessions are unaffected, since they own their stores.
- `_close_store()` is the internal close, used by `__exit__`. It releases
  the store but leaves the notary working, so code that used the notary
  before it was a context manager keeps working after a block exits.

A store assigned to `notary.store` by the caller belongs to the caller
and is never closed by either path. Store creation takes a lock, and a
failed `store.close()` puts the store back rather than dropping it.

Because the store is no longer opened in `__init__`, `jupyter trust
--reset` no longer opens the database it is about to delete, and
`compute_signature`/`mark_cells`/`check_cells` open nothing at all.
Signing through the notary itself leaves its store open until someone
remembers to call close(), which is what sessions exist to fix. Warn
once per notary, both when the signing methods are called directly and
when the notary is used as a context manager, and point at
`open_session()` in both cases.

The warning is a FutureWarning rather than a PendingDeprecationWarning:
nothing here is scheduled for removal, and the notary's own methods stay
supported, so the message recommends rather than announcing a
deprecation. FutureWarning is also shown by default, and since it fires
once per notary that is one line per process, not one per notebook.

Using the notary as a context manager is included in the nudge: it only
shipped in 5.11, so there is little code to move and moving it now is
cheaper than letting more accumulate.
Add versionadded/versionchanged directives to the docstrings and to
docs/api.rst:

- 5.11 for what shipped in that release: `close()`, `__enter__`/`__exit__`
  and the `NotebookNotaryContext` protocol (all from jupyter#436).
- 5.12 for what is new here: `NotarySession` and `open_session()`, plus
  the behaviour changes to `close()`, `store`, the context manager, and
  the three direct signing methods that now warn.

5.12 is the assumption that the next release is a minor one; if it ships
as something else these need updating.
@Carreau
Carreau requested a review from krassowski August 12, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants