-
-
Notifications
You must be signed in to change notification settings - Fork 584
feat: Add support for REPLs #2723
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
base: main
Are you sure you want to change the base?
Conversation
This patch adds three different ways to invoke a REPL for a given target, each with slightly unique use cases. Deployed binaries: Sometimes it's really useful to start the REPL for a binary that's already deployed in a docker container. You can do this with the `RULES_PYTHON_BOOTSTRAP_REPL` environment variable. For example: $ RULES_PYTHON_BOOTSTRAP_REPL=1 bazel run --//python/config_settings:bootstrap_impl=script //tools:wheelmaker Python 3.11.1 (main, Jan 16 2023, 22:41:20) [Clang 15.0.7 ] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> import tools.wheelmaker >>> `py_{library,binary,test}` targets: These targets will now auto-generate additional `<name>.repl` targets. $ bazel run --//python/config_settings:bootstrap_impl=script //python/runfiles:runfiles.repl Python 3.11.1 (main, Jan 16 2023, 22:41:20) [Clang 15.0.7 ] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> import python.runfiles >>> Arbitrary `PyInfo` providers: Spawn a REPL for any target that provides `PyInfo` like this: $ bazel run --//python/config_settings:bootstrap_impl=script //python/bin:repl --//python/bin:repl_dep=//tools:wheelmaker Python 3.11.1 (main, Jan 16 2023, 22:41:20) [Clang 15.0.7 ] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> import tools.wheelmaker >>>
Broadly in favor of this, had a similar macro feature aligned with the verbs pattern at a former employer. I don't love having magical behavior that runs ipython if it's on the path, IMO an ipython shell or any other notebook-like thing is a different kind of target than a vanilla shell and we shouldn't overload the one with try-import behavior which is generally an antipattern. IMO the kind of shell to run should be explicitly selected either by having separate targets or by having a feature flag we can While REPL is the more familiar term to me from the Lisp world, the Python ecosystem appears to prefer the words "shell" or "console". My previous macros emitted |
What is the difference between 2 and 3? Could we just get away with 3? |
I generally agree, but everywhere I've implemented this (3 places), one of the first questions is "how do I get ipython?". So I've just given up on that front and made these automatically use that. I can be convinced to drop it, but I suspect without it, folks are just going to implement it themselves which reduces the value of having it in a shared place.
Perhaps we could add an additional label_flag to let the user specify a .py file that invokes the actual shell (to replace |
The difference is convenience. I have found that folks picked up the "bazel run this library with .repl at the end" a whole lot easier and they even taught it to other folks. |
Hmm. Perhaps I'm old 😛 REPL does appear in the docs: https://docs.python.org/3/tutorial/appendix.html#tut-interac
And there's even an environment variable for configuration: I'm happy to change it to ".shell" or similar if you all want, but I don't think REPL is un-Pythonic so to speak. |
One extra question about what is the difference between your Should we have the approach where we:
|
Can you elaborate on this a bit? Is the use case to do something like The goal with this PR here is to create a REPL in an environment identical to what you would see when running your code in a
You don't get that with only the interpreter, even if it could import the deps. I think if we can figure out how to move all those things into a pre-generated venv, then that could work. But it's not obvious to me that we can do that at this time.
Removed the env var in the bootstrap script and the macro stuff for future PRs. |
The concrete usecase is that I am using Right now I am hacking this around by having a import subprocess
import sys
if __name__ == "__main__":
subprocess.run([sys.executable] + sys.argv[1:]) # noqa: S603 This basically re-execs the interpreter from the environment with any parameters that may be passed to the target. If I run this target I get the If on the other hand I pass the My question here is:
|
My 2c here. I would prefer to have this in user-space. We have a similar need at work, and we added a small macro to create an ipython py_console_script_binary with the dependencies. (It occurs to me with the new This deals with most of the concerns listed above?
Im worried about adding more complexity into the toolchains and bootstrapping areas at the moment. If your main motivation is convenience for users, it's unclear why you couldn't create some user facing macros in your repository (automated or not via gazelle) that wrap the native py_library and py_binary targets with an ipython repl and targetnames of your choosing? |
I am curious if having the
I agree with @groodt here that running I personally liked the already existing target |
The goal with this PR here is to create a REPL in an environment identical to what you would see when running your code in a py_binary. I.e. all the things that the first and second stage bootstrap script do. The three that I can think of:
You don't get that with only the interpreter, even if it could import the deps. If we want to to make
When you just want the interpreter (for deploying, bootstrapping, etc.) you use Your work around with a
I removed all the changes to IPython, the bootstrapping scripts, and there are no changes to the toolchain stuff. Could I ask you to double check that you're still interested in seeing some changes?
I removed all the IPython-related changes. The user can create their own macro or substitute their own stub if they like.
That's actually pretty cool! I think that could work, but it would be limited to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as the REPL implementation goes, I don't have major issues with this, but love to discuss on the comments I left.
# can point this at their version of ipython. | ||
label_flag( | ||
name = "repl_stub_dep", | ||
build_setting_default = "//python/private:empty", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should we just use //python:none
as the default? I think it is working above in setting the python_src
?
If it does not work, maybe we just need to add an empty PyInfo
provider in the sentinel target.
# The user can modify this flag to make arbitrary PyInfo targets available for | ||
# import on the REPL. | ||
label_flag( | ||
name = "repl_dep", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just use python_src
from above instead?
}, | ||
) | ||
|
||
def py_repl_binary(name, stub, deps = [], data = [], **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document the values with the usual docs.
"stub": attr.label( | ||
mandatory = True, | ||
allow_single_file = True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is stub
?
# Copyright 2025 The Bazel Authors. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Copyright can be removed from the PR, bazel-contrib
does not require that.
import code | ||
|
||
code.interact() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is code
and what is code.interact
? Is it something special? Could you please add a comment?
# The user can modify this flag to make an interpreter shell library available | ||
# for the stub. E.g. if they switch the stub for an ipython-based one, then they | ||
# can point this at their version of ipython. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In our docs
we have documentation for targets, e.g. the config settings, so these comments could be written there so that the user can easily find the docs.
Changed to |
This patch adds a new target that lets users invoke a REPL for a given
PyInfo
target.For example, the following command will spawn a REPL for any target
that provides
PyInfo
:If the user wants an IPython shell instead, they can create a file like this:
Then they can set this up in their
.bazelrc
file: