compose.up()
contains the command's stdout.In addition stdout can be redirected to a file, or to another command, or to a callable, — which will be called for chunks of stdout while the command is in operation.
34 karma · joined May 2, 2022
Python developer, interested in:
- software architecture & code quality
- linked data & semantic web (more at https://iolanta.tech)
- tools for development productivity (more at https://jeeves.sh)
compose.up()
contains the command's stdout.In addition stdout can be redirected to a file, or to another command, or to a callable, — which will be called for chunks of stdout while the command is in operation.
- command actually executed,
- snippet of stdout
- and snippet of stderr.
They can be handled using standard exception techniques.
Experiences differ, and de gustibus on est disputandum; in my bubble, virtualenv is a must-have for Python development.
I have multiple Python projects, they run different versions of Python itself and its libraries, and for many of them Docker would be an overkill; so it is hard for me to imagine my routine without virtual environments.
I employ pyenv to manage them but venv is a venv.
Why not use native Python methods, namely endpoints, instead, and automate this away?
@pre(file_name=_file_is_in_sync)
def build(file_name: Path):
...
I believe such libraries do exist even, but I haven't yet researched the topic; if jeeves proves to be useful for the simplest use case then it will make sense to expand its scope.- Is putting a symlink to one of your project directories, probably residing in $HOME, to /usr/bin/ a good practice?
- As an alternative one can put a symlink into ~/bin; but what if you have multiple different projects?
- And they can have different jeeves commands and different plugins installed. For instance, `j lint` implementations in different projects are work on are completely different.
Because these are different projects.
Rather, I prefer to have jeeves automatically create the executable command in each virtual env, and its behavior in different virtual envs will be different.
- via direct function calls in Python code,
- or using Typer callback functions
…is easy.
There are potential approaches worth exploring (annotations, decorators, …). I might come up with a list of options with syntax examples and ask for community's thoughts about it, — but at this point jeeves, as an MVP, is already useful for me and a few people I work with, and that motivated me to share it with the community.
> just `python your_file.py`
That's actually one of the points.
j lint
is shorter than python scripts.py lint
and the letter `j` is what the index finger of your right hand is pointing to on the keyboard. There is usually a tactile bump on that key. It is something you can type very quickly. You can also install shell completion to improve the experience further.Ergonomics matters.
compose = sh.docker.compose.bake('-f', 'deploy/dev.yml')
…
compose.down()
…
compose.up('--force-recreate', '-d')
I feel this is a major improvement on top of Makefiles + shell commands in them. Nice API _matters_; it is ergonomics and therefore productivity.You can specify `_out=rich.print` and it will work but AFAIK it won't scrape ASCII terminal formatting characters.
I had to fix these characters in my `sh` based scripts but do not consider that as a big deal.
> did actually do a suitable job of replacing gnu make
The title does not say _a replacement_, it says _an alternative_. I never intended to replace GNU Make; if I said that anywhere — that was a mistake on my part.
> do improve upon those who created unix
I did not intend to improve upon UNIX ideas or philosophy. The improvements I am aiming for are:
- developer experience,
- conciseness and maintainability of the code.
…and these are being addressed for a narrow use case. For that use case, in my experience, Make is very often used.
I argue that within the bounds of this use case, this is an alternative which can be of use to improve productivity and everyday experience.
> I don't really understand the full job make does, so here is something which only does a simpler job which I do understand, and I like python
I classify these assumptions about my understanding or misunderstanding as ad hominem and unprofessional.
* jeeves is the name of the project which I'm advertising, which converts a Python file to a set of commands; * `j` is the name of executable which aformentioned project exposes; * and `sh` is the library (the correct link to which you have provided) which is an optional dependency of `jeeves` and provides a more convenient interface to calling processes and executing commands from Python than `subprocess.run()`.
However, dealing with Python mostly in my time in the industry, I scarcely ever had the need to use these particular features of Make. I used it as a task runner, guilty of that, — and that's what I wanted to reimplement in Python.
Even writing in Rust, everything I have to do is `cargo build`, — there is no need to write Makefiles for building the project.
I will consider changing that wording to set proper expectations, but I still feel that for me "a Pythonic alternative to Make" perfectly conveys the use case I daily employ this thing for.
pip install jeeves-yeti-pyproject
will provide you with jeeves, a bunch of commands, and a pack of dev dependencies which I personally happen to like and to use in my projects.I don't believe Make has plugins.
- no type hints for docs & validation, I wanted them
- Makefile in its basic form is very concise and doesn't require a @task decorator, I didn't want it either
I didn't need dependency graph much.
The way I'd recommend to run shell commands from jeeves is `sh` library. It has features for ssh support: https://sh.readthedocs.io/en/latest/sections/contrib.html#ss...
my_sh = sh.bake(_tty_out=False)
my_sh.do_whatever()
The way how sh captures output can apparently be altered, say, by providing a callable to _out argument.By making .PHONY and .ONESHELL labels history, as a particular example.
Instead of `make` as entry point for all project-specific commands (like `make lint`, `make build`, `make deploy`), users can rely upon `j` — both locally and CI.
Regarding your particular points:
* dependency graphs aren't presently at scope, I haven't yet come up with the method of expressing them in Python which would have entirely satisfied me;
* I am not so sure I would want to implement pattern rules due to complexity they bring about. Maybe just writing explicit Python code would be enough for cases where they're used. No strong opinion though.
jeeves, on the other hand, does no such thing. It is a command runner: you write a function → it is converted into a shell command.
I believe jeeves is easier to get started with, partially due to its simplicity and partially because it follows Python standards. For instance, waf uses a peculiar syntax to define command parameters:
waf employs a peculiar method to configure command options:
ctx.add_option('--foo', action='store', default=False, help='Silly test')
while jeeves relies upon function arguments and type hints (as it is based upon Typer): def do(foo: Annotated[Option(help='Silly test')]):
…
While jeeves is easy to start with it also promises ability to scale: with modular packages for commands, subcommands, and installable/shareable plugins.Perhaps this particular robot is running a much more civilized version of Skynet/Cyberdyne firmware.
And is sure to be speaking impeccable English.