feat(python): Add option to change the python binary (#1297)

* Add option to change the python binary

We are going to start to have problems with the python binary as python2
is removed and replaced with python3. To make the transition easier I
have added an option to the python module to allow the user to pick a
particular binary, e.g `python3`, for the module to use when selecting
the version of python. I have also refactored the python tests moving
almost all of them into the module and removing the dependency on the
version of python that is installed on the system.

* Add advanced config section to python module docs

Have added an advanced config section to the python module docs and
moved the `python_binary` option into that section.
This commit is contained in:
Thomas O'Donnell 2020-06-14 11:27:10 +02:00 committed by GitHub
parent b563d841c7
commit 055986e2b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 187 additions and 204 deletions

View File

@ -1225,6 +1225,25 @@ The module will be shown if any of the following conditions are met:
| `style` | `"bold yellow"` | The style for the module. |
| `disabled` | `false` | Disables the `python` module. |
<details>
<summary>This module has some advanced configuration options.</summary>
| Variable | Default | Description |
| --------------- | -------- | ---------------------------------------------------------------------------- |
| `python_binary` | `python` | Confgures the python binary that Starship executes when getting the version. |
The `python_binary` variable changes the binary that Starship executes to get
the version of Python, it doesn't change the arguments that are used.
```toml
# ~/.config/starship.toml
[python]
python_binary = "python3"
```
</details>
### Example
```toml
@ -1480,7 +1499,7 @@ will simply show all custom modules in the order they were defined.
If unset, it will fallback to STARSHIP_SHELL and then to "sh" on Linux, and "cmd /C" on Windows.
If `shell` is not given or only contains one element and Starship detects PowerShell will be used,
the following arguments will automatically be added: `-NoProfile -Command -`.
the following arguments will automatically be added: `-NoProfile -Command -`.
This behavior can be avoided by explicitly passing arguments to the shell, e.g.
```toml

View File

@ -9,6 +9,7 @@ pub struct PythonConfig<'a> {
pub version: SegmentConfig<'a>,
pub pyenv_prefix: SegmentConfig<'a>,
pub pyenv_version_name: bool,
pub python_binary: &'a str,
pub scan_for_pyfiles: bool,
pub style: Style,
pub disabled: bool,
@ -21,6 +22,7 @@ impl<'a> RootModuleConfig<'a> for PythonConfig<'a> {
version: SegmentConfig::default(),
pyenv_prefix: SegmentConfig::new("pyenv "),
pyenv_version_name: false,
python_binary: "python",
scan_for_pyfiles: true,
style: Color::Yellow.bold(),
disabled: false,

View File

@ -49,7 +49,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
module.create_segment("pyenv_prefix", &config.pyenv_prefix);
module.create_segment("version", &SegmentConfig::new(&python_version.trim()));
} else {
let python_version = get_python_version()?;
let python_version = get_python_version(&config.python_binary)?;
let formatted_version = format_python_version(&python_version);
module.create_segment("version", &SegmentConfig::new(&formatted_version));
};
@ -64,8 +64,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(module)
}
fn get_python_version() -> Option<String> {
match utils::exec_cmd("python", &["--version"]) {
fn get_python_version(python_binary: &str) -> Option<String> {
match utils::exec_cmd(python_binary, &["--version"]) {
Some(output) => {
if output.stdout.is_empty() {
Some(output.stderr)
@ -98,6 +98,10 @@ fn get_python_virtual_env() -> Option<String> {
#[cfg(test)]
mod tests {
use super::*;
use crate::modules::utils::test::render_module;
use ansi_term::Color;
use std::fs::File;
use std::io;
#[test]
fn test_format_python_version() {
@ -110,4 +114,149 @@ mod tests {
let input = "Python 3.6.10 :: Anaconda, Inc.";
assert_eq!(format_python_version(input), "v3.6.10");
}
#[test]
fn folder_without_python_files() -> io::Result<()> {
let dir = tempfile::tempdir()?;
let actual = render_module("python", dir.path(), None);
let expected = None;
assert_eq!(expected, actual);
dir.close()
}
#[test]
fn folder_with_python_version() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join(".python-version"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_requirements_txt() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("requirements.txt"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_pyproject_toml() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("pyproject.toml"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_pipfile() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("Pipfile"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_tox() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("tox.ini"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_setup_py() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("setup.py"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_init_py() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("__init__.py"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn folder_with_py_file() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("main.py"))?.sync_all()?;
check_python2_renders(&dir, None);
check_python3_renders(&dir, None);
dir.close()
}
#[test]
fn disabled_scan_for_pyfiles_and_folder_with_ignored_py_file() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("foo.py"))?.sync_all()?;
let expected = None;
let config = toml::toml! {
[python]
scan_for_pyfiles = false
};
let actual = render_module("python", dir.path(), Some(config));
assert_eq!(expected, actual);
dir.close()
}
#[test]
fn disabled_scan_for_pyfiles_and_folder_with_setup_py() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("setup.py"))?.sync_all()?;
let config = toml::toml! {
[python]
scan_for_pyfiles = false
};
check_python2_renders(&dir, Some(config));
let config_python3 = toml::toml! {
[python]
python_binary = "python3"
scan_for_pyfiles = false
};
check_python3_renders(&dir, Some(config_python3));
dir.close()
}
fn check_python2_renders(dir: &tempfile::TempDir, starship_config: Option<toml::Value>) {
let actual = render_module("python", dir.path(), starship_config);
let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐍 v2.7.17")));
assert_eq!(expected, actual);
}
fn check_python3_renders(dir: &tempfile::TempDir, starship_config: Option<toml::Value>) {
let config = Some(starship_config.unwrap_or(toml::toml! {
[python]
python_binary = "python3"
}));
let actual = render_module("python", dir.path(), config);
let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐍 v3.8.0")));
assert_eq!(expected, actual);
}
}

View File

@ -97,6 +97,14 @@ active boot switches: -d:release\n",
stdout: String::from("0.13.5"),
stderr: String::default(),
}),
"python --version" => Some(CommandOutput {
stdout: String::from("Python 2.7.17"),
stderr: String::default(),
}),
"python3 --version" => Some(CommandOutput {
stdout: String::from("Python 3.8.0"),
stderr: String::default(),
}),
"ruby -v" => Some(CommandOutput {
stdout: String::from("ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]"),
stderr: String::default(),

View File

@ -1,163 +1,12 @@
use std::fs::File;
use std::io;
use ansi_term::Color;
use crate::common;
use crate::common::{self, TestCommand};
// TODO - These tests should be moved into the python module when we have sorted out mocking of env
// vars.
#[test]
fn show_nothing_on_empty_dir() -> io::Result<()> {
let dir = tempfile::tempdir()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = "";
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn folder_with_python_version() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join(".python-version"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn folder_with_requirements_txt() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("requirements.txt"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn folder_with_pyproject_toml() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("pyproject.toml"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn folder_with_pipfile() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("Pipfile"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn folder_with_tox() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("tox.ini"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn folder_with_setup_py() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("setup.py"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
Ok(())
}
#[test]
#[ignore]
fn folder_with_init_py() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("__init__.py"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
Ok(())
}
#[test]
#[ignore]
fn folder_with_py_file() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("main.py"))?.sync_all()?;
let output = common::render_module("python")
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
dir.close()
}
#[test]
#[ignore]
fn with_virtual_env() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("main.py"))?.sync_all()?;
@ -168,13 +17,11 @@ fn with_virtual_env() -> io::Result<()> {
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7 (my_venv)"));
assert_eq!(expected, actual);
assert!(actual.contains("my_venv"));
dir.close()
}
#[test]
#[ignore]
fn with_active_venv() -> io::Result<()> {
let dir = tempfile::tempdir()?;
@ -185,48 +32,6 @@ fn with_active_venv() -> io::Result<()> {
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7 (my_venv)"));
assert_eq!(expected, actual);
assert!(actual.contains("my_venv"));
dir.close()
}
#[test]
fn disabled_scan_for_pyfiles_and_folder_with_ignored_py_file() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("foo.py"))?.sync_all()?;
let output = common::render_module("python")
.use_config(toml::toml! {
[python]
scan_for_pyfiles = false
})
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = "";
assert_eq!(expected, actual);
Ok(())
}
#[test]
#[ignore]
fn disabled_scan_for_pyfiles_and_folder_with_setup_py() -> io::Result<()> {
let dir = tempfile::tempdir()?;
File::create(dir.path().join("setup.py"))?.sync_all()?;
let output = common::render_module("python")
.use_config(toml::toml! {
[python]
scan_for_pyfiles = false
})
.arg("--path")
.arg(dir.path())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let expected = format!("via {} ", Color::Yellow.bold().paint("🐍 v3.7.7"));
assert_eq!(expected, actual);
Ok(())
}