The abstractions you keep rewriting, written once. rna packages the design patterns and polymorphism helpers every grown codebase re-invents — strategy, backend, composite, decorator, singleton, switch, attribute links, and Storable/Plottable mixins that turn one _save_<ext> method into full save/load dispatch. On top of that base it ships the proof the patterns carry weight: one plotting API over matplotlib and pyvista, and configuration that layers. The core has no required dependencies at all.
import numpy as np
import rna.plotting
vertices = np.array([[0.0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]])
faces = np.array([[0, 1, 2], [0, 2, 3]])
axes = rna.plotting.add_subplot(dim=3) # matplotlib, pyvista, ...
artist = rna.plotting.plot_mesh(axes, vertices, faces, color=[0.2, 0.8], cmap="plasma")
rna.plotting.set_colorbar(axes, artist)
rna.plotting.save("mesh.png")
That is the whole program on either backend, and it is rna’s own patterns at work: the renderer behind those verbs is a strategy, so rna.plotting.use("pyvista") swaps it without touching a line, and the default — "auto" — makes the choice itself once a mesh gets big enough to need a GPU.
Why rna
Patterns, tested once. Strategy, backend, composite, decorator, singleton, switch and symlink-like attribute links. The classic design patterns live in books and blog posts — as code you are expected to retype — and rna is the package that ships them instead: base classes with doctests, in place of the fourth hand-rolled registry in your codebase.
Polymorphism you declare in one method. Subclass Storable, implement _save_json, and your object has save/load with extension dispatch, path expansion and in-memory buffers; the same move gives any class a backend-agnostic plot. Try finding that on PyPI — this is the piece rna exists for.
One plotting API is the patterns’ proof. matplotlib is right until a mesh has a hundred thousand faces, and then it is very wrong. Built on the strategy pattern above, rna puts both it and pyvista behind fifteen verbs (add_subplot, plot_mesh, set_colorbar, save, …), so switching is a one-line decision instead of a rewrite. Those fifteen names are the package’s semver promise — see the plotting guide.
Nothing you did not ask for. The core installs zero dependencies. numpy, matplotlib, pydantic, hydra and pyvista each live behind an extra, so a library that only wants rna.path and rna.log pays for neither a plotting stack nor a settings framework.
Configuration that actually layers. Packaged defaults, then a user file, then environment variables, validated by pydantic, with ${...} references resolved across all three. And, deliberately, without pinning antlr4 the way hydra does.
Features
rna.pattern — reusable design-pattern implementations: strategy, backend, composite, decorator, singleton, switch, attribute links
rna.polymorphism — Storable and Plottable mixins: implement _save_<ext> and get extension dispatch, save/load and buffers for free
rna.plotting — a multi-backend plotting facade: matplotlib, pyvista, or an automatic choice between them behind one API
rna.config — layered, typed package settings on pydantic-settings, plus composed application config for hydra-based CLIs (rna.config.app)
rna.log — colour-formatted logging with an optional tqdm progress bar
rna.path — pathlib-based path utilities that resolve ~, symlinks and ..
rna.parsing — argparse parent parser that wires up logging from the command line
rna.process — run a shell command with its output streamed into a logger
Installation
pip install rna
The core is dependency-free; each subpackage that needs more declares an extra:
Extra |
Pulls in |
Needed for |
|---|---|---|
rna[plotting] |
numpy, matplotlib, more-itertools |
rna.plotting |
rna[pyvista] |
pyvista (and VTK, ~68 MB) |
the GPU plotting backend |
rna[config] |
pydantic, pydantic-settings |
rna.config |
rna[app] |
hydra-core, omegaconf |
rna.config.app |
rna[categorical] |
scikit-learn |
categorical plot colours |
rna[full] |
all of the above |
everything |
Usage
import json
import rna.polymorphism
class Run(rna.polymorphism.Storable):
def __init__(self, samples):
self.samples = samples
def _save_json(self, path, **kwargs):
with open(path, "w") as file_:
json.dump(self.samples, file_)
@classmethod
def _load_json(cls, path, **kwargs):
with open(path) as file_:
return cls(json.load(file_))
Run([1, 2, 3]).save("~/runs", "2026-08", "latest.json") # dispatch on the extension,
run = Run.load("~/runs/2026-08/latest.json") # "~" resolved, parents created
One method per format is all you write; save/load grow extension dispatch, path resolution, buffers and a NotImplementedError that names the method missing for a format you did not implement.
The usage guide walks through every subpackage with runnable examples; the API reference has the details.
Resources
Documentation: https://dboe.gitlab.io/rna
Source code: https://gitlab.com/dboe/rna
Bug reports: https://gitlab.com/dboe/rna/-/issues/
Changelog: https://gitlab.com/dboe/rna/-/blob/master/CHANGELOG.md
Contributing
pixi run help lists every task in the repository; pixi run test runs the suite and pixi run docs builds and opens these pages. The contributing guide covers the branching model, how a release is cut, and what the pipeline checks before a merge.
License
MIT — see LICENSE.rst.
Release files for rna 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rna-1.0.0.tar.gz | 530.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rna-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 625.2 kB
Release files / rna-1.0.0.tar.gz
| Download URL | rna-1.0.0.tar.gz |
|---|---|
| Size | 530.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
942a82f30b23e7bd5bd3be5c4e2ce928c47a56646910038ff779d86c6a0d0db2
|
|
BLAKE2b-256 checksum How to use checksums |
5d81cb2ff8896407eb6e6b500fece83dcbd0c458f0b7e3b545dd26d04e7d929c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.0 {"installer":{"name":"uv","version":"0.11.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / rna-1.0.0-py3-none-any.whl
| Download URL | rna-1.0.0-py3-none-any.whl |
|---|---|
| Size | 94.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4709de0db61733a80bb1f6881d70bb6baa91c978feda1700a7418dabe4b2d72c
|
|
BLAKE2b-256 checksum How to use checksums |
4f51a3fe442e0e00087de5e07c1b71845c22cae559f03de7f47dbcc9bcb3a1fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.0 {"installer":{"name":"uv","version":"0.11.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|