# Writing a plugin ```bash noodlelab new-plugin thermo # scaffolds noodlelab-thermo/ cd noodlelab-thermo git init && git add -A && git commit -m "First version" uv pip install -e . # into the environment noodlelab runs in noodlelab check-plugin . # the standard: manifest, imports, permissions, nodes, examples ``` The scaffold is a complete plugin: `pyproject.toml` (the version comes from git tags), the manifest, two nodes, an example graph, a test, and a release workflow that checks it and publishes signed wheels. ## Nodes A node is a type-hinted function with `@node`. The hints decide its sockets, units and widgets, and its docstring is the help text people and agents read: ```python from typing import Annotated from noodlelab.plugin import Param, Quantity, node @node(category="Thermodynamics") def isentropic_efficiency( h_in: Quantity["kJ/kg"], h_out: Quantity["kJ/kg"], h_out_isentropic: Quantity["kJ/kg"], ) -> float: """Efficiency of a turbine stage: actual over ideal enthalpy drop.""" return float((h_in - h_out) / (h_in - h_out_isentropic)) ``` Uncertainty propagates through a node without any code in it. A node never changes its inputs in place: arrays arrive read-only, tables and lists as copies of their own. ## The public API Import noodlelab only through {mod}`noodlelab.plugin`: | Module | What | |---|---| | `noodlelab.plugin` | `node`, `Param`, `Quantity`, `RunContext`, `FileRef`, `Probe`, `Uncertain`, `Check`, `warning`, `error`, `warn`, `register_*`, `require` | | `noodlelab.plugin.units` | the unit registry (`ureg`), `is_quantity`, `magnitude`, `parse`... | | `noodlelab.plugin.uncertainty` | making and reading values with uncertainties | | `noodlelab.plugin.about` | units and descriptions of table columns | | `noodlelab.plugin.tables` | columns as arrays, table attributes | | `noodlelab.plugin.expr` | checking expressions typed into widgets before evaluating them | | `noodlelab.plugin.constants`, `.figures`, `.studies`, `.math` | constants, saving figures, parameter studies, Typst maths | `warning` and `error` are what a node's `.check` returns before a run; `warn("...")` is for what a node finds while it runs (a matrix close to singular, a fit the data cannot pin down): the run goes on, and the warning is shown on the node and kept in the run's provenance. The executor also warns by itself when a node returns a value that is not a number (NaN or infinite). A node that judges its result returns a `Check` among its outputs (see the Checks pack): it is reported like Expect Value's, without stopping the run. Everything else in noodlelab is internal and may change in any release. `noodlelab.plugin.API_VERSION` is the version of this contract; a plugin states the one it was written for in its manifest, and noodlelab refuses, before running any of its code, a plugin written for a version it does not speak. To use another plugin's helpers, import its public (not `_`-prefixed) modules and declare it in `requires`. Optional dependencies are checked at import with `noodlelab.plugin.require("noodlelab-thermo[steam]", "CoolProp")`, which fails with the install command instead of an ImportError deep in a run. ## The manifest `src//noodlelab-plugin.toml`, shipped inside the package: ```toml name = "thermo" # = the entry point name = the node id prefix title = "Thermodynamics" description = "Steam tables, cycles and heat exchangers" api = 1 # the noodlelab.plugin API it was written for requires = { maths = ">=1.2,<2" } # other plugins, with version ranges ("*": any) permissions = [] # see below examples = ["examples/rankine.graph.json"] templates = ["reports/datasheet"] # report templates: folders with template.typ homepage = "https://github.com/you/noodlelab-thermo" license = "MIT" ``` The version is not in the manifest: it comes from the git tag. **Permissions** declare what the plugin does besides computing on its inputs and reading and writing files in the workspace: `network`, `subprocess`, `filesystem:home`, `filesystem:any`, `native` (ctypes/cffi) and `env`. `check-plugin` and the index read the code and refuse a version that does more than it declares, and admins decide whether to grant them. ## Versions Plugins follow semantic versioning: - **major** (2.0.0): a node id, socket, unit or meaning changed, so saved graphs may break; - **minor** (1.3.0): new nodes or options; - **patch** (1.2.1): fixes. A saved graph records the plugins it uses as `>=,`. When a node's results change (a corrected formula), also raise its own `@node(version=N)`: that invalidates cached results computed by the old code. ## `check-plugin` `noodlelab check-plugin .` checks everything an index checks, before you publish: - the manifest, and that its name is the entry point's; - the plugin API; - imports: only `noodlelab.plugin`, its own package, the plugins it requires and third-party libraries; - permissions: nothing it does that it does not declare (and what it hides, like `eval` or dynamic imports, is pointed out for a reviewer); - the nodes, as strict mode judges them; - the example graphs, which must run; - at a tag, that the package builds that version.