From 5f223605cdf943a8daa48872281d1a240729dec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Snoksrud?= Date: Thu, 18 Jun 2026 10:25:23 +0200 Subject: [PATCH 1/2] docs: document API-error exceptions and request timeouts Add a README "Error handling" section covering the two newest changes: - PR #236: writes now raise NovemException (or a subclass) carrying the server message instead of silently printing a placeholder. Documents the exception hierarchy (all importable from novem.exceptions) and the create PUT 409 no-op for objects that already exist. - PR #240: requests now time out instead of hanging, with the default (10s, 2min) and job.run() (30s, 30min) values and the resulting requests.exceptions.Timeout. --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 0d1fe1c..814d12b 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,48 @@ the plot in real time. +## Error handling +Every novem operation talks to the platform over the network, so any call can +fail — a rejected token, a missing resource, or a value the server won't accept. +When a read or write fails, novem raises a `NovemException` (or a more specific +subclass) carrying the server's error message, rather than failing silently. + +```python +from novem import Plot +from novem.exceptions import NovemException, Novem403 + +try: + # the constructor creates the plot (a PUT) unless create=False, so this + # call can raise too — not just the writes that follow it + plot = Plot("my-plot") + plot.type = "line" +except Novem403: + print("not permitted to write this plot") +except NovemException as e: + # the server's message (plus any rejected lines) is on the exception + print(f"novem rejected the write: {e}") +``` + +All exceptions are importable from `novem.exceptions` and inherit from +`NovemException`, so catching the base class catches them all: + + * `NovemException` — base class; raised for any otherwise-unclassified API error + * `Novem401` — the supplied token was rejected by the server + * `Novem403` — authenticated, but not permitted to perform the operation + * `Novem404` — the resource does not exist + * `NovemAuthError` — no usable token could be resolved locally (distinct from + `Novem401`, which is a server-side rejection of a token that *was* sent) + +Creating an object that already exists is not an error — the create PUT returns +a 409, which is treated as a no-op rather than raised. + +### Timeouts +novem requests time out instead of hanging indefinitely: every call uses a +default `(10s connect, 2min read)` timeout. Job execution (`job.run()`) can +legitimately take longer and allows up to `(30s connect, 30min read)`. A request +that exceeds its timeout raises `requests.exceptions.Timeout`. + + ## Contribution and development The novem python library and platform is under active development, contributions or issues are most welcome. From f90fa60a5c4b9bc205a4ec7df77a8effb0da1e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Snoksrud?= Date: Thu, 18 Jun 2026 10:32:14 +0200 Subject: [PATCH 2/2] docs: README style pass for docs-site conventions Align the README with the docs-site style guide: direct prose, em-dashes used sparingly (not paired twice a sentence), Oxford-spec punctuation, complete sentences, and Note: callouts. - NB: -> Note: on the two inline callouts - fix two comma splices and the "already exist" typo - add Oxford commas; recast the paired-em-dash asides as parentheses/sentences - keep single, well-placed em-dashes where they earn their place --- README.md | 57 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 814d12b..a652a73 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ # novem - data visualisation for coders A wrapper library for the novem.io data visualisation platform. Create charts, -documents, e-mails and dashboards through one simple API. +documents, e-mails, and dashboards through one simple API. -**NB:** novem is currently in closed alpha, if you want to try it out please +**Note:** novem is currently in closed alpha. If you want to try it out, please reach out to hello@novem.io ## Examples -Create a linechart from a pandas dataframe (assumes a configured profile — -see "Getting started" below). +Create a linechart from a pandas dataframe (this assumes a configured profile, +described under "Getting started" below). ```python import numpy as np @@ -59,7 +59,7 @@ These are resolved, in order of precedence, from: 4. the config file written by `python -m novem --init` The simplest setup is the config file (`--init` above). To configure novem -programmatically instead — handy in notebooks, scripts or CI — set a token on +programmatically instead (handy in notebooks, scripts, or CI), set a token on the global `novem.config` object once, and objects created afterwards pick it up automatically: @@ -82,9 +82,9 @@ plot = novem.Plot("my-plot", token="your-token") ``` ### Multiple accounts / profiles -A `Session` captures connection settings (token, api_root or a config-file -profile) and constructs objects bound to them, without touching the global -defaults — useful when working against several accounts at once: +A `Session` captures connection settings (token, api_root, or a config-file +profile) and constructs objects bound to them without touching the global +defaults — useful when you work against several accounts at once: ```python import novem @@ -105,7 +105,7 @@ novem package `from novem import Plot`. The `Plot` class takes a single mandatory positional argument, the name of the plot. * If the plot name is new, the instantiation of the class will create the plot. - * If the plot name already exist, then the new object will operate on the + * If the plot name already exists, then the new object will operate on the existing plot. In addition to the name, there are two broad categories of options for a @@ -117,7 +117,7 @@ plot: data and config. * Titles/captions/names/colors/legends/axis etc -There are two ways to interact with the plots, one can either supply all +There are two ways to interact with the plots: you can either supply all the necessary options as named arguments when creating the plot, or use the property accessors to modify them one by one (this is more helpful when working with the plot interactively). Below is an example of both approaches. @@ -171,7 +171,7 @@ df.pipe(line) ``` -**NB:** All novem plot operations are live. +**Note:** All novem plot operations are live. This means that as soon as you write to or modify any aspects of the plot object, those changes are reflected on the novem server and anyone watching the plot in real time. @@ -180,9 +180,9 @@ the plot in real time. ## Error handling Every novem operation talks to the platform over the network, so any call can -fail — a rejected token, a missing resource, or a value the server won't accept. +fail: a rejected token, a missing resource, or a value the server won't accept. When a read or write fails, novem raises a `NovemException` (or a more specific -subclass) carrying the server's error message, rather than failing silently. +subclass) carrying the server's error message instead of failing silently. ```python from novem import Plot @@ -190,34 +190,35 @@ from novem.exceptions import NovemException, Novem403 try: # the constructor creates the plot (a PUT) unless create=False, so this - # call can raise too — not just the writes that follow it + # call can raise too, not only the writes that follow it plot = Plot("my-plot") plot.type = "line" except Novem403: print("not permitted to write this plot") except NovemException as e: - # the server's message (plus any rejected lines) is on the exception + # the exception carries the server message and any rejected lines print(f"novem rejected the write: {e}") ``` -All exceptions are importable from `novem.exceptions` and inherit from +Every exception is importable from `novem.exceptions` and inherits from `NovemException`, so catching the base class catches them all: - * `NovemException` — base class; raised for any otherwise-unclassified API error - * `Novem401` — the supplied token was rejected by the server - * `Novem403` — authenticated, but not permitted to perform the operation - * `Novem404` — the resource does not exist - * `NovemAuthError` — no usable token could be resolved locally (distinct from - `Novem401`, which is a server-side rejection of a token that *was* sent) + * `NovemException`: the base class, raised for any otherwise-unclassified API + error. + * `Novem401`: the server rejected the supplied token. + * `Novem403`: the token is valid but lacks permission for this operation. + * `Novem404`: the resource does not exist. + * `NovemAuthError`: no usable token could be resolved locally. This is distinct + from `Novem401`, which is the server rejecting a token that was sent. -Creating an object that already exists is not an error — the create PUT returns -a 409, which is treated as a no-op rather than raised. +Note: creating an object that already exists is not an error — the create PUT +returns a 409, which novem treats as a no-op rather than raising. ### Timeouts -novem requests time out instead of hanging indefinitely: every call uses a -default `(10s connect, 2min read)` timeout. Job execution (`job.run()`) can -legitimately take longer and allows up to `(30s connect, 30min read)`. A request -that exceeds its timeout raises `requests.exceptions.Timeout`. +novem requests time out instead of hanging indefinitely. Every call uses a +default timeout of `(10s connect, 2min read)`. Job execution (`job.run()`) can +take far longer and uses `(30s connect, 30min read)`. A request that exceeds its +timeout raises `requests.exceptions.Timeout`. ## Contribution and development