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

Add support for global, non venv Python invocations #336

Merged
merged 3 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ that were not yet released.

_Unreleased_

- Added support for the new `behavior.global-python` flag which turns on global
Python shimming. When enabled then the `python` shim works even outside of
Rye managed projects. Additionally the shim (when run outside of Rye managed
projects) supports a special first parameter `+VERSION` which requests a
specific version of Python (eg: `python +3.8` to request Python 3.8). #336

- Renamed the config key `default.dependency_operator` to `default.dependency-operator`
and `behavior.force_rye_managed` to `behavior.force-rye-managed`. #338

Expand Down
5 changes: 5 additions & 0 deletions docs/guide/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ https = "http://127.0.0.1:4000"
# When set to true the `managed` flag is always assumed to be true.
force-rye-managed = false

# Enables global shims when set to `true`. This means that the installed
# `python` shim will resolve to a Rye managed toolchain even outside of
# virtual environments.
global-python = false

# a array of tables with optional sources. Same format as in pyproject.toml
[[sources]]
name = "default"
Expand Down
3 changes: 3 additions & 0 deletions docs/guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ interpreter.

Note that you might need to restart your login session for this to take effect.

There is a quite a bit to shims and their behavior. Make sure to [read up on shims](shims.md)
to learn more.

## Updating Rye

To update rye to the latest version you can use `rye` itself:
Expand Down
43 changes: 43 additions & 0 deletions docs/guide/shims.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Shims

After installation Rye places two shims on your `PATH`: `python` and `python3`. These
shims have specific behavior that changes depending on if they are used within a Rye
managed project or outside.

Inside a Rye managed project the resolve to the Python interpreter of the virtualenv.
This means that even if you do not enable the virtualenv, you can just run `python`
in a shell, and it will automatically operate in the right environment.

Outside a Rye managed project it typically resolves to your system Python, though you
can also opt to have it resolve to a Rye managed Python installation for you. This is
done so that it's not disruptive to your existing workflows which might depend on the
System python installation.

## Global Shims

+++ 0.9.0

To enable global shims, you need to enable the `global-python` flag in
the [`config.toml`](config.md) file:

```toml
[behavior]
global-python = true
```

Afterwards if you run `python` outside of a Rye managed project it will
spawn a Python interpreter that is shipped with Rye. It will honor the
closest `.python-version` file for you. Additionally you can also
explicitly request a specific Python version by adding `+VERSION` after
the `python` command. For instance this runs a script with Python 3.8:

```bash
python +3.8 my-script.py
```

!!! Note

Selecting a specific Python version this way only works outside of
Rye managed projects. Within Rye managed projects, the version needs
to be explicitly selected via `.python-version` or with the
`requires-python` key in `pyproject.toml`.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ nav:
- Installation: guide/installation.md
- Basics: guide/basics.md
- Configuration: guide/config.md
- Shims: guide/shims.md
- Python Project: guide/pyproject.md
- Syncing and Locking: guide/sync.md
- Building and Publishing: guide/publish.md
Expand Down
62 changes: 60 additions & 2 deletions rye/src/cli/shim.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use std::convert::Infallible;
use std::env;
use std::ffi::{OsStr, OsString};
use std::str::FromStr;

use anyhow::{bail, Context, Error};
use anyhow::{anyhow, bail, Context, Error};
use same_file::is_same_file;
use std::process::Command;
use which::which_in_global;

use crate::bootstrap::{ensure_self_venv, get_pip_runner};
use crate::config::Config;
use crate::consts::VENV_BIN;
use crate::pyproject::PyProject;
use crate::platform::{get_python_version_request_from_pyenv_pin, get_toolchain_python_bin};
use crate::pyproject::{latest_available_python_version, PyProject};
use crate::sources::{PythonVersion, PythonVersionRequest};
use crate::sync::{sync, SyncOptions};
use crate::utils::{exec_spawn, get_venv_python_bin, CommandOutput};

Expand Down Expand Up @@ -90,6 +94,15 @@ fn get_shim_target(target: &str, args: &[OsString]) -> Result<Option<Vec<OsStrin
sync(SyncOptions::python_only()).context("sync ahead of shim resolution failed")?;
}

if (target == "python" || target == "python3")
&& args
.get(1)
.and_then(|x| x.as_os_str().to_str())
.map_or(false, |x| x.starts_with('+'))
{
bail!("Explicit Python selection is not possible within Rye managed projects.");
}

let mut args = args.to_vec();
let folder = pyproject.venv_path().join(VENV_BIN);
if let Some(m) = which_in_global(target, Some(folder))?.next() {
Expand All @@ -101,6 +114,51 @@ fn get_shim_target(target: &str, args: &[OsString]) -> Result<Option<Vec<OsStrin
if target == "pip" || target == "pip3" {
return Ok(Some(get_pip_shim(&pyproject, args, CommandOutput::Normal)?));
}

// Global shims (either implicit or requested)
} else if target == "python" || target == "python3" {
let config = Config::current();
let mut remove1 = false;

let version_request = if let Some(rest) = args
.get(1)
.and_then(|x| x.as_os_str().to_str())
.and_then(|x| x.strip_prefix('+'))

This comment was marked as outdated.

{
remove1 = true;
PythonVersionRequest::from_str(rest)
.context("invalid python version requested from command line")?
} else if config.global_python() {
match get_python_version_request_from_pyenv_pin(&std::env::current_exe()?) {
Some(version_request) => version_request,
None => config.default_toolchain()?,
}
} else {
// if neither requested explicitly, nor global-python is enabled, we fall
// back to the next shadowed target
return find_shadowed_target(target, args);
};

let py_ver = match PythonVersion::try_from(version_request.clone()) {
Ok(py_ver) => py_ver,
Err(_) => latest_available_python_version(&version_request)
.ok_or_else(|| anyhow!("Unable to determine target Python version"))?,
};
let py = get_toolchain_python_bin(&py_ver)?;
if !py.is_file() {
bail!(
"Requested Python version ({}) is not installed. Install with `rye fetch {}`",
py_ver,
py_ver
);
}

let mut args = args.to_vec();
args[0] = py.into();
if remove1 {
args.remove(1);
}
return Ok(Some(args));
}

// if we make it this far, we did not find a shim in the project, look for
Expand Down
9 changes: 9 additions & 0 deletions rye/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ impl Config {
})
}

/// Allow rye shims to resolve globally installed Pythons.
pub fn global_python(&self) -> bool {
self.doc
.get("behavior")
.and_then(|x| x.get("global-python"))
.and_then(|x| x.as_bool())
.unwrap_or(false)
}

/// Pretend that all projects are rye managed.
pub fn force_rye_managed(&self) -> bool {
self.doc
Expand Down