Skip to main content

pytruenas

PyPI version Python versions Documentation License: MIT

A typed, Pythonic client and CLI for the TrueNAS middleware API.

pytruenas speaks the middleware's JSON-RPC 2.0 websocket protocol directly — over wss:///ws:// to a remote host, or over the local unix socket (ws+unix://) when running on the NAS itself. It exposes the whole API surface through an attribute-style namespace, adds convenience helpers for the common create/update/upsert patterns, a remote-filesystem abstraction, an optional typings generator, and a small CLI for scripting host configuration.

Running commands and reading files is delegated to hostctl: a client is a hostctl host, so run() and path() pick whichever transport a target actually offers rather than assuming one.

Install

pip install pytruenas

Optional extras:

Extra Enables
pytruenas[ssh] SSH commands + the SFTP filesystem leg (asyncssh)
pytruenas[config] YAML config/targets file for the CLI (pyyaml)
pytruenas[codegen] generate-typings command (jinja2)
pytruenas[repo] deploy --source repo (pathspec, tomli below 3.11)

Quickstart

from pytruenas import TrueNASClient

# Remote host (api key, or "user:password", or a token)
client = TrueNASClient("nas.example.com", "1-<64-char-api-key>", sslverify=False)

# Attribute-style access to any API namespace/method:
for user in client.api.user.query():
    print(user["username"])

# Convenience helpers for common DB patterns:
client.api.user._upsert(("username",), username="svc", full_name="Service", group_create=True)

# Running on the NAS itself talks to the local unix socket, no auth:
local = TrueNASClient()            # ws+unix:///var/run/middleware/middlewared.sock
print(local.api.system.info())

Commands and files

run() and path() are inherited from hostctl, which selects a transport rather than assuming one:

client = TrueNASClient("nas.example.com", api_key, shell="ssh://root@nas.example.com")

client.run("zpool status", capture_output="stdout", encoding="utf-8").stdout
client.path("/mnt/tank/notes.txt").read_text()

client.capabilities      # {"run", "path", ...} -- what this target can actually do
client.last_selection    # which transport served the last run(), and why
target commands files
on the NAS local (plain subprocess) local
remote, SSH configured ssh, then webshell sftp, then tnasws
remote, no SSH webshell tnasws

webshell runs commands over /websocket/shell — the same PTY the web UI's Shell page uses — so a host reachable on the API port but not on 22 (NAT, a firewall allowing only 443) can still run commands. Name providers explicitly with executor= / path= to force or exclude one.

Output is exact: commands and input travel base64-encoded and output is framed by markers the command prints, so the terminal's echo, prompt and banner never reach your result. Uncaptured stdout streams live; stderr is captured separately (delivered when the command ends); input=/stdin= are delivered through a temporary file, never typed into the terminal.

Credentials

TrueNASClient(target, credentials) accepts, for the second argument:

  • an API key string "<id>-<64 chars>",
  • "user:password" (optionally "user:password\n<otp>"),
  • a token string,
  • a (user, password) tuple,
  • None / omitted → local socket auth.

Credentials.from_env() reads TN_CREDS. Credentials may also travel in the target (wss://root:secret@nas). In a URI an OTP follows the password after a newline as a keyed line — otp:<token>, with the newline percent-encoded: wss://root:secret%0Aotp:123456@nas. In the plain credential string above the token follows the newline on its own ("user:password\n123456").

A password containing /, ? or # must be percent-encoded (%2F, %3F, %23); a raw one is refused, because the URL parser would otherwise read the rest of it as a host, port and path.

CLI

pytruenas --help
pytruenas query user -f username=root nas.example.com
pytruenas call system.info nas.example.com          # any method by dotted name
pytruenas dump-api nas.example.com > api.json
pytruenas generate-typings --path typings --api-version v26.0.0 nas.example.com
pytruenas deploy nas.example.com                    # install pytruenas ON the host

The target host(s) are the trailing positional arguments — a command's own positionals (like query's namespace) come first, then the hosts. Each target may be comma-separated and supports [A-Z]/[0-9] range expansion (e.g. 'nas[1-3].example.com'); with no target the command runs against localhost. --parallel N runs several targets concurrently. Filter query with -f/--filter KEY=VALUE (repeatable).

Running on the appliance

TrueNAS has a read-only root and no pip, so deploy bootstraps instead: it asks the target which distributions it already has, bundles only the difference, and copies that over. In practice that is five pure-Python packages under a megabyte — the appliance already ships requests, websocket-client, pyyaml, asyncssh and jinja2.

pytruenas deploy nas.example.com                 # a single executable .pyz
pytruenas deploy --mode dir nas.example.com      # an unpacked bin/ + lib/ tree
pytruenas deploy nas.example.com -- call system.info   # install, then run there

Everything after -- runs on the target once it is installed. The default destination is under /var/db/system, which is a dataset on a data pool and so survives an update — unlike /var/db itself, /root or /data, which live in the boot environment and are replaced by one. A digest is recorded beside the payload, so redeploying verifies instead of re-copying; --force overrides. An existing --path that a previous deploy did not create is refused rather than replaced.

When pytruenas is a dependency of your own tool rather than the thing being deployed, name yours as the root: --pkg-root mytool (or PYTRUENAS_PKG_ROOT), and your package ships with pytruenas bundled underneath it.

--source repo ships a working tree as-is instead of an installed dependency closure — the files a clone would have, filtered by whichever of .gitignore, .ignore and .bundleignore are present, with nothing needing to be installed locally first (needs pytruenas[repo]). It requires --mode dir: a zipapp needs an importable package root, which a working tree does not have.

pytruenas deploy --source repo --mode dir --repo-root . nas.example.com

Typings generator

generate-typings turns a host's API definition into a package of .pyi stubs so editors and type checkers understand client.api.<namespace>.<method>(...). It is validated against the full real API (every version in a live dump).

pytruenas generate-typings --path truenasapi_typings/current nas.example.com

Development

py -3.14 -m venv .venv/3.14-nt-amd64
.venv/3.14-nt-amd64/Scripts/python -m pip install -e ".[dev,ssh,config,codegen]"
.venv/3.14-nt-amd64/Scripts/python -m pytest

Supports Python 3.9+. The run() tests need a POSIX shell and skip on Windows, so verify anything touching command or path dispatch on a real target.

License

MIT — see LICENSE.

Release files for pytruenas 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytruenas 0.5.1
File Size Uploaded
pytruenas-0.5.1.tar.gz 351.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytruenas 0.5.1
File Interpreter ABI Platform
pytruenas-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 554.1 kB

Release files / pytruenas-0.5.1.tar.gz

Download URL pytruenas-0.5.1.tar.gz
Size 351.4 kB
Tags Source
SHA-256 checksum
How to use checksums
68a85af1a3f5315441073e2b4e9a36a930fc60d1c572ed70b155ee89588ebe53
BLAKE2b-256 checksum
How to use checksums
98c56045d0911c4e18ef1307c6e5bf50be5d610a0b5610cbe3cb5758eaad3e78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / pytruenas-0.5.1-py3-none-any.whl

Download URL pytruenas-0.5.1-py3-none-any.whl
Size 202.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
07cb317f0d37b87ebec39dc2957d89388e07761b857b9f6a1ad3c3f758eb9794
BLAKE2b-256 checksum
How to use checksums
03276756429a88f5016f9df51398602f7d9a624efbec5e1fde52774b2c174f2c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.2

2 release files

This release

0.5.1 This release

2 release files

0.5.0

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page