├── .copier-answers.yml ├── .editorconfig ├── .flake8 ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── ci.yml │ ├── doc.yml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── docs │ ├── changelog.md │ ├── code_of_conduct.md │ ├── contributing.md │ └── index.md └── mkdocs.yml ├── news └── towncrier_template.md ├── noxfile.py ├── pdm.lock ├── pyproject.toml ├── src └── pdm_venv │ ├── __init__.py │ ├── backends.py │ ├── commands │ ├── __init__.py │ ├── activate.py │ ├── create.py │ ├── list.py │ ├── purge.py │ └── remove.py │ ├── config.py │ ├── plugin.py │ └── utils.py ├── tasks └── release.py └── tests ├── __init__.py ├── conftest.py ├── test_backends.py ├── test_commands.py ├── test_project.py └── test_utils.py /.copier-answers.yml: -------------------------------------------------------------------------------- 1 | # Changes here will be overwritten by Copier 2 | _commit: 0.1.1 3 | _src_path: gh:pdm-project/copier-pdm 4 | author_email: mianghong@gmail.com 5 | author_fullname: Frost Ming 6 | author_username: frostming 7 | copyright_date: '2021' 8 | copyright_holder: Frost Ming 9 | copyright_holder_email: mianghong@gmail.com 10 | copyright_license: MIT 11 | project_description: A plugin for pdm that enables virtualenv management 12 | project_name: pdm-venv 13 | python_package_distribution_name: pdm-venv 14 | python_package_import_name: pdm_venv 15 | python_package_requires_python: '>=3.7' 16 | repository_name: pdm-venv 17 | repository_namespace: pdm-project 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdm-project/pdm-venv/6d9d0c929be10cebcaa816533f494ac11c0ce0f5/.editorconfig -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | .git, 4 | tests/fixtures/*, 5 | env, 6 | dist, 7 | build, 8 | __pypackages__, 9 | max_line_length = 88 10 | ignore = 11 | E203 12 | W503 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: unconfirmed 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Run command '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **System (please complete the following information):** 27 | - `pdm-venv` version: [e.g. 0.2.1] 28 | - Python version: [e.g. 3.8] 29 | - OS: [Windows/Linux] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "docs/**" 7 | - "news/**" 8 | - "*.md" 9 | push: 10 | branches: 11 | - master 12 | - main 13 | paths-ignore: 14 | - "docs/**" 15 | - "news/**" 16 | - "*.md" 17 | 18 | jobs: 19 | Testing: 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | python-version: [3.7, 3.8, 3.9, "3.10"] 24 | venv-backend: [virtualenv, venv, conda] 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up PDM 29 | uses: pdm-project/setup-pdm@main 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Set Variables 33 | id: set_variables 34 | run: | 35 | echo "::set-output name=PIP_CACHE::$(pip cache dir)" 36 | - name: Set up Miniconda 37 | uses: conda-incubator/setup-miniconda@v2 38 | if: matrix.venv-backend == 'conda' 39 | with: 40 | auto-update-conda: true 41 | python-version: ${{ matrix.python-version }} 42 | - name: Cache PIP 43 | uses: actions/cache@v2 44 | with: 45 | path: | 46 | ${{ steps.set_variables.outputs.PIP_CACHE }} 47 | key: ${{ runner.os }}-pip-${{ matrix.python-version }} 48 | restore-keys: ${{ runner.os }}-pip- 49 | 50 | - name: Install packages 51 | run: python -m pip install -U nox . 52 | - name: Run Tests 53 | run: | 54 | pdm config install.parallel false 55 | nox -s "test-${{ matrix.python-version }}(backend='${{ matrix.venv-backend }}')" 56 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - "*.md" 10 | - docs/** 11 | - .github/workflows/doc.yml 12 | 13 | jobs: 14 | build-doc: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: pdm-project/setup-pdm@v1.1 20 | with: 21 | python-version: 3.8 22 | architecture: "x64" 23 | - name: Build pages 24 | run: | 25 | pdm install -G doc 26 | cd docs && pdm run mkdocs build 27 | - name: Deploy 28 | uses: peaceiris/actions-gh-pages@v3 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | publish_dir: ./docs/site 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | release-pypi: 10 | name: release-pypi 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: pdm-project/setup-pdm@main 16 | with: 17 | python-version: 3.8 18 | prereleases: true 19 | - name: Build artifacts 20 | run: | 21 | pdm build -v 22 | - name: Test Build 23 | run: | 24 | python3 -m venv fresh_env 25 | . fresh_env/bin/activate 26 | pip install dist/*.whl 27 | - name: Upload to Pypi 28 | run: | 29 | pip install twine 30 | twine upload --username __token__ --password ${{ secrets.PYPI_TOKEN }} dist/* 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__/ 3 | dist/ 4 | build/ 5 | .coverage* 6 | *.egg-info/ 7 | pip-wheel-metadata/ 8 | .pytest_cache/ 9 | .mypy_cache/ 10 | 11 | docs/_build/ 12 | docs/site 13 | 14 | .venv/ 15 | venv/ 16 | env/ 17 | 18 | .pdm.toml 19 | __pypackages__/ 20 | 21 | .nox/ 22 | .tox/ 23 | 24 | .vscode/ 25 | .idea/ 26 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.3.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-added-large-files 10 | - repo: https://github.com/psf/black 11 | rev: 22.3.0 # Replace by any tag/version: https://github.com/psf/black/tags 12 | hooks: 13 | - id: black 14 | 15 | - repo: https://github.com/PyCQA/flake8 16 | rev: 4.0.1 17 | hooks: 18 | - id: flake8 19 | 20 | - repo: https://github.com/pycqa/isort 21 | rev: 5.10.1 22 | hooks: 23 | - id: isort 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | [v0.5.0](https://github.com/pdm-project/pdm-venv/releases/tag/0.5.0) (2022-02-18) 5 | --------------------------------------------------------------------------------- 6 | 7 | ### Features & Improvements 8 | 9 | - Update the plugin to support PDM>=1.13.0. [#35](https://github.com/pdm-project/pdm-venv/issues/35) 10 | 11 | 12 | [v0.4.1](https://github.com/pdm-project/pdm-venv/releases/tag/0.4.1) (2022-01-29) 13 | --------------------------------------------------------------------------------- 14 | 15 | ### Bug Fixes 16 | 17 | - Fix missing argument in the create command [#32](https://github.com/pdm-project/pdm-venv/issues/32) 18 | 19 | 20 | [v0.4.0](https://github.com/pdm-project/pdm-venv/releases/tag/0.4.0) (2022-01-03) 21 | --------------------------------------------------------------------------------- 22 | 23 | ### Features & Improvements 24 | 25 | - Support creating venv in project with a config `venv.in_project`. [#20](https://github.com/pdm-project/pdm-venv/issues/20) 26 | - Add an option to purge created Virtualenvs [#21](https://github.com/pdm-project/pdm-venv/issues/21) 27 | 28 | ### Bug Fixes 29 | 30 | - Fix a conda creation args that 1)uses what the interpreter version is used when no python arg is given, and 2)allows using python version that is not installed yet. [#25](https://github.com/pdm-project/pdm-venv/issues/25) 31 | 32 | 33 | [v0.3.1](https://github.com/pdm-project/pdm-venv/releases/tag/0.3.1) (2021-08-23) 34 | --------------------------------------------------------------------------------- 35 | 36 | ### Bug Fixes 37 | 38 | - Ensure the location is string when calling subprocesses. This is for the compatibility of Python 3.8-. [#18](https://github.com/pdm-project/pdm-venv/issues/18) 39 | 40 | 41 | [v0.3.0](https://github.com/pdm-project/pdm-venv/releases/tag/0.3.0) (2021-07-30) 42 | --------------------------------------------------------------------------------- 43 | 44 | ### Bug Fixes 45 | 46 | - Add support for powershell version>=6.0 as [executable name](https://powershellexplained.com/2017-12-29-Powershell-what-is-pwsh/#:~:text=The%20pwsh.exe%20process%20is%20the%20new%20name%20for%20PowerShell%20Core%20starting%20with%20version%206.0.%20The%20executable%20changed%20names%20from%20powershell.exe%20to%20pwsh.exe.%20Let%E2%80%99s%20take%20a%20look%20at%20this%20executable.) has changed starting with version 6.0. [##10](https://github.com/pdm-project/pdm-venv/issues/#10) 47 | - Fix the activate script of conda env. Now the output should be eval'd rather than source'd [#14](https://github.com/pdm-project/pdm-venv/issues/14) 48 | 49 | 50 | [v0.2.0](https://github.com/pdm-project/pdm-venv/releases/tag/0.2.0) (2021-04-12) 51 | --------------------------------------------------------------------------------- 52 | 53 | ### Features & Improvements 54 | 55 | - Update per the changes of PDM 1.5.0 prerelease. [#2](https://github.com/pdm-project/pdm-venv/issues/2) 56 | 57 | 58 | [v0.1.1](https://github.com/pdm-project/pdm-venv/releases/tag/0.1.1) (2021-03-25) 59 | --------------------------------------------------------------------------------- 60 | 61 | ### Features & Improvements 62 | 63 | - Honor the virtualenv in active for reuse. This however won't store the interpreter path in `.pdm.toml`. [#1](https://github.com/pdm-project/pdm-venv/issues/1) 64 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Everyone interacting in the pdm-venv project's codebases and issue trackers is expected to 2 | follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/). 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! 4 | Every little bit helps, and credit will always be given. 5 | 6 | ## Environment setup 7 | 8 | Nothing easier! 9 | 10 | Fork and clone the repository: 11 | 12 | ```bash 13 | git clone https://github.com/pdm-project/pdm-venv 14 | cd pdm-venv 15 | ``` 16 | 17 | We use [pdm](https://pdm.fming.dev) to manage the project and dependencies, install PDM if it isn't done yet, then: 18 | 19 | ```bash 20 | pdm install -d 21 | ``` 22 | 23 | You now have the dependencies installed. 24 | 25 | You can run the tests with `pdm run test [ARGS...]`. 26 | 27 | ## Test against multiple Python versions 28 | 29 | This project uses [nox](https://nox.thea.codes/) as the test runner. See what sessions are list: 30 | 31 | ```bash 32 | nox --list 33 | ``` 34 | 35 | And run the test suite on specified Python versions: 36 | 37 | ```bash 38 | nox -s tests-3.8 39 | ``` 40 | 41 | !!! important "TIPS" 42 | `nox` and `pre-commit` in the following section are not list in the `dev-dependencies` of the project, 43 | because they can be installed separately to the system and used via the external executable. If you are willing to 44 | reproduce the development environment without external dependencies. Run `pdm add -d nox pre-commit` and the 45 | corresponding commands should be prefixed with `pdm run` as well. 46 | 47 | ## Development 48 | 49 | As usual: 50 | 51 | 1. create a new branch: `git checkout -b feature-or-bugfix-name` 52 | 1. edit the code and/or the documentation 53 | 54 | If you updated the documentation or the project dependencies: 55 | 56 | 1. run `pdm run doc` 57 | 1. go to http://localhost:8000 and check that everything looks good 58 | 59 | **Before committing:** 60 | 61 | 1. Make sure you submit a news entry under `news/` directory with the name pattern `..md` where `` should be one of: 62 | 1. `bugfix` for bug fixes 63 | 1. `feature` for features and improvements 64 | 1. `doc` for documentation improvements 65 | 1. `remove` for deprecations and removals 66 | 1. `dep ` for dependencies updates 67 | 1. `misc` for miscellany tasks 68 | 69 | 1. Install [pre-commit](https://pre-commit.com/) and hooks: 70 | ```bash 71 | pre-commit install 72 | ``` 73 | 1. Then linter task will be run each time when you commit something. Or you can run it manually: 74 | ```bash 75 | pdm run lint 76 | ``` 77 | 78 | If you are unsure about how to fix or ignore a warning, 79 | just let the continuous integration fail, 80 | and we will help you during review. 81 | 82 | Don't bother updating the changelog, we will take care of this. 83 | 84 | ## Pull requests guidelines 85 | 86 | Link to any related issue in the Pull Request message. 87 | 88 | During review, we recommend using fixups: 89 | 90 | ```bash 91 | # SHA is the SHA of the commit you want to fix 92 | git commit --fixup=SHA 93 | ``` 94 | 95 | Once all the changes are approved, you can squash your commits: 96 | 97 | ```bash 98 | git rebase -i --autosquash master 99 | ``` 100 | 101 | And force-push: 102 | 103 | ```bash 104 | git push -f 105 | ``` 106 | 107 | If this seems all too complicated, you can push or force-push each new commit, 108 | and we will squash them ourselves if needed, before merging. 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT 2 | 3 | Copyright (c) 2021 Frost Ming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdm-venv 2 | 3 | [![Tests](https://github.com/pdm-project/pdm-venv/workflows/Tests/badge.svg)](https://github.com/pdm-project/pdm-venv/actions?query=workflow%3Aci) 4 | [![pypi version](https://img.shields.io/pypi/v/pdm-venv.svg)](https://pypi.org/project/pdm-venv/) 5 | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) 6 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 7 | 8 | A plugin for pdm that enables virtualenv management 9 | 10 | ## DEPRECATION WARNING 11 | 12 | venv support will be integrated into the `pdm` after `2.0.0`. This project is not necessary and won't be maintained anymore. 13 | The last version of PDM it supports is `1.15`. 14 | 15 | ## Requirements 16 | 17 | pdm-venv requires Python>=3.7 18 | 19 | ## Installation 20 | 21 | On PDM 1.6.4+, you can install the plugin directly by: 22 | 23 | ```bash 24 | $ pdm plugin add pdm-venv 25 | ``` 26 | 27 | If `pdm` is installed via [pipx](https://github.com/pipxproject/pipx)(the recommended way), run: 28 | 29 | ```bash 30 | $ pipx inject pdm pdm-venv 31 | ``` 32 | 33 | Otherwise if `pdm` is installed via Homebrew, run: 34 | 35 | ```bash 36 | $ $(brew --prefix pdm)/libexec/bin/pip install pdm-venv 37 | ``` 38 | 39 | Or install with `pip` to the user site: 40 | 41 | ```bash 42 | $ python -m pip install --user pdm-venv 43 | ``` 44 | 45 | Note that `pdm-venv` must be installed to the same environment as `pdm`. 46 | 47 | ## Usage 48 | 49 | `pdm-venv` enhances `pdm`'s CLI with the support of virtualenv creation and management. With `pdm-venv` installed, 50 | the default value of `python.use_venv` will turn to `True`, you can disable the whole plugin by `pdm config python.use_venv false`. 51 | 52 | ### Create a virtualenv 53 | 54 | ```bash 55 | # Create a virtualenv based on 3.8 interpreter 56 | $ pdm venv create 3.8 57 | # Assign a different name other than the version string 58 | $ pdm venv create --name for-test 3.8 59 | # Use venv as the backend to create, support 3 backends: virtualenv(default), venv, conda 60 | $ pdm venv create --with venv 3.9 61 | ``` 62 | 63 | ### List all virtualenv created with this project 64 | 65 | ```console 66 | $ pdm venv list 67 | Virtualenvs created with this project: 68 | 69 | - 3.8.6: C:\Users\Frost Ming\AppData\Local\pdm\pdm\venvs\test-project-8Sgn_62n-3.8.6 70 | - for-test: C:\Users\Frost Ming\AppData\Local\pdm\pdm\venvs\test-project-8Sgn_62n-for-test 71 | - 3.9.1: C:\Users\Frost Ming\AppData\Local\pdm\pdm\venvs\test-project-8Sgn_62n-3.9.1 72 | ``` 73 | 74 | The name before the colon(:) is the key of the virtualenv which is used in `remove` and `activate` commands below. 75 | 76 | ### Remove a virtualenv 77 | 78 | ```console 79 | $ pdm venv remove for-test 80 | Virtualenvs created with this project: 81 | Will remove: C:\Users\Frost Ming\AppData\Local\pdm\pdm\venvs\test-project-8Sgn_62n-for-test, continue? [y/N]:y 82 | Removed C:\Users\Frost Ming\AppData\Local\pdm\pdm\venvs\test-project-8Sgn_62n-for-test 83 | ``` 84 | 85 | ### Activate a virtualenv 86 | 87 | Instead of spawning a subshell like what `pipenv` and `poetry` do, `pdm-venv` doesn't create the shell for you but print the activate command to the console. 88 | In this way you won't lose the fancy shell features. You can then feed the output to `eval` to activate the virtualenv without leaving the current shell: 89 | 90 | **Bash/csh/zsh** 91 | 92 | ```console 93 | $ eval $(pdm venv activate for-test) 94 | (test-project-8Sgn_62n-for-test) $ # Virtualenv entered 95 | ``` 96 | 97 | **Fish** 98 | 99 | ```console 100 | $ eval (pdm venv activate for-test) 101 | ``` 102 | 103 | **Powershell** 104 | 105 | ```console 106 | PS1> Invoke-Expression (pdm venv activate for-test) 107 | ``` 108 | 109 | You can make your own shell shortcut function to avoid the input of long command. Here is an example of Bash: 110 | 111 | ```bash 112 | pdm_venv_activate() { 113 | eval $('pdm' 'venv' 'activate' "$1") 114 | } 115 | ``` 116 | 117 | Then you can activate it by `pdm_venv_activate $venv_name` and deactivate by `deactivate` directly. 118 | 119 | Additionally, if the saved Python interpreter is a venv Python, you can omit the name argument following `activate`. 120 | 121 | ### Switch Python interpreter 122 | 123 | When `pdm-venv` is enabled, Python interpreters associated with the venvs will also show in the interpreter list of `pdm use` or `pdm init` command. 124 | 125 | Additionally, if `pdm` detects it is inside an active virtualenv by examining `VIRTUAL_ENV` env var, it will reuse that virtualenv for later actions. 126 | 127 | ### Virtualenv auto creation 128 | 129 | If no Python interpreter is selected for the project, `pdm-venv` will take charge to create one for you and select the venv interpreter automatically, just like 130 | what `pipenv` and `poetry` do. Additionaly, if config item `venv.in_project` is `True`, `pdm-venv` will create the virtualenv in `${PROJECT_ROOT}/.venv`. 131 | 132 | ## Configuration 133 | 134 | | Config Item | Description | Default Value | Available in Project | Env var | 135 | | ----------------- | ----------------------------------------------- | ----------------------------------- | -------------------- | --------------------- | 136 | | `venv.location` | The root directory to store virtualenvs | `appdirs.user_data_dir() / "venvs"` | No | | 137 | | `venv.backend` | The default backend used to create virtualenvs | `virtualenv` | No | | 138 | | `venv.in_project` | Create virtualenv in `.venv` under project root | `False` | Yes | `PDM_VENV_IN_PROJECT` | 139 | -------------------------------------------------------------------------------- /docs/docs/changelog.md: -------------------------------------------------------------------------------- 1 | --8<-- "../CHANGELOG.md" 2 | -------------------------------------------------------------------------------- /docs/docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | --8<-- "../CODE_OF_CONDUCT.md" 2 | -------------------------------------------------------------------------------- /docs/docs/contributing.md: -------------------------------------------------------------------------------- 1 | --8<-- "../CONTRIBUTING.md" 2 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --8<-- "../README.md" 2 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "pdm-venv" 2 | site_description: "A plugin for pdm that enables virtualenv management" 3 | site_url: "https://pdm-project.github.io/pdm-venv" 4 | repo_url: "https://github.com/pdm-project/pdm-venv" 5 | repo_name: "pdm-project/pdm-venv" 6 | site_dir: "site" 7 | 8 | nav: 9 | - Home: 10 | - Overview: index.md 11 | - Changelog: changelog.md 12 | - Development: 13 | - Contributing: contributing.md 14 | - Code of Conduct: code_of_conduct.md 15 | 16 | theme: 17 | name: material 18 | palette: 19 | primary: deep purple 20 | accent: teal 21 | font: 22 | text: Open Sans 23 | code: Fira Code 24 | 25 | 26 | markdown_extensions: 27 | - admonition 28 | - pymdownx.emoji 29 | - pymdownx.magiclink 30 | - pymdownx.snippets: 31 | check_paths: true 32 | - pymdownx.superfences 33 | - pymdownx.tabbed 34 | - pymdownx.tasklist 35 | - toc: 36 | permalink: "#" 37 | 38 | plugins: 39 | - search 40 | -------------------------------------------------------------------------------- /news/towncrier_template.md: -------------------------------------------------------------------------------- 1 | {% for section in sections %} 2 | {% if sections[section] %} 3 | {% for category, val in definitions.items() if category in sections[section] and category != 'trivial' %} 4 | 5 | ### {{ definitions[category]['name'] }} 6 | 7 | {% if definitions[category]['showcontent'] %} 8 | {% for text, values in sections[section][category]|dictsort(by='value') %} 9 | - {{ text }} {% if category != 'process' %}{{ values|sort|join(',\n ') }}{% endif %} 10 | 11 | {% endfor %} 12 | {% else %} 13 | - {{ sections[section][category]['']|sort|join(', ') }} 14 | {% endif %} 15 | {% if sections[section][category]|length == 0 %} 16 | 17 | No significant changes. 18 | {% else %} 19 | {% endif %} 20 | {% endfor %} 21 | {% else %} 22 | 23 | No significant changes. 24 | {% endif %} 25 | {% endfor %} 26 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import nox 4 | 5 | os.environ["PDM_IGNORE_SAVED_PYTHON"] = "1" 6 | 7 | 8 | @nox.session(python=("3.7", "3.8", "3.9", "3.10")) 9 | @nox.parametrize("backend", ("virtualenv", "venv", "conda")) 10 | def test(session, backend): 11 | os.environ["DEFAULT_BACKEND"] = backend 12 | session.run("pdm", "install", "-vd", external=True) 13 | session.run("pytest", "tests/") 14 | -------------------------------------------------------------------------------- /pdm.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "argcomplete" 3 | version = "1.12.3" 4 | summary = "Bash tab completion for argparse" 5 | dependencies = [ 6 | "importlib-metadata<5,>=0.23; python_version == \"3.7\"", 7 | ] 8 | 9 | [[package]] 10 | name = "arpeggio" 11 | version = "1.10.2" 12 | summary = "Packrat parser interpreter" 13 | 14 | [[package]] 15 | name = "atomicwrites" 16 | version = "1.4.0" 17 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 18 | summary = "Atomic file writes." 19 | 20 | [[package]] 21 | name = "attrs" 22 | version = "21.4.0" 23 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 24 | summary = "Classes Without Boilerplate" 25 | 26 | [[package]] 27 | name = "blinker" 28 | version = "1.4" 29 | summary = "Fast, simple object-to-object and broadcast signaling" 30 | 31 | [[package]] 32 | name = "cfgv" 33 | version = "3.3.1" 34 | requires_python = ">=3.6.1" 35 | summary = "Validate configuration and produce human readable error messages." 36 | 37 | [[package]] 38 | name = "click" 39 | version = "8.1.3" 40 | requires_python = ">=3.7" 41 | summary = "Composable command line interface toolkit" 42 | dependencies = [ 43 | "colorama; platform_system == \"Windows\"", 44 | "importlib-metadata; python_version < \"3.8\"", 45 | ] 46 | 47 | [[package]] 48 | name = "click-default-group" 49 | version = "1.2.2" 50 | summary = "Extends click.Group to invoke a command without explicit subcommand name" 51 | dependencies = [ 52 | "click", 53 | ] 54 | 55 | [[package]] 56 | name = "colorama" 57 | version = "0.4.5" 58 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 59 | summary = "Cross-platform colored terminal text." 60 | 61 | [[package]] 62 | name = "colorlog" 63 | version = "6.6.0" 64 | requires_python = ">=3.6" 65 | summary = "Add colours to the output of Python's logging module." 66 | dependencies = [ 67 | "colorama; sys_platform == \"win32\"", 68 | ] 69 | 70 | [[package]] 71 | name = "distlib" 72 | version = "0.3.4" 73 | summary = "Distribution utilities" 74 | 75 | [[package]] 76 | name = "filelock" 77 | version = "3.7.1" 78 | requires_python = ">=3.7" 79 | summary = "A platform independent file lock." 80 | 81 | [[package]] 82 | name = "findpython" 83 | version = "0.2.0" 84 | requires_python = ">=3.7" 85 | summary = "A utility to find python versions on your system" 86 | dependencies = [ 87 | "packaging>=20", 88 | ] 89 | 90 | [[package]] 91 | name = "ghp-import" 92 | version = "2.1.0" 93 | summary = "Copy your docs directly to the gh-pages branch." 94 | dependencies = [ 95 | "python-dateutil>=2.8.1", 96 | ] 97 | 98 | [[package]] 99 | name = "identify" 100 | version = "2.5.1" 101 | requires_python = ">=3.7" 102 | summary = "File identification library for Python" 103 | 104 | [[package]] 105 | name = "importlib-metadata" 106 | version = "4.12.0" 107 | requires_python = ">=3.7" 108 | summary = "Read metadata from Python packages" 109 | dependencies = [ 110 | "typing-extensions>=3.6.4; python_version < \"3.8\"", 111 | "zipp>=0.5", 112 | ] 113 | 114 | [[package]] 115 | name = "incremental" 116 | version = "21.3.0" 117 | summary = "A small library that versions your Python projects." 118 | 119 | [[package]] 120 | name = "iniconfig" 121 | version = "1.1.1" 122 | summary = "iniconfig: brain-dead simple config-ini parsing" 123 | 124 | [[package]] 125 | name = "installer" 126 | version = "0.5.1" 127 | requires_python = ">=3.7" 128 | summary = "A library for installing Python wheels." 129 | 130 | [[package]] 131 | name = "jinja2" 132 | version = "3.1.2" 133 | requires_python = ">=3.7" 134 | summary = "A very fast and expressive template engine." 135 | dependencies = [ 136 | "MarkupSafe>=2.0", 137 | ] 138 | 139 | [[package]] 140 | name = "markdown" 141 | version = "3.3.7" 142 | requires_python = ">=3.6" 143 | summary = "Python implementation of Markdown." 144 | dependencies = [ 145 | "importlib-metadata>=4.4; python_version < \"3.10\"", 146 | ] 147 | 148 | [[package]] 149 | name = "markupsafe" 150 | version = "2.1.1" 151 | requires_python = ">=3.7" 152 | summary = "Safely add untrusted strings to HTML/XML markup." 153 | 154 | [[package]] 155 | name = "mergedeep" 156 | version = "1.3.4" 157 | requires_python = ">=3.6" 158 | summary = "A deep merge function for 🐍." 159 | 160 | [[package]] 161 | name = "mkdocs" 162 | version = "1.3.0" 163 | requires_python = ">=3.6" 164 | summary = "Project documentation with Markdown." 165 | dependencies = [ 166 | "Jinja2>=2.10.2", 167 | "Markdown>=3.2.1", 168 | "PyYAML>=3.10", 169 | "click>=3.3", 170 | "ghp-import>=1.0", 171 | "importlib-metadata>=4.3", 172 | "mergedeep>=1.3.4", 173 | "packaging>=20.5", 174 | "pyyaml-env-tag>=0.1", 175 | "watchdog>=2.0", 176 | ] 177 | 178 | [[package]] 179 | name = "mkdocs-material" 180 | version = "8.3.8" 181 | requires_python = ">=3.7" 182 | summary = "Documentation that simply works" 183 | dependencies = [ 184 | "jinja2>=3.0.2", 185 | "markdown>=3.2", 186 | "mkdocs-material-extensions>=1.0.3", 187 | "mkdocs>=1.3.0", 188 | "pygments>=2.12", 189 | "pymdown-extensions>=9.4", 190 | ] 191 | 192 | [[package]] 193 | name = "mkdocs-material-extensions" 194 | version = "1.0.3" 195 | requires_python = ">=3.6" 196 | summary = "Extension pack for Python Markdown." 197 | 198 | [[package]] 199 | name = "nodeenv" 200 | version = "1.7.0" 201 | requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 202 | summary = "Node.js virtual environment builder" 203 | dependencies = [ 204 | "setuptools", 205 | ] 206 | 207 | [[package]] 208 | name = "nox" 209 | version = "2022.1.7" 210 | requires_python = ">=3.6" 211 | summary = "Flexible test automation." 212 | dependencies = [ 213 | "argcomplete<2.0,>=1.9.4", 214 | "colorlog<7.0.0,>=2.6.1", 215 | "importlib-metadata; python_version < \"3.8\"", 216 | "packaging>=20.9", 217 | "py<2.0.0,>=1.4.0", 218 | "typing-extensions>=3.7.4; python_version < \"3.8\"", 219 | "virtualenv>=14.0.0", 220 | ] 221 | 222 | [[package]] 223 | name = "packaging" 224 | version = "21.3" 225 | requires_python = ">=3.6" 226 | summary = "Core utilities for Python packages" 227 | dependencies = [ 228 | "pyparsing!=3.0.5,>=2.0.2", 229 | ] 230 | 231 | [[package]] 232 | name = "parver" 233 | version = "0.3.1" 234 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 235 | summary = "Parse and manipulate version numbers." 236 | dependencies = [ 237 | "arpeggio~=1.7", 238 | "attrs>=19.2", 239 | "six~=1.13", 240 | ] 241 | 242 | [[package]] 243 | name = "pdm" 244 | version = "1.15.4" 245 | requires_python = ">=3.7" 246 | summary = "Python Development Master" 247 | dependencies = [ 248 | "blinker", 249 | "click>=7", 250 | "findpython", 251 | "importlib-metadata; python_version < \"3.8\"", 252 | "installer<0.6,>=0.5.1", 253 | "packaging", 254 | "pdm-pep517<1,>=0.9", 255 | "pep517>=0.11.0", 256 | "pip>=20.1", 257 | "platformdirs", 258 | "python-dotenv>=0.15", 259 | "resolvelib<0.9,>=0.8", 260 | "shellingham>=1.3.2", 261 | "tomli>=1.1.0", 262 | "tomlkit<1,>=0.8.0", 263 | "typing-extensions; python_version < \"3.8\"", 264 | "wheel>=0.36.2", 265 | ] 266 | 267 | [[package]] 268 | name = "pdm-pep517" 269 | version = "0.12.7" 270 | requires_python = ">=3.7" 271 | summary = "A PEP 517 backend for PDM that supports PEP 621 metadata" 272 | 273 | [[package]] 274 | name = "pep517" 275 | version = "0.12.0" 276 | summary = "Wrappers to build Python packages using PEP 517 hooks" 277 | dependencies = [ 278 | "importlib-metadata; python_version < \"3.8\"", 279 | "tomli>=1.1.0; python_version >= \"3.6\"", 280 | "zipp; python_version < \"3.8\"", 281 | ] 282 | 283 | [[package]] 284 | name = "pip" 285 | version = "22.1.2" 286 | requires_python = ">=3.7" 287 | summary = "The PyPA recommended tool for installing Python packages." 288 | 289 | [[package]] 290 | name = "platformdirs" 291 | version = "2.5.2" 292 | requires_python = ">=3.7" 293 | summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 294 | 295 | [[package]] 296 | name = "pluggy" 297 | version = "1.0.0" 298 | requires_python = ">=3.6" 299 | summary = "plugin and hook calling mechanisms for python" 300 | dependencies = [ 301 | "importlib-metadata>=0.12; python_version < \"3.8\"", 302 | ] 303 | 304 | [[package]] 305 | name = "pre-commit" 306 | version = "2.19.0" 307 | requires_python = ">=3.7" 308 | summary = "A framework for managing and maintaining multi-language pre-commit hooks." 309 | dependencies = [ 310 | "cfgv>=2.0.0", 311 | "identify>=1.0.0", 312 | "importlib-metadata; python_version < \"3.8\"", 313 | "nodeenv>=0.11.1", 314 | "pyyaml>=5.1", 315 | "toml", 316 | "virtualenv>=20.0.8", 317 | ] 318 | 319 | [[package]] 320 | name = "py" 321 | version = "1.11.0" 322 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 323 | summary = "library with cross-python path, ini-parsing, io, code, log facilities" 324 | 325 | [[package]] 326 | name = "pygments" 327 | version = "2.12.0" 328 | requires_python = ">=3.6" 329 | summary = "Pygments is a syntax highlighting package written in Python." 330 | 331 | [[package]] 332 | name = "pymdown-extensions" 333 | version = "9.5" 334 | requires_python = ">=3.7" 335 | summary = "Extension pack for Python Markdown." 336 | dependencies = [ 337 | "markdown>=3.2", 338 | ] 339 | 340 | [[package]] 341 | name = "pyparsing" 342 | version = "3.0.9" 343 | requires_python = ">=3.6.8" 344 | summary = "pyparsing module - Classes and methods to define and execute parsing grammars" 345 | 346 | [[package]] 347 | name = "pytest" 348 | version = "7.1.2" 349 | requires_python = ">=3.7" 350 | summary = "pytest: simple powerful testing with Python" 351 | dependencies = [ 352 | "atomicwrites>=1.0; sys_platform == \"win32\"", 353 | "attrs>=19.2.0", 354 | "colorama; sys_platform == \"win32\"", 355 | "importlib-metadata>=0.12; python_version < \"3.8\"", 356 | "iniconfig", 357 | "packaging", 358 | "pluggy<2.0,>=0.12", 359 | "py>=1.8.2", 360 | "tomli>=1.0.0", 361 | ] 362 | 363 | [[package]] 364 | name = "pytest-mock" 365 | version = "3.8.1" 366 | requires_python = ">=3.7" 367 | summary = "Thin-wrapper around the mock package for easier use with pytest" 368 | dependencies = [ 369 | "pytest>=5.0", 370 | ] 371 | 372 | [[package]] 373 | name = "python-dateutil" 374 | version = "2.8.2" 375 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 376 | summary = "Extensions to the standard Python datetime module" 377 | dependencies = [ 378 | "six>=1.5", 379 | ] 380 | 381 | [[package]] 382 | name = "python-dotenv" 383 | version = "0.20.0" 384 | requires_python = ">=3.5" 385 | summary = "Read key-value pairs from a .env file and set them as environment variables" 386 | 387 | [[package]] 388 | name = "pyyaml" 389 | version = "6.0" 390 | requires_python = ">=3.6" 391 | summary = "YAML parser and emitter for Python" 392 | 393 | [[package]] 394 | name = "pyyaml-env-tag" 395 | version = "0.1" 396 | requires_python = ">=3.6" 397 | summary = "A custom YAML tag for referencing environment variables in YAML files. " 398 | dependencies = [ 399 | "pyyaml", 400 | ] 401 | 402 | [[package]] 403 | name = "resolvelib" 404 | version = "0.8.1" 405 | summary = "Resolve abstract dependencies into concrete ones" 406 | 407 | [[package]] 408 | name = "setuptools" 409 | version = "62.6.0" 410 | requires_python = ">=3.7" 411 | summary = "Easily download, build, install, upgrade, and uninstall Python packages" 412 | 413 | [[package]] 414 | name = "shellingham" 415 | version = "1.4.0" 416 | requires_python = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" 417 | summary = "Tool to Detect Surrounding Shell" 418 | 419 | [[package]] 420 | name = "six" 421 | version = "1.16.0" 422 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 423 | summary = "Python 2 and 3 compatibility utilities" 424 | 425 | [[package]] 426 | name = "toml" 427 | version = "0.10.2" 428 | requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 429 | summary = "Python Library for Tom's Obvious, Minimal Language" 430 | 431 | [[package]] 432 | name = "tomli" 433 | version = "2.0.1" 434 | requires_python = ">=3.7" 435 | summary = "A lil' TOML parser" 436 | 437 | [[package]] 438 | name = "tomlkit" 439 | version = "0.11.0" 440 | requires_python = ">=3.6,<4.0" 441 | summary = "Style preserving TOML library" 442 | 443 | [[package]] 444 | name = "towncrier" 445 | version = "21.9.0" 446 | summary = "Building newsfiles for your project." 447 | dependencies = [ 448 | "click", 449 | "click-default-group", 450 | "incremental", 451 | "jinja2", 452 | "setuptools", 453 | "tomli; python_version >= \"3.6\"", 454 | ] 455 | 456 | [[package]] 457 | name = "typing-extensions" 458 | version = "4.3.0" 459 | requires_python = ">=3.7" 460 | summary = "Backported and Experimental Type Hints for Python 3.7+" 461 | 462 | [[package]] 463 | name = "virtualenv" 464 | version = "20.15.1" 465 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 466 | summary = "Virtual Python Environment builder" 467 | dependencies = [ 468 | "distlib<1,>=0.3.1", 469 | "filelock<4,>=3.2", 470 | "importlib-metadata>=0.12; python_version < \"3.8\"", 471 | "platformdirs<3,>=2", 472 | "six<2,>=1.9.0", 473 | ] 474 | 475 | [[package]] 476 | name = "watchdog" 477 | version = "2.1.9" 478 | requires_python = ">=3.6" 479 | summary = "Filesystem events monitoring" 480 | 481 | [[package]] 482 | name = "wheel" 483 | version = "0.37.1" 484 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 485 | summary = "A built-package format for Python" 486 | 487 | [[package]] 488 | name = "zipp" 489 | version = "3.8.0" 490 | requires_python = ">=3.7" 491 | summary = "Backport of pathlib-compatible object wrapper for zip files" 492 | 493 | [metadata] 494 | lock_version = "3.1" 495 | content_hash = "sha256:eae2ab4408817e6bf2a8e3cd01d09a7f38769c41095a693ef22e2e0e13a75122" 496 | 497 | [metadata.files] 498 | "argcomplete 1.12.3" = [ 499 | {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, 500 | {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, 501 | ] 502 | "arpeggio 1.10.2" = [ 503 | {file = "Arpeggio-1.10.2-py2.py3-none-any.whl", hash = "sha256:fed68a1cb7f529cbd4d725597cc811b7506885fcdef17d4cdcf564341a1e210b"}, 504 | {file = "Arpeggio-1.10.2.tar.gz", hash = "sha256:bfe349f252f82f82d84cb886f1d5081d1a31451e6045275e9f90b65d0daa06f1"}, 505 | ] 506 | "atomicwrites 1.4.0" = [ 507 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 508 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 509 | ] 510 | "attrs 21.4.0" = [ 511 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 512 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 513 | ] 514 | "blinker 1.4" = [ 515 | {file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"}, 516 | ] 517 | "cfgv 3.3.1" = [ 518 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 519 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 520 | ] 521 | "click 8.1.3" = [ 522 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 523 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 524 | ] 525 | "click-default-group 1.2.2" = [ 526 | {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, 527 | ] 528 | "colorama 0.4.5" = [ 529 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 530 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 531 | ] 532 | "colorlog 6.6.0" = [ 533 | {file = "colorlog-6.6.0-py2.py3-none-any.whl", hash = "sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e"}, 534 | {file = "colorlog-6.6.0.tar.gz", hash = "sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8"}, 535 | ] 536 | "distlib 0.3.4" = [ 537 | {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, 538 | {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, 539 | ] 540 | "filelock 3.7.1" = [ 541 | {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, 542 | {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, 543 | ] 544 | "findpython 0.2.0" = [ 545 | {file = "findpython-0.2.0-py3-none-any.whl", hash = "sha256:110ec222a43aca3fcd154fd90b911f465c70e86787ae0532bab2266a95870fc9"}, 546 | {file = "findpython-0.2.0.tar.gz", hash = "sha256:c2099ee0b71fc2714b64f68fd1f40bc0ee47f49dfe9547fb64d7cbcc02fe0871"}, 547 | ] 548 | "ghp-import 2.1.0" = [ 549 | {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, 550 | {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, 551 | ] 552 | "identify 2.5.1" = [ 553 | {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"}, 554 | {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"}, 555 | ] 556 | "importlib-metadata 4.12.0" = [ 557 | {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, 558 | {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, 559 | ] 560 | "incremental 21.3.0" = [ 561 | {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, 562 | {file = "incremental-21.3.0.tar.gz", hash = "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57"}, 563 | ] 564 | "iniconfig 1.1.1" = [ 565 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 566 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 567 | ] 568 | "installer 0.5.1" = [ 569 | {file = "installer-0.5.1-py3-none-any.whl", hash = "sha256:1d6c8d916ed82771945b9c813699e6f57424ded970c9d8bf16bbc23e1e826ed3"}, 570 | {file = "installer-0.5.1.tar.gz", hash = "sha256:f970995ec2bb815e2fdaf7977b26b2091e1e386f0f42eafd5ac811953dc5d445"}, 571 | ] 572 | "jinja2 3.1.2" = [ 573 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 574 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 575 | ] 576 | "markdown 3.3.7" = [ 577 | {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, 578 | {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, 579 | ] 580 | "markupsafe 2.1.1" = [ 581 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, 582 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, 583 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, 584 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, 585 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, 586 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, 587 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, 588 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, 589 | {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, 590 | {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, 591 | {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, 592 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, 593 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, 594 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, 595 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, 596 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, 597 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, 598 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, 599 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, 600 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, 601 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, 602 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, 603 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, 604 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, 605 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, 606 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, 607 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, 608 | {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, 609 | {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, 610 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, 611 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, 612 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, 613 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, 614 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, 615 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, 616 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, 617 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, 618 | {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, 619 | {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, 620 | {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, 621 | ] 622 | "mergedeep 1.3.4" = [ 623 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 624 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 625 | ] 626 | "mkdocs 1.3.0" = [ 627 | {file = "mkdocs-1.3.0-py3-none-any.whl", hash = "sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde"}, 628 | {file = "mkdocs-1.3.0.tar.gz", hash = "sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"}, 629 | ] 630 | "mkdocs-material 8.3.8" = [ 631 | {file = "mkdocs_material-8.3.8-py2.py3-none-any.whl", hash = "sha256:949c75fa934d4b9ecc7b519964e58f0c9fc29f2ceb04736c85809cdbc403dfb5"}, 632 | {file = "mkdocs-material-8.3.8.tar.gz", hash = "sha256:b9cd305c3c29ef758931dae06e4aea0ca9f8bcc8ac6b2d45f10f932a015d6b83"}, 633 | ] 634 | "mkdocs-material-extensions 1.0.3" = [ 635 | {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, 636 | {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, 637 | ] 638 | "nodeenv 1.7.0" = [ 639 | {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, 640 | {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, 641 | ] 642 | "nox 2022.1.7" = [ 643 | {file = "nox-2022.1.7-py3-none-any.whl", hash = "sha256:efee12f02d39405b16d68f60e7a06fe1fc450ae58669d6cdda8c7f48e3bae9e3"}, 644 | {file = "nox-2022.1.7.tar.gz", hash = "sha256:b375238cebb0b9df2fab74b8d0ce1a50cd80df60ca2e13f38f539454fcd97d7e"}, 645 | ] 646 | "packaging 21.3" = [ 647 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 648 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 649 | ] 650 | "parver 0.3.1" = [ 651 | {file = "parver-0.3.1-py2.py3-none-any.whl", hash = "sha256:41a548c51b006a2f2522b54293cbfd2514bffa10774ece8430c9964a20cbd8b4"}, 652 | {file = "parver-0.3.1.tar.gz", hash = "sha256:c902e0653bcce927cc156a7fd9b3a51924cbce3bf3d0bfd49fc282bfd0c5dfd3"}, 653 | ] 654 | "pdm 1.15.4" = [ 655 | {file = "pdm-1.15.4-py3-none-any.whl", hash = "sha256:37e2a3238e857096f55b69302bcbef3dd84cf4a843ee669e8627633169e640ca"}, 656 | {file = "pdm-1.15.4.tar.gz", hash = "sha256:e68ec492c9c849c59468972576cef5ce88e72c49b9f575ea35314b2da50b9629"}, 657 | ] 658 | "pdm-pep517 0.12.7" = [ 659 | {file = "pdm_pep517-0.12.7-py3-none-any.whl", hash = "sha256:d01ac717a44863844ba3361fcfcd5acefd67f63ee8a4f5a549f0febe12d46cb5"}, 660 | {file = "pdm-pep517-0.12.7.tar.gz", hash = "sha256:6a4ed513edbcfd518e3b07e9118575d3fb35cc04551dd8acf3531e2463ef7fe3"}, 661 | ] 662 | "pep517 0.12.0" = [ 663 | {file = "pep517-0.12.0-py2.py3-none-any.whl", hash = "sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161"}, 664 | {file = "pep517-0.12.0.tar.gz", hash = "sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0"}, 665 | ] 666 | "pip 22.1.2" = [ 667 | {file = "pip-22.1.2-py3-none-any.whl", hash = "sha256:a3edacb89022ef5258bf61852728bf866632a394da837ca49eb4303635835f17"}, 668 | {file = "pip-22.1.2.tar.gz", hash = "sha256:6d55b27e10f506312894a87ccc59f280136bad9061719fac9101bdad5a6bce69"}, 669 | ] 670 | "platformdirs 2.5.2" = [ 671 | {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, 672 | {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, 673 | ] 674 | "pluggy 1.0.0" = [ 675 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 676 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 677 | ] 678 | "pre-commit 2.19.0" = [ 679 | {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, 680 | {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, 681 | ] 682 | "py 1.11.0" = [ 683 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 684 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 685 | ] 686 | "pygments 2.12.0" = [ 687 | {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, 688 | {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, 689 | ] 690 | "pymdown-extensions 9.5" = [ 691 | {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, 692 | {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, 693 | ] 694 | "pyparsing 3.0.9" = [ 695 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 696 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 697 | ] 698 | "pytest 7.1.2" = [ 699 | {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, 700 | {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, 701 | ] 702 | "pytest-mock 3.8.1" = [ 703 | {file = "pytest_mock-3.8.1-py3-none-any.whl", hash = "sha256:d989f11ca4a84479e288b0cd1e6769d6ad0d3d7743dcc75e460d1416a5f2135a"}, 704 | {file = "pytest-mock-3.8.1.tar.gz", hash = "sha256:2c6d756d5d3bf98e2e80797a959ca7f81f479e7d1f5f571611b0fdd6d1745240"}, 705 | ] 706 | "python-dateutil 2.8.2" = [ 707 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 708 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 709 | ] 710 | "python-dotenv 0.20.0" = [ 711 | {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"}, 712 | {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"}, 713 | ] 714 | "pyyaml 6.0" = [ 715 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 716 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 717 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 718 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 719 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 720 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 721 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 722 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 723 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 724 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 725 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 726 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 727 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 728 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 729 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 730 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 731 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 732 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 733 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 734 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 735 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 736 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 737 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 738 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 739 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 740 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 741 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 742 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 743 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 744 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 745 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 746 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 747 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 748 | ] 749 | "pyyaml-env-tag 0.1" = [ 750 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 751 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 752 | ] 753 | "resolvelib 0.8.1" = [ 754 | {file = "resolvelib-0.8.1-py2.py3-none-any.whl", hash = "sha256:d9b7907f055c3b3a2cfc56c914ffd940122915826ff5fb5b1de0c99778f4de98"}, 755 | {file = "resolvelib-0.8.1.tar.gz", hash = "sha256:c6ea56732e9fb6fca1b2acc2ccc68a0b6b8c566d8f3e78e0443310ede61dbd37"}, 756 | ] 757 | "setuptools 62.6.0" = [ 758 | {file = "setuptools-62.6.0-py3-none-any.whl", hash = "sha256:c1848f654aea2e3526d17fc3ce6aeaa5e7e24e66e645b5be2171f3f6b4e5a178"}, 759 | {file = "setuptools-62.6.0.tar.gz", hash = "sha256:990a4f7861b31532871ab72331e755b5f14efbe52d336ea7f6118144dd478741"}, 760 | ] 761 | "shellingham 1.4.0" = [ 762 | {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, 763 | {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, 764 | ] 765 | "six 1.16.0" = [ 766 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 767 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 768 | ] 769 | "toml 0.10.2" = [ 770 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 771 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 772 | ] 773 | "tomli 2.0.1" = [ 774 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 775 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 776 | ] 777 | "tomlkit 0.11.0" = [ 778 | {file = "tomlkit-0.11.0-py3-none-any.whl", hash = "sha256:0f4050db66fd445b885778900ce4dd9aea8c90c4721141fde0d6ade893820ef1"}, 779 | {file = "tomlkit-0.11.0.tar.gz", hash = "sha256:71ceb10c0eefd8b8f11fe34e8a51ad07812cb1dc3de23247425fbc9ddc47b9dd"}, 780 | ] 781 | "towncrier 21.9.0" = [ 782 | {file = "towncrier-21.9.0-py2.py3-none-any.whl", hash = "sha256:fc5a88a2a54988e3a8ed2b60d553599da8330f65722cc607c839614ed87e0f92"}, 783 | {file = "towncrier-21.9.0.tar.gz", hash = "sha256:9cb6f45c16e1a1eec9d0e7651165e7be60cd0ab81d13a5c96ca97a498ae87f48"}, 784 | ] 785 | "typing-extensions 4.3.0" = [ 786 | {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, 787 | {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, 788 | ] 789 | "virtualenv 20.15.1" = [ 790 | {file = "virtualenv-20.15.1-py2.py3-none-any.whl", hash = "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf"}, 791 | {file = "virtualenv-20.15.1.tar.gz", hash = "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4"}, 792 | ] 793 | "watchdog 2.1.9" = [ 794 | {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, 795 | {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, 796 | {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"}, 797 | {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"}, 798 | {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"}, 799 | {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"}, 800 | {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"}, 801 | {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"}, 802 | {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"}, 803 | {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"}, 804 | {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"}, 805 | {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"}, 806 | {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"}, 807 | {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"}, 808 | {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"}, 809 | {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"}, 810 | {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"}, 811 | {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"}, 812 | {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"}, 813 | {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"}, 814 | {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"}, 815 | {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"}, 816 | {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"}, 817 | {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"}, 818 | {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"}, 819 | ] 820 | "wheel 0.37.1" = [ 821 | {file = "wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a"}, 822 | {file = "wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4"}, 823 | ] 824 | "zipp 3.8.0" = [ 825 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 826 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 827 | ] 828 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pdm-pep517"] 3 | build-backend = "pdm.pep517.api" 4 | 5 | [project] 6 | name = "pdm-venv" 7 | description = "(Deprecated)A plugin for pdm that enables virtualenv management" 8 | authors = [ 9 | {name = "Frost Ming", email = "mianghong@gmail.com"} 10 | ] 11 | license = {text = "MIT"} 12 | readme = "README.md" 13 | requires-python = ">=3.7" 14 | dependencies = [ 15 | "virtualenv>=20.4", 16 | "platformdirs>=2.4", 17 | "pdm<2,>=1.13.0", 18 | ] 19 | 20 | dynamic = ["version"] 21 | 22 | classifiers = [ 23 | "Development Status :: 4 - Beta", 24 | "License :: OSI Approved :: MIT License", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3.7", 27 | "Programming Language :: Python :: 3.8", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | ] 31 | 32 | [project.urls] 33 | Repository = "https://github.com/pdm-project/pdm-venv" 34 | Homepage = "https://github.com/pdm-project/pdm-venv" 35 | Documentation = "https://pdm-project.github.io/pdm-venv" 36 | 37 | [project.optional-dependencies] 38 | doc = [ 39 | "mkdocs>=1.1", 40 | "mkdocs-material>=6.2" 41 | ] 42 | 43 | [project.entry-points.pdm] 44 | venv = "pdm_venv.plugin:entry_point" 45 | [tool.pdm] 46 | version = {source = "scm"} 47 | 48 | [tool.pdm.build] 49 | package-dir = "src" 50 | 51 | [tool.pdm.scripts] 52 | release = "python -m tasks.release" 53 | test = "pytest tests/" 54 | doc = {shell = "cd docs && mkdocs serve", help = "Start the dev server for doc preview"} 55 | lint = "pre-commit run --all-files" 56 | 57 | [tool.pdm.dev-dependencies] 58 | dev = [ 59 | "pytest>=6.1", 60 | "towncrier>=19.2", 61 | "parver>=0.3", 62 | "pytest-mock>=3.5", 63 | "pre-commit>=2.15", 64 | "nox>=2021.6", 65 | ] 66 | 67 | [tool.black] 68 | line-length = 88 69 | exclude = ''' 70 | /( 71 | \.eggs 72 | | \.git 73 | | \.hg 74 | | \.mypy_cache 75 | | \.tox 76 | | \.venv 77 | | _build 78 | | buck-out 79 | | build 80 | | dist 81 | | tests/fixtures 82 | )/ 83 | ''' 84 | 85 | [tool.towncrier] 86 | package = "pdm_venv" 87 | filename = "CHANGELOG.md" 88 | issue_format = "[#{issue}](https://github.com/pdm-project/pdm-venv/issues/{issue})" 89 | directory = "news/" 90 | start_string = "" 91 | title_format = "[v{version}](https://github.com/pdm-project/pdm-venv/releases/tag/{version}) ({project_date})" 92 | template = "news/towncrier_template.md" 93 | underlines = "-~^" 94 | 95 | [[tool.towncrier.type]] 96 | directory = "feature" 97 | name = "Features & Improvements" 98 | showcontent = true 99 | 100 | [[tool.towncrier.type]] 101 | directory = "bugfix" 102 | name = "Bug Fixes" 103 | showcontent = true 104 | 105 | [[tool.towncrier.type]] 106 | directory = "doc" 107 | name = "Improved Documentation" 108 | showcontent = true 109 | 110 | [[tool.towncrier.type]] 111 | directory = "dep" 112 | name = "Dependencies" 113 | showcontent = true 114 | 115 | [[tool.towncrier.type]] 116 | directory = "removal" 117 | name = "Removals and Deprecations" 118 | showcontent = true 119 | 120 | [[tool.towncrier.type]] 121 | directory = "misc" 122 | name = "Miscellany" 123 | showcontent = true 124 | 125 | [tool.isort] 126 | profile = "black" 127 | atomic = true 128 | skip_glob = ["*/setup.py"] 129 | filter_files = true 130 | known_first_party = ["pdm_venv"] 131 | 132 | 133 | [tool.pytest.ini_options] 134 | filterwarnings = [ 135 | "ignore::DeprecationWarning" 136 | ] 137 | -------------------------------------------------------------------------------- /src/pdm_venv/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | pdm-venv 3 | 4 | A plugin for pdm that enables virtualenv management 5 | :author: Frost Ming 6 | :license: MIT 7 | """ 8 | 9 | from typing import List 10 | 11 | __all__: List[str] = [] # noqa: WPS410 (the only __variable__ we use) 12 | -------------------------------------------------------------------------------- /src/pdm_venv/backends.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import shutil 3 | import subprocess 4 | import sys 5 | from pathlib import Path 6 | from typing import List, Mapping, Optional, Tuple, Type 7 | 8 | from pdm import Project, termui 9 | from pdm.exceptions import PdmUsageError, ProjectError 10 | from pdm.models.python import PythonInfo 11 | from pdm.utils import cached_property 12 | 13 | from pdm_venv.utils import get_venv_prefix 14 | 15 | 16 | class VirtualenvCreateError(ProjectError): 17 | pass 18 | 19 | 20 | class Backend(abc.ABC): 21 | """The base class for virtualenv backends""" 22 | 23 | def __init__(self, project: Project, python: Optional[str]) -> None: 24 | self.project = project 25 | self.python = python 26 | 27 | @cached_property 28 | def _resolved_interpreter(self) -> PythonInfo: 29 | if not self.python: 30 | return self.project.python 31 | try: 32 | return next(self.project.find_interpreters(self.python)) 33 | except StopIteration: 34 | raise VirtualenvCreateError(f"Can't find python interpreter {self.python}") 35 | 36 | @property 37 | def ident(self) -> str: 38 | """Get the identifier of this virtualenv. 39 | self.python can be one of: 40 | 3.8 41 | /usr/bin/python 42 | 3.9.0a4 43 | python3.8 44 | """ 45 | return self._resolved_interpreter.identifier 46 | 47 | def subprocess_call(self, cmd: List[str], **kwargs) -> None: 48 | self.project.core.ui.echo(f"Run command: {cmd}", verbosity=termui.DETAIL) 49 | try: 50 | subprocess.check_call(cmd) 51 | except subprocess.CalledProcessError as e: 52 | raise VirtualenvCreateError(e) from None 53 | 54 | def _ensure_clean(self, location: Path, force: bool = False) -> None: 55 | if not location.exists(): 56 | return 57 | if not force: 58 | raise VirtualenvCreateError(f"The location {location} is not empty") 59 | self.project.core.ui.echo( 60 | f"Cleaning existing target directory {location}", err=True 61 | ) 62 | shutil.rmtree(location) 63 | 64 | def get_location(self, name: Optional[str]) -> Path: 65 | venv_parent = Path(self.project.config["venv.location"]) 66 | if not venv_parent.is_dir(): 67 | venv_parent.mkdir(exist_ok=True, parents=True) 68 | return venv_parent / f"{get_venv_prefix(self.project)}{name or self.ident}" 69 | 70 | def create( 71 | self, 72 | name: Optional[str] = None, 73 | args: Tuple[str] = (), 74 | force: bool = False, 75 | in_project: bool = False, 76 | ) -> Path: 77 | if in_project: 78 | location = self.project.root / ".venv" 79 | else: 80 | location = self.get_location(name) 81 | self._ensure_clean(location, force) 82 | self.perform_create(location, args) 83 | return location 84 | 85 | @abc.abstractmethod 86 | def perform_create(self, location: Path, args: Tuple[str] = ()) -> Path: 87 | pass 88 | 89 | 90 | class VirtualenvBackend(Backend): 91 | def perform_create(self, location: Path, args: Tuple[str] = ()) -> Path: 92 | cmd = [sys.executable, "-m", "virtualenv", str(location)] 93 | cmd.extend(["-p", str(self._resolved_interpreter.executable)]) 94 | cmd.extend(args) 95 | self.subprocess_call(cmd) 96 | 97 | 98 | class VenvBackend(VirtualenvBackend): 99 | def perform_create(self, location: Path, args: Tuple[str]) -> Path: 100 | cmd = [ 101 | str(self._resolved_interpreter.executable), 102 | "-m", 103 | "venv", 104 | str(location), 105 | ] + list(args) 106 | self.subprocess_call(cmd) 107 | 108 | 109 | class CondaBackend(Backend): 110 | @property 111 | def ident(self) -> str: 112 | # Conda supports specifying python that doesn't exist, 113 | # use the passed-in name directly 114 | if self.python: 115 | return self.python 116 | return super().ident 117 | 118 | def perform_create(self, location: Path, args: Tuple[str]) -> Path: 119 | if self.python: 120 | python_ver = self.python 121 | else: 122 | python = self._resolved_interpreter 123 | python_ver = f"{python.major}.{python.minor}" 124 | if any(arg.startswith("python=") for arg in args): 125 | raise PdmUsageError("Cannot use python= in conda creation arguments") 126 | cmd = [ 127 | "conda", 128 | "create", 129 | "--yes", 130 | "--prefix", 131 | str(location), 132 | # Ensure the pip package is installed. 133 | "pip", 134 | f"python={python_ver}", 135 | *args, 136 | ] 137 | 138 | self.subprocess_call(cmd) 139 | 140 | 141 | BACKENDS: Mapping[str, Type[Backend]] = { 142 | "virtualenv": VirtualenvBackend, 143 | "venv": VenvBackend, 144 | "conda": CondaBackend, 145 | } 146 | -------------------------------------------------------------------------------- /src/pdm_venv/commands/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from pdm import Project 4 | from pdm.cli.commands.base import BaseCommand 5 | 6 | from pdm_venv.commands.activate import ActivateCommand 7 | from pdm_venv.commands.create import CreateCommand 8 | from pdm_venv.commands.list import ListCommand 9 | from pdm_venv.commands.purge import PurgeCommand 10 | from pdm_venv.commands.remove import RemoveCommand 11 | 12 | 13 | class VenvCommand(BaseCommand): 14 | """Virtualenv management""" 15 | 16 | name = "venv" 17 | arguments = [] 18 | 19 | def add_arguments(self, parser: argparse.ArgumentParser) -> None: 20 | subparser = parser.add_subparsers() 21 | CreateCommand.register_to(subparser, "create") 22 | ListCommand.register_to(subparser, "list") 23 | RemoveCommand.register_to(subparser, "remove") 24 | ActivateCommand.register_to(subparser, "activate") 25 | PurgeCommand.register_to(subparser, "purge") 26 | self.parser = parser 27 | 28 | def handle(self, project: Project, options: argparse.Namespace) -> None: 29 | self.parser.print_help() 30 | -------------------------------------------------------------------------------- /src/pdm_venv/commands/activate.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import shlex 3 | from pathlib import Path 4 | 5 | import shellingham 6 | from pdm import termui 7 | from pdm.cli.commands.base import BaseCommand 8 | from pdm.cli.options import verbose_option 9 | from pdm.project import Project 10 | from pdm.utils import is_venv_python 11 | 12 | from pdm_venv.utils import BIN_DIR, iter_venvs 13 | 14 | 15 | class ActivateCommand(BaseCommand): 16 | """Activate the virtualenv with the given name""" 17 | 18 | arguments = [verbose_option] 19 | 20 | def add_arguments(self, parser: argparse.ArgumentParser) -> None: 21 | parser.add_argument("env", nargs="?", help="The key of the virtualenv") 22 | 23 | def handle(self, project: Project, options: argparse.Namespace) -> None: 24 | if options.env: 25 | venv = next( 26 | (venv for key, venv in iter_venvs(project) if key == options.env), None 27 | ) 28 | if not venv: 29 | project.core.ui.echo( 30 | termui.yellow(f"No virtualenv with key {options.env} is found"), 31 | err=True, 32 | ) 33 | raise SystemExit(1) 34 | else: 35 | # Use what is saved in .pdm.toml 36 | interpreter = project.python_executable 37 | if is_venv_python(interpreter): 38 | venv = Path(interpreter).parent.parent 39 | else: 40 | project.core.ui.echo( 41 | termui.yellow( 42 | f"Can't activate a non-venv Python{interpreter}, " 43 | "you can specify one with pdm venv activate " 44 | ) 45 | ) 46 | raise SystemExit(1) 47 | project.core.ui.echo(self.get_activate_command(venv)) 48 | 49 | def get_activate_command(self, venv: Path) -> str: 50 | shell, _ = shellingham.detect_shell() 51 | if shell == "fish": 52 | command, filename = "source", "activate.fish" 53 | elif shell == "csh": 54 | command, filename = "source", "activate.csh" 55 | elif shell in ["powershell", "pwsh"]: 56 | command, filename = ".", "Activate.ps1" 57 | else: 58 | command, filename = "source", "activate" 59 | activate_script = venv / BIN_DIR / filename 60 | if activate_script.exists(): 61 | return f"{command} {shlex.quote(str(activate_script))}" 62 | # Conda backed virtualenvs don't have activate scripts 63 | return f"conda activate {shlex.quote(str(venv))}" 64 | -------------------------------------------------------------------------------- /src/pdm_venv/commands/create.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from pdm import BaseCommand, Project, termui 4 | from pdm.cli.options import verbose_option 5 | 6 | from pdm_venv.backends import BACKENDS 7 | 8 | 9 | class CreateCommand(BaseCommand): 10 | """Create a virtualenv 11 | 12 | pdm venv create [-other args] 13 | """ 14 | 15 | description = "Create a virtualenv" 16 | arguments = [verbose_option] 17 | 18 | def add_arguments(self, parser: argparse.ArgumentParser) -> None: 19 | parser.add_argument( 20 | "-w", 21 | "--with", 22 | dest="backend", 23 | choices=BACKENDS.keys(), 24 | help="Specify the backend to create the virtualenv", 25 | ) 26 | parser.add_argument( 27 | "-f", 28 | "--force", 29 | action="store_true", 30 | help="Recreate if the virtualenv already exists", 31 | ) 32 | parser.add_argument("-n", "--name", help="Specify the name of the virtualenv") 33 | parser.add_argument( 34 | "python", 35 | nargs="?", 36 | help="Specify which python should be used to create the virtualenv", 37 | ) 38 | parser.add_argument( 39 | "venv_args", 40 | nargs=argparse.REMAINDER, 41 | help="Additional arguments that will be passed to the backend", 42 | ) 43 | 44 | def handle(self, project: Project, options: argparse.Namespace) -> None: 45 | in_project = ( 46 | project.config["venv.in_project"] if not options.name else False 47 | ) # ignore venv.in_project flag if name is given 48 | backend: str = options.backend or project.config["venv.backend"] 49 | venv_backend = BACKENDS[backend](project, options.python) 50 | with project.core.ui.open_spinner( 51 | f"Creating virtualenv using {backend}..." 52 | ) as spinner: 53 | path = venv_backend.create( 54 | options.name, options.venv_args, options.force, in_project 55 | ) 56 | spinner.succeed( 57 | f"Virtualenv {termui.green(str(path))} is created successfully" 58 | ) 59 | -------------------------------------------------------------------------------- /src/pdm_venv/commands/list.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from pathlib import Path 3 | 4 | from pdm import termui 5 | from pdm.cli.commands.base import BaseCommand 6 | from pdm.cli.options import verbose_option 7 | from pdm.project import Project 8 | 9 | from pdm_venv.utils import iter_venvs 10 | 11 | 12 | class ListCommand(BaseCommand): 13 | """List all virtualenvs associated with this project""" 14 | 15 | arguments = [verbose_option] 16 | 17 | def handle(self, project: Project, options: argparse.Namespace) -> None: 18 | project.core.ui.echo("Virtualenvs created with this project:\n") 19 | for ident, venv in iter_venvs(project): 20 | saved_python = project.project_config.get("python.path") 21 | if saved_python and Path(saved_python).parent.parent == venv: 22 | mark = "*" 23 | else: 24 | mark = "-" 25 | project.core.ui.echo(f"{mark} {termui.green(ident)}: {venv}") 26 | -------------------------------------------------------------------------------- /src/pdm_venv/commands/purge.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import shutil 3 | 4 | import click 5 | from pdm import Project, termui 6 | from pdm.cli.commands.base import BaseCommand 7 | from pdm.cli.options import verbose_option 8 | 9 | from pdm_venv.utils import get_all_venvs 10 | 11 | 12 | class PurgeCommand(BaseCommand): 13 | """Purge selected/all created Virtualenvs""" 14 | 15 | arguments = [verbose_option] 16 | 17 | def add_arguments(self, parser: argparse.ArgumentParser) -> None: 18 | parser.add_argument( 19 | "-f", 20 | "--force", 21 | action="store_true", 22 | help="Force purging without prompting for confirmation", 23 | ) 24 | parser.add_argument( 25 | "-i", 26 | "--interactive", 27 | action="store_true", 28 | help="Interatively purge selected Virtualenvs", 29 | ) 30 | 31 | def handle(self, project: Project, options: argparse.Namespace) -> None: 32 | 33 | if not options.force: 34 | project.core.ui.echo( 35 | termui.red("The following Virtualenvs will be purged:") 36 | ) 37 | for i, venv in enumerate(get_all_venvs(project)): 38 | project.core.ui.echo(f"{i}. {termui.green(venv[0])}") 39 | 40 | if not options.interactive and ( 41 | options.force or click.confirm(termui.yellow("continue? ")) 42 | ): 43 | self.del_all_venvs(project) 44 | 45 | if options.interactive: 46 | selection = click.prompt( 47 | "Please select", 48 | type=click.Choice( 49 | [str(i) for i in range(len(list(get_all_venvs(project))))] 50 | + ["all", "none"] 51 | ), 52 | default="none", 53 | show_choices=False, 54 | ) 55 | 56 | if selection == "all": 57 | self.del_all_venvs(project) 58 | elif selection != "none": 59 | for i, venv in enumerate(get_all_venvs(project)): 60 | if i == int(selection): 61 | shutil.rmtree(venv[1]) 62 | project.core.ui.echo("Purged successfully!") 63 | 64 | def del_all_venvs(self, project): 65 | for _, venv in get_all_venvs(project): 66 | shutil.rmtree(venv) 67 | project.core.ui.echo("Purged successfully!") 68 | -------------------------------------------------------------------------------- /src/pdm_venv/commands/remove.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import shutil 3 | from pathlib import Path 4 | 5 | import click 6 | from pdm import Project, termui 7 | from pdm.cli.commands.base import BaseCommand 8 | from pdm.cli.options import verbose_option 9 | 10 | from pdm_venv.utils import iter_venvs 11 | 12 | 13 | class RemoveCommand(BaseCommand): 14 | """Remove the virtualenv with the given name""" 15 | 16 | arguments = [verbose_option] 17 | 18 | def add_arguments(self, parser: argparse.ArgumentParser) -> None: 19 | parser.add_argument( 20 | "-y", 21 | "--yes", 22 | action="store_true", 23 | help="Answer yes on the following question", 24 | ) 25 | parser.add_argument("env", help="The key of the virtualenv") 26 | 27 | def handle(self, project: Project, options: argparse.Namespace) -> None: 28 | project.core.ui.echo("Virtualenvs created with this project:") 29 | for ident, venv in iter_venvs(project): 30 | if ident == options.env: 31 | if options.yes or click.confirm( 32 | termui.yellow(f"Will remove: {venv}, continue?") 33 | ): 34 | shutil.rmtree(venv) 35 | if ( 36 | project.project_config.get("python.path") 37 | and Path(project.project_config["python.path"]).parent.parent 38 | == venv 39 | ): 40 | del project.project_config["python.path"] 41 | project.core.ui.echo("Removed successfully!") 42 | break 43 | else: 44 | project.core.ui.echo( 45 | termui.yellow(f"No virtualenv with key {options.env} is found"), 46 | err=True, 47 | ) 48 | raise SystemExit(1) 49 | -------------------------------------------------------------------------------- /src/pdm_venv/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import platformdirs 4 | from pdm.project import ConfigItem 5 | from pdm.project.config import ensure_boolean 6 | 7 | venv_configs = { 8 | "venv.location": ConfigItem( 9 | "Parent directory for virtualenvs", 10 | os.path.join(platformdirs.user_data_dir("pdm"), "venvs"), 11 | global_only=True, 12 | ), 13 | "venv.backend": ConfigItem( 14 | "Default backend to create virtualenv", default="virtualenv" 15 | ), 16 | "venv.in_project": ConfigItem( 17 | "Create virtualenv in `.venv` under project root", 18 | default=False, 19 | env_var="PDM_VENV_IN_PROJECT", 20 | coerce=ensure_boolean, 21 | ), 22 | # Override the default use_venv value to True 23 | "python.use_venv": ConfigItem( 24 | "Install packages into the activated venv site packages instead of PEP 582", 25 | True, 26 | env_var="PDM_USE_VENV", 27 | coerce=ensure_boolean, 28 | replace="use_venv", 29 | ), 30 | } 31 | -------------------------------------------------------------------------------- /src/pdm_venv/plugin.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from typing import Iterable, Optional 4 | 5 | from pdm import Project as PdmProject 6 | from pdm import termui 7 | from pdm.core import Core 8 | from pdm.models.environment import Environment, GlobalEnvironment 9 | from pdm.models.python import PythonInfo 10 | from pdm.models.specifiers import PySpecSet 11 | from pdm.utils import is_venv_python 12 | 13 | from pdm_venv.backends import BACKENDS 14 | from pdm_venv.commands import VenvCommand 15 | from pdm_venv.config import venv_configs 16 | from pdm_venv.utils import get_venv_python, iter_venvs 17 | 18 | 19 | class Project(PdmProject): 20 | def find_interpreters( 21 | self, python_spec: Optional[str] = None 22 | ) -> Iterable[PythonInfo]: 23 | 24 | for _, venv in iter_venvs(self): 25 | python = get_venv_python(venv).as_posix() 26 | py_version = PythonInfo.from_path(python) 27 | if not python_spec: 28 | yield py_version 29 | elif all(d.isdigit() for d in python_spec.split(".")): 30 | desired = tuple(int(d) for d in python_spec.split(".")) 31 | if py_version.version_tuple[: len(desired)] == desired: 32 | yield py_version 33 | 34 | yield from super().find_interpreters(python_spec) 35 | 36 | def get_environment(self) -> Environment: 37 | if self.is_global: 38 | env = GlobalEnvironment(self) 39 | # Rewrite global project's python requires to be 40 | # compatible with the exact version 41 | env.python_requires = PySpecSet(f"=={self.python.version}") 42 | return env 43 | if self.config["python.use_venv"]: 44 | if self.project_config.get("python.path") and not os.getenv( 45 | "PDM_IGNORE_SAVED_PYTHON" 46 | ): 47 | return ( 48 | GlobalEnvironment(self) 49 | if is_venv_python(self.python.executable) 50 | else Environment(self) 51 | ) 52 | if os.getenv("VIRTUAL_ENV"): 53 | venv = os.getenv("VIRTUAL_ENV") 54 | self.core.ui.echo( 55 | f"Detected inside an active virtualenv {termui.green(venv)}, " 56 | "reuse it." 57 | ) 58 | # Temporary usage, do not save in .pdm.toml 59 | self._python = PythonInfo.from_path(get_venv_python(Path(venv))) 60 | return GlobalEnvironment(self) 61 | existing_venv = next((venv for _, venv in iter_venvs(self)), None) 62 | if existing_venv: 63 | self.core.ui.echo( 64 | f"Virtualenv {termui.green(str(existing_venv))} is reused.", 65 | err=True, 66 | ) 67 | path = existing_venv 68 | else: 69 | # Create a virtualenv using the selected Python interpreter 70 | self.core.ui.echo( 71 | "python.use_venv is on, creating a virtualenv for this project...", 72 | fg="yellow", 73 | err=True, 74 | ) 75 | backend: str = self.config["venv.backend"] 76 | venv_backend = BACKENDS[backend](self, None) 77 | path = venv_backend.create( 78 | None, (), False, self.config["venv.in_project"] 79 | ) 80 | self.core.ui.echo(f"Virtualenv {path} is created successfully") 81 | self.python = PythonInfo.from_path(get_venv_python(path)) 82 | return GlobalEnvironment(self) 83 | else: 84 | return Environment(self) 85 | 86 | 87 | def entry_point(core: Core) -> None: 88 | core.register_command(VenvCommand) 89 | for name, item in venv_configs.items(): 90 | core.add_config(name, item) 91 | core.project_class = Project 92 | -------------------------------------------------------------------------------- /src/pdm_venv/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import sys 4 | from pathlib import Path 5 | from typing import Iterable, Tuple 6 | 7 | from pdm.project import Project 8 | from pdm.utils import get_in_project_venv_python 9 | 10 | IS_WIN = sys.platform == "win32" 11 | BIN_DIR = "Scripts" if IS_WIN else "bin" 12 | 13 | 14 | def hash_path(path: str) -> str: 15 | """Generate a hash for the given path.""" 16 | return base64.urlsafe_b64encode(hashlib.md5(path.encode()).digest()).decode()[:8] 17 | 18 | 19 | def get_venv_prefix(project: Project) -> str: 20 | """Get the venv prefix for the project""" 21 | path = project.root 22 | name_hash = hash_path(path.as_posix()) 23 | return f"{path.name}-{name_hash}-" 24 | 25 | 26 | def iter_venvs(project: Project) -> Iterable[Tuple[str, Path]]: 27 | """Return an iterable of venv paths associated with the project""" 28 | in_project_venv_python = get_in_project_venv_python(project.root) 29 | if in_project_venv_python is not None: 30 | yield "in-project", Path(in_project_venv_python).parent.parent 31 | venv_prefix = get_venv_prefix(project) 32 | venv_parent = Path(project.config["venv.location"]) 33 | for venv in venv_parent.glob(f"{venv_prefix}*"): 34 | ident = venv.name[len(venv_prefix) :] 35 | yield ident, venv 36 | 37 | 38 | def get_venv_python(venv: Path) -> Path: 39 | """Get the interpreter path inside the given venv.""" 40 | suffix = ".exe" if IS_WIN else "" 41 | return venv / BIN_DIR / f"python{suffix}" 42 | 43 | 44 | def get_all_venvs(project: Project) -> Iterable[Tuple[str, Path]]: 45 | """Return a list of all created venv paths""" 46 | venv_parent = Path(project.config["venv.location"]) 47 | for venv in venv_parent.glob("*"): 48 | ident = venv.name 49 | yield ident, venv 50 | -------------------------------------------------------------------------------- /tasks/release.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import subprocess 3 | import sys 4 | from pathlib import Path 5 | 6 | import parver 7 | 8 | PROJECT_DIR = Path(__file__).parent.parent 9 | 10 | 11 | def get_current_version(): 12 | cmd = ["git", "describe", "--tags", "--abbrev=0"] 13 | return subprocess.check_output(cmd).decode("utf-8").strip() 14 | 15 | 16 | def bump_version(pre=None, major=False, minor=False, patch=True): 17 | if not any([major, minor, patch]): 18 | patch = True 19 | if len([v for v in [major, minor, patch] if v]) != 1: 20 | print( 21 | "Only one option should be provided among " "(--major, --minor, --patch)", 22 | file=sys.stderr, 23 | ) 24 | sys.exit(1) 25 | current_version = parver.Version.parse(get_current_version()) 26 | if not pre: 27 | version_idx = [major, minor, patch].index(True) 28 | version = current_version.bump_release(index=version_idx).replace( 29 | pre=None, post=None 30 | ) 31 | else: 32 | version = current_version.bump_pre(pre) 33 | version = version.replace(local=None, dev=None) 34 | return str(version) 35 | 36 | 37 | def release(dry_run=False, commit=True, pre=None, major=False, minor=False, patch=True): 38 | new_version = bump_version(pre, major, minor, patch) 39 | print(f"Bump version to: {new_version}") 40 | if dry_run: 41 | subprocess.check_call(["towncrier", "--version", new_version, "--draft"]) 42 | else: 43 | subprocess.check_call(["towncrier", "--yes", "--version", new_version]) 44 | subprocess.check_call(["git", "add", "."]) 45 | if commit: 46 | subprocess.check_call(["git", "commit", "-m", f"Release {new_version}"]) 47 | subprocess.check_call( 48 | ["git", "tag", "-a", new_version, "-m", f"v{new_version}"] 49 | ) 50 | subprocess.check_call(["git", "push"]) 51 | subprocess.check_call(["git", "push", "--tags"]) 52 | 53 | 54 | def parse_args(argv=None): 55 | parser = argparse.ArgumentParser("release.py") 56 | 57 | parser.add_argument("--dry-run", action="store_true", help="Dry run mode") 58 | parser.add_argument( 59 | "--no-commit", 60 | action="store_false", 61 | dest="commit", 62 | default=True, 63 | help="Do not commit to Git", 64 | ) 65 | group = parser.add_argument_group(title="version part") 66 | group.add_argument("--pre", help="Pre tag") 67 | group.add_argument("--major", action="store_true", help="Bump major version") 68 | group.add_argument("--minor", action="store_true", help="Bump minor version") 69 | group.add_argument("--patch", action="store_true", help="Bump patch version") 70 | 71 | return parser.parse_args(argv) 72 | 73 | 74 | if __name__ == "__main__": 75 | args = parse_args() 76 | release(args.dry_run, args.commit, args.pre, args.major, args.minor, args.patch) 77 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests suite for `pdm_venv`.""" 2 | 3 | from pathlib import Path 4 | 5 | TESTS_DIR = Path(__file__).parent 6 | FIXTURES_DIR = TESTS_DIR / "fixtures" 7 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Configuration for the pytest test suite.""" 2 | import functools 3 | import os 4 | 5 | import pytest 6 | from click.testing import CliRunner 7 | from pdm.core import Core 8 | from pdm.utils import cd 9 | 10 | DUMMY_PYPROJECT = """[project] 11 | requires-python = ">=3.6" 12 | dependencies = [] 13 | """ 14 | 15 | 16 | @pytest.fixture 17 | def isolated(tmp_path, mocker, monkeypatch): 18 | tmp_path.joinpath("pyproject.toml").write_text(DUMMY_PYPROJECT) 19 | mocker.patch("pathlib.Path.home", return_value=tmp_path) 20 | monkeypatch.delenv("VIRTUAL_ENV", raising=False) 21 | return tmp_path 22 | 23 | 24 | @pytest.fixture 25 | def project(isolated): 26 | core = Core() 27 | core.init_parser() 28 | core.load_plugins() 29 | p = core.create_project(isolated) 30 | p.root.joinpath(".global").mkdir() 31 | p.GLOBAL_PROJECT = p.root.joinpath(".global/project") 32 | p.global_config["venv.location"] = str(isolated / "venvs") 33 | p.global_config["venv.backend"] = os.getenv("VENV_BACKEND", "virtualenv") 34 | return p 35 | 36 | 37 | @pytest.fixture 38 | def invoke(isolated, monkeypatch): 39 | runner = CliRunner(mix_stderr=False) 40 | core = Core() 41 | invoker = functools.partial(runner.invoke, core, prog_name="pdm") 42 | monkeypatch.delenv("PDM_IGNORE_SAVED_PYTHON", raising=False) 43 | with cd(isolated): 44 | invoker(["config", "venv.location", str(isolated / "venvs")]) 45 | invoker(["config", "venv.backend", os.getenv("VENV_BACKEND", "virtualenv")]) 46 | if (isolated / ".pdm.toml").exists(): 47 | (isolated / ".pdm.toml").unlink() 48 | yield invoker 49 | -------------------------------------------------------------------------------- /tests/test_backends.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pdm_venv.backends import CondaBackend, VenvBackend, VirtualenvBackend 4 | 5 | 6 | def test_virtualenv_backend_create(project, mocker): 7 | interpreter = project.python_executable 8 | backend = VirtualenvBackend(project, None) 9 | assert backend.ident 10 | mock_call = mocker.patch("subprocess.check_call") 11 | location = backend.create() 12 | mock_call.assert_called_once_with( 13 | [sys.executable, "-m", "virtualenv", str(location), "-p", interpreter] 14 | ) 15 | 16 | 17 | def test_venv_backend_create(project, mocker): 18 | interpreter = project.python_executable 19 | backend = VenvBackend(project, None) 20 | assert backend.ident 21 | mock_call = mocker.patch("subprocess.check_call") 22 | location = backend.create() 23 | mock_call.assert_called_once_with([interpreter, "-m", "venv", str(location)]) 24 | 25 | 26 | def test_conda_backend_create(project, mocker): 27 | backend = CondaBackend(project, "3.8") 28 | assert backend.ident == "3.8" 29 | mock_call = mocker.patch("subprocess.check_call") 30 | location = backend.create() 31 | mock_call.assert_called_once_with( 32 | ["conda", "create", "--yes", "--prefix", str(location), "pip", "python=3.8"] 33 | ) 34 | 35 | backend = CondaBackend(project, None) 36 | python_version = f"{sys.version_info.major}.{sys.version_info.minor}" 37 | assert backend.ident == python_version 38 | location = backend.create() 39 | mock_call.assert_called_with( 40 | [ 41 | "conda", 42 | "create", 43 | "--yes", 44 | "--prefix", 45 | str(location), 46 | "pip", 47 | f"python={python_version}", 48 | ] 49 | ) 50 | -------------------------------------------------------------------------------- /tests/test_commands.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | import pytest 5 | 6 | 7 | def test_venv_create(invoke): 8 | result = invoke(["venv", "create"]) 9 | assert result.exit_code == 0, result.stderr 10 | venv_path = re.match( 11 | r"Virtualenv (.+) is created successfully", result.output 12 | ).group(1) 13 | assert os.path.exists(venv_path) 14 | 15 | 16 | def test_venv_list(invoke): 17 | result = invoke(["venv", "create"]) 18 | assert result.exit_code == 0, result.stderr 19 | venv_path = re.match( 20 | r"Virtualenv (.+) is created successfully", result.output 21 | ).group(1) 22 | 23 | result = invoke(["venv", "list"]) 24 | assert result.exit_code == 0, result.stderr 25 | assert venv_path in result.output 26 | 27 | 28 | def test_venv_remove(invoke): 29 | result = invoke(["venv", "create"]) 30 | assert result.exit_code == 0, result.stderr 31 | venv_path = re.match( 32 | r"Virtualenv (.+) is created successfully", result.output 33 | ).group(1) 34 | key = venv_path.rsplit("-", 1)[-1] 35 | 36 | result = invoke(["venv", "remove", "non-exist"]) 37 | assert result.exit_code != 0 38 | 39 | result = invoke(["venv", "remove", "-y", key]) 40 | assert result.exit_code == 0, result.stderr 41 | 42 | assert not os.path.exists(venv_path) 43 | 44 | 45 | def test_venv_recreate(invoke): 46 | invoke(["use", "-f", "python"]) 47 | result = invoke(["venv", "create"]) 48 | assert result.exit_code == 0, result.stderr 49 | 50 | result = invoke(["venv", "create"]) 51 | assert result.exit_code != 0 52 | 53 | result = invoke(["venv", "create", "-f"]) 54 | assert result.exit_code == 0, result.stderr 55 | 56 | 57 | def test_venv_activate(invoke, mocker): 58 | result = invoke(["venv", "create"]) 59 | assert result.exit_code == 0, result.stderr 60 | venv_path = re.match( 61 | r"Virtualenv (.+) is created successfully", result.output 62 | ).group(1) 63 | key = venv_path.rsplit("-", 1)[-1] 64 | 65 | mocker.patch("shellingham.detect_shell", return_value=("bash", None)) 66 | result = invoke(["venv", "activate", key]) 67 | assert result.exit_code == 0, result.stderr 68 | 69 | if os.getenv("DEFAULT_BACKEND") == "conda": 70 | assert result.output.startswith("conda activate") 71 | else: 72 | assert result.output.strip("'\"\n").endswith("activate") 73 | assert result.output.startswith("source") 74 | 75 | 76 | def test_venv_auto_create(invoke, mocker): 77 | creator = mocker.patch("pdm_venv.backends.Backend.create") 78 | invoke(["install"]) 79 | creator.assert_called_once() 80 | 81 | 82 | def test_venv_purge(invoke, mocker): 83 | result = invoke(["venv", "create"]) 84 | assert result.exit_code == 0, result.stderr 85 | venv_path = re.match( 86 | r"Virtualenv (.+) is created successfully", result.output 87 | ).group(1) 88 | result = invoke(["venv", "purge"], input="y") 89 | assert result.exit_code == 0, result.stderr 90 | assert not os.path.exists(venv_path) 91 | 92 | 93 | def test_venv_purge_force(invoke, mocker): 94 | result = invoke(["venv", "create"]) 95 | assert result.exit_code == 0, result.stderr 96 | venv_path = re.match( 97 | r"Virtualenv (.+) is created successfully", result.output 98 | ).group(1) 99 | result = invoke(["venv", "purge", "-f"]) 100 | assert result.exit_code == 0, result.stderr 101 | assert not os.path.exists(venv_path) 102 | 103 | 104 | user_options = [("none", True), ("0", False), ("all", False)] 105 | 106 | 107 | @pytest.mark.parametrize("user_choices, is_path_exists", user_options) 108 | def test_venv_purge_interactive(invoke, mocker, user_choices, is_path_exists): 109 | result = invoke(["venv", "create"]) 110 | assert result.exit_code == 0, result.stderr 111 | venv_path = re.match( 112 | r"Virtualenv (.+) is created successfully", result.output 113 | ).group(1) 114 | result = invoke(["venv", "purge", "-i"], input=user_choices) 115 | assert result.exit_code == 0, result.stderr 116 | assert os.path.exists(venv_path) == is_path_exists 117 | -------------------------------------------------------------------------------- /tests/test_project.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def test_use_venv_default_to_true(invoke): 5 | result = invoke(["config", "use_venv"]) 6 | assert result.output.strip() == "True" 7 | 8 | 9 | def test_create_venv_first_time(invoke, project): 10 | result = invoke(["install"]) 11 | assert result.exit_code == 0 12 | venv_parent = project.root / "venvs" 13 | venv_path = next(venv_parent.iterdir(), None) 14 | assert venv_path is not None 15 | 16 | assert Path(project.project_config["python.path"]).relative_to(venv_path) 17 | 18 | 19 | def test_create_venv_in_project(invoke, project): 20 | project.project_config["venv.in_project"] = True 21 | result = invoke(["install"]) 22 | assert result.exit_code == 0 23 | assert project.root.joinpath(".venv").exists() 24 | 25 | 26 | def test_find_interpreters_from_venv(invoke, project): 27 | result = invoke(["install"]) 28 | assert result.exit_code == 0 29 | venv_parent = project.root / "venvs" 30 | venv_path = next(venv_parent.iterdir(), None) 31 | 32 | assert Path(next(project.find_interpreters()).executable).relative_to(venv_path) 33 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from pdm_venv import utils 4 | 5 | 6 | def test_iter_project_venvs(project): 7 | venv_parent = Path(project.config["venv.location"]) 8 | venv_prefix = utils.get_venv_prefix(project) 9 | for name in ("foo", "bar", "baz"): 10 | venv_parent.joinpath(venv_prefix + name).mkdir(parents=True) 11 | dot_venv_python = utils.get_venv_python(project.root / ".venv") 12 | dot_venv_python.parent.mkdir(parents=True) 13 | dot_venv_python.touch() 14 | venv_keys = [key for key, _ in utils.iter_venvs(project)] 15 | assert sorted(venv_keys) == ["bar", "baz", "foo", "in-project"] 16 | --------------------------------------------------------------------------------