├── .codespell-whitelist.txt ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── docs.yaml │ └── rust-compile.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── cliff.toml ├── crates ├── rattler_installs_packages │ ├── Cargo.toml │ ├── benches │ │ └── html.rs │ ├── src │ │ ├── artifacts │ │ │ ├── mod.rs │ │ │ ├── sdist.rs │ │ │ ├── snapshots │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_as_folder_as_source_dependency.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_git_reference_source_code.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_git_reference_with_tag_source_code.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_http_reference_source_code.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_no_metadata.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_sdist_as_source_dependency.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_rich_with_metadata.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_wheel_and_pass_env_variables.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_wheel_and_with_clean_env_and_pass_env_variables.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__build_wheel_with_backend_path.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_by_tag_for_remote_git.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_for_local_wheel.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_for_remote_sdist.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_with_commit_for_remote_git.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__get_only_metadata_for_local_sdist_rich_without_calling_available_artifacts.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__get_only_metadata_for_local_stree_rich_without_calling_available_artifacts.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__get_only_metadata_for_local_whl_rich_without_calling_available_artifacts.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__get_whl_for_local_sdist_rich.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__get_whl_for_local_stree_rich.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__get_whl_for_local_whl.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__read_tar_gz_archive_for_a_file-2.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__read_tar_gz_archive_for_a_file.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__read_zip_metadata.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__sdist_metadata.snap │ │ │ │ ├── rattler_installs_packages__artifacts__sdist__tests__sdist_without_name.snap │ │ │ │ └── rattler_installs_packages__artifacts__sdist__tests__zip_timestamps_1980.snap │ │ │ ├── stree.rs │ │ │ └── wheel.rs │ │ ├── index │ │ │ ├── direct_url │ │ │ │ ├── file.rs │ │ │ │ ├── git.rs │ │ │ │ ├── http.rs │ │ │ │ └── mod.rs │ │ │ ├── file_store.rs │ │ │ ├── git_interop.rs │ │ │ ├── html.rs │ │ │ ├── http.rs │ │ │ ├── lazy_metadata.rs │ │ │ ├── mod.rs │ │ │ ├── package_database.rs │ │ │ └── package_sources.rs │ │ ├── install │ │ │ ├── install_paths.rs │ │ │ ├── mod.rs │ │ │ └── snapshots │ │ │ │ ├── rattler_installs_packages__install__test__byte_code_compilation.snap │ │ │ │ ├── rattler_installs_packages__install__test__entry_points.snap │ │ │ │ ├── rattler_installs_packages__install__test__miniblack-23.1.0-py3-none-any.whl.snap │ │ │ │ ├── rattler_installs_packages__install__test__purelib_and_platlib-1.0.0-cp38-cp38-linux_x86_64.whl.snap │ │ │ │ ├── rattler_installs_packages__install__test__selenium-2.53.2-py2.py3-none-any.whl.snap │ │ │ │ └── rattler_installs_packages__install__test__selenium-4.1.0-py3-none-any.whl.snap │ │ ├── lib.rs │ │ ├── python_env │ │ │ ├── byte_code_compiler.rs │ │ │ ├── compile_pyc.py │ │ │ ├── distribution_finder.rs │ │ │ ├── env_markers │ │ │ │ ├── from_env.rs │ │ │ │ ├── mod.rs │ │ │ │ └── pep508.py │ │ │ ├── mod.rs │ │ │ ├── snapshots │ │ │ │ └── rattler_installs_packages__python_env__distribution_finder__test__find_distributions.snap │ │ │ ├── system_python.rs │ │ │ ├── tags │ │ │ │ ├── from_env.rs │ │ │ │ ├── mod.rs │ │ │ │ └── platform_tags.py │ │ │ ├── uninstall.rs │ │ │ └── venv.rs │ │ ├── resolve │ │ │ ├── dependency_provider.rs │ │ │ ├── mod.rs │ │ │ ├── pypi_version_types.rs │ │ │ ├── solve.rs │ │ │ ├── solve_options.rs │ │ │ └── solve_types.rs │ │ ├── types │ │ │ ├── artifact.rs │ │ │ ├── artifact_name.rs │ │ │ ├── core_metadata.rs │ │ │ ├── direct_url_json.rs │ │ │ ├── entry_points.rs │ │ │ ├── extra.rs │ │ │ ├── mod.rs │ │ │ ├── package_name.rs │ │ │ ├── project_info.rs │ │ │ ├── record.rs │ │ │ └── rfc822ish.rs │ │ ├── utils │ │ │ ├── mod.rs │ │ │ ├── read_and_seek.rs │ │ │ ├── seek_slice.rs │ │ │ ├── streaming_or_local.rs │ │ │ └── test.rs │ │ ├── wheel_builder │ │ │ ├── build_environment.rs │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ ├── wheel_builder_frontend.py │ │ │ └── wheel_cache.rs │ │ └── win │ │ │ ├── launcher.rs │ │ │ ├── mod.rs │ │ │ └── windows-launcher │ │ │ ├── README.md │ │ │ ├── t32.exe │ │ │ ├── t64-arm.exe │ │ │ ├── t64.exe │ │ │ ├── w32.exe │ │ │ ├── w64-arm.exe │ │ │ └── w64.exe │ ├── tests │ │ └── resolver.rs │ └── vendor │ │ └── packaging │ │ ├── LICENSE │ │ ├── LICENSE.APACHE │ │ ├── LICENSE.BSD │ │ ├── __init__.py │ │ ├── _elffile.py │ │ ├── _manylinux.py │ │ ├── _musllinux.py │ │ ├── _parser.py │ │ ├── _structures.py │ │ ├── _tokenizer.py │ │ ├── markers.py │ │ ├── metadata.py │ │ ├── py.typed │ │ ├── requirements.py │ │ ├── specifiers.py │ │ ├── tags.py │ │ ├── utils.py │ │ └── version.py ├── rip_bin │ ├── Cargo.toml │ └── src │ │ ├── cli │ │ ├── mod.rs │ │ ├── resolve.rs │ │ └── wheels.rs │ │ ├── lib.rs │ │ └── main.rs └── test-utils │ ├── Cargo.toml │ └── src │ └── lib.rs ├── end_to_end_tests └── test_endtoend.py ├── pixi.lock ├── pixi.toml ├── rust-toolchain └── test-data ├── find_distributions ├── Lib │ └── site-packages │ │ ├── Flask-1.1.4.dist-info │ │ ├── INSTALLER │ │ ├── LICENSE.rst │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ ├── entry_points.txt │ │ └── top_level.txt │ │ ├── Jinja2-2.11.3.dist-info │ │ ├── INSTALLER │ │ ├── LICENSE.rst │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ ├── entry_points.txt │ │ └── top_level.txt │ │ ├── MarkupSafe-1.1.1.dist-info │ │ ├── INSTALLER │ │ ├── LICENSE.rst │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ └── top_level.txt │ │ ├── Werkzeug-1.0.1.dist-info │ │ ├── INSTALLER │ │ ├── LICENSE.rst │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ └── top_level.txt │ │ ├── click-7.1.2.dist-info │ │ ├── INSTALLER │ │ ├── LICENSE.rst │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ └── top_level.txt │ │ ├── itsdangerous-1.1.0.dist-info │ │ ├── INSTALLER │ │ ├── LICENSE.rst │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ └── top_level.txt │ │ ├── pip-9.0.1.dist-info │ │ ├── DESCRIPTION.rst │ │ ├── INSTALLER │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ ├── entry_points.txt │ │ ├── metadata.json │ │ └── top_level.txt │ │ ├── setuptools-28.8.0.dist-info │ │ ├── DESCRIPTION.rst │ │ ├── INSTALLER │ │ ├── METADATA │ │ ├── RECORD │ │ ├── WHEEL │ │ ├── dependency_links.txt │ │ ├── entry_points.txt │ │ ├── metadata.json │ │ ├── top_level.txt │ │ └── zip-safe │ │ ├── zipp-3.17.0.dist-info │ │ └── .gitkeep │ │ └── zipp │ │ └── .gitkeep └── pyvenv.cfg ├── scripts └── test_wordle.py ├── sdists ├── env_package-0.1.tar.gz ├── fake-flask-3.0.0.tar.gz ├── filterpy-1.4.5.zip ├── rich-13.6.0.tar.gz ├── rich_without_metadata_in_path.tar.gz ├── setuptools-69.0.2.tar.gz ├── tampered-rich-13.6.0.tar.gz └── zip_read_package-1.0.0.zip ├── stree └── dev_folder_with_rich │ ├── LICENSE │ ├── README.md │ ├── pyproject.toml │ └── rich │ └── __init__.py └── wheels ├── miniblack-23.1.0-py3-none-any.whl ├── purelib_and_platlib-1.0.0-cp38-cp38-linux_x86_64.whl └── wordle_python-2.3.32-py3-none-any.whl /.codespell-whitelist.txt: -------------------------------------------------------------------------------- 1 | crate 2 | ser 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub syntax highlighting 2 | pixi.lock linguist-language=YAML 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | time: "04:00" # UTC 8 | labels: 9 | - "dependencies" 10 | commit-message: 11 | prefix: "bump" 12 | open-pull-requests-limit: 10 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "daily" 17 | labels: 18 | - "dependencies" 19 | commit-message: 20 | prefix: "chore(ci)" 21 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | RUSTFLAGS: "-D warnings" 11 | RUSTDOCFLAGS: --html-in-header header.html 12 | 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow one concurrent deployment 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | build-and-deploy: 27 | if: github.repository == 'prefix-dev/rattler_installs_packages' 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v4 32 | 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | 37 | - name: Setup Pages 38 | uses: actions/configure-pages@v5 39 | 40 | # This does the following: 41 | # - Replaces the docs icon with one that clearly denotes it's not the released package on crates.io 42 | # - Adds a meta tag that forces Google not to index any page on the site. 43 | - name: Pre-docs-build 44 | run: | 45 | echo "" > header.html 46 | 47 | - name: Build Documentation 48 | run: cargo doc --workspace --no-deps --all-features --lib 49 | 50 | # This adds the following: 51 | # - A top level redirect to the rattler_installs_packages crate documentation 52 | # - A robots.txt file to forbid any crawling of the site (to defer to the docs.rs site on search engines). 53 | # - A .nojekyll file to disable Jekyll GitHub Pages builds. 54 | - name: Finalize documentation 55 | run: | 56 | echo "" > target/doc/index.html 57 | echo "User-Agent: *\nDisallow: /" > target/doc/robots.txt 58 | touch target/doc/.nojekyll 59 | 60 | # https://github.com/actions/upload-pages-artifact#file-permissions 61 | - run: chmod -c -R +rX target/doc/ 62 | 63 | - name: Upload artifact 64 | uses: actions/upload-pages-artifact@v3 65 | with: 66 | path: 'target/doc' 67 | 68 | - name: Deploy to GitHub Pages 69 | id: deployment 70 | uses: actions/deploy-pages@v4 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea/ 3 | *.sqlite3 4 | **/__pycache__/** 5 | .DS_STORE 6 | # pixi environments 7 | .pixi 8 | # other venvs 9 | .venv*/ 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test-data/packse"] 2 | path = test-data/packse 3 | url = https://github.com/zanieb/packse 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | # Copied from Mozilla https://github.com/mozilla/grcov/blob/master/.pre-commit-config.yaml 9 | - repo: https://github.com/DevinR528/cargo-sort 10 | rev: v1.0.9 11 | hooks: 12 | - id: cargo-sort 13 | - repo: local 14 | hooks: 15 | - id: fmt 16 | name: fmt 17 | language: system 18 | types: [file, rust] 19 | entry: cargo fmt 20 | pass_filenames: false 21 | 22 | - id: clippy 23 | name: clippy 24 | language: system 25 | types: [file, rust] 26 | entry: cargo clippy --all -- -D warnings # Use -D warnings option to ensure the job fails when encountering warnings 27 | pass_filenames: false 28 | 29 | - id: test 30 | name: test 31 | language: system 32 | stages: [push] 33 | types: [file, rust] 34 | entry: cargo test 35 | pass_filenames: false 36 | - repo: https://github.com/codespell-project/codespell 37 | rev: v2.2.5 38 | hooks: 39 | - id: codespell 40 | args: [--ignore-words=.codespell-whitelist.txt] 41 | exclude: '(Cargo.lock|CHANGELOG.md)' 42 | exclude: 'crates/rattler_installs_packages/vendor/.*|test-data/' 43 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 😍 2 | 3 | We would love to have you contribute! 4 | For a good list of things you could help us with, take a look at our [*good first issues*](https://github.com/prefix-dev/rip/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). 5 | If you want to go deeper though, any unassigned [open issue](https://github.com/prefix-dev/rip/issues) is up for grabs. 6 | When starting on an issue please let us know, and we'll assign you to the issue. 7 | When contributing to this repository, please first discuss the change you wish to make via issue, email, chat or any other method with the owners of this repository before making a change. 8 | 9 | For questions, requests or a casual chat, we are very active on our discord server. 10 | You can [join our discord server via this link][chat-url]. 11 | 12 | [chat-url]: https://discord.gg/kKV8ZxyzY4 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/*"] 4 | 5 | [profile.dev.package.insta] 6 | opt-level = 3 7 | 8 | [workspace.package] 9 | version = "0.10.0" 10 | categories = ["development-tools"] 11 | homepage = "https://github.com/prefix-dev/rip" 12 | repository = "https://github.com/prefix-dev/rip" 13 | license = "BSD-3-Clause" 14 | edition = "2021" 15 | readme = "README.md" 16 | rust-version = "1.70" 17 | 18 | [workspace.metadata.release] 19 | allow-branch = ["main"] 20 | consolidate-commits = true 21 | tag-prefix = "" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, prefix.dev GmbH 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | banner 6 | 7 | 8 | 9 | # RIP: Fast, barebones **pip** implementation in Rust 10 | 11 | ![License][license-badge] 12 | [![Build Status][build-badge]][build] 13 | [![Project Chat][chat-badge]][chat-url] 14 | [![docs main][docs-main-badge]][docs-main] 15 | 16 | # Introduction 17 | 18 | `rip` is a library that allows the resolving and installing of Python [PyPI](https://pypi.org/) packages from Rust into a virtual environment. 19 | It's based on our experience with building [rattler] and aims to provide the same 20 | experience but for PyPI instead of Conda. 21 | 22 | ## What should I use this for? 23 | 24 | Like [rattler], `rip` should be fast and easy to use. This library is not a package manager itself but provides the low-level plumbing to be used in one. 25 | To see an example of this take a look at our package manager: [pixi](https://github.com/prefix-dev/pixi) 📦 26 | 27 | `rip` is based on the quite excellent work of [posy](https://github.com/njsmith/posy) and we have tried to credit 28 | the authors where possible. 29 | 30 | # Showcase 31 | 32 | `rip` has a very incomplete pip-like binary that can be used to test package installs. 33 | Let's resolve and install the `flask` python package. Running `cargo run install flask /tmp/flask` we get something like this: 34 | 35 | ![rip-install](https://github.com/prefix-dev/rip/assets/417374/1d55754f-de3a-474f-8ee8-06f7dd098eea) 36 | 37 | This showcases the downloading and caching of metadata from PyPI. As well as the package resolution using our incremental SAT solver: [Resolvo](https://github.com/mamba-org/resolvo), more on this below. 38 | Finally, after resolution it installs the package into a venv. 39 | We cache everything locally so that we can reuse the PyPI metadata. 40 | 41 | ## Features 42 | 43 | This is a list of current features of `rip`, the biggest are listed below: 44 | 45 | - [x] Async downloading and aggressive caching of PyPI metadata. 46 | - [x] Resolving of PyPI packages using [Resolvo](https://github.com/mamba-org/resolvo). 47 | - [x] Installation of wheel files. 48 | - [x] Support sdist files (must currently adhere to the `PEP 517` and `PEP 518` standards). 49 | - [x] Caching of locally built wheels. 50 | 51 | More intricacies of the PyPI ecosystem need to be implemented, see our [GitHub issues](https://github.com/prefix-dev/rip/issues) for more details. 52 | 53 | # Details 54 | 55 | ## Resolving 56 | 57 | We have integrated the stand-alone packaging SAT solver [Resolvo](https://github.com/mamba-org/resolvo), to resolve PyPI packages. 58 | This solver is incremental and adds packaging metadata during resolution of the SAT problem. 59 | This feature can be enabled with the `resolvo` feature flag. 60 | 61 | ## Installation 62 | 63 | We have very simple installation support for the resolved packages. 64 | This should be used for testing purposes exclusively 65 | e.g. `cargo run -- install flask /tmp/flask_env` to create a venv and install the flask and it's into it. 66 | After which you can run: 67 | 68 | 1. `/tmp/flask_env/bin/python` to start python in the venv. 69 | 2. `import flask #`, this should import the flask package from the venv. 70 | There is no detection of existing packages in the venv yet, although this should be relatively straightforward. 71 | 72 | # Contributing 😍 73 | 74 | We would love to have you contribute! 75 | See the [CONTRIBUTING.md](./CONTRIBUTING.md) for more info. 76 | 77 | For questions, requests or a casual chat, we are very active on our [Discord server][chat-url]. 78 | 79 | [//]: # "[![crates.io][crates-badge]][crates]" 80 | [license-badge]: https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square 81 | [build-badge]: https://img.shields.io/github/actions/workflow/status/prefix-dev/rip/rust-compile.yml?style=flat-square&branch=main 82 | [build]: https://github.com/prefix-dev/rip/actions 83 | [chat-badge]: https://img.shields.io/discord/1082332781146800168.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2&style=flat-square 84 | [chat-url]: https://discord.gg/kKV8ZxyzY4 85 | [docs-main-badge]: https://img.shields.io/badge/docs-main-yellow.svg?style=flat-square 86 | [docs-main]: https://prefix-dev.github.io/rip 87 | [crates]: https://crates.io/crates/rattler_installs_packages 88 | [crates-badge]: https://img.shields.io/crates/v/rattler_installs_packages.svg 89 | [rattler]: https://github.com/mamba-org/rattler 90 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [remote.github] 9 | owner = "prefix-dev" 10 | repo = "pixi" 11 | 12 | [changelog] 13 | # changelog header 14 | header = """ 15 | # Changelog\n 16 | All notable changes to this project will be documented in this file. 17 | 18 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 19 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n 20 | """ 21 | # template for the changelog body 22 | # https://keats.github.io/tera/docs/#introduction 23 | body = """ 24 | {% if version %}\ 25 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 26 | {% else %}\ 27 | ## [Unreleased] 28 | {% endif %}\ 29 | 30 | ### ✨ Highlights 31 | 32 | ### 📃 Details 33 | {% for group, commits in commits | group_by(attribute="group") %} 34 | #### {{ group | upper_first }} 35 | {%- for commit in commits %} 36 | - {{ commit.message | upper_first | trim }}\ 37 | {% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%} 38 | {% if commit.github.pr_number %} in [#{{ commit.github.pr_number }}]\ 39 | (https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/pull/{{ commit.github.pr_number }}){%- endif -%} 40 | {% endfor %} 41 | {% endfor %} 42 | 43 | """ 44 | # # template for the changelog footer 45 | # footer = """ 46 | # {% for release in releases -%} 47 | # {% if release.version -%} 48 | # {% if release.previous.version -%} 49 | # [{{ release.version | trim_start_matches(pat="v") }}]: \ 50 | # https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ 51 | # /compare/{{ release.previous.version }}..{{ release.version }} 52 | # {% endif -%} 53 | # {% else -%} 54 | # [unreleased]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ 55 | # /compare/{{ release.previous.version }}..HEAD 56 | # {% endif -%} 57 | # {% endfor %} 58 | # 59 | # """ 60 | # remove the leading and trailing whitespace from the template 61 | trim = true 62 | # changelog footer 63 | # postprocessors 64 | postprocessors = [ 65 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL 66 | ] 67 | [git] 68 | # parse the commits based on https://www.conventionalcommits.org 69 | conventional_commits = true 70 | # filter out the commits that are not conventional 71 | filter_unconventional = true 72 | # process each line of a commit as an individual commit 73 | split_commits = false 74 | # regex for preprocessing the commit messages 75 | commit_preprocessors = [ 76 | # remove issue numbers from commits 77 | { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, 78 | ] 79 | # regex for parsing and grouping commits 80 | commit_parsers = [ 81 | { message = "^.*: add", group = "Added" }, 82 | { message = "^.*: support", group = "Added" }, 83 | { message = "^.*: remove", group = "Removed" }, 84 | { message = "^.*: delete", group = "Removed" }, 85 | { message = "^test", group = "Fixed" }, 86 | { message = "^fix", group = "Fixed" }, 87 | { message = "^.*: fix", group = "Fixed" }, 88 | { message = "^chore|ci", skip = true }, 89 | { message = "^.*", group = "Changed" }, 90 | ] 91 | # protect breaking changes from being skipped due to matching a skipping commit_parser 92 | protect_breaking_commits = false 93 | # filter out the commits that are not matched by commit parsers 94 | filter_commits = false 95 | # regex for matching git tags 96 | tag_pattern = "v[0-9].*" 97 | # regex for skipping tags 98 | skip_tags = "" 99 | # regex for ignoring tags 100 | ignore_tags = "" 101 | # sort the tags topologically 102 | topo_order = false 103 | # sort the commits inside sections by oldest/newest order 104 | sort_commits = "oldest" 105 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rattler_installs_packages" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Bas Zalmstra ", "Tim de Jager "] 6 | description = "Datastructures and algorithms to interact with Python packaging ecosystem" 7 | categories.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | license.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | include = ["src/", "vendor/", "benches/"] 14 | 15 | [features] 16 | default = ["native-tls"] 17 | native-tls = ['reqwest/native-tls'] 18 | rustls-tls = ['reqwest/rustls-tls'] 19 | 20 | [dependencies] 21 | async-trait = "0.1.80" 22 | bytes = "1.6.0" 23 | ciborium = "0.2.2" 24 | csv = "1.3.0" 25 | data-encoding = "2.5.0" 26 | dunce = "1.0.4" 27 | elsa = "1.10.0" 28 | fs4 = "0.8.2" 29 | futures = "0.3.30" 30 | html-escape = "0.2.13" 31 | # reqwest needs an update to 1.0.0 32 | http = "1.1.0" 33 | http-cache-semantics = { version = "2.1.0", default-features = false, features = ["serde", "reqwest"] } 34 | include_dir = "0.7.3" 35 | indexmap = { version = "2.2.6", features = ["serde"] } 36 | itertools = "0.12.1" 37 | miette = "7.2.0" 38 | mime = "0.3.17" 39 | once_cell = "1.19.0" 40 | parking_lot = "0.12.1" 41 | peg = "0.8.2" 42 | pep440_rs = { version = "0.4.0", features = ["serde"] } 43 | pep508_rs = { version = "0.3.0", features = ["serde"] } 44 | pin-project-lite = "0.2.14" 45 | rattler_digest = { version = "0.19.3", features = ["serde"] } 46 | regex = "1.10.4" 47 | reqwest = { version = "0.12.3", default-features = false, features = ["json", "stream"] } 48 | reqwest-middleware = "0.4.0" 49 | serde = "1.0.198" 50 | serde_json = "1.0.116" 51 | serde_with = "3.7.0" 52 | smallvec = { version = "1.13.2", features = ["const_generics", "const_new"] } 53 | tempfile = "3.10.1" 54 | thiserror = "1.0.58" 55 | tl = "0.7.8" 56 | tokio = { version = "1.37.0", features = ["process", "rt-multi-thread"] } 57 | tokio-util = { version = "0.7.10", features = ["compat"] } 58 | tracing = { version = "0.1.40", default-features = false, features = ["attributes"] } 59 | url = { version = "2.5.0", features = ["serde"] } 60 | zip = "0.6.6" 61 | resolvo = { version = "0.4.0", default-features = false, features = ["tokio"] } 62 | pathdiff = "0.2.1" 63 | async_zip = { version = "0.0.16", features = ["tokio", "deflate"] } 64 | tar = "0.4.40" 65 | flate2 = "1.0.28" 66 | pyproject-toml = "0.9.0" 67 | async-once-cell = "0.5.3" 68 | configparser = "3.0.4" 69 | cacache = { version = "13.0.0", default-features = false, features = ["tokio-runtime", "mmap"] } 70 | async-recursion = "1.1.0" 71 | fs-err = "2.11.0" 72 | fs_extra = "1.3.0" 73 | async_http_range_reader = "0.9.1" 74 | which = "6.0.1" 75 | 76 | [dev-dependencies] 77 | anyhow = "1.0.82" 78 | axum = "0.7.5" 79 | criterion = "0.5" 80 | insta = { version = "1.38.0", features = ["ron", "redactions"] } 81 | miette = { version = "7.2.0", features = ["fancy"] } 82 | once_cell = "1.19.0" 83 | rstest = "0.19.0" 84 | test-utils = { path = "../test-utils" } 85 | tokio = { version = "1.37.0", features = ["rt", "macros", "rt-multi-thread"] } 86 | tokio-test = "0.4.4" 87 | tower-http = { version = "0.5.2", features = ["add-extension"] } 88 | tracing-test = "0.2.4" 89 | 90 | [[bench]] 91 | name = "html" 92 | harness = false 93 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/benches/html.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; 2 | use rattler_installs_packages::index::html::{parse_package_names_html, parse_project_info_html}; 3 | use std::str::FromStr; 4 | use url::Url; 5 | 6 | fn parse_project_info(c: &mut Criterion) { 7 | let html = r#" 8 | 9 | 10 | 11 | 12 | 13 | link1 14 | link2 15 | link3 16 | 17 | 18 | "#; 19 | let url = Url::from_str("https://example.com/simple/link").unwrap(); 20 | c.bench_with_input( 21 | BenchmarkId::new("parse_project_info", "html"), 22 | &(html, url), 23 | |b, (html, url)| b.iter(|| parse_project_info_html(url, html)), 24 | ); 25 | } 26 | 27 | fn parse_package_names(c: &mut Criterion) { 28 | let html = r#" 29 | 30 | 31 | 32 | Simple index 33 | 34 | 35 | 0 36 | 0-._.-._.-._.-._.-._.-._.-0 37 | 000 38 | 0.0.1 39 | 00101s 40 | 00print_lol 41 | 00SMALINUX 42 | 0101 43 | 01changer 44 | 01d61084-d29e-11e9-96d1-7c5cf84ffe8e 45 | 01-distributions 46 | 021 47 | 024travis-test024 48 | 02exercicio 49 | 0411-test 50 | 0.618 51 | 0706xiaoye 52 | 0805nexter 53 | 090807040506030201testpip 54 | 0-core-client 55 | 0FELA 56 | 0html 57 | 0imap 58 | 0lever-so 59 | 0lever-utils 60 | 0-orchestrator 61 | 0proto 62 | 0rest 63 | 0rss 64 | 0wdg9nbmpm 65 | 0wneg 66 | 0x01-autocert-dns-aliyun 67 | 0x01-cubic-sdk 68 | 0x01-letsencrypt 69 | 0x0-python 70 | 0x10c-asm 71 | 0x20bf 72 | 0x2nac0nda 73 | 0x-contract-addresses 74 | 0x-contract-artifacts 75 | 0x-contract-wrappers 76 | 77 | 78 | "#; 79 | 80 | c.bench_function("parse_package_names", |b| { 81 | b.iter(|| parse_package_names_html(black_box(html))) 82 | }); 83 | } 84 | 85 | criterion_group!(benches, parse_project_info, parse_package_names); 86 | criterion_main!(benches); 87 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module containing artifacts that can be resolved and installed. 2 | mod sdist; 3 | 4 | mod stree; 5 | /// Module for working with PyPA wheels. Contains the [`Wheel`] type, and related functionality. 6 | pub mod wheel; 7 | 8 | pub use sdist::SDist; 9 | pub use stree::STree; 10 | pub use wheel::Wheel; 11 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__build_rich_as_folder_as_source_dependency.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: "artifact_info[0].filename.as_stree().unwrap().distribution" 4 | --- 5 | PackageName { 6 | source: "rich", 7 | normalized: "rich", 8 | } 9 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__build_rich_git_reference_source_code.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: "artifact_info[0].filename" 4 | --- 5 | STree( 6 | STreeFilename { 7 | distribution: PackageName { 8 | source: "rich", 9 | normalized: "rich", 10 | }, 11 | version: Version { 12 | epoch: 0, 13 | release: [ 14 | 13, 15 | 7, 16 | 0, 17 | ], 18 | pre: None, 19 | post: None, 20 | dev: None, 21 | local: None, 22 | }, 23 | url: Url { 24 | scheme: "git+https", 25 | cannot_be_a_base: false, 26 | username: "", 27 | password: None, 28 | host: Some( 29 | Domain( 30 | "github.com", 31 | ), 32 | ), 33 | port: None, 34 | path: "/Textualize/rich.git@v13.7.0", 35 | query: None, 36 | fragment: None, 37 | }, 38 | }, 39 | ) 40 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__build_rich_sdist_as_source_dependency.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: "artifact_info[0].filename" 4 | --- 5 | SDist( 6 | SDistFilename { 7 | distribution: PackageName { 8 | source: "rich", 9 | normalized: "rich", 10 | }, 11 | version: Version { 12 | epoch: 0, 13 | release: [ 14 | 13, 15 | 6, 16 | 0, 17 | ], 18 | pre: None, 19 | post: None, 20 | dev: None, 21 | local: None, 22 | }, 23 | format: TarGz, 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__build_wheel_and_pass_env_variables.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: metadata 4 | --- 5 | WheelCoreMetadata { 6 | name: PackageName { 7 | source: "env_package", 8 | normalized: "env-package", 9 | }, 10 | version: Version { 11 | epoch: 0, 12 | release: [ 13 | 0, 14 | 1, 15 | ], 16 | pre: None, 17 | post: None, 18 | dev: None, 19 | local: None, 20 | }, 21 | metadata_version: MetadataVersion( 22 | Version { 23 | epoch: 0, 24 | release: [ 25 | 2, 26 | 1, 27 | ], 28 | pre: None, 29 | post: None, 30 | dev: None, 31 | local: None, 32 | }, 33 | ), 34 | requires_dist: [], 35 | requires_python: None, 36 | extras: {}, 37 | } 38 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__build_wheel_and_with_clean_env_and_pass_env_variables.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: metadata 4 | --- 5 | WheelCoreMetadata { 6 | name: PackageName { 7 | source: "env_package", 8 | normalized: "env-package", 9 | }, 10 | version: Version { 11 | epoch: 0, 12 | release: [ 13 | 0, 14 | 1, 15 | ], 16 | pre: None, 17 | post: None, 18 | dev: None, 19 | local: None, 20 | }, 21 | metadata_version: MetadataVersion( 22 | Version { 23 | epoch: 0, 24 | release: [ 25 | 2, 26 | 1, 27 | ], 28 | pre: None, 29 | post: None, 30 | dev: None, 31 | local: None, 32 | }, 33 | ), 34 | requires_dist: [], 35 | requires_python: None, 36 | extras: {}, 37 | } 38 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_by_tag_for_remote_git.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: direct_url_json 4 | --- 5 | Some( 6 | DirectUrlJson { 7 | url: Url { 8 | scheme: "https", 9 | cannot_be_a_base: false, 10 | username: "", 11 | password: None, 12 | host: Some( 13 | Domain( 14 | "github.com", 15 | ), 16 | ), 17 | port: None, 18 | path: "/mahmoud/boltons.git", 19 | query: None, 20 | fragment: None, 21 | }, 22 | source: Vcs { 23 | vcs: Git, 24 | requested_revision: Some( 25 | "21.0.0", 26 | ), 27 | commit_id: "737daafc388a2380da5c5c517fc1d08f9db36990", 28 | }, 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_for_local_wheel.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: json 4 | --- 5 | DirectUrlJson { 6 | url: Url { 7 | scheme: "file", 8 | cannot_be_a_base: false, 9 | username: "", 10 | password: None, 11 | host: None, 12 | port: None, 13 | path: "/", 14 | query: None, 15 | fragment: None, 16 | }, 17 | source: Dir { 18 | editable: None, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_for_remote_sdist.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: direct_url_json 4 | --- 5 | Some( 6 | DirectUrlJson { 7 | url: Url { 8 | scheme: "https", 9 | cannot_be_a_base: false, 10 | username: "", 11 | password: None, 12 | host: Some( 13 | Domain( 14 | "files.pythonhosted.org", 15 | ), 16 | ), 17 | port: None, 18 | path: "/packages/ea/65/163134cb3c06d42557c0d1a7bc0b53d28fb674c16489f990d9e1bbccfa7b/boltons-20.2.1.tar.gz", 19 | query: None, 20 | fragment: None, 21 | }, 22 | source: Archive { 23 | hashes: Some( 24 | DirectUrlHashes { 25 | sha256: "dd362291a460cc1e0c2e91cc6a60da3036ced77099b623112e8f833e6734bdc5", 26 | }, 27 | ), 28 | }, 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__check_direct_url_json_with_commit_for_remote_git.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: direct_url_json 4 | --- 5 | Some( 6 | DirectUrlJson { 7 | url: Url { 8 | scheme: "https", 9 | cannot_be_a_base: false, 10 | username: "", 11 | password: None, 12 | host: Some( 13 | Domain( 14 | "github.com", 15 | ), 16 | ), 17 | port: None, 18 | path: "/mahmoud/boltons.git", 19 | query: None, 20 | fragment: None, 21 | }, 22 | source: Vcs { 23 | vcs: Git, 24 | requested_revision: Some( 25 | "47c8046492d4db49f163bb977d20d5942e4ddb25", 26 | ), 27 | commit_id: "47c8046492d4db49f163bb977d20d5942e4ddb25", 28 | }, 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__read_zip_metadata.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: result.1 4 | --- 5 | WheelCoreMetadata { 6 | name: PackageName { 7 | source: "filterpy", 8 | normalized: "filterpy", 9 | }, 10 | version: Version { 11 | epoch: 0, 12 | release: [ 13 | 1, 14 | 4, 15 | 5, 16 | ], 17 | pre: None, 18 | post: None, 19 | dev: None, 20 | local: None, 21 | }, 22 | metadata_version: MetadataVersion( 23 | Version { 24 | epoch: 0, 25 | release: [ 26 | 2, 27 | 1, 28 | ], 29 | pre: None, 30 | post: None, 31 | dev: None, 32 | local: None, 33 | }, 34 | ), 35 | requires_dist: [ 36 | Requirement { 37 | name: "numpy", 38 | extras: None, 39 | version_or_url: None, 40 | marker: None, 41 | }, 42 | Requirement { 43 | name: "scipy", 44 | extras: None, 45 | version_or_url: None, 46 | marker: None, 47 | }, 48 | Requirement { 49 | name: "matplotlib", 50 | extras: None, 51 | version_or_url: None, 52 | marker: None, 53 | }, 54 | ], 55 | requires_python: None, 56 | extras: {}, 57 | } 58 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/snapshots/rattler_installs_packages__artifacts__sdist__tests__sdist_without_name.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/artifacts/sdist.rs 3 | expression: "artifact_info[0].filename" 4 | --- 5 | SDist( 6 | SDistFilename { 7 | distribution: PackageName { 8 | source: "rich", 9 | normalized: "rich", 10 | }, 11 | version: Version { 12 | epoch: 0, 13 | release: [ 14 | 13, 15 | 6, 16 | 0, 17 | ], 18 | pre: None, 19 | post: None, 20 | dev: None, 21 | local: None, 22 | }, 23 | format: TarGz, 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/artifacts/stree.rs: -------------------------------------------------------------------------------- 1 | use crate::resolve::PypiVersion; 2 | use crate::types::ArtifactFromSource; 3 | use crate::types::ReadPyProjectError; 4 | use crate::types::{HasArtifactName, STreeFilename, SourceArtifactName}; 5 | use fs_err as fs; 6 | use std::collections::hash_map::DefaultHasher; 7 | use std::hash::{Hash, Hasher}; 8 | use std::path::{Path, PathBuf}; 9 | 10 | /// Represents a source tree which can be a simple directory on filesystem 11 | /// or something cloned from git 12 | pub struct STree { 13 | /// Name of the source tree 14 | pub name: STreeFilename, 15 | 16 | /// Source tree location 17 | pub location: parking_lot::Mutex, 18 | } 19 | 20 | impl STree { 21 | /// Get a lock on the inner data 22 | pub fn lock_data(&self) -> parking_lot::MutexGuard { 23 | self.location.lock() 24 | } 25 | 26 | /// Copy source tree directory in specific location 27 | fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { 28 | fs::create_dir_all(&dst)?; 29 | for entry in fs::read_dir(src.as_ref())? { 30 | let entry = entry?; 31 | let ty = entry.file_type()?; 32 | if ty.is_dir() { 33 | Self::copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; 34 | } else { 35 | fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; 36 | } 37 | } 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl HasArtifactName for STree { 43 | type Name = STreeFilename; 44 | 45 | fn name(&self) -> &Self::Name { 46 | &self.name 47 | } 48 | } 49 | 50 | impl ArtifactFromSource for STree { 51 | fn try_get_bytes(&self) -> Result, std::io::Error> { 52 | let vec = vec![]; 53 | let inner = self.lock_data(); 54 | let mut dir_entry = fs::read_dir(inner.as_path())?; 55 | 56 | let next_entry = dir_entry.next(); 57 | if let Some(Ok(root_folder)) = next_entry { 58 | let modified = root_folder.metadata()?.modified()?; 59 | let mut hasher = DefaultHasher::new(); 60 | modified.hash(&mut hasher); 61 | let hash = hasher.finish().to_be_bytes().as_slice().to_owned(); 62 | return Ok(hash); 63 | } 64 | 65 | Ok(vec) 66 | } 67 | 68 | fn distribution_name(&self) -> String { 69 | self.name.distribution.as_source_str().to_owned() 70 | } 71 | 72 | fn version(&self) -> PypiVersion { 73 | PypiVersion::Url(self.name.url.clone()) 74 | } 75 | 76 | fn artifact_name(&self) -> SourceArtifactName { 77 | SourceArtifactName::STree(self.name.clone()) 78 | } 79 | 80 | fn read_pyproject_toml(&self) -> Result { 81 | let location = self.lock_data().join("pyproject.toml"); 82 | 83 | if let Ok(bytes) = fs::read(location) { 84 | let source = String::from_utf8(bytes).map_err(|e| { 85 | ReadPyProjectError::PyProjectTomlParseError(format!( 86 | "could not parse pyproject.toml (bad encoding): {}", 87 | e 88 | )) 89 | })?; 90 | let project = pyproject_toml::PyProjectToml::new(&source).map_err(|e| { 91 | ReadPyProjectError::PyProjectTomlParseError(format!( 92 | "could not parse pyproject.toml (bad toml): {}", 93 | e 94 | )) 95 | })?; 96 | Ok(project) 97 | } else { 98 | Err(ReadPyProjectError::NoPyProjectTomlFound) 99 | } 100 | } 101 | /// move all files to a specific directory 102 | fn extract_to(&self, work_dir: &Path) -> std::io::Result<()> { 103 | let src = self.lock_data(); 104 | Self::copy_dir_all(src.as_path(), work_dir) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/index/direct_url/git.rs: -------------------------------------------------------------------------------- 1 | use crate::index::git_interop::{git_clone, GitSource, ParsedUrl}; 2 | use crate::index::package_database::DirectUrlArtifactResponse; 3 | use crate::resolve::PypiVersion; 4 | use crate::types::{ 5 | ArtifactHashes, ArtifactInfo, ArtifactName, ArtifactType, DirectUrlJson, DirectUrlSource, 6 | DirectUrlVcs, DistInfoMetadata, HasArtifactName, NormalizedPackageName, Yanked, 7 | }; 8 | use crate::wheel_builder::WheelBuilder; 9 | use indexmap::IndexMap; 10 | use miette::IntoDiagnostic; 11 | use rattler_digest::{compute_bytes_digest, Sha256}; 12 | use std::str::FromStr; 13 | use std::sync::Arc; 14 | use url::Url; 15 | 16 | /// Get artifact by git reference 17 | pub(crate) async fn get_artifacts_and_metadata>( 18 | p: P, 19 | url: Url, 20 | wheel_builder: &Arc, 21 | ) -> miette::Result { 22 | let normalized_package_name = p.into(); 23 | 24 | let parsed_url = ParsedUrl::new(&url)?; 25 | 26 | let git_source = GitSource { 27 | url: parsed_url.git_url, 28 | rev: parsed_url.revision, 29 | }; 30 | 31 | let (mut location, git_rev) = git_clone(&git_source).into_diagnostic()?; 32 | 33 | if let Some(subdirectory) = parsed_url.subdirectory { 34 | location.push(&subdirectory); 35 | if !location.exists() { 36 | return Err(miette::miette!( 37 | "Requested subdirectory fragment {:?} can't be located at following url {:?}", 38 | subdirectory, 39 | url 40 | )); 41 | } 42 | }; 43 | 44 | let (wheel_metadata, artifact) = super::file::get_stree_from_file_path( 45 | &normalized_package_name, 46 | url.clone(), 47 | Some(location), 48 | wheel_builder, 49 | ) 50 | .await?; 51 | 52 | let requires_python = wheel_metadata.1.requires_python.clone(); 53 | 54 | let dist_info_metadata = DistInfoMetadata { 55 | available: false, 56 | hashes: ArtifactHashes::default(), 57 | }; 58 | 59 | let yanked = Yanked { 60 | yanked: false, 61 | reason: None, 62 | }; 63 | 64 | let direct_url_json = DirectUrlJson { 65 | url: Url::from_str(parsed_url.url.as_str()).expect("URL should be parseable"), 66 | source: DirectUrlSource::Vcs { 67 | vcs: DirectUrlVcs::Git, 68 | requested_revision: git_source.rev, 69 | commit_id: git_rev.get_commit(), 70 | }, 71 | }; 72 | 73 | let project_hash = ArtifactHashes { 74 | sha256: Some(compute_bytes_digest::(url.as_str().as_bytes())), 75 | }; 76 | 77 | let artifact_info = Arc::new(ArtifactInfo { 78 | filename: ArtifactName::STree(artifact.name().clone()), 79 | url: url.clone(), 80 | is_direct_url: true, 81 | hashes: Some(project_hash), 82 | requires_python, 83 | dist_info_metadata, 84 | yanked, 85 | }); 86 | 87 | let mut result = IndexMap::default(); 88 | result.insert(PypiVersion::Url(url.clone()), vec![artifact_info.clone()]); 89 | 90 | Ok(DirectUrlArtifactResponse { 91 | artifact_info, 92 | metadata: (wheel_metadata.0, wheel_metadata.1), 93 | artifact_versions: result, 94 | artifact: ArtifactType::STree(artifact), 95 | direct_url_json, 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/index/direct_url/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::index::http::Http; 4 | use crate::index::package_database::DirectUrlArtifactResponse; 5 | use crate::types::NormalizedPackageName; 6 | use crate::wheel_builder::WheelBuilder; 7 | use url::Url; 8 | 9 | pub(crate) mod file; 10 | pub(crate) mod git; 11 | pub(crate) mod http; 12 | 13 | /// Get artifact directly from file, vcs, or url 14 | pub(crate) async fn fetch_artifact_and_metadata_by_direct_url>( 15 | http: &Http, 16 | p: P, 17 | url: Url, 18 | wheel_builder: &Arc, 19 | ) -> miette::Result { 20 | let p = p.into(); 21 | 22 | let response = if url.scheme() == "file" { 23 | // This can result in a Wheel, Sdist or STree 24 | super::direct_url::file::get_artifacts_and_metadata(p.clone(), url, wheel_builder).await 25 | } else if url.scheme() == "https" { 26 | // This can be a Wheel or SDist artifact 27 | super::direct_url::http::get_artifacts_and_metadata(http, p.clone(), url, wheel_builder) 28 | .await 29 | } else if url.scheme() == "git+https" || url.scheme() == "git+file" { 30 | // This can be a STree artifact 31 | super::direct_url::git::get_artifacts_and_metadata(p.clone(), url, wheel_builder).await 32 | } else { 33 | Err(miette::miette!( 34 | "Usage of insecure protocol or unsupported scheme {:?}", 35 | url.scheme() 36 | )) 37 | }?; 38 | 39 | Ok(response) 40 | } 41 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/index/lazy_metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::artifacts::wheel::{find_dist_info_metadata, WheelVitalsError}; 2 | use crate::types::{WheelCoreMetadata, WheelFilename}; 3 | use async_http_range_reader::AsyncHttpRangeReader; 4 | use async_zip::base::read::seek::ZipFileReader; 5 | use tokio_util::compat::TokioAsyncReadCompatExt; 6 | 7 | /// Reads the metadata from a wheel by only reading parts of the wheel zip. 8 | /// 9 | /// This function uses [`AsyncHttpRangeReader`] which allows reading parts of a file by performing 10 | /// http range requests. First the end of the file is read to index the central directory of the 11 | /// zip. This provides an index into the file which allows accessing the exact bytes that contain 12 | /// the METADATA file. 13 | pub(crate) async fn lazy_read_wheel_metadata( 14 | name: &WheelFilename, 15 | stream: &mut AsyncHttpRangeReader, 16 | ) -> Result<(Vec, WheelCoreMetadata), WheelVitalsError> { 17 | // Make sure we have the back part of the stream. 18 | // Best guess for the central directory size inside the zip 19 | const CENTRAL_DIRECTORY_SIZE: u64 = 16384; 20 | // Because the zip index is at the back 21 | stream 22 | .prefetch(stream.len().saturating_sub(CENTRAL_DIRECTORY_SIZE)..stream.len()) 23 | .await; 24 | 25 | // Construct a zip reader to uses the stream. 26 | let mut reader = ZipFileReader::new(stream.compat()) 27 | .await 28 | .map_err(|err| WheelVitalsError::from_async_zip("/".into(), err))?; 29 | 30 | // Collect all top-level filenames 31 | let file_names = reader 32 | .file() 33 | .entries() 34 | .iter() 35 | .enumerate() 36 | .filter_map(|(idx, entry)| Some(((idx, entry), entry.filename().as_str().ok()?))); 37 | 38 | // Determine the name of the dist-info directory 39 | let ((metadata_idx, metadata_entry), dist_info_prefix) = 40 | find_dist_info_metadata(name, file_names)?; 41 | let metadata_path = format!("{dist_info_prefix}.dist-info/METADATA"); 42 | 43 | // Get the size of the entry plus the header + size of the filename. We should also actually 44 | // include bytes for the extra fields but we don't have that information. 45 | let offset = metadata_entry.header_offset(); 46 | let size = metadata_entry.compressed_size() 47 | + 30 // Header size in bytes 48 | + metadata_entry.filename().as_bytes().len() as u64; 49 | 50 | // The zip archive uses as BufReader which reads in chunks of 8192. To ensure we prefetch 51 | // enough data we round the size up to the nearest multiple of the buffer size. 52 | let buffer_size = 8192; 53 | let size = ((size + buffer_size - 1) / buffer_size) * buffer_size; 54 | 55 | // Fetch the bytes from the zip archive that contain the requested file. 56 | reader 57 | .inner_mut() 58 | .get_mut() 59 | .prefetch(offset..offset + size) 60 | .await; 61 | 62 | // Read the contents of the metadata.json file 63 | let mut contents = Vec::new(); 64 | reader 65 | .reader_with_entry(metadata_idx) 66 | .await 67 | .map_err(|e| WheelVitalsError::from_async_zip(metadata_path.clone(), e))? 68 | .read_to_end_checked(&mut contents) 69 | .await 70 | .map_err(|e| WheelVitalsError::from_async_zip(metadata_path, e))?; 71 | 72 | // Parse the wheel data 73 | let metadata = WheelCoreMetadata::try_from(contents.as_slice())?; 74 | 75 | let stream = reader.into_inner().into_inner(); 76 | let ranges = stream.requested_ranges().await; 77 | let total_bytes_fetched: u64 = ranges.iter().map(|r| r.end - r.start).sum(); 78 | tracing::debug!( 79 | "fetched {} ranges, total of {} bytes, total file length {} ({}%)", 80 | ranges.len(), 81 | total_bytes_fetched, 82 | stream.len(), 83 | (total_bytes_fetched as f64 / stream.len() as f64 * 100000.0).round() / 100.0 84 | ); 85 | 86 | Ok((contents, metadata)) 87 | } 88 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/index/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains functions for working with PyPA packaging repositories. 2 | 3 | mod file_store; 4 | 5 | mod direct_url; 6 | mod git_interop; 7 | pub mod html; 8 | mod http; 9 | mod lazy_metadata; 10 | mod package_database; 11 | mod package_sources; 12 | 13 | pub use package_database::{ArtifactRequest, CheckAvailablePackages, PackageDb}; 14 | pub use package_sources::{PackageSources, PackageSourcesBuilder}; 15 | 16 | pub use self::http::CacheMode; 17 | pub use html::parse_hash; 18 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/install/install_paths.rs: -------------------------------------------------------------------------------- 1 | use crate::python_env::PythonInterpreterVersion; 2 | use std::borrow::Cow; 3 | use std::path::{Path, PathBuf}; 4 | 5 | /// A struct of installation categories to where they should be stored relative to the 6 | /// installation destination. 7 | #[derive(Debug, Clone)] 8 | pub struct InstallPaths { 9 | purelib: PathBuf, 10 | platlib: PathBuf, 11 | scripts: PathBuf, 12 | data: PathBuf, 13 | headers: PathBuf, 14 | windows: bool, 15 | } 16 | 17 | impl InstallPaths { 18 | /// Populates mappings of installation targets for a virtualenv layout. The mapping depends on 19 | /// the python version and whether or not the installation targets windows. Specifically on 20 | /// windows some of the paths are different. :shrug: 21 | pub fn for_venv>(version: V, windows: bool) -> Self { 22 | let version = version.into(); 23 | 24 | let site_packages = if windows { 25 | Path::new("Lib").join("site-packages") 26 | } else { 27 | Path::new("lib").join(format!( 28 | "python{}.{}/site-packages", 29 | version.major, version.minor 30 | )) 31 | }; 32 | let scripts = if windows { 33 | PathBuf::from("Scripts") 34 | } else { 35 | PathBuf::from("bin") 36 | }; 37 | 38 | // Data should just be the root of the venv 39 | let data = PathBuf::from(""); 40 | 41 | // purelib and platlib locations are not relevant when using venvs 42 | // https://stackoverflow.com/a/27882460/3549270 43 | Self { 44 | purelib: site_packages.clone(), 45 | platlib: site_packages, 46 | scripts, 47 | data, 48 | windows, 49 | headers: PathBuf::from("include"), 50 | } 51 | } 52 | 53 | /// Determines whether this is a windows InstallPath 54 | pub fn is_windows(&self) -> bool { 55 | self.windows 56 | } 57 | 58 | /// Returns the site-packages location. This is done by searching for the purelib location. 59 | pub fn site_packages(&self) -> &Path { 60 | &self.purelib 61 | } 62 | 63 | /// Reference to pure python library location. 64 | pub fn purelib(&self) -> &Path { 65 | &self.purelib 66 | } 67 | 68 | /// Reference to platform specific library location. 69 | pub fn platlib(&self) -> &Path { 70 | &self.platlib 71 | } 72 | 73 | /// Returns the binaries location. 74 | pub fn scripts(&self) -> &Path { 75 | &self.scripts 76 | } 77 | 78 | /// Returns the location of the data directory 79 | pub fn data(&self) -> &Path { 80 | &self.data 81 | } 82 | 83 | /// Returns the location of the include directory 84 | pub fn include(&self) -> PathBuf { 85 | if self.windows { 86 | PathBuf::from("Include") 87 | } else { 88 | PathBuf::from("include") 89 | } 90 | } 91 | 92 | /// Returns the location of the headers directory. The location of headers is specific to a 93 | /// distribution name. 94 | pub fn headers(&self, distribution_name: &str) -> PathBuf { 95 | self.headers.join(distribution_name) 96 | } 97 | 98 | /// Matches the different categories to their install paths. 99 | pub fn match_category(&self, category: &str, distribution_name: &str) -> Option> { 100 | match category { 101 | "purelib" => Some(self.purelib().into()), 102 | "platlib" => Some(self.platlib().into()), 103 | "scripts" => Some(self.scripts().into()), 104 | "data" => Some(self.data().into()), 105 | "headers" => Some(self.headers(distribution_name).into()), 106 | &_ => None, 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/install/snapshots/rattler_installs_packages__install__test__entry_points.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/install/mod.rs 3 | expression: stdout 4 | --- 5 | borg : '==' 6 | dead : 'xx' 7 | default : 'oo' 8 | greedy : '$$' 9 | paranoid : '@@' 10 | tired : '--' 11 | wired : 'OO' 12 | young : '..' 13 | 14 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/install/snapshots/rattler_installs_packages__install__test__miniblack-23.1.0-py3-none-any.whl.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/install/mod.rs 3 | expression: record_content 4 | --- 5 | ../../../bin/black,sha256=G2L8O1CciVzKcy6rDIj66_vauFAGNMXYpKPgqaow3XE,212 6 | ../../../bin/blackd,sha256=7FFQGF_2mwatv_1_GpVmEh9QJhn0IIi9PtJUHvnRySs,213 7 | black/__init__.py,sha256=VWazgq80l38r1Cn5Ea7fH5bMuDdFuQJtYgBKGWJganI,46682 8 | blackd/__init__.py,sha256=cTJotjnER9YB0rDWo3hOWSg4v0mNAx5DFmCJdyB1s5k,8068 9 | miniblack-23.1.0.dist-info/INSTALLER,sha256=FpzDrzfcP6oI1mbsvYAjw7hT2I4P4wr99k5NKqmKJt0,10 10 | miniblack-23.1.0.dist-info/METADATA,sha256=galtIfXUJ5UEq_3ERFi03BdCo6KHkz2r6jc0EYPsEPY,58963 11 | miniblack-23.1.0.dist-info/RECORD,, 12 | miniblack-23.1.0.dist-info/WHEEL,sha256=rE4t8wm7r0N5wTLLeSbOvJha-KwM1ALPuB-FmvNahDo,144 13 | miniblack-23.1.0.dist-info/entry_points.txt,sha256=qBIyywHwGRkJj7kieq86kqf77rz3qGC4Joj36lHnxwc,78 14 | miniblack-23.1.0.dist-info/licenses/AUTHORS.md,sha256=cpHlH2nYyIXWEnA0LccoGenN-g5HZ7341klZ7MwbdIU,8043 15 | miniblack-23.1.0.dist-info/licenses/LICENSE,sha256=nAQo8MO0d5hQz1vZbhGqqK_HLUqG1KNiI9erouWNbgA,1080 16 | 17 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/install/snapshots/rattler_installs_packages__install__test__purelib_and_platlib-1.0.0-cp38-cp38-linux_x86_64.whl.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/install/mod.rs 3 | expression: record_content 4 | --- 5 | pure.py,sha256=VRmPEZgJrqUEQPsd8wxEp7cMWzySWs7MojKDGZhjUCQ,28 6 | purelib_and_platlib-1.0.0.dist-info/INSTALLER,sha256=FpzDrzfcP6oI1mbsvYAjw7hT2I4P4wr99k5NKqmKJt0,10 7 | purelib_and_platlib-1.0.0.dist-info/METADATA,sha256=3YoTq5hsIcUfHSB9eMPScL0ntE8JfFNnwUAL9qUjy5g,62 8 | purelib_and_platlib-1.0.0.dist-info/RECORD,, 9 | purelib_and_platlib-1.0.0.dist-info/WHEEL,sha256=3L-5uHiSOZxNkLCtSZK7mYrls2MmCfq0WHor5xNVzco,86 10 | 11 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! RIP is a library that allows the resolving and installing of Python PyPI packages from Rust into a virtual environment. 2 | //! It's based on our experience with building Rattler and aims to provide the same experience but for PyPI instead of Conda. 3 | //! It should be fast and easy to use. 4 | //! Like Rattler, this library is not a package manager itself but provides the low-level plumbing to be used in one. 5 | 6 | #![deny(missing_docs)] 7 | 8 | /// Contains the types that are used throughout the library. 9 | pub mod types; 10 | 11 | pub mod python_env; 12 | 13 | pub mod index; 14 | 15 | pub mod install; 16 | mod utils; 17 | 18 | pub mod resolve; 19 | 20 | pub mod wheel_builder; 21 | 22 | mod win; 23 | 24 | pub mod artifacts; 25 | pub use utils::normalize_index_url; 26 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/compile_pyc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import importlib 4 | import compileall 5 | from multiprocessing import Pool 6 | 7 | 8 | def compile_one(path): 9 | success = compileall.compile_file(path, quiet=2, force=True) 10 | output_path = importlib.util.cache_from_source(path) if success else None 11 | return path, output_path 12 | 13 | 14 | def compilation_finished(compilation_result): 15 | path, output_path = compilation_result 16 | print(json.dumps({"path": path, "output_path": output_path})) 17 | 18 | 19 | if __name__ == "__main__": 20 | with sys.stdin: 21 | with Pool() as pool: 22 | while True: 23 | path = sys.stdin.readline().strip() 24 | if not path: 25 | break 26 | pool.apply_async(compile_one, (path,), callback=compilation_finished) 27 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/env_markers/from_env.rs: -------------------------------------------------------------------------------- 1 | use super::Pep508EnvMakers; 2 | use crate::python_env::{system_python_executable, FindPythonError}; 3 | use std::io; 4 | use std::io::ErrorKind; 5 | use std::path::Path; 6 | use std::process::ExitStatus; 7 | use thiserror::Error; 8 | 9 | #[derive(Debug, Error)] 10 | pub enum FromPythonError { 11 | #[error(transparent)] 12 | CouldNotFindPythonExecutable(#[from] FindPythonError), 13 | 14 | #[error(transparent)] 15 | FailedToExecute(#[from] io::Error), 16 | 17 | #[error(transparent)] 18 | FailedToParse(#[from] serde_json::Error), 19 | 20 | #[error("execution failed with exit code {0}")] 21 | FailedToRun(ExitStatus), 22 | } 23 | 24 | impl Pep508EnvMakers { 25 | /// Try to determine the environment markers by executing python. 26 | pub async fn from_env() -> Result { 27 | let python = system_python_executable()?; 28 | tracing::info!("using python executable at {}", python.display()); 29 | Self::from_python(python.as_path()).await 30 | } 31 | 32 | /// Try to determine the environment markers from an existing python executable. The executable 33 | /// is used to run a simple python program to extract the information. 34 | pub async fn from_python(python: &Path) -> Result { 35 | let pep508_bytes = include_str!("pep508.py"); 36 | 37 | // Execute the python executable 38 | let output = match tokio::process::Command::new(python) 39 | .arg("-c") 40 | .arg(pep508_bytes) 41 | .output() 42 | .await 43 | { 44 | Err(e) if e.kind() == ErrorKind::NotFound => { 45 | return Err(FromPythonError::CouldNotFindPythonExecutable( 46 | FindPythonError::NotFound, 47 | )) 48 | } 49 | Err(e) => return Err(FromPythonError::FailedToExecute(e)), 50 | Ok(output) => output, 51 | }; 52 | 53 | // Ensure that we have a valid success code 54 | if !output.status.success() { 55 | return Err(FromPythonError::FailedToRun(output.status)); 56 | } 57 | 58 | // Convert the JSON 59 | let stdout = String::from_utf8_lossy(&output.stdout); 60 | Ok(serde_json::from_str(stdout.trim())?) 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod test { 66 | use super::*; 67 | 68 | #[tokio::test] 69 | pub async fn test_from_env() { 70 | match Pep508EnvMakers::from_env().await { 71 | Err(FromPythonError::CouldNotFindPythonExecutable(_)) => { 72 | // This is fine, the test machine does not include a python binary. 73 | } 74 | Err(e) => panic!("{e}"), 75 | Ok(env) => { 76 | println!( 77 | "Found the following environment markers on the current system:\n\n{env:#?}" 78 | ) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/env_markers/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::ops::Deref; 3 | 4 | mod from_env; 5 | 6 | /// Describes the environment markers that can be used in dependency specifications to enable or 7 | /// disable certain dependencies based on runtime environment. 8 | /// 9 | /// Exactly the markers defined in this struct must be present during version resolution. Unknown 10 | /// variables should raise an error. 11 | /// 12 | /// Note that the "extra" variable is not defined in this struct because it depends on the wheel 13 | /// that is being inspected. 14 | /// 15 | /// The behavior and the names of the markers are described in PEP 508. 16 | #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] 17 | #[allow(missing_docs)] 18 | #[serde(transparent)] 19 | pub struct Pep508EnvMakers(pub pep508_rs::MarkerEnvironment); 20 | 21 | impl From for Pep508EnvMakers { 22 | fn from(value: pep508_rs::MarkerEnvironment) -> Self { 23 | Self(value) 24 | } 25 | } 26 | 27 | impl Deref for Pep508EnvMakers { 28 | type Target = pep508_rs::MarkerEnvironment; 29 | 30 | fn deref(&self) -> &Self::Target { 31 | &self.0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/env_markers/pep508.py: -------------------------------------------------------------------------------- 1 | # A program that outputs PEP 508 environment markers in a JSON format. Most of the 2 | # implementation has been taken from the example in the PEP. 3 | # 4 | # See: https://peps.python.org/pep-0508/ 5 | 6 | import os 7 | import sys 8 | import platform 9 | import json 10 | 11 | 12 | def format_full_version(info): 13 | version = '{0.major}.{0.minor}.{0.micro}'.format(info) 14 | kind = info.releaselevel 15 | if kind != 'final': 16 | version += kind[0] + str(info.serial) 17 | return version 18 | 19 | 20 | if hasattr(sys, 'implementation'): 21 | implementation_version = format_full_version(sys.implementation.version) 22 | implementation_name = sys.implementation.name 23 | else: 24 | implementation_version = '0' 25 | implementation_name = '' 26 | bindings = { 27 | 'implementation_name': implementation_name, 28 | 'implementation_version': implementation_version, 29 | 'os_name': os.name, 30 | 'platform_machine': platform.machine(), 31 | 'platform_python_implementation': platform.python_implementation(), 32 | 'platform_release': platform.release(), 33 | 'platform_system': platform.system(), 34 | 'platform_version': platform.version(), 35 | 'python_full_version': platform.python_version(), 36 | 'python_version': '.'.join(platform.python_version_tuple()[:2]), 37 | 'sys_platform': sys.platform, 38 | } 39 | 40 | json.dump(bindings, sys.stdout) 41 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module for working with python environments. 2 | //! Contains functionality for querying and manipulating python environments. 3 | 4 | mod tags; 5 | 6 | mod distribution_finder; 7 | 8 | mod env_markers; 9 | 10 | mod system_python; 11 | 12 | mod uninstall; 13 | mod venv; 14 | 15 | mod byte_code_compiler; 16 | 17 | pub use tags::{WheelTag, WheelTags}; 18 | 19 | pub use byte_code_compiler::{ByteCodeCompiler, CompilationError, SpawnCompilerError}; 20 | pub use distribution_finder::{ 21 | find_distributions_in_directory, find_distributions_in_venv, Distribution, 22 | FindDistributionError, 23 | }; 24 | pub use env_markers::Pep508EnvMakers; 25 | pub(crate) use system_python::{system_python_executable, FindPythonError}; 26 | pub use system_python::{ParsePythonInterpreterVersionError, PythonInterpreterVersion}; 27 | pub use uninstall::{uninstall_distribution, UninstallDistributionError}; 28 | pub use venv::{PythonLocation, VEnv, VEnvError}; 29 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/snapshots/rattler_installs_packages__python_env__distribution_finder__test__find_distributions.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/rattler_installs_packages/src/python_env/distribution_finder.rs 3 | expression: distributions 4 | --- 5 | [ 6 | Distribution( 7 | name: "click", 8 | version: "7.1.2", 9 | installer: Some("pip"), 10 | dist_info: "Lib/site-packages/click-7.1.2.dist-info", 11 | tags: Some([ 12 | "py2-none-any", 13 | "py3-none-any", 14 | ]), 15 | ), 16 | Distribution( 17 | name: "flask", 18 | version: "1.1.4", 19 | installer: Some("pip"), 20 | dist_info: "Lib/site-packages/Flask-1.1.4.dist-info", 21 | tags: Some([ 22 | "py2-none-any", 23 | "py3-none-any", 24 | ]), 25 | ), 26 | Distribution( 27 | name: "itsdangerous", 28 | version: "1.1.0", 29 | installer: Some("pip"), 30 | dist_info: "Lib/site-packages/itsdangerous-1.1.0.dist-info", 31 | tags: Some([ 32 | "py2-none-any", 33 | "py3-none-any", 34 | ]), 35 | ), 36 | Distribution( 37 | name: "jinja2", 38 | version: "2.11.3", 39 | installer: Some("pip"), 40 | dist_info: "Lib/site-packages/Jinja2-2.11.3.dist-info", 41 | tags: Some([ 42 | "py2-none-any", 43 | "py3-none-any", 44 | ]), 45 | ), 46 | Distribution( 47 | name: "markupsafe", 48 | version: "1.1.1", 49 | installer: Some("pip"), 50 | dist_info: "Lib/site-packages/MarkupSafe-1.1.1.dist-info", 51 | tags: Some([ 52 | "cp35-cp35m-win_amd64", 53 | ]), 54 | ), 55 | Distribution( 56 | name: "pip", 57 | version: "9.0.1", 58 | installer: Some("pip"), 59 | dist_info: "Lib/site-packages/pip-9.0.1.dist-info", 60 | tags: Some([ 61 | "py2-none-any", 62 | "py3-none-any", 63 | ]), 64 | ), 65 | Distribution( 66 | name: "setuptools", 67 | version: "28.8.0", 68 | installer: Some("pip"), 69 | dist_info: "Lib/site-packages/setuptools-28.8.0.dist-info", 70 | tags: Some([ 71 | "py2-none-any", 72 | "py3-none-any", 73 | ]), 74 | ), 75 | Distribution( 76 | name: "werkzeug", 77 | version: "1.0.1", 78 | installer: Some("pip"), 79 | dist_info: "Lib/site-packages/Werkzeug-1.0.1.dist-info", 80 | tags: Some([ 81 | "py2-none-any", 82 | "py3-none-any", 83 | ]), 84 | ), 85 | ] 86 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/tags/from_env.rs: -------------------------------------------------------------------------------- 1 | use crate::python_env::{system_python_executable, FindPythonError, WheelTag, WheelTags}; 2 | use crate::utils::VENDORED_PACKAGING_DIR; 3 | use serde::Deserialize; 4 | use std::io; 5 | use std::io::ErrorKind; 6 | use std::path::Path; 7 | use std::process::ExitStatus; 8 | use thiserror::Error; 9 | 10 | #[derive(Debug, Error)] 11 | pub enum FromPythonError { 12 | #[error(transparent)] 13 | CouldNotFindPythonExecutable(#[from] FindPythonError), 14 | 15 | #[error("{0}")] 16 | PythonError(String), 17 | 18 | #[error(transparent)] 19 | FailedToExecute(#[from] io::Error), 20 | 21 | #[error(transparent)] 22 | FailedToParse(#[from] serde_json::Error), 23 | 24 | #[error("execution failed with exit code {0}")] 25 | FailedToRun(ExitStatus), 26 | } 27 | 28 | impl WheelTags { 29 | /// Try to determine the platform tags by executing the python command and extracting `sys_tags` 30 | /// using the vendored `packaging` module. 31 | pub async fn from_env() -> Result { 32 | Self::from_python(system_python_executable()?.as_path()).await 33 | } 34 | 35 | /// Try to determine the platform tags by executing the python command and extracting `sys_tags` 36 | /// using the vendored `packaging` module. 37 | pub async fn from_python(python: &Path) -> Result { 38 | // Create a temporary directory to place our vendored packages in 39 | let vendored_dir = tempfile::tempdir()?; 40 | let packaging_target_dir = vendored_dir.path().join("packaging"); 41 | tokio::fs::create_dir_all(&packaging_target_dir).await?; 42 | VENDORED_PACKAGING_DIR.extract(&packaging_target_dir)?; 43 | 44 | // Execute the python executable 45 | let output = match tokio::process::Command::new(python) 46 | .arg("-c") 47 | .arg(include_str!("platform_tags.py")) 48 | .env("PYTHONPATH", vendored_dir.path()) 49 | .output() 50 | .await 51 | { 52 | Err(e) if e.kind() == ErrorKind::NotFound => { 53 | return Err(FromPythonError::CouldNotFindPythonExecutable( 54 | FindPythonError::NotFound, 55 | )) 56 | } 57 | Err(e) => return Err(FromPythonError::FailedToExecute(e)), 58 | Ok(output) => output, 59 | }; 60 | 61 | // Ensure that we have a valid success code 62 | if !output.status.success() { 63 | return Err(FromPythonError::FailedToRun(output.status)); 64 | } 65 | 66 | #[derive(Deserialize)] 67 | #[serde(untagged)] 68 | enum Result { 69 | Tags(Vec<(String, String, String)>), 70 | Error(String), 71 | } 72 | 73 | // Convert the JSON 74 | let stdout = String::from_utf8_lossy(&output.stdout); 75 | match serde_json::from_str(stdout.trim())? { 76 | Result::Tags(tags) => Ok(Self { 77 | tags: tags 78 | .into_iter() 79 | .map(|(interpreter, abi, platform)| WheelTag { 80 | interpreter, 81 | abi, 82 | platform, 83 | }) 84 | .collect(), 85 | }), 86 | Result::Error(err) => Err(FromPythonError::PythonError(err)), 87 | } 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod test { 93 | use super::*; 94 | use itertools::Itertools; 95 | 96 | #[tokio::test] 97 | pub async fn test_from_env() { 98 | match WheelTags::from_env().await { 99 | Err(FromPythonError::CouldNotFindPythonExecutable(_)) => { 100 | // This is fine, the test machine does not include a python binary. 101 | } 102 | Err(FromPythonError::PythonError(e)) => { 103 | println!("{e}") 104 | } 105 | Err(e) => panic!("{e:?}"), 106 | Ok(tags) => { 107 | println!( 108 | "Found the following platform tags on the current system:\n{}", 109 | tags.tags.iter().format(", ") 110 | ) 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/tags/mod.rs: -------------------------------------------------------------------------------- 1 | //! Wheels encode the Python interpreter, ABI, and platform that they support in their filenames 2 | //! using platform compatibility tags. This module provides support for discovering what tags the 3 | //! running Python interpreter supports and determining if a wheel is compatible with a set of tags. 4 | 5 | mod from_env; 6 | 7 | use indexmap::IndexSet; 8 | use itertools::Itertools; 9 | use serde_with::{DeserializeFromStr, SerializeDisplay}; 10 | use std::fmt::{Debug, Display, Formatter}; 11 | use std::str::FromStr; 12 | 13 | /// A representation of a tag triple for a wheel. 14 | #[derive(Debug, Clone, Hash, Eq, PartialEq, SerializeDisplay, DeserializeFromStr)] 15 | pub struct WheelTag { 16 | /// The interpreter name, e.g. "py" 17 | pub interpreter: String, 18 | 19 | /// The ABI that a wheel supports, e.g. "cp37m" 20 | pub abi: String, 21 | 22 | /// The OS/platform the wheel supports, e.g. "win_am64". 23 | pub platform: String, 24 | } 25 | 26 | impl WheelTag { 27 | /// Parses a compound string into a `WheelTag`. A compound string is a string that contains 28 | /// multiple tags in a single string. 29 | /// 30 | /// ```rust 31 | /// # use rattler_installs_packages::python_env::WheelTag; 32 | /// let tags = WheelTag::from_compound_string( 33 | /// "cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64").unwrap(); 34 | /// 35 | /// assert_eq!(tags.len(), 2); 36 | /// assert_eq!(tags[0].interpreter, "cp310"); 37 | /// assert_eq!(tags[0].abi, "cp310"); 38 | /// assert_eq!(tags[0].platform, "manylinux_2_17_x86_64"); 39 | /// assert_eq!(tags[1].interpreter, "cp310"); 40 | /// assert_eq!(tags[1].abi, "cp310"); 41 | /// assert_eq!(tags[1].platform, "manylinux2014_x86_64"); 42 | /// 43 | /// ``` 44 | pub fn from_compound_string(s: &str) -> Result, String> { 45 | let Some((interpreter, abi, platform)) = 46 | s.split('-').map(ToOwned::to_owned).collect_tuple() 47 | else { 48 | return Err(String::from("not enough '-' separators")); 49 | }; 50 | 51 | Ok(interpreter 52 | .split('.') 53 | .cartesian_product(abi.split('.')) 54 | .cartesian_product(platform.split('.')) 55 | .map(|((interpreter, abi), platform)| Self { 56 | interpreter: interpreter.to_string(), 57 | abi: abi.to_string(), 58 | platform: platform.to_string(), 59 | }) 60 | .collect()) 61 | } 62 | } 63 | 64 | impl FromStr for WheelTag { 65 | type Err = String; 66 | 67 | fn from_str(s: &str) -> Result { 68 | let Some((interpreter, abi, platform)) = 69 | s.split('-').map(ToOwned::to_owned).collect_tuple() 70 | else { 71 | return Err(String::from("not enough '-' separators")); 72 | }; 73 | Ok(Self { 74 | interpreter, 75 | abi, 76 | platform, 77 | }) 78 | } 79 | } 80 | 81 | impl Display for WheelTag { 82 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 83 | write!(f, "{}-{}-{}", &self.interpreter, &self.abi, &self.platform) 84 | } 85 | } 86 | 87 | /// Contains an ordered set of platform tags with which compatibility of wheels can be determined. 88 | #[derive(Debug, Clone)] 89 | pub struct WheelTags { 90 | tags: IndexSet, 91 | } 92 | 93 | impl WheelTags { 94 | /// Returns an iterator over the supported tags. 95 | pub fn tags(&self) -> impl Iterator + '_ { 96 | self.tags.iter() 97 | } 98 | 99 | /// Determines the compatibility of the specified tag with the tags in this instance. Returns 100 | /// `None` if the specified tag is not compatible with any of the tags in this instance. Returns 101 | /// `Some(i)` where `i` indicates the compatibility level. The higher the number the more 102 | /// specific the tag is to the platform. The wheel artifact with the highest number should be 103 | /// preferred over others. 104 | pub fn compatibility(&self, tag: &WheelTag) -> Option { 105 | self.tags.get_index_of(tag).map(|score| -(score as i32)) 106 | } 107 | 108 | /// Returns if the specified tag is compatible with this set. 109 | pub fn is_compatible(&self, tag: &WheelTag) -> bool { 110 | self.tags.contains(tag) 111 | } 112 | } 113 | 114 | impl FromIterator for WheelTags { 115 | fn from_iter>(iter: T) -> Self { 116 | Self { 117 | tags: FromIterator::from_iter(iter), 118 | } 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod test { 124 | use super::*; 125 | 126 | #[test] 127 | fn test_from_str() { 128 | let tag = WheelTag::from_str("py2-none-any").unwrap(); 129 | assert_eq!(tag.interpreter, "py2"); 130 | assert_eq!(tag.abi, "none"); 131 | assert_eq!(tag.platform, "any"); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/python_env/tags/platform_tags.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | import platform 4 | 5 | if sys.version_info < (3, 6): 6 | print( 7 | '"could not determine compatible interpreter tags, the python version is too old. ' 8 | 'Requires at least 3.6, but currently running %s"' % platform.python_version() 9 | ) 10 | exit(0) 11 | 12 | # The implementation has the packaging module vendored 13 | from packaging.tags import sys_tags 14 | 15 | json.dump([(tag.interpreter, tag.abi, tag.platform) for tag in sys_tags()], sys.stdout) 16 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/resolve/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the [`resolve`] function which is used 2 | //! to make the PyPI ecosystem compatible with the [`resolvo`] crate. 3 | //! 4 | //! To use this enable the `resolve` feature. 5 | //! Note that this module can also serve an example to integrate an alternate packaging system 6 | //! with [`resolvo`]. 7 | //! 8 | //! See the `rip_bin` crate for an example of how to use the [`resolve`] function in the: [RIP Repo](https://github.com/prefix-dev/rip) 9 | //! 10 | 11 | mod dependency_provider; 12 | mod pypi_version_types; 13 | mod solve; 14 | pub mod solve_options; 15 | mod solve_types; 16 | 17 | pub use pypi_version_types::PypiVersion; 18 | pub use pypi_version_types::PypiVersionSet; 19 | pub use solve::{resolve, PinnedPackage}; 20 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/resolve/solve_types.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/types/artifact.rs: -------------------------------------------------------------------------------- 1 | use super::artifact_name::InnerAsArtifactName; 2 | use crate::resolve::PypiVersion; 3 | use crate::types::SourceArtifactName; 4 | use crate::utils::ReadAndSeek; 5 | use std::path::Path; 6 | 7 | /// Trait to implement if it is a type that has an [`super::artifact_name::ArtifactName`] 8 | /// this is then used by the [`crate::index::PackageDb`] to make a difference 9 | /// between the different types of artifacts. 10 | pub trait HasArtifactName { 11 | /// The name of the artifact which describes the artifact. 12 | /// 13 | /// Artifacts are describes by a string. [`super::artifact_name::ArtifactName`] describes the 14 | /// general format. 15 | type Name: Clone + InnerAsArtifactName; 16 | 17 | /// Returns the name of this instance 18 | fn name(&self) -> &Self::Name; 19 | } 20 | 21 | /// Trait that represents an artifact type in the PyPI ecosystem. 22 | /// That is a single file like a wheel, sdist. 23 | pub trait ArtifactFromBytes: HasArtifactName + Sized { 24 | /// Construct a new artifact from the given bytes 25 | fn from_bytes(name: Self::Name, bytes: Box) -> miette::Result; 26 | } 27 | 28 | /// Error while reading pyproject.toml 29 | #[derive(thiserror::Error, Debug)] 30 | #[allow(missing_docs)] 31 | pub enum ReadPyProjectError { 32 | #[error("IO error while reading pyproject.toml: {0}")] 33 | Io(#[from] std::io::Error), 34 | 35 | #[error("No pyproject.toml found in archive")] 36 | NoPyProjectTomlFound, 37 | 38 | #[error("Could not parse pyproject.toml")] 39 | PyProjectTomlParseError(String), 40 | } 41 | 42 | /// SDist or STree act as a SourceArtifact 43 | /// so we can use it in methods where we expect sdist 44 | /// to extract metadata 45 | pub trait ArtifactFromSource: HasArtifactName + Sync { 46 | /// get bytes of an artifact 47 | /// that will we be used for hashing 48 | fn try_get_bytes(&self) -> Result, std::io::Error>; 49 | 50 | /// Distribution Name 51 | fn distribution_name(&self) -> String; 52 | 53 | /// Version ( URL or Version ) 54 | fn version(&self) -> PypiVersion; 55 | 56 | /// Source artifact name 57 | fn artifact_name(&self) -> SourceArtifactName; 58 | 59 | /// Read the build system info from the pyproject.toml 60 | fn read_pyproject_toml(&self) -> Result; 61 | 62 | /// extract to a specific location 63 | /// for sdist we unpack it 64 | /// for stree we move it 65 | /// as example this method is used by install_build_files 66 | fn extract_to(&self, work_dir: &Path) -> std::io::Result<()>; 67 | } 68 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/types/direct_url_json.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use url::Url; 3 | 4 | /// Specifies the PyPa `direct_url.json` format. 5 | /// See: 6 | /// 7 | #[derive(Debug, Serialize, Deserialize)] 8 | #[serde_with::skip_serializing_none] 9 | pub struct DirectUrlJson { 10 | /// Url to the source. 11 | pub url: Url, 12 | /// Information about the source. 13 | #[serde(flatten)] 14 | pub source: DirectUrlSource, 15 | } 16 | 17 | /// Specifies the source of a direct url. 18 | /// 19 | /// currently we do not support the deprecated `hash` field 20 | #[derive(Debug, Serialize, Deserialize)] 21 | pub enum DirectUrlSource { 22 | #[serde(rename = "archive_info")] 23 | /// Information about the archive file. 24 | Archive { 25 | /// Hashes of the archive file. 26 | hashes: Option, 27 | }, 28 | /// Information about a source from a VCS directly 29 | #[serde(rename = "vcs_info")] 30 | Vcs { 31 | /// The VCS used 32 | vcs: DirectUrlVcs, 33 | /// Revision of the source 34 | requested_revision: Option, 35 | /// Actual commit 36 | commit_id: String, 37 | }, 38 | /// Information about a local directory source 39 | #[serde(rename = "dir_info")] 40 | Dir { 41 | /// Is this a editable source 42 | /// See: 43 | editable: Option, 44 | }, 45 | } 46 | 47 | /// Hashes for internal archive files. 48 | /// multiple hashes can be included but per recommendation only sha256 should be used. 49 | #[derive(Debug, Serialize, Deserialize)] 50 | pub struct DirectUrlHashes { 51 | /// Sha256 hash of the archive file. 52 | pub sha256: String, 53 | } 54 | 55 | /// Name of the VCS in a DirectUrlSource 56 | #[derive(Debug, Serialize, Deserialize)] 57 | #[allow(missing_docs)] 58 | pub enum DirectUrlVcs { 59 | #[serde(rename = "git")] 60 | Git, 61 | #[serde(rename = "svn")] 62 | Svn, 63 | #[serde(rename = "bzr")] 64 | Bazaar, 65 | #[serde(rename = "hg")] 66 | Mercurial, 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use crate::types::direct_url_json::DirectUrlJson; 72 | 73 | /// Tests if json outputs aligns with the examples at: 74 | /// https://packaging.python.org/en/latest/specifications/direct-url-data-structure/ 75 | /// try to parse the example cases from there 76 | #[test] 77 | pub fn test_examples_pypa() { 78 | // Source archive: 79 | let example = r#" 80 | { 81 | "url": "https://github.com/pypa/pip/archive/1.3.1.zip", 82 | "archive_info": { 83 | "hashes": { 84 | "sha256": "2dc6b5a470a1bde68946f263f1af1515a2574a150a30d6ce02c6ff742fcc0db8" 85 | } 86 | } 87 | } 88 | "#; 89 | serde_json::from_str::(example).unwrap(); 90 | 91 | // Git URL with tag and commit-hash: 92 | let example = r#" 93 | { 94 | "url": "https://github.com/pypa/pip.git", 95 | "vcs_info": { 96 | "vcs": "git", 97 | "requested_revision": "1.3.1", 98 | "commit_id": "7921be1537eac1e97bc40179a57f0349c2aee67d" 99 | } 100 | } 101 | "#; 102 | serde_json::from_str::(example).unwrap(); 103 | 104 | // Local directory: 105 | let example = r#" 106 | { 107 | "url": "file:///home/user/project", 108 | "dir_info": {} 109 | } 110 | "#; 111 | serde_json::from_str::(example).unwrap(); 112 | 113 | // Local directory in editable mode: 114 | let example = r#" 115 | { 116 | "url": "file:///home/user/project", 117 | "dir_info": { 118 | "editable": true 119 | } 120 | } 121 | "#; 122 | serde_json::from_str::(example).unwrap(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/types/extra.rs: -------------------------------------------------------------------------------- 1 | // Implementation comes from https://github.com/njsmith/posy/blob/main/src/vocab/extra.rs 2 | // Licensed under MIT or Apache-2.0 3 | 4 | // 'Extra' string format is not well specified. It looks like what poetry does is simply normalize 5 | // the name and be done with it. 6 | // 7 | // PEP 508's grammar for requirement specifiers says that extras have to 8 | // be "identifiers", which means: first char [A-Za-z0-9], remaining chars also 9 | // allowed to include '-_.'. But in practice we've seen Extras like "ssl:sys_platform=='win32'" 10 | // which do not follow that rule at all. 11 | 12 | // ORIGINAL comment from Posy. 13 | 14 | // 'Extra' string format is not well specified. It looks like what pip does is 15 | // run things through pkg_resources.safe_extra, which does: 16 | // 17 | // re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() 18 | // 19 | // So A-Z becomes a-z, a-z 0-9 . - are preserved, and any contiguous run of 20 | // other characters becomes a single _. 21 | // 22 | // OTOH, PEP 508's grammar for requirement specifiers says that extras have to 23 | // be "identifiers", which means: first char [A-Za-z0-9], remaining chars also 24 | // allowed to include -_. 25 | // 26 | // I guess for now I'll just pretend that they act the same as package names, 27 | // and see how long I can get away with it. 28 | // 29 | // There's probably a better way to factor this and reduce code duplication... 30 | 31 | use miette::Diagnostic; 32 | use serde::{Serialize, Serializer}; 33 | use serde_with::DeserializeFromStr; 34 | use std::borrow::Borrow; 35 | use std::cmp::Ordering; 36 | use std::hash::{Hash, Hasher}; 37 | use std::str::FromStr; 38 | use thiserror::Error; 39 | 40 | #[derive(Debug, Clone, Eq, DeserializeFromStr)] 41 | /// Structure that holds both the source string and the normalized version of an extra. 42 | pub struct Extra { 43 | /// The original string this instance was created from 44 | source: Box, 45 | 46 | /// The normalized version of `source`. 47 | normalized: Box, 48 | } 49 | 50 | impl Extra { 51 | /// Returns the source representation of the name. This is the string from which this 52 | /// instance was created. 53 | pub fn as_source_str(&self) -> &str { 54 | self.source.as_ref() 55 | } 56 | 57 | /// Returns the normalized version of the name. The normalized string is guaranteed to 58 | /// be a valid python package name. 59 | pub fn as_str(&self) -> &str { 60 | self.normalized.as_ref() 61 | } 62 | } 63 | 64 | #[derive(Debug, Clone, Error, Diagnostic)] 65 | pub enum ParseExtraError {} 66 | 67 | impl FromStr for Extra { 68 | type Err = ParseExtraError; 69 | 70 | fn from_str(s: &str) -> Result { 71 | // https://www.python.org/dev/peps/pep-0503/#normalized-names 72 | let mut normalized = s.replace(['-', '_', '.'], "-"); 73 | normalized.make_ascii_lowercase(); 74 | 75 | Ok(Self { 76 | source: s.to_owned().into_boxed_str(), 77 | normalized: normalized.into_boxed_str(), 78 | }) 79 | } 80 | } 81 | 82 | impl Hash for Extra { 83 | fn hash(&self, state: &mut H) { 84 | self.normalized.hash(state) 85 | } 86 | } 87 | 88 | impl PartialEq for Extra { 89 | fn eq(&self, other: &Self) -> bool { 90 | self.normalized.eq(&other.normalized) 91 | } 92 | } 93 | 94 | impl PartialOrd for Extra { 95 | fn partial_cmp(&self, other: &Self) -> Option { 96 | Some(self.cmp(other)) 97 | } 98 | } 99 | 100 | impl Ord for Extra { 101 | fn cmp(&self, other: &Self) -> Ordering { 102 | self.normalized.cmp(&other.normalized) 103 | } 104 | } 105 | 106 | impl Serialize for Extra { 107 | fn serialize(&self, serializer: S) -> Result 108 | where 109 | S: Serializer, 110 | { 111 | self.source.as_ref().serialize(serializer) 112 | } 113 | } 114 | 115 | impl Borrow for Extra { 116 | fn borrow(&self) -> &str { 117 | self.normalized.as_ref() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains all the types for working with PyPA packaging repositories. 2 | //! We have tried to follow the PEP's and PyPA packaging guide as closely as possible. 3 | mod artifact; 4 | 5 | mod artifact_name; 6 | 7 | mod package_name; 8 | 9 | mod core_metadata; 10 | 11 | mod record; 12 | 13 | mod extra; 14 | 15 | mod entry_points; 16 | 17 | mod project_info; 18 | 19 | mod direct_url_json; 20 | mod rfc822ish; 21 | 22 | pub use artifact::{ArtifactFromBytes, ArtifactFromSource, HasArtifactName, ReadPyProjectError}; 23 | 24 | pub use artifact_name::{ 25 | ArtifactName, ArtifactType, BuildTag, InnerAsArtifactName, ParseArtifactNameError, 26 | SDistFilename, SDistFormat, STreeFilename, SourceArtifactName, WheelFilename, 27 | }; 28 | 29 | pub use direct_url_json::{DirectUrlHashes, DirectUrlJson, DirectUrlSource, DirectUrlVcs}; 30 | 31 | pub use core_metadata::{MetadataVersion, PackageInfo, WheelCoreMetaDataError, WheelCoreMetadata}; 32 | 33 | pub use record::{Record, RecordEntry}; 34 | 35 | pub use package_name::{NormalizedPackageName, PackageName, ParsePackageNameError}; 36 | 37 | pub use extra::Extra; 38 | 39 | pub use entry_points::{EntryPoint, ParseEntryPointError}; 40 | 41 | pub use project_info::{ArtifactHashes, ArtifactInfo, DistInfoMetadata, Meta, ProjectInfo, Yanked}; 42 | 43 | pub(crate) use rfc822ish::RFC822ish; 44 | 45 | pub use pep440_rs::*; 46 | pub use pep508_rs::*; 47 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/types/record.rs: -------------------------------------------------------------------------------- 1 | //! Defines the [`Record`] struct which holds the information stored in a `RECORD` file which is 2 | //! found in a wheel archive or installation. 3 | 4 | use itertools::Itertools; 5 | use serde::{Deserialize, Serialize}; 6 | use std::io::Read; 7 | use std::path::Path; 8 | 9 | /// Represents the RECORD file found in a wheels .dist-info folder. 10 | /// 11 | /// See for more information about the format. 12 | #[derive(Debug, Clone)] 13 | pub struct Record { 14 | entries: Vec, 15 | } 16 | 17 | /// A single entry in a `RECORD` file 18 | #[derive(Debug, Deserialize, Serialize, PartialOrd, PartialEq, Ord, Eq, Clone)] 19 | pub struct RecordEntry { 20 | /// The path relative to the root of the environment or archive 21 | pub path: String, 22 | 23 | /// The hash of the file. Usually this is a Sha256 hash. 24 | pub hash: Option, 25 | 26 | /// The size of the file in bytes. 27 | pub size: Option, 28 | } 29 | 30 | impl Record { 31 | /// Reads the contents of a `RECORD` file from disk. 32 | pub fn from_path(path: &Path) -> csv::Result { 33 | Self::from_reader(fs_err::File::open(path)?) 34 | } 35 | 36 | /// Reads the contents of a `RECORD` file from a reader. 37 | pub fn from_reader(reader: impl Read) -> csv::Result { 38 | Ok(Self { 39 | entries: csv::ReaderBuilder::new() 40 | .has_headers(false) 41 | .escape(Some(b'"')) 42 | .from_reader(reader) 43 | .deserialize() 44 | .collect::, csv::Error>>()?, 45 | }) 46 | } 47 | 48 | /// Write to a `RECORD` file on disk 49 | pub fn write_to_path(&self, path: &Path) -> csv::Result<()> { 50 | let mut record_writer = csv::WriterBuilder::new() 51 | .has_headers(false) 52 | .escape(b'"') 53 | .from_path(path)?; 54 | for entry in self.entries.iter().sorted() { 55 | record_writer.serialize(entry)?; 56 | } 57 | Ok(()) 58 | } 59 | 60 | /// Returns an iterator over the entries in this instance. 61 | pub fn iter(&self) -> std::slice::Iter { 62 | self.entries.iter() 63 | } 64 | } 65 | 66 | impl IntoIterator for Record { 67 | type Item = RecordEntry; 68 | type IntoIter = std::vec::IntoIter; 69 | 70 | fn into_iter(self) -> Self::IntoIter { 71 | self.entries.into_iter() 72 | } 73 | } 74 | 75 | impl FromIterator for Record { 76 | fn from_iter>(iter: T) -> Self { 77 | Self { 78 | entries: FromIterator::from_iter(iter), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/types/rfc822ish.rs: -------------------------------------------------------------------------------- 1 | // Implementation comes from https://github.com/njsmith/posy/blob/main/src/vocab/rfc822ish.rs 2 | // Licensed under MIT or Apache-2.0 3 | use std::collections::HashMap; 4 | use std::str::FromStr; 5 | 6 | pub type Fields = HashMap>; 7 | 8 | #[cfg_attr(test, derive(Debug, serde::Deserialize, PartialEq, Eq))] 9 | pub struct RFC822ish { 10 | pub fields: Fields, 11 | pub body: Option, 12 | } 13 | 14 | impl RFC822ish { 15 | pub fn take_all(&mut self, key: &str) -> Vec { 16 | match self.fields.remove(&key.to_ascii_lowercase()) { 17 | Some(vec) => vec, 18 | None => Vec::new(), 19 | } 20 | } 21 | 22 | pub fn maybe_take(&mut self, key: &str) -> miette::Result> { 23 | let mut values = self.take_all(key); 24 | match values.len() { 25 | 0 => Ok(None), 26 | 1 => Ok(values.pop()), 27 | _ => miette::bail!("multiple values for singleton key {}", key), 28 | } 29 | } 30 | 31 | pub fn take(&mut self, key: &str) -> miette::Result { 32 | match self.maybe_take(key)? { 33 | Some(result) => Ok(result), 34 | None => miette::bail!("can't find required key {}", key), 35 | } 36 | } 37 | } 38 | 39 | impl FromStr for RFC822ish { 40 | type Err = peg::error::ParseError; 41 | 42 | fn from_str(s: &str) -> Result { 43 | rfc822ish_parser::rfc822ish(s) 44 | } 45 | } 46 | 47 | // Allegedly, a METADATA file is formatted as an RFC822 email message. 48 | // This is absolutely not true. The actual format is "whatever 49 | // the Python stdlib module email.parser does". To probe its behavior, a 50 | // convenient entry point is 'email.message_from_string'. 51 | // 52 | // Overall structure: A series of header lines, then an empty line, then 53 | // the "message body" (= description field, in modern PKG-INFO/METADATA 54 | // files). 55 | // 56 | // email.parser module is also extremely lenient of errors. We'll try to be a 57 | // bit more strict -- we try to be lenient of mangled utf-8, because obviously 58 | // someone must have messed that up in the history of PyPI, and aren't picky 59 | // about stuff like trailing newlines. But we fail on oddities like an empty 60 | // field name or a continuation line at the start of input, where email.parser 61 | // would keep on trucking. Fingers crossed that it works out. 62 | peg::parser! { 63 | grammar rfc822ish_parser() for str { 64 | // In real RFC822, only CRLF is legal. email.parser is more lenient. 65 | rule line_ending() 66 | = quiet!{"\r\n" / "\r" / "\n"} 67 | / expected!("end of line") 68 | 69 | rule field_name() -> &'input str 70 | = quiet!{$(['\x21'..='\x39' | '\x3b'..='\x7e']+)} 71 | / expected!("field name") 72 | 73 | // email.parser drops any " \t" after the colon, but preserves other 74 | // whitespace in the field value. 75 | rule field_separator() 76 | = ":" [' ' | '\t']* 77 | 78 | rule field_value_piece() 79 | = [^ '\r' | '\n']* 80 | 81 | rule continuation_line_ending() 82 | = quiet!{line_ending() [' ' | '\t']} / expected!("continuation line") 83 | 84 | // In real RFC822, continuation lines are folded together into a 85 | // single line, removing the newline characters. email.parser doesn't 86 | // do that though -- continuation lines just get embedded newlines. 87 | // (But you don't include any *trailing* newlines. Those are 88 | // discarded.) 89 | rule field_value() -> &'input str 90 | = $(field_value_piece() ** continuation_line_ending()) 91 | 92 | rule field() -> (String, String) 93 | = n:field_name() field_separator() v:field_value() 94 | { (n.to_ascii_lowercase(), v.to_owned()) } 95 | 96 | rule fields() -> Vec<(String, String)> 97 | = field() ** line_ending() 98 | 99 | // I think in real RFC822, the body is mandatory? But in early 100 | // versions of the metadata spec, PKG-INFO/METADATA files didn't have 101 | // a body, and email.parser don't care, it does what it wants. 102 | rule trailing_body() -> String 103 | = line_ending() line_ending() b:$([_]*) { b.to_owned() } 104 | 105 | // The extra line_ending() is to handle the case where there's 106 | // no trailing body, and exactly one line ending at EOF. If 107 | // trailing_body matches then the input will be fully consumed by 108 | // then; if not, then we might have a stray trailing newline to 109 | // absorb. 110 | pub rule rfc822ish() -> RFC822ish 111 | = f:fields() body:(trailing_body()?) line_ending()? 112 | { 113 | let mut fields = Fields::new(); 114 | for (name, value) in f { 115 | fields.entry(name).or_default().push(value) 116 | }; 117 | RFC822ish { fields, body, } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod read_and_seek; 2 | mod streaming_or_local; 3 | 4 | mod seek_slice; 5 | #[cfg(test)] 6 | mod test; 7 | #[cfg(test)] 8 | pub use test::{get_package_db, setup}; 9 | 10 | use std::path::{Component, Path, PathBuf}; 11 | 12 | use include_dir::{include_dir, Dir}; 13 | use url::Url; 14 | 15 | pub use read_and_seek::ReadAndSeek; 16 | pub use streaming_or_local::StreamingOrLocal; 17 | 18 | pub use seek_slice::SeekSlice; 19 | 20 | /// Keep retrying a certain IO function until it either succeeds or until it doesn't return 21 | /// [`std::io::ErrorKind::Interrupted`]. 22 | pub fn retry_interrupted(mut f: F) -> std::io::Result 23 | where 24 | F: FnMut() -> std::io::Result, 25 | { 26 | loop { 27 | match f() { 28 | Ok(result) => return Ok(result), 29 | Err(err) if err.kind() != std::io::ErrorKind::Interrupted => { 30 | return Err(err); 31 | } 32 | _ => { 33 | // Otherwise keep looping! 34 | } 35 | } 36 | } 37 | } 38 | 39 | /// Normalize url according to pip standards 40 | pub fn normalize_index_url(mut url: Url) -> Url { 41 | let path = url.path(); 42 | if !path.ends_with('/') { 43 | url.set_path(&format!("{path}/")); 44 | } 45 | url 46 | } 47 | 48 | pub(crate) static VENDORED_PACKAGING_DIR: Dir<'_> = 49 | include_dir!("$CARGO_MANIFEST_DIR/vendor/packaging/"); 50 | 51 | /// Normalize path (remove .. and . components) 52 | pub(crate) fn normalize_path(path: &Path) -> PathBuf { 53 | let mut normalized = PathBuf::new(); 54 | 55 | for component in path.components() { 56 | match component { 57 | Component::ParentDir => { 58 | normalized.pop(); 59 | } 60 | Component::CurDir => {} // Do nothing for current directory (.) 61 | _ => normalized.push(component.as_os_str()), 62 | } 63 | } 64 | 65 | normalized 66 | } 67 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/utils/read_and_seek.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek}; 2 | 3 | /// Defines that a type can be read and seeked. This trait has a blanket implementation for any type 4 | /// that implements both [`Read`] and [`Seek`]. 5 | pub trait ReadAndSeek: Read + Seek {} 6 | 7 | impl ReadAndSeek for T where T: Read + Seek {} 8 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/utils/seek_slice.rs: -------------------------------------------------------------------------------- 1 | // Implementation comes from https://github.com/njsmith/posy/blob/main/src/seek_slice.rs 2 | // Licensed under MIT or Apache-2.0 3 | 4 | use std::io; 5 | use std::io::{Read, Seek, SeekFrom}; 6 | 7 | pub struct SeekSlice { 8 | inner: T, 9 | start: u64, 10 | end: u64, 11 | current: u64, 12 | } 13 | 14 | impl SeekSlice { 15 | pub fn new(mut inner: T, start: u64, end: u64) -> io::Result> { 16 | assert!(end >= start); 17 | // initialize current position to something sensible 18 | let current = inner.seek(SeekFrom::Start(start))?; 19 | Ok(SeekSlice { 20 | inner, 21 | start, 22 | end, 23 | current, 24 | }) 25 | } 26 | } 27 | 28 | impl Seek for SeekSlice { 29 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 30 | let maybe_goal_idx = match pos { 31 | SeekFrom::Start(amount) => self.start.checked_add(amount), 32 | SeekFrom::End(amount) => self.end.checked_add_signed(amount), 33 | SeekFrom::Current(amount) => self.current.checked_add_signed(amount), 34 | }; 35 | match maybe_goal_idx { 36 | Some(goal_idx) => { 37 | if goal_idx < self.start || goal_idx > self.end { 38 | Err(io::Error::new( 39 | io::ErrorKind::InvalidInput, 40 | format!("invalid seek to a negative or overflowing position (goal: {}, start: {}, end: {})", goal_idx, self.start, self.end), 41 | )) 42 | } else { 43 | self.current = self.inner.seek(SeekFrom::Start(goal_idx))?; 44 | Ok(self.current.checked_sub(self.start).unwrap()) 45 | } 46 | } 47 | None => Err(io::Error::new( 48 | io::ErrorKind::InvalidInput, 49 | "integer overflow while seeking", 50 | )), 51 | } 52 | } 53 | } 54 | 55 | impl Read for SeekSlice { 56 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 57 | let max_read: usize = (self.end - self.current).try_into().unwrap_or(usize::MAX); 58 | let read_size = std::cmp::min(max_read, buf.len()); 59 | let amount = self.inner.read(&mut buf[..read_size])?; 60 | self.current += amount as u64; 61 | Ok(amount) 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod test { 67 | use super::*; 68 | use std::io::Cursor; 69 | 70 | #[test] 71 | fn test_seek_slice() { 72 | let buf: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 73 | let mut cursor = Cursor::new(&buf); 74 | let mut slice = SeekSlice::new(&mut cursor, 2, 8).unwrap(); 75 | // starts at offset zero 76 | assert_eq!(slice.stream_position().unwrap(), 0); 77 | // reading advances position as expected 78 | fn next_byte(value: T) -> u8 { 79 | value.bytes().next().unwrap().unwrap() 80 | } 81 | assert_eq!(next_byte(&mut slice), 2u8); 82 | assert_eq!(next_byte(&mut slice), 3u8); 83 | assert_eq!(slice.stream_position().unwrap(), 2); 84 | assert_eq!(next_byte(&mut slice), 4u8); 85 | 86 | // out of range seeks caught and have no effect 87 | assert!(slice.seek(SeekFrom::Current(-10)).is_err()); 88 | assert!(slice.seek(SeekFrom::Current(10)).is_err()); 89 | assert_eq!(next_byte(&mut slice), 5u8); 90 | 91 | assert_eq!(slice.seek(SeekFrom::Start(1)).unwrap(), 1); 92 | assert_eq!(next_byte(&mut slice), 3u8); 93 | 94 | assert_eq!(slice.seek(SeekFrom::End(-1)).unwrap(), 5); 95 | assert_eq!(next_byte(&mut slice), 7u8); 96 | assert!(slice.bytes().next().is_none()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/utils/streaming_or_local.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::ReadAndSeek; 2 | use futures::TryFutureExt; 3 | use std::{ 4 | io, 5 | io::{Read, Seek, Write}, 6 | }; 7 | use tempfile::SpooledTempFile; 8 | use tokio::io::{AsyncRead, AsyncReadExt}; 9 | use tokio::task::JoinError; 10 | 11 | /// Represents a stream of data that is either coming in asynchronously from a remote source or from 12 | /// a synchronous location (like the filesystem). 13 | /// 14 | /// It is often useful to make this distinction because reading from a remote source is often slower 15 | /// than reading synchronously (from disk or memory). 16 | pub enum StreamingOrLocal { 17 | /// Represents an asynchronous stream of data. 18 | Streaming(Box), 19 | 20 | /// Represents a synchronous stream of data. 21 | Local(Box), 22 | } 23 | 24 | impl StreamingOrLocal { 25 | /// Stream in the contents of the stream and make sure we have a fast locally accessible stream. 26 | /// 27 | /// If the stream is already local this will simply return that stream. If however the file is 28 | /// remote it will first be read to a temporary spooled file. 29 | pub async fn into_local(self) -> io::Result> { 30 | match self { 31 | StreamingOrLocal::Streaming(mut stream) => { 32 | // Create a [`SpooledTempFile`] which is a blob of memory that is kept in memory if 33 | // it does not grow beyond 5MB, otherwise it is written to disk. 34 | let mut local_file = SpooledTempFile::new(5 * 1024 * 1024); 35 | 36 | // Stream in the bytes and copy them to the temporary file. 37 | let mut buf = [0u8; 1024 * 8]; 38 | loop { 39 | let bytes_read = stream.read(&mut buf).await?; 40 | if bytes_read == 0 { 41 | break; 42 | } 43 | local_file.write_all(&buf[..bytes_read])?; 44 | } 45 | 46 | // Restart the file from the start so we can start reading from it. 47 | local_file.rewind()?; 48 | 49 | Ok(Box::new(local_file)) 50 | } 51 | StreamingOrLocal::Local(stream) => Ok(stream), 52 | } 53 | } 54 | 55 | /// Asynchronously read the contents of the stream into a vector of bytes. 56 | pub async fn read_to_end(self, bytes: &mut Vec) -> std::io::Result { 57 | match self { 58 | StreamingOrLocal::Streaming(mut streaming) => streaming.read_to_end(bytes).await, 59 | StreamingOrLocal::Local(mut local) => { 60 | let read_to_end = move || { 61 | let mut bytes = Vec::new(); 62 | local.read_to_end(&mut bytes).map(|_| bytes) 63 | }; 64 | 65 | match tokio::task::spawn_blocking(read_to_end) 66 | .map_err(JoinError::try_into_panic) 67 | .await 68 | { 69 | Ok(Ok(result)) => { 70 | *bytes = result; 71 | Ok(bytes.len()) 72 | } 73 | Ok(Err(err)) => Err(err), 74 | // Resume the panic on the main task 75 | Err(Ok(panic)) => std::panic::resume_unwind(panic), 76 | Err(Err(_)) => Err(io::ErrorKind::Interrupted.into()), 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/utils/test.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use reqwest::Client; 4 | use reqwest_middleware::ClientWithMiddleware; 5 | use tempfile::TempDir; 6 | 7 | use crate::{ 8 | index::{PackageDb, PackageSourcesBuilder}, 9 | python_env::Pep508EnvMakers, 10 | resolve::solve_options::ResolveOptions, 11 | wheel_builder::WheelBuilder, 12 | }; 13 | 14 | pub fn get_package_db() -> (Arc, TempDir) { 15 | let tempdir = tempfile::tempdir().unwrap(); 16 | let client = ClientWithMiddleware::from(Client::new()); 17 | 18 | let url = url::Url::parse("https://pypi.org/simple/").unwrap(); 19 | let sources = PackageSourcesBuilder::new(url).build().unwrap(); 20 | 21 | ( 22 | Arc::new(PackageDb::new(sources, client, tempdir.path(), Default::default()).unwrap()), 23 | tempdir, 24 | ) 25 | } 26 | 27 | // Setup the test environment 28 | pub async fn setup(resolve_options: ResolveOptions) -> (Arc, TempDir) { 29 | let (package_db, tempdir) = get_package_db(); 30 | let env_markers = Arc::new(Pep508EnvMakers::from_env().await.unwrap().0); 31 | 32 | ( 33 | WheelBuilder::new( 34 | package_db.clone(), 35 | env_markers.clone(), 36 | None, 37 | resolve_options, 38 | ) 39 | .unwrap(), 40 | tempdir, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/wheel_builder/error.rs: -------------------------------------------------------------------------------- 1 | use crate::install::InstallError; 2 | use crate::python_env::VEnvError; 3 | use crate::types::{ParseArtifactNameError, WheelCoreMetaDataError}; 4 | use crate::wheel_builder::wheel_cache; 5 | use pep508_rs::Requirement; 6 | use std::path::PathBuf; 7 | 8 | /// An error that can occur while building a wheel 9 | #[allow(missing_docs)] 10 | #[derive(thiserror::Error, Debug)] 11 | pub enum WheelBuildError { 12 | #[error("could not build wheel: {0}")] 13 | Error(String), 14 | 15 | #[error("could not install artifact in virtual environment: {0}")] 16 | UnpackError(#[from] InstallError), 17 | 18 | #[error("could not build wheel: {0}")] 19 | IoError(#[from] std::io::Error), 20 | 21 | #[error("could not run command {0} to build wheel: {1}")] 22 | CouldNotRunCommand(String, std::io::Error), 23 | 24 | #[error("could not resolve environment for wheel building: {1:?}")] 25 | CouldNotResolveEnvironment(Vec, miette::Report), 26 | 27 | #[error("error parsing JSON from extra_requirements.json: {0}")] 28 | JSONError(#[from] serde_json::Error), 29 | 30 | #[error("could not parse generated wheel metadata: {0}")] 31 | WheelCoreMetadataError(#[from] WheelCoreMetaDataError), 32 | 33 | #[error("could not get artifact: {0}")] 34 | CouldNotGetArtifact(miette::Report), 35 | 36 | #[error("could not get artifact from cache: {0}")] 37 | CacheError(#[from] wheel_cache::WheelCacheError), 38 | 39 | #[error("error parsing artifact name: {0}")] 40 | ArtifactError(#[from] ParseArtifactNameError), 41 | 42 | #[error("error creating venv: {0}")] 43 | VEnvError(#[from] VEnvError), 44 | 45 | #[error("backend path in pyproject.toml not relative: {0}")] 46 | BackendPathNotRelative(PathBuf), 47 | 48 | #[error( 49 | "backend path in pyproject.toml not resolving to a path in the package directory: {0}" 50 | )] 51 | BackendPathNotInPackageDir(PathBuf), 52 | 53 | #[error("could not join path: {0}")] 54 | CouldNotJoinPath(#[from] std::env::JoinPathsError), 55 | } 56 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/wheel_builder/wheel_builder_frontend.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from sys import exit 4 | from pathlib import Path 5 | from importlib import import_module 6 | from json import loads 7 | from types import ModuleType 8 | import json 9 | 10 | ################################################################ 11 | # Begin janky attempt to workaround 12 | # https://github.com/pypa/setuptools/issues/3786 13 | ################################################################ 14 | 15 | try: 16 | import sysconfig 17 | import distutils.sysconfig 18 | 19 | 20 | def get_python_inc(plat_specific=0, prefix=None): 21 | if plat_specific: 22 | return sysconfig.get_path("platinclude") 23 | else: 24 | return sysconfig.get_path("include") 25 | 26 | 27 | distutils.sysconfig.get_python_inc = get_python_inc 28 | except ImportError: 29 | # we ignore missing distutils (was removed in Python 3.12) 30 | pass 31 | 32 | ################################################################ 33 | # End janky workaround 34 | ################################################################ 35 | 36 | def get_backend_from_entry_point(entrypoint: str) -> ModuleType: 37 | # https://packaging.python.org/en/latest/specifications/entry-points/ 38 | modname, qualname_separator, qualname = entrypoint.partition(":") 39 | backend = import_module(modname) 40 | if qualname_separator: 41 | for attr in qualname.split("."): 42 | backend = getattr(backend, attr) 43 | if backend is None: 44 | raise AttributeError(f"Attribute '{attr}' not found in '{modname}'") 45 | 46 | return backend 47 | 48 | 49 | def get_requires_for_build_wheel(backend: ModuleType, work_dir: Path) -> [str]: 50 | """ 51 | Returns a list of requirements. This is only necessary if we do not 52 | have a pyproject.toml file. 53 | """ 54 | f = getattr(backend, "get_requires_for_build_wheel") 55 | if f is None: 56 | result = [] 57 | else: 58 | result = f() 59 | 60 | j = json.dumps(result) 61 | out_json_file = work_dir / "extra_requirements.json" 62 | out_json_file.write_text(j) 63 | print(j) 64 | 65 | def metadata_dirs(work_dir: Path): 66 | return work_dir / "metadata" 67 | 68 | def prepare_metadata_for_build_wheel(backend: ModuleType, work_dir: Path): 69 | """ 70 | Prepare any files that need to be generated before building the wheel. 71 | """ 72 | if hasattr(backend, "prepare_metadata_for_build_wheel"): 73 | # Create an output file for the metadata 74 | result_file = work_dir / "metadata_result" 75 | 76 | # Create the metadata output directory 77 | d = metadata_dirs(work_dir) 78 | d.mkdir() 79 | dist_info = backend.prepare_metadata_for_build_wheel(str(d)) 80 | # Path to the dist-info directory 81 | result = str(d / dist_info) 82 | # Write the path to the dist-info directory to a file 83 | result_file.write_text(result) 84 | else: 85 | exit(50) 86 | 87 | def wheel_dirs(work_dir: Path): 88 | return work_dir / "wheel" 89 | 90 | def build_wheel(backend: ModuleType, work_dir: Path): 91 | """Take a folder with an SDist and build a wheel from it.""" 92 | wheel_dir = wheel_dirs(work_dir) 93 | result_file = work_dir / "wheel_result" 94 | 95 | # Use the metadata result if it exists, otherwise set this to None 96 | metadata_result = work_dir / "metadata_result" 97 | if metadata_result.exists(): 98 | metadata_dir = metadata_result.read_text().strip() 99 | else: 100 | metadata_dir = None 101 | 102 | wheel_dir.mkdir() 103 | wheel_basename = backend.build_wheel( 104 | str(wheel_dir), 105 | metadata_directory=metadata_dir, 106 | ) 107 | 108 | result_file.write_text(str(wheel_dir / wheel_basename)) 109 | 110 | if __name__ == "__main__": 111 | work_dir, entry_point, goal = sys.argv[1:] 112 | 113 | backend_path = os.environ.get("PEP517_BACKEND_PATH") 114 | if backend_path: 115 | # split the path into a list of paths 116 | extra_pathitems = backend_path.split(os.pathsep) 117 | sys.path[:0] = extra_pathitems 118 | 119 | backend = get_backend_from_entry_point(entry_point) 120 | 121 | work_dir = Path(work_dir) 122 | 123 | if goal == "GetRequiresForBuildWheel": 124 | get_requires_for_build_wheel(backend, work_dir) 125 | if goal == "WheelMetadata": 126 | prepare_metadata_for_build_wheel(backend, work_dir) 127 | elif goal == "Wheel": 128 | build_wheel(backend, work_dir) 129 | 130 | exit(0) 131 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/launcher.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the code to create a launcher executable for windows. 2 | 3 | use std::{ 4 | env, 5 | io::{Cursor, Write}, 6 | }; 7 | use zip::{write::FileOptions, ZipWriter}; 8 | 9 | /// Defines the type of script to run. This is either a GUI application or a console application. 10 | /// When running a console application a terminal is expected. When running a GUI application the 11 | /// user should not see a terminal. 12 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 13 | pub enum LauncherType { 14 | /// A GUI application, the application will not spawn a terminal/console. 15 | Gui, 16 | 17 | /// A console application, the application will be run inside a terminal/console. 18 | Console, 19 | } 20 | 21 | /// Defines the architecture of the launcher executable that is created for every entry point on 22 | /// windows. 23 | #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] 24 | pub enum WindowsLauncherArch { 25 | /// Create a launcher for the x86 architecture. 26 | X86, 27 | 28 | /// Create a launcher for the x86_64 architecture. 29 | X86_64, 30 | 31 | /// Create a launcher for the arm64 architecture. 32 | Arm64, 33 | } 34 | 35 | impl WindowsLauncherArch { 36 | /// Try determine the current architecture from the environment. Returns `None` if the 37 | /// architecture could not be determined or is not supported. 38 | pub fn current() -> Option { 39 | match env::consts::ARCH { 40 | "x86" => Some(Self::X86), 41 | "x86_64" => Some(Self::X86_64), 42 | "aarch64" => Some(Self::Arm64), 43 | _ => None, 44 | } 45 | } 46 | 47 | /// Returns the bytes of the launcher executable for this architecture. 48 | pub fn launcher_bytes(self, script_type: LauncherType) -> &'static [u8] { 49 | match (self, script_type) { 50 | (Self::X86, LauncherType::Console) => include_bytes!("./windows-launcher/t32.exe"), 51 | (Self::X86_64, LauncherType::Console) => include_bytes!("./windows-launcher/t64.exe"), 52 | (Self::Arm64, LauncherType::Console) => { 53 | include_bytes!("./windows-launcher/t64-arm.exe") 54 | } 55 | (Self::X86, LauncherType::Gui) => include_bytes!("./windows-launcher/w32.exe"), 56 | (Self::X86_64, LauncherType::Gui) => include_bytes!("./windows-launcher/w64.exe"), 57 | (Self::Arm64, LauncherType::Gui) => include_bytes!("./windows-launcher/w64-arm.exe"), 58 | } 59 | } 60 | } 61 | 62 | /// Constructs an executable that can be used to launch a python script on Windows. 63 | pub fn build_windows_launcher( 64 | shebang: &str, 65 | launcher_python_script: &[u8], 66 | launcher_arch: WindowsLauncherArch, 67 | script_type: LauncherType, 68 | ) -> Vec { 69 | let mut launcher = launcher_arch.launcher_bytes(script_type).to_vec(); 70 | 71 | // We'r e using the zip writer,but it turns out we're not actually deflating apparently 72 | // we're just using an offset 73 | // https://github.com/pypa/distlib/blob/8ed03aab48add854f377ce392efffb79bb4d6091/PC/launcher.c#L259-L271 74 | let mut stream: Vec = Vec::new(); 75 | { 76 | let stored = FileOptions::default().compression_method(zip::CompressionMethod::Stored); 77 | let mut archive = ZipWriter::new(Cursor::new(&mut stream)); 78 | let error_msg = "Writing to Vec should never fail"; 79 | archive.start_file("__main__.py", stored).expect(error_msg); 80 | archive.write_all(launcher_python_script).expect(error_msg); 81 | archive.finish().expect(error_msg); 82 | } 83 | 84 | launcher.append(&mut format!("{}\n", shebang.trim()).into_bytes()); 85 | launcher.append(&mut stream); 86 | launcher 87 | } 88 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod launcher; 2 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/README.md: -------------------------------------------------------------------------------- 1 | # Python windows launchers 2 | 3 | This folder contains compiled executables that are used to create launchers for python scripts. 4 | 5 | The files have been copied from https://github.com/pypa/distlib. 6 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/t32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/src/win/windows-launcher/t32.exe -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/t64-arm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/src/win/windows-launcher/t64-arm.exe -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/t64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/src/win/windows-launcher/t64.exe -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/w32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/src/win/windows-launcher/w32.exe -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/w64-arm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/src/win/windows-launcher/w64-arm.exe -------------------------------------------------------------------------------- /crates/rattler_installs_packages/src/win/windows-launcher/w64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/src/win/windows-launcher/w64.exe -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/LICENSE: -------------------------------------------------------------------------------- 1 | This software is made available under the terms of *either* of the licenses 2 | found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made 3 | under the terms of *both* these licenses. 4 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/LICENSE.BSD: -------------------------------------------------------------------------------- 1 | Copyright (c) Donald Stufft and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | __title__ = "packaging" 6 | __summary__ = "Core utilities for Python packages" 7 | __uri__ = "https://github.com/pypa/packaging" 8 | 9 | __version__ = "23.2" 10 | 11 | __author__ = "Donald Stufft and individual contributors" 12 | __email__ = "donald@stufft.io" 13 | 14 | __license__ = "BSD-2-Clause or Apache-2.0" 15 | __copyright__ = "2014 %s" % __author__ 16 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/_elffile.py: -------------------------------------------------------------------------------- 1 | """ 2 | ELF file parser. 3 | 4 | This provides a class ``ELFFile`` that parses an ELF executable in a similar 5 | interface to ``ZipFile``. Only the read interface is implemented. 6 | 7 | Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca 8 | ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html 9 | """ 10 | 11 | import enum 12 | import os 13 | import struct 14 | from typing import IO, Optional, Tuple 15 | 16 | 17 | class ELFInvalid(ValueError): 18 | pass 19 | 20 | 21 | class EIClass(enum.IntEnum): 22 | C32 = 1 23 | C64 = 2 24 | 25 | 26 | class EIData(enum.IntEnum): 27 | Lsb = 1 28 | Msb = 2 29 | 30 | 31 | class EMachine(enum.IntEnum): 32 | I386 = 3 33 | S390 = 22 34 | Arm = 40 35 | X8664 = 62 36 | AArc64 = 183 37 | 38 | 39 | class ELFFile: 40 | """ 41 | Representation of an ELF executable. 42 | """ 43 | 44 | def __init__(self, f: IO[bytes]) -> None: 45 | self._f = f 46 | 47 | try: 48 | ident = self._read("16B") 49 | except struct.error: 50 | raise ELFInvalid("unable to parse identification") 51 | magic = bytes(ident[:4]) 52 | if magic != b"\x7fELF": 53 | raise ELFInvalid(f"invalid magic: {magic!r}") 54 | 55 | self.capacity = ident[4] # Format for program header (bitness). 56 | self.encoding = ident[5] # Data structure encoding (endianness). 57 | 58 | try: 59 | # e_fmt: Format for program header. 60 | # p_fmt: Format for section header. 61 | # p_idx: Indexes to find p_type, p_offset, and p_filesz. 62 | e_fmt, self._p_fmt, self._p_idx = { 63 | (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. 65 | (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. 67 | }[(self.capacity, self.encoding)] 68 | except KeyError: 69 | raise ELFInvalid( 70 | f"unrecognized capacity ({self.capacity}) or " 71 | f"encoding ({self.encoding})" 72 | ) 73 | 74 | try: 75 | ( 76 | _, 77 | self.machine, # Architecture type. 78 | _, 79 | _, 80 | self._e_phoff, # Offset of program header. 81 | _, 82 | self.flags, # Processor-specific flags. 83 | _, 84 | self._e_phentsize, # Size of section. 85 | self._e_phnum, # Number of sections. 86 | ) = self._read(e_fmt) 87 | except struct.error as e: 88 | raise ELFInvalid("unable to parse machine and section information") from e 89 | 90 | def _read(self, fmt: str) -> Tuple[int, ...]: 91 | return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) 92 | 93 | @property 94 | def interpreter(self) -> Optional[str]: 95 | """ 96 | The path recorded in the ``PT_INTERP`` section header. 97 | """ 98 | for index in range(self._e_phnum): 99 | self._f.seek(self._e_phoff + self._e_phentsize * index) 100 | try: 101 | data = self._read(self._p_fmt) 102 | except struct.error: 103 | continue 104 | if data[self._p_idx[0]] != 3: # Not PT_INTERP. 105 | continue 106 | self._f.seek(data[self._p_idx[1]]) 107 | return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") 108 | return None 109 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/_musllinux.py: -------------------------------------------------------------------------------- 1 | """PEP 656 support. 2 | 3 | This module implements logic to detect if the currently running Python is 4 | linked against musl, and what musl version is used. 5 | """ 6 | 7 | import functools 8 | import re 9 | import subprocess 10 | import sys 11 | from typing import Iterator, NamedTuple, Optional, Sequence 12 | 13 | from ._elffile import ELFFile 14 | 15 | 16 | class _MuslVersion(NamedTuple): 17 | major: int 18 | minor: int 19 | 20 | 21 | def _parse_musl_version(output: str) -> Optional[_MuslVersion]: 22 | lines = [n for n in (n.strip() for n in output.splitlines()) if n] 23 | if len(lines) < 2 or lines[0][:4] != "musl": 24 | return None 25 | m = re.match(r"Version (\d+)\.(\d+)", lines[1]) 26 | if not m: 27 | return None 28 | return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) 29 | 30 | 31 | @functools.lru_cache() 32 | def _get_musl_version(executable: str) -> Optional[_MuslVersion]: 33 | """Detect currently-running musl runtime version. 34 | 35 | This is done by checking the specified executable's dynamic linking 36 | information, and invoking the loader to parse its output for a version 37 | string. If the loader is musl, the output would be something like:: 38 | 39 | musl libc (x86_64) 40 | Version 1.2.2 41 | Dynamic Program Loader 42 | """ 43 | try: 44 | with open(executable, "rb") as f: 45 | ld = ELFFile(f).interpreter 46 | except (OSError, TypeError, ValueError): 47 | return None 48 | if ld is None or "musl" not in ld: 49 | return None 50 | proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True) 51 | return _parse_musl_version(proc.stderr) 52 | 53 | 54 | def platform_tags(archs: Sequence[str]) -> Iterator[str]: 55 | """Generate musllinux tags compatible to the current platform. 56 | 57 | :param archs: Sequence of compatible architectures. 58 | The first one shall be the closest to the actual architecture and be the part of 59 | platform tag after the ``linux_`` prefix, e.g. ``x86_64``. 60 | The ``linux_`` prefix is assumed as a prerequisite for the current platform to 61 | be musllinux-compatible. 62 | 63 | :returns: An iterator of compatible musllinux tags. 64 | """ 65 | sys_musl = _get_musl_version(sys.executable) 66 | if sys_musl is None: # Python not dynamically linked against musl. 67 | return 68 | for arch in archs: 69 | for minor in range(sys_musl.minor, -1, -1): 70 | yield f"musllinux_{sys_musl.major}_{minor}_{arch}" 71 | 72 | 73 | if __name__ == "__main__": # pragma: no cover 74 | import sysconfig 75 | 76 | plat = sysconfig.get_platform() 77 | assert plat.startswith("linux-"), "not linux" 78 | 79 | print("plat:", plat) 80 | print("musl:", _get_musl_version(sys.executable)) 81 | print("tags:", end=" ") 82 | for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): 83 | print(t, end="\n ") 84 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/_structures.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | 6 | class InfinityType: 7 | def __repr__(self) -> str: 8 | return "Infinity" 9 | 10 | def __hash__(self) -> int: 11 | return hash(repr(self)) 12 | 13 | def __lt__(self, other: object) -> bool: 14 | return False 15 | 16 | def __le__(self, other: object) -> bool: 17 | return False 18 | 19 | def __eq__(self, other: object) -> bool: 20 | return isinstance(other, self.__class__) 21 | 22 | def __gt__(self, other: object) -> bool: 23 | return True 24 | 25 | def __ge__(self, other: object) -> bool: 26 | return True 27 | 28 | def __neg__(self: object) -> "NegativeInfinityType": 29 | return NegativeInfinity 30 | 31 | 32 | Infinity = InfinityType() 33 | 34 | 35 | class NegativeInfinityType: 36 | def __repr__(self) -> str: 37 | return "-Infinity" 38 | 39 | def __hash__(self) -> int: 40 | return hash(repr(self)) 41 | 42 | def __lt__(self, other: object) -> bool: 43 | return True 44 | 45 | def __le__(self, other: object) -> bool: 46 | return True 47 | 48 | def __eq__(self, other: object) -> bool: 49 | return isinstance(other, self.__class__) 50 | 51 | def __gt__(self, other: object) -> bool: 52 | return False 53 | 54 | def __ge__(self, other: object) -> bool: 55 | return False 56 | 57 | def __neg__(self: object) -> InfinityType: 58 | return Infinity 59 | 60 | 61 | NegativeInfinity = NegativeInfinityType() 62 | -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/crates/rattler_installs_packages/vendor/packaging/py.typed -------------------------------------------------------------------------------- /crates/rattler_installs_packages/vendor/packaging/requirements.py: -------------------------------------------------------------------------------- 1 | # This file is dual licensed under the terms of the Apache License, Version 2 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 3 | # for complete details. 4 | 5 | from typing import Any, Iterator, Optional, Set 6 | 7 | from ._parser import parse_requirement as _parse_requirement 8 | from ._tokenizer import ParserSyntaxError 9 | from .markers import Marker, _normalize_extra_values 10 | from .specifiers import SpecifierSet 11 | from .utils import canonicalize_name 12 | 13 | 14 | class InvalidRequirement(ValueError): 15 | """ 16 | An invalid requirement was found, users should refer to PEP 508. 17 | """ 18 | 19 | 20 | class Requirement: 21 | """Parse a requirement. 22 | 23 | Parse a given requirement string into its parts, such as name, specifier, 24 | URL, and extras. Raises InvalidRequirement on a badly-formed requirement 25 | string. 26 | """ 27 | 28 | # TODO: Can we test whether something is contained within a requirement? 29 | # If so how do we do that? Do we need to test against the _name_ of 30 | # the thing as well as the version? What about the markers? 31 | # TODO: Can we normalize the name and extra name? 32 | 33 | def __init__(self, requirement_string: str) -> None: 34 | try: 35 | parsed = _parse_requirement(requirement_string) 36 | except ParserSyntaxError as e: 37 | raise InvalidRequirement(str(e)) from e 38 | 39 | self.name: str = parsed.name 40 | self.url: Optional[str] = parsed.url or None 41 | self.extras: Set[str] = set(parsed.extras if parsed.extras else []) 42 | self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) 43 | self.marker: Optional[Marker] = None 44 | if parsed.marker is not None: 45 | self.marker = Marker.__new__(Marker) 46 | self.marker._markers = _normalize_extra_values(parsed.marker) 47 | 48 | def _iter_parts(self, name: str) -> Iterator[str]: 49 | yield name 50 | 51 | if self.extras: 52 | formatted_extras = ",".join(sorted(self.extras)) 53 | yield f"[{formatted_extras}]" 54 | 55 | if self.specifier: 56 | yield str(self.specifier) 57 | 58 | if self.url: 59 | yield f"@ {self.url}" 60 | if self.marker: 61 | yield " " 62 | 63 | if self.marker: 64 | yield f"; {self.marker}" 65 | 66 | def __str__(self) -> str: 67 | return "".join(self._iter_parts(self.name)) 68 | 69 | def __repr__(self) -> str: 70 | return f"" 71 | 72 | def __hash__(self) -> int: 73 | return hash( 74 | ( 75 | self.__class__.__name__, 76 | *self._iter_parts(canonicalize_name(self.name)), 77 | ) 78 | ) 79 | 80 | def __eq__(self, other: Any) -> bool: 81 | if not isinstance(other, Requirement): 82 | return NotImplemented 83 | 84 | return ( 85 | canonicalize_name(self.name) == canonicalize_name(other.name) 86 | and self.extras == other.extras 87 | and self.specifier == other.specifier 88 | and self.url == other.url 89 | and self.marker == other.marker 90 | ) 91 | -------------------------------------------------------------------------------- /crates/rip_bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rip_bin" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Bas Zalmstra ", "Tim de Jager "] 6 | description = "Binary to verify and play around with rattler_installs_packages" 7 | categories.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | license.workspace = true 11 | readme.workspace = true 12 | default-run = "rip" 13 | 14 | [[bin]] 15 | name = "rip" 16 | path = "src/main.rs" 17 | 18 | [features] 19 | default = ["native-tls"] 20 | native-tls = ['rattler_installs_packages/native-tls'] 21 | rustls-tls = ['rattler_installs_packages/rustls-tls'] 22 | 23 | [dependencies] 24 | clap = { version = "4.5.4", features = ["derive"] } 25 | console = { version = "0.15.8", features = ["windows-console-colors"] } 26 | dirs = "5.0.1" 27 | indexmap = "2.2.6" 28 | indicatif = "0.17.8" 29 | itertools = "0.12.1" 30 | miette = { version = "7.2.0", features = ["fancy"] } 31 | rattler_installs_packages = { path = "../rattler_installs_packages", default-features = false } 32 | reqwest = { version = "0.12.3", default-features = false } 33 | reqwest-middleware = "0.4.0" 34 | tabwriter = { version = "1.4.0", features = ["ansi_formatting"] } 35 | tokio = { version = "1.37.0", features = ["rt", "macros", "rt-multi-thread"] } 36 | tracing = "0.1.40" 37 | tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } 38 | url = "2.5.0" 39 | rand = "0.8.5" 40 | serde = "1.0.198" 41 | serde_json = "1.0.116" 42 | fs-err = "2.11.0" 43 | clap-verbosity-flag = "2.2.0" 44 | 45 | [package.metadata.release] 46 | release = false 47 | -------------------------------------------------------------------------------- /crates/rip_bin/src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod resolve; 2 | 3 | pub mod wheels; 4 | -------------------------------------------------------------------------------- /crates/rip_bin/src/cli/wheels.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use miette::IntoDiagnostic; 3 | use rattler_installs_packages::index::PackageDb; 4 | use std::sync::Arc; 5 | 6 | #[derive(Parser)] 7 | #[command(version, about, long_about = None)] 8 | #[command(propagate_version = true)] 9 | pub struct Args { 10 | #[clap(subcommand)] 11 | command: Commands, 12 | } 13 | 14 | #[derive(Subcommand)] 15 | pub enum Commands { 16 | /// List locally built wheels 17 | List, 18 | } 19 | 20 | pub fn wheels(package_db: Arc, args: Args) -> miette::Result<()> { 21 | match args.command { 22 | Commands::List => list_wheels(package_db), 23 | } 24 | } 25 | 26 | fn list_wheels(package_db: Arc) -> miette::Result<()> { 27 | let wheels = package_db.local_wheel_cache().wheels(); 28 | for wheel in wheels { 29 | println!("{}", wheel.into_diagnostic()?); 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /crates/rip_bin/src/lib.rs: -------------------------------------------------------------------------------- 1 | use indicatif::{MultiProgress, ProgressDrawTarget}; 2 | use std::io; 3 | use std::sync::OnceLock; 4 | use tracing_subscriber::fmt::MakeWriter; 5 | 6 | pub mod cli; 7 | 8 | /// Returns a global instance of [`indicatif::MultiProgress`]. 9 | /// 10 | /// Although you can always create an instance yourself any logging will interrupt pending 11 | /// progressbars. To fix this issue, logging has been configured in such a way to it will not 12 | /// interfere if you use the [`indicatif::MultiProgress`] returning by this function. 13 | pub fn global_multi_progress() -> MultiProgress { 14 | static GLOBAL_MP: OnceLock = OnceLock::new(); 15 | GLOBAL_MP 16 | .get_or_init(|| { 17 | let mp = MultiProgress::new(); 18 | mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(20)); 19 | mp 20 | }) 21 | .clone() 22 | } 23 | 24 | #[derive(Clone)] 25 | pub struct IndicatifWriter { 26 | progress_bars: MultiProgress, 27 | } 28 | 29 | impl IndicatifWriter { 30 | pub fn new(pb: MultiProgress) -> Self { 31 | Self { progress_bars: pb } 32 | } 33 | } 34 | 35 | impl io::Write for IndicatifWriter { 36 | fn write(&mut self, buf: &[u8]) -> io::Result { 37 | self.progress_bars.suspend(|| io::stderr().write(buf)) 38 | } 39 | 40 | fn flush(&mut self) -> io::Result<()> { 41 | self.progress_bars.suspend(|| io::stderr().flush()) 42 | } 43 | } 44 | 45 | impl<'a> MakeWriter<'a> for IndicatifWriter { 46 | type Writer = IndicatifWriter; 47 | 48 | fn make_writer(&'a self) -> Self::Writer { 49 | self.clone() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/rip_bin/src/main.rs: -------------------------------------------------------------------------------- 1 | use rip_bin::{cli, global_multi_progress, IndicatifWriter}; 2 | 3 | use std::str::FromStr; 4 | use std::sync::Arc; 5 | 6 | use clap::{Parser, Subcommand}; 7 | use miette::Context; 8 | use tracing_subscriber::filter::Directive; 9 | use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; 10 | 11 | use rattler_installs_packages::index::{CheckAvailablePackages, PackageSourcesBuilder}; 12 | 13 | use rattler_installs_packages::normalize_index_url; 14 | use reqwest::Client; 15 | use reqwest_middleware::ClientWithMiddleware; 16 | use rip_bin::cli::wheels::wheels; 17 | use tracing::metadata::LevelFilter; 18 | use url::Url; 19 | 20 | #[derive(Parser)] 21 | #[command(version, about, long_about = None)] 22 | #[command(propagate_version = true)] 23 | struct Cli { 24 | /// Subcommand to run 25 | #[command(subcommand)] 26 | command: Commands, 27 | 28 | /// Sets the logging level 29 | #[command(flatten)] 30 | verbose: clap_verbosity_flag::Verbosity, 31 | 32 | /// Base URL of the Python Package Index (default ). This should point 33 | /// to a repository compliant with PEP 503 (the simple repository API). 34 | #[clap(default_value = "https://pypi.org/simple/", long, global = true)] 35 | index_url: Url, 36 | 37 | /// Species whether to always check for new package versions 38 | /// or if we have a server response for a package, use the 39 | /// age provided by the server to determine should send a request 40 | #[clap(long, global = true)] 41 | use_server_timeout: bool, 42 | } 43 | 44 | #[derive(Subcommand)] 45 | enum Commands { 46 | /// Options w.r.t locally built wheels 47 | Wheels(cli::wheels::Args), 48 | 49 | #[command(flatten)] 50 | InstallOrResolve(cli::resolve::Commands), 51 | } 52 | 53 | async fn actual_main() -> miette::Result<()> { 54 | let args = Cli::parse(); 55 | 56 | // Setup tracing subscriber 57 | tracing_subscriber::registry() 58 | .with(fmt::layer().with_writer(IndicatifWriter::new(global_multi_progress()))) 59 | .with( 60 | EnvFilter::try_from_default_env() 61 | .unwrap_or_else(|_| get_default_env_filter(args.verbose)), 62 | ) 63 | .init(); 64 | 65 | // Determine cache directory 66 | let cache_dir = dirs::cache_dir() 67 | .ok_or_else(|| miette::miette!("failed to determine cache directory"))? 68 | .join("rattler/pypi"); 69 | tracing::info!("cache directory: {}", cache_dir.display()); 70 | 71 | // Construct a package database 72 | let index_url = normalize_index_url(args.index_url.clone()); 73 | let sources = PackageSourcesBuilder::new(index_url).build()?; 74 | 75 | let check_available_packages = if args.use_server_timeout { 76 | CheckAvailablePackages::UseServerTime 77 | } else { 78 | CheckAvailablePackages::Always 79 | }; 80 | 81 | let client = ClientWithMiddleware::from(Client::new()); 82 | let package_db = Arc::new( 83 | rattler_installs_packages::index::PackageDb::new( 84 | sources, 85 | client, 86 | &cache_dir, 87 | check_available_packages, 88 | ) 89 | .wrap_err_with(|| { 90 | format!( 91 | "failed to construct package database for index {}", 92 | args.index_url 93 | ) 94 | })?, 95 | ); 96 | 97 | match args.command { 98 | Commands::InstallOrResolve(cmds) => cli::resolve::execute(package_db.clone(), cmds).await, 99 | Commands::Wheels(args) => wheels(package_db.clone(), args), 100 | } 101 | } 102 | 103 | #[tokio::main] 104 | async fn main() { 105 | if let Err(e) = actual_main().await { 106 | eprintln!("{e:?}"); 107 | } 108 | } 109 | 110 | /// Constructs a default [`EnvFilter`] that is used when the user did not specify a custom RUST_LOG. 111 | pub fn get_default_env_filter(verbose: clap_verbosity_flag::Verbosity) -> EnvFilter { 112 | // Always log info for rattler_installs_packages 113 | let (rip, rest) = match verbose.log_level_filter() { 114 | clap_verbosity_flag::LevelFilter::Off => (LevelFilter::OFF, LevelFilter::OFF), 115 | clap_verbosity_flag::LevelFilter::Error => (LevelFilter::INFO, LevelFilter::ERROR), 116 | clap_verbosity_flag::LevelFilter::Warn => (LevelFilter::INFO, LevelFilter::WARN), 117 | clap_verbosity_flag::LevelFilter::Info => (LevelFilter::INFO, LevelFilter::INFO), 118 | clap_verbosity_flag::LevelFilter::Debug => (LevelFilter::DEBUG, LevelFilter::DEBUG), 119 | clap_verbosity_flag::LevelFilter::Trace => (LevelFilter::TRACE, LevelFilter::TRACE), 120 | }; 121 | 122 | EnvFilter::builder() 123 | .with_default_directive(rest.into()) 124 | .from_env() 125 | .expect("failed to get env filter") 126 | .add_directive( 127 | Directive::from_str(&format!("rattler_installs_packages={}", rip)) 128 | .expect("cannot parse directive"), 129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /crates/test-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-utils" 3 | version.workspace = true 4 | categories.workspace = true 5 | homepage.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | edition.workspace = true 9 | readme.workspace = true 10 | rust-version.workspace = true 11 | 12 | [dependencies] 13 | url = "2.5.0" 14 | thiserror = "1.0.58" 15 | rattler_digest = "0.19.3" 16 | reqwest = { version = "0.12.3", default-features = false, features = ["blocking", "rustls-tls"] } 17 | tempfile = "3.10.1" 18 | dirs = "5.0.1" 19 | fslock = "0.2.1" 20 | data-encoding = "2.5.0" 21 | tokio = "1.37.0" 22 | 23 | [package.metadata.release] 24 | release = false 25 | -------------------------------------------------------------------------------- /crates/test-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rattler_digest::Sha256; 2 | use reqwest::blocking::Client; 3 | use std::time::Instant; 4 | use std::{ 5 | path::PathBuf, 6 | sync::{Mutex, OnceLock}, 7 | }; 8 | use thiserror::Error; 9 | use url::Url; 10 | 11 | #[derive(Debug, Error)] 12 | pub enum Error { 13 | #[error("invalid url: {0}")] 14 | InvalidUrl(String), 15 | 16 | #[error("could not determine the systems cache directory")] 17 | FailedToDetermineCacheDir, 18 | 19 | #[error("failed to create temporary file")] 20 | FailedToCreateTemporaryFile(#[source] std::io::Error), 21 | 22 | #[error("failed to acquire cache lock")] 23 | FailedToAcquireCacheLock(#[source] std::io::Error), 24 | 25 | #[error("failed to create cache dir {0}")] 26 | FailedToCreateCacheDir(String, #[source] std::io::Error), 27 | 28 | #[error(transparent)] 29 | HttpError(#[from] reqwest::Error), 30 | 31 | #[error(transparent)] 32 | IoError(#[from] std::io::Error), 33 | 34 | #[error("hash mismatch. Expected: {0}, Actual: {1}")] 35 | HashMismatch(String, String), 36 | } 37 | 38 | /// Returns a [`Client`] that can be shared between all requests. 39 | fn reqwest_client() -> Client { 40 | static CLIENT: OnceLock> = OnceLock::new(); 41 | CLIENT 42 | .get_or_init(|| Mutex::new(Client::new())) 43 | .lock() 44 | .unwrap() 45 | .clone() 46 | } 47 | 48 | /// Returns the cache directory to use for storing cached files 49 | fn cache_dir() -> Result { 50 | Ok(dirs::cache_dir() 51 | .ok_or(Error::FailedToDetermineCacheDir)? 52 | .join("rip/tests/cache/")) 53 | } 54 | 55 | /// Downloads a file to a semi-temporary location that can be used for testing. 56 | pub async fn download_and_cache_file_async( 57 | url: Url, 58 | expected_sha256: &str, 59 | ) -> Result { 60 | let expected_sha256 = expected_sha256.to_owned(); 61 | tokio::task::spawn_blocking(move || download_and_cache_file(url, &expected_sha256)) 62 | .await 63 | .unwrap() 64 | } 65 | 66 | /// Downloads a file to a semi-temporary location that can be used for testing. 67 | pub fn download_and_cache_file(url: Url, expected_sha256: &str) -> Result { 68 | // Acquire a lock to the cache directory 69 | let cache_dir = cache_dir()?; 70 | 71 | // Determine the extension of the file 72 | let filename = url 73 | .path_segments() 74 | .into_iter() 75 | .flatten() 76 | .last() 77 | .ok_or_else(|| Error::InvalidUrl(String::from("missing filename")))?; 78 | 79 | // Determine the final location of the file 80 | let final_parent_dir = cache_dir.join(expected_sha256); 81 | let final_path = final_parent_dir.join(filename); 82 | 83 | // Ensure the cache directory exists 84 | std::fs::create_dir_all(&final_parent_dir) 85 | .map_err(|e| Error::FailedToCreateCacheDir(final_parent_dir.display().to_string(), e))?; 86 | 87 | // Acquire the lock on the cache directory 88 | let mut lock = fslock::LockFile::open(&cache_dir.join(".lock")) 89 | .map_err(Error::FailedToAcquireCacheLock)?; 90 | lock.lock_with_pid() 91 | .map_err(Error::FailedToAcquireCacheLock)?; 92 | 93 | // Check if the file is already there 94 | if final_path.is_file() { 95 | return Ok(final_path); 96 | } 97 | 98 | eprintln!("Downloading {} to {}", url, final_path.display()); 99 | let start_download = Instant::now(); 100 | 101 | // Construct a temporary file to which we will write the file 102 | let tempfile = tempfile::NamedTempFile::new_in(&final_parent_dir) 103 | .map_err(Error::FailedToCreateTemporaryFile)?; 104 | 105 | // Execute the download request 106 | let mut response = reqwest_client().get(url).send()?.error_for_status()?; 107 | 108 | // Compute the hash while downloading 109 | let mut writer = rattler_digest::HashingWriter::<_, Sha256>::new(tempfile); 110 | std::io::copy(&mut response, &mut writer)?; 111 | let (tempfile, hash) = writer.finalize(); 112 | 113 | // Check if the hash matches 114 | let actual_hash = format!("{hash:x}"); 115 | if actual_hash != expected_sha256 { 116 | return Err(Error::HashMismatch(expected_sha256.to_owned(), actual_hash)); 117 | } 118 | 119 | // Write the file to its final destination 120 | tempfile.persist(&final_path).map_err(|e| e.error)?; 121 | 122 | let end_download = Instant::now(); 123 | eprintln!( 124 | "Finished download in {}s", 125 | (end_download - start_download).as_secs_f32() 126 | ); 127 | 128 | Ok(final_path) 129 | } 130 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rip" 3 | version = "0.1.0" 4 | description = "Add a short description here" 5 | authors = ["Wolf Vollprecht "] 6 | channels = ["conda-forge"] 7 | platforms = ["osx-arm64", "linux-64"] 8 | 9 | [tasks] 10 | install_packse = "poetry install -C ./test-data/packse" 11 | fmt = "ruff format ./end_to_end_tests" 12 | end_to_end_tests = "pytest ./end_to_end_tests" 13 | 14 | [dependencies] 15 | pytest = ">=7.4.4,<7.5" 16 | pytest-xprocess = ">=0.23.0,<0.24" 17 | poetry = ">=1.7.1,<1.8" 18 | ruff = ">=0.1.13,<0.2" 19 | 20 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.76 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Flask-1.1.4.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Flask-1.1.4.dist-info/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2010 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Flask-1.1.4.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | flask/__init__.py,sha256=A_TFetAI_ZnVdVxWyEczlCUtwgIpvcKmE6_Yg8CQqnI,1894 2 | flask/__main__.py,sha256=fjVtt3QTANXlpJCOv3Ha7d5H-76MwzSIOab7SFD9TEk,254 3 | flask/_compat.py,sha256=8KPT54Iig96TuLipdogLRHNYToIcg-xPhnSV5VRERnw,4099 4 | flask/app.py,sha256=tmEhx_XrIRP24vZg39dHMWFzJ2jj-YxIcd51LaIT5cE,98059 5 | flask/blueprints.py,sha256=vkdm8NusGsfZUeIfPdCluj733QFmiQcT4Sk1tuZLUjw,21400 6 | flask/cli.py,sha256=SIb22uq9wYBeB2tKMl0pYdhtZ1MAQyZtPL-3m6es4G0,31035 7 | flask/config.py,sha256=3dejvQRYfNHw_V7dCLMxU8UNFpL34xIKemN7gHZIZ8Y,10052 8 | flask/ctx.py,sha256=cks-omGedkxawHFo6bKIrdOHsJCAgg1i_NWw_htxb5U,16724 9 | flask/debughelpers.py,sha256=-whvPKuAoU8AZ9c1z_INuOeBgfYDqE1J2xNBsoriugU,6475 10 | flask/globals.py,sha256=OgcHb6_NCyX6-TldciOdKcyj4PNfyQwClxdMhvov6aA,1637 11 | flask/helpers.py,sha256=FxNLfj8wvloctRgk1thoJCs6MK2wbCO6vZtN770kpxo,43082 12 | flask/logging.py,sha256=WcY5UkqTysGfmosyygSlXyZYGwOp3y-VsE6ehoJ48dk,3250 13 | flask/sessions.py,sha256=G0KsEkr_i1LG_wOINwFSOW3ts7Xbv4bNgEZKc7TRloc,14360 14 | flask/signals.py,sha256=yYLOed2x8WnQ7pirGalQYfpYpCILJ0LJhmNSrnWvjqw,2212 15 | flask/templating.py,sha256=F8E_IZXn9BGsjMzUJ5N_ACMyZdiFBp_SSEaUunvfZ7g,4939 16 | flask/testing.py,sha256=WXsciCQbHBP7xjHqNvOA4bT0k86GvSNpgzncfXLDEEg,10146 17 | flask/views.py,sha256=eeWnadLAj0QdQPLtjKipDetRZyG62CT2y7fNOFDJz0g,5802 18 | flask/wrappers.py,sha256=kgsvtZuMM6RQaDqhRbc5Pcj9vqTnaERl2pmXcdGL7LU,4736 19 | flask/json/__init__.py,sha256=dPJbU8Lf-YMAOMjlFV8FZtNtHxW356nhYpVINLUSAs4,11988 20 | flask/json/tag.py,sha256=vq9GOllg_0kTWKuVFrwmkeOQzR-jdBD23x-89JyCCQI,8306 21 | Flask-1.1.4.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 22 | Flask-1.1.4.dist-info/METADATA,sha256=o7qfWZ3WZ1a_uYYTqrUv06vJbF7ZyZoO85RbFI_5xak,4611 23 | Flask-1.1.4.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 24 | Flask-1.1.4.dist-info/entry_points.txt,sha256=gBLA1aKg0OYR8AhbAfg8lnburHtKcgJLDU52BBctN0k,42 25 | Flask-1.1.4.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6 26 | Flask-1.1.4.dist-info/RECORD,, 27 | ../../Scripts/flask.exe,sha256=ilieUQeFxFyKxXtTQq2hilI1q9uhqEpWdaNKI4dYRdQ,98202 28 | Flask-1.1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 29 | flask/__pycache__/views.cpython-35.pyc,, 30 | flask/__pycache__/debughelpers.cpython-35.pyc,, 31 | flask/json/__pycache__/tag.cpython-35.pyc,, 32 | flask/__pycache__/ctx.cpython-35.pyc,, 33 | flask/__pycache__/config.cpython-35.pyc,, 34 | flask/__pycache__/helpers.cpython-35.pyc,, 35 | flask/__pycache__/globals.cpython-35.pyc,, 36 | flask/__pycache__/blueprints.cpython-35.pyc,, 37 | flask/json/__pycache__/__init__.cpython-35.pyc,, 38 | flask/__pycache__/testing.cpython-35.pyc,, 39 | flask/__pycache__/__main__.cpython-35.pyc,, 40 | flask/__pycache__/signals.cpython-35.pyc,, 41 | flask/__pycache__/app.cpython-35.pyc,, 42 | flask/__pycache__/templating.cpython-35.pyc,, 43 | flask/__pycache__/__init__.cpython-35.pyc,, 44 | flask/__pycache__/sessions.cpython-35.pyc,, 45 | flask/__pycache__/cli.cpython-35.pyc,, 46 | flask/__pycache__/wrappers.cpython-35.pyc,, 47 | flask/__pycache__/_compat.cpython-35.pyc,, 48 | flask/__pycache__/logging.cpython-35.pyc,, 49 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Flask-1.1.4.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.36.2) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Flask-1.1.4.dist-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | flask = flask.cli:main 3 | 4 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Flask-1.1.4.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | flask 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2007 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: Jinja2 3 | Version: 2.11.3 4 | Summary: A very fast and expressive template engine. 5 | Home-page: https://palletsprojects.com/p/jinja/ 6 | Author: Armin Ronacher 7 | Author-email: armin.ronacher@active-4.com 8 | Maintainer: Pallets 9 | Maintainer-email: contact@palletsprojects.com 10 | License: BSD-3-Clause 11 | Project-URL: Documentation, https://jinja.palletsprojects.com/ 12 | Project-URL: Code, https://github.com/pallets/jinja 13 | Project-URL: Issue tracker, https://github.com/pallets/jinja/issues 14 | Platform: UNKNOWN 15 | Classifier: Development Status :: 5 - Production/Stable 16 | Classifier: Environment :: Web Environment 17 | Classifier: Intended Audience :: Developers 18 | Classifier: License :: OSI Approved :: BSD License 19 | Classifier: Operating System :: OS Independent 20 | Classifier: Programming Language :: Python 21 | Classifier: Programming Language :: Python :: 2 22 | Classifier: Programming Language :: Python :: 2.7 23 | Classifier: Programming Language :: Python :: 3 24 | Classifier: Programming Language :: Python :: 3.5 25 | Classifier: Programming Language :: Python :: 3.6 26 | Classifier: Programming Language :: Python :: 3.7 27 | Classifier: Programming Language :: Python :: 3.8 28 | Classifier: Programming Language :: Python :: Implementation :: CPython 29 | Classifier: Programming Language :: Python :: Implementation :: PyPy 30 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content 31 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 32 | Classifier: Topic :: Text Processing :: Markup :: HTML 33 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* 34 | Description-Content-Type: text/x-rst 35 | Requires-Dist: MarkupSafe (>=0.23) 36 | Provides-Extra: i18n 37 | Requires-Dist: Babel (>=0.8) ; extra == 'i18n' 38 | 39 | Jinja 40 | ===== 41 | 42 | Jinja is a fast, expressive, extensible templating engine. Special 43 | placeholders in the template allow writing code similar to Python 44 | syntax. Then the template is passed data to render the final document. 45 | 46 | It includes: 47 | 48 | - Template inheritance and inclusion. 49 | - Define and import macros within templates. 50 | - HTML templates can use autoescaping to prevent XSS from untrusted 51 | user input. 52 | - A sandboxed environment can safely render untrusted templates. 53 | - AsyncIO support for generating templates and calling async 54 | functions. 55 | - I18N support with Babel. 56 | - Templates are compiled to optimized Python code just-in-time and 57 | cached, or can be compiled ahead-of-time. 58 | - Exceptions point to the correct line in templates to make debugging 59 | easier. 60 | - Extensible filters, tests, functions, and even syntax. 61 | 62 | Jinja's philosophy is that while application logic belongs in Python if 63 | possible, it shouldn't make the template designer's job difficult by 64 | restricting functionality too much. 65 | 66 | 67 | Installing 68 | ---------- 69 | 70 | Install and update using `pip`_: 71 | 72 | .. code-block:: text 73 | 74 | $ pip install -U Jinja2 75 | 76 | .. _pip: https://pip.pypa.io/en/stable/quickstart/ 77 | 78 | 79 | In A Nutshell 80 | ------------- 81 | 82 | .. code-block:: jinja 83 | 84 | {% extends "base.html" %} 85 | {% block title %}Members{% endblock %} 86 | {% block content %} 87 | 92 | {% endblock %} 93 | 94 | 95 | Links 96 | ----- 97 | 98 | - Website: https://palletsprojects.com/p/jinja/ 99 | - Documentation: https://jinja.palletsprojects.com/ 100 | - Releases: https://pypi.org/project/Jinja2/ 101 | - Code: https://github.com/pallets/jinja 102 | - Issue tracker: https://github.com/pallets/jinja/issues 103 | - Test status: https://dev.azure.com/pallets/jinja/_build 104 | - Official chat: https://discord.gg/t6rrQZH 105 | 106 | 107 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | jinja2/__init__.py,sha256=LZUXmxJc2GIchfSAeMWsxCWiQYO-w1-736f2Q3I8ms8,1549 2 | jinja2/_compat.py,sha256=B6Se8HjnXVpzz9-vfHejn-DV2NjaVK-Iewupc5kKlu8,3191 3 | jinja2/_identifier.py,sha256=EdgGJKi7O1yvr4yFlvqPNEqV6M1qHyQr8Gt8GmVTKVM,1775 4 | jinja2/asyncfilters.py,sha256=XJtYXTxFvcJ5xwk6SaDL4S0oNnT0wPYvXBCSzc482fI,4250 5 | jinja2/asyncsupport.py,sha256=ZBFsDLuq3Gtji3Ia87lcyuDbqaHZJRdtShZcqwpFnSQ,7209 6 | jinja2/bccache.py,sha256=3Pmp4jo65M9FQuIxdxoDBbEDFwe4acDMQf77nEJfrHA,12139 7 | jinja2/compiler.py,sha256=Ta9W1Lit542wItAHXlDcg0sEOsFDMirCdlFPHAurg4o,66284 8 | jinja2/constants.py,sha256=RR1sTzNzUmKco6aZicw4JpQpJGCuPuqm1h1YmCNUEFY,1458 9 | jinja2/debug.py,sha256=neR7GIGGjZH3_ILJGVUYy3eLQCCaWJMXOb7o0kGInWc,8529 10 | jinja2/defaults.py,sha256=85B6YUUCyWPSdrSeVhcqFVuu_bHUAQXeey--FIwSeVQ,1126 11 | jinja2/environment.py,sha256=XDSLKc4SqNLMOwTSq3TbWEyA5WyXfuLuVD0wAVjEFwM,50629 12 | jinja2/exceptions.py,sha256=VjNLawcmf2ODffqVMCQK1cRmvFaUfQWF4u8ouP3QPcE,5425 13 | jinja2/ext.py,sha256=AtwL5O5enT_L3HR9-oBvhGyUTdGoyaqG_ICtnR_EVd4,26441 14 | jinja2/filters.py,sha256=9ORilsZrUoydSI9upz8_qGy7gozDWLYoFmlIBFSVRnQ,41439 15 | jinja2/idtracking.py,sha256=J3O4VHsrbf3wzwiBc7Cro26kHb6_5kbULeIOzocchIU,9211 16 | jinja2/lexer.py,sha256=nUFLRKhhKmmEWkLI65nQePgcQs7qsRdjVYZETMt_v0g,30331 17 | jinja2/loaders.py,sha256=C-fST_dmFjgWkp0ZuCkrgICAoOsoSIF28wfAFink0oU,17666 18 | jinja2/meta.py,sha256=QjyYhfNRD3QCXjBJpiPl9KgkEkGXJbAkCUq4-Ur10EQ,4131 19 | jinja2/nativetypes.py,sha256=Ul__gtVw4xH-0qvUvnCNHedQeNDwmEuyLJztzzSPeRg,2753 20 | jinja2/nodes.py,sha256=Mk1oJPVgIjnQw9WOqILvcu3rLepcFZ0ahxQm2mbwDwc,31095 21 | jinja2/optimizer.py,sha256=gQLlMYzvQhluhzmAIFA1tXS0cwgWYOjprN-gTRcHVsc,1457 22 | jinja2/parser.py,sha256=fcfdqePNTNyvosIvczbytVA332qpsURvYnCGcjDHSkA,35660 23 | jinja2/runtime.py,sha256=0y-BRyIEZ9ltByL2Id6GpHe1oDRQAwNeQvI0SKobNMw,30618 24 | jinja2/sandbox.py,sha256=knayyUvXsZ-F0mk15mO2-ehK9gsw04UhB8td-iUOtLc,17127 25 | jinja2/tests.py,sha256=iO_Y-9Vo60zrVe1lMpSl5sKHqAxe2leZHC08OoZ8K24,4799 26 | jinja2/utils.py,sha256=Wy4yC3IByqUWwnKln6SdaixdzgK74P6F5nf-gQZrYnU,22436 27 | jinja2/visitor.py,sha256=DUHupl0a4PGp7nxRtZFttUzAi1ccxzqc2hzetPYUz8U,3240 28 | Jinja2-2.11.3.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 29 | Jinja2-2.11.3.dist-info/METADATA,sha256=PscpJ1C3RSp8xcjV3fAuTz13rKbGxmzJXnMQFH-WKhs,3535 30 | Jinja2-2.11.3.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 31 | Jinja2-2.11.3.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61 32 | Jinja2-2.11.3.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7 33 | Jinja2-2.11.3.dist-info/RECORD,, 34 | Jinja2-2.11.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 35 | jinja2/__pycache__/ext.cpython-35.pyc,, 36 | jinja2/__pycache__/__init__.cpython-35.pyc,, 37 | jinja2/__pycache__/_identifier.cpython-35.pyc,, 38 | jinja2/__pycache__/loaders.cpython-35.pyc,, 39 | jinja2/__pycache__/exceptions.cpython-35.pyc,, 40 | jinja2/__pycache__/tests.cpython-35.pyc,, 41 | jinja2/__pycache__/meta.cpython-35.pyc,, 42 | jinja2/__pycache__/nativetypes.cpython-35.pyc,, 43 | jinja2/__pycache__/debug.cpython-35.pyc,, 44 | jinja2/__pycache__/compiler.cpython-35.pyc,, 45 | jinja2/__pycache__/sandbox.cpython-35.pyc,, 46 | jinja2/__pycache__/utils.cpython-35.pyc,, 47 | jinja2/__pycache__/lexer.cpython-35.pyc,, 48 | jinja2/__pycache__/runtime.cpython-35.pyc,, 49 | jinja2/__pycache__/nodes.cpython-35.pyc,, 50 | jinja2/__pycache__/defaults.cpython-35.pyc,, 51 | jinja2/__pycache__/bccache.cpython-35.pyc,, 52 | jinja2/__pycache__/filters.cpython-35.pyc,, 53 | jinja2/__pycache__/environment.cpython-35.pyc,, 54 | jinja2/__pycache__/_compat.cpython-35.pyc,, 55 | jinja2/__pycache__/visitor.cpython-35.pyc,, 56 | jinja2/__pycache__/idtracking.cpython-35.pyc,, 57 | jinja2/__pycache__/parser.cpython-35.pyc,, 58 | jinja2/__pycache__/constants.cpython-35.pyc,, 59 | jinja2/__pycache__/optimizer.cpython-35.pyc,, 60 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.36.2) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [babel.extractors] 2 | jinja2 = jinja2.ext:babel_extract [i18n] 3 | 4 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Jinja2-2.11.3.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | jinja2 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/MarkupSafe-1.1.1.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/MarkupSafe-1.1.1.dist-info/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2010 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/MarkupSafe-1.1.1.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: MarkupSafe 3 | Version: 1.1.1 4 | Summary: Safely add untrusted strings to HTML/XML markup. 5 | Home-page: https://palletsprojects.com/p/markupsafe/ 6 | Author: Armin Ronacher 7 | Author-email: armin.ronacher@active-4.com 8 | Maintainer: The Pallets Team 9 | Maintainer-email: contact@palletsprojects.com 10 | License: BSD-3-Clause 11 | Project-URL: Documentation, https://markupsafe.palletsprojects.com/ 12 | Project-URL: Code, https://github.com/pallets/markupsafe 13 | Project-URL: Issue tracker, https://github.com/pallets/markupsafe/issues 14 | Platform: UNKNOWN 15 | Classifier: Development Status :: 5 - Production/Stable 16 | Classifier: Environment :: Web Environment 17 | Classifier: Intended Audience :: Developers 18 | Classifier: License :: OSI Approved :: BSD License 19 | Classifier: Operating System :: OS Independent 20 | Classifier: Programming Language :: Python 21 | Classifier: Programming Language :: Python :: 2 22 | Classifier: Programming Language :: Python :: 2.7 23 | Classifier: Programming Language :: Python :: 3 24 | Classifier: Programming Language :: Python :: 3.4 25 | Classifier: Programming Language :: Python :: 3.5 26 | Classifier: Programming Language :: Python :: 3.6 27 | Classifier: Programming Language :: Python :: 3.7 28 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content 29 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 30 | Classifier: Topic :: Text Processing :: Markup :: HTML 31 | Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* 32 | 33 | MarkupSafe 34 | ========== 35 | 36 | MarkupSafe implements a text object that escapes characters so it is 37 | safe to use in HTML and XML. Characters that have special meanings are 38 | replaced so that they display as the actual characters. This mitigates 39 | injection attacks, meaning untrusted user input can safely be displayed 40 | on a page. 41 | 42 | 43 | Installing 44 | ---------- 45 | 46 | Install and update using `pip`_: 47 | 48 | .. code-block:: text 49 | 50 | pip install -U MarkupSafe 51 | 52 | .. _pip: https://pip.pypa.io/en/stable/quickstart/ 53 | 54 | 55 | Examples 56 | -------- 57 | 58 | .. code-block:: pycon 59 | 60 | >>> from markupsafe import Markup, escape 61 | >>> # escape replaces special characters and wraps in Markup 62 | >>> escape('') 63 | Markup(u'<script>alert(document.cookie);</script>') 64 | >>> # wrap in Markup to mark text "safe" and prevent escaping 65 | >>> Markup('Hello') 66 | Markup('hello') 67 | >>> escape(Markup('Hello')) 68 | Markup('hello') 69 | >>> # Markup is a text subclass (str on Python 3, unicode on Python 2) 70 | >>> # methods and operators escape their arguments 71 | >>> template = Markup("Hello %s") 72 | >>> template % '"World"' 73 | Markup('Hello "World"') 74 | 75 | 76 | Donate 77 | ------ 78 | 79 | The Pallets organization develops and supports MarkupSafe and other 80 | libraries that use it. In order to grow the community of contributors 81 | and users, and allow the maintainers to devote more time to the 82 | projects, `please donate today`_. 83 | 84 | .. _please donate today: https://palletsprojects.com/donate 85 | 86 | 87 | Links 88 | ----- 89 | 90 | * Website: https://palletsprojects.com/p/markupsafe/ 91 | * Documentation: https://markupsafe.palletsprojects.com/ 92 | * License: `BSD-3-Clause `_ 93 | * Releases: https://pypi.org/project/MarkupSafe/ 94 | * Code: https://github.com/pallets/markupsafe 95 | * Issue tracker: https://github.com/pallets/markupsafe/issues 96 | * Test status: 97 | 98 | * Linux, Mac: https://travis-ci.org/pallets/markupsafe 99 | * Windows: https://ci.appveyor.com/project/pallets/markupsafe 100 | 101 | * Test coverage: https://codecov.io/gh/pallets/markupsafe 102 | 103 | 104 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/MarkupSafe-1.1.1.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | markupsafe/__init__.py,sha256=oTblO5f9KFM-pvnq9bB0HgElnqkJyqHnFN1Nx2NIvnY,10126 2 | markupsafe/_compat.py,sha256=uEW1ybxEjfxIiuTbRRaJpHsPFf4yQUMMKaPgYEC5XbU,558 3 | markupsafe/_constants.py,sha256=zo2ajfScG-l1Sb_52EP3MlDCqO7Y1BVHUXXKRsVDRNk,4690 4 | markupsafe/_native.py,sha256=d-8S_zzYt2y512xYcuSxq0NeG2DUUvG80wVdTn-4KI8,1873 5 | markupsafe/_speedups.cp35-win_amd64.pyd,sha256=Gl6I45geMZhMkY1_2_MkvzYKIwFyARHjzSKzI7ZGIFU,15360 6 | MarkupSafe-1.1.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 7 | MarkupSafe-1.1.1.dist-info/METADATA,sha256=nJHwJ4_4ka-V39QH883jPrslj6inNdyyNASBXbYgHXQ,3570 8 | MarkupSafe-1.1.1.dist-info/WHEEL,sha256=Q_8UIgV2becTkGqdKVgmaXdyabd83UTktF08jttIiu0,106 9 | MarkupSafe-1.1.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 10 | MarkupSafe-1.1.1.dist-info/RECORD,, 11 | MarkupSafe-1.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 12 | markupsafe/__pycache__/_constants.cpython-35.pyc,, 13 | markupsafe/__pycache__/__init__.cpython-35.pyc,, 14 | markupsafe/__pycache__/_compat.cpython-35.pyc,, 15 | markupsafe/__pycache__/_native.cpython-35.pyc,, 16 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/MarkupSafe-1.1.1.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.33.1) 3 | Root-Is-Purelib: false 4 | Tag: cp35-cp35m-win_amd64 5 | 6 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/MarkupSafe-1.1.1.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | markupsafe 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Werkzeug-1.0.1.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Werkzeug-1.0.1.dist-info/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2007 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Werkzeug-1.0.1.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.34.2) 3 | Root-Is-Purelib: true 4 | Tag: py2.py3-none-any 5 | 6 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/Werkzeug-1.0.1.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | werkzeug 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/click-7.1.2.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/click-7.1.2.dist-info/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2014 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/click-7.1.2.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: click 3 | Version: 7.1.2 4 | Summary: Composable command line interface toolkit 5 | Home-page: https://palletsprojects.com/p/click/ 6 | Maintainer: Pallets 7 | Maintainer-email: contact@palletsprojects.com 8 | License: BSD-3-Clause 9 | Project-URL: Documentation, https://click.palletsprojects.com/ 10 | Project-URL: Code, https://github.com/pallets/click 11 | Project-URL: Issue tracker, https://github.com/pallets/click/issues 12 | Platform: UNKNOWN 13 | Classifier: Development Status :: 5 - Production/Stable 14 | Classifier: Intended Audience :: Developers 15 | Classifier: License :: OSI Approved :: BSD License 16 | Classifier: Operating System :: OS Independent 17 | Classifier: Programming Language :: Python 18 | Classifier: Programming Language :: Python :: 2 19 | Classifier: Programming Language :: Python :: 3 20 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* 21 | 22 | \$ click\_ 23 | ========== 24 | 25 | Click is a Python package for creating beautiful command line interfaces 26 | in a composable way with as little code as necessary. It's the "Command 27 | Line Interface Creation Kit". It's highly configurable but comes with 28 | sensible defaults out of the box. 29 | 30 | It aims to make the process of writing command line tools quick and fun 31 | while also preventing any frustration caused by the inability to 32 | implement an intended CLI API. 33 | 34 | Click in three points: 35 | 36 | - Arbitrary nesting of commands 37 | - Automatic help page generation 38 | - Supports lazy loading of subcommands at runtime 39 | 40 | 41 | Installing 42 | ---------- 43 | 44 | Install and update using `pip`_: 45 | 46 | .. code-block:: text 47 | 48 | $ pip install -U click 49 | 50 | .. _pip: https://pip.pypa.io/en/stable/quickstart/ 51 | 52 | 53 | A Simple Example 54 | ---------------- 55 | 56 | .. code-block:: python 57 | 58 | import click 59 | 60 | @click.command() 61 | @click.option("--count", default=1, help="Number of greetings.") 62 | @click.option("--name", prompt="Your name", help="The person to greet.") 63 | def hello(count, name): 64 | """Simple program that greets NAME for a total of COUNT times.""" 65 | for _ in range(count): 66 | click.echo(f"Hello, {name}!") 67 | 68 | if __name__ == '__main__': 69 | hello() 70 | 71 | .. code-block:: text 72 | 73 | $ python hello.py --count=3 74 | Your name: Click 75 | Hello, Click! 76 | Hello, Click! 77 | Hello, Click! 78 | 79 | 80 | Donate 81 | ------ 82 | 83 | The Pallets organization develops and supports Click and other popular 84 | packages. In order to grow the community of contributors and users, and 85 | allow the maintainers to devote more time to the projects, `please 86 | donate today`_. 87 | 88 | .. _please donate today: https://palletsprojects.com/donate 89 | 90 | 91 | Links 92 | ----- 93 | 94 | - Website: https://palletsprojects.com/p/click/ 95 | - Documentation: https://click.palletsprojects.com/ 96 | - Releases: https://pypi.org/project/click/ 97 | - Code: https://github.com/pallets/click 98 | - Issue tracker: https://github.com/pallets/click/issues 99 | - Test status: https://dev.azure.com/pallets/click/_build 100 | - Official chat: https://discord.gg/t6rrQZH 101 | 102 | 103 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/click-7.1.2.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | click/__init__.py,sha256=FkyGDQ-cbiQxP_lxgUspyFYS48f2S_pTcfKPz-d_RMo,2463 2 | click/_bashcomplete.py,sha256=9J98IHQYmCAr2Jup6TDshUr5FJEen-AoQCZR0K5nKxQ,12309 3 | click/_compat.py,sha256=AoMaYnZ-3pwtNXuHtlb6_UXsayoG0QZiHKIRy2VFezc,24169 4 | click/_termui_impl.py,sha256=yNktUMAdjYOU1HMkq915jR3zgAzUNtGSQqSTSSMn3eQ,20702 5 | click/_textwrap.py,sha256=ajCzkzFly5tjm9foQ5N9_MOeaYJMBjAltuFa69n4iXY,1197 6 | click/_unicodefun.py,sha256=apLSNEBZgUsQNPMUv072zJ1swqnm0dYVT5TqcIWTt6w,4201 7 | click/_winconsole.py,sha256=6YDu6Rq1Wxx4w9uinBMK2LHvP83aerZM9GQurlk3QDo,10010 8 | click/core.py,sha256=V6DJzastGhrC6WTDwV9MSLwcJUdX2Uf1ypmgkjBdn_Y,77650 9 | click/decorators.py,sha256=3TvEO_BkaHl7k6Eh1G5eC7JK4LKPdpFqH9JP0QDyTlM,11215 10 | click/exceptions.py,sha256=3pQAyyMFzx5A3eV0Y27WtDTyGogZRbrC6_o5DjjKBbw,8118 11 | click/formatting.py,sha256=Wb4gqFEpWaKPgAbOvnkCl8p-bEZx5KpM5ZSByhlnJNk,9281 12 | click/globals.py,sha256=ht7u2kUGI08pAarB4e4yC8Lkkxy6gJfRZyzxEj8EbWQ,1501 13 | click/parser.py,sha256=mFK-k58JtPpqO0AC36WAr0t5UfzEw1mvgVSyn7WCe9M,15691 14 | click/termui.py,sha256=G7QBEKIepRIGLvNdGwBTYiEtSImRxvTO_AglVpyHH2s,23998 15 | click/testing.py,sha256=EUEsDUqNXFgCLhZ0ZFOROpaVDA5I_rijwnNPE6qICgA,12854 16 | click/types.py,sha256=wuubik4VqgqAw5dvbYFkDt-zSAx97y9TQXuXcVaRyQA,25045 17 | click/utils.py,sha256=4VEcJ7iEHwjnFuzEuRtkT99o5VG3zqSD7Q2CVzv13WU,15940 18 | click-7.1.2.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 19 | click-7.1.2.dist-info/METADATA,sha256=LrRgakZKV7Yg3qJqX_plu2WhFW81MzP3EqQmZhHIO8M,2868 20 | click-7.1.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 21 | click-7.1.2.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 22 | click-7.1.2.dist-info/RECORD,, 23 | click-7.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 24 | click/__pycache__/_textwrap.cpython-35.pyc,, 25 | click/__pycache__/termui.cpython-35.pyc,, 26 | click/__pycache__/_termui_impl.cpython-35.pyc,, 27 | click/__pycache__/_unicodefun.cpython-35.pyc,, 28 | click/__pycache__/exceptions.cpython-35.pyc,, 29 | click/__pycache__/_bashcomplete.cpython-35.pyc,, 30 | click/__pycache__/_compat.cpython-35.pyc,, 31 | click/__pycache__/decorators.cpython-35.pyc,, 32 | click/__pycache__/formatting.cpython-35.pyc,, 33 | click/__pycache__/core.cpython-35.pyc,, 34 | click/__pycache__/__init__.cpython-35.pyc,, 35 | click/__pycache__/parser.cpython-35.pyc,, 36 | click/__pycache__/types.cpython-35.pyc,, 37 | click/__pycache__/globals.cpython-35.pyc,, 38 | click/__pycache__/_winconsole.cpython-35.pyc,, 39 | click/__pycache__/testing.cpython-35.pyc,, 40 | click/__pycache__/utils.cpython-35.pyc,, 41 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/click-7.1.2.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.34.2) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/click-7.1.2.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | click 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/itsdangerous-1.1.0.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/itsdangerous-1.1.0.dist-info/LICENSE.rst: -------------------------------------------------------------------------------- 1 | `BSD 3-Clause `_ 2 | 3 | Copyright © 2011 by the Pallets team. 4 | 5 | Some rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | - Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | - Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | - Neither the name of the copyright holder nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | We kindly ask you to use these themes in an unmodified manner only with 23 | Pallets and Pallets-related projects, not for unrelated projects. If you 24 | like the visual style and want to use it for your own projects, please 25 | consider making some larger changes to the themes (such as changing font 26 | faces, sizes, colors or margins). 27 | 28 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 29 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 30 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 31 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 32 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 33 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 34 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 35 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 36 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 37 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 38 | THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF 39 | SUCH DAMAGE. 40 | 41 | ---- 42 | 43 | The initial implementation of itsdangerous was inspired by Django's 44 | signing module. 45 | 46 | Copyright © Django Software Foundation and individual contributors. 47 | All rights reserved. 48 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/itsdangerous-1.1.0.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: itsdangerous 3 | Version: 1.1.0 4 | Summary: Various helpers to pass data to untrusted environments and back. 5 | Home-page: https://palletsprojects.com/p/itsdangerous/ 6 | Author: Armin Ronacher 7 | Author-email: armin.ronacher@active-4.com 8 | Maintainer: Pallets Team 9 | Maintainer-email: contact@palletsprojects.com 10 | License: BSD 11 | Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ 12 | Project-URL: Code, https://github.com/pallets/itsdangerous 13 | Project-URL: Issue tracker, https://github.com/pallets/itsdangerous/issues 14 | Platform: UNKNOWN 15 | Classifier: Development Status :: 5 - Production/Stable 16 | Classifier: Intended Audience :: Developers 17 | Classifier: License :: OSI Approved :: BSD License 18 | Classifier: Operating System :: OS Independent 19 | Classifier: Programming Language :: Python 20 | Classifier: Programming Language :: Python :: 2 21 | Classifier: Programming Language :: Python :: 2.7 22 | Classifier: Programming Language :: Python :: 3 23 | Classifier: Programming Language :: Python :: 3.4 24 | Classifier: Programming Language :: Python :: 3.5 25 | Classifier: Programming Language :: Python :: 3.6 26 | Classifier: Programming Language :: Python :: 3.7 27 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* 28 | 29 | itsdangerous 30 | ============ 31 | 32 | ... so better sign this 33 | 34 | Various helpers to pass data to untrusted environments and to get it 35 | back safe and sound. Data is cryptographically signed to ensure that a 36 | token has not been tampered with. 37 | 38 | It's possible to customize how data is serialized. Data is compressed as 39 | needed. A timestamp can be added and verified automatically while 40 | loading a token. 41 | 42 | 43 | Installing 44 | ---------- 45 | 46 | Install and update using `pip`_: 47 | 48 | .. code-block:: text 49 | 50 | pip install -U itsdangerous 51 | 52 | .. _pip: https://pip.pypa.io/en/stable/quickstart/ 53 | 54 | 55 | A Simple Example 56 | ---------------- 57 | 58 | Here's how you could generate a token for transmitting a user's id and 59 | name between web requests. 60 | 61 | .. code-block:: python 62 | 63 | from itsdangerous import URLSafeSerializer 64 | auth_s = URLSafeSerializer("secret key", "auth") 65 | token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) 66 | 67 | print(token) 68 | # eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg 69 | 70 | data = auth_s.loads(token) 71 | print(data["name"]) 72 | # itsdangerous 73 | 74 | 75 | Donate 76 | ------ 77 | 78 | The Pallets organization develops and supports itsdangerous and other 79 | popular packages. In order to grow the community of contributors and 80 | users, and allow the maintainers to devote more time to the projects, 81 | `please donate today`_. 82 | 83 | .. _please donate today: https://palletsprojects.com/donate 84 | 85 | 86 | Links 87 | ----- 88 | 89 | * Website: https://palletsprojects.com/p/itsdangerous/ 90 | * Documentation: https://itsdangerous.palletsprojects.com/ 91 | * License: `BSD `_ 92 | * Releases: https://pypi.org/project/itsdangerous/ 93 | * Code: https://github.com/pallets/itsdangerous 94 | * Issue tracker: https://github.com/pallets/itsdangerous/issues 95 | * Test status: https://travis-ci.org/pallets/itsdangerous 96 | * Test coverage: https://codecov.io/gh/pallets/itsdangerous 97 | 98 | 99 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/itsdangerous-1.1.0.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | itsdangerous/__init__.py,sha256=Dr-SkfFdOyiR_WjiqIXnlFpYRMW0XvPBNV5muzE5N_A,708 2 | itsdangerous/_compat.py,sha256=oAAMcQAjwQXQpIbuHT3o-aL56ztm_7Fe-4lD7IteF6A,1133 3 | itsdangerous/_json.py,sha256=W7BLL4RPnSOjNdo2gfKT3BeARMCIikY6O75rwWV0XoE,431 4 | itsdangerous/encoding.py,sha256=KhY85PsH3bGHe5JANN4LMZ_3b0IwUWRRnnw1wvLlaIg,1224 5 | itsdangerous/exc.py,sha256=KFxg7K2XMliMQAxL4jkRNgE8e73z2jcRaLrzwqVObnI,2959 6 | itsdangerous/jws.py,sha256=6Lh9W-Lu8D9s7bRazs0Zb35eyAZm3pzLeZqHmRELeII,7470 7 | itsdangerous/serializer.py,sha256=bT-dfjKec9zcKa8Qo8n7mHW_8M-XCTPMOFq1TQI_Fv4,8653 8 | itsdangerous/signer.py,sha256=OOZbK8XomBjQfOFEul8osesn7fc80MXB0L1r7E86_GQ,6345 9 | itsdangerous/timed.py,sha256=on5Q5lX7LT_LaETOhzF1ZmrRbia8P98263R8FiRyM6Y,5635 10 | itsdangerous/url_safe.py,sha256=xnFTaukIPmW6Qwn6uNQLgzdau8RuAKnp5N7ukuXykj0,2275 11 | itsdangerous-1.1.0.dist-info/LICENSE.rst,sha256=_rKL-jSNgWsOfbrt3xhJnufoAHxngT241qs3xl4EbNQ,2120 12 | itsdangerous-1.1.0.dist-info/METADATA,sha256=yyKjL2WOg_WybH2Yt-7NIvGpV3B93IsMc2HbToWc7Sk,3062 13 | itsdangerous-1.1.0.dist-info/WHEEL,sha256=CihQvCnsGZQBGAHLEUMf0IdA4fRduS_NBUTMgCTtvPM,110 14 | itsdangerous-1.1.0.dist-info/top_level.txt,sha256=gKN1OKLk81i7fbWWildJA88EQ9NhnGMSvZqhfz9ICjk,13 15 | itsdangerous-1.1.0.dist-info/RECORD,, 16 | itsdangerous-1.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 17 | itsdangerous/__pycache__/signer.cpython-35.pyc,, 18 | itsdangerous/__pycache__/_json.cpython-35.pyc,, 19 | itsdangerous/__pycache__/exc.cpython-35.pyc,, 20 | itsdangerous/__pycache__/encoding.cpython-35.pyc,, 21 | itsdangerous/__pycache__/url_safe.cpython-35.pyc,, 22 | itsdangerous/__pycache__/timed.cpython-35.pyc,, 23 | itsdangerous/__pycache__/_compat.cpython-35.pyc,, 24 | itsdangerous/__pycache__/jws.cpython-35.pyc,, 25 | itsdangerous/__pycache__/serializer.cpython-35.pyc,, 26 | itsdangerous/__pycache__/__init__.cpython-35.pyc,, 27 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/itsdangerous-1.1.0.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.32.2) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/itsdangerous-1.1.0.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | itsdangerous 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | pip 2 | === 3 | 4 | The `PyPA recommended 5 | `_ 6 | tool for installing Python packages. 7 | 8 | * `Installation `_ 9 | * `Documentation `_ 10 | * `Changelog `_ 11 | * `Github Page `_ 12 | * `Issue Tracking `_ 13 | * `User mailing list `_ 14 | * `Dev mailing list `_ 15 | * User IRC: #pypa on Freenode. 16 | * Dev IRC: #pypa-dev on Freenode. 17 | 18 | 19 | .. image:: https://img.shields.io/pypi/v/pip.svg 20 | :target: https://pypi.python.org/pypi/pip 21 | 22 | .. image:: https://img.shields.io/travis/pypa/pip/master.svg 23 | :target: http://travis-ci.org/pypa/pip 24 | 25 | .. image:: https://img.shields.io/appveyor/ci/pypa/pip.svg 26 | :target: https://ci.appveyor.com/project/pypa/pip/history 27 | 28 | .. image:: https://readthedocs.org/projects/pip/badge/?version=stable 29 | :target: https://pip.pypa.io/en/stable 30 | 31 | Code of Conduct 32 | --------------- 33 | 34 | Everyone interacting in the pip project's codebases, issue trackers, chat 35 | rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. 36 | 37 | .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.0 2 | Name: pip 3 | Version: 9.0.1 4 | Summary: The PyPA recommended tool for installing Python packages. 5 | Home-page: https://pip.pypa.io/ 6 | Author: The pip developers 7 | Author-email: python-virtualenv@groups.google.com 8 | License: MIT 9 | Keywords: easy_install distutils setuptools egg virtualenv 10 | Platform: UNKNOWN 11 | Classifier: Development Status :: 5 - Production/Stable 12 | Classifier: Intended Audience :: Developers 13 | Classifier: License :: OSI Approved :: MIT License 14 | Classifier: Topic :: Software Development :: Build Tools 15 | Classifier: Programming Language :: Python :: 2 16 | Classifier: Programming Language :: Python :: 2.6 17 | Classifier: Programming Language :: Python :: 2.7 18 | Classifier: Programming Language :: Python :: 3 19 | Classifier: Programming Language :: Python :: 3.3 20 | Classifier: Programming Language :: Python :: 3.4 21 | Classifier: Programming Language :: Python :: 3.5 22 | Classifier: Programming Language :: Python :: Implementation :: PyPy 23 | Requires-Python: >=2.6,!=3.0.*,!=3.1.*,!=3.2.* 24 | Provides-Extra: testing 25 | Requires-Dist: mock; extra == 'testing' 26 | Requires-Dist: pretend; extra == 'testing' 27 | Requires-Dist: pytest; extra == 'testing' 28 | Requires-Dist: scripttest (>=1.3); extra == 'testing' 29 | Requires-Dist: virtualenv (>=1.10); extra == 'testing' 30 | 31 | pip 32 | === 33 | 34 | The `PyPA recommended 35 | `_ 36 | tool for installing Python packages. 37 | 38 | * `Installation `_ 39 | * `Documentation `_ 40 | * `Changelog `_ 41 | * `Github Page `_ 42 | * `Issue Tracking `_ 43 | * `User mailing list `_ 44 | * `Dev mailing list `_ 45 | * User IRC: #pypa on Freenode. 46 | * Dev IRC: #pypa-dev on Freenode. 47 | 48 | 49 | .. image:: https://img.shields.io/pypi/v/pip.svg 50 | :target: https://pypi.python.org/pypi/pip 51 | 52 | .. image:: https://img.shields.io/travis/pypa/pip/master.svg 53 | :target: http://travis-ci.org/pypa/pip 54 | 55 | .. image:: https://img.shields.io/appveyor/ci/pypa/pip.svg 56 | :target: https://ci.appveyor.com/project/pypa/pip/history 57 | 58 | .. image:: https://readthedocs.org/projects/pip/badge/?version=stable 59 | :target: https://pip.pypa.io/en/stable 60 | 61 | Code of Conduct 62 | --------------- 63 | 64 | Everyone interacting in the pip project's codebases, issue trackers, chat 65 | rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. 66 | 67 | .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ 68 | 69 | 70 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.29.0) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | pip = pip:main 3 | pip3 = pip:main 4 | pip3.5 = pip:main 5 | 6 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/metadata.json: -------------------------------------------------------------------------------- 1 | {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Build Tools", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: PyPy"], "extensions": {"python.commands": {"wrap_console": {"pip": "pip:main", "pip3": "pip:main", "pip3.5": "pip:main"}}, "python.details": {"contacts": [{"email": "python-virtualenv@groups.google.com", "name": "The pip developers", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://pip.pypa.io/"}}, "python.exports": {"console_scripts": {"pip": "pip:main", "pip3": "pip:main", "pip3.5": "pip:main"}}}, "extras": ["testing"], "generator": "bdist_wheel (0.29.0)", "keywords": ["easy_install", "distutils", "setuptools", "egg", "virtualenv"], "license": "MIT", "metadata_version": "2.0", "name": "pip", "requires_python": ">=2.6,!=3.0.*,!=3.1.*,!=3.2.*", "run_requires": [{"extra": "testing", "requires": ["mock", "pretend", "pytest", "scripttest (>=1.3)", "virtualenv (>=1.10)"]}], "summary": "The PyPA recommended tool for installing Python packages.", "test_requires": [{"requires": ["mock", "pretend", "pytest", "scripttest (>=1.3)", "virtualenv (>=1.10)"]}], "version": "9.0.1"} -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/pip-9.0.1.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.29.0) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | https://files.pythonhosted.org/packages/source/c/certifi/certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d 2 | https://files.pythonhosted.org/packages/source/w/wincertstore/wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2 3 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | easy_install = setuptools.command.easy_install:main 3 | easy_install-3.5 = setuptools.command.easy_install:main 4 | 5 | [distutils.commands] 6 | alias = setuptools.command.alias:alias 7 | bdist_egg = setuptools.command.bdist_egg:bdist_egg 8 | bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm 9 | bdist_wininst = setuptools.command.bdist_wininst:bdist_wininst 10 | build_ext = setuptools.command.build_ext:build_ext 11 | build_py = setuptools.command.build_py:build_py 12 | develop = setuptools.command.develop:develop 13 | easy_install = setuptools.command.easy_install:easy_install 14 | egg_info = setuptools.command.egg_info:egg_info 15 | install = setuptools.command.install:install 16 | install_egg_info = setuptools.command.install_egg_info:install_egg_info 17 | install_lib = setuptools.command.install_lib:install_lib 18 | install_scripts = setuptools.command.install_scripts:install_scripts 19 | register = setuptools.command.register:register 20 | rotate = setuptools.command.rotate:rotate 21 | saveopts = setuptools.command.saveopts:saveopts 22 | sdist = setuptools.command.sdist:sdist 23 | setopt = setuptools.command.setopt:setopt 24 | test = setuptools.command.test:test 25 | upload = setuptools.command.upload:upload 26 | upload_docs = setuptools.command.upload_docs:upload_docs 27 | 28 | [distutils.setup_keywords] 29 | convert_2to3_doctests = setuptools.dist:assert_string_list 30 | dependency_links = setuptools.dist:assert_string_list 31 | eager_resources = setuptools.dist:assert_string_list 32 | entry_points = setuptools.dist:check_entry_points 33 | exclude_package_data = setuptools.dist:check_package_data 34 | extras_require = setuptools.dist:check_extras 35 | include_package_data = setuptools.dist:assert_bool 36 | install_requires = setuptools.dist:check_requirements 37 | namespace_packages = setuptools.dist:check_nsp 38 | package_data = setuptools.dist:check_package_data 39 | packages = setuptools.dist:check_packages 40 | python_requires = setuptools.dist:check_specifier 41 | setup_requires = setuptools.dist:check_requirements 42 | test_loader = setuptools.dist:check_importable 43 | test_runner = setuptools.dist:check_importable 44 | test_suite = setuptools.dist:check_test_suite 45 | tests_require = setuptools.dist:check_requirements 46 | use_2to3 = setuptools.dist:assert_bool 47 | use_2to3_exclude_fixers = setuptools.dist:assert_string_list 48 | use_2to3_fixers = setuptools.dist:assert_string_list 49 | zip_safe = setuptools.dist:assert_bool 50 | 51 | [egg_info.writers] 52 | PKG-INFO = setuptools.command.egg_info:write_pkg_info 53 | dependency_links.txt = setuptools.command.egg_info:overwrite_arg 54 | depends.txt = setuptools.command.egg_info:warn_depends_obsolete 55 | eager_resources.txt = setuptools.command.egg_info:overwrite_arg 56 | entry_points.txt = setuptools.command.egg_info:write_entries 57 | namespace_packages.txt = setuptools.command.egg_info:overwrite_arg 58 | requires.txt = setuptools.command.egg_info:write_requirements 59 | top_level.txt = setuptools.command.egg_info:write_toplevel_names 60 | 61 | [setuptools.installation] 62 | eggsecutable = setuptools.command.easy_install:bootstrap 63 | 64 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/metadata.json: -------------------------------------------------------------------------------- 1 | {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Archiving :: Packaging", "Topic :: System :: Systems Administration", "Topic :: Utilities"], "extensions": {"python.commands": {"wrap_console": {"easy_install": "setuptools.command.easy_install:main", "easy_install-3.5": "setuptools.command.easy_install:main"}}, "python.details": {"contacts": [{"email": "distutils-sig@python.org", "name": "Python Packaging Authority", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/pypa/setuptools"}}, "python.exports": {"console_scripts": {"easy_install": "setuptools.command.easy_install:main", "easy_install-3.5": "setuptools.command.easy_install:main"}, "distutils.commands": {"alias": "setuptools.command.alias:alias", "bdist_egg": "setuptools.command.bdist_egg:bdist_egg", "bdist_rpm": "setuptools.command.bdist_rpm:bdist_rpm", "bdist_wininst": "setuptools.command.bdist_wininst:bdist_wininst", "build_ext": "setuptools.command.build_ext:build_ext", "build_py": "setuptools.command.build_py:build_py", "develop": "setuptools.command.develop:develop", "easy_install": "setuptools.command.easy_install:easy_install", "egg_info": "setuptools.command.egg_info:egg_info", "install": "setuptools.command.install:install", "install_egg_info": "setuptools.command.install_egg_info:install_egg_info", "install_lib": "setuptools.command.install_lib:install_lib", "install_scripts": "setuptools.command.install_scripts:install_scripts", "register": "setuptools.command.register:register", "rotate": "setuptools.command.rotate:rotate", "saveopts": "setuptools.command.saveopts:saveopts", "sdist": "setuptools.command.sdist:sdist", "setopt": "setuptools.command.setopt:setopt", "test": "setuptools.command.test:test", "upload": "setuptools.command.upload:upload", "upload_docs": "setuptools.command.upload_docs:upload_docs"}, "distutils.setup_keywords": {"convert_2to3_doctests": "setuptools.dist:assert_string_list", "dependency_links": "setuptools.dist:assert_string_list", "eager_resources": "setuptools.dist:assert_string_list", "entry_points": "setuptools.dist:check_entry_points", "exclude_package_data": "setuptools.dist:check_package_data", "extras_require": "setuptools.dist:check_extras", "include_package_data": "setuptools.dist:assert_bool", "install_requires": "setuptools.dist:check_requirements", "namespace_packages": "setuptools.dist:check_nsp", "package_data": "setuptools.dist:check_package_data", "packages": "setuptools.dist:check_packages", "python_requires": "setuptools.dist:check_specifier", "setup_requires": "setuptools.dist:check_requirements", "test_loader": "setuptools.dist:check_importable", "test_runner": "setuptools.dist:check_importable", "test_suite": "setuptools.dist:check_test_suite", "tests_require": "setuptools.dist:check_requirements", "use_2to3": "setuptools.dist:assert_bool", "use_2to3_exclude_fixers": "setuptools.dist:assert_string_list", "use_2to3_fixers": "setuptools.dist:assert_string_list", "zip_safe": "setuptools.dist:assert_bool"}, "egg_info.writers": {"PKG-INFO": "setuptools.command.egg_info:write_pkg_info", "dependency_links.txt": "setuptools.command.egg_info:overwrite_arg", "depends.txt": "setuptools.command.egg_info:warn_depends_obsolete", "eager_resources.txt": "setuptools.command.egg_info:overwrite_arg", "entry_points.txt": "setuptools.command.egg_info:write_entries", "namespace_packages.txt": "setuptools.command.egg_info:overwrite_arg", "requires.txt": "setuptools.command.egg_info:write_requirements", "top_level.txt": "setuptools.command.egg_info:write_toplevel_names"}, "setuptools.installation": {"eggsecutable": "setuptools.command.easy_install:bootstrap"}}}, "extras": ["certs", "ssl"], "generator": "bdist_wheel (0.29.0)", "keywords": ["CPAN", "PyPI", "distutils", "eggs", "package", "management"], "metadata_version": "2.0", "name": "setuptools", "run_requires": [{"extra": "certs", "requires": ["certifi (==2016.9.26)"]}, {"environment": "sys_platform=='win32'", "extra": "ssl", "requires": ["wincertstore (==0.2)"]}], "summary": "Easily download, build, install, upgrade, and uninstall Python packages", "version": "28.8.0"} -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | easy_install 2 | pkg_resources 3 | setuptools 4 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/setuptools-28.8.0.dist-info/zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/zipp-3.17.0.dist-info/.gitkeep: -------------------------------------------------------------------------------- 1 | This file tests that this directory is not detected as a distribution. 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/Lib/site-packages/zipp/.gitkeep: -------------------------------------------------------------------------------- 1 | This file tests that this directory is not detected as a distribution. 2 | -------------------------------------------------------------------------------- /test-data/find_distributions/pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = E:\Developer\prefix\pip\.pixi\env 2 | include-system-site-packages = false 3 | version = 3.5.5 4 | -------------------------------------------------------------------------------- /test-data/scripts/test_wordle.py: -------------------------------------------------------------------------------- 1 | import wordle 2 | 3 | # Instantiate a game object 4 | game = wordle.Wordle(word = 'grape', real_words = True) 5 | 6 | # Send your object a guess 7 | print(game.send_guess('adieu')) 8 | -------------------------------------------------------------------------------- /test-data/sdists/env_package-0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/env_package-0.1.tar.gz -------------------------------------------------------------------------------- /test-data/sdists/fake-flask-3.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/fake-flask-3.0.0.tar.gz -------------------------------------------------------------------------------- /test-data/sdists/filterpy-1.4.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/filterpy-1.4.5.zip -------------------------------------------------------------------------------- /test-data/sdists/rich-13.6.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/rich-13.6.0.tar.gz -------------------------------------------------------------------------------- /test-data/sdists/rich_without_metadata_in_path.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/rich_without_metadata_in_path.tar.gz -------------------------------------------------------------------------------- /test-data/sdists/setuptools-69.0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/setuptools-69.0.2.tar.gz -------------------------------------------------------------------------------- /test-data/sdists/tampered-rich-13.6.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/tampered-rich-13.6.0.tar.gz -------------------------------------------------------------------------------- /test-data/sdists/zip_read_package-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/sdists/zip_read_package-1.0.0.zip -------------------------------------------------------------------------------- /test-data/stree/dev_folder_with_rich/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Will McGugan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /test-data/stree/dev_folder_with_rich/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "rich" 3 | homepage = "https://github.com/Textualize/rich" 4 | documentation = "https://rich.readthedocs.io/en/latest/" 5 | version = "13.6.0" 6 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 7 | authors = ["Will McGugan "] 8 | license = "MIT" 9 | readme = "README.md" 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Environment :: Console", 13 | "Framework :: IPython", 14 | "Intended Audience :: Developers", 15 | "Operating System :: Microsoft :: Windows", 16 | "Operating System :: MacOS", 17 | "Operating System :: POSIX :: Linux", 18 | "Programming Language :: Python :: 3.7", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Typing :: Typed", 25 | ] 26 | include = ["rich/py.typed"] 27 | 28 | 29 | [tool.poetry.dependencies] 30 | python = ">=3.7.0" 31 | typing-extensions = { version = ">=4.0.0, <5.0", python = "<3.9" } 32 | pygments = "^2.13.0" 33 | ipywidgets = { version = ">=7.5.1,<9", optional = true } 34 | markdown-it-py = { git = "https://github.com/executablebooks/markdown-it-py.git" } 35 | 36 | [tool.poetry.extras] 37 | jupyter = ["ipywidgets"] 38 | 39 | [tool.poetry.dev-dependencies] 40 | pytest = "^7.0.0" 41 | black = "^22.6" 42 | mypy = "^0.971" 43 | pytest-cov = "^3.0.0" 44 | attrs = "^21.4.0" 45 | pre-commit = "^2.17.0" 46 | asv = "^0.5.1" 47 | 48 | [build-system] 49 | requires = ["poetry-core>=1.0.0"] 50 | build-backend = "poetry.core.masonry.api" 51 | 52 | 53 | [tool.mypy] 54 | files = ["rich"] 55 | show_error_codes = true 56 | strict = true 57 | enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] 58 | 59 | [[tool.mypy.overrides]] 60 | module = ["pygments.*", "IPython.*", "ipywidgets.*"] 61 | ignore_missing_imports = true 62 | 63 | [tool.pytest.ini_options] 64 | testpaths = ["tests"] 65 | 66 | [tool.isort] 67 | profile = "black" 68 | -------------------------------------------------------------------------------- /test-data/stree/dev_folder_with_rich/rich/__init__.py: -------------------------------------------------------------------------------- 1 | """Rich text and beautiful formatting in the terminal.""" 2 | 3 | print("HEY IMPORTING IT IT's working") 4 | 5 | if __name__ == "__main__": # pragma: no cover 6 | print("Hello, **World**") 7 | -------------------------------------------------------------------------------- /test-data/wheels/miniblack-23.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/wheels/miniblack-23.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /test-data/wheels/purelib_and_platlib-1.0.0-cp38-cp38-linux_x86_64.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/wheels/purelib_and_platlib-1.0.0-cp38-cp38-linux_x86_64.whl -------------------------------------------------------------------------------- /test-data/wheels/wordle_python-2.3.32-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prefix-dev/rip/6225bfbfc8d56ea130f0614f6a42d48a0ded1c78/test-data/wheels/wordle_python-2.3.32-py3-none-any.whl --------------------------------------------------------------------------------