Skip to content

Commit

Permalink
Add support for global, non venv Python invocations (#336)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jun 20, 2023
1 parent 10038ba commit 544811a
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 2 deletions.
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('+'))
{
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

0 comments on commit 544811a

Please sign in to comment.