├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1-bug.yml │ ├── 2-feature.md │ └── config.yml └── workflows │ ├── clippy.yml │ ├── docs.yml │ ├── python-format.yml │ ├── python-lint.yml │ ├── release.yml │ ├── rustfmt.yml │ ├── spelling.yml │ ├── sync-python-releases.yml │ ├── sync-uv-releases.yml │ └── tests.yml ├── .gitignore ├── .python-version ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── artwork ├── badge.json ├── logo-white.svg ├── logo.ai └── logo.svg ├── docs ├── .includes │ ├── curl-to-bash-options.md │ ├── installer-options.md │ └── quick-install.md ├── .overrides │ └── main.html ├── CNAME ├── changelog.md ├── community.md ├── guide │ ├── basics.md │ ├── commands │ │ ├── add.md │ │ ├── build.md │ │ ├── config.md │ │ ├── fetch.md │ │ ├── fmt.md │ │ ├── index.md │ │ ├── init.md │ │ ├── install.md │ │ ├── lint.md │ │ ├── list.md │ │ ├── lock.md │ │ ├── make-req.md │ │ ├── pin.md │ │ ├── publish.md │ │ ├── remove.md │ │ ├── run.md │ │ ├── self │ │ │ ├── completion.md │ │ │ ├── index.md │ │ │ ├── uninstall.md │ │ │ └── update.md │ │ ├── show.md │ │ ├── sync.md │ │ ├── test.md │ │ ├── toolchain │ │ │ ├── fetch.md │ │ │ ├── index.md │ │ │ ├── list.md │ │ │ ├── register.md │ │ │ └── remove.md │ │ ├── tools │ │ │ ├── index.md │ │ │ ├── install.md │ │ │ ├── list.md │ │ │ └── uninstall.md │ │ ├── uninstall.md │ │ └── version.md │ ├── config.md │ ├── deps.md │ ├── docker.md │ ├── faq.md │ ├── index.md │ ├── installation.md │ ├── publish.md │ ├── pyproject.md │ ├── rust.md │ ├── shims.md │ ├── sources.md │ ├── sync.md │ ├── toolchains │ │ ├── cpython.md │ │ ├── index.md │ │ └── pypy.md │ ├── tools.md │ ├── virtual.md │ └── workspaces.md ├── hooks.py ├── index.md ├── philosophy.md └── static │ ├── banner-dark.png │ ├── banner.png │ ├── extra.css │ ├── favicon.svg │ ├── logo-auto.svg │ └── logo.svg ├── mkdocs.yml ├── notes ├── README.md ├── markers.md ├── metasrv.md └── pep508.md ├── pyproject.toml ├── requirements-dev.lock ├── requirements.lock ├── rust-toolchain.toml ├── rye-devtools ├── .python-version ├── pyproject.toml ├── src │ └── rye_devtools │ │ ├── __init__.py │ │ ├── common.py │ │ ├── find_downloads.py │ │ └── find_uv_downloads.py └── tests │ └── test_basic.py ├── rye ├── .gitignore ├── Cargo.toml ├── build.rs ├── src │ ├── bootstrap.rs │ ├── cli │ │ ├── add.rs │ │ ├── build.rs │ │ ├── config.rs │ │ ├── fetch.rs │ │ ├── fmt.rs │ │ ├── init.rs │ │ ├── install.rs │ │ ├── lint.rs │ │ ├── list.rs │ │ ├── lock.rs │ │ ├── make_req.rs │ │ ├── mod.rs │ │ ├── pin.rs │ │ ├── publish.rs │ │ ├── remove.rs │ │ ├── run.rs │ │ ├── rye.rs │ │ ├── shim.rs │ │ ├── show.rs │ │ ├── sync.rs │ │ ├── test.rs │ │ ├── toolchain.rs │ │ ├── tools.rs │ │ ├── uninstall.rs │ │ └── version.rs │ ├── config.rs │ ├── consts.rs │ ├── installer.rs │ ├── lock.rs │ ├── main.rs │ ├── platform.rs │ ├── pyproject.rs │ ├── sources │ │ ├── generated │ │ │ ├── python_downloads.inc │ │ │ └── uv_downloads.inc │ │ ├── mod.rs │ │ ├── py.rs │ │ └── uv.rs │ ├── sync.rs │ ├── templates │ │ ├── LICENSE.txt.j2 │ │ ├── README.md.j2 │ │ ├── gitignore.j2 │ │ ├── lib │ │ │ ├── default │ │ │ │ └── __init__.py.j2 │ │ │ └── maturin │ │ │ │ ├── Cargo.toml.j2 │ │ │ │ ├── __init__.py.j2 │ │ │ │ └── lib.rs.j2 │ │ ├── pyproject.toml.j2 │ │ ├── script │ │ │ └── default │ │ │ │ ├── __init__.py.j2 │ │ │ │ └── __main__.py.j2 │ │ └── setuptools.py.j2 │ ├── tui.rs │ ├── utils │ │ ├── mod.rs │ │ ├── panic.rs │ │ ├── ruff.rs │ │ ├── toml.rs │ │ ├── unix.rs │ │ └── windows.rs │ └── uv.rs └── tests │ ├── common │ └── mod.rs │ ├── test-list.rs │ ├── test_add.rs │ ├── test_cli.rs │ ├── test_config.rs │ ├── test_init.rs │ ├── test_publish.rs │ ├── test_ruff.rs │ ├── test_scripts.rs │ ├── test_self.rs │ ├── test_sync.rs │ ├── test_test.rs │ ├── test_toolchain.rs │ ├── test_tools.rs │ └── test_version.rs └── scripts ├── cargo-out-dir ├── install.sh ├── sha256.py └── summarize-release.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mitsuhiko] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report something not working correctly. 3 | body: 4 | - type: textarea 5 | id: repro 6 | attributes: 7 | label: Steps to Reproduce 8 | description: How can we see what you're seeing? Specific is terrific. 9 | placeholder: |- 10 | 1. foo 11 | 2. bar 12 | 3. baz 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: expected 17 | attributes: 18 | label: Expected Result 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: actual 23 | attributes: 24 | label: Actual Result 25 | description: Logs? Screenshots? Yes, please. 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: version-info 30 | attributes: 31 | label: Version Info 32 | description: Paste the output of `rye --version` 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: stacktrace 37 | attributes: 38 | label: Stacktrace 39 | description: If rye crashed, run it again with `RUST_BACKTRACE=1` set as environment variable and share the output. 40 | validations: 41 | required: false 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Suggestion 3 | about: Want to report a suggestion for a feature? 4 | --- 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discussions 4 | url: https://github.com/astral-sh/rye/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: Swatinem/rust-cache@v2 16 | - name: "Install Rust toolchain" 17 | run: rustup component add clippy 18 | - name: Run clippy 19 | run: make lint 20 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish docs via GitHub Pages 2 | on: 3 | push: 4 | paths: 5 | - 'docs/**' 6 | - 'CHANGELOG.md' 7 | - 'mkdocs.yml' 8 | - 'scripts/install.sh' 9 | branches: 10 | - main 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | if: github.repository == 'astral-sh/rye' 16 | name: Deploy docs 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout main 20 | uses: actions/checkout@v2 21 | - uses: Swatinem/rust-cache@v2 22 | - name: Deploy docs 23 | uses: mhausenblas/mkdocs-deploy-gh-pages@master 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | CONFIG_FILE: mkdocs.yml 27 | REQUIREMENTS: ./requirements.lock 28 | -------------------------------------------------------------------------------- /.github/workflows/python-format.yml: -------------------------------------------------------------------------------- 1 | name: Python format 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | format: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: eifinger/setup-rye@v2 17 | - name: Rye fmt 18 | run: rye fmt --check -------------------------------------------------------------------------------- /.github/workflows/python-lint.yml: -------------------------------------------------------------------------------- 1 | name: Python lint 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: eifinger/setup-rye@v2 17 | - name: Rye lint 18 | run: rye lint 19 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: dtolnay/rust-toolchain@master 16 | with: 17 | toolchain: stable 18 | components: clippy, rustfmt 19 | - uses: Swatinem/rust-cache@v2 20 | - name: Run rustfmt 21 | run: make format-check 22 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | typos: 11 | name: "Spell check" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: crate-ci/typos@master 16 | with: 17 | files: . 18 | -------------------------------------------------------------------------------- /.github/workflows/sync-python-releases.yml: -------------------------------------------------------------------------------- 1 | # For this action to work you must explicitly allow GitHub Actions to create pull requests. 2 | # This setting can be found in a repository's settings under Actions > General > Workflow permissions. 3 | # For repositories belonging to an organization, this setting can be managed by 4 | # admins in organization settings under Actions > General > Workflow permissions. 5 | name: Sync Python Releases 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | 15 | jobs: 16 | sync: 17 | if: github.repository == 'astral-sh/rye' 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Install Rye 22 | uses: eifinger/setup-rye@v2 23 | with: 24 | enable-cache: true 25 | - name: Sync Python Releases 26 | run: make sync-python-releases 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Create PR 30 | uses: peter-evans/create-pull-request@v6 31 | with: 32 | commit-message: "Sync latest Python releases" 33 | add-paths: "rye/src/sources/generated/python_downloads.inc" 34 | branch: "sync-python-releases" 35 | title: "Sync Python Releases" 36 | body: | 37 | - Synced latest Python releases 38 | 39 | Auto-generated by [sync-python-releases.yml](https://github.com/astral-sh/rye/blob/main/.github/workflows/sync-python-releases.yml) 40 | -------------------------------------------------------------------------------- /.github/workflows/sync-uv-releases.yml: -------------------------------------------------------------------------------- 1 | # For this action to work you must explicitly allow GitHub Actions to create pull requests. 2 | # This setting can be found in a repository's settings under Actions > General > Workflow permissions. 3 | # For repositories belonging to an organization, this setting can be managed by 4 | # admins in organization settings under Actions > General > Workflow permissions. 5 | name: Sync UV Releases 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | 15 | jobs: 16 | sync: 17 | if: github.repository == 'astral-sh/rye' 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Install Rye 22 | uses: eifinger/setup-rye@v2 23 | with: 24 | enable-cache: true 25 | - name: Sync UV Releases 26 | run: make sync-uv-releases 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Create PR 30 | uses: peter-evans/create-pull-request@v6 31 | with: 32 | commit-message: "Sync latest UV releases" 33 | add-paths: "rye/src/sources/generated/uv_downloads.inc" 34 | branch: "sync-uv-releases" 35 | title: "Sync UV Releases" 36 | body: | 37 | - Synced latest UV releases 38 | 39 | Auto-generated by [sync-uv-releases.yml](https://github.com/astral-sh/rye/blob/main/.github/workflows/sync-uv-releases.yml) 40 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | 9 | jobs: 10 | test-latest-linux: 11 | name: Test (Linux) 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: Swatinem/rust-cache@v2 17 | - name: "Install Rust toolchain" 18 | run: rustup show 19 | - name: Install cargo insta 20 | uses: taiki-e/install-action@v2 21 | with: 22 | tool: cargo-insta 23 | - name: Check 24 | run: make check 25 | - name: Test 26 | run: make test 27 | 28 | test-latest-macos: 29 | name: Test (macOS) 30 | runs-on: macos-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: Swatinem/rust-cache@v2 35 | - name: "Install Rust toolchain" 36 | run: rustup show 37 | - name: Install cargo insta 38 | uses: taiki-e/install-action@v2 39 | with: 40 | tool: cargo-insta 41 | - name: Check 42 | run: make check 43 | - name: Test 44 | run: make test 45 | 46 | test-latest-windows: 47 | name: Test (Windows) 48 | runs-on: windows-latest 49 | 50 | steps: 51 | - uses: actions/checkout@v4 52 | - uses: Swatinem/rust-cache@v2 53 | - name: "Install Rust toolchain" 54 | run: rustup show 55 | - name: Install cargo insta 56 | uses: taiki-e/install-action@v2 57 | with: 58 | tool: cargo-insta 59 | - name: Check 60 | run: make check 61 | - name: Test 62 | run: make test 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .venv 3 | .env 4 | /x 5 | site 6 | __pycache__ 7 | .idea 8 | token.txt 9 | dist 10 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11.1 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.check.command": "clippy" 3 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["rye"] 3 | resolver = "1" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023, Armin Ronacher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: test 3 | 4 | .PHONY: build 5 | build: 6 | @cargo build --all 7 | 8 | .PHONY: test 9 | test: 10 | @cargo insta test --workspace --all-features 11 | 12 | .PHONY: check 13 | check: 14 | @cargo check --all 15 | 16 | .PHONY: format 17 | format: 18 | @cargo fmt --all 19 | 20 | .PHONY: format-check 21 | format-check: 22 | @cargo fmt --all -- --check 23 | 24 | .PHONY: serve-docs 25 | serve-docs: .venv 26 | @rye run serve-docs 27 | 28 | .PHONY: lint 29 | lint: 30 | @cargo clippy --all -- -D clippy::dbg-macro -D warnings 31 | 32 | .venv: 33 | @rye sync 34 | 35 | .PHONY: sync-python-releases 36 | sync-python-releases: .venv 37 | @rye run find-downloads > rye/src/sources/generated/python_downloads.inc 38 | 39 | .PHONY: sync-uv-releases 40 | sync-uv-releases: .venv 41 | @rye run uv-downloads > rye/src/sources/generated/uv_downloads.inc 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Rye: a Hassle-Free Python Experience

4 |
5 | 6 | ---- 7 |
8 | 9 | [![Rye](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/rye/main/artwork/badge.json)](https://rye.astral.sh) 10 | [![](https://dcbadge.vercel.app/api/server/drbkcdtSbg?style=flat)](https://discord.gg/drbkcdtSbg) 11 | 12 |
13 | 14 | > [!NOTE] 15 | > 16 | > If you're getting started with Rye, consider [uv](https://github.com/astral-sh/uv), the 17 | > [successor project](https://lucumr.pocoo.org/2024/2/15/rye-grows-with-uv/) from the same maintainers. 18 | > 19 | > While Rye is actively maintained, uv offers a more stable and feature-complete experience, and is the recommended 20 | > choice for new projects. 21 | > 22 | > Having trouble migrating? [Let us know what's missing.](https://github.com/astral-sh/rye/discussions/1342) 23 | 24 | Rye is a comprehensive project and package management solution for Python. 25 | Born from [its creator's](https://github.com/mitsuhiko) desire to establish a 26 | one-stop-shop for all Python users, Rye provides a unified experience to install and manage Python 27 | installations, `pyproject.toml` based projects, dependencies and virtualenvs 28 | seamlessly. It's designed to accommodate complex projects, monorepos and to 29 | facilitate global tool installations. Curious? [Watch an introduction](https://youtu.be/q99TYA7LnuA). 30 | 31 | A hassle-free experience for Python developers at every level. 32 | 33 |
34 | 35 | Watch the instruction 36 | 37 |

Click on the thumbnail to watch a 16 minute introduction video

38 |
39 | 40 | ## In The Box 41 | 42 | Rye picks and ships the right tools so you can get started in minutes: 43 | 44 | * **Bootstraps Python:** it provides an automated way to get access to the amazing [Indygreg Python Builds](https://github.com/indygreg/python-build-standalone/) as well as the PyPy binary distributions. 45 | * **Linting and Formatting:** it bundles [ruff](https://github.com/astral-sh/ruff) and makes it available with `rye lint` and `rye fmt`. 46 | * **Managing Virtualenvs:** it uses the well established virtualenv library under the hood. 47 | * **Building Wheels:** it delegates that work largely to [build](https://pypi.org/project/build/). 48 | * **Publishing:** its publish command uses [twine](https://pypi.org/project/twine/) to accomplish this task. 49 | * **Locking and Dependency Installation:** is today implemented by using [uv](https://github.com/astral-sh/uv) with a fallback to [unearth](https://pypi.org/project/unearth/) and [pip-tools](https://github.com/jazzband/pip-tools/). 50 | * **Workspace support:** Rye lets you work with complex projects consisting 51 | of multiple libraries. 52 | 53 | ## Installation 54 | 55 | The installation takes just a minute: 56 | 57 | * **Linux and macOS:** 58 | 59 | ``` 60 | curl -sSf https://rye.astral.sh/get | bash 61 | ``` 62 | 63 | * **Windows:** 64 | 65 | Download and run the installer ([64-bit (x86-64)](https://github.com/astral-sh/rye/releases/latest/download/rye-x86_64-windows.exe) or [32-bit (x86)](https://github.com/astral-sh/rye/releases/latest/download/rye-x86-windows.exe)). 66 | 67 | For more details and other options, refer to the [installation instructions](https://rye.astral.sh/guide/installation/). 68 | 69 | ## Learn More 70 | 71 | Did I spark your interest? 72 | 73 | * [Visit the Website](https://rye.astral.sh/) 74 | * [Read the Documentation](https://rye.astral.sh/guide/) 75 | * [Report Problems in the Issue Tracker](https://github.com/astral-sh/rye/issues) 76 | 77 | ## More 78 | 79 | * [Discussion Forum](https://github.com/astral-sh/rye/discussions), to discuss the project 80 | on GitHub 81 | * [Discord](https://discord.gg/drbkcdtSbg), for conversations with other developers in text form 82 | * [Issue Tracker](https://github.com/astral-sh/rye/issues), if you run into bugs or have suggestions 83 | * [Badges](https://rye.astral.sh/community/#badges), if you want to show that you use Rye 84 | * License: MIT 85 | -------------------------------------------------------------------------------- /artwork/badge.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "", 3 | "message": "Rye", 4 | "logoSvg": "", 5 | "logoWidth": 12, 6 | "labelColor": "white", 7 | "color": "#ADC541" 8 | } -------------------------------------------------------------------------------- /artwork/logo-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /artwork/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/rye/e213938342cd08553a7672a417c4fa821c4d7172/artwork/logo.ai -------------------------------------------------------------------------------- /artwork/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.includes/curl-to-bash-options.md: -------------------------------------------------------------------------------- 1 | The install script that is piped to `bash` can be customized with some environment 2 | variables: 3 | 4 | `RYE_VERSION` 5 | 6 | : Defaults to `latest`. Can be set to an explicit version to install a specific one. 7 | 8 | `RYE_INSTALL_OPTION` 9 | 10 | : Can optionally be set to `"--yes"` to skip all prompts. 11 | 12 | {% include-markdown "../.includes/installer-options.md" %} 13 | 14 | This for instance installs a specific version of Rye without asking questions: 15 | 16 | ```bash 17 | curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.4.0" RYE_INSTALL_OPTION="--yes" bash 18 | ``` -------------------------------------------------------------------------------- /docs/.includes/installer-options.md: -------------------------------------------------------------------------------- 1 | `RYE_TOOLCHAIN` 2 | 3 | : Optionally this environment variable can be set to point to a Python 4 | interpreter that should be used as the internal interpreter. If not 5 | provided a suitable interpreter is automatically downloaded. 6 | 7 | At present only CPython 3.9 to 3.12 are supported. 8 | 9 | `RYE_TOOLCHAIN_VERSION` 10 | 11 | : For Rye 0.22 and later a specific Python version can be picked rather 12 | than the default. This affects the internal toolchain version only. 13 | It's useful for Docker builds where you can set the internal toolchain 14 | to the same as your project to only fetch a single Python. 15 | 16 | At present only CPython 3.9 to 3.12 are supported. 17 | -------------------------------------------------------------------------------- /docs/.includes/quick-install.md: -------------------------------------------------------------------------------- 1 | === "Linux" 2 | 3 | To install you can run a curl command which will install the right binary for your 4 | operating system and CPU architecture and install it: 5 | 6 | ```bash 7 | curl -sSf https://rye.astral.sh/get | bash 8 | ``` 9 | 10 | Alternatively if you don't trust this approach, you can download the latest release 11 | binary. On first run it will install itself. 12 | 13 | * [rye-x86_64-linux.gz](https://github.com/astral-sh/rye/releases/latest/download/rye-x86_64-linux.gz) Intel/AMD (x86-64). 14 | * [rye-aarch64-linux.gz](https://github.com/astral-sh/rye/releases/latest/download/rye-aarch64-linux.gz) for ARM64. 15 | 16 | ```bash 17 | gunzip rye-x86_64-linux.gz 18 | chmod +x ./rye-x86_64-linux 19 | ./rye-x86_64-linux 20 | ``` 21 | 22 | === "macOS" 23 | 24 | To install you can run a curl command which will install the right binary for your 25 | operating system and CPU architecture and install it: 26 | 27 | ```bash 28 | curl -sSf https://rye.astral.sh/get | bash 29 | ``` 30 | 31 | Alternatively if you don't trust this approach, you can download the latest release 32 | binary. On first run it will install itself. 33 | 34 | * [rye-aarch64-macos.gz](https://github.com/astral-sh/rye/releases/latest/download/rye-aarch64-macos.gz) for Apple Silicon (M1/M2/M3) (ARM64). 35 | * [rye-x86_64-macos.gz](https://github.com/astral-sh/rye/releases/latest/download/rye-x86_64-macos.gz) for Intel processors (x86-64). 36 | 37 | ```bash 38 | gunzip rye-aarch64-macos.gz 39 | chmod +x ./rye-aarch64-macos 40 | ./rye-aarch64-macos 41 | ``` 42 | 43 | === "Windows" 44 | 45 | To install Rye on windows download the latest release and run the binary. Upon 46 | first run it will install itself. Please note that it's strongly recommended 47 | to have "Developer Mode" activated when using Rye and before starting the 48 | installation. [Learn more](../guide/faq.md). 49 | 50 | * [rye-x86_64-windows.exe](https://github.com/astral-sh/rye/releases/latest/download/rye-x86_64-windows.exe) for 64-bit (x86-64). 51 | * [rye-x86-windows.exe](https://github.com/astral-sh/rye/releases/latest/download/rye-x86-windows.exe) for 32-bit (x86). 52 | 53 | !!!Note 54 | 55 | Rye does not yet use signed binaries which means that you will need to allow 56 | the execution of the downloaded executable. If there is no obvious way to do so, click 57 | on "More info" on the error message that shows up and then on "Run anyway". 58 | 59 | Additionally sometimes a Trojan warning about "Bearfoos" is shown. This is a false 60 | positive. For more information see the discussion [Windows Bearfoos 61 | virus associated with rye](https://github.com/astral-sh/rye/issues/468). 62 | 63 | === "Compile Yourself" 64 | 65 | You need to have Rust and Cargo installed. If you don't have, you can use 66 | [rustup](https://rustup.rs/) to get them onto your machine. 67 | 68 | Afterwards you can install `Rye` via `cargo`: 69 | 70 | ```bash 71 | cargo install --git https://github.com/astral-sh/rye rye 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/.overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block hero %} 4 | 16 | {% endblock %} 17 | 18 | {% block extrahead %} 19 | {{ super() }} 20 | 50 | {% endblock %} -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | rye.astral.sh 2 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Here you can find all the released changes to Rye. If you want to also see 4 | the in-development changes that were not released yet, refer to the 5 | [CHANGELOG.md](https://github.com/astral-sh/rye/blob/main/CHANGELOG.md) file 6 | in the repository. 7 | 8 | {% 9 | include-markdown "../CHANGELOG.md" 10 | start="" 11 | %} 12 | -------------------------------------------------------------------------------- /docs/community.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | --- 5 | 6 | # Community 7 | 8 | Rye is a new project and feedback is greatly appreciated. Lots of it. Because 9 | of this there are various different ways in which you can engage with either 10 | the developer or other members of the community: 11 | 12 | * [Discussion Forum](https://github.com/astral-sh/rye/discussions), to discuss the project 13 | on GitHub 14 | * [Discord](https://discord.gg/drbkcdtSbg), for conversations with other developers in text form 15 | * [Issue Tracker](https://github.com/astral-sh/rye/issues), if you run into bugs or have suggestions 16 | 17 | You can also reach out [via Twitter](https://twitter.com/mitsuhiko) or 18 | [Bluesky](https://bsky.app/profile/mitsuhiko.at). 19 | 20 | ## Badges 21 | 22 | Want to show that you are using Rye? Why not throw a badge into your project's `README.md`: 23 | 24 | ```md 25 | [![Rye](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/rye/main/artwork/badge.json)](https://rye.astral.sh) 26 | ``` 27 | 28 | ... or `README.rst`: 29 | 30 | ```rst 31 | .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/rye/main/artwork/badge.json 32 | :target: https://rye.astral.sh 33 | :alt: Rye 34 | ``` 35 | 36 | ... or, as HTML: 37 | 38 | ```html 39 | Rye 40 | ``` 41 | 42 | [![Rye](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/rye/main/artwork/badge.json)](https://rye.astral.sh) 43 | -------------------------------------------------------------------------------- /docs/guide/commands/add.md: -------------------------------------------------------------------------------- 1 | # `add` 2 | 3 | Adds a Python package to this project. The command takes a PEP 508 requirement string 4 | but provides additional helper arguments to make this process more user friendly. For 5 | instance instead of passing git references within the requirement string, the `--git` 6 | parameter can be used. 7 | 8 | If auto sync is disabled, after a dependency is added it's not automatically 9 | installed. To do that, you need to invoke the [`sync`](sync.md) command or pass 10 | `--sync`. To remove a dependency again use the [`remove`](remove.md) command. 11 | 12 | +++ 0.26.0 13 | 14 | Added support for auto-sync and the `--sync` / `--no-sync` flags. 15 | 16 | ## Example 17 | 18 | Add the latest version of a dependency that is compatible with the configured Python version: 19 | 20 | ``` 21 | $ rye add flask 22 | Added flask>=3.0.1 as regular dependency 23 | ``` 24 | 25 | Add a dependency but add an optional extra feature: 26 | 27 | ``` 28 | $ rye add flask --features dotenv 29 | Added flask[dotenv]>=3.0.1 as regular dependency 30 | ``` 31 | 32 | Add a git dependency: 33 | 34 | ``` 35 | $ rye add flask --git https://github.com/pallets/flask 36 | Added flask @ git+https://github.com/pallets/flask as regular dependency 37 | ``` 38 | 39 | Add a local dependency: 40 | 41 | ``` 42 | $ rye add packagename --path path/to/packagename 43 | Added packagename @ file:///path/to/packagename as regular dependency 44 | ``` 45 | 46 | ## Arguments 47 | 48 | * `...`: The package to add as PEP 508 requirement string. e.g. 'flask==2.2.3' 49 | 50 | ## Options 51 | 52 | * `--git `: Install the given package from this git repository 53 | 54 | * `--url `: Install the given package from this URL 55 | 56 | * `--path `: Install the given package from this local path 57 | 58 | * `--absolute`: Force non interpolated absolute paths 59 | 60 | * `--tag `: Install a specific tag 61 | 62 | * `--rev `: Update to a specific git rev 63 | 64 | * `--branch `: Update to a specific git branch 65 | 66 | * `--features `: Adds a dependency with a specific feature 67 | 68 | * `--dev`: Add this as dev dependency 69 | 70 | * `--excluded`: Add this as an excluded dependency that will not be installed even if it's a sub dependency 71 | 72 | * `--optional `: Add this to an optional dependency group 73 | 74 | * `--pre`: Include pre-releases when finding a package version 75 | 76 | * `--pin `: Overrides the pin operator [possible values: `equal`, `tilde-equal`, `greater-than-equal`] 77 | 78 | * `--sync`: Runs `sync` automatically even if auto-sync is disabled. 79 | 80 | * `--no-sync`: Does not run `sync` automatically even if auto-sync is enabled. 81 | 82 | * `-v, --verbose`: Enables verbose diagnostics 83 | 84 | * `-q, --quiet`: Turns off all output 85 | 86 | * `-h, --help`: Print help (see a summary with `-h`) 87 | -------------------------------------------------------------------------------- /docs/guide/commands/build.md: -------------------------------------------------------------------------------- 1 | # `build` 2 | 3 | Builds a package for distribution. 4 | 5 | Under normal circumstances Rye automatically builds packages for 6 | local development. However if you want to publish packages you need 7 | to first build them into source distributions (`sdist`) and 8 | binary/portable distributions (`wheel`). 9 | 10 | For more information see [Building and Publishing](../publish.md). 11 | 12 | ## Example 13 | 14 | This builds wheels and source distributions at once: 15 | 16 | ``` 17 | $ rye build 18 | building my-project 19 | * Creating virtualenv isolated environment... 20 | * Installing packages in isolated environment... (hatchling) 21 | * Getting build dependencies for sdist... 22 | * Building sdist... 23 | * Building wheel from sdist 24 | * Creating virtualenv isolated environment... 25 | * Installing packages in isolated environment... (hatchling) 26 | * Getting build dependencies for wheel... 27 | * Building wheel... 28 | Successfully built my_project-0.1.0.tar.gz and my_project-0.1.0-py3-none-any.whl 29 | ``` 30 | 31 | By default you will find the artifacts in the `dist` folder. 32 | 33 | ## Arguments 34 | 35 | *no arguments* 36 | 37 | ## Options 38 | 39 | * `--sdist`: Build an sdist 40 | 41 | * `--wheel`: Build a wheel 42 | 43 | * `-a, --all`: Build all packages 44 | 45 | * `-p, --package `: Build a specific package 46 | 47 | * `-o, --out `: An output directory (defaults to `workspace/dist`) 48 | 49 | * `--pyproject `: Use this `pyproject.toml` file 50 | 51 | * `-c, --clean`: Clean the output directory first 52 | 53 | * `-v, --verbose`: Enables verbose diagnostics 54 | 55 | * `-q, --quiet`: Turns off all output 56 | 57 | * `-h, --help`: Print help 58 | -------------------------------------------------------------------------------- /docs/guide/commands/config.md: -------------------------------------------------------------------------------- 1 | # `config` 2 | 3 | Reads or modifies the global `config.toml` file. 4 | 5 | The config file can be read via `--get` and it can be set with one of the set options (`--set`, `--set-int`, 6 | `--set-bool`, or `--unset`). Each of the set operations takes a key=value pair. All of these can be supplied 7 | multiple times. 8 | 9 | ## Example 10 | 11 | This command turns on global shims: 12 | 13 | ``` 14 | rye config --set-bool behavior.global-python=true 15 | ``` 16 | 17 | Reads the currently set config value for global Python shims: 18 | 19 | ``` 20 | $ rye config --get behavior.global-python 21 | true 22 | ``` 23 | 24 | Show the path to the config: 25 | 26 | ``` 27 | $ rye config --show-path 28 | /Users/username/.rye/config.toml 29 | ``` 30 | 31 | ## Arguments 32 | 33 | *no arguments* 34 | 35 | ## Options 36 | 37 | * `--get `: Reads a config key 38 | 39 | * `--set `: Sets a config key to a string 40 | 41 | * `--set-int `: Sets a config key to an integer 42 | 43 | * `--set-bool `: Sets a config key to a bool 44 | 45 | * `--unset `: Remove a config key 46 | 47 | * `--show-path`: Print the path to the config 48 | 49 | * `--format `: Request parseable output format rather than lines 50 | 51 | [possible values: json] 52 | 53 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/fetch.md: -------------------------------------------------------------------------------- 1 | # `fetch` 2 | 3 | Fetches a Python interpreter for the local machine. This command is 4 | available under the aliases `rye fetch` and `rye toolchain fetch`. 5 | 6 | As of Rye 0.31.0 toolchains are always fetched without build info. This 7 | means that in the folder where toolchains are stored only the interpreter 8 | is found. For more information see [Fetching Toolchains](../toolchains/index.md#build-info). 9 | 10 | ## Example 11 | 12 | Fetch a specific version of Python: 13 | 14 | ``` 15 | $ rye fetch 3.8.13 16 | Downloading cpython@3.8.13 17 | Checking checksum 18 | Unpacking 19 | Downloaded cpython@3.8.13 20 | ``` 21 | 22 | To fetch the pinned version of Python you can leave out the argument: 23 | 24 | ``` 25 | $ rye fetch 26 | Downloading cpython@3.8.17 27 | Checking checksum 28 | Unpacking 29 | Downloaded cpython@3.8.17 30 | ``` 31 | 32 | To fetch a version of Python into a specific location rather than rye's 33 | interpreter cache: 34 | 35 | ``` 36 | $ rye fetch cpython@3.9.1 --target-path=my-interpreter 37 | ``` 38 | 39 | ## Arguments 40 | 41 | * `[VERSION]`: The version of Python to fetch. 42 | 43 | If no version is provided, the requested version will be fetched. 44 | 45 | ## Options 46 | 47 | * `-f, --force`: Fetch the Python toolchain even if it is already installed. 48 | 49 | * `--target-path` ``: Fetches the Python toolchain into an explicit location rather 50 | 51 | * `--build-info`: Fetches with build info 52 | 53 | * `--no-build-info`: Fetches without build info 54 | 55 | * `-v, --verbose`: Enables verbose diagnostics 56 | 57 | * `-q, --quiet`: Turns off all output 58 | 59 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/fmt.md: -------------------------------------------------------------------------------- 1 | # `fmt` 2 | 3 | +++ 0.20.0 4 | 5 | Run the code formatter on the project. This command is aliased to `format`. 6 | 7 | For more information about how to configure Ruff, have a look at the 8 | [Ruff Configuration Documentation](https://docs.astral.sh/ruff/configuration/). 9 | 10 | ## Example 11 | 12 | To format the code and write back to the files: 13 | 14 | ``` 15 | $ rye fmt 16 | 1 file reformatted, 231 files left unchanged 17 | ``` 18 | 19 | To just check if the code needs formatting: 20 | 21 | ``` 22 | $ rye fmt --check 23 | Would reformat: src/my_project/utils.py 24 | 1 file would be reformatted, 231 files already formatted 25 | ``` 26 | 27 | To pass extra arguments to the underlying `ruff` formatter use `--`: 28 | 29 | ``` 30 | $ rye fmt -- --diff 31 | --- src/my_project/utils.py 32 | +++ src/my_project/utils.py 33 | @@ -2,5 +2,4 @@ 34 | 35 | 36 | def foo(): 37 | - 38 | pass 39 | 40 | 1 file would be reformatted, 231 files already formatted 41 | ``` 42 | 43 | Format a specific file: 44 | 45 | ``` 46 | rye fmt src/foo.py 47 | ``` 48 | 49 | ## Arguments 50 | 51 | * `[PATHS]...` List of files or directories to lint. If not supplied all files are formatted. 52 | 53 | * `[EXTRA_ARGS]...` Extra arguments to the formatter. 54 | 55 | These arguments are forwarded directly to the underlying formatter (currently 56 | always `ruff`). Note that extra arguments must be separated from other arguments 57 | with the `--` marker. 58 | 59 | ## Options 60 | 61 | * `-a, --all`: Format all packages in the workspace 62 | 63 | * `-p, --package `: Format a specific package 64 | 65 | * `--pyproject `: Use this `pyproject.toml` file 66 | 67 | * `--check`: Run format in check mode 68 | 69 | * `-v, --verbose`: Enables verbose diagnostics 70 | 71 | * `-q, --quiet`: Turns off all output 72 | 73 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/index.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | This is a list of all the commands that rye provides: 4 | 5 | * [add](add.md): Adds a Python package to this project 6 | * [build](build.md): Builds a package for distribution 7 | * [config](config.md): Reads or updates the Rye configuration 8 | * [fetch](fetch.md): Fetches a Python interpreter for the local machine (alias) 9 | * [fmt](fmt.md): Run the code formatter on the project 10 | * [init](init.md): Initializes a new project 11 | * [install](install.md): Installs a global tool (alias) 12 | * [lock](lock.md): Updates the lockfiles without installing dependencies 13 | * [lint](lint.md): Run the linter on the project 14 | * [make-req](make-req.md): Builds and prints a PEP 508 requirement string from parts 15 | * [pin](pin.md): Pins a Python version to the project 16 | * [publish](publish.md): Publish packages to a package repository 17 | * [remove](remove.md): Remove a dependency from this project 18 | * [run](run.md): Runs a command installed into this package 19 | * [show](show.md): Prints the current state of the project 20 | * [sync](sync.md): Updates the virtualenv based on the pyproject.toml 21 | * [test](test.md): Runs the project's tests 22 | * [toolchain](toolchain/index.md): Helper utility to manage Python toolchains 23 | * [tools](tools/index.md): Helper utility to manage global tools. 24 | * [self](self/index.md): Rye self management 25 | * [uninstall](uninstall.md): Uninstalls a global tool (alias) 26 | * [version](version.md): Get or set project version 27 | 28 | ## Options 29 | 30 | The toplevel `rye` command accepts the following options: 31 | 32 | * `--env-file` ``: This can be supplied multiple times to make rye load 33 | a given `.env` file. Note that this file is not referenced to handle the 34 | `RYE_HOME` variable which must be supplied as environment variable always. -------------------------------------------------------------------------------- /docs/guide/commands/init.md: -------------------------------------------------------------------------------- 1 | # `init` 2 | 3 | This command initializes a new or existing Python project with Rye. Running it in 4 | a folder with an already existing Python project will attempt to convert it over 5 | and bootstrap Rye right there. Otherwise it can be used to create a completely new 6 | project from scratch. 7 | 8 | For more information see the [Basics Guide](../basics.md). 9 | 10 | ## Example 11 | 12 | ``` 13 | $ rye init 14 | success: Initialized project in /Users/john/Development/my-project. 15 | Run `rye sync` to get started 16 | ``` 17 | 18 | ## Arguments 19 | 20 | * `[PATH]`: Where to place the project (defaults to current path) 21 | 22 | ## Options 23 | 24 | * `--min-py `: Minimal Python version supported by this project 25 | 26 | * `-p, --py `: Python version to use for the virtualenv 27 | 28 | * `--no-readme`: Do not create a readme 29 | 30 | * `--no-pin`: Do not create .python-version file (requires-python will be used) 31 | 32 | * `--build-system `: Which build system should be used(defaults to hatchling)? 33 | 34 | [possible values: `hatchling`, `setuptools`, `flit`, `pdm`, `maturin`] 35 | 36 | * `--license `: Which license should be used? [SPDX identifier](https://spdx.org/licenses/) 37 | 38 | * `--name `: The name of the package 39 | 40 | * `--private`: Set "Private :: Do Not Upload" classifier, used for private projects 41 | 42 | * `--no-import`: Don't import from setup.cfg, setup.py, or requirements files 43 | 44 | * `--virtual`: Initialize this as a virtual package. 45 | 46 | A virtual package can have dependencies but is itself not installed as a Python package. It also cannot be published. 47 | 48 | * `-r, --requirements `: Requirements files to initialize pyproject.toml with 49 | 50 | * `--dev-requirements `: Development requirements files to initialize pyproject.toml with 51 | 52 | * `-v, --verbose`: Enables verbose diagnostics 53 | 54 | * `-q, --quiet`: Turns off all output 55 | 56 | * `-h, --help`: Print help (see a summary with '-h') 57 | -------------------------------------------------------------------------------- /docs/guide/commands/install.md: -------------------------------------------------------------------------------- 1 | # `install` 2 | 3 | Installs a package as global tool. This command has two names 4 | to `rye tools install` and `rye install`. 5 | 6 | This can be used to install useful Python scripts globally into it's own 7 | separated virtualenv. For instance if you want to use the `black` formatter 8 | you can install it once. 9 | 10 | Normally only scripts installed by the top level dependency are installed. In 11 | some cases you might also want to install commands from sub-dependencies. In 12 | that case pass those dependencies with `--include-dep`. 13 | 14 | For more information see [Tools](/guide/tools/). 15 | 16 | ## Example 17 | 18 | ``` 19 | $ rye tools install pycowsay 20 | Looking in indexes: https://pypi.org/simple/ 21 | Collecting pycowsay 22 | Downloading pycowsay-0.0.0.2-py3-none-any.whl.metadata (965 bytes) 23 | Downloading pycowsay-0.0.0.2-py3-none-any.whl (4.0 kB) 24 | Installing collected packages: pycowsay 25 | Successfully installed pycowsay-0.0.0.2 26 | 27 | Installed scripts: 28 | - pycowsay 29 | 30 | $ pycowsay "Great Stuff" 31 | 32 | ----------- 33 | < Great Stuff > 34 | ----------- 35 | \ ^__^ 36 | \ (oo)\_______ 37 | (__)\ )\/\ 38 | ||----w | 39 | || || 40 | ``` 41 | 42 | ## Arguments 43 | 44 | * `...`: The package to install as PEP 508 requirement string. 45 | 46 | ## Options 47 | 48 | * `--git `: Install the given package from this git repository 49 | 50 | * `--url `: Install the given package from this URL 51 | 52 | * `--path `: Install the given package from this local path 53 | 54 | * `--absolute`: Force non interpolated absolute paths 55 | 56 | * `--tag `: Install a specific tag 57 | 58 | * `--rev `: Update to a specific git rev 59 | 60 | * `--branch `: Update to a specific git branch 61 | 62 | * `--features `: Adds a dependency with a specific feature 63 | 64 | * `--include-dep `: Include scripts from a given dependency 65 | 66 | * `--extra-requirement `: Additional dependencies to install that are not declared by the main package 67 | 68 | * `-p, --python `: Optionally the Python version to use 69 | 70 | * `-f, --force`: Force install the package even if it's already there 71 | 72 | * `-v, --verbose`: Enables verbose diagnostics 73 | 74 | * `-q, --quiet`: Turns off all output 75 | 76 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/lint.md: -------------------------------------------------------------------------------- 1 | # `lint` 2 | 3 | +++ 0.20.0 4 | 5 | Run the linter on the project. This command is aliased to `check`. At the moment 6 | this always runs `ruff` in lint mode. 7 | 8 | For more information about how to configure Ruff, have a look at the 9 | [Ruff Configuration Documentation](https://docs.astral.sh/ruff/configuration/). 10 | 11 | ## Example 12 | 13 | Run the linter: 14 | 15 | ``` 16 | $ rye lint 17 | src/myproject/sdk.py:1:8: F401 [*] `sys` imported but unused 18 | Found 1 error. 19 | [*] 1 fixable with the `--fix` option. 20 | ``` 21 | 22 | For issues that can be auto fixed pass `--fix`: 23 | 24 | ``` 25 | $ rye lint --fix 26 | Found 1 error (1 fixed, 0 remaining). 27 | ``` 28 | 29 | To pass extra arguments: 30 | 31 | ``` 32 | $ rye lint -- --watch 33 | ``` 34 | 35 | Lint a specific file: 36 | 37 | ``` 38 | rye lint src/foo.py 39 | ``` 40 | 41 | ## Arguments 42 | 43 | * `[PATHS]...` List of files or directories to lint. If not supplied all files are linted. 44 | 45 | * `[EXTRA_ARGS]...` Extra arguments to the linter. 46 | 47 | These arguments are forwarded directly to the underlying linter (currently 48 | always `ruff`). Note that extra arguments must be separated from other arguments 49 | with the `--` marker. 50 | 51 | ## Options 52 | 53 | * `-a, --all`: Lint all packages in the workspace 54 | 55 | * `-p, --package `: Format a specific package 56 | 57 | * `--pyproject `: Use this `pyproject.toml` file 58 | 59 | * `--fix`: Automatically fix fixable issues 60 | 61 | * `-v, --verbose`: Enables verbose diagnostics 62 | 63 | * `-q, --quiet`: Turns off all output 64 | 65 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/list.md: -------------------------------------------------------------------------------- 1 | # `list` 2 | 3 | +++ 0.24.0 4 | 5 | Prints a list of installed dependencies. 6 | 7 | ## Example 8 | 9 | ``` 10 | $ rye list 11 | asgiref==3.7.2 12 | blinker==1.7.0 13 | click==8.1.7 14 | Flask @ git+https://github.com/pallets/flask@4df377cfbfc1d15e962a61c18920b22aebc9aa41 15 | itsdangerous==2.1.2 16 | Jinja2==3.1.3 17 | MarkupSafe==2.1.4 18 | Werkzeug==3.0.1 19 | ``` 20 | 21 | ## Arguments 22 | 23 | *no arguments* 24 | 25 | ## Options 26 | 27 | * `--pyproject`: Use this `pyproject.toml` file 28 | 29 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/lock.md: -------------------------------------------------------------------------------- 1 | # `lock` 2 | 3 | Updates the lockfiles without installing dependencies. Usually one would use 4 | the [`sync`](sync.md) command instead which both locks and installs dependencies. 5 | 6 | For more information see [Syncing and Locking](../sync.md). 7 | 8 | ## Example 9 | 10 | ``` 11 | $ rye lock 12 | Generating production lockfile: /Users/username/my-project/requirements.lock 13 | Generating dev lockfile: /Users/username/my-project/requirements-dev.lock 14 | Done! 15 | ``` 16 | 17 | ## Arguments 18 | 19 | *no arguments* 20 | 21 | ## Options 22 | 23 | * `--update `: Update a specific package 24 | 25 | * `--update-all`: Update all packages to the latest 26 | 27 | * `--pre`: Update to pre-release versions 28 | 29 | * `--features `: Extras/features to enable when locking the workspace 30 | 31 | * `--all-features`: Enables all features 32 | 33 | * `--generate-hashes`: Set to true to lock with hashes in the lockfile 34 | 35 | * `--with-sources`: Set to true to lock with sources in the lockfile 36 | 37 | * `--pyproject `: Use this pyproject.toml file 38 | 39 | * `-v, --verbose`: Enables verbose diagnostics 40 | 41 | * `-q, --quiet`: Turns off all output 42 | 43 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/make-req.md: -------------------------------------------------------------------------------- 1 | # `make-req` 2 | 3 | Builds and prints a PEP 508 requirement string from parts. This is a utility command 4 | that rarely needs to be used but can help creating requirements strings for pasting into 5 | other tools. It takes the same arguments as [`add`](add.md) but rather than adding the 6 | requirements into the requirements file it just spits out a formatted PEP 508 requirement 7 | string on stdout. 8 | 9 | ## Example 10 | 11 | ``` 12 | $ rye make-req flask --git https://github.com/pallets/flask --rev 4df377cfbf 13 | flask @ git+https://github.com/pallets/flask@4df377cfbf 14 | ``` 15 | 16 | ## Arguments 17 | 18 | * `[REQUIREMENTS]...` The package to add as PEP 508 requirement string. e.g. `'flask==2.2.3'` 19 | 20 | ## Options 21 | 22 | * `--git `: Install the given package from this git repository 23 | 24 | * `--url `: Install the given package from this URL 25 | 26 | * `--path `: Install the given package from this local path 27 | 28 | * `--absolute`: Force non interpolated absolute paths 29 | 30 | * `--tag `: Install a specific tag 31 | 32 | * `--rev `: Update to a specific git rev 33 | 34 | * `--branch `: Update to a specific git branch 35 | 36 | * `--features `: Adds a dependency with a specific feature 37 | 38 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/pin.md: -------------------------------------------------------------------------------- 1 | # `pin` 2 | 3 | Pins a Python version to this project. 4 | 5 | This will update the `.python-version` to point to the provided version. 6 | Additionally it will update `requires-python` in the `pyproject.toml` if it's 7 | lower than the current version. This can be disabled by passing 8 | `--no-update-requires-python`. 9 | 10 | Which toolchain Rye prefers depends on the Rye version. From 0.22 onwards 11 | the latest compatible installed toolchain is picked, and only if a non 12 | existing one is found a download will be attempted. For older versions 13 | Rye will always attempt to download the latest available if it's not 14 | installed yet unless a precise pin is selected. 15 | 16 | ## Example 17 | 18 | Pin a specific version of Python: 19 | 20 | ``` 21 | $ rye pin 3.9 22 | pinned 3.9.18 in /Users/username/my-project 23 | ``` 24 | 25 | To issue a relaxed and not a specific pin use `--relaxed`: 26 | 27 | ``` 28 | $ rye pin 3.9 --relaxed 29 | pinned 3.9 in /Users/username/my-project 30 | ``` 31 | 32 | ## Arguments 33 | 34 | * ``: The version of Python to pin 35 | 36 | This can be a short version (3.9) or a full one (`cpython@3.9.18`). 37 | 38 | ## Options 39 | 40 | * `--relaxed`: Issue a relaxed pin 41 | 42 | * `--no-update-requires-python`: Prevent updating requires-python in the `pyproject.toml` 43 | 44 | * `--pyproject `: Use this `pyproject.toml` file 45 | 46 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/publish.md: -------------------------------------------------------------------------------- 1 | # `publish` 2 | 3 | Publish packages to a package repository. This publishes the packages which are 4 | produced by the build command. 5 | 6 | For more information see [Building and Publishing](../publish.md). 7 | 8 | ## Example 9 | 10 | Build and publish: 11 | 12 | ``` 13 | $ rye build 14 | $ rye publish 15 | ``` 16 | 17 | Publish a specific artifact: 18 | 19 | ``` 20 | $ rye publish dist/example-0.1.0.tar.gz 21 | ``` 22 | 23 | ## Arguments 24 | 25 | * `[DIST]...`: The distribution files to upload to the repository (defaults to `/dist/*`) 26 | 27 | ## Options 28 | 29 | * `-r, --repository `: The repository to publish to [default: `pypi`] 30 | 31 | * `--repository-url `: The repository url to publish to 32 | 33 | * `-u, --username `: The username to authenticate to the repository with 34 | 35 | * `--token `: An access token used for the upload 36 | 37 | * `--sign`: Sign files to upload using GPG 38 | 39 | * `-i, --identity `: GPG identity used to sign files 40 | 41 | * `--cert `: Path to alternate CA bundle 42 | 43 | * `--skip-existing`: Skip files already published (repository must support this feature) 44 | 45 | * `-y, --yes`: Skip prompts 46 | 47 | * `-v, --verbose`: Enables verbose diagnostics 48 | 49 | * `-q, --quiet`: Turns off all output 50 | 51 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/remove.md: -------------------------------------------------------------------------------- 1 | # `remove` 2 | 3 | Removes a package from this project. This removes a package from the `pyproject.toml` 4 | dependency list. 5 | 6 | If auto sync is disabled, after a dependency is removed it's not automatically 7 | uninstalled. To do that, you need to invoke the [`sync`](sync.md) command or pass 8 | `--sync`. 9 | 10 | +++ 0.26.0 11 | 12 | Added support for auto-sync and the `--sync` / `--no-sync` flags. 13 | 14 | ## Example 15 | 16 | ``` 17 | $ rye remove flask 18 | Removed flask>=3.0.1 19 | ``` 20 | 21 | ## Arguments 22 | 23 | * `...`: The packages to remove from the project 24 | 25 | ## Options 26 | 27 | * `--dev`: Remove this from dev dependencies 28 | 29 | * `--optional `: Remove this from the optional dependency group 30 | 31 | * `--sync`: Runs `sync` automatically even if auto-sync is disabled. 32 | 33 | * `--no-sync`: Does not run `sync` automatically even if auto-sync is enabled. 34 | 35 | * `-v, --verbose`: Enables verbose diagnostics 36 | 37 | * `-q, --quiet`: Turns off all output 38 | 39 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/run.md: -------------------------------------------------------------------------------- 1 | # `run` 2 | 3 | Runs a command installed into this package. This either runs a script or application 4 | made available in the virtualenv or a Rye specific script. 5 | 6 | For more information see [`rye.tool.scripts`](../pyproject.md#toolryescripts). 7 | 8 | ## Example 9 | 10 | Run a tool from the virtualenv: 11 | 12 | ``` 13 | $ rye run flask 14 | ``` 15 | 16 | Invoke it without arguments to see all available scripts: 17 | 18 | ``` 19 | $ rye run 20 | flask 21 | hello 22 | python 23 | python3 24 | python3.9 25 | ``` 26 | 27 | ## Arguments 28 | 29 | * `[COMMAND]`: The name of the command and the arguments to it. 30 | 31 | ## Options 32 | 33 | * `-l, --list`: List all commands (implied without arguments) 34 | 35 | * `--pyproject`: Use this `pyproject.toml` file 36 | 37 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/self/completion.md: -------------------------------------------------------------------------------- 1 | # `completion` 2 | 3 | Generates a completion script for a shell 4 | 5 | ## Example 6 | 7 | Generate a completion script for zsh and load it: 8 | 9 | ``` 10 | $ eval "$(rye self completion -s zsh)" 11 | ``` 12 | 13 | ## Arguments 14 | 15 | _no arguments_ 16 | 17 | ## Options 18 | 19 | * `-s, --shell `: The shell to generate a completion script for (defaults to 'bash') 20 | 21 | [possible values: `bash`, `elvish`, `fish`, `powershell`, `zsh`, `nushell`] 22 | 23 | * `-h, --help`: Print help (see a summary with '-h') 24 | -------------------------------------------------------------------------------- /docs/guide/commands/self/index.md: -------------------------------------------------------------------------------- 1 | # `self` 2 | 3 | Command to manage Rye itself. 4 | 5 | * [`completion`](completion.md): Generates a completion script for Rye. 6 | 7 | * [`update`](update.md): Performs an update of Rye. 8 | 9 | * [`uninstall`](uninstall.md): Uninstalls Rye again. 10 | -------------------------------------------------------------------------------- /docs/guide/commands/self/uninstall.md: -------------------------------------------------------------------------------- 1 | # `uninstall` 2 | 3 | Uninstalls rye again. Note that this leaves a trace 4 | `.rye` folder behind with an empty `env` file. You also 5 | need to remove the sourcing of that script from your 6 | `.profile` file. 7 | 8 | ## Example 9 | 10 | Uninstall rye without asking: 11 | 12 | ``` 13 | $ rye self uninstall --yes 14 | ``` 15 | 16 | ## Arguments 17 | 18 | _no arguments_ 19 | 20 | ## Options 21 | 22 | * `-y, --yes`: Do not prompt and uninstall. 23 | 24 | * `-h, --help`: Print help (see a summary with '-h') 25 | -------------------------------------------------------------------------------- /docs/guide/commands/self/update.md: -------------------------------------------------------------------------------- 1 | # `update` 2 | 3 | Performs an update of rye. 4 | 5 | This can install updates from the latest release binaries or trigger a manual 6 | compilation of Rye if Rust is installed. 7 | 8 | ## Example 9 | 10 | Update to the latest version: 11 | 12 | ``` 13 | $ rye self update 14 | ``` 15 | 16 | Update (or downgrade) to a specific version: 17 | 18 | ``` 19 | $ rye self update --version 0.20 20 | ``` 21 | 22 | Compile a specific revision: 23 | 24 | ``` 25 | $ rye self update --rev 08910bc9b3b7c72a3d3ac694c4f3412259161477 26 | ``` 27 | 28 | Compile latest development version: 29 | 30 | ``` 31 | $ rye self update --branch main 32 | ``` 33 | 34 | ## Arguments 35 | 36 | _no arguments_ 37 | 38 | ## Options 39 | 40 | * `--version `: Update to a specific version 41 | 42 | * `--tag `: Update to a specific tag 43 | 44 | * `--rev `: Update to a specific git rev 45 | 46 | * `--branch `: Update to a specific git branch 47 | 48 | * `--force`: Force reinstallation 49 | 50 | * `-h, --help`: Print help (see a summary with '-h') 51 | -------------------------------------------------------------------------------- /docs/guide/commands/show.md: -------------------------------------------------------------------------------- 1 | # `show` 2 | 3 | Prints the current state of the project. This can print out information about the 4 | virtualenv, the project or workspace as well as a list of installed dependencies. 5 | 6 | ## Example 7 | 8 | Print out the status of a project: 9 | 10 | ``` 11 | $ rye show 12 | project: my-project 13 | path: /Users/username/my-project 14 | venv: /Users/username/my-project/.venv 15 | target python: 3.8 16 | venv python: cpython@3.9.18 17 | virtual: false 18 | ``` 19 | 20 | ## Arguments 21 | 22 | *no arguments* 23 | 24 | ## Options 25 | 26 | * `--installed-deps`: Print the currently installed dependencies. 27 | 28 | This option is being replaced with [`rye list`](list.md) 29 | 30 | * `--pyproject`: Use this `pyproject.toml` file 31 | 32 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/sync.md: -------------------------------------------------------------------------------- 1 | # `sync` 2 | 3 | Updates the lockfiles and installs the dependencies into the virtualenv. 4 | 5 | For more information see [Syncing and Locking](../sync.md). 6 | 7 | ## Example 8 | 9 | Sync the project: 10 | 11 | ``` 12 | $ rye sync 13 | Reusing already existing virtualenv 14 | Generating production lockfile: /Users/username/my-project/requirements.lock 15 | Generating dev lockfile: /Users/username/my-project/requirements-dev.lock 16 | Installing dependencies 17 | ... 18 | ``` 19 | 20 | To sync without updating the lock file use `--no-lock`: 21 | 22 | ``` 23 | $ rye sync --no-lock 24 | ``` 25 | 26 | If you do not want dev dependencies to be installed use `--no-dev`: 27 | 28 | ``` 29 | $ rye sync --no-dev 30 | ``` 31 | 32 | To exit the sub shell run `exit`. 33 | 34 | ## Arguments 35 | 36 | *no arguments* 37 | 38 | ## Options 39 | 40 | * `-f, --force`: Force the virtualenv to be re-created 41 | 42 | * `--no-dev`: Do not install dev dependencies 43 | 44 | * `--no-lock`: Do not update the lockfile. 45 | 46 | * `--update `: Update a specific package 47 | 48 | * `--update-all`: Update all packages to the latest 49 | 50 | * `--pre`: Update to pre-release versions 51 | 52 | * `--features `: Extras/features to enable when locking the workspace 53 | 54 | * `--all-features`: Enables all features 55 | 56 | * `--generate-hashes`: Set to true to lock with hashes in the lockfile 57 | 58 | * `--with-sources`: Set to true to lock with sources in the lockfile 59 | 60 | * `--pyproject `: Use this pyproject.toml file 61 | 62 | * `-v, --verbose`: Enables verbose diagnostics 63 | 64 | * `-q, --quiet`: Turns off all output 65 | 66 | * `-h, --help`: Print help (see a summary with '-h') 67 | -------------------------------------------------------------------------------- /docs/guide/commands/test.md: -------------------------------------------------------------------------------- 1 | # `test` 2 | 3 | +++ 0.28.0 4 | 5 | Run the test suites of the project. At the moment this always runs `pytest`. 6 | Note that `pytest` must be installed into the virtual env unlike `ruff` 7 | which is used behind the scenes automatically for linting and formatting. 8 | Thus in order to use this, you need to declare `pytest` as dev dependency. 9 | 10 | ``` 11 | $ rye add --dev pytest 12 | ``` 13 | 14 | It's recommended to place tests in a folder called `tests` adjacent to the 15 | `src` folder of your project. 16 | 17 | For more information about how to use pytest, have a look at the 18 | [Pytest Documentation](https://docs.pytest.org/en/8.0.x/). 19 | 20 | ## Example 21 | 22 | Run the test suite: 23 | 24 | ``` 25 | $ rye test 26 | platform win32 -- Python 3.11.1, pytest-8.0.2, pluggy-1.4.0 27 | rootdir: /Users/john/Development/stuff 28 | plugins: anyio-4.3.0 29 | collected 1 item 30 | 31 | stuff/tests/test_batch.py . [100%] 32 | ``` 33 | 34 | ## Arguments 35 | 36 | * `[EXTRA_ARGS]...` Extra arguments to the test runner. 37 | 38 | These arguments are forwarded directly to the underlying test runner (currently 39 | always `pytest`). Note that extra arguments must be separated from other arguments 40 | with the `--` marker. 41 | 42 | ## Options 43 | 44 | * `-a, --all`: Test all packages in the workspace 45 | 46 | * `-p, --package `: Run the test suite of a specific package 47 | 48 | * `--pyproject `: Use this `pyproject.toml` file 49 | 50 | * `-v, --verbose`: Enables verbose diagnostics 51 | 52 | * `-q, --quiet`: Turns off all output 53 | 54 | * `-i, --ignore`: Ignore the specified directory 55 | 56 | * `-s`, `--no-capture`: Disable stdout/stderr capture for the test runner 57 | 58 | * `-h, --help`: Print help (see a summary with '-h') 59 | -------------------------------------------------------------------------------- /docs/guide/commands/toolchain/fetch.md: -------------------------------------------------------------------------------- 1 | ../fetch.md -------------------------------------------------------------------------------- /docs/guide/commands/toolchain/index.md: -------------------------------------------------------------------------------- 1 | # `toolchain` 2 | 3 | Helper utility to manage Python toolchains. The following subcommands exist: 4 | 5 | * [`fetch`](fetch.md): fetches a toolchain 6 | 7 | * [`list`](list.md): lists all registered toolchains 8 | 9 | * [`register`](register.md): register a Python binary as custom toolchain 10 | 11 | * [`remove`](remove.md): removes or uninstalls a toolchain -------------------------------------------------------------------------------- /docs/guide/commands/toolchain/list.md: -------------------------------------------------------------------------------- 1 | # `list` 2 | 3 | List all registered toolchains. It can list the toolchains which are installed as 4 | well as toolchains which can be downloaded if `--include-downloadable` is passed. 5 | 6 | ## Example 7 | 8 | List installed toolchains: 9 | 10 | ``` 11 | $ rye toolchain list 12 | cpython@3.12.1 (/Users/username/.rye/py/cpython@3.12.1/install/bin/python3) 13 | cpython@3.11.6 (/Users/username/.rye/py/cpython@3.11.6/install/bin/python3) 14 | ``` 15 | 16 | Lists downloadable toolchains: 17 | 18 | ``` 19 | $ rye toolchain list --include-downloadable 20 | cpython@3.12.1 (/Users/mitsuhiko/.rye/py/cpython@3.12.1/install/bin/python3) 21 | cpython-x86_64@3.12.1 (downloadable) 22 | cpython@3.11.7 (downloadable) 23 | ... 24 | ``` 25 | 26 | ## Arguments 27 | 28 | *no arguments* 29 | 30 | ## Options 31 | 32 | * `--include-downloadable`: Also include non installed, but downloadable toolchains 33 | 34 | * `--format `: Request parseable output format [possible values: json] 35 | 36 | * `-h, --help`: Print help 37 | -------------------------------------------------------------------------------- /docs/guide/commands/toolchain/register.md: -------------------------------------------------------------------------------- 1 | # `register` 2 | 3 | Register a Python binary as custom toolchain. 4 | 5 | Rye by default will automatically download Python releases from the internet. 6 | However it's also possible to register already available local Python 7 | installations. This allows you to use rye with self compiled Pythons. 8 | 9 | The name of the toolchain is auto detected (eg: `cpython`, `pypy` etc.) 10 | 11 | To unregister use the [`remove`](remove.md) command. 12 | 13 | ## Example 14 | 15 | ``` 16 | $ rye toolchain register /opt/homebrew/Cellar/python@3.10/3.10.6_1/bin/python3.10 17 | Registered /opt/homebrew/Cellar/python@3.10/3.10.6_1/bin/python3.10 as cpython@3.10.6 18 | ``` 19 | 20 | ## Arguments 21 | 22 | * ``: Path to the python binary that should be registered 23 | 24 | ## Options 25 | 26 | * `-n, --name `: Name of the toolchain. If not provided a name is auto detected. 27 | 28 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/toolchain/remove.md: -------------------------------------------------------------------------------- 1 | # `remove` 2 | 3 | Removes or uninstalls a toolchain. 4 | 5 | ## Example 6 | 7 | ``` 8 | $ rye toolchain remove 3.9.5 9 | Removed installed toolchain cpython@3.9.5 10 | ``` 11 | 12 | ## Arguments 13 | 14 | * `` The version of Python to remove. 15 | 16 | ## Options 17 | 18 | * `-f, --force`: Force removal even if the toolchain is in use 19 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/tools/index.md: -------------------------------------------------------------------------------- 1 | # `tools` 2 | 3 | Helper utility to manage global tool installations. 4 | 5 | * [`install`](install.md): installs a tool globally. 6 | 7 | * [`uninstall`](uninstall.md): uninstalls a globally installed tool. 8 | 9 | * [`list`](list.md): lists all globally installed tools. 10 | -------------------------------------------------------------------------------- /docs/guide/commands/tools/install.md: -------------------------------------------------------------------------------- 1 | ../install.md -------------------------------------------------------------------------------- /docs/guide/commands/tools/list.md: -------------------------------------------------------------------------------- 1 | # `list` 2 | 3 | Lists all already installed global tools. 4 | 5 | For more information see [Tools](/guide/tools/). 6 | 7 | ## Example 8 | 9 | List installed tools: 10 | 11 | ``` 12 | $ rye tools list 13 | pycowsay 14 | ``` 15 | 16 | List installed tools with version: 17 | 18 | ``` 19 | $ rye tools list --include-version 20 | pycowsay 0.0.0.2 (cpython@3.12.1) 21 | ``` 22 | 23 | ## Arguments 24 | 25 | *no arguments* 26 | 27 | ## Options 28 | 29 | * `-s, --include-scripts`: Show all the scripts installed by the tools 30 | 31 | +/- 0.26.0 32 | 33 | Renamed from `-i, --include-scripts` to `-s, --include-scripts`. 34 | 35 | * `-v, --include-version`: Show the version of tools 36 | 37 | +/- 0.26.0 38 | 39 | Renamed from `-v, --version-show` to `-v, --include-version`. 40 | 41 | * `-h, --help`: Print help 42 | -------------------------------------------------------------------------------- /docs/guide/commands/tools/uninstall.md: -------------------------------------------------------------------------------- 1 | ../uninstall.md -------------------------------------------------------------------------------- /docs/guide/commands/uninstall.md: -------------------------------------------------------------------------------- 1 | # `uninstall` 2 | 3 | Uninstalls an already installed global tool. This command has two names 4 | to `rye tools uninstall` and `rye uninstall`. 5 | 6 | For more information see [Tools](/guide/tools/). 7 | 8 | ## Example 9 | 10 | ``` 11 | $ rye tools uninstall pycowsay 12 | Uninstalled pycowsay 13 | ``` 14 | 15 | ## Arguments 16 | 17 | * ``: The package to uninstall. 18 | 19 | ## Options 20 | 21 | * `-v, --verbose`: Enables verbose diagnostics 22 | 23 | * `-q, --quiet`: Turns off all output 24 | 25 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/commands/version.md: -------------------------------------------------------------------------------- 1 | # `version` 2 | 3 | Get or set project version. Note that this does not refer to the version of Rye 4 | itself but the version that is set in the `pyproject.toml` file. 5 | 6 | ## Example 7 | 8 | Get the current version: 9 | 10 | ``` 11 | $ rye version 12 | 0.1.0 13 | ``` 14 | 15 | Bump the version by minor: 16 | 17 | ``` 18 | $ rye version -b minor 19 | version bumped to 0.2.0 20 | ``` 21 | 22 | Set to a specific version: 23 | 24 | ``` 25 | $ rye version 1.0.0 26 | version set to 1.0.0 27 | ``` 28 | 29 | ## Arguments 30 | 31 | * `[VERSION]`: the version to set 32 | 33 | ## Options 34 | 35 | * `-b, --bump `: automatically bump the version in a specific way (`major`, `minor` or `patch`) 36 | 37 | * `-h, --help`: Print help (see a summary with '-h') -------------------------------------------------------------------------------- /docs/guide/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Most of Rye's configuration is contained within the `pyproject.toml` file. There is however 4 | also a bit of global configuration to influence how it works. 5 | 6 | ## Changing Home Folder 7 | 8 | By default Rye places all its configuration in `~/.rye` on Unix and `%USERPROFILE%\.rye` on 9 | Windows. This behavior can be changed via the `RYE_HOME` environment variable. This is useful 10 | if you do not like the default location where Rye places its configuration or if you need 11 | to isolate it. 12 | 13 | ## Home Folder Structure 14 | 15 | The `.rye` home folder contains both user configuration as well as Rye-managed state such 16 | as [installed toolchains](toolchains/index.md). The following files and folders are placed within the 17 | `.rye` folder. Note that not all are always there. 18 | 19 | ### `config.toml` 20 | 21 | This is a configuration file that influences how Rye operates. Today very little configuration 22 | is available there. For the available config keys see [Config File](#config-file). 23 | 24 | ### `self` 25 | 26 | While Rye is written in Rust, it uses a lot of Python tools internally. These are maintained in 27 | an internal virtualenv stored in this location. 28 | 29 | ### `py` 30 | 31 | In this folder Rye stores the different [toolchains](toolchains/index.md). Normally those are folders 32 | containing downloaded Python distributions, but they can also be symlinks or special reference 33 | files. 34 | 35 | ### `shims` 36 | 37 | This folder contains shim binaries. These binaries are for instance the `python` executable 38 | which automatically proxies to the current virtualenv or globally installed [tools](tools.md). 39 | 40 | ## Config File 41 | 42 | The config file `config.toml` in the `.rye` folder today is only used to manage defaults. This 43 | is a fully annotated config file: 44 | 45 | ```toml 46 | [default] 47 | # This is the default value that is written into new pyproject.toml 48 | # files for the `project.requires-python` key 49 | requires-python = ">= 3.8" 50 | 51 | # This is the default toolchain that is used 52 | toolchain = "cpython@3.11.1" 53 | 54 | # This is the default build system that is used 55 | build-system = "hatchling" 56 | 57 | # This is the default license that is used 58 | license = "MIT" 59 | 60 | # This sets the default author (overrides the defaults from git). The 61 | # format here is "Name ". 62 | author = "Full Name " 63 | 64 | # The dependency operator to use by default for dependencies. The options are 65 | # '>=', '~=', and '=='. The default currently is '>='. This affects the behavior 66 | # of `rye add`. 67 | dependency-operator = ">=" 68 | 69 | [proxy] 70 | # the proxy to use for HTTP (overridden by the http_proxy environment variable) 71 | http = "http://127.0.0.1:4000" 72 | # the proxy to use for HTTPS (overridden by the https_proxy environment variable) 73 | https = "http://127.0.0.1:4000" 74 | 75 | [behavior] 76 | # When set to `true` the `managed` flag is always assumed to be `true`. 77 | force-rye-managed = false 78 | 79 | # Enables global shims when set to `true`. This means that the installed 80 | # `python` shim will resolve to a Rye-managed toolchain even outside of 81 | # virtual environments. 82 | global-python = false 83 | 84 | # Enable or disable automatic `sync` after `add` and `remove`. This defaults 85 | # to `true` when uv is enabled and `false` otherwise. 86 | autosync = true 87 | 88 | # Marks the managed .venv in a way that cloud-based synchronization systems 89 | # like Dropbox and iCloud Files will not upload it. This defaults to `true` 90 | # as a .venv in cloud storage typically does not make sense. Set this to 91 | # `false` to disable this behavior. 92 | venv-mark-sync-ignore = true 93 | 94 | # When set to `true` Rye will fetch certain interpreters with build information. 95 | # This will increase the space requirements, will put the interpreter into an 96 | # extra folder called `./install/` and place build artifacts adjacent in `./build`. 97 | fetch-with-build-info = false 98 | 99 | # An array of tables with optional sources. Same format as in pyproject.toml 100 | [[sources]] 101 | name = "default" 102 | url = "https://pypi.org/simple/" 103 | ``` 104 | 105 | ## Manipulating Config 106 | 107 | +++ 0.9.0 108 | 109 | The configuration can be read and modified with `rye config`. The 110 | keys are in dotted notation. `--get` reads a key, `--set`, `--set-int`, 111 | `--set-bool`, and `--unset` modify one. 112 | 113 | ```bash 114 | rye config --set proxy.http=http://127.0.0.1:4000 115 | rye config --set-bool behavior.force-rye-managed=true 116 | rye config --get default.requires-python 117 | ``` 118 | 119 | For more information see [`config`](commands/config.md). 120 | 121 | ## Per Project Config 122 | 123 | For the project-specific `pyproject.toml` config see [pyproject.toml](pyproject.md). 124 | -------------------------------------------------------------------------------- /docs/guide/deps.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | Dependencies are declared in [pyproject.toml](pyproject.md) however adding them can be 4 | simplified with the [`rye add`](commands/add.md) command. In the most simple invocation it adds a regular 5 | dependency, but it can be customized. 6 | 7 | ## Adding Basic Dependency 8 | 9 | To add a regular dependency just invoke [`rye add`](commands/add.md) with the name of the Python package: 10 | 11 | ``` 12 | rye add Flask 13 | ``` 14 | 15 | If you also want to define a version, use a [PEP 508](https://peps.python.org/pep-0508/) 16 | requirement: 17 | 18 | ``` 19 | rye add "Flask>=2.0" 20 | ``` 21 | 22 | For extra/feature dependencies you can either use PEP 508 syntax or use `--features`: 23 | 24 | ``` 25 | rye add "Flask[dotenv]" 26 | rye add Flask --features=dotenv 27 | ``` 28 | 29 | These dependencies are stored in [`project.dependencies`](pyproject.md#projectdependencies). 30 | 31 | !!! tip "Note about pre-releases" 32 | 33 | By default `add` will not consider pre-releases. This means if you add a dependency 34 | that has `.dev` or similar in the version number you will not find a match. To 35 | consider them, add them with `--pre`: 36 | 37 | ``` 38 | rye add "Flask==2.0.0rc2" --pre 39 | ``` 40 | 41 | ## Development Dependencies 42 | 43 | For dependencies that should only be installed during development pass `--dev` 44 | 45 | ``` 46 | rye add --dev black 47 | ``` 48 | 49 | These dependencies are stored in the non-standard 50 | [`tool.rye.dev-dependencies`](pyproject.md#toolryedev-dependencies) key. 51 | 52 | To run tools added this way without enabling the virtualenv use [`rye run`](commands/run.md): 53 | 54 | ``` 55 | rye run black 56 | ``` 57 | 58 | ## Git / Local Dependencies 59 | 60 | To add a local or git dependency, you can pass additional parameters like `--path` 61 | or `--git`: 62 | 63 | ``` 64 | rye add Flask --git=https://github.com/pallets/flask 65 | rye add My-Utility --path ./my-utility 66 | ``` 67 | 68 | Note that when adding such dependencies, it's necessary to also provide the name 69 | of the package. Additionally for git dependencies all kinds of extra parameters 70 | such as `--tag`, `--rev` or `--branch` are supported. 71 | 72 | When working with local dependencies it's strongly encouraged to configure a 73 | [workspace](pyproject.md#toolryeworkspace). 74 | -------------------------------------------------------------------------------- /docs/guide/docker.md: -------------------------------------------------------------------------------- 1 | # Building a Container with Docker 2 | 3 | If you want to put your Python code into a container, you probably have some server code that you don't submit to PyPI or another registry. 4 | If that's the case, read on. Else, skip to [the next section](#container-from-a-python-package). 5 | 6 | This guide requires some familiarity with Docker and Dockerfiles. 7 | 8 | ## Container from Source 9 | 10 | 1. Make sure that your project is set up as a [virtual project](./virtual.md). 11 | This means that you can't install it, and it won't mark itself as a dependency. 12 | If you need your project to be installable, go to [the next section](#container-from-a-python-package). 13 | 14 | - Your `pyproject.toml` should contain `virtual = true` under the `[tool.rye]` section. If it's not there, add it and run `rye sync`. 15 | - If you're just setting up a project, run `rye init --virtual` instead of `rye init`. 16 | 17 | 2. Create a `Dockerfile` in your project root with the following content, using [`uv`](https://github.com/astral-sh/uv): 18 | 19 | ```Dockerfile 20 | FROM python:slim 21 | 22 | RUN pip install uv 23 | 24 | WORKDIR /app 25 | COPY requirements.lock ./ 26 | RUN uv pip install --no-cache --system -r requirements.lock 27 | 28 | COPY src . 29 | CMD python main.py 30 | ``` 31 | 32 | Or, using `pip`: 33 | 34 | ```Dockerfile 35 | FROM python:slim 36 | 37 | WORKDIR /app 38 | COPY requirements.lock ./ 39 | RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -r requirements.lock 40 | 41 | COPY src . 42 | CMD python main.py 43 | ``` 44 | 45 | 3. You can now build your image like this: 46 | 47 | ```bash 48 | docker build . 49 | ``` 50 | 51 | ### Dockerfile Adjustments 52 | 53 | The `Dockerfile`s in this guide are examples. Some adjustments you might want to make: 54 | 55 | - The command (`CMD python src/main.py`) should point to your script. 56 | - Adjust the base image (`FROM python:slim`): 57 | - Prefer a tagged version that matches the one from your `.python-version` file, e.g. `FROM python:3.12.0-slim`. 58 | - The `-slim` variants are generally a good tradeoff between image size and compatibility and should work fine for most workloads. 59 | But you can also use `-alpine` for smaller images (but potential compatibility issues) or no suffix for ones that contain more system tools. 60 | - If you need additional system packages, install them before copying your source code, i.e. before the line `COPY src .`. 61 | When using Debian-based images (i.e. `-slim` or no-suffix variants), that could look like this: 62 | 63 | ```Dockerfile 64 | RUN apt-get update \ 65 | && apt-get install -y --no-install-recommends some-dependency another-dependency \ 66 | && rm -rf /var/lib/apt/lists/* 67 | ``` 68 | 69 | ## Container from a Python Package 70 | 71 | If your code is an installable package, it's recommended that you first build it, then install it inside your Docker image. 72 | This way you can be sure that the image is exactly the same as what a user installation would be. 73 | 74 | An example `Dockerfile` might look like this with [`uv`](https://github.com/astral-sh/uv): 75 | 76 | ```Dockerfile 77 | FROM python:slim 78 | RUN pip install uv 79 | RUN --mount=source=dist,target=/dist uv pip install --no-cache /dist/*.whl 80 | CMD python -m my_package 81 | ``` 82 | 83 | To build your docker image, you'll have to first build your wheel, like this: 84 | 85 | ```bash 86 | rye build --wheel --clean 87 | docker build . --tag your-image-name 88 | ``` 89 | 90 | Note that this approach bundles your dependencies and code in a single layer. 91 | This might be nice for performance, but it also means that all dependencies are re-installed during every image build, and different versions won't share the disk space for the dependencies. 92 | 93 | The [Dockerfile adjustments from the previous section](#dockerfile-adjustments) apply. 94 | 95 | ## Explanations 96 | 97 | Rye's lockfile standard is the `requirements.txt` format from `pip` (and used by [`uv`](https://github.com/astral-sh/uv)), so you don't actually need `rye` in your container to be able to install dependencies. 98 | This makes the Dockerfile much simpler and avoids the necessity for multi-stage builds if small images are desired. 99 | 100 | The `--no-cache-dir` and `--no-cache` parameters, passed to `pip` and `uv` respectively, make the image smaller by not 101 | writing any temporary files. While caching can speed up subsequent builds, it's not necessary in a container where the 102 | image is built once and then used many times. 103 | 104 | Similarly, the `PYTHONDONTWRITEBYTECODE=1` environment variable is set to avoid writing `.pyc` files, which are not 105 | needed in a container. (`uv` skips writing `.pyc` files by default.) 106 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Rye is still a very experimental tool, but this guide is here to help you get 4 | started. Before we dive into the installation and basic usage guide it's 5 | important for you to understand what Rye actually is. 6 | 7 | Rye is a one-stop-shop tool. The idea is that as a Python developer all you 8 | need to know is Rye, because Rye is your start into the experience. As a Rye 9 | user you do not even need to install Python yourself as Rye does this for you. 10 | This means to use Rye, you just need to install Rye, the rest is done by Rye 11 | itself. 12 | 13 | Once Rye is on your system, it can automatically install Python interpreters 14 | for you, install packages from package indexes, manage virtualenvs behind 15 | the scenes and more. 16 | 17 |
18 | 19 |
20 | 21 | Interested? Then head over to [Installation](./installation.md) to learn about 22 | how to get Rye onto your system. Once that is done, read the [Basics](./basics.md) 23 | to learn about how Rye can be used. 24 | -------------------------------------------------------------------------------- /docs/guide/publish.md: -------------------------------------------------------------------------------- 1 | # Building and Publishing 2 | 3 | Rye currently uses [build](https://github.com/pypa/build) to build the package and uses [twine](https://github.com/pypa/twine) to publish it. 4 | 5 | ## Build 6 | 7 | By default, `rye` will build both the sdist and wheel targets in the `dist` directory. The command for this is called [`build`](commands/build.md). 8 | 9 | ``` 10 | rye build 11 | ``` 12 | 13 | You can use the `--sdist` or `--wheel` flag to build the specific target, or specify the output directory with `--out`. 14 | 15 | ``` 16 | rye build --wheel --out target 17 | ``` 18 | 19 | If you want to clean the build directory before building, run: 20 | 21 | ``` 22 | rye build --clean 23 | ``` 24 | 25 | ## Publish 26 | 27 | Rye will publish the distribution files under the `dist` directory to PyPI by default. 28 | 29 | ```bash 30 | rye publish 31 | ``` 32 | 33 | You might be asked to input your access token and some other info if needed. 34 | 35 | ``` 36 | No access token found, generate one at: https://pypi.org/manage/account/token/ 37 | Access token: 38 | 39 | ``` 40 | 41 | You can also specify the distribution files to be published: 42 | 43 | ``` 44 | rye publish dist/example-0.1.0.tar.gz 45 | ``` 46 | 47 | ### --repository 48 | 49 | Rye supports publishing the package to a different repository by using the `--repository` and `--repository-url` flags. For example, to publish to the test PyPI repository: 50 | 51 | ``` 52 | rye publish --repository testpypi --repository-url https://test.pypi.org/legacy/ 53 | ``` 54 | 55 | ### --yes 56 | 57 | You can optionally set the `--yes` flag to skip the confirmation prompt. This can be useful for CI/CD pipelines. 58 | 59 | ``` 60 | rye publish --token --yes 61 | ``` 62 | 63 | Rye will store your repository info in `$HOME/.rye/credentials` for future use. 64 | 65 | ### --skip-existing 66 | 67 | You can use `--skip-existing` to skip any distribution files that have already been published to the repository. Note that some repositories may not support this feature. 68 | -------------------------------------------------------------------------------- /docs/guide/rust.md: -------------------------------------------------------------------------------- 1 | # Rust Modules 2 | 3 | Rye recommends using [maturin](https://github.com/PyO3/maturin) to develop Rust Python 4 | extension modules. This process is largely automated and new projects can be created 5 | with `rye init`. 6 | 7 | ## New Project 8 | 9 | ``` 10 | rye init my-project --build-system maturin 11 | cd my-project 12 | ``` 13 | 14 | The following structure will be created: 15 | 16 | ``` 17 | . 18 | ├── .git 19 | ├── .gitignore 20 | ├── .python-version 21 | ├── README.md 22 | ├── pyproject.toml 23 | ├── Cargo.toml 24 | ├── python 25 | └── my_project 26 | └── __init__.py 27 | └── src 28 | └── lib.rs 29 | ``` 30 | 31 | ## Iterating 32 | 33 | When you use maturin as a build system then `rye sync` will automatically build the rust 34 | extension module into your venv. Likewise `rye build` will use maturin to trigger a 35 | wheel build. For faster iteration it's recommended to use `maturin` directly. 36 | 37 | If you want to use other maturin commands such as `maturin develop` you can install 38 | it as a global tool: 39 | 40 | ``` 41 | rye install maturin 42 | ``` 43 | 44 | Rye recommends mixed python/rust modules. In that case you can save some valuable 45 | iteration time by running `maturin develop --skip-install`: 46 | 47 | ``` 48 | maturin develop --skip-install 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/guide/shims.md: -------------------------------------------------------------------------------- 1 | # Shims 2 | 3 | After installation Rye places two shims on your `PATH`: `python` and `python3`. These 4 | shims have specific behavior that changes depending on if they are used within a Rye 5 | managed project or outside. 6 | 7 | Inside a Rye managed project they resolve to the Python interpreter of the virtualenv. 8 | This means that even if you do not enable the virtualenv, you can just run `python` 9 | in a shell, and it will automatically operate in the right environment. 10 | 11 | Outside a Rye managed project it typically resolves to your system Python, though you 12 | can also opt to have it resolve to a Rye managed Python installation for you. This is 13 | done so that it's not disruptive to your existing workflows which might depend on the 14 | System python installation. 15 | 16 | ## Global Shims 17 | 18 | +++ 0.9.0 19 | 20 | To enable global shims, you need to enable the `global-python` flag in 21 | the [`config.toml`](config.md) file: 22 | 23 | ```bash 24 | rye config --set-bool behavior.global-python=true 25 | ``` 26 | 27 | Afterwards if you run `python` outside of a Rye managed project it will 28 | spawn a Python interpreter that is shipped with Rye. It will honor the 29 | closest `.python-version` file for you. Additionally you can also 30 | explicitly request a specific Python version by adding `+VERSION` after 31 | the `python` command. For instance this runs a script with Python 3.8: 32 | 33 | ```bash 34 | python +3.8 my-script.py 35 | ``` 36 | 37 | !!! Note 38 | 39 | Selecting a specific Python version this way only works outside of 40 | Rye managed projects. Within Rye managed projects, the version needs 41 | to be explicitly selected via `.python-version` or with the 42 | `requires-python` key in `pyproject.toml`. 43 | -------------------------------------------------------------------------------- /docs/guide/sources.md: -------------------------------------------------------------------------------- 1 | # Dependency Sources 2 | 3 | +++ 0.2.0 4 | 5 | Normally Rye loads packages from PyPI only. However it is possible to instruct it to 6 | load packages from other indexes as well. 7 | 8 | ## Adding a Source 9 | 10 | An index can be added to a project or workspace (via `pyproject.toml`) or into the 11 | [global config](config.md#config-file). Rye will always consult both files where the 12 | `pyproject.toml` file wins over the global config. 13 | 14 | Each source needs to have a unique name. The default source is always called `default` 15 | and out of the box points to PyPI. 16 | 17 | === "Global Source" 18 | 19 | Add this to `~/.rye/config.toml`: 20 | 21 | ```toml 22 | [[sources]] 23 | name = "company-internal" 24 | url = "https://company.internal/simple/" 25 | ``` 26 | 27 | === "Project Source" 28 | 29 | Add this to `pyproject.toml`: 30 | 31 | ```toml 32 | [[tool.rye.sources]] 33 | name = "company-internal" 34 | url = "https://company.internal/simple/" 35 | ``` 36 | 37 | +/- 0.4.0 38 | 39 | Sources in the global config are also considered for tool installations. 40 | 41 | ### Index Types 42 | 43 | Rye supports different types of sources and also allows overriding the `default` 44 | PyPI index. If you give another source the name `default`, PyPI will no longer be 45 | used for resolution. 46 | 47 | === "Regular Index" 48 | 49 | ```toml 50 | [[sources]] 51 | name = "company-internal" 52 | url = "https://company.internal/simple/" 53 | type = "index" # this is implied 54 | ``` 55 | 56 | === "Find Links" 57 | 58 | ```toml 59 | [[sources]] 60 | name = "company-internal" 61 | url = "https://company.internal/" 62 | type = "find-links" 63 | ``` 64 | 65 | === "Default Index" 66 | 67 | ```toml 68 | [[sources]] 69 | name = "default" 70 | url = "https://company.internal/simple/" 71 | ``` 72 | 73 | !!! Warning 74 | 75 | Please take note that the default index cannot be of type `find-links`. 76 | 77 | ## Source Types 78 | 79 | The two sources types (`index` vs `find-links`) are determined by the underlying pip 80 | infrastructure: 81 | 82 | ### `index` 83 | 84 | This is a [PEP 503](https://www.python.org/dev/peps/pep-0503/) type index as provided 85 | by tools such as PyPI or [devpi](https://github.com/devpi/devpi). It corresponds to 86 | the arguments `--index-url` or `--extra-index-url` in pip. 87 | 88 | Note: see the [`uv` documentation](https://github.com/astral-sh/uv/blob/main/PIP_COMPATIBILITY.md#packages-that-exist-on-multiple-indexes) 89 | for more on the use of multiple indexes. 90 | 91 | ### `find-links` 92 | 93 | This is a source that can be of a variety of types and has to point to a file path 94 | or hosted HTML page linking to packages. It corresponds to the `--find-links` 95 | argument. The format of the HTML page is somewhat underspecified but generally 96 | all HTML links pointing to `.tar.gz` or `.whl` files are considered. 97 | 98 | ## Index Authentication 99 | 100 | HTTP basic auth is supported for index authentication. It can be supplied in two 101 | ways. `username` and `password` can be directly embedded in the config, or they 102 | can be supplied with environment variables. 103 | 104 | === "Configured Credentials" 105 | 106 | ```toml 107 | [[sources]] 108 | name = "company-internal" 109 | url = "https://company.internal/simple/" 110 | username = "username" 111 | password = "super secret" 112 | ``` 113 | 114 | === "Environment Variables" 115 | 116 | ```toml 117 | [[sources]] 118 | name = "company-internal" 119 | url = "https://${INDEX_USERNAME}:${INDEX_PASSWORD}@company.internal/simple/" 120 | ``` 121 | 122 | ## SSL/TLS Verification 123 | 124 | By default a source needs to be SSL/TLS protected. If not, rye will refuse to honor 125 | the source. You can override this by setting `verify-ssl` to `false`: 126 | 127 | ```toml 128 | [[sources]] 129 | name = "company-internal" 130 | url = "http://company.internal/simple/" 131 | verify-ssl = false 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/guide/sync.md: -------------------------------------------------------------------------------- 1 | # Syncing and Locking 2 | 3 | Rye uses [`uv`](https://github.com/astral-sh/uv) to manage dependencies. 4 | 5 | In order to download dependencies rye creates two "lockfiles" (called 6 | `requirements.lock` and `requirements-dev.lock`). These are not real lockfiles, 7 | but they fulfill a similar purpose until a better solution has been implemented. 8 | 9 | Whenever `rye sync` is called, it will update lockfiles as well as the 10 | virtualenv. If you only want to update the lockfiles, then `rye lock` can be 11 | used. 12 | 13 | ## Lock 14 | 15 | When locking, some options can be provided to change the locking behavior. These flags are 16 | also all available on `rye sync`. 17 | 18 | ### `--update` / `--update-all` 19 | 20 | Updates a specific or all requirements to the latest and greatest version. Without this flag 21 | a dependency will only be updated if necessary. 22 | 23 | ``` 24 | rye lock --update-all 25 | ``` 26 | 27 | ### `--features` / `--all-features` 28 | 29 | Python packages can have extra dependencies. By default the local package that is installed 30 | will only be installed with the default features. If for instance you have an extra dependency 31 | this will only be installed if the feature is enabled. 32 | 33 | ``` 34 | rye add --optional=web flask 35 | rye lock --features=web 36 | ``` 37 | 38 | When working with [workspaces](../workspaces/), the package name needs to be prefixed with a slash: 39 | 40 | ``` 41 | rye lock --features=package-name/feature-name 42 | ``` 43 | 44 | The `--features` parameter can be passed multiple times and features can also be comma 45 | separated. To turn on all features, the `--all-features` parameter can be used. 46 | 47 | ``` 48 | rye lock --all-features 49 | ``` 50 | 51 | ### `--pre` 52 | 53 | By default updates and version resolution will not consider pre-releases of packages. If you 54 | do want to include those, pass `--pre` 55 | 56 | ``` 57 | rye lock Flask --pre 58 | ``` 59 | 60 | ### `--with-sources` 61 | 62 | +++ 0.18.0 63 | 64 | By default (unless the `tool.rye.lock-with-sources` config key is set to `true` in the 65 | `pyproject.toml`) lockfiles are not generated with source references. This means that 66 | if custom sources are used the lockfile cannot be installed via `uv` or `pip`, unless 67 | `--find-links` and other parameters are manually passed. This can be particularly useful 68 | when the lockfile is used for Docker image builds. 69 | 70 | When this flag is passed then the lockfile is generated with references to `--index-url`, 71 | `--extra-index-url` or `--find-links`. 72 | 73 | ``` 74 | rye lock --with-sources 75 | ``` 76 | 77 | ## Sync 78 | 79 | Syncing takes the same parameters as `lock` and then some. Sync will usually first do what 80 | `lock` does and then use the lockfiles to update the virtualenv. 81 | 82 | ### `--no-lock` 83 | 84 | To prevent the lock step from automatically running, pass `--no-lock`. 85 | 86 | ``` 87 | rye sync --no-lock 88 | ``` 89 | 90 | ### `--no-dev` 91 | 92 | Only sync based on the production lockfile (`requirements.lock`) instead of the development 93 | lockfile (`requirements-dev.lock`). 94 | 95 | ``` 96 | rye sync --no-dev 97 | ``` 98 | 99 | ## Platform Compatibility 100 | 101 | By default, lockfiles depend on the platform they were generated on. 102 | 103 | For example, if your project relies on platform-specific packages and you generate 104 | lockfiles on Windows, these lockfiles will include Windows-specific projects. 105 | Consequently, they won't be compatible with other platforms like Linux or macOS. 106 | 107 | To generate a cross-platform lockfile, you can enable uv's `universal` setting 108 | by adding the following to your `pyproject.toml`: 109 | 110 | ```toml 111 | [tool.rye] 112 | universal = true 113 | ``` 114 | -------------------------------------------------------------------------------- /docs/guide/toolchains/cpython.md: -------------------------------------------------------------------------------- 1 | # Portable CPython 2 | 3 | Rye is capable (and prefers) to download its own Python distribution over what 4 | you might already have on your computer. For CPython, the 5 | [indygreg/python-build-standalone](https://github.com/indygreg/python-build-standalone) 6 | builds from the PyOxidizer project are used. 7 | 8 | The motivation for this is that it makes it easy to switch between Python 9 | versions, to have a common experience across different Rye users and to 10 | avoid odd bugs caused by changes in behavior. 11 | 12 | Unfortunately Python itself does not release binaries (or the right types of 13 | binaries) for all operating systems which is why Rye leverages the portable 14 | Python builds from PyOxidizer. 15 | 16 | Unlike many other Python versions you can install on your computer are 17 | non-portable which means that if you move them to a new location on your 18 | machine, or you copy it onto another computer (even with the same operating 19 | system) they will no longer run. This is undesirable for what Rye wants to do. 20 | For one we want the same experience for any of the Python developers, no matter 21 | which operating system they used. Secondly we want to enable self-contained 22 | Python builds later, which requires that the Python installation is portable. 23 | 24 | To achieve this, the Python builds we use come with some changes that are 25 | different from a regular Python build. 26 | 27 | ## Limitations 28 | 29 | The following changes to a regular Python versions you should be aware of: 30 | 31 | * `libedit` instead of `readline`: unfortunately `readline` is GPLv3 licensed 32 | and this is a hazard for redistributions. As such, the portable Python 33 | builds link against the more freely licensed `libedit` instead. 34 | 35 | * `dbm.gnu` is unavailable. This is a rather uncommonly used module and the 36 | standard library provides alternatives. 37 | 38 | Additionally due to how these builds are created, there are some other quirks 39 | you might run into related to terminal support or TKinter. Some of these 40 | issues are collected in the [FAQ](../faq.md). Additionally, the Python 41 | Standalone Builds have a [Behavior Quirks](https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html) 42 | page. 43 | 44 | ## Sources 45 | 46 | Portable CPython builds are downloaded from GitHub 47 | ([indygreg/python-build-standalone/releases](https://github.com/indygreg/python-build-standalone/releases)) 48 | and SHA256 hashes are generally validated. Some older versions might not 49 | have hashes available in which case the validation is skipped. 50 | 51 | ## Usage 52 | 53 | When you pin a Python version to `cpython@major.minor.patch` (or just 54 | `major.minor.patch`) then Rye will automatically download the right version 55 | for you whenever it is needed. If a [custom toolchain](index.md#registering-toolchains) has already been registered with that name and 56 | version, that this is used instead. 57 | -------------------------------------------------------------------------------- /docs/guide/toolchains/pypy.md: -------------------------------------------------------------------------------- 1 | # PyPy 2 | 3 | [PyPy](https://www.pypy.org/) is supported as alternative Python distribution. 4 | Like the portable CPython builds it's downloaded automatically. The name for 5 | PyPy distributions is `pypy`. 6 | 7 | ## Limitations 8 | 9 | PyPy has some limitations compared to regular Python builds when it comes to 10 | working with Rye. Most specifically PyPy uses some internal pypi dependencies 11 | and you might notice warnings show up when syching. PyPy also lags behind 12 | regular Python installations quite a bit these days so you likely need to 13 | target older Python packages. 14 | 15 | ## Sources 16 | 17 | PyPy builds are downloaded from 18 | [downloads.python.org](https://downloads.python.org/pypy/). 19 | 20 | ## Usage 21 | 22 | When you pin a Python version to `pypy@major.minor.patch` then Rye will 23 | automatically download the right version for you whenever it is needed. If a 24 | [custom toolchain](index.md#registering-toolchains) has already been registered 25 | with that name and version, that this is used instead. Note that the version 26 | refers to the PyPy **CPython** version. 27 | 28 | That means for instance that PyPy 7.3.11 is identified as `pypy@3.9.16` as this 29 | is the Python version it provides. As PyPy also lacks builds for some CPU 30 | architectures, not all platforms might provide the right PyPy versions. 31 | -------------------------------------------------------------------------------- /docs/guide/tools.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | 3 | Rye supports global tool installations. This for instance allows you to install 4 | tools like `black` or `ruff` globally. 5 | 6 | ## Installing Tools 7 | 8 | Use the [`rye tools install`](commands/tools/install.md) (aliased to [`rye 9 | install`](commands/install.md)) command to install a tool globally with a shim: 10 | 11 | ```bash 12 | rye install ruff 13 | ``` 14 | 15 | Afterwards the tool is installed into `~/.rye/tools/ruff` and the necessary shims 16 | are placed in `~/.rye/shims`. 17 | 18 | +/- 0.4.0 19 | 20 | The `install` command now considers custom sources configured 21 | in the `config.toml` file. For more information see [Dependency Sources](sources.md). 22 | 23 | ## Extra Requirements 24 | 25 | Some tools do not declare all of their dependencies since they might be optional. 26 | In some cases these can be declared by passing extra features to the installer: 27 | 28 | ```bash 29 | rye install black --features colorama 30 | ``` 31 | 32 | If dependencies are not at all specified, then they can be provided with `--extra-requirement`. 33 | This is particularly sometimes necessary if the tool uses `pkg_resources` (part of 34 | `setuptools`) but forgets to declare that dependency: 35 | 36 | ```bash 37 | rye install gradio --extra-requirement setuptools 38 | ``` 39 | 40 | ## Listing Tools 41 | 42 | If you want to see which tools are installed, you can use `rye tools list`: 43 | 44 | ``` 45 | rye tools list 46 | ``` 47 | 48 | ``` 49 | black 50 | black 51 | blackd 52 | ruff 53 | ruff 54 | ``` 55 | 56 | To also see which scripts those tools provide, also pass `--include-scripts` 57 | 58 | ``` 59 | rye tools list --include-scripts 60 | ``` 61 | 62 | ## Uninstalling Tools 63 | 64 | To uninstall a tool again, use `rye tools uninstall` (aliased to `rye uninstall`): 65 | 66 | ``` 67 | rye uninstall black 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/guide/virtual.md: -------------------------------------------------------------------------------- 1 | # Virtual Projects 2 | 3 | +++ 0.20.0 4 | 5 | Virtual projects are projects which are themselves not installable Python 6 | packages, but that will sync their dependencies. They are declared like a 7 | normal python package in a `pyproject.toml`, but they do not create a package. 8 | Instead the `tool.rye.virtual` key is set to `true`. 9 | 10 | For instance this is useful if you want to use a program like `mkdocs` without 11 | declaring a package yourself: 12 | 13 | ``` 14 | rye init --virtual 15 | rye add mkdocs 16 | rye sync 17 | rye run mkdocs 18 | ``` 19 | 20 | This will create a `pyproject.toml` but does not actually declare any python code itself. 21 | Yet when syncing you will end up with mkdocs in your project. 22 | 23 | ## Behavior Changes 24 | 25 | When syncing the project itself is never installed into the virtualenv as it's not 26 | considered to be a valid package. Likewise you cannot publish virtual packages to 27 | PyPI or another index. 28 | 29 | ## Limitations 30 | 31 | Virtual projects can not have optional dependencies. These even if declared are not 32 | installed. 33 | 34 | ## Workspaces 35 | 36 | If a [workspace](../workspaces/) does not have a toplevel package it's 37 | recommended that it's declared as virtual. 38 | -------------------------------------------------------------------------------- /docs/guide/workspaces.md: -------------------------------------------------------------------------------- 1 | # Workspaces 2 | 3 | Workspaces are a feature that allows you to work with multiple packages that 4 | have dependencies on each other. A workspace is declared by setting the 5 | `tool.rye.workspace` key in `pyproject.toml`. Afterwards, all projects within 6 | that workspace share a singular virtualenv. 7 | 8 | ## Declaring Workspaces 9 | 10 | A workspace is declared in the "toplevel" `pyproject.toml`. At the very least 11 | the key `tool.rye.workspace` needs to be added. It's also recommended to 12 | set a glob pattern in the `members` key to prevent accidentally including 13 | unintended folders as projects. 14 | 15 | ```toml 16 | [tool.rye.workspace] 17 | members = ["myname-*"] 18 | ``` 19 | 20 | This declares a workspace where all folders starting with the name `myname-` 21 | are considered. If the toplevel workspace itself should not be a project, 22 | then it should be declared as a virtual package: 23 | 24 | ```toml 25 | [tool.rye] 26 | virtual = true 27 | 28 | [tool.rye.workspace] 29 | members = ["myname-*"] 30 | ``` 31 | 32 | For more information on that, see [Virtual Packages](../virtual/). 33 | 34 | ## Syncing 35 | 36 | In a workspace, it does not matter which project you are working with, the entire 37 | workspace is synchronized at all times. This has some atypical consequences but 38 | simplifies the general development workflow. 39 | 40 | When a package depends on another package it's first located in the workspace locally 41 | before it's attempted to be downloaded from an index. The `--all-features` flag is 42 | automatically applied to all packages, but to turn on the feature of a specific 43 | package the feature name must be prefixed. For instance to enable the `foo` extra feature 44 | of the `myname-bar` package you would need to do this: 45 | 46 | ``` 47 | rye sync --features=myname-bar/foo 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/hooks.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | 5 | def copy_get(config, **kwargs): 6 | site_dir = config["site_dir"] 7 | shutil.copy("scripts/install.sh", os.path.join(site_dir, "get")) 8 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | --- 5 | 6 |
7 | 8 |

Rye: a Hassle-Free Python Experience

9 |
10 | 11 | !!! note 12 | 13 | If you're getting started with Rye, consider [uv](https://github.com/astral-sh/uv), the 14 | [successor project](https://lucumr.pocoo.org/2024/2/15/rye-grows-with-uv/) from the same maintainers. 15 | 16 | While Rye is actively maintained, uv offers a more stable and feature-complete experience, and is the recommended 17 | choice for new projects. 18 | 19 | Having trouble migrating? [Let us know what's missing.](https://github.com/astral-sh/rye/discussions/1342) 20 | 21 | Rye is a comprehensive project and package management solution for Python. 22 | Born from [its creator's](https://github.com/mitsuhiko) desire to establish a 23 | one-stop-shop for all Python users, Rye provides a unified experience to install and manages Python 24 | installations, `pyproject.toml` based projects, dependencies and virtualenvs 25 | seamlessly. It's designed to accommodate complex projects, monorepos and to 26 | facilitate global tool installations. Curious? [Watch an introduction](https://youtu.be/q99TYA7LnuA). 27 | 28 | A hassle-free experience for Python developers at every level. 29 | 30 | 31 |

32 | Star 33 | Discuss 34 | Sponsor 35 |

36 | 37 | !!! abstract "Installation Instructions" 38 | 39 | {% include-markdown ".includes/quick-install.md" %} 40 | 41 | For the next steps or ways to customize the installation, head over to the detailed 42 | [installation](./guide/installation.md) guide. 43 | -------------------------------------------------------------------------------- /docs/static/banner-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/rye/e213938342cd08553a7672a417c4fa821c4d7172/docs/static/banner-dark.png -------------------------------------------------------------------------------- /docs/static/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/rye/e213938342cd08553a7672a417c4fa821c4d7172/docs/static/banner.png -------------------------------------------------------------------------------- /docs/static/extra.css: -------------------------------------------------------------------------------- 1 | [data-md-color-scheme="default"], 2 | :root { 3 | --md-primary-fg-color: #7C9F35; 4 | --md-primary-fg-color--light: #ADC541; 5 | --md-primary-fg-color--dark: #9BB029; 6 | --md-accent-fg-color: #54780d; 7 | } 8 | 9 | [data-md-color-scheme="slate"] { 10 | --md-hue: 190; 11 | --md-accent-fg-color: #90c820; 12 | } 13 | 14 | .md-header { 15 | background: url(/static/banner.png) repeat-x; 16 | background-color: var(--md-primary-fg-color); 17 | background-size: 605px 75px; 18 | } 19 | 20 | nav.md-tabs { 21 | background: url(/static/banner-dark.png) repeat-x; 22 | background-color: transparent; 23 | background-size: 605px 75px; 24 | } 25 | 26 | [data-md-component="palette"] { 27 | display: none; 28 | } 29 | 30 | /* version changed */ 31 | :root { 32 | /* Icon for "version-added" admonition: Material Design Icons "plus-box-outline" */ 33 | --md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,'); 34 | /* Icon for "version-changed" admonition: Material Design Icons "delta" */ 35 | --md-admonition-icon--version-changed: url('data:image/svg+xml;charset=utf-8,'); 36 | /* Icon for "version-removed" admonition: Material Design Icons "minus-circle-outline" */ 37 | --md-admonition-icon--version-removed: url('data:image/svg+xml;charset=utf-8,'); 38 | } 39 | 40 | .md-typeset .admonition.version-added, 41 | .md-typeset .admonition.version-changed, 42 | .md-typeset .admonition.version-removed { 43 | border-radius: 3px; 44 | } 45 | 46 | .md-typeset .admonition.version-added p.admonition-title, 47 | .md-typeset .admonition.version-changed p.admonition-title, 48 | .md-typeset .admonition.version-removed p.admonition-title { 49 | font-weight: normal; 50 | } 51 | 52 | /* "version-added" admonition in green */ 53 | .md-typeset .admonition.version-added, 54 | .md-typeset details.version-added { 55 | border-color: rgb(11, 129, 38); 56 | } 57 | 58 | .md-typeset .version-added>.admonition-title, 59 | .md-typeset .version-added>summary { 60 | background-color: rgba(4, 91, 40, 0.1); 61 | } 62 | 63 | .md-typeset .version-added>.admonition-title::before, 64 | .md-typeset .version-added>summary::before { 65 | background-color: rgb(0, 200, 83); 66 | -webkit-mask-image: var(--md-admonition-icon--version-added); 67 | mask-image: var(--md-admonition-icon--version-added); 68 | } 69 | 70 | /* "version-changed" admonition in orange */ 71 | .md-typeset .admonition.version-changed, 72 | .md-typeset details.version-changed { 73 | border-color: rgb(255, 145, 0); 74 | } 75 | 76 | .md-typeset .version-changed>.admonition-title, 77 | .md-typeset .version-changed>summary { 78 | background-color: rgba(255, 145, 0, .1); 79 | } 80 | 81 | .md-typeset .version-changed>.admonition-title::before, 82 | .md-typeset .version-changed>summary::before { 83 | background-color: rgb(255, 145, 0); 84 | -webkit-mask-image: var(--md-admonition-icon--version-changed); 85 | mask-image: var(--md-admonition-icon--version-changed); 86 | } 87 | 88 | /* "version-removed" admonition in red */ 89 | .md-typeset .admonition.version-removed, 90 | .md-typeset details.version-removed { 91 | border-color: rgb(255, 82, 82); 92 | } 93 | 94 | .md-typeset .version-removed>.admonition-title, 95 | .md-typeset .version-removed>summary { 96 | background-color: rgba(255, 82, 82, .1); 97 | } 98 | 99 | .md-typeset .version-removed>.admonition-title::before, 100 | .md-typeset .version-removed>summary::before { 101 | background-color: rgb(255, 82, 82); 102 | -webkit-mask-image: var(--md-admonition-icon--version-removed); 103 | mask-image: var(--md-admonition-icon--version-removed); 104 | } -------------------------------------------------------------------------------- /docs/static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/static/logo-auto.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/static/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notes/README.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | This folder contains various thoughts and notes about Python packaging. 4 | 5 | * [Meta Server](metasrv.md): why a meta data server should exist. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rye-dev" 3 | version = "1.0.0" 4 | description = "Workspace for rye development" 5 | authors = [{ name = "Armin Ronacher", email = "armin.ronacher@active-4.com" }] 6 | dependencies = [ 7 | "mkdocs~=1.4.3", 8 | "mkdocs-material~=9.1.12", 9 | "pymdown-extensions~=9.11", 10 | "mdx-gh-links~=0.3", 11 | "mkdocs-simple-hooks~=0.1.5", 12 | "mkdocs-version-annotations~=1.0.0", 13 | "mkdocs-include-markdown-plugin~=4.0.4", 14 | ] 15 | readme = "README.md" 16 | requires-python = ">= 3.8" 17 | 18 | [tool.rye] 19 | managed = true 20 | virtual = true 21 | 22 | [tool.rye.scripts] 23 | serve-docs = "mkdocs serve" 24 | 25 | [tool.rye.workspace] 26 | members = ["rye-devtools"] 27 | 28 | [tool.pytest.ini_options] 29 | addopts = "--ignore target" 30 | -------------------------------------------------------------------------------- /requirements-dev.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | 11 | -e file:rye-devtools 12 | anyio==4.4.0 13 | # via httpx 14 | certifi==2024.6.2 15 | # via httpcore 16 | # via httpx 17 | # via requests 18 | charset-normalizer==3.3.2 19 | # via requests 20 | click==8.1.7 21 | # via mkdocs 22 | colorama==0.4.6 23 | # via mkdocs-material 24 | ghp-import==2.1.0 25 | # via mkdocs 26 | h11==0.14.0 27 | # via httpcore 28 | httpcore==1.0.5 29 | # via httpx 30 | httpx==0.27.0 31 | # via rye-devtools 32 | idna==3.7 33 | # via anyio 34 | # via httpx 35 | # via requests 36 | iniconfig==2.0.0 37 | # via pytest 38 | jinja2==3.1.4 39 | # via mkdocs 40 | # via mkdocs-material 41 | markdown==3.3.7 42 | # via mdx-gh-links 43 | # via mkdocs 44 | # via mkdocs-material 45 | # via pymdown-extensions 46 | markupsafe==2.1.5 47 | # via jinja2 48 | mdx-gh-links==0.4 49 | mergedeep==1.3.4 50 | # via mkdocs 51 | mkdocs==1.4.3 52 | # via mkdocs-material 53 | # via mkdocs-simple-hooks 54 | mkdocs-include-markdown-plugin==4.0.4 55 | mkdocs-material==9.1.20 56 | mkdocs-material-extensions==1.3.1 57 | # via mkdocs-material 58 | mkdocs-simple-hooks==0.1.5 59 | mkdocs-version-annotations==1.0.0 60 | packaging==24.1 61 | # via mkdocs 62 | # via pytest 63 | pluggy==1.5.0 64 | # via pytest 65 | pygments==2.18.0 66 | # via mkdocs-material 67 | pymdown-extensions==9.11 68 | # via mkdocs-material 69 | pytest==8.0.2 70 | python-dateutil==2.9.0.post0 71 | # via ghp-import 72 | pyyaml==6.0.1 73 | # via mkdocs 74 | # via pymdown-extensions 75 | # via pyyaml-env-tag 76 | pyyaml-env-tag==0.1 77 | # via mkdocs 78 | regex==2024.5.15 79 | # via mkdocs-material 80 | requests==2.32.3 81 | # via mkdocs-material 82 | six==1.16.0 83 | # via python-dateutil 84 | sniffio==1.3.1 85 | # via anyio 86 | # via httpx 87 | socksio==1.0.0 88 | # via httpx 89 | urllib3==2.2.2 90 | # via requests 91 | watchdog==4.0.1 92 | # via mkdocs 93 | -------------------------------------------------------------------------------- /requirements.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | 11 | -e file:rye-devtools 12 | anyio==4.4.0 13 | # via httpx 14 | certifi==2024.6.2 15 | # via httpcore 16 | # via httpx 17 | # via requests 18 | charset-normalizer==3.3.2 19 | # via requests 20 | click==8.1.7 21 | # via mkdocs 22 | colorama==0.4.6 23 | # via mkdocs-material 24 | ghp-import==2.1.0 25 | # via mkdocs 26 | h11==0.14.0 27 | # via httpcore 28 | httpcore==1.0.5 29 | # via httpx 30 | httpx==0.27.0 31 | # via rye-devtools 32 | idna==3.7 33 | # via anyio 34 | # via httpx 35 | # via requests 36 | jinja2==3.1.4 37 | # via mkdocs 38 | # via mkdocs-material 39 | markdown==3.3.7 40 | # via mdx-gh-links 41 | # via mkdocs 42 | # via mkdocs-material 43 | # via pymdown-extensions 44 | markupsafe==2.1.5 45 | # via jinja2 46 | mdx-gh-links==0.4 47 | mergedeep==1.3.4 48 | # via mkdocs 49 | mkdocs==1.4.3 50 | # via mkdocs-material 51 | # via mkdocs-simple-hooks 52 | mkdocs-include-markdown-plugin==4.0.4 53 | mkdocs-material==9.1.20 54 | mkdocs-material-extensions==1.3.1 55 | # via mkdocs-material 56 | mkdocs-simple-hooks==0.1.5 57 | mkdocs-version-annotations==1.0.0 58 | packaging==24.1 59 | # via mkdocs 60 | pygments==2.18.0 61 | # via mkdocs-material 62 | pymdown-extensions==9.11 63 | # via mkdocs-material 64 | python-dateutil==2.9.0.post0 65 | # via ghp-import 66 | pyyaml==6.0.1 67 | # via mkdocs 68 | # via pymdown-extensions 69 | # via pyyaml-env-tag 70 | pyyaml-env-tag==0.1 71 | # via mkdocs 72 | regex==2024.5.15 73 | # via mkdocs-material 74 | requests==2.32.3 75 | # via mkdocs-material 76 | six==1.16.0 77 | # via python-dateutil 78 | sniffio==1.3.1 79 | # via anyio 80 | # via httpx 81 | socksio==1.0.0 82 | # via httpx 83 | urllib3==2.2.2 84 | # via requests 85 | watchdog==4.0.1 86 | # via mkdocs 87 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.84" 3 | -------------------------------------------------------------------------------- /rye-devtools/.python-version: -------------------------------------------------------------------------------- 1 | 3.11.1 2 | -------------------------------------------------------------------------------- /rye-devtools/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rye-devtools" 3 | version = "1.0.0" 4 | description = "Development tools for rye" 5 | authors = [{ name = "Armin Ronacher", email = "armin.ronacher@active-4.com" }] 6 | dependencies = [ 7 | "httpx[socks]>=0.26.0", 8 | ] 9 | requires-python = ">= 3.11" 10 | 11 | [project.scripts] 12 | find-downloads = "rye_devtools.find_downloads:main" 13 | uv-downloads = "rye_devtools.find_uv_downloads:main" 14 | 15 | [build-system] 16 | requires = ["hatchling"] 17 | build-backend = "hatchling.build" 18 | 19 | [tool.rye] 20 | managed = true 21 | dev-dependencies = [ 22 | "pytest==8.0.2", 23 | ] 24 | 25 | [tool.hatch.metadata] 26 | allow-direct-references = true 27 | 28 | [tool.hatch.build.targets.wheel] 29 | packages = ["src/rye_devtools"] 30 | -------------------------------------------------------------------------------- /rye-devtools/src/rye_devtools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/rye/e213938342cd08553a7672a417c4fa821c4d7172/rye-devtools/src/rye_devtools/__init__.py -------------------------------------------------------------------------------- /rye-devtools/src/rye_devtools/common.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import sys 3 | import time 4 | from datetime import datetime, timezone 5 | from typing import NamedTuple, Self 6 | 7 | import httpx 8 | 9 | 10 | def log(*args, **kwargs): 11 | print(*args, file=sys.stderr, **kwargs) 12 | 13 | 14 | def batched(iterable, n): 15 | "Batch data into tuples of length n. The last batch may be shorter." 16 | # batched('ABCDEFG', 3) --> ABC DEF G 17 | if n < 1: 18 | raise ValueError("n must be at least one") 19 | it = iter(iterable) 20 | while batch := tuple(itertools.islice(it, n)): 21 | yield batch 22 | 23 | 24 | class PlatformTriple(NamedTuple): 25 | arch: str 26 | platform: str 27 | environment: str | None 28 | flavor: str | None 29 | 30 | 31 | class Version(NamedTuple): 32 | major: int 33 | minor: int 34 | patch: int 35 | 36 | @classmethod 37 | def from_str(cls, version: str) -> Self: 38 | major, minor, patch = version.split(".", 3) 39 | return cls(int(major), int(minor), int(patch)) 40 | 41 | def __str__(self) -> str: 42 | return f"{self.major}.{self.minor}.{self.patch}" 43 | 44 | def __neg__(self) -> Self: 45 | return Version(-self.major, -self.minor, -self.patch) 46 | 47 | 48 | async def fetch(client: httpx.AsyncClient, url: str) -> httpx.Response: 49 | """Fetch from GitHub API with rate limit awareness.""" 50 | resp = await client.get(url, timeout=15) 51 | if ( 52 | resp.status_code in [403, 429] 53 | and resp.headers.get("x-ratelimit-remaining") == "0" 54 | ): 55 | # See https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28 56 | if (retry_after := resp.headers.get("retry-after")) is not None: 57 | log(f"Got retry-after header, retry in {retry_after} seconds.") 58 | time.sleep(int(retry_after)) 59 | 60 | return await fetch(client, url) 61 | 62 | if (retry_at := resp.headers.get("x-ratelimit-reset")) is not None: 63 | utc = datetime.now(timezone.utc).timestamp() 64 | retry_after = max(int(retry_at) - int(utc), 0) 65 | 66 | log(f"Got x-ratelimit-reset header, retry in {retry_after} seconds.") 67 | time.sleep(retry_after) 68 | 69 | return await fetch(client, url) 70 | 71 | log("Got rate limited but no information how long, wait for 2 minutes.") 72 | time.sleep(60 * 2) 73 | return await fetch(client, url) 74 | 75 | resp.raise_for_status() 76 | return resp 77 | -------------------------------------------------------------------------------- /rye-devtools/src/rye_devtools/find_uv_downloads.py: -------------------------------------------------------------------------------- 1 | """This script is used to generate rye/src/sources/generated/uv_downloads.inc. 2 | 3 | It finds the latest UV releases and generates rust code that can be included 4 | into rye at build time. 5 | """ 6 | 7 | import asyncio 8 | import os 9 | import re 10 | import sys 11 | from dataclasses import dataclass 12 | from typing import AsyncIterator 13 | 14 | import httpx 15 | 16 | from .common import PlatformTriple, Version, fetch, log 17 | 18 | 19 | @dataclass 20 | class UvDownload: 21 | triple: PlatformTriple 22 | version: Version 23 | url: str 24 | sha256: str 25 | 26 | 27 | class UvDownloads: 28 | client: httpx.Client 29 | 30 | RELEASE_URL = "https://api.github.com/repos/astral-sh/uv/releases" 31 | 32 | ARCH = { 33 | "x86_64": "x86_64", 34 | "i686": "i686", 35 | "aarch64": "aarch64", 36 | } 37 | 38 | GLIBC = { 39 | "x86_64": "gnu", 40 | "i686": "gnu", 41 | "aarch64": "musl", 42 | } 43 | 44 | PLATFORM_ENV = { 45 | "unknown-linux-gnu": ("linux", "gnu"), 46 | "unknown-linux-musl": ("linux", "musl"), 47 | "apple-darwin": ("macos", None), 48 | "pc-windows-msvc": ("windows", None), 49 | } 50 | 51 | RE = re.compile(r"uv-(?P[^-]+)-(?P.+)(\.tar\.gz|\.zip)$") 52 | 53 | def __init__(self, client: httpx.Client) -> None: 54 | self.client = client 55 | 56 | async def most_recent_downloads( 57 | self, pages: int = 100 58 | ) -> AsyncIterator[UvDownload]: 59 | highest_version = None 60 | for page in range(1, pages): 61 | log(f"fetching page {page}") 62 | resp = await fetch(self.client, "%s?page=%d" % (self.RELEASE_URL, page)) 63 | rows = resp.json() 64 | if not rows: 65 | break 66 | for row in rows: 67 | version = Version.from_str(row["tag_name"]) 68 | if highest_version is None or highest_version < version: 69 | for asset in row["assets"]: 70 | url = asset["browser_download_url"] 71 | if (triple := self.parse_triple(url)) is not None: 72 | sha_resp = await fetch(self.client, url + ".sha256") 73 | sha256 = sha_resp.text.split(" ")[0].strip() 74 | yield UvDownload( 75 | triple=triple, 76 | version=version, 77 | url=url, 78 | sha256=sha256, 79 | ) 80 | highest_version = version 81 | 82 | @classmethod 83 | def parse_triple(cls, url: str) -> PlatformTriple | None: 84 | if (m := re.search(cls.RE, url)) is not None: 85 | arch_str = m.group("arch") 86 | plat_env_str = m.group("plat_env") 87 | if arch_str in cls.ARCH and plat_env_str in cls.PLATFORM_ENV: 88 | arch = cls.ARCH[arch_str] 89 | plat, env = cls.PLATFORM_ENV[plat_env_str] 90 | if env is None or env == cls.GLIBC[arch_str]: 91 | return PlatformTriple( 92 | arch=arch, platform=plat, environment=env, flavor=None 93 | ) 94 | 95 | return None 96 | 97 | 98 | def render(downloads: list[UvDownload]): 99 | print("// Generated by rye-devtools. DO NOT EDIT.") 100 | print( 101 | "// To regenerate, run `rye run uv-downloads > rye/src/sources/generated/uv_downloads.inc` from the root of the repository." 102 | ) 103 | print("use std::borrow::Cow;") 104 | print("pub const UV_DOWNLOADS: &[UvDownload] = &[") 105 | 106 | for download in downloads: 107 | triple = download.triple 108 | version = download.version 109 | sha = download.sha256 110 | url = download.url 111 | print( 112 | f' UvDownload {{arch: Cow::Borrowed("{triple.arch}"), os: Cow::Borrowed("{triple.platform}"), major: {version.major}, minor: {version.minor}, patch: {version.patch}, suffix: None, url: Cow::Borrowed("{url}"), sha256: Cow::Borrowed("{sha}") }},' 113 | ) 114 | 115 | print("];") 116 | 117 | 118 | async def async_main(): 119 | token = os.environ.get("GITHUB_TOKEN") 120 | if not token: 121 | try: 122 | token = open("token.txt").read().strip() 123 | except Exception: 124 | pass 125 | 126 | if not token: 127 | log("Please set GITHUB_TOKEN environment variable or create a token.txt file.") 128 | sys.exit(1) 129 | 130 | headers = { 131 | "X-GitHub-Api-Version": "2022-11-28", 132 | "Authorization": "Bearer " + token, 133 | } 134 | 135 | log("Fetching all uv downloads.") 136 | async with httpx.AsyncClient(follow_redirects=True, headers=headers) as client: 137 | finder = UvDownloads(client) 138 | downloads = [download async for download in finder.most_recent_downloads()] 139 | log("Generating code.") 140 | render(downloads) 141 | 142 | 143 | def main(): 144 | asyncio.run(async_main()) 145 | 146 | 147 | if __name__ == "__main__": 148 | main() 149 | -------------------------------------------------------------------------------- /rye-devtools/tests/test_basic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from rye_devtools.common import batched 3 | from rye_devtools.find_downloads import CPythonFinder, PlatformTriple 4 | 5 | 6 | def test_batched(): 7 | assert list(batched("ABCDEFG", 3)) == [tuple("ABC"), tuple("DEF"), tuple("G")] 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "input, expected", 12 | [ 13 | ("aarch64-apple-darwin-lto", PlatformTriple("aarch64", "macos", None, "lto")), 14 | ( 15 | "aarch64-unknown-linux-gnu-pgo+lto", 16 | PlatformTriple("aarch64", "linux", "gnu", "pgo+lto"), 17 | ), 18 | # ( 19 | # "x86_64-unknown-linux-musl-debug", 20 | # PlatformTriple("x86_64", "linux", "musl", "debug"), 21 | # ), 22 | ( 23 | "aarch64-unknown-linux-gnu-debug-full", 24 | PlatformTriple("aarch64", "linux", "gnu", "debug"), 25 | ), 26 | ( 27 | "x86_64-unknown-linux-gnu-debug", 28 | PlatformTriple("x86_64", "linux", "gnu", "debug"), 29 | ), 30 | ("linux64", PlatformTriple("x86_64", "linux", "gnu", None)), 31 | ("ppc64le-unknown-linux-gnu-noopt-full", None), 32 | ("x86_64_v3-unknown-linux-gnu-lto", None), 33 | ( 34 | "x86_64-pc-windows-msvc-shared-pgo", 35 | PlatformTriple("x86_64", "windows", None, "shared-pgo"), 36 | ), 37 | ("aarch64-apple-darwin-freethreaded+pgo-full", None), 38 | ], 39 | ) 40 | def test_parse_triplets(input, expected): 41 | assert CPythonFinder.parse_triple(input) == expected 42 | -------------------------------------------------------------------------------- /rye/.gitignore: -------------------------------------------------------------------------------- 1 | token.txt 2 | -------------------------------------------------------------------------------- /rye/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rye" 3 | version = "0.44.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | age = "0.10.0" 11 | anyhow = { version = "1.0.70", features = ["backtrace"] } 12 | clap = { version = "4.3.5", default-features = false, features = [ 13 | "derive", 14 | "usage", 15 | "wrap_help", 16 | "std", 17 | ] } 18 | clap_complete = "4.2.1" 19 | clap_complete_nushell = "4.5.1" 20 | console = "0.15.7" 21 | curl = { version = "0.4.44", features = ["ssl", "static-curl", "static-ssl"] } 22 | flate2 = "1.0.25" 23 | git-testament = "0.2.4" 24 | globset = "0.4.10" 25 | indicatif = "0.17.3" 26 | license = { version = "3.1.1", features = ["offline"] } 27 | minijinja = { version = "2.0.1", features = ["json"] } 28 | once_cell = "1.17.1" 29 | pathdiff = "0.2.1" 30 | pep440_rs = "0.4.0" 31 | pep508_rs = "0.3.0" 32 | regex = "1.8.1" 33 | same-file = "1.0.6" 34 | serde = { version = "1.0.160", features = ["derive"] } 35 | serde_json = "1.0.94" 36 | shlex = "1.3.0" 37 | slug = "0.1.4" 38 | tar = "0.4.38" 39 | tempfile = "3.5.0" 40 | toml_edit = "0.22.9" 41 | url = "2.3.1" 42 | walkdir = "2.3.3" 43 | which = "6.0.0" 44 | zstd = "0.13.0" 45 | sha2 = "0.10.6" 46 | dialoguer = { git = "https://github.com/console-rs/dialoguer", rev = "47a9d4df729db7ffc1492bd0845be786e6f20153" } 47 | hex = "0.4.3" 48 | junction = "1.0.0" 49 | bzip2 = "0.4.4" 50 | zip = { version = "0.6.5", features = ["deflate"], default-features = false } 51 | self-replace = "1.3.5" 52 | configparser = "3.0.2" 53 | monotrail-utils = { git = "https://github.com/konstin/poc-monotrail", rev = "e0251f68c254f834180198b8677fcf85d4b6a844" } 54 | python-pkginfo = { version = "0.6.0", features = ["serde"] } 55 | home = "0.5.9" 56 | ctrlc = "3.4.2" 57 | dotenvy = "0.15.7" 58 | 59 | [target."cfg(unix)".dependencies] 60 | xattr = "1.3.1" 61 | 62 | [target."cfg(windows)".dependencies] 63 | winapi = { version = "0.3.9", default-features = false, features = ["winuser", "winioctl", "ioapiset"] } 64 | winreg = "0.52.0" 65 | 66 | [target."cfg(windows)".build-dependencies] 67 | static_vcruntime = "2.0.0" 68 | 69 | [dev-dependencies] 70 | fslock = "0.2.1" 71 | insta = { version = "1.35.1", features = ["filters"] } 72 | insta-cmd = "0.5.0" 73 | -------------------------------------------------------------------------------- /rye/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(windows)] 3 | { 4 | static_vcruntime::metabuild(); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /rye/src/cli/build.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | use std::process::{Command, Stdio}; 4 | 5 | use anyhow::{anyhow, bail, Context, Error}; 6 | use clap::Parser; 7 | use console::style; 8 | 9 | use crate::bootstrap::{fetch, FetchOptions}; 10 | 11 | use crate::platform::get_toolchain_python_bin; 12 | use crate::pyproject::{locate_projects, PyProject}; 13 | use crate::utils::{get_venv_python_bin, prepend_path_to_path_env, CommandOutput, IoPathContext}; 14 | use crate::uv::UvBuilder; 15 | 16 | /// Builds a package for distribution. 17 | #[derive(Parser, Debug)] 18 | pub struct Args { 19 | /// Build a sdist 20 | #[arg(long)] 21 | sdist: bool, 22 | /// Build a wheel 23 | #[arg(long)] 24 | wheel: bool, 25 | /// Build all packages 26 | #[arg(short, long)] 27 | all: bool, 28 | /// Build a specific package 29 | #[arg(short, long)] 30 | package: Vec, 31 | /// An output directory (defaults to `workspace/dist`) 32 | #[arg(short, long)] 33 | out: Option, 34 | /// Use this pyproject.toml file 35 | #[arg(long, value_name = "PYPROJECT_TOML")] 36 | pyproject: Option, 37 | /// Clean the output directory first 38 | #[arg(short, long)] 39 | clean: bool, 40 | /// Enables verbose diagnostics. 41 | #[arg(short, long)] 42 | verbose: bool, 43 | /// Turns off all output. 44 | #[arg(short, long, conflicts_with = "verbose")] 45 | quiet: bool, 46 | } 47 | 48 | pub fn execute(cmd: Args) -> Result<(), Error> { 49 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 50 | let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?; 51 | let py_ver = project.venv_python_version()?; 52 | 53 | let out = match cmd.out { 54 | Some(path) => path, 55 | None => project.workspace_path().join("dist"), 56 | }; 57 | 58 | if out.exists() && cmd.clean { 59 | for entry in fs::read_dir(&out).path_context(&out, "enumerate build output")? { 60 | let path = entry?.path(); 61 | if path.is_file() { 62 | fs::remove_file(&path).path_context(&path, "clean build artifact")?; 63 | } 64 | } 65 | } 66 | 67 | let projects = locate_projects(project, cmd.all, &cmd.package[..])?; 68 | 69 | let all_virtual = projects.iter().all(|p| p.is_virtual()); 70 | if all_virtual { 71 | warn!("skipping build, all projects are virtual"); 72 | return Ok(()); 73 | } 74 | 75 | // Make sure we have a compatible Python version. 76 | let py_ver = fetch(&py_ver.into(), FetchOptions::with_output(output)) 77 | .context("failed fetching toolchain ahead of sync")?; 78 | echo!(if output, "Python version: {}", style(&py_ver).cyan()); 79 | let py_bin = get_toolchain_python_bin(&py_ver)?; 80 | 81 | // Create a virtual environment in which to perform the builds. 82 | let uv = UvBuilder::new() 83 | .with_output(CommandOutput::Quiet) 84 | .ensure_exists()?; 85 | let venv_dir = tempfile::tempdir().context("failed to create temporary directory")?; 86 | let uv_venv = uv 87 | .venv(venv_dir.path(), &py_bin, &py_ver, None) 88 | .context("failed to create build environment")?; 89 | uv_venv.write_marker()?; 90 | uv_venv.bootstrap()?; 91 | 92 | for project in projects { 93 | // skip over virtual packages on build 94 | if project.is_virtual() { 95 | continue; 96 | } 97 | 98 | echo!( 99 | if output, 100 | "building {}", 101 | style(project.normalized_name()?).cyan() 102 | ); 103 | 104 | let mut build_cmd = Command::new(get_venv_python_bin(venv_dir.path())); 105 | build_cmd 106 | .arg("-mbuild") 107 | .env("NO_COLOR", "1") 108 | .arg("--outdir") 109 | .arg(&out) 110 | .arg(&*project.root_path()); 111 | 112 | // we need to ensure uv is available to use without installing it into self_venv 113 | let uv = UvBuilder::new() 114 | .with_output(output) 115 | .ensure_exists()? 116 | .with_output(output); 117 | let uv_dir = uv 118 | .uv_bin() 119 | .parent() 120 | .ok_or_else(|| anyhow!("Could not find uv binary in self venv: empty path"))?; 121 | build_cmd.env("PATH", prepend_path_to_path_env(uv_dir)?); 122 | build_cmd.arg("--installer=uv"); 123 | 124 | if cmd.wheel { 125 | build_cmd.arg("--wheel"); 126 | } 127 | if cmd.sdist { 128 | build_cmd.arg("--sdist"); 129 | } 130 | 131 | if output == CommandOutput::Verbose { 132 | build_cmd.arg("--verbose"); 133 | } 134 | 135 | if output == CommandOutput::Quiet { 136 | build_cmd.stdout(Stdio::null()); 137 | build_cmd.stderr(Stdio::null()); 138 | } 139 | 140 | let status = build_cmd.status()?; 141 | if !status.success() { 142 | bail!("failed to build dist"); 143 | } 144 | } 145 | Ok(()) 146 | } 147 | -------------------------------------------------------------------------------- /rye/src/cli/fetch.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::{Context, Error}; 4 | use clap::Parser; 5 | 6 | use crate::bootstrap::{fetch, FetchOptions}; 7 | use crate::config::Config; 8 | use crate::platform::get_python_version_request_from_pyenv_pin; 9 | use crate::pyproject::PyProject; 10 | use crate::sources::py::PythonVersionRequest; 11 | use crate::utils::CommandOutput; 12 | 13 | /// Fetches a Python interpreter for the local machine. 14 | #[derive(Parser, Debug)] 15 | pub struct Args { 16 | /// The version of Python to fetch. 17 | /// 18 | /// If no version is provided, the requested version from local project or `.python-version` will be fetched. 19 | version: Option, 20 | /// Fetch the Python toolchain even if it is already installed. 21 | #[arg(short, long)] 22 | force: bool, 23 | /// Fetches the Python toolchain into an explicit location rather. 24 | #[arg(long)] 25 | target_path: Option, 26 | /// Fetches with build info. 27 | #[arg(long)] 28 | build_info: bool, 29 | /// Fetches without build info. 30 | #[arg(long, conflicts_with = "build_info")] 31 | no_build_info: bool, 32 | /// Enables verbose diagnostics. 33 | #[arg(short, long)] 34 | verbose: bool, 35 | /// Turns off all output. 36 | #[arg(short, long, conflicts_with = "verbose")] 37 | quiet: bool, 38 | } 39 | 40 | pub fn execute(cmd: Args) -> Result<(), Error> { 41 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 42 | 43 | let version: PythonVersionRequest = match cmd.version { 44 | Some(version) => version.parse()?, 45 | None => { 46 | if let Ok(pyproject) = PyProject::discover() { 47 | pyproject.venv_python_version()?.into() 48 | } else { 49 | match get_python_version_request_from_pyenv_pin(&std::env::current_dir()?) { 50 | Some(version) => version, 51 | None => Config::current().default_toolchain()?, 52 | } 53 | } 54 | } 55 | }; 56 | 57 | fetch( 58 | &version, 59 | FetchOptions { 60 | output, 61 | force: cmd.force, 62 | target_path: cmd.target_path, 63 | build_info: if cmd.build_info { 64 | Some(true) 65 | } else if cmd.no_build_info { 66 | Some(false) 67 | } else { 68 | None 69 | }, 70 | }, 71 | ) 72 | .context("error while fetching Python installation")?; 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /rye/src/cli/fmt.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use clap::Parser; 3 | 4 | use crate::utils::ruff; 5 | 6 | /// Run the code formatter on the project. 7 | /// 8 | /// This invokes ruff in format mode. 9 | #[derive(Parser, Debug)] 10 | pub struct Args { 11 | #[command(flatten)] 12 | ruff: ruff::RuffArgs, 13 | /// Run format in check mode 14 | #[arg(long)] 15 | check: bool, 16 | } 17 | 18 | pub fn execute(cmd: Args) -> Result<(), Error> { 19 | let mut args = Vec::new(); 20 | args.push("format"); 21 | if cmd.check { 22 | args.push("--check"); 23 | } 24 | ruff::execute_ruff(cmd.ruff, &args) 25 | } 26 | -------------------------------------------------------------------------------- /rye/src/cli/install.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::{Context, Error}; 4 | use clap::Parser; 5 | use pep508_rs::Requirement; 6 | 7 | use crate::cli::add::ReqExtras; 8 | use crate::config::Config; 9 | use crate::installer::{install, resolve_local_requirement}; 10 | use crate::lock::KeyringProvider; 11 | use crate::sources::py::PythonVersionRequest; 12 | use crate::utils::CommandOutput; 13 | 14 | /// Installs a package as global tool. 15 | #[derive(Parser, Debug)] 16 | pub struct Args { 17 | /// The name of the package to install. 18 | requirement: String, 19 | #[command(flatten)] 20 | req_extras: ReqExtras, 21 | /// Include scripts from a given dependency. 22 | #[arg(long)] 23 | include_dep: Vec, 24 | /// Additional dependencies to install that are not declared by the main package. 25 | #[arg(long)] 26 | extra_requirement: Vec, 27 | /// Optionally the Python version to use. 28 | #[arg(short, long)] 29 | python: Option, 30 | /// Force install the package even if it's already there. 31 | #[arg(short, long)] 32 | force: bool, 33 | /// Attempt to use `keyring` for authentication for index URLs. 34 | #[arg(long, value_enum, default_value_t)] 35 | keyring_provider: KeyringProvider, 36 | /// Enables verbose diagnostics. 37 | #[arg(short, long)] 38 | verbose: bool, 39 | /// Turns off all output. 40 | #[arg(short, long, conflicts_with = "verbose")] 41 | quiet: bool, 42 | } 43 | 44 | pub fn execute(mut cmd: Args) -> Result<(), Error> { 45 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 46 | let mut extra_requirements = Vec::new(); 47 | 48 | // main requirement 49 | let mut requirement = handle_requirement(&cmd.requirement, output, true)?; 50 | // installations here always use absolute paths for local references 51 | // because we do not have a rye workspace to work with. 52 | cmd.req_extras.force_absolute(); 53 | cmd.req_extras.apply_to_requirement(&mut requirement)?; 54 | 55 | for req in cmd.extra_requirement { 56 | extra_requirements.push(handle_requirement(&req, output, false)?); 57 | } 58 | 59 | let py_ver: PythonVersionRequest = match cmd.python { 60 | Some(ref py) => py.parse()?, 61 | None => Config::current() 62 | .default_toolchain() 63 | .unwrap_or(PythonVersionRequest { 64 | name: None, 65 | arch: None, 66 | os: None, 67 | major: 3, 68 | minor: None, 69 | patch: None, 70 | suffix: None, 71 | }), 72 | }; 73 | 74 | install( 75 | requirement, 76 | &py_ver, 77 | cmd.force, 78 | &cmd.include_dep, 79 | &extra_requirements, 80 | output, 81 | cmd.keyring_provider, 82 | )?; 83 | Ok(()) 84 | } 85 | 86 | fn handle_requirement( 87 | req: &str, 88 | output: CommandOutput, 89 | local_hint: bool, 90 | ) -> Result { 91 | Ok(match resolve_local_requirement(Path::new(req), output)? { 92 | Some(req) => req, 93 | None => req.parse::().with_context(|| { 94 | if local_hint && req.contains("://") { 95 | format!( 96 | "failed to parse requirement '{}'. It looks like a URL, maybe \ 97 | you wanted to use --url or --git", 98 | req 99 | ) 100 | } else { 101 | format!("failed to parse requirement '{}'", req) 102 | } 103 | })?, 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /rye/src/cli/lint.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use clap::Parser; 3 | 4 | use crate::utils::ruff; 5 | 6 | /// Run the linter on the project. 7 | /// 8 | /// This invokes ruff in lint mode. 9 | #[derive(Parser, Debug)] 10 | pub struct Args { 11 | #[command(flatten)] 12 | ruff: ruff::RuffArgs, 13 | /// Apply fixes. 14 | #[arg(long)] 15 | fix: bool, 16 | } 17 | 18 | pub fn execute(cmd: Args) -> Result<(), Error> { 19 | let mut args = Vec::new(); 20 | args.push("check"); 21 | if cmd.fix { 22 | args.push("--fix"); 23 | } 24 | ruff::execute_ruff(cmd.ruff, &args) 25 | } 26 | -------------------------------------------------------------------------------- /rye/src/cli/list.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Error; 4 | use clap::Parser; 5 | 6 | use crate::pyproject::PyProject; 7 | use crate::utils::{get_venv_python_bin, CommandOutput}; 8 | use crate::uv::{UvBuilder, Venv}; 9 | 10 | /// Prints the currently installed packages. 11 | #[derive(Parser, Debug)] 12 | pub struct Args { 13 | /// Use this pyproject.toml file 14 | #[arg(long, value_name = "PYPROJECT_TOML")] 15 | pub(crate) pyproject: Option, 16 | } 17 | 18 | pub fn execute(cmd: Args) -> Result<(), Error> { 19 | let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?; 20 | let python = get_venv_python_bin(&project.venv_path()); 21 | if !python.is_file() { 22 | warn!("Project is not synced, no virtualenv found. Run `rye sync`."); 23 | return Ok(()); 24 | } 25 | let uv = UvBuilder::new() 26 | .with_output(CommandOutput::Normal) 27 | .ensure_exists()?; 28 | uv.read_only_venv(&project.venv_path())?.freeze()?; 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /rye/src/cli/lock.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Error; 4 | use clap::Parser; 5 | 6 | use crate::lock::{KeyringProvider, LockOptions}; 7 | use crate::sync::{sync, SyncMode, SyncOptions}; 8 | use crate::utils::CommandOutput; 9 | 10 | /// Updates the lockfiles without installing dependencies. 11 | #[derive(Parser, Debug)] 12 | pub struct Args { 13 | /// Enables verbose diagnostics. 14 | #[arg(short, long)] 15 | verbose: bool, 16 | /// Turns off all output. 17 | #[arg(short, long, conflicts_with = "verbose")] 18 | quiet: bool, 19 | /// Update a specific package. 20 | #[arg(long)] 21 | update: Vec, 22 | /// Update all packages to the latest 23 | #[arg(long)] 24 | update_all: bool, 25 | /// Update to pre-release versions 26 | #[arg(long)] 27 | pre: bool, 28 | /// Extras/features to enable when locking the workspace. 29 | #[arg(long)] 30 | features: Vec, 31 | /// Enables all features. 32 | #[arg(long)] 33 | all_features: bool, 34 | /// Set to true to lock with sources in the lockfile. 35 | #[arg(long)] 36 | with_sources: bool, 37 | /// Attempt to use `keyring` for authentication for index URLs. 38 | #[arg(long, value_enum, default_value_t)] 39 | keyring_provider: KeyringProvider, 40 | /// Set to true to lock with hashes in the lockfile. 41 | #[arg(long)] 42 | generate_hashes: bool, 43 | /// Use universal lock files. 44 | #[arg(long)] 45 | universal: bool, 46 | /// Reset prior lock options. 47 | #[arg(long)] 48 | reset: bool, 49 | /// Use this pyproject.toml file. 50 | #[arg(long, value_name = "PYPROJECT_TOML")] 51 | pyproject: Option, 52 | } 53 | 54 | pub fn execute(cmd: Args) -> Result<(), Error> { 55 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 56 | sync(SyncOptions { 57 | output, 58 | mode: SyncMode::LockOnly, 59 | lock_options: LockOptions { 60 | update: cmd.update, 61 | update_all: cmd.update_all, 62 | pre: cmd.pre, 63 | features: cmd.features, 64 | all_features: cmd.all_features, 65 | with_sources: cmd.with_sources, 66 | reset: cmd.reset, 67 | generate_hashes: cmd.generate_hashes, 68 | universal: cmd.universal, 69 | }, 70 | pyproject: cmd.pyproject, 71 | keyring_provider: cmd.keyring_provider, 72 | ..SyncOptions::default() 73 | })?; 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /rye/src/cli/make_req.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::{Context, Error}; 4 | use clap::Parser; 5 | use pep508_rs::Requirement; 6 | 7 | use crate::cli::add::ReqExtras; 8 | use crate::utils::format_requirement; 9 | 10 | /// Builds and prints a PEP 508 requirement string from parts. 11 | #[derive(Parser, Debug)] 12 | pub struct Args { 13 | /// The package to add as PEP 508 requirement string. e.g. 'flask==2.2.3' 14 | requirements: Vec, 15 | #[command(flatten)] 16 | req_extras: ReqExtras, 17 | } 18 | 19 | pub fn execute(cmd: Args) -> Result<(), Error> { 20 | for requirement_str in cmd.requirements { 21 | let mut requirement = Requirement::from_str(&requirement_str) 22 | .with_context(|| format!("unable to parse requirement '{}'", requirement_str))?; 23 | cmd.req_extras.apply_to_requirement(&mut requirement)?; 24 | echo!("{}", format_requirement(&requirement)); 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /rye/src/cli/pin.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | use anyhow::Context; 6 | use anyhow::{anyhow, Error}; 7 | use clap::Parser; 8 | 9 | use crate::platform::get_pinnable_version; 10 | use crate::pyproject::DiscoveryUnsuccessful; 11 | use crate::pyproject::PyProject; 12 | use crate::sources::py::PythonVersionRequest; 13 | use crate::utils::IoPathContext; 14 | 15 | /// Pins a Python version to this project. 16 | /// 17 | /// This will update the `.python-version` to point to the provided version. 18 | /// Additionally it will update `requires-python` in the `pyproject.toml` 19 | /// if it's lower than the current version. This can be disabled by passing 20 | /// `--no-update-requires-python`. 21 | #[derive(Parser, Debug)] 22 | pub struct Args { 23 | /// The version of Python to pin. 24 | version: String, 25 | /// Issue a relaxed pin 26 | #[arg(long)] 27 | relaxed: bool, 28 | /// Prevent updating requires-python in the pyproject.toml. 29 | #[arg(long)] 30 | no_update_requires_python: bool, 31 | /// Use this pyproject.toml file 32 | #[arg(long, value_name = "PYPROJECT_TOML")] 33 | pyproject: Option, 34 | } 35 | 36 | pub fn execute(cmd: Args) -> Result<(), Error> { 37 | let req: PythonVersionRequest = cmd 38 | .version 39 | .parse() 40 | .with_context(|| format!("'{}' is not a valid version", cmd.version))?; 41 | let to_write = get_pinnable_version(&req, cmd.relaxed) 42 | .ok_or_else(|| anyhow!("unsupported/unknown version for this platform"))?; 43 | 44 | let pyproject = match PyProject::load_or_discover(cmd.pyproject.as_deref()) { 45 | Ok(proj) => Some(proj), 46 | Err(err) => { 47 | if err.is::() { 48 | // ok 49 | None 50 | } else { 51 | return Err(err); 52 | } 53 | } 54 | }; 55 | 56 | let version_file = match pyproject { 57 | Some(ref proj) => proj.root_path().join(".python-version"), 58 | None => env::current_dir()?.join(".python-version"), 59 | }; 60 | fs::write(&version_file, format!("{}\n", to_write)) 61 | .path_context(&version_file, "failed to write .python-version file")?; 62 | 63 | if !cmd.no_update_requires_python { 64 | if let Some(mut pyproject_toml) = pyproject { 65 | let new_version = to_write.parse::()?; 66 | if let Some(curr_version) = pyproject_toml.target_python_version() { 67 | if new_version < curr_version { 68 | pyproject_toml.set_target_python_version(&new_version); 69 | pyproject_toml.save()?; 70 | } 71 | } 72 | } 73 | } 74 | 75 | echo!("pinned {} in {}", to_write, version_file.display()); 76 | 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /rye/src/cli/remove.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Error; 4 | use clap::Parser; 5 | use pep508_rs::Requirement; 6 | 7 | use crate::config::Config; 8 | use crate::lock::KeyringProvider; 9 | use crate::pyproject::{DependencyKind, PyProject}; 10 | use crate::sync::autosync; 11 | use crate::utils::{format_requirement, CommandOutput}; 12 | 13 | /// Removes a package from this project. 14 | #[derive(Parser, Debug)] 15 | pub struct Args { 16 | /// The packages to remove. 17 | #[arg(required = true)] 18 | requirements: Vec, 19 | /// Remove this from dev dependencies. 20 | #[arg(short, long)] 21 | dev: bool, 22 | /// Remove this from an optional dependency group. 23 | #[arg(long, conflicts_with = "dev")] 24 | optional: Option, 25 | /// Runs `sync` even if auto-sync is disabled. 26 | #[arg(long)] 27 | sync: bool, 28 | /// Does not run `sync` even if auto-sync is enabled. 29 | #[arg(long, conflicts_with = "sync")] 30 | no_sync: bool, 31 | /// Enables verbose diagnostics. 32 | #[arg(short, long)] 33 | verbose: bool, 34 | /// Turns off all output. 35 | #[arg(short, long, conflicts_with = "verbose")] 36 | quiet: bool, 37 | 38 | /// Include pre-releases when automatically syncing the workspace. 39 | #[arg(long)] 40 | pre: bool, 41 | /// Set to `true` to lock with sources in the lockfile when automatically syncing the workspace. 42 | #[arg(long)] 43 | with_sources: bool, 44 | /// Set to `true` to lock with hashes in the lockfile when automatically syncing the workspace. 45 | #[arg(long)] 46 | generate_hashes: bool, 47 | /// Attempt to use `keyring` for authentication for index URLs. 48 | #[arg(long, value_enum, default_value_t)] 49 | keyring_provider: KeyringProvider, 50 | } 51 | 52 | pub fn execute(cmd: Args) -> Result<(), Error> { 53 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 54 | let mut removed_packages = Vec::new(); 55 | 56 | let mut pyproject_toml = PyProject::discover()?; 57 | for str_requirement in cmd.requirements { 58 | let requirement = Requirement::from_str(&str_requirement)?; 59 | if let Some(removed) = pyproject_toml.remove_dependency( 60 | &requirement, 61 | if cmd.dev { 62 | DependencyKind::Dev 63 | } else if let Some(ref section) = cmd.optional { 64 | DependencyKind::Optional(section.into()) 65 | } else { 66 | DependencyKind::Normal 67 | }, 68 | )? { 69 | removed_packages.push(removed); 70 | } 71 | } 72 | 73 | pyproject_toml.save()?; 74 | 75 | if output != CommandOutput::Quiet { 76 | for requirement in removed_packages { 77 | echo!("Removed {}", format_requirement(&requirement)); 78 | } 79 | } 80 | 81 | if (Config::current().autosync() && !cmd.no_sync) || cmd.sync { 82 | autosync( 83 | &pyproject_toml, 84 | output, 85 | cmd.pre, 86 | cmd.with_sources, 87 | cmd.generate_hashes, 88 | cmd.keyring_provider, 89 | )?; 90 | } 91 | 92 | Ok(()) 93 | } 94 | -------------------------------------------------------------------------------- /rye/src/cli/show.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::path::PathBuf; 3 | 4 | use anyhow::Error; 5 | use clap::Parser; 6 | use console::style; 7 | 8 | use crate::pyproject::{get_current_venv_python_version, PyProject}; 9 | 10 | /// Prints the current state of the project. 11 | #[derive(Parser, Debug)] 12 | pub struct Args { 13 | /// Print the installed dependencies from the venv 14 | #[arg(long)] 15 | installed_deps: bool, 16 | /// Use this pyproject.toml file 17 | #[arg(long, value_name = "PYPROJECT_TOML")] 18 | pyproject: Option, 19 | } 20 | 21 | pub fn execute(cmd: Args) -> Result<(), Error> { 22 | if cmd.installed_deps { 23 | warn!("--installed-deps is deprecated, use `rye list`"); 24 | return crate::cli::list::execute(crate::cli::list::Args { 25 | pyproject: cmd.pyproject, 26 | }); 27 | } 28 | 29 | let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?; 30 | echo!( 31 | "project: {}", 32 | style(project.name().unwrap_or("")).yellow() 33 | ); 34 | echo!("path: {}", style(project.root_path().display()).cyan()); 35 | echo!("venv: {}", style(project.venv_path().display()).cyan()); 36 | if let Some(ver) = project.target_python_version() { 37 | echo!("target python: {}", style(ver).cyan()); 38 | } 39 | if let Ok(ver) = project.venv_python_version() { 40 | echo!("venv python: {}", style(&ver).cyan()); 41 | if let Some(actual) = get_current_venv_python_version(&project.venv_path()) { 42 | if actual != ver { 43 | echo!("last synced venv python: {}", style(&actual).red()); 44 | } 45 | } 46 | } 47 | echo!("virtual: {}", style(project.is_virtual()).cyan()); 48 | 49 | if let Some(workspace) = project.workspace() { 50 | echo!( 51 | "workspace: {}", 52 | style(project.workspace_path().display()).cyan() 53 | ); 54 | echo!(" members:"); 55 | let mut projects = workspace.iter_projects().collect::, _>>()?; 56 | projects.sort_by(|a, b| a.root_path().cmp(&b.root_path())); 57 | for child in projects { 58 | let root_path = child.root_path(); 59 | let rel_path = Path::new(".").join( 60 | root_path 61 | .strip_prefix(project.workspace_path()) 62 | .unwrap_or(&root_path), 63 | ); 64 | echo!( 65 | " {} ({})", 66 | style(child.name().unwrap_or("")).cyan(), 67 | style(rel_path.display()).dim(), 68 | ); 69 | } 70 | } 71 | 72 | match project.sources() { 73 | Ok(mut sources) => { 74 | sources.sort_by_cached_key(|x| (x.name != "default", x.name.to_string())); 75 | echo!("configured sources:"); 76 | for source in sources { 77 | echo!( 78 | " {} ({}: {})", 79 | style(&source.name).cyan(), 80 | style(&source.ty).yellow(), 81 | style(&source.url).dim(), 82 | ); 83 | } 84 | } 85 | Err(err) => echo!("invalid source config: {}", style(err).red()), 86 | } 87 | 88 | Ok(()) 89 | } 90 | -------------------------------------------------------------------------------- /rye/src/cli/sync.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Error; 4 | use clap::Parser; 5 | 6 | use crate::lock::{KeyringProvider, LockOptions}; 7 | use crate::sync::{sync, SyncMode, SyncOptions}; 8 | use crate::utils::CommandOutput; 9 | 10 | /// Updates the virtualenv based on the pyproject.toml 11 | #[derive(Parser, Debug)] 12 | pub struct Args { 13 | /// Force the environment to be re-created 14 | #[arg(short, long)] 15 | force: bool, 16 | /// Do not include dev dependencies. 17 | #[arg(long)] 18 | no_dev: bool, 19 | /// Do not update the lockfile. 20 | #[arg(long)] 21 | no_lock: bool, 22 | /// Enables verbose diagnostics. 23 | #[arg(short, long)] 24 | verbose: bool, 25 | /// Turns off all output. 26 | #[arg(short, long, conflicts_with = "verbose")] 27 | quiet: bool, 28 | /// Update a specific package. 29 | #[arg(long)] 30 | update: Vec, 31 | /// Update all packages to the latest 32 | #[arg(long)] 33 | update_all: bool, 34 | /// Update to pre-release versions 35 | #[arg(long)] 36 | pre: bool, 37 | /// Extras/features to enable when syncing the workspace. 38 | #[arg(long)] 39 | features: Vec, 40 | /// Enables all features. 41 | #[arg(long)] 42 | all_features: bool, 43 | /// Set to true to lock with sources in the lockfile. 44 | #[arg(long)] 45 | with_sources: bool, 46 | /// Attempt to use `keyring` for authentication for index URLs. 47 | #[arg(long, value_enum, default_value_t)] 48 | keyring_provider: KeyringProvider, 49 | /// Set to true to lock with hashes in the lockfile. 50 | #[arg(long)] 51 | generate_hashes: bool, 52 | /// Use this pyproject.toml file 53 | #[arg(long, value_name = "PYPROJECT_TOML")] 54 | pyproject: Option, 55 | /// Do not reuse (reset) prior lock options. 56 | #[arg(long)] 57 | reset: bool, 58 | /// Use universal lock files 59 | #[arg(long)] 60 | universal: bool, 61 | } 62 | 63 | pub fn execute(cmd: Args) -> Result<(), Error> { 64 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 65 | sync(SyncOptions { 66 | output, 67 | dev: !cmd.no_dev, 68 | mode: if cmd.force { 69 | SyncMode::Full 70 | } else { 71 | SyncMode::Regular 72 | }, 73 | force: cmd.force, 74 | no_lock: cmd.no_lock, 75 | lock_options: LockOptions { 76 | update: cmd.update, 77 | update_all: cmd.update_all, 78 | pre: cmd.pre, 79 | features: cmd.features, 80 | all_features: cmd.all_features, 81 | with_sources: cmd.with_sources, 82 | reset: cmd.reset, 83 | generate_hashes: cmd.generate_hashes, 84 | universal: cmd.universal, 85 | }, 86 | keyring_provider: cmd.keyring_provider, 87 | pyproject: cmd.pyproject, 88 | })?; 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /rye/src/cli/tools.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use clap::Parser; 3 | use console::style; 4 | 5 | use crate::installer::list_installed_tools; 6 | 7 | /// Helper utility to manage global tools. 8 | #[derive(Parser, Debug)] 9 | pub struct Args { 10 | #[command(subcommand)] 11 | command: SubCommand, 12 | } 13 | 14 | /// List all registered tools 15 | #[derive(Parser, Debug)] 16 | pub struct ListCommand { 17 | /// Show all the scripts installed by the tools. 18 | #[arg(short = 's', long)] 19 | include_scripts: bool, 20 | /// Show the version of tools. 21 | #[arg(short = 'v', long)] 22 | include_version: bool, 23 | } 24 | 25 | #[derive(Parser, Debug)] 26 | #[allow(clippy::large_enum_variant)] 27 | enum SubCommand { 28 | Install(crate::cli::install::Args), 29 | Uninstall(crate::cli::uninstall::Args), 30 | List(ListCommand), 31 | } 32 | 33 | pub fn execute(cmd: Args) -> Result<(), Error> { 34 | match cmd.command { 35 | SubCommand::Install(args) => crate::cli::install::execute(args), 36 | SubCommand::Uninstall(args) => crate::cli::uninstall::execute(args), 37 | SubCommand::List(args) => list_tools(args), 38 | } 39 | } 40 | 41 | fn list_tools(cmd: ListCommand) -> Result<(), Error> { 42 | let mut tools = list_installed_tools()?.into_iter().collect::>(); 43 | tools.sort_by_key(|(tool, _)| tool.clone()); 44 | 45 | for (tool, mut info) in tools { 46 | if !info.valid { 47 | echo!("{} ({})", style(tool).red(), style("seems broken").red()); 48 | continue; 49 | } 50 | if cmd.include_version { 51 | if let Some(ref venv) = info.venv_marker { 52 | echo!("{} {} ({})", style(tool).cyan(), info.version, venv.python); 53 | } else { 54 | echo!("{} {}", style(tool).cyan(), info.version); 55 | } 56 | } else { 57 | echo!("{}", style(tool).cyan()); 58 | } 59 | if cmd.include_scripts { 60 | info.scripts.sort(); 61 | for script in info.scripts { 62 | echo!(" {}", script); 63 | } 64 | } 65 | } 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /rye/src/cli/uninstall.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use clap::Parser; 3 | 4 | use crate::installer::uninstall; 5 | use crate::utils::CommandOutput; 6 | 7 | /// Uninstalls a global tool. 8 | #[derive(Parser, Debug)] 9 | pub struct Args { 10 | /// The package to uninstall. 11 | name: String, 12 | /// Enables verbose diagnostics. 13 | #[arg(short, long)] 14 | verbose: bool, 15 | /// Turns off all output. 16 | #[arg(short, long, conflicts_with = "verbose")] 17 | quiet: bool, 18 | } 19 | 20 | pub fn execute(cmd: Args) -> Result<(), Error> { 21 | let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); 22 | uninstall(&cmd.name, output)?; 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /rye/src/cli/version.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::pyproject::PyProject; 4 | use anyhow::{anyhow, bail, Error}; 5 | use clap::{Parser, ValueEnum}; 6 | use pep440_rs::Version; 7 | 8 | /// Get or set project version 9 | #[derive(Parser, Debug)] 10 | pub struct Args { 11 | /// The version to set 12 | version: Option, 13 | /// The version bump to apply 14 | #[arg(short, long)] 15 | bump: Option, 16 | } 17 | 18 | #[derive(Debug, Clone, ValueEnum)] 19 | pub enum Bump { 20 | Major, 21 | Minor, 22 | Patch, 23 | } 24 | 25 | pub fn execute(cmd: Args) -> Result<(), Error> { 26 | let mut pyproject_toml = PyProject::discover()?; 27 | match cmd.version { 28 | Some(version) => { 29 | let version = 30 | Version::from_str(&version).map_err(|msg| anyhow!("invalid version: {}", msg))?; 31 | if pyproject_toml 32 | .dynamic() 33 | .unwrap() 34 | .contains(&"version".to_string()) 35 | { 36 | bail!("unsupported set dynamic version"); 37 | } else { 38 | pyproject_toml.set_version(&version); 39 | pyproject_toml.save()?; 40 | 41 | echo!("version set to {}", version); 42 | } 43 | } 44 | None => { 45 | let mut version = pyproject_toml.version()?; 46 | match cmd.bump { 47 | Some(bump) => bump_version(&mut version, bump, &mut pyproject_toml)?, 48 | None => echo!("{}", version), 49 | } 50 | } 51 | } 52 | Ok(()) 53 | } 54 | 55 | fn bump_version(version: &mut Version, bump: Bump, pyproject: &mut PyProject) -> Result<(), Error> { 56 | if version.is_post() { 57 | version.post = None; 58 | } 59 | if version.is_dev() { 60 | version.dev = None; 61 | warn!("dev version will be bumped to release version"); 62 | } else { 63 | let index = bump as usize; 64 | if version.release.get(index).is_none() { 65 | version.release.resize(index + 1, 0); 66 | } 67 | version.release[index] += 1; 68 | for i in index + 1..version.release.len() { 69 | version.release[i] = 0; 70 | } 71 | } 72 | 73 | pyproject.set_version(version); 74 | pyproject.save().unwrap(); 75 | 76 | echo!("version bumped to {}", version); 77 | 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /rye/src/consts.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | pub const VENV_BIN: &str = "bin"; 3 | 4 | #[cfg(windows)] 5 | pub const VENV_BIN: &str = "Scripts"; 6 | -------------------------------------------------------------------------------- /rye/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | 3 | use crate::utils::panic::trap_bad_pipe; 4 | use crate::utils::QuietExit; 5 | 6 | #[macro_use] 7 | mod tui; 8 | 9 | mod bootstrap; 10 | mod cli; 11 | mod config; 12 | mod consts; 13 | mod installer; 14 | mod lock; 15 | mod platform; 16 | mod pyproject; 17 | mod sources; 18 | mod sync; 19 | mod utils; 20 | mod uv; 21 | 22 | static SHOW_CONTINUE_PROMPT: AtomicBool = AtomicBool::new(false); 23 | static DISABLE_CTRLC_HANDLER: AtomicBool = AtomicBool::new(false); 24 | 25 | /// Changes the shutdown behavior to request a continue prompt. 26 | pub fn request_continue_prompt() { 27 | SHOW_CONTINUE_PROMPT.store(true, Ordering::Relaxed); 28 | } 29 | 30 | /// Disables the ctrl-c handler 31 | pub fn disable_ctrlc_handler() { 32 | DISABLE_CTRLC_HANDLER.store(true, Ordering::Relaxed); 33 | } 34 | 35 | pub fn main() { 36 | crate::utils::panic::set_panic_hook(); 37 | 38 | ctrlc::set_handler(move || { 39 | if !DISABLE_CTRLC_HANDLER.load(Ordering::Relaxed) { 40 | let term = console::Term::stderr(); 41 | term.show_cursor().ok(); 42 | term.flush().ok(); 43 | std::process::exit(if cfg!(windows) { 44 | 0xC000013Au32 as i32 45 | } else { 46 | 130 47 | }); 48 | } 49 | }) 50 | .unwrap(); 51 | 52 | trap_bad_pipe(|| { 53 | let result = cli::execute(); 54 | let status = match result { 55 | Ok(()) => 0, 56 | Err(err) => { 57 | if let Some(err) = err.downcast_ref::() { 58 | err.print().ok(); 59 | err.exit_code() 60 | } else if let Some(QuietExit(code)) = err.downcast_ref() { 61 | *code 62 | } else { 63 | error!("{:?}", err); 64 | 1 65 | } 66 | } 67 | }; 68 | 69 | if SHOW_CONTINUE_PROMPT.load(Ordering::Relaxed) { 70 | echo!("Press any key to continue"); 71 | console::Term::buffered_stderr().read_key().ok(); 72 | } 73 | status 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /rye/src/sources/generated/uv_downloads.inc: -------------------------------------------------------------------------------- 1 | // Generated by rye-devtools. DO NOT EDIT. 2 | // To regenerate, run `rye run uv-downloads > rye/src/sources/generated/uv_downloads.inc` from the root of the repository. 3 | use std::borrow::Cow; 4 | pub const UV_DOWNLOADS: &[UvDownload] = &[ 5 | UvDownload {arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-aarch64-apple-darwin.tar.gz"), sha256: Cow::Borrowed("51b84818bbfe08358a298ba3389c6d448d3ddc0f2601a2d63c5a62cb7b704062") }, 6 | UvDownload {arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("windows"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-aarch64-pc-windows-msvc.zip"), sha256: Cow::Borrowed("ec3561ca86328aa351919de2d5208f6761a58d42a2e0e50e1d1d80d10039756a") }, 7 | UvDownload {arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-aarch64-unknown-linux-musl.tar.gz"), sha256: Cow::Borrowed("2bb44c9fb8a13e244d502a577d6c32669b680941c996c35524817971e6e56460") }, 8 | UvDownload {arch: Cow::Borrowed("i686"), os: Cow::Borrowed("windows"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-i686-pc-windows-msvc.zip"), sha256: Cow::Borrowed("83173da302701020c44cefdab5d127e5cde9e4333ca7e7cbefc03e39908b7a39") }, 9 | UvDownload {arch: Cow::Borrowed("i686"), os: Cow::Borrowed("linux"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-i686-unknown-linux-gnu.tar.gz"), sha256: Cow::Borrowed("7c044bd2db0690cce49b0613abf01daaeb6fb829737ef9ec7978191f218e1542") }, 10 | UvDownload {arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-x86_64-apple-darwin.tar.gz"), sha256: Cow::Borrowed("a675d2d0fcf533f89f4b584bfa8ee3173a1ffbc87d9d1d48fcc3abb8c55d946d") }, 11 | UvDownload {arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-x86_64-pc-windows-msvc.zip"), sha256: Cow::Borrowed("40b50b3da3cf74dc5717802acd076b4669b6d7d2c91c4482875b4e5e46c62ba3") }, 12 | UvDownload {arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 0, minor: 6, patch: 3, suffix: None, url: Cow::Borrowed("https://github.com/astral-sh/uv/releases/download/0.6.3/uv-x86_64-unknown-linux-gnu.tar.gz"), sha256: Cow::Borrowed("b7a37a33d62cb7672716c695226450231e8c02a8eb2b468fa61cd28a8f86eab2") }, 13 | ]; 14 | -------------------------------------------------------------------------------- /rye/src/sources/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod py; 2 | pub(crate) mod uv; 3 | -------------------------------------------------------------------------------- /rye/src/sources/uv.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use std::borrow::Cow; 3 | use std::env::consts::{ARCH, OS}; 4 | 5 | mod downloads { 6 | use super::UvDownload; 7 | include!("generated/uv_downloads.inc"); 8 | } 9 | 10 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)] 11 | pub struct UvDownload { 12 | pub arch: Cow<'static, str>, 13 | pub os: Cow<'static, str>, 14 | pub major: u8, 15 | pub minor: u8, 16 | pub patch: u8, 17 | pub suffix: Option>, 18 | pub url: Cow<'static, str>, 19 | pub sha256: Cow<'static, str>, 20 | } 21 | 22 | impl std::fmt::Display for UvDownload { 23 | // The format of the version string is: "uv--@..." 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | write!(f, "uv")?; 26 | if self.arch != ARCH || self.os != OS { 27 | write!(f, "-{}", self.arch)?; 28 | if self.os != OS { 29 | write!(f, "-{}", self.os)?; 30 | } 31 | } 32 | write!(f, "@{}.{}.{}", self.major, self.minor, self.patch)?; 33 | 34 | if let Some(ref suffix) = self.suffix { 35 | write!(f, ".{}", suffix)?; 36 | } 37 | Ok(()) 38 | } 39 | } 40 | 41 | impl UvDownload { 42 | // See [`UvDownload::fmt`] for the format of the version string. 43 | pub fn version(&self) -> String { 44 | format!("{}.{}.{}", self.major, self.minor, self.patch) 45 | } 46 | } 47 | 48 | // This is the request for the version of uv to download. 49 | // At the moment, we only support requesting the current architecture and OS. 50 | // We only have one version included in the binary, so we do not need to request 51 | // versions just yet. However, this implementation is designed to be extensible. 52 | pub struct UvRequest { 53 | pub arch: Option>, 54 | pub os: Option>, 55 | } 56 | 57 | impl Default for UvRequest { 58 | fn default() -> Self { 59 | Self { 60 | arch: Some(ARCH.into()), 61 | os: Some(OS.into()), 62 | } 63 | } 64 | } 65 | 66 | impl TryFrom for UvDownload { 67 | type Error = Error; 68 | 69 | // Searches our list of downloads for the current architecture and OS. 70 | // Note: We do not need to search for versions just yet, since we only have one of 71 | // uv at a time. 72 | fn try_from(v: UvRequest) -> Result { 73 | downloads::UV_DOWNLOADS 74 | .iter() 75 | .rev() 76 | .find(|d| { 77 | (v.arch.is_none() || v.arch.as_ref().unwrap() == &d.arch) 78 | && (v.os.is_none() || v.os.as_ref().unwrap() == &d.os) 79 | }) 80 | .cloned() 81 | .ok_or_else(|| anyhow!("No matching download found")) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /rye/src/templates/LICENSE.txt.j2: -------------------------------------------------------------------------------- 1 | {{ license_text }} 2 | -------------------------------------------------------------------------------- /rye/src/templates/README.md.j2: -------------------------------------------------------------------------------- 1 | # {{ name }} 2 | 3 | Describe your project here. 4 | 5 | {%- if license %} 6 | * License: {{ license }} 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /rye/src/templates/gitignore.j2: -------------------------------------------------------------------------------- 1 | # python generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | {%- if is_rust %} 10 | # Rust 11 | target/ 12 | {%- endif %} 13 | 14 | # venv 15 | .venv 16 | -------------------------------------------------------------------------------- /rye/src/templates/lib/default/__init__.py.j2: -------------------------------------------------------------------------------- 1 | def hello() -> str: 2 | return "Hello from {{ name }}!" 3 | -------------------------------------------------------------------------------- /rye/src/templates/lib/maturin/Cargo.toml.j2: -------------------------------------------------------------------------------- 1 | [package] 2 | name = {{ name }} 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [lib] 8 | name = {{ name_safe }} 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | pyo3 = "0.19.0" 13 | -------------------------------------------------------------------------------- /rye/src/templates/lib/maturin/__init__.py.j2: -------------------------------------------------------------------------------- 1 | from {{ name_safe }}._lowlevel import hello 2 | 3 | __all__ = ["hello"] 4 | -------------------------------------------------------------------------------- /rye/src/templates/lib/maturin/lib.rs.j2: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | 3 | /// Prints a message. 4 | #[pyfunction] 5 | fn hello() -> PyResult { 6 | Ok("Hello from {{ name }}!".into()) 7 | } 8 | 9 | /// A Python module implemented in Rust. 10 | #[pymodule] 11 | fn _lowlevel(_py: Python, m: &PyModule) -> PyResult<()> { 12 | m.add_function(wrap_pyfunction!(hello, m)?)?; 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /rye/src/templates/pyproject.toml.j2: -------------------------------------------------------------------------------- 1 | [project] 2 | name = {{ name }} 3 | version = {{ version }} 4 | description = {{ description }} 5 | {%- if author %} 6 | authors = [ 7 | { name = {{ author[0] }}, email = {{ author[1] }} } 8 | ] 9 | {%- endif %} 10 | {%- if dependencies %} 11 | dependencies = [ 12 | {%- for dependency in dependencies %} 13 | {{ dependency }}, 14 | {%- endfor %} 15 | ] 16 | {%- else %} 17 | dependencies = [] 18 | {%- endif %} 19 | {%- if with_readme %} 20 | readme = "README.md" 21 | {%- endif %} 22 | requires-python = {{ requires_python }} 23 | {%- if license %} 24 | license = { text = {{ license }} } 25 | {%- endif %} 26 | {%- if private %} 27 | classifiers = ["Private :: Do Not Upload"] 28 | {%- endif %} 29 | {%- if is_script %} 30 | 31 | [project.scripts] 32 | {{ name }} = {{ name_safe ~ ":main"}} 33 | {%- endif %} 34 | 35 | {%- if not is_virtual %} 36 | 37 | [build-system] 38 | {%- if build_system == "hatchling" %} 39 | requires = ["hatchling"] 40 | build-backend = "hatchling.build" 41 | {%- elif build_system == "setuptools" %} 42 | requires = ["setuptools>=61.0"] 43 | build-backend = "setuptools.build_meta" 44 | {%- elif build_system == "flit" %} 45 | requires = ["flit_core>=3.4"] 46 | build-backend = "flit_core.buildapi" 47 | {%- elif build_system == "pdm" %} 48 | requires = ["pdm-backend"] 49 | build-backend = "pdm.backend" 50 | {%- elif build_system == "maturin" %} 51 | requires = ["maturin>=1.2,<2.0"] 52 | build-backend = "maturin" 53 | {%- endif %} 54 | {%- endif %} 55 | 56 | [tool.rye] 57 | managed = true 58 | {%- if is_virtual %} 59 | virtual = true 60 | {%- endif %} 61 | {%- if dev_dependencies %} 62 | dev-dependencies = [ 63 | {%- for dependency in dev_dependencies %} 64 | {{ dependency }}, 65 | {%- endfor %} 66 | ] 67 | {%- else %} 68 | dev-dependencies = [] 69 | {%- endif %} 70 | 71 | {%- if not is_virtual %} 72 | {%- if build_system == "hatchling" %} 73 | 74 | [tool.hatch.metadata] 75 | allow-direct-references = true 76 | 77 | [tool.hatch.build.targets.wheel] 78 | packages = [{{ "src/" ~ name_safe }}] 79 | {%- elif build_system == "maturin" %} 80 | 81 | [tool.maturin] 82 | python-source = "python" 83 | module-name = {{ name_safe ~ "._lowlevel" }} 84 | features = ["pyo3/extension-module"] 85 | {%- endif %} 86 | {%- endif %} 87 | -------------------------------------------------------------------------------- /rye/src/templates/script/default/__init__.py.j2: -------------------------------------------------------------------------------- 1 | def main() -> int: 2 | print("Hello from {{ name }}!") 3 | return 0 4 | -------------------------------------------------------------------------------- /rye/src/templates/script/default/__main__.py.j2: -------------------------------------------------------------------------------- 1 | import {{ name_safe }} 2 | import sys 3 | 4 | sys.exit({{ name_safe }}.main()) 5 | -------------------------------------------------------------------------------- /rye/src/templates/setuptools.py.j2: -------------------------------------------------------------------------------- 1 | import json, sys 2 | from pathlib import Path 3 | from tempfile import TemporaryDirectory 4 | 5 | def setup(**kwargs) -> None: 6 | print(json.dumps(kwargs), file=sys.stderr) 7 | 8 | if __name__ == "setuptools": 9 | _setup_proxy_module = sys.modules.pop("setuptools") 10 | _setup_proxy_cwd = sys.path.pop(0) 11 | import setuptools as __setuptools 12 | sys.path.insert(0, _setup_proxy_cwd) 13 | sys.modules["setuptools"] = _setup_proxy_module 14 | def __getattr__(name): 15 | return getattr(__setuptools, name) 16 | -------------------------------------------------------------------------------- /rye/src/tui.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::sync::atomic::{AtomicBool, Ordering}; 3 | 4 | static ECHO_TO_STDERR: AtomicBool = AtomicBool::new(false); 5 | 6 | #[doc(hidden)] 7 | pub fn _print(args: fmt::Arguments) { 8 | // use eprintln and println so that tests can still intercept this 9 | if ECHO_TO_STDERR.load(Ordering::Relaxed) { 10 | eprintln!("{}", args); 11 | } else { 12 | println!("{}", args); 13 | } 14 | } 15 | 16 | /// Until the guard is dropped, echo goes to stderr. 17 | pub fn redirect_to_stderr(yes: bool) -> RedirectGuard { 18 | let old = ECHO_TO_STDERR.load(Ordering::Relaxed); 19 | ECHO_TO_STDERR.store(yes, Ordering::Relaxed); 20 | RedirectGuard(old) 21 | } 22 | 23 | #[must_use] 24 | pub struct RedirectGuard(bool); 25 | 26 | impl Drop for RedirectGuard { 27 | fn drop(&mut self) { 28 | ECHO_TO_STDERR.store(self.0, Ordering::Relaxed); 29 | } 30 | } 31 | 32 | /// Echo a line to the output stream (usually stdout). 33 | macro_rules! echo { 34 | () => { 35 | $crate::tui::_print(format_args!("")) 36 | }; 37 | (if verbose $out:expr, $($arg:tt)+) => { 38 | match $out { 39 | $crate::utils::CommandOutput::Verbose => { 40 | $crate::tui::_print(format_args!($($arg)*)) 41 | } 42 | _ => {} 43 | } 44 | }; 45 | (if $out:expr, $($arg:tt)+) => { 46 | match $out { 47 | $crate::utils::CommandOutput::Normal | $crate::utils::CommandOutput::Verbose => { 48 | $crate::tui::_print(format_args!($($arg)*)) 49 | } 50 | _ => {} 51 | } 52 | }; 53 | ($($arg:tt)+) => { 54 | // TODO: this is bloaty, but this way capturing of outputs 55 | // for stdout works in tests still. 56 | $crate::tui::_print(format_args!($($arg)*)) 57 | }; 58 | } 59 | 60 | /// Like echo but always goes to stderr. 61 | macro_rules! elog { 62 | ($($arg:tt)*) => { eprintln!($($arg)*) } 63 | } 64 | 65 | /// Emits a warning 66 | macro_rules! warn { 67 | ($($arg:tt)+) => { 68 | elog!( 69 | "{} {}", 70 | console::style("warning:").yellow().bold(), 71 | format_args!($($arg)*) 72 | ) 73 | } 74 | } 75 | 76 | /// Logs errors 77 | macro_rules! error { 78 | ($($arg:tt)+) => { 79 | elog!( 80 | "{} {}", 81 | console::style("error:").red().bold(), 82 | format_args!($($arg)*) 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rye/src/utils/panic.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::{panic, process}; 3 | 4 | fn is_bad_pipe(payload: &dyn Any) -> bool { 5 | payload 6 | .downcast_ref::() 7 | .is_some_and(|x| x.contains("failed printing to stdout: ")) 8 | } 9 | 10 | /// Registers a panic hook that hides stdout printing failures. 11 | pub fn set_panic_hook() { 12 | let default_hook = panic::take_hook(); 13 | panic::set_hook(Box::new(move |info| { 14 | if !is_bad_pipe(info.payload()) { 15 | default_hook(info) 16 | } 17 | })); 18 | } 19 | 20 | /// Catches down panics that are caused by bad pipe errors. 21 | pub fn trap_bad_pipe i32 + Send + Sync>(f: F) -> ! { 22 | process::exit(match panic::catch_unwind(panic::AssertUnwindSafe(f)) { 23 | Ok(status) => status, 24 | Err(panic) => { 25 | if is_bad_pipe(&panic) { 26 | 1 27 | } else { 28 | panic::resume_unwind(panic); 29 | } 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /rye/src/utils/ruff.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::path::PathBuf; 4 | use std::process::Command; 5 | 6 | use anyhow::Error; 7 | use clap::Parser; 8 | 9 | use crate::bootstrap::ensure_self_venv; 10 | use crate::consts::VENV_BIN; 11 | use crate::pyproject::{locate_projects, PyProject}; 12 | use crate::utils::{CommandOutput, QuietExit}; 13 | 14 | #[derive(Parser, Debug)] 15 | pub struct RuffArgs { 16 | /// List of files or directories to limit the operation to 17 | paths: Vec, 18 | /// Perform the operation on all packages 19 | #[arg(short, long)] 20 | all: bool, 21 | /// Perform the operation on a specific package 22 | #[arg(short, long)] 23 | package: Vec, 24 | /// Use this pyproject.toml file 25 | #[arg(long, value_name = "PYPROJECT_TOML")] 26 | pyproject: Option, 27 | /// Enables verbose diagnostics. 28 | #[arg(short, long)] 29 | verbose: bool, 30 | /// Turns off all output. 31 | #[arg(short, long, conflicts_with = "verbose")] 32 | quiet: bool, 33 | /// Extra arguments to ruff 34 | #[arg(last = true)] 35 | extra_args: Vec, 36 | } 37 | 38 | pub fn execute_ruff(args: RuffArgs, extra_args: &[&str]) -> Result<(), Error> { 39 | let project = PyProject::load_or_discover(args.pyproject.as_deref())?; 40 | let output = CommandOutput::from_quiet_and_verbose(args.quiet, args.verbose); 41 | let venv = ensure_self_venv(output)?; 42 | let ruff = venv.join(VENV_BIN).join("ruff"); 43 | 44 | let mut ruff_cmd = Command::new(ruff); 45 | if env::var_os("RUFF_CACHE_DIR").is_none() { 46 | ruff_cmd.env( 47 | "RUFF_CACHE_DIR", 48 | project.workspace_path().join(".ruff_cache"), 49 | ); 50 | } 51 | ruff_cmd.args(extra_args); 52 | 53 | match output { 54 | CommandOutput::Normal => {} 55 | CommandOutput::Verbose => { 56 | ruff_cmd.arg("--verbose"); 57 | } 58 | CommandOutput::Quiet => { 59 | ruff_cmd.arg("--quiet"); 60 | } 61 | } 62 | 63 | ruff_cmd.args(args.extra_args); 64 | 65 | ruff_cmd.arg("--"); 66 | if args.paths.is_empty() { 67 | let projects = locate_projects(project, args.all, &args.package[..])?; 68 | for project in projects { 69 | ruff_cmd.arg(project.root_path().as_os_str()); 70 | } 71 | } else { 72 | for file in args.paths { 73 | ruff_cmd.arg(file.as_os_str()); 74 | } 75 | } 76 | 77 | let status = ruff_cmd.status()?; 78 | if !status.success() { 79 | let code = status.code().unwrap_or(1); 80 | Err(QuietExit(code).into()) 81 | } else { 82 | Ok(()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rye/src/utils/toml.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Error}; 2 | use toml_edit::{Array, DocumentMut, Item, RawString, Table, TableLike}; 3 | 4 | /// Given a toml document, ensures that a given named table exists toplevel. 5 | /// 6 | /// The table is created as a non inline table which is the preferred style. 7 | pub fn ensure_table<'a>(doc: &'a mut DocumentMut, name: &str) -> &'a mut Item { 8 | if doc.as_item().get(name).is_none() { 9 | let mut tbl = Table::new(); 10 | tbl.set_implicit(true); 11 | doc.as_item_mut()[name] = Item::Table(tbl); 12 | } 13 | &mut doc.as_item_mut()[name] 14 | } 15 | 16 | /// Reformats a TOML array to multi line while trying to 17 | /// preserve all comments and move them around. This also makes 18 | /// the array to have a trailing comma. 19 | pub fn reformat_array_multiline(deps: &mut Array) { 20 | fn find_comments(s: Option<&RawString>) -> impl Iterator { 21 | s.and_then(|x| x.as_str()) 22 | .unwrap_or("") 23 | .lines() 24 | .filter_map(|line| { 25 | let line = line.trim(); 26 | line.starts_with('#').then_some(line) 27 | }) 28 | } 29 | 30 | for item in deps.iter_mut() { 31 | let decor = item.decor_mut(); 32 | let mut prefix = String::new(); 33 | for comment in find_comments(decor.prefix()).chain(find_comments(decor.suffix())) { 34 | prefix.push_str("\n "); 35 | prefix.push_str(comment); 36 | } 37 | prefix.push_str("\n "); 38 | decor.set_prefix(prefix); 39 | decor.set_suffix(""); 40 | } 41 | 42 | deps.set_trailing(&{ 43 | let mut comments = find_comments(Some(deps.trailing())).peekable(); 44 | let mut rv = String::new(); 45 | if comments.peek().is_some() { 46 | for comment in comments { 47 | rv.push_str("\n "); 48 | rv.push_str(comment); 49 | } 50 | } 51 | if !rv.is_empty() || !deps.is_empty() { 52 | rv.push('\n'); 53 | } 54 | rv 55 | }); 56 | deps.set_trailing_comma(true); 57 | } 58 | 59 | /// Iterate over tables in an array. 60 | /// 61 | /// This helps one iterate over 62 | pub fn iter_tables<'x>( 63 | item: &'x Item, 64 | ) -> Box> + 'x> { 65 | if let Some(aot) = item.as_array_of_tables() { 66 | Box::new(aot.into_iter().map(|x| Ok(x as &dyn TableLike))) 67 | } else if let Some(arr) = item.as_array() { 68 | Box::new(arr.into_iter().map(|x| match x.as_inline_table() { 69 | Some(table) => Ok(table as &dyn TableLike), 70 | None => bail!("expected inline table, got {}", x.type_name()), 71 | })) 72 | } else { 73 | Box::new( 74 | Some(Err(anyhow!( 75 | "expected array of tables, got {}", 76 | item.type_name() 77 | ))) 78 | .into_iter(), 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rye/src/utils/unix.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::{env, fs}; 3 | 4 | use anyhow::{Context, Error}; 5 | 6 | use crate::utils::IoPathContext; 7 | 8 | pub(crate) fn add_to_path(rye_home: &Path) -> Result<(), Error> { 9 | // for regular shells just add the path to `.profile` 10 | add_source_line_to_profile( 11 | &home::home_dir() 12 | .context("could not find home dir")? 13 | .join(".profile"), 14 | &(format!( 15 | ". \"{}\"", 16 | reverse_resolve_env_home(rye_home.join("env")).display() 17 | )), 18 | )?; 19 | Ok(()) 20 | } 21 | 22 | fn add_source_line_to_profile(profile_path: &Path, source_line: &str) -> Result<(), Error> { 23 | let mut profile = if profile_path.is_file() { 24 | fs::read_to_string(profile_path) 25 | .path_context(profile_path, "failed to read profile file")? 26 | } else { 27 | String::new() 28 | }; 29 | 30 | if !profile.lines().any(|x| x.trim() == source_line) { 31 | profile.push_str(source_line); 32 | profile.push('\n'); 33 | fs::write(profile_path, profile) 34 | .path_context(profile_path, "failed to write updated .profile")?; 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | fn reverse_resolve_env_home(path: PathBuf) -> PathBuf { 41 | if let Some(env_home) = env::var_os("HOME").map(PathBuf::from) { 42 | if let Ok(rest) = path.strip_prefix(&env_home) { 43 | return Path::new("$HOME").join(rest); 44 | } 45 | } 46 | path 47 | } 48 | -------------------------------------------------------------------------------- /rye/tests/test-list.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{rye_cmd_snapshot, Space}; 2 | use toml_edit::value; 3 | 4 | mod common; 5 | 6 | #[test] 7 | fn test_basic_list() { 8 | let space = Space::new(); 9 | space.init("my-project"); 10 | 11 | space 12 | .rye_cmd() 13 | .arg("add") 14 | .arg("jinja2") 15 | .status() 16 | .expect("ok"); 17 | 18 | rye_cmd_snapshot!( 19 | space.rye_cmd().arg("list"), @r###" 20 | success: true 21 | exit_code: 0 22 | ----- stdout ----- 23 | jinja2==3.1.2 24 | markupsafe==2.1.3 25 | -e file:[TEMP_PATH]/project 26 | 27 | ----- stderr ----- 28 | "###); 29 | } 30 | 31 | #[test] 32 | fn test_list_not_rye_managed() { 33 | let space = Space::new(); 34 | space.init("my-project"); 35 | 36 | space.edit_toml("pyproject.toml", |doc| { 37 | doc["tool"]["rye"]["managed"] = value(false); 38 | }); 39 | 40 | space 41 | .rye_cmd() 42 | .arg("add") 43 | .arg("jinja2") 44 | .status() 45 | .expect("Add package failed"); 46 | 47 | rye_cmd_snapshot!( 48 | space.rye_cmd().arg("list"), @r###" 49 | success: true 50 | exit_code: 0 51 | ----- stdout ----- 52 | jinja2==3.1.2 53 | markupsafe==2.1.3 54 | -e file:[TEMP_PATH]/project 55 | 56 | ----- stderr ----- 57 | "###); 58 | } 59 | 60 | #[test] 61 | fn test_list_never_overwrite() { 62 | let space = Space::new(); 63 | space.init("my-project"); 64 | 65 | space.rye_cmd().arg("sync").status().expect("Sync failed"); 66 | 67 | let venv_marker = space.read_string(".venv/rye-venv.json"); 68 | assert!( 69 | venv_marker.contains("@3.12"), 70 | "asserting contents of venv marker: {}", 71 | venv_marker 72 | ); 73 | 74 | // Pick different python version 75 | space 76 | .rye_cmd() 77 | .arg("pin") 78 | .arg("3.11") 79 | .status() 80 | .expect("Sync failed"); 81 | 82 | // List keeps the existing virtualenv unchanged 83 | 84 | rye_cmd_snapshot!( 85 | space.rye_cmd().arg("list"), @r###" 86 | success: true 87 | exit_code: 0 88 | ----- stdout ----- 89 | -e file:[TEMP_PATH]/project 90 | 91 | ----- stderr ----- 92 | "###); 93 | 94 | let venv_marker = space.read_string(".venv/rye-venv.json"); 95 | assert!( 96 | venv_marker.contains("@3.12"), 97 | "asserting contents of venv marker: {}", 98 | venv_marker 99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /rye/tests/test_cli.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use toml_edit::value; 4 | 5 | use crate::common::{rye_cmd_snapshot, Space}; 6 | 7 | mod common; 8 | 9 | #[test] 10 | fn test_dotenv() { 11 | let space = Space::new(); 12 | space.init("my-project"); 13 | space.edit_toml("pyproject.toml", |doc| { 14 | doc["tool"]["rye"]["scripts"]["hello"]["cmd"] = 15 | value("python -c \"import os; print(os.environ['MY_COOL_VAR'], os.environ['MY_COOL_OTHER_VAR'])\""); 16 | doc["tool"]["rye"]["scripts"]["hello"]["env-file"] = value(".other.env"); 17 | }); 18 | fs::write(space.project_path().join(".env"), "MY_COOL_VAR=42").unwrap(); 19 | fs::write( 20 | space.project_path().join(".other.env"), 21 | "MY_COOL_OTHER_VAR=23", 22 | ) 23 | .unwrap(); 24 | rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###" 25 | success: true 26 | exit_code: 0 27 | ----- stdout ----- 28 | Initializing new virtualenv in [TEMP_PATH]/project/.venv 29 | Python version: cpython@3.12.8 30 | Generating production lockfile: [TEMP_PATH]/project/requirements.lock 31 | Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock 32 | Installing dependencies 33 | Done! 34 | 35 | ----- stderr ----- 36 | Resolved 1 package in [EXECUTION_TIME] 37 | Prepared 1 package in [EXECUTION_TIME] 38 | Installed 1 package in [EXECUTION_TIME] 39 | + my-project==0.1.0 (from file:[TEMP_PATH]/project) 40 | "###); 41 | rye_cmd_snapshot!(space.rye_cmd() 42 | .arg("--env-file=.env") 43 | .arg("run") 44 | .arg("hello"), @r###" 45 | success: true 46 | exit_code: 0 47 | ----- stdout ----- 48 | 42 23 49 | 50 | ----- stderr ----- 51 | "###); 52 | } 53 | -------------------------------------------------------------------------------- /rye/tests/test_config.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use insta::assert_snapshot; 4 | 5 | use crate::common::{rye_cmd_snapshot, Space}; 6 | 7 | mod common; 8 | 9 | #[test] 10 | fn test_config_empty() { 11 | let space = Space::new(); 12 | rye_cmd_snapshot!(space.rye_cmd().arg("config"), @r###" 13 | success: false 14 | exit_code: 2 15 | ----- stdout ----- 16 | 17 | ----- stderr ----- 18 | Reads or modifies the global `config.toml` file 19 | 20 | Usage: rye config [OPTIONS] 21 | 22 | Options: 23 | --show-path Print the path to the config 24 | --format Request parseable output format rather than lines [possible values: 25 | json] 26 | --get Reads a config key 27 | --set Sets a config key to a string 28 | --set-int Sets a config key to an integer 29 | --set-bool Sets a config key to a bool 30 | --unset Remove a config key 31 | -h, --help Print help (see more with '--help') 32 | "###); 33 | } 34 | 35 | #[test] 36 | fn test_config_show_path() { 37 | let space = Space::new(); 38 | rye_cmd_snapshot!(space.rye_cmd().arg("config").arg("--show-path"), @r###" 39 | success: true 40 | exit_code: 0 41 | ----- stdout ----- 42 | [RYE_HOME]/config.toml 43 | 44 | ----- stderr ----- 45 | "###); 46 | } 47 | 48 | #[test] 49 | fn test_config_incompatible_format_and_show_path() { 50 | let space = Space::new(); 51 | rye_cmd_snapshot!(space.rye_cmd().arg("config").arg("--show-path").arg("--format=json"), @r###" 52 | success: false 53 | exit_code: 2 54 | ----- stdout ----- 55 | 56 | ----- stderr ----- 57 | error: an argument cannot be used with one or more of the other specified arguments 58 | "###); 59 | } 60 | 61 | #[test] 62 | fn test_config_get_set_multiple() { 63 | let space = Space::new(); 64 | rye_cmd_snapshot!(space.rye_cmd() 65 | .arg("config") 66 | .arg("--set") 67 | .arg("default.toolchain=cpython@3.12") 68 | .arg("--set-bool") 69 | .arg("behavior.use-uv=true"), 70 | @r###" 71 | success: true 72 | exit_code: 0 73 | ----- stdout ----- 74 | 75 | ----- stderr ----- 76 | "###); 77 | 78 | rye_cmd_snapshot!(space.rye_cmd() 79 | .arg("config") 80 | .arg("--get") 81 | .arg("default.toolchain") 82 | .arg("--get") 83 | .arg("behavior.use-uv") 84 | .arg("--format=json"), 85 | @r###" 86 | success: true 87 | exit_code: 0 88 | ----- stdout ----- 89 | { 90 | "behavior.use-uv": true, 91 | "default.toolchain": "cpython@3.12" 92 | } 93 | 94 | ----- stderr ----- 95 | "###); 96 | } 97 | 98 | #[test] 99 | // This test ensure that --show-path is not compatible with any other action 100 | fn test_config_show_path_and_any_action() { 101 | let space = Space::new(); 102 | rye_cmd_snapshot!(space.rye_cmd() 103 | .arg("config") 104 | .arg("--set") 105 | .arg("default.toolchain=cpython@3.12") 106 | .arg("--show-path"), 107 | @r###" 108 | success: false 109 | exit_code: 2 110 | ----- stdout ----- 111 | 112 | ----- stderr ----- 113 | error: an argument cannot be used with one or more of the other specified arguments 114 | "###); 115 | } 116 | 117 | #[test] 118 | fn test_config_save_missing_folder() { 119 | let space = Space::new(); 120 | let fake_home = space.project_path().join("missing-thing"); 121 | rye_cmd_snapshot!(space.rye_cmd() 122 | .arg("config") 123 | .arg("--set") 124 | .arg("default.toolchain=cpython@3.12") 125 | .env("RYE_HOME", fake_home.as_os_str()), @r###" 126 | success: true 127 | exit_code: 0 128 | ----- stdout ----- 129 | 130 | ----- stderr ----- 131 | "###); 132 | assert_snapshot!(fs::read_to_string(fake_home.join("config.toml")).unwrap(), @r###" 133 | [default] 134 | toolchain = "cpython@3.12" 135 | "###); 136 | } 137 | -------------------------------------------------------------------------------- /rye/tests/test_publish.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{rye_cmd_snapshot, Space}; 2 | 3 | mod common; 4 | 5 | #[test] 6 | fn test_publish_outside_project() { 7 | let space = Space::new(); 8 | space.init("my-project"); 9 | 10 | let status = space.rye_cmd().arg("build").status().unwrap(); 11 | assert!(status.success()); 12 | 13 | // Publish outside the project. 14 | // Since we provide a fake token, the failure is expected. 15 | rye_cmd_snapshot!(space 16 | .rye_cmd() 17 | .arg("publish") 18 | .arg("--yes") 19 | .arg("--token") 20 | .arg("fake-token") 21 | .arg("--quiet") 22 | .current_dir(space.project_path().parent().unwrap()) 23 | .arg(space.project_path().join("dist").join("*")), @r###" 24 | success: false 25 | exit_code: 1 26 | ----- stdout ----- 27 | 28 | ----- stderr ----- 29 | error: failed to publish files 30 | "###); 31 | } 32 | -------------------------------------------------------------------------------- /rye/tests/test_ruff.rs: -------------------------------------------------------------------------------- 1 | use insta::assert_snapshot; 2 | 3 | use crate::common::{rye_cmd_snapshot, Space}; 4 | 5 | mod common; 6 | 7 | #[test] 8 | fn test_lint_and_format() { 9 | let space = Space::new(); 10 | space.init("my-project"); 11 | space.write( 12 | // `test.py` is used instead of `__init__.py` to make ruff consider it a fixable 13 | // issue instead of requiring user intervention. 14 | // ref: https://github.com/astral-sh/ruff/pull/11168 15 | "src/my_project/test.py", 16 | r#"import os 17 | 18 | def hello(): 19 | 20 | 21 | return "Hello World"; 22 | "#, 23 | ); 24 | 25 | // start with lint 26 | rye_cmd_snapshot!(space.rye_cmd().arg("lint"), @r###" 27 | success: false 28 | exit_code: 1 29 | ----- stdout ----- 30 | src/my_project/test.py:1:8: F401 [*] `os` imported but unused 31 | | 32 | 1 | import os 33 | | ^^ F401 34 | 2 | 35 | 3 | def hello(): 36 | | 37 | = help: Remove unused import: `os` 38 | 39 | src/my_project/test.py:6:25: E703 [*] Statement ends with an unnecessary semicolon 40 | | 41 | 6 | return "Hello World"; 42 | | ^ E703 43 | | 44 | = help: Remove unnecessary semicolon 45 | 46 | Found 2 errors. 47 | [*] 2 fixable with the `--fix` option. 48 | 49 | ----- stderr ----- 50 | "###); 51 | rye_cmd_snapshot!(space.rye_cmd().arg("lint").arg("--fix"), @r###" 52 | success: true 53 | exit_code: 0 54 | ----- stdout ----- 55 | Found 2 errors (2 fixed, 0 remaining). 56 | 57 | ----- stderr ----- 58 | "###); 59 | assert_snapshot!(space.read_string("src/my_project/test.py"), @r###" 60 | 61 | def hello(): 62 | 63 | 64 | return "Hello World" 65 | "###); 66 | 67 | // fmt next 68 | // Already reformatted file mentioned bellow is `__init__.py` 69 | rye_cmd_snapshot!(space.rye_cmd().arg("fmt").arg("--check"), @r###" 70 | success: false 71 | exit_code: 1 72 | ----- stdout ----- 73 | Would reformat: src/my_project/test.py 74 | 1 file would be reformatted, 1 file already formatted 75 | 76 | ----- stderr ----- 77 | "###); 78 | rye_cmd_snapshot!(space.rye_cmd().arg("fmt"), @r###" 79 | success: true 80 | exit_code: 0 81 | ----- stdout ----- 82 | 1 file reformatted, 1 file left unchanged 83 | 84 | ----- stderr ----- 85 | "###); 86 | assert_snapshot!(space.read_string("src/my_project/test.py"), @r###" 87 | def hello(): 88 | return "Hello World" 89 | "###); 90 | } 91 | -------------------------------------------------------------------------------- /rye/tests/test_scripts.rs: -------------------------------------------------------------------------------- 1 | use toml_edit::value; 2 | 3 | use crate::common::{rye_cmd_snapshot, Space}; 4 | 5 | mod common; 6 | 7 | #[test] 8 | fn test_basic_script() { 9 | let mut settings = insta::Settings::clone_current(); 10 | settings.add_filter(r"(?m)(^py[a-z\d._]+$\r?\n)+", "[PYTHON SCRIPTS]\n"); 11 | let _guard = settings.bind_to_scope(); 12 | 13 | let space = Space::new(); 14 | space.init("my-project"); 15 | space.edit_toml("pyproject.toml", |doc| { 16 | doc["tool"]["rye"]["scripts"]["test-script"] = value("python -c 'print(\"Hello World\")'"); 17 | }); 18 | 19 | rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("test-script"), @r###" 20 | success: true 21 | exit_code: 0 22 | ----- stdout ----- 23 | Hello World 24 | 25 | ----- stderr ----- 26 | Initializing new virtualenv in [TEMP_PATH]/project/.venv 27 | Python version: cpython@3.12.8 28 | "###); 29 | 30 | rye_cmd_snapshot!(space.rye_cmd().arg("run"), @r###" 31 | success: true 32 | exit_code: 0 33 | ----- stdout ----- 34 | [PYTHON SCRIPTS] 35 | test-script (python -c 'print("Hello World")') 36 | 37 | ----- stderr ----- 38 | "###); 39 | } 40 | -------------------------------------------------------------------------------- /rye/tests/test_self.rs: -------------------------------------------------------------------------------- 1 | use insta::Settings; 2 | 3 | use crate::common::{rye_cmd_snapshot, Space}; 4 | mod common; 5 | 6 | // This test is self-destructive, making other tests slow, ignore it by default. 7 | #[test] 8 | #[ignore] 9 | fn test_self_uninstall() { 10 | let space = Space::new(); 11 | let _guard = space.lock_rye_home(); 12 | 13 | // install a global tool to ensure tools directory is created 14 | space 15 | .rye_cmd() 16 | .arg("install") 17 | .arg("pycowsay") 18 | .arg("-f") 19 | .status() 20 | .unwrap(); 21 | 22 | assert!(space.rye_home().join("self").is_dir()); 23 | assert!(space.rye_home().join("py").is_dir()); 24 | assert!(space.rye_home().join("tools").is_dir()); 25 | 26 | let status = space 27 | .rye_cmd() 28 | .arg("self") 29 | .arg("uninstall") 30 | .arg("--yes") 31 | .status() 32 | .unwrap(); 33 | assert!(status.success()); 34 | 35 | let may_left = &["env", "config.toml", "lock"]; 36 | let leftovers: Vec<_> = space 37 | .rye_home() 38 | .read_dir() 39 | .unwrap() 40 | .filter(|x| { 41 | let x = x.as_ref().unwrap(); 42 | !may_left.contains(&x.file_name().to_str().unwrap()) 43 | }) 44 | .collect(); 45 | assert!(leftovers.is_empty(), "leftovers: {:?}", leftovers); 46 | } 47 | 48 | #[test] 49 | fn test_version() { 50 | let space = Space::new(); 51 | let _guard = space.lock_rye_home(); 52 | 53 | let mut settings = Settings::clone_current(); 54 | settings.add_filter(r"(?m)^(rye )\d+\.\d+\.\d+$", "$1[VERSION]"); 55 | settings.add_filter(r"(?m)^(commit: ).*", "$1[COMMIT]"); 56 | settings.add_filter(r"(?m)^(platform: ).*", "$1[PLATFORM]"); 57 | let _guard = settings.bind_to_scope(); 58 | 59 | rye_cmd_snapshot!(space.rye_cmd().arg("--version"), @r###" 60 | success: true 61 | exit_code: 0 62 | ----- stdout ----- 63 | rye [VERSION] 64 | commit: [COMMIT] 65 | platform: [PLATFORM] 66 | self-python: cpython@3.12.8 67 | symlink support: true 68 | uv enabled: true 69 | 70 | ----- stderr ----- 71 | "###); 72 | } 73 | -------------------------------------------------------------------------------- /rye/tests/test_toolchain.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{rye_cmd_snapshot, Space}; 2 | 3 | mod common; 4 | 5 | #[test] 6 | fn test_fetch() { 7 | let space = Space::new(); 8 | // Use a version not in use by other tests and will be supported for a long time. 9 | let version = "cpython@3.12.1"; 10 | 11 | // Make sure the version is installed. 12 | let status = space.rye_cmd().arg("fetch").arg(version).status().unwrap(); 13 | assert!(status.success()); 14 | 15 | // Fetching the same version again should be a no-op. 16 | rye_cmd_snapshot!(space.rye_cmd().arg("fetch").arg(version).arg("--verbose"), @r###" 17 | success: true 18 | exit_code: 0 19 | ----- stdout ----- 20 | Python version already downloaded. Skipping. 21 | 22 | ----- stderr ----- 23 | "###); 24 | 25 | // Fetching the same version again with --force should re-download it. 26 | rye_cmd_snapshot!(space.rye_cmd().arg("fetch").arg(version).arg("--force"), @r###" 27 | success: true 28 | exit_code: 0 29 | ----- stdout ----- 30 | Removing the existing Python version 31 | Downloading cpython@3.12.1 32 | Checking checksum 33 | Unpacking 34 | Downloaded cpython@3.12.1 35 | 36 | ----- stderr ----- 37 | "###); 38 | } 39 | -------------------------------------------------------------------------------- /rye/tests/test_tools.rs: -------------------------------------------------------------------------------- 1 | use std::env::consts::EXE_EXTENSION; 2 | use std::fs; 3 | use tempfile::TempDir; 4 | 5 | use crate::common::{rye_cmd_snapshot, Space}; 6 | 7 | mod common; 8 | 9 | #[test] 10 | fn test_basic_tool_behavior() { 11 | let space = Space::new(); 12 | 13 | // Cache alongside the home directory, so that the cache lives alongside the tools directory. 14 | let cache_dir = TempDir::new_in(space.rye_home()).unwrap(); 15 | 16 | // in case we left things behind from last run. 17 | fs::remove_dir_all(space.rye_home().join("tools")).ok(); 18 | fs::remove_file( 19 | space 20 | .rye_home() 21 | .join("shims") 22 | .join("pycowsay") 23 | .with_extension(EXE_EXTENSION), 24 | ) 25 | .ok(); 26 | 27 | rye_cmd_snapshot!( 28 | space.rye_cmd() 29 | .env("UV_CACHE_DIR", cache_dir.path()) 30 | .arg("tools") 31 | .arg("install") 32 | .arg("pycowsay") 33 | .arg("-p") 34 | .arg("cpython@3.11"), @r###" 35 | success: true 36 | exit_code: 0 37 | ----- stdout ----- 38 | 39 | Installed scripts: 40 | - pycowsay 41 | 42 | ----- stderr ----- 43 | Using Python 3.11.11 environment at: [RYE_HOME]/tools/pycowsay 44 | Resolved 1 package in [EXECUTION_TIME] 45 | Prepared 1 package in [EXECUTION_TIME] 46 | Installed 1 package in [EXECUTION_TIME] 47 | + pycowsay==0.0.0.2 48 | "###); 49 | 50 | rye_cmd_snapshot!( 51 | space.rye_cmd() 52 | .env("UV_CACHE_DIR", cache_dir.path()) 53 | .arg("tools") 54 | .arg("list"), @r###" 55 | success: true 56 | exit_code: 0 57 | ----- stdout ----- 58 | pycowsay 59 | 60 | ----- stderr ----- 61 | "###); 62 | 63 | rye_cmd_snapshot!( 64 | space.rye_cmd() 65 | .env("UV_CACHE_DIR", cache_dir.path()) 66 | .arg("tools") 67 | .arg("list") 68 | .arg("--include-version"), @r###" 69 | success: true 70 | exit_code: 0 71 | ----- stdout ----- 72 | pycowsay 0.0.0.2 (cpython@3.11.11) 73 | 74 | ----- stderr ----- 75 | "###); 76 | 77 | rye_cmd_snapshot!( 78 | space.rye_cmd() 79 | .env("UV_CACHE_DIR", cache_dir.path()) 80 | .arg("toolchain") 81 | .arg("remove") 82 | .arg("cpython@3.11.11"), @r###" 83 | success: false 84 | exit_code: 1 85 | ----- stdout ----- 86 | 87 | ----- stderr ----- 88 | error: toolchain cpython@3.11.11 is still in use by tool pycowsay 89 | "###); 90 | 91 | rye_cmd_snapshot!( 92 | space.rye_cmd() 93 | .env("UV_CACHE_DIR", cache_dir.path()) 94 | .arg("tools") 95 | .arg("uninstall") 96 | .arg("pycowsay"), @r###" 97 | success: true 98 | exit_code: 0 99 | ----- stdout ----- 100 | Uninstalled pycowsay 101 | 102 | ----- stderr ----- 103 | "###); 104 | 105 | assert!(!space.rye_home().join("tools").join("pycowsay").is_dir()); 106 | } 107 | -------------------------------------------------------------------------------- /rye/tests/test_version.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{rye_cmd_snapshot, Space}; 2 | 3 | mod common; 4 | 5 | #[test] 6 | fn test_version_show() { 7 | let space = Space::new(); 8 | space.init("my-project"); 9 | rye_cmd_snapshot!(space.rye_cmd().arg("version"), @r###" 10 | success: true 11 | exit_code: 0 12 | ----- stdout ----- 13 | 0.1.0 14 | 15 | ----- stderr ----- 16 | "###); 17 | } 18 | 19 | #[test] 20 | fn test_version_bump() { 21 | let space = Space::new(); 22 | space.init("my-project"); 23 | rye_cmd_snapshot!(space.rye_cmd().arg("version").arg("--bump").arg("patch"), @r###" 24 | success: true 25 | exit_code: 0 26 | ----- stdout ----- 27 | version bumped to 0.1.1 28 | 29 | ----- stderr ----- 30 | "###); 31 | 32 | rye_cmd_snapshot!(space.rye_cmd().arg("version").arg("--bump").arg("minor"), @r###" 33 | success: true 34 | exit_code: 0 35 | ----- stdout ----- 36 | version bumped to 0.2.0 37 | 38 | ----- stderr ----- 39 | "###); 40 | 41 | rye_cmd_snapshot!(space.rye_cmd().arg("version").arg("--bump").arg("major"), @r###" 42 | success: true 43 | exit_code: 0 44 | ----- stdout ----- 45 | version bumped to 1.0.0 46 | 47 | ----- stderr ----- 48 | "###); 49 | } 50 | -------------------------------------------------------------------------------- /scripts/cargo-out-dir: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Finds Cargo's `OUT_DIR` directory from the most recent build. 4 | # 5 | # This requires one parameter corresponding to the target directory 6 | # to search for the build output. 7 | 8 | if [ $# != 1 ]; then 9 | echo "Usage: $(basename "$0") " >&2 10 | exit 2 11 | fi 12 | 13 | # This works by finding the most recent stamp file, which is produced by 14 | # every ripgrep build. 15 | target_dir="$1" 16 | find "$target_dir" -name ripgrep-stamp -print0 \ 17 | | xargs -0 ls -t \ 18 | | head -n1 \ 19 | | xargs dirname 20 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Wrap everything in a function so that a truncated script 5 | # does not have the chance to cause issues. 6 | __wrap__() { 7 | 8 | # allow overriding the version 9 | VERSION=${RYE_VERSION:-latest} 10 | # allow overriding the install option 11 | INSTALL_OPTION=${RYE_INSTALL_OPTION:-""} 12 | 13 | REPO=astral-sh/rye 14 | PLATFORM=`uname -s` 15 | ARCH=`uname -m` 16 | 17 | if [[ $PLATFORM == "Darwin" ]]; then 18 | PLATFORM="macos" 19 | elif [[ $PLATFORM == "Linux" ]]; then 20 | PLATFORM="linux" 21 | fi 22 | 23 | if [[ $ARCH == armv8* ]] || [[ $ARCH == arm64* ]] || [[ $ARCH == aarch64* ]]; then 24 | ARCH="aarch64" 25 | elif [[ $ARCH == i686* ]]; then 26 | ARCH="x86" 27 | fi 28 | 29 | BINARY="rye-${ARCH}-${PLATFORM}" 30 | 31 | # Oddly enough GitHub has different URLs for latest vs specific version 32 | if [[ $VERSION == "latest" ]]; then 33 | DOWNLOAD_URL=https://github.com/${REPO}/releases/latest/download/${BINARY}.gz 34 | else 35 | DOWNLOAD_URL=https://github.com/${REPO}/releases/download/${VERSION}/${BINARY}.gz 36 | fi 37 | 38 | echo "This script will automatically download and install rye (${VERSION}) for you." 39 | if [ "x$(id -u)" == "x0" ]; then 40 | echo "warning: this script is running as root. This is dangerous and unnecessary!" 41 | fi 42 | 43 | if ! hash curl 2> /dev/null; then 44 | echo "error: you do not have 'curl' installed which is required for this script." 45 | exit 1 46 | fi 47 | 48 | if ! hash gunzip 2> /dev/null; then 49 | echo "error: you do not have 'gunzip' installed which is required for this script." 50 | exit 1 51 | fi 52 | 53 | TEMP_FILE=`mktemp "${TMPDIR:-/tmp}/.ryeinstall.XXXXXXXX"` 54 | TEMP_FILE_GZ="${TEMP_FILE}.gz" 55 | 56 | cleanup() { 57 | rm -f "$TEMP_FILE" 58 | rm -f "$TEMP_FILE_GZ" 59 | } 60 | 61 | trap cleanup EXIT 62 | HTTP_CODE=$(curl -SL --progress-bar "$DOWNLOAD_URL" --output "$TEMP_FILE_GZ" --write-out "%{http_code}") 63 | if [[ ${HTTP_CODE} -lt 200 || ${HTTP_CODE} -gt 299 ]]; then 64 | echo "error: platform ${PLATFORM} (${ARCH}) is unsupported." 65 | exit 1 66 | fi 67 | 68 | rm -f "$TEMP_FILE" 69 | gunzip "$TEMP_FILE_GZ" 70 | chmod +x "$TEMP_FILE" 71 | 72 | # Detect when the file cannot be executed due to NOEXEC /tmp. Taken from rustup 73 | # https://github.com/rust-lang/rustup/blob/87fa15d13e3778733d5d66058e5de4309c27317b/rustup-init.sh#L158-L159 74 | if [ ! -x "$TEMP_FILE" ]; then 75 | printf '%s\n' "Cannot execute $TEMP_FILE (likely because of mounting /tmp as noexec)." 1>&2 76 | printf '%s\n' "Please copy the file to a location where you can execute binaries and run it manually." 1>&2 77 | exit 1 78 | fi 79 | 80 | "$TEMP_FILE" self install $INSTALL_OPTION 81 | 82 | }; __wrap__ 83 | -------------------------------------------------------------------------------- /scripts/sha256.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import sys 3 | 4 | h = hashlib.sha256() 5 | 6 | with open(sys.argv[1], "rb") as f: 7 | while True: 8 | chunk = f.read(4096) 9 | if not chunk: 10 | break 11 | h.update(chunk) 12 | 13 | print(h.hexdigest()) 14 | -------------------------------------------------------------------------------- /scripts/summarize-release.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | 5 | version = sys.argv[1] 6 | base = sys.argv[2] 7 | 8 | checksums = {} 9 | 10 | for folder in os.listdir(base): 11 | for filename in os.listdir(os.path.join(base, folder)): 12 | if filename.endswith(".sha256"): 13 | with open(os.path.join(base, folder, filename)) as f: 14 | sha256 = f.read().strip() 15 | checksums[filename[:-7]] = sha256 16 | 17 | print( 18 | json.dumps( 19 | { 20 | "version": version, 21 | "checksums": checksums, 22 | }, 23 | indent=2, 24 | ) 25 | ) 26 | --------------------------------------------------------------------------------