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 Linuxsystemd --user, macOSlaunchd, 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_helpperforms a regression assertion and returns nothing. Its short name is retained for consumer tests. See the CLI help guide.Jsonl,CompressandDecompressretain their existing names for consumers; they encode dictionary deltas as described above, not JSONL text or compression of arbitrary data.ServiceSpec.socket_fileis the generic control socket's relative path. Its existinggui.sockbasename 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.ServiceControllermanages systemd on Linux, not XDG autostart entries.AudioMidiDeviceSpecstores 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 unusedipc.Replymodel 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.