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)
| File | Size | Uploaded | |
|---|---|---|---|
| recmd-0.2.0.tar.gz | 17.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|