Skip to main content

reccy

Shared Python utilities for local command-line apps that need daemon control, Tyro-based configuration, subprocess launching, and reusable Pydantic validators.

The initial consumers are expected to be recs, showco, tuney, and lyte.

Included modules

  • reccy.protocol: IPC transports, RPC clients and servers, and JSONL stream compression.
  • reccy.services: per-user service definitions, rendering, paths, lifecycle control, and runner support for Linux systemd --user, macOS launchd, and Windows scheduled tasks.
  • reccy.configuration: Tyro helpers, settings persistence, unit-aware Pydantic numeric types, and reusable value validators.
  • reccy.runtime: logging and child-process utilities.
  • reccy.reccy: shared application lifecycle, status, settings, RPC, and service integration.
  • reccy.cli: first-token command routing and user-facing exception handling.
  • reccy.device: audio/MIDI candidate-name specifications and device-key helpers.

The previous flat module paths have been removed. Reccy code and consumers use the grouped module paths.

Application-specific recording, show control, audio, MIDI, lighting, web UI, and IPC payloads stay in the consuming projects.

See shared infrastructure features for atomic output, validated configuration edits, event connection completion, local resource claims and retry scheduling, including migration notes for consumer projects.

Tests

uv run pytest runs the unit-test suite with three work-stealing workers. Use uv run pytest -n 0 for single-process debugging, including interactive output with uv run pytest -n 0 -s. Use -n N to choose a different worker count.

IPC endpoints and lifecycle

IPC accepts filesystem socket paths as either Path or str. Local Windows named-pipe addresses, such as \\.\pipe\app, select the pipe transport regardless of the Python argument type. Serialized service metadata endpoints can therefore be passed directly to an RPC client.

ProtocolClient and EventClient are single-use after connecting; create a new client to reconnect. Both expose close() to release their local connection. ProtocolClient.shutdown() requests shutdown of the peer instead. Application messages are accepted only after hello, and event subscription startup has a one-second deadline. Set RPC request deadlines using rpc.Client(endpoint, timeout=...).call(...).

RPC server shutdown disconnects accepted clients, including incomplete handshakes. Application handlers already executing may finish; shutdown does not forcibly interrupt them.

RPC handlers return raw strings or dictionaries for success, and ipc.Error for failure. The top-level dictionary discriminator type="error" is reserved for protocol errors and must not be used in successful results. Errors require a string message; clients raise ConnectionError with that message. Application data describing an error can use a different discriminator or a nested object. This contract does not require a response envelope or a wire-version change.

Reccy.start() rejects an already-started application. close() is idempotent. RPC cleanup and on_closed() run even if startup, shutdown hooks, or status publication fail. Release application-owned resources in on_closed() and allow for partial startup there. Exceptions still propagate to the caller.

Set status_model to a ReccyStatus subclass to enable status persistence. The default snapshot constructs that model, and the service controller reads the same model. Override status_snapshot() when additional required fields need values. Status retains the latest 1,000 errors. Every error is also logged; older log history is subject to the configured log retention policy.

Service lifecycle results confirm manager command completion, not the running state (running=None). Call status() to observe the manager's current state. Installation requests startup on every platform. Failed or interrupted install and uninstall operations attempt to restore prior local files and manager state; rollback failures are attached to the original error. Manager operations cannot be made atomic, so an already-unloaded service may still require manager-specific attention. Linux reloads its manager after removing the unit file and before deleting metadata.

logging.configure(path=..., service_name=...) replaces root handlers and redirects stdout/stderr to the rotating file. Repeating the same path reuses the stream. Only one writer can hold a log path at a time; a second writer raises a resource claim conflict. The adjacent .lock file persists so the claim remains stable across process restarts. Without a path, existing handlers are preserved and only the log level changes.

Service installation contract

Installation requires a normal Python interpreter; frozen applications are rejected before any files are written. Definitions use the installing interpreter and run reccy.services.runner, which configures logging and runs the application module with the recorded arguments. Reinstall after changing interpreters.

Metadata version 1 describes the installed module, arguments, platform and endpoints. Clients may use its endpoints for discovery, but the runner does not load this file or inject endpoints into the application. Applications own their endpoint configuration and must agree with the installation metadata. Unsupported metadata versions fail validation.

An explicit home controls generated file paths and the working directory. On Windows it also overrides APPDATA/LOCALAPPDATA; those environment variables are used only when home is omitted. These paths do not change the service-manager account: installation still targets the current user.

Unit-aware configuration dumps

Authored quantities use Pint expressions, with exact rational magnitudes during conversion. Prefixes, plurals, unit-only values, and arithmetic such as 1 ms / 3 are supported. Angles, logarithmic gain, pitch intervals, information sizes, beats, frames, and ticks have separate semantic dimensions. dB and musical cents are authored coordinates, not implicit power or pitch ratios. Clock notation such as 1:30 remains supported for seconds-valued configuration fields.

runtime_dump() emits normalized numbers; authored_dump() retains authored unit strings. Both emit canonical field names, even when a model has aliases or enables alias serialization. revalidation_dump() is the Python-mode authored dump intended for rebuilding a model without losing provenance. Models using validation aliases must also accept field names to revalidate these dumps.

These helpers support ordinary models, nested models, lists and dictionaries. They reject RootModel and custom field/model or annotated serializers with TypeError, including in nested values. Use Pydantic's own dump methods when custom serialization is needed, without unit-provenance restoration.

collect_unit_provenance() returns JSON Pointer paths: /nested/delay and /history/0, with ~ escaped as ~0 and / as ~1. Dictionary keys must be strings. The empty path identifies a quantity passed directly to the collector.

named_choice_spec() (formerly prefix_spec) parses exact choice names, not prefixes. Formatting selects the first matching name in mapping order, so aliases for the same value are deterministic and formatted values parse back successfully.

Dictionary delta streams

reccy.protocol.jsonl.Compress and Decompress operate on dictionaries, not JSON text or line framing. Non-key fields that are absent or None are equivalent in full records: an initially null field is omitted; clearing a previously non-null field emits None, which decompression retains. Exact dictionary reconstruction is not promised. In an encoded delta, an absent field means unchanged.

Each codec keeps state per string key across calls. Consume batches in order and create fresh codec instances for every independent stream. A fresh decoder cannot reconstruct values omitted by a compressor continuing an earlier stream. Keys are not evicted automatically, because that could corrupt later deltas; discard the codec instance when its stream ends.

Child-process output

capture_stderr() drains in chunks of at most 4,096 bytes, splitting oversized lines. Its OutputTail retains at most 80 chunks of 4,096 characters. Callbacks receive those chunks, not necessarily complete lines; invalid or split UTF-8 is decoded with replacement. If a callback raises, capture stops calling it but continues draining before reporting the exception through threading.excepthook. After child exit, tail.wait(timeout) waits for capture completion and returns whether it finished. A blocking callback can still delay capture.

Use runtime.process for managed child lifetimes, bounded output tails and run_silent() (capture output, log it on failure, then re-raise). runtime.subprocess.run() is the configurable standard-library wrapper; app_command() chooses source versus frozen command arguments for ordinary application launches, not service installation.

Application lifecycle and extension points

Reccy is the application/daemon lifecycle base, not an event loop. A minimal application can publish status without enabling RPC or service installation:

from reccy.reccy import Reccy, ReccyStatus


class Application(Reccy):
    name = 'example'
    status_model = ReccyStatus


def main() -> None:
    application = Application()
    try:
        application.start()
        print(application.status_snapshot().model_dump())
    finally:
        application.close()

The caller owns the run loop and calls start/close serially. on_started() may allocate application resources; on_stopping() runs before shutdown publication; on_closed() releases resources even after partial startup. Cleanup must tolerate missing resources. Override status_snapshot(), rpc_command() and mutable attribute hooks only as needed. Set rpc_enabled=True to expose RPC, and provide service_spec separately for service-manager integration.

RPC commands execute concurrently in worker threads. Applications must protect their own shared state and coordinate shutdown with running handlers; closing the server disconnects clients but does not stop executing handlers. Event callbacks run on the event-reader thread, so they must not block that reader.

Controller health is the last saved status snapshot, not proof of current health. Inspect its updated_at using an application-appropriate freshness limit; the manager's running value is a separate observation.

API naming and ownership notes

  • cli_help performs a regression assertion and returns nothing. Its short name is retained for consumer tests. See the CLI help guide.
  • Jsonl, Compress and Decompress retain their existing names for consumers; they encode dictionary deltas as described above, not JSONL text or compression of arbitrary data.
  • ServiceSpec.socket_file is the generic control socket's relative path. Its existing gui.sock basename is retained to avoid changing endpoint discovery; it does not imply a GUI-only service.
  • ProtocolClient.shutdown() sends a peer-shutdown request; close() releases the local connection. Neither name is an alias for the other.
  • run_main() reports interruption on stderr and returns 130, distinct from successful completion.
  • linux_xdg_autostart() only renders a desktop entry. Its caller owns writing and removing that file. ServiceController manages systemd on Linux, not XDG autostart entries.
  • AudioMidiDeviceSpec stores candidate names; consumers own matching and ambiguity handling. device_key() prefers a supplied persistent ID but falls back to a display name, which is not guaranteed unique.
  • Control RPC has no request IDs or reply envelope; use raw results or ipc.Error. The unused ipc.Reply model has been removed.

Metadata

Release files for reccy 0.2.0

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

Source distribution (sdist)

Source distribution for reccy 0.2.0
File Size Uploaded Provenance Actions
reccy-0.2.0.tar.gz 129.7 kB Without provenance Details for reccy-0.2.0.tar.gz

Built distribution (wheel)

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

Total release size: 206.5 kB

Release files / reccy-0.2.0.tar.gz

Download URL reccy-0.2.0.tar.gz
Size 129.7 kB
Tags Source
SHA-256 checksum
How to use checksums
439bf95cc92d92faecac5237c879e89adb565a6a8d5ab0f8a0112b508acc296d
BLAKE2b-256 checksum
How to use checksums
788abddab2013a412f22b0c4d1cb78bd56c25b00ca6d33ee6db64b5d294e9257
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / reccy-0.2.0-py3-none-any.whl

Download URL reccy-0.2.0-py3-none-any.whl
Size 76.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e5ce8a156f6cfaee3170c286d8f4e9314d8c3a9a6a609c4274e7bd162b939825
BLAKE2b-256 checksum
How to use checksums
f107def59337bd716ecd7ccc4ecfa09dc461497f47979c5475f8d1785317e699
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for reccy 0.2.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

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