Skip to content
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

Support for PEP 723 #808

Open
henryiii opened this issue Apr 6, 2024 · 7 comments
Open

Support for PEP 723 #808

henryiii opened this issue Apr 6, 2024 · 7 comments

Comments

@henryiii
Copy link
Collaborator

henryiii commented Apr 6, 2024

How would this feature be useful?

I'd love to have good support for PEP 723 in nox. As a reminder, scripts can start with this:

# /// script
# dependencies = ["requests"]
# requires-python = ">=3.10"
# ///

(I wrote a post about it a while back, see here. I've started adding this to helper scripts; but most of the time I also have nox sessions that run the helper scripts, and it's nice to have then all in once place and show up with nox -l, and I'd like to keep the canonical dependency list inside the script.

Now, being written in Python, you could implement this yourself in a noxfile - except if nox runs on Python < 3.11, no TOML library is installed (this hits me a lot - since I also often need to pull dependency lists from pyproject.toml, too, which can't be done since nox doesn't provide tomli on Python < 3.11 - I've really wanted it to for a while now).

Describe the solution you'd like

I thought about a lot of ways, and there is a simple solution that could be built upon later, but provides a lot of flexibility and utility for now. A new function could be added, nox.project.load_toml (happy to bike shed on name). This would take a path to either a toml file (which it would load) or a python file, which it would load the /// script block. The example above then would be:

@nox.session
def myscript(session: nox.Session) -> None:
    deps = nox.project.load_toml("myscript.py")["dependencies"]
    nox.install(*deps)
    nox.run("python", "myscript.py", *session.posargs)

And you could use it to for example install a project's dependencies without building the project:

@nox.session
def prepare(session: nox.Session) -> None:
    deps = nox.project.load_toml("pyproject.toml")["project"]["dependencies"]
    nox.install(*deps)
    ...

(you could modify this for Poetry, as well) You can't do this today unless you assume nox will always run on Python 3.11 or above.

You could also access requires-python too, though nox can't do much with it yet. Might be a future addition, though, to allow Python to be specified as a SpecifierSet.

Describe alternatives you've considered

I've thought about a lot of different ways, and finally arrived on the one above. A few of them were:


We could provide a way to turn a script into a session. This might look like this:

nox.script_to_session("myscript.py", name="my-script")
# Docstring could become the docs for the session, too!

The problem is that this is quite rigid; if you want to add anything, like logging or something, you can't easily make this a normal session without something like proposed above. This could come later, but wouldn't be a good starting point.


We could provide a file= argument to install. This would mean that session.install(file="myscript.py") would install the dependencies in myscript.py. You could also use it for requirements.txt; not that helpful, but nice. The problem comes when using file="pyproject.toml", as there's no way to specify extras. Adding another keyword argument that only worked for pyproject.toml files seems like poor design, and would the extras= keyword also install project.dependencies? And in both cases, some people might also expect this to install the project itself, which wouldn't be helpful (you can just install "." for that), but it's not clear by the design. We could support Poetry transparently, which is nice. And this might be nice for supporting conda (in conda_install). But the issues above are big downsides. And it doesn't provide a way to get other information, like requires-python.


A variation of the above idea would be a .get_deps() method that read a file and produced a list of deps that the user could then pass to install, or do something else with. This has many of the same drawbacks though.

Anything else?

Name ideas:

  • nox.load_pyproject
  • nox.load_toml
  • nox.load_config

Edit: using current proposal of nox.project.load_toml.

@cjolowicz
Copy link
Collaborator

PEP 735 seems relevant too. The proposed solution would allow installing dependency groups, if that PEP gets accepted.

@henryiii
Copy link
Collaborator Author

henryiii commented Apr 7, 2024

What about nox.toml.load as the name? That would leave the namespace open for future helpers to get dependency groups (for example). You can support PEP 735 with the current toml load function, but a user would have to implement the 'include' feature manually; I think having a helper to load it would make sense if it gets accepted.

@cjolowicz
Copy link
Collaborator

I like the idea of creating a namespace.

Bike shedding: I'm not sure about the name TOML, it's just a format. Can we come up with a name that expresses what we are loading? Is it a Python project configuration? In which case, nox.project.load seems appropriate. (I always feel that pyproject is a bit redundant when you're already in Python. But no strong opinion here.)

@cjolowicz
Copy link
Collaborator

Slightly OT but I've been pondering if a nox.project.build primitive would let us tackle #740

@henryiii
Copy link
Collaborator Author

henryiii commented Apr 8, 2024

I was assuming build would need to be a session method, since it needs the current Python to build with. It would need to be able to cache between sessions, but it would need to start with the current's session's Python. And it would ideally also respect the venv setting (at least uv vs. virtualenv). However, I suppose it could be standalone and just take the python version and venv mode. It doesn't need the session's virtualenv itself.

nox.project isn't as clearly pyproject.toml related; I could see someone thinking it loads some sort of nox project, like trying to load noxfile.py somehow. pyproject.toml needed the py in it because other tooling (like Rust's Cargo) also use toml.

I think it would be good to think about possible future additions, and see which they would make the most sense in. If there was a build, nox.pyproject.build is nice for how it mirrors pyproject-build, the command pypa/build installs.

Regardless of project vs. pyproject, how about going back to the load_toml name in the namespace? The function itself does load toml - either a .toml file or embedded toml in .py files via PEP 723.

Edit: thinking a bit more about it, I think nox.project is okay. It might eventually contain things not specifically tied to the pyproject.toml, and is more about managing a project, I think. But still fine with either one, I used project in the PR for now.

@henryiii
Copy link
Collaborator Author

henryiii commented Apr 8, 2024

I think #631 would be a good step for build, hopefully that can be revived in the future. I wasn't planning on doing much for build until that was worked on.

@henryiii
Copy link
Collaborator Author

I've been finding a helper like this to be very useful:

def install_and_run(session: nox.Session, script: str, *args: str, **kwargs: Any) -> str | None:
    deps = nox.project.load_toml(script)["dependencies"]
    session.install(*deps)
    return session.run("python", script, *args, **kwargs)

Typing it is non-trivial, as it really should wrap session.run. Would making something like this available on session (with the install part being skipped if the script doesn't have PEP 723 dependencies) be a useful next step? What would the function be called? install_and_run_python_script is a bit too long. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants