-
Notifications
You must be signed in to change notification settings - Fork 506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Embed Python #537
Comments
@runeimp, continuing the discussion from #531: I think implementing it is probably out of the question, but there's actually a rust implementation of Python3, and that might be easy to embed one day. It would probably increase the size of the binary by a few megs, but it could be optional, so people could turn it off if they wanted. (Although honestly, in an age of 100+ meg electron apps, I don't think anyone would notice :P) Can you give me some examples of how you would use embedded scripting? |
RustPython looks cool. Installed it and immediately ran into a problem. But it's still in development so not too surprising. I also may have done something dumb in a rush to play with it. 👼 Examples of scripting I use in Justfiles:
That's about 95% of what I do scripting wise. I typically do it in Bash as that's what's common for my terminal environment most of the time. Though for Windows I'll use |
Gotcha, that all makes sense and sounds reasonable. I suppose that the feature would take the form of a recipe annotation indicating to Just that the recipe should be evaluated with a built-in python3 interpreter: [python]
foo:
print("Hello from python!") |
I'm against this. I really like that the |
@dionjwa the binary is already about 2.6MB. It's hardly a lightweight. The addition of a meg or so shouldn't really affect the choice to utilize it. It needs a bit of scripting ability for flow control and the like. Relying on the presence of |
I agree that sh/bash is actually a pretty terrible scripting solution compared to e.g. python. If it's only a meg or so (I wasn't aware it would be that size) then that criticism of mine is not valid. Would there be issues if you required a specific version of python? Like if the embedded version was different to the host? If so, then I would be supportive, because scripting complex commands in sh/bash is somehow always painful, and often hard to read later. |
Yeah, I expected it would be bigger too but in my earlier discussions on the topic with @casey he'd already looked into it a bit and expects a megabyte or so of additional size. As I understand it (I not on the dev team nor am I a Rust programmer) the single I just hope it's at least Python 3.6 so I can use f-strings. I love python f-strings. One of the few things I really miss in every other language I use. |
An alternative to Pros:
Cons:
I tried using |
I like the idea of (optionally) embedding RustPython. One instance of prior art: Task embeds a cross-platform shell interpreter written in Go. I have not found a POSIX-compatible shell in Rust that is similarly mature, or one that builds on Windows. |
As nice as an embedded scripting language could be for performance, isn't it likely to make backwards-compatibility a minefield? If
In the case of Python specifically, I have a lot of custom Python scripts and some of them have broken from one minor Python 3.x version to the next. So Theoretically maybe It would be nice if there is a solution that would make all these concerns a non-issue, but can't think of what that might be |
You are right that Python version compatibility is a potential problem. In my opinion, if just embeds Python, it should not include it in the semver public interface. Anything else would entail forking Python, which seems like an unsustainable amount of work, and would also lead to users asking, "Where is feature X? I used it two Python releases ago." This is something where POSIX shell has an advantage. It is not going to break compatibility. Lua 5.1 and JavaScript have it, too. While 5.x releases of Lua make breaking changes, Lua 5.1 is going to be supported indefinitely thanks to LuaJIT. The situation is similar with the gradually-typed fork of Lua 5.1 Luau, because it is the scripting language of Roblox. My own preference goes, Python > POSIX-compatible shell > Lua > JavaScript. When it comes to JavaScript, note that Deno is planning a 2.0 release. Among other things, Deno 2.0 is going to remove An option I have thought of is to embed a Wasm VM and let the user choose their own programming language runtime. With Wasmer, you could leverage their packages, including the versioning. This means that example 1 could become something like example 2. Example 1: # This works right now.
# It requires Wasmer to be installed as a separate binary.
test:
#! /usr/bin/env -S wasmer run python/python@0.1.0 --mapdir /tmp:/tmp --mapdir .:.
from pathlib import Path
test_file = Path("test.txt")
test_file.write_text("Hello from Python!\n")
print(test_file.read_text(), end="") Example 2: # A theoretical example of how things could work
# with Wasmer embedded in just.
set script-wasmer-package := "python/python@0.1.0"
# Access to the temporary directory just creates is granted implicitly.
set script-wasmer-dirs := [".:."]
# set script-wasmer-dirs := [".", "."] # ?
[script]
test:
from pathlib import Path
test_file = Path("test.txt")
test_file.write_text("Hello from Python!\n")
print(test_file.read_text(), end="") |
@laniakea64 I don't see the potential for a backwards compatibility problem. If Any special features added via a language/interpreter update could be locked behind a specific edition. |
@dbohdan I'd love to see Lua used but the discussion has already happened and Python was the preference. See #531 and others. Having In my mind, the point is to have much better scripting that is cross-platform and I only have to update the one executable ( |
This would effectively mean forking RustPython and maintaining the fork. According to tokei, just has 17k lines of Rust code in I think a never-upgrade approach isn't viable because it amounts to forking. I would prefer either tracking stable RustPython without it being part of just's commitment to backward compatibility or some way to choose the Python version.
The idea is to let the user specify a fixed version of Python (or another interpreter) to run their scripts with. This would give you both stability (old versions don't change) and upgrades (new versions become available).
Wasmer automatically downloads packages before running them. I am not sure how reliable it is. If it is reliable, you would only need to install things manually on a machine that wasn't connected to the Internet.
This would be the main reason to embed Wasmer. That being said, I am not advocating for Wasmer over tracking RustPython. I am suggesting it as a more speculative high-risk, high-reward option. |
@dbohdan one of the many problems of adding anything to I'm not the project author so that would be up to @casey on how he wants to handle all of that. But as we've been discussing this feature for several years now I'm guessing that is going to be close to how he sees it. High-risk, high-reward is rarely done these days. When it started out that may have been an option. But lots of users depend on the stability of @casey there was a time where the Rust concept of Editions was going to be added to |
Right, I agree this is a concern. How do you see this being implemented in just? Edit: What I want to ask about is how you see it happening on a technical level. Do you consider forking RustPython an acceptable solution (the correct solution)? Are you thinking of adding a certain version of RustPython as a dependency and never going past it (without a fork)?
I am not sure you meant this, but if staying essentially the same is the goal (i.e., minor breaks in compatibility are allowed), this is pretty much how Python is developed. |
Just since it hasn't been mentioned: is Nushell under consideration here?
Advantages:
|
@starthal I think Nu is a great option and was suggested early on in the discussion. But suffers from its current lack of popularity. Python on the other hand is one of the, if not the, most popular languages known across several domains. So chances are high that familiarity with Python, for anyone who would benefit from the tool, is extremely high. |
I understand the desire to use a language that is well known, but I want to make the claim that Python doesn't succeed at that: Most Justfile usage calls out to external programs. Python makes this very verbose and difficult, and I believe this is not a common task for most Python programmers, meaning they will need to check the documentation to perform this. This code: subprocess.run(["git", "lfs", "lock", path]) is equivalent to this: git lfs lock "$path" Definitely the quoting isn't great in the Bash version, but it also isn't fantastic in the Python version. But what I'm really arguing here is not to go with bash, but to go with any language where the shell is first class. This includes nushell, oilshell and also Xonsh. If you wished for the familiarity of Python loops and conditionals, but still have first class shell, this would be it, but I don't know how it would go embedding into something like Just. All of these languages have a better story around quoting, conditionals and loop. Condtionals are likely the next most common action, and I think basically all newer shells fix this, and all modern shells look similar. Similar argument for loops. Making these scripts is going to require everyone to learn something new, whether it be subprocess calling notation, or the exact syntax for an if statement. But the majority will simply want to string a series of commands together, and this should be the easiest action in the chosen language. People will still be able to choose an external language to run a script in. |
My two cents on this issue, specifically on the Python version compatibility.
TL;DR: you can specify python version and dependencies in the recipe itself. Here are some examples:
All #!/usr/bin/env -S uv run --script
#!/usr/bin/env -S uv run --python='>=3.11' --script I recognize this means depending on |
@lucabello That's a really good suggestion! I think with Python packaging being the way it is, this might be the best option. Python packaging and versioning is such a mess that, even if Also, this works very nicely with the set unstable
set script-interpreter := ['uv', 'run', '--script']
[script]
hello:
print("Hello from Python!")
[script]
goodbye:
# /// script
# requires-python = ">=3.11"
# dependencies=["sh"]
# ///
import sh
sh.echo("this doesn't print anything")
print("this does print something") However, the |
@casey by default, |
@starthal Okay nice. Yah, I think maybe this is what we should be recommending. I think when I first opened this issue, I was hopeful we would see a feature-complete Rust implementation of Python, but that doesn't seem to be materializing. Suggesting |
Added a little documentation in #2526. I think I'm probably inclined to close this issue now, just because I think embedding python would wind up being a huge headache, and would require a lot of supporting features to manage the installation, environment, etc, which |
Maybe out-of-scope for this issue, but: in the hypothetical future where just embeds a scripting language, do you have an annotation syntax in mind to distinguish "use embedded interpreter X" from I guess at worst this would result in a new annotation, so probably not a major concern. |
@starthal I haven't thought too much about it, but yeah, probably a different annotation, or something like |
I'm down voting this just because |
Yah I think this is a pretty comprehensive solution, so I'll go ahead and close this. God bless the |
Once |
👋 I'd like to mention that in the same way that set unstable
set script-interpreter := ['elixir']
# add this if you want to force a different directory for dependencies, and easily cache them on CI
# export MIX_INSTALL_DIR := justfile_dir() + "/.cache"
[script]
my-ip:
Mix.install([{:req, "~> 0.5"}])
Req.get!("https://icanhazip.com").body |> IO.puts()
[script]
error:
IO.puts(:stderr, "Some error") $ just my-ip
82.125.67.147
$ just error
Some error I wrote about it here and there are a lot of more example in mix install examples repo. I wish |
Yup, added! https://github.com/casey/just/?tab=readme-ov-file#python-recipes-with-uv |
Very nice! If you think this is useful, feel free to open a PR to add it to the readme. (Probably under the Python example with |
I think this is a good resolution. Even half a year ago, rye was on the scene, and the future of Python tooling was less certain. Now it looks like uv will play a big role in it.
There is a number of tools and runtimes with this feature. I have compiled a list, which includes uv and Elixir: https://dbohdan.com/scripts-with-dependencies. (To my surprise and joy, it got referenced in PEP 722, which led to PEP 723.) |
@dbohdan Damn that's a great post! |
@dbohdan that's a great post! Looks like all most popular languages can do that to some extent! Thanks for sharing. I won't submit any PR for just Elixir in this case, too niche 😄 |
Thanks, @casey and @arathunku! Feel free to contact me if you would like to suggest additions. |
@runeimp and I discussed this a bit in #531, but I thought that it would be good to give it its own issue.
In that thread, we discussed embedding a scripting language to give people an alternative to shell.
I think Python would be the best choice, since it's widely known and very nice for scripting tasks. Unfortunately, Python is hard to embed. That might change with RustPython though.
The text was updated successfully, but these errors were encountered: