Skip to main content

Recmd: Inspired by zx command constructor

Easy to use, type-safe (pyright), sync and async, and cross-platform command executor

Installation

Install recmd using pip: pip install recmd With async support: pip install recmd[async]

Usage

Convert formatted strings to argument lists

Using ast transformation (python 3.12+):

from recmd import shell, sh

@sh
def argument_list(value: str, *args):
    return shell(f"executable arguments --value {value} 'more arguments with {value}' {args:*!s}")
    # :*!s converts args into `*[f"{x!s}" for x in args]`
    # if !s is omitted then it turned into just `*args`
    # you can also add any python format after :* (:*:.2f)

assert argument_list("test asd", 1, 2, 3) == [
    "executable", "arguments", "--value", "test asd", "more arguments with test asd", "1", "2", "3"
]

Using template strings (python 3.14+):

from recmd import shell

def argument_list(value: str, *args):
    return shell(t"executable arguments --value {value} 'more arguments with {value}' {args:*!s}")
    # :*!s converts args into `*[f"{x!s}" for x in args]`
    # if !s is omitted then it turned into just `*args`
    # you can also add any python format after :* (:*:.2f)

assert argument_list("test asd", 1, 2, 3) == [
    "executable", "arguments", "--value", "test asd", "more arguments with test asd", "1", "2", "3"
]

Constructing commands

Using ast transformation (python 3.12+):

import sys
from recmd.shell import sh

@sh
def python(code: str, *args):
    return sh(f"{sys.executable} -c {code} {args:*}")

Using template strings (python 3.14+):

import sys
from recmd.shell import sh

def python(code: str, *args):
    return sh(t"{sys.executable} -c {code} {args:*}")

Running commands

Sync:

from recmd.executor.subprocess import SubprocessExecutor

# set globally (context api)
SubprocessExecutor.context.set(SubprocessExecutor())

# set for code block
with SubprocessExecutor().use():
    ...


# `~` runs and waits for process to exit, then `assert` checks that exit code == 0
assert ~python("pass")

# you can also use process as context manager
with python("pass"):
    pass

Async:

import anyio
from recmd.executor.anyio import AnyioExecutor

# set globally (context api)
AnyioExecutor.context.set(AnyioExecutor())

# set for code block
with AnyioExecutor().use():
    ...

async def run():
    # `await` runs and waits for process to exit, then `assert` checks that exit code == 0
    assert await python("pass")

    # you can also use process as context manager
    async with python("pass"):
        pass

anyio.run(run)

Interacting with processes

Sync:

# send to stdin and read from stdout
assert ~python("print(input(),end='')").send("123").output() == "123"

from recmd import IOStream


# manually control streams
with IOStream() >> python("print(input(),end='')") >> IOStream() as process:
    process.stdin.sync_io.write(b"hello")
    process.stdin.sync_io.close()
    assert process.stdout.sync_io.read() == b"hello"

Async:

# send to stdin and read from stdout
assert await python("print(input(),end='')").send("123").output() == "123"

from recmd import IOStream


# manually control streams
async with IOStream() >> python("print(input(),end='')") >> IOStream() as process:
    await process.stdin.async_write.send(b"hello")
    await process.stdin.async_write.aclose()
    assert await process.stdout.async_read.receive() == "bhello"

Pipes

Sync:

# redirect stdout from first process to stdin of second
from recmd import Capture

group = ~(python("print(123)") | python("print(input(),end='')") >> Capture())

with python("print(123)") | python("print(input(),end='')") >> Capture() as group:
    ...

assert group.commands[-1].stdout.get() == b"123"

Async:

# redirect stdout from first process to stdin of second
from recmd import Capture

group = await (python("print(123)") | python("print(input(),end='')") >> Capture())

async with python("print(123)") | python("print(input(),end='')") >> Capture() as group:
    ...

assert group.commands[-1].stdout.get() == b"123"

Release files for recmd 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 recmd 0.2.0
File Size Uploaded
recmd-0.2.0.tar.gz 17.6 kB Details

Built distribution (wheel)

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

Total release size: 35.2 kB

Release files / recmd-0.2.0.tar.gz

Download URL recmd-0.2.0.tar.gz
Size 17.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b6fe4ad3ceee66da64e5377ea644921b166bd8eee252e695e10f7bc65d6b846e
BLAKE2b-256 checksum
How to use checksums
c278646e9be19db5e6941633f37e2bc576724e6b8d0745100b27f3a52124b150
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.14.0b2

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

Download URL recmd-0.2.0-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6c0fc0a4cebb39f0fc106a2eedc15148e64def935178ce259423c88cc3520ab9
BLAKE2b-256 checksum
How to use checksums
adf3cfcae0ef15680ce517121af40c3c720ad0d1599b8ba72c931bea117905dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.14.0b2

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

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