diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..8ac6b8c4984 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41d3ab9463a..5f7cd942bf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -81,7 +81,7 @@ jobs: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.x" @@ -112,7 +112,7 @@ jobs: - "3.12" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} @@ -164,7 +164,7 @@ jobs: group: [1, 2] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} @@ -215,7 +215,7 @@ jobs: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.10" diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index 990440dd6c8..dc68b683bef 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -17,7 +17,7 @@ jobs: if: github.repository_owner == 'pypa' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3 + - uses: dessant/lock-threads@v4 with: issue-inactive-days: '30' pr-inactive-days: '15' diff --git a/.github/workflows/news-file.yml b/.github/workflows/news-file.yml index 371e12fd755..398ad1b7e67 100644 --- a/.github/workflows/news-file.yml +++ b/.github/workflows/news-file.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # `towncrier check` runs `git diff --name-only origin/main...`, which # needs a non-shallow clone. diff --git a/.github/workflows/update-rtd-redirects.yml b/.github/workflows/update-rtd-redirects.yml index 8259b6c0b6a..c333a09a30d 100644 --- a/.github/workflows/update-rtd-redirects.yml +++ b/.github/workflows/update-rtd-redirects.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest environment: RTD Deploys steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.11" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c576d90a5b..18d911256d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,25 +22,26 @@ repos: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.4 hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 + rev: v1.6.1 hooks: - id: mypy exclude: tests/data args: ["--pretty", "--show-error-codes"] additional_dependencies: [ - 'keyring==23.0.1', - 'nox==2021.6.12', + 'keyring==24.2.0', + 'nox==2023.4.22', 'pytest', - 'types-docutils==0.18.3', - 'types-setuptools==57.4.14', - 'types-freezegun==1.1.9', - 'types-six==1.16.15', - 'types-pyyaml==6.0.12.2', + 'types-docutils==0.20.0.3', + 'types-setuptools==68.2.0.0', + 'types-freezegun==1.1.10', + 'types-six==1.16.21.9', + 'types-pyyaml==6.0.12.12', ] - repo: https://github.com/pre-commit/pygrep-hooks diff --git a/AUTHORS.txt b/AUTHORS.txt index 77eb39a427d..e02de32bcf3 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -20,6 +20,7 @@ Albert-Guan albertg Alberto Sottile Aleks Bunin +Ales Erjavec Alethea Flowers Alex Gaynor Alex Grönholm @@ -30,6 +31,7 @@ Alex Stachowiak Alexander Shtyrov Alexandre Conrad Alexey Popravka +Aleš Erjavec Alli Ami Fischman Ananya Maiti @@ -196,9 +198,11 @@ David Runge David Tucker David Wales Davidovich +ddelange Deepak Sharma Deepyaman Datta Denise Yu +dependabot[bot] derwolfe Desetude Devesh Kumar Singh @@ -223,6 +227,8 @@ Dwayne Bailey Ed Morley Edgar Ramírez Ee Durbin +Efflam Lemaillet +efflamlemaillet Eitan Adler ekristina elainechan @@ -312,6 +318,7 @@ Ilya Baryshev Inada Naoki Ionel Cristian Mărieș Ionel Maries Cristian +Itamar Turner-Trauring Ivan Pozdeev Jacob Kim Jacob Walls @@ -338,6 +345,7 @@ Jay Graves Jean-Christophe Fillion-Robin Jeff Barber Jeff Dairiki +Jeff Widman Jelmer Vernooij jenix21 Jeremy Stanley @@ -367,6 +375,7 @@ Joseph Long Josh Bronson Josh Hansen Josh Schneier +Joshua Juan Luis Cano Rodríguez Juanjo Bazán Judah Rand @@ -397,6 +406,7 @@ KOLANICH kpinc Krishna Oza Kumar McMillan +Kurt McKee Kyle Persohn lakshmanaram Laszlo Kiss-Kollar @@ -413,6 +423,7 @@ lorddavidiii Loren Carvalho Lucas Cimon Ludovic Gasc +Lukas Geiger Lukas Juhrich Luke Macken Luo Jiebin @@ -529,6 +540,7 @@ Patrick Jenkins Patrick Lawson patricktokeeffe Patrik Kopkan +Paul Ganssle Paul Kehrer Paul Moore Paul Nasrat @@ -609,6 +621,7 @@ ryneeverett Sachi King Salvatore Rinchiera sandeepkiran-js +Sander Van Balen Savio Jomton schlamar Scott Kitterman @@ -621,6 +634,7 @@ SeongSoo Cho Sergey Vasilyev Seth Michael Larson Seth Woodworth +Shahar Epstein Shantanu shireenrao Shivansh-007 @@ -648,6 +662,7 @@ Steve Kowalik Steven Myint Steven Silvester stonebig +studioj Stéphane Bidoul Stéphane Bidoul (ACSONE) Stéphane Klein diff --git a/NEWS.rst b/NEWS.rst index fc3bb6697ad..8738e181e2e 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,13 +9,90 @@ .. towncrier release notes start +23.3.2 (2023-12-17) +=================== + +Bug Fixes +--------- + +- Fix a bug in extras handling for link requirements (`#12372 `_) +- Fix mercurial revision "parse error": use ``--rev={ref}`` instead of ``-r={ref}`` (`#12373 `_) + + +23.3.1 (2023-10-21) +=================== + +Bug Fixes +--------- + +- Handle a timezone indicator of Z when parsing dates in the self check. (`#12338 `_) +- Fix bug where installing the same package at the same time with multiple pip processes could fail. (`#12361 `_) + + +23.3 (2023-10-15) +================= + +Process +------- + +- Added reference to `vulnerability reporting guidelines `_ to pip's security policy. + +Deprecations and Removals +------------------------- + +- Drop a fallback to using SecureTransport on macOS. It was useful when pip detected OpenSSL older than 1.0.1, but the current pip does not support any Python version supporting such old OpenSSL versions. (`#12175 `_) + +Features +-------- + +- Improve extras resolution for multiple constraints on same base package. (`#11924 `_) +- Improve use of datastructures to make candidate selection 1.6x faster. (`#12204 `_) +- Allow ``pip install --dry-run`` to use platform and ABI overriding options. (`#12215 `_) +- Add ``is_yanked`` boolean entry to the installation report (``--report``) to indicate whether the requirement was yanked from the index, but was still selected by pip conform to :pep:`592`. (`#12224 `_) + +Bug Fixes +--------- + +- Ignore errors in temporary directory cleanup (show a warning instead). (`#11394 `_) +- Normalize extras according to :pep:`685` from package metadata in the resolver + for comparison. This ensures extras are correctly compared and merged as long + as the package providing the extra(s) is built with values normalized according + to the standard. Note, however, that this *does not* solve cases where the + package itself contains unnormalized extra values in the metadata. (`#11649 `_) +- Prevent downloading sdists twice when :pep:`658` metadata is present. (`#11847 `_) +- Include all requested extras in the install report (``--report``). (`#11924 `_) +- Removed uses of ``datetime.datetime.utcnow`` from non-vendored code. (`#12005 `_) +- Consistently report whether a dependency comes from an extra. (`#12095 `_) +- Fix completion script for zsh (`#12166 `_) +- Fix improper handling of the new onexc argument of ``shutil.rmtree()`` in Python 3.12. (`#12187 `_) +- Filter out yanked links from the available versions error message: "(from versions: 1.0, 2.0, 3.0)" will not contain yanked versions conform PEP 592. The yanked versions (if any) will be mentioned in a separate error message. (`#12225 `_) +- Fix crash when the git version number contains something else than digits and dots. (`#12280 `_) +- Use ``-r=...`` instead of ``-r ...`` to specify references with Mercurial. (`#12306 `_) +- Redact password from URLs in some additional places. (`#12350 `_) +- pip uses less memory when caching large packages. As a result, there is a new on-disk cache format stored in a new directory ($PIP_CACHE_DIR/http-v2). (`#2984 `_) + +Vendored Libraries +------------------ + +- Upgrade certifi to 2023.7.22 +- Add truststore 0.8.0 +- Upgrade urllib3 to 1.26.17 + +Improved Documentation +---------------------- + +- Document that ``pip search`` support has been removed from PyPI (`#12059 `_) +- Clarify --prefer-binary in CLI and docs (`#12122 `_) +- Document that using OS-provided Python can cause pip's test suite to report false failures. (`#12334 `_) + + 23.2.1 (2023-07-22) =================== Bug Fixes --------- -- Disable PEP 658 metadata fetching with the legacy resolver. (`#12156 `_) +- Disable :pep:`658` metadata fetching with the legacy resolver. (`#12156 `_) 23.2 (2023-07-15) @@ -29,8 +106,9 @@ Process Deprecations and Removals ------------------------- -- Deprecate legacy version and version specifiers that don't conform to `PEP 440 - `_ (`#12063 `_) +- Deprecate legacy version and version specifiers that don't conform to the + :ref:`specification `. + (`#12063 `_) - ``freeze`` no longer excludes the ``setuptools``, ``distribute``, and ``wheel`` from the output when running on Python 3.12 or later, where they are not included in a virtual environment by default. Use ``--exclude`` if you wish to @@ -45,11 +123,11 @@ Bug Fixes --------- - Fix ``pip completion --zsh``. (`#11417 `_) -- Prevent downloading files twice when PEP 658 metadata is present (`#11847 `_) +- Prevent downloading files twice when :pep:`658` metadata is present (`#11847 `_) - Add permission check before configuration (`#11920 `_) - Fix deprecation warnings in Python 3.12 for usage of shutil.rmtree (`#11957 `_) - Ignore invalid or unreadable ``origin.json`` files in the cache of locally built wheels. (`#11985 `_) -- Fix installation of packages with PEP658 metadata using non-canonicalized names (`#12038 `_) +- Fix installation of packages with :pep:`658` metadata using non-canonicalized names (`#12038 `_) - Correctly parse ``dist-info-metadata`` values from JSON-format index data. (`#12042 `_) - Fail with an error if the ``--python`` option is specified after the subcommand name. (`#12067 `_) - Fix slowness when using ``importlib.metadata`` (the default way for pip to read metadata in Python 3.11+) and there is a large overlap between already installed and to-be-installed packages. (`#12079 `_) @@ -220,7 +298,7 @@ Features - Change the hashes in the installation report to be a mapping. Emit the ``archive_info.hashes`` dictionary in ``direct_url.json``. (`#11312 `_) -- Implement logic to read the ``EXTERNALLY-MANAGED`` file as specified in PEP 668. +- Implement logic to read the ``EXTERNALLY-MANAGED`` file as specified in :pep:`668`. This allows a downstream Python distributor to prevent users from using pip to modify the externally managed environment. (`#11381 `_) - Enable the use of ``keyring`` found on ``PATH``. This allows ``keyring`` @@ -236,7 +314,7 @@ Bug Fixes - Use the "venv" scheme if available to obtain prefixed lib paths. (`#11598 `_) - Deprecated a historical ambiguity in how ``egg`` fragments in URL-style requirements are formatted and handled. ``egg`` fragments that do not look - like PEP 508 names now produce a deprecation warning. (`#11617 `_) + like :pep:`508` names now produce a deprecation warning. (`#11617 `_) - Fix scripts path in isolated build environment on Debian. (`#11623 `_) - Make ``pip show`` show the editable location if package is editable (`#11638 `_) - Stop checking that ``wheel`` is present when ``build-system.requires`` diff --git a/docs/html/cli/pip_install.rst b/docs/html/cli/pip_install.rst index 951dc2705a3..2664c75223d 100644 --- a/docs/html/cli/pip_install.rst +++ b/docs/html/cli/pip_install.rst @@ -45,11 +45,11 @@ When looking at the items to be installed, pip checks what type of item each is, in the following order: 1. Project or archive URL. -2. Local directory (which must contain a ``setup.py``, or pip will report - an error). +2. Local directory (which must contain a ``pyproject.toml`` or ``setup.py``, + otherwise pip will report an error). 3. Local file (a sdist or wheel format archive, following the naming conventions for those formats). -4. A requirement, as specified in :pep:`440`. +4. A :ref:`version specifier `. Each item identified is added to the set of requirements to be satisfied by the install. @@ -97,7 +97,8 @@ Installation Order .. note:: This section is only about installation order of runtime dependencies, and - does not apply to build dependencies (those are specified using PEP 518). + does not apply to build dependencies (those are specified using the + :ref:`[build-system] table `). As of v6.1.0, pip installs dependencies before their dependents, i.e. in "topological order." This is the only commitment pip currently makes related @@ -181,8 +182,9 @@ Pre-release Versions -------------------- Starting with v1.4, pip will only install stable versions as specified by -`pre-releases`_ by default. If a version cannot be parsed as a compliant :pep:`440` -version then it is assumed to be a pre-release. +`pre-releases`_ by default. If a version cannot be parsed as a +:ref:`compliant ` version then it is assumed to be +a pre-release. If a Requirement specifier includes a pre-release or development version (e.g. ``>=0.0.dev0``) then pip will allow pre-release and development versions @@ -214,8 +216,8 @@ pip looks for packages in a number of places: on PyPI (if not disabled via ``--no-index``), in the local filesystem, and in any additional repositories specified via ``--find-links`` or ``--index-url``. There is no ordering in the locations that are searched. Rather they are all checked, and the "best" -match for the requirements (in terms of version number - see :pep:`440` for -details) is selected. +match for the requirements (in terms of version number - see the +:ref:`specification ` for details) is selected. See the :ref:`pip install Examples`. @@ -380,7 +382,8 @@ Examples py -m pip install -e "git+https://git.repo/some_pkg.git@feature#egg=SomePackage" # from 'feature' branch py -m pip install -e "git+https://git.repo/some_repo.git#egg=subdir&subdirectory=subdir_path" # install a python package from a repo subdirectory -#. Install a package with `extras`_. +#. Install a package with extras, i.e., optional dependencies + (:ref:`specification `). .. tab:: Unix/macOS @@ -418,7 +421,8 @@ Examples py -m pip install "./downloads/SomePackage-1.0.4.tar.gz" py -m pip install "http://my.package.repo/SomePackage-1.0.4.zip" -#. Install a particular source archive file following :pep:`440` direct references. +#. Install a particular source archive file following direct references + (:ref:`specification `). .. tab:: Unix/macOS @@ -539,5 +543,4 @@ Examples py -m pip install SomePackage1 SomePackage2 --no-binary SomePackage1 -.. _extras: https://www.python.org/dev/peps/pep-0508/#extras .. _PyPI: https://pypi.org/ diff --git a/docs/html/cli/pip_wheel.rst b/docs/html/cli/pip_wheel.rst index bfd19a0ccb1..ba749529c0c 100644 --- a/docs/html/cli/pip_wheel.rst +++ b/docs/html/cli/pip_wheel.rst @@ -34,7 +34,8 @@ Differences to ``build`` ------------------------ `build `_ is a simple tool which can among other things build -wheels for projects using PEP 517. It is comparable to the execution of ``pip wheel --no-deps .``. +wheels for projects using the standard ``pyproject.toml``-based build interface. It +is comparable to the execution of ``pip wheel --no-deps .``. It can also build source distributions which is not possible with ``pip``. ``pip wheel`` covers the wheel scope of ``build`` but offers many additional features. diff --git a/docs/html/development/architecture/package-finding.rst b/docs/html/development/architecture/package-finding.rst index 0b64d420d93..4885d925ee3 100644 --- a/docs/html/development/architecture/package-finding.rst +++ b/docs/html/development/architecture/package-finding.rst @@ -182,8 +182,9 @@ example, whether a pre-release is eligible for selection or whether a file whose hash doesn't match is eligible depends on properties of the collection as a whole. -The ``CandidateEvaluator`` class uses information like the list of `PEP 425`_ -tags compatible with the target Python interpreter, hashes provided by the +The ``CandidateEvaluator`` class uses information like the list of +:ref:`platform tags ` +compatible with the target Python interpreter, hashes provided by the user, and other user preferences, etc. Specifically, the class has a ``get_applicable_candidates()`` method. @@ -236,5 +237,4 @@ The class is the return type of both the ``CandidateEvaluator`` class's ``find_best_candidate()`` method. -.. _`PEP 425`: https://www.python.org/dev/peps/pep-0425/ .. _`PEP 503`: https://www.python.org/dev/peps/pep-0503/ diff --git a/docs/html/development/contributing.rst b/docs/html/development/contributing.rst index 87734ee4d55..b2f6f1d1378 100644 --- a/docs/html/development/contributing.rst +++ b/docs/html/development/contributing.rst @@ -112,7 +112,7 @@ the ``news/`` directory with the extension of ``.trivial.rst``. If you are on a POSIX like operating system, one can be added by running ``touch news/$(uuidgen).trivial.rst``. On Windows, the same result can be achieved in Powershell using ``New-Item "news/$([guid]::NewGuid()).trivial.rst"``. -Core committers may also add a "trivial" label to the PR which will accomplish +Core committers may also add a "skip news" label to the PR which will accomplish the same thing. Upgrading, removing, or adding a new vendored library gets a special mention diff --git a/docs/html/development/getting-started.rst b/docs/html/development/getting-started.rst index e248259f08d..bc483997a64 100644 --- a/docs/html/development/getting-started.rst +++ b/docs/html/development/getting-started.rst @@ -73,7 +73,7 @@ pip's tests are written using the :pypi:`pytest` test framework and :mod:`unittest.mock`. :pypi:`nox` is used to automate the setup and execution of pip's tests. -It is preferable to run the tests in parallel for better experience during development, +It is preferable to run the tests in parallel for a better experience during development, since the tests can take a long time to finish when run sequentially. To run tests: @@ -104,6 +104,15 @@ can select tests using the various ways that pytest provides: $ # Using keywords $ nox -s test-3.10 -- -k "install and not wheel" +.. note:: + + When running pip's tests with OS distribution Python versions, be aware that some + functional tests may fail due to potential patches introduced by the distribution. + For all tests to pass consider: + + - Installing Python from `python.org`_ or compile from source + - Or, using `pyenv`_ to assist with source compilation + Running pip's entire test suite requires supported version control tools (subversion, bazaar, git, and mercurial) to be installed. If you are missing any of these VCS, those tests should be skipped automatically. You can also @@ -114,6 +123,9 @@ explicitly tell pytest to skip those tests: $ nox -s test-3.10 -- -k "not svn" $ nox -s test-3.10 -- -k "not (svn or git)" +.. _python.org: https://www.python.org/downloads/ +.. _pyenv: https://github.com/pyenv/pyenv + Running Linters =============== @@ -194,7 +206,6 @@ in order to start contributing. .. _`open an issue`: https://github.com/pypa/pip/issues/new?title=Trouble+with+pip+development+environment .. _`install Python`: https://realpython.com/installing-python/ -.. _`PEP 484 type-comments`: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code .. _`rich CLI`: https://docs.pytest.org/en/latest/usage.html#specifying-tests-selecting-tests .. _`GitHub`: https://github.com/pypa/pip .. _`good first issues`: https://github.com/pypa/pip/labels/good%20first%20issue diff --git a/docs/html/development/release-process.rst b/docs/html/development/release-process.rst index b71e2820bd2..65ed567070e 100644 --- a/docs/html/development/release-process.rst +++ b/docs/html/development/release-process.rst @@ -145,8 +145,8 @@ Creating a new release #. Push the tag created by ``prepare-release``. #. Regenerate the ``get-pip.py`` script in the `get-pip repository`_ (as documented there) and commit the results. -#. Submit a Pull Request to `CPython`_ adding the new version of pip (and upgrading - setuptools) to ``Lib/ensurepip/_bundled``, removing the existing version, and +#. Submit a Pull Request to `CPython`_ adding the new version of pip + to ``Lib/ensurepip/_bundled``, removing the existing version, and adjusting the versions listed in ``Lib/ensurepip/__init__.py``. diff --git a/docs/html/reference/build-system/pyproject-toml.md b/docs/html/reference/build-system/pyproject-toml.md index a42a3b8c484..9719023cced 100644 --- a/docs/html/reference/build-system/pyproject-toml.md +++ b/docs/html/reference/build-system/pyproject-toml.md @@ -135,13 +135,13 @@ section, it will be assumed to have the following backend settings: ```toml [build-system] -requires = ["setuptools>=40.8.0", "wheel"] +requires = ["setuptools>=40.8.0"] build-backend = "setuptools.build_meta:__legacy__" ``` If a project has a `build-system` section but no `build-backend`, then: -- It is expected to include `setuptools` and `wheel` as build requirements. An +- It is expected to include `setuptools` as a build requirement. An error is reported if the available version of `setuptools` is not recent enough. diff --git a/docs/html/reference/inspect-report.md b/docs/html/reference/inspect-report.md index 1355e5d4274..ad8263c6742 100644 --- a/docs/html/reference/inspect-report.md +++ b/docs/html/reference/inspect-report.md @@ -27,9 +27,8 @@ The report is a JSON object with the following properties: distribution packages that are installed. - `environment`: an object describing the environment where the installation report was - generated. See [PEP 508 environment - markers](https://peps.python.org/pep-0508/#environment-markers) for more information. - Values have a string type. + generated. See the section on environment markers in the {ref}`pypug:dependency-specifiers` + specification for more information. Values have a string type. (InspectReportItem)= diff --git a/docs/html/topics/configuration.md b/docs/html/topics/configuration.md index e4aafcd2b98..8b54db56ce6 100644 --- a/docs/html/topics/configuration.md +++ b/docs/html/topics/configuration.md @@ -19,8 +19,8 @@ and how they are related to pip's various command line options. ## Configuration Files -Configuration files can change the default values for command line option. -They are written using a standard INI style configuration files. +Configuration files can change the default values for command line options. +The files are written using standard INI format. pip has 3 "levels" of configuration files: @@ -28,11 +28,15 @@ pip has 3 "levels" of configuration files: - `user`: per-user configuration file. - `site`: per-environment configuration file; i.e. per-virtualenv. +Additionally, environment variables can be specified which will override any of the above. + ### Location pip's configuration files are located in fairly standard locations. This location is different on different operating systems, and has some additional -complexity for backwards compatibility reasons. +complexity for backwards compatibility reasons. Note that if user config files +exist in both the legacy and current locations, values in the current file +will override values in the legacy file. ```{tab} Unix @@ -88,9 +92,10 @@ Site ### `PIP_CONFIG_FILE` Additionally, the environment variable `PIP_CONFIG_FILE` can be used to specify -a configuration file that's loaded first, and whose values are overridden by -the values set in the aforementioned files. Setting this to {any}`os.devnull` -disables the loading of _all_ configuration files. +a configuration file that's loaded last, and whose values override the values +set in the aforementioned files. Setting this to {any}`os.devnull` +disables the loading of _all_ configuration files. Note that if a file exists +at the location that this is set to, the user config file will not be loaded. (config-precedence)= @@ -99,10 +104,10 @@ disables the loading of _all_ configuration files. When multiple configuration files are found, pip combines them in the following order: -- `PIP_CONFIG_FILE`, if given. - Global - User - Site +- `PIP_CONFIG_FILE`, if given. Each file read overrides any values read from previous files, so if the global timeout is specified in both the global file and the per-user file diff --git a/docs/html/topics/more-dependency-resolution.md b/docs/html/topics/more-dependency-resolution.md index 1c7836e5c0f..b955e2ec114 100644 --- a/docs/html/topics/more-dependency-resolution.md +++ b/docs/html/topics/more-dependency-resolution.md @@ -97,10 +97,10 @@ feeding candidates to the resolver, and has a key role to play in selecting suitable candidates. Note that the resolver is *only* relevant for packages fetched from an index. -Candidates coming from other sources (local source directories, PEP 508 -direct URL references) do *not* go through the finder, and are merged with the -candidates provided by the finder as part of the resolver's "provider" -implementation. +Candidates coming from other sources (local source directories, {ref}`direct +URL references `) do *not* go through the finder, +and are merged with the candidates provided by the finder as part of the resolver's +"provider" implementation. As well as determining what versions exist in the index for a given project, the finder selects the best distribution file to use for that candidate. This diff --git a/docs/html/topics/vcs-support.md b/docs/html/topics/vcs-support.md index 465d5ecb78c..c8169dbe24c 100644 --- a/docs/html/topics/vcs-support.md +++ b/docs/html/topics/vcs-support.md @@ -140,9 +140,8 @@ pip also looks at the `egg` fragment specifying the "project name". In practice mode. In all other circumstances, the `egg` fragment is not necessary and its use is discouraged. -The `egg` fragment **should** be a bare -[PEP 508](https://peps.python.org/pep-0508/) project name. Anything else -is not guaranteed to work. +The `egg` fragment **should** be a bare {ref}`project name `. +Anything else is not guaranteed to work. ````{admonition} Example If your repository layout is: diff --git a/docs/html/user_guide.rst b/docs/html/user_guide.rst index 9a6f2901cd5..f0cbded683d 100644 --- a/docs/html/user_guide.rst +++ b/docs/html/user_guide.rst @@ -264,7 +264,7 @@ Installing from Wheels "Wheel" is a built, archive format that can greatly speed installation compared to building and installing from source archives. For more information, see the -`Wheel docs `_ , :pep:`427`, and :pep:`425`. +:ref:`specification `. pip prefers Wheels where they are available. To disable this, use the :ref:`--no-binary ` flag for :ref:`pip install`. @@ -306,7 +306,8 @@ name: .. note:: In the future, the ``path[extras]`` syntax may become deprecated. It is - recommended to use PEP 508 syntax wherever possible. + recommended to use :ref:`standard ` + syntax wherever possible. For the cases where wheels are not available, pip offers :ref:`pip wheel` as a convenience, to build wheels for all your requirements and dependencies. diff --git a/docs/pip_sphinxext.py b/docs/pip_sphinxext.py index 2e559702294..fe3f41e8b79 100644 --- a/docs/pip_sphinxext.py +++ b/docs/pip_sphinxext.py @@ -194,22 +194,17 @@ def process_options(self) -> None: opt = option() opt_name = opt._long_opts[0] if opt._short_opts: - short_opt_name = "{}, ".format(opt._short_opts[0]) + short_opt_name = f"{opt._short_opts[0]}, " else: short_opt_name = "" if option in cmdoptions.general_group["options"]: prefix = "" else: - prefix = "{}_".format(self.determine_opt_prefix(opt_name)) + prefix = f"{self.determine_opt_prefix(opt_name)}_" self.view_list.append( - "* :ref:`{short}{long}<{prefix}{opt_name}>`".format( - short=short_opt_name, - long=opt_name, - prefix=prefix, - opt_name=opt_name, - ), + f"* :ref:`{short_opt_name}{opt_name}<{prefix}{opt_name}>`", "\n", ) diff --git a/news/11394.bugfix.rst b/news/11394.bugfix.rst deleted file mode 100644 index 9f2501db46c..00000000000 --- a/news/11394.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Ignore errors in temporary directory cleanup (show a warning instead). diff --git a/news/11649.bugfix.rst b/news/11649.bugfix.rst deleted file mode 100644 index 65511711f59..00000000000 --- a/news/11649.bugfix.rst +++ /dev/null @@ -1,5 +0,0 @@ -Normalize extras according to :pep:`685` from package metadata in the resolver -for comparison. This ensures extras are correctly compared and merged as long -as the package providing the extra(s) is built with values normalized according -to the standard. Note, however, that this *does not* solve cases where the -package itself contains unnormalized extra values in the metadata. diff --git a/news/11815.doc.rst b/news/11815.doc.rst new file mode 100644 index 00000000000..8e7e8d21bef --- /dev/null +++ b/news/11815.doc.rst @@ -0,0 +1 @@ +Fix explanation of how PIP_CONFIG_FILE works diff --git a/news/11909.process.rst b/news/11909.process.rst new file mode 100644 index 00000000000..a396d93d963 --- /dev/null +++ b/news/11909.process.rst @@ -0,0 +1 @@ +Most project metadata is now defined statically via pip's ``pyproject.toml`` file. diff --git a/news/11924.bugfix.rst b/news/11924.bugfix.rst deleted file mode 100644 index 7a9ee3151a4..00000000000 --- a/news/11924.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Include all requested extras in the install report (``--report``). diff --git a/news/11924.feature.rst b/news/11924.feature.rst deleted file mode 100644 index 30bc60e6bce..00000000000 --- a/news/11924.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Improve extras resolution for multiple constraints on same base package. diff --git a/news/12005.bugfix.rst b/news/12005.bugfix.rst deleted file mode 100644 index 98a3e5112df..00000000000 --- a/news/12005.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Removed uses of ``datetime.datetime.utcnow`` from non-vendored code. diff --git a/news/12059.doc.rst b/news/12059.doc.rst deleted file mode 100644 index bf3a8d3e662..00000000000 --- a/news/12059.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Document that ``pip search`` support has been removed from PyPI diff --git a/news/12095.bugfix.rst b/news/12095.bugfix.rst deleted file mode 100644 index 1f5018326ba..00000000000 --- a/news/12095.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Consistently report whether a dependency comes from an extra. diff --git a/news/12122.doc.rst b/news/12122.doc.rst deleted file mode 100644 index 49a3308a25c..00000000000 --- a/news/12122.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Clarify --prefer-binary in CLI and docs diff --git a/news/12155.process.rst b/news/12155.process.rst deleted file mode 100644 index 5f77231c864..00000000000 --- a/news/12155.process.rst +++ /dev/null @@ -1,6 +0,0 @@ -The metadata-fetching log message is moved to the VERBOSE level and now hidden -by default. The more significant information in this message to most users are -already available in surrounding logs (the package name and version of the -metadata being fetched), while the URL to the exact metadata file is generally -too long and clutters the output. The message can be brought back with -``--verbose``. diff --git a/news/12175.removal.rst b/news/12175.removal.rst deleted file mode 100644 index bf3500f351a..00000000000 --- a/news/12175.removal.rst +++ /dev/null @@ -1 +0,0 @@ -Drop a fallback to using SecureTransport on macOS. It was useful when pip detected OpenSSL older than 1.0.1, but the current pip does not support any Python version supporting such old OpenSSL versions. diff --git a/news/12183.trivial.rst b/news/12183.trivial.rst deleted file mode 100644 index c22e854c9a5..00000000000 --- a/news/12183.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Add test cases for some behaviors of ``install --dry-run`` and ``--use-feature=fast-deps``. diff --git a/news/12187.bugfix.rst b/news/12187.bugfix.rst deleted file mode 100644 index b4d106b974f..00000000000 --- a/news/12187.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix improper handling of the new onexc argument of ``shutil.rmtree()`` in Python 3.12. diff --git a/news/12191.bugfix.rst b/news/12191.bugfix.rst deleted file mode 100644 index 1f384835fef..00000000000 --- a/news/12191.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Prevent downloading sdists twice when PEP 658 metadata is present. diff --git a/news/12194.trivial.rst b/news/12194.trivial.rst deleted file mode 100644 index dfe5bbf1f06..00000000000 --- a/news/12194.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Add lots of comments to the ``BuildTracker``. diff --git a/news/12204.feature.rst b/news/12204.feature.rst deleted file mode 100644 index 6ffdf5123b1..00000000000 --- a/news/12204.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Improve use of datastructures to make candidate selection 1.6x faster diff --git a/news/12215.feature.rst b/news/12215.feature.rst deleted file mode 100644 index 407dc903ed9..00000000000 --- a/news/12215.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Allow ``pip install --dry-run`` to use platform and ABI overriding options similar to ``--target``. diff --git a/news/12224.feature.rst b/news/12224.feature.rst deleted file mode 100644 index d874265787a..00000000000 --- a/news/12224.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Add ``is_yanked`` boolean entry to the installation report (``--report``) to indicate whether the requirement was yanked from the index, but was still selected by pip conform to PEP 592. diff --git a/news/12225.bugfix.rst b/news/12225.bugfix.rst deleted file mode 100644 index e1e0c323dc3..00000000000 --- a/news/12225.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Filter out yanked links from the available versions error message: "(from versions: 1.0, 2.0, 3.0)" will not contain yanked versions conform PEP 592. The yanked versions (if any) will be mentioned in a separate error message. diff --git a/news/12254.process.rst b/news/12254.process.rst deleted file mode 100644 index e546902685b..00000000000 --- a/news/12254.process.rst +++ /dev/null @@ -1 +0,0 @@ -Added reference to `vulnerability reporting guidelines `_ to pip's security policy. diff --git a/news/12261.trivial.rst b/news/12261.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/12280.bugfix.rst b/news/12280.bugfix.rst deleted file mode 100644 index 77de283d398..00000000000 --- a/news/12280.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix crash when the git version number contains something else than digits and dots. diff --git a/news/12306.bugfix.rst b/news/12306.bugfix.rst deleted file mode 100644 index eb6eecaaf1b..00000000000 --- a/news/12306.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Use ``-r=...`` instead of ``-r ...`` to specify references with Mercurial. diff --git a/news/12389.bugfix.rst b/news/12389.bugfix.rst new file mode 100644 index 00000000000..84871873328 --- /dev/null +++ b/news/12389.bugfix.rst @@ -0,0 +1 @@ +Update mypy to 1.6.1 and fix/ignore types diff --git a/news/12390.trivial.rst b/news/12390.trivial.rst new file mode 100644 index 00000000000..52b21413ca0 --- /dev/null +++ b/news/12390.trivial.rst @@ -0,0 +1 @@ +Update ruff versions and config for dev diff --git a/news/12393.trivial.rst b/news/12393.trivial.rst new file mode 100644 index 00000000000..15452737aef --- /dev/null +++ b/news/12393.trivial.rst @@ -0,0 +1 @@ +Enforce and update code to use f-strings via Ruff rule UP032 diff --git a/news/12417.doc.rst b/news/12417.doc.rst new file mode 100644 index 00000000000..efde79a5808 --- /dev/null +++ b/news/12417.doc.rst @@ -0,0 +1 @@ +Fix outdated pip install argument description in documentation. diff --git a/news/12434.doc.rst b/news/12434.doc.rst new file mode 100644 index 00000000000..c1d3635df78 --- /dev/null +++ b/news/12434.doc.rst @@ -0,0 +1 @@ +Replace some links to PEPs with links to the canonical specifications on the :doc:`pypug:index` diff --git a/news/12449.bugfix.rst b/news/12449.bugfix.rst new file mode 100644 index 00000000000..19f1d9809ac --- /dev/null +++ b/news/12449.bugfix.rst @@ -0,0 +1,2 @@ +Removed ``wheel`` from the ``[build-system].requires`` list fallback +that is used when ``pyproject.toml`` is absent. diff --git a/news/12449.doc.rst b/news/12449.doc.rst new file mode 100644 index 00000000000..431475f51eb --- /dev/null +++ b/news/12449.doc.rst @@ -0,0 +1,2 @@ +Updated the ``pyproject.toml`` document to stop suggesting +to depend on ``wheel`` as a build dependency directly. diff --git a/news/12AE57EC-683C-4A8E-BCCB-851FCD0730B4.trivial.rst b/news/12AE57EC-683C-4A8E-BCCB-851FCD0730B4.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/1F54AB69-21F3-49F6-B938-AB16E326F82C.trivial.rst b/news/1F54AB69-21F3-49F6-B938-AB16E326F82C.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/2984.bugfix.rst b/news/2984.bugfix.rst deleted file mode 100644 index cce561815c9..00000000000 --- a/news/2984.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -pip uses less memory when caching large packages. As a result, there is a new on-disk cache format stored in a new directory ($PIP_CACHE_DIR/http-v2). diff --git a/news/4A0C40FF-ABE1-48C7-954C-7C3EB229135F.trivial.rst b/news/4A0C40FF-ABE1-48C7-954C-7C3EB229135F.trivial.rst deleted file mode 100644 index 7f6c1d5612e..00000000000 --- a/news/4A0C40FF-ABE1-48C7-954C-7C3EB229135F.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Add ruff rules ASYNC,C4,C90,PERF,PLE,PLR for minor optimizations and to set upper limits on code complexity. diff --git a/news/732404DE-8011-4146-8CAD-85D7756D88A6.trivial.rst b/news/732404DE-8011-4146-8CAD-85D7756D88A6.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/80291DF4-7B0F-4268-B682-E1FCA1C3ACED.trivial.rst b/news/80291DF4-7B0F-4268-B682-E1FCA1C3ACED.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/85F7E260-68FF-4C1E-A2CB-CF8634829D2D.trivial.rst b/news/85F7E260-68FF-4C1E-A2CB-CF8634829D2D.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/E2B261CA-A0CF-4309-B808-1210C0B54632.trivial.rst b/news/E2B261CA-A0CF-4309-B808-1210C0B54632.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/news/certifi.vendor.rst b/news/certifi.vendor.rst deleted file mode 100644 index aacd17183f1..00000000000 --- a/news/certifi.vendor.rst +++ /dev/null @@ -1 +0,0 @@ -Upgrade certifi to 2023.7.22 diff --git a/news/truststore.vendor.rst b/news/truststore.vendor.rst deleted file mode 100644 index 63c71d72d2f..00000000000 --- a/news/truststore.vendor.rst +++ /dev/null @@ -1 +0,0 @@ -Add truststore 0.8.0 diff --git a/news/zhsdgdlsjgksdfj.trivial.rst b/news/zhsdgdlsjgksdfj.trivial.rst deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/noxfile.py b/noxfile.py index a3e7ceab4cc..878dbbd0ad6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -322,7 +322,7 @@ def build_release(session: nox.Session) -> None: ) session.log("# Install dependencies") - session.install("setuptools", "wheel", "twine") + session.install("build", "twine") with release.isolated_temporary_checkout(session, version) as build_dir: session.log( @@ -358,8 +358,7 @@ def build_dists(session: nox.Session) -> List[str]: ) session.log("# Build distributions") - session.install("setuptools", "wheel") - session.run("python", "setup.py", "sdist", "bdist_wheel", silent=True) + session.run("python", "-m", "build", silent=True) produced_dists = glob.glob("dist/*") session.log(f"# Verify distributions: {', '.join(produced_dists)}") diff --git a/pyproject.toml b/pyproject.toml index b720c460297..7496a08ee2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,72 @@ +[project] +dynamic = ["version", "scripts"] + +name = "pip" +description = "The PyPA recommended tool for installing Python packages." +readme = "README.rst" +license = {text = "MIT"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +authors = [ + {name = "The pip developers", email = "distutils-sig@python.org"}, +] + +# NOTE: requires-python is duplicated in __pip-runner__.py. +# When changing this value, please change the other copy as well. +requires-python = ">=3.7" + +[project.urls] +Homepage = "https://pip.pypa.io/" +Documentation = "https://pip.pypa.io" +Source = "https://github.com/pypa/pip" +Changelog = "https://pip.pypa.io/en/stable/news/" + [build-system] -requires = ["setuptools", "wheel"] +# The lower bound is for . +requires = ["setuptools>=67.6.1", "wheel"] build-backend = "setuptools.build_meta" +[tool.setuptools] +package-dir = {"" = "src"} +include-package-data = false + +[tool.setuptools.dynamic] +version = {attr = "pip.__version__"} + +[tool.setuptools.packages.find] +where = ["src"] +exclude = ["contrib", "docs", "tests*", "tasks"] + +[tool.setuptools.package-data] +"pip" = ["py.typed"] +"pip._vendor" = ["vendor.txt"] +"pip._vendor.certifi" = ["*.pem"] +"pip._vendor.requests" = ["*.pem"] +"pip._vendor.distlib._backport" = ["sysconfig.cfg"] +"pip._vendor.distlib" = [ + "t32.exe", + "t64.exe", + "t64-arm.exe", + "w32.exe", + "w64.exe", + "w64-arm.exe", +] + [tool.towncrier] # For finding the __version__ package = "pip" @@ -84,8 +149,8 @@ ignore = [ "B020", "B904", # Ruff enables opinionated warnings by default "B905", # Ruff enables opinionated warnings by default - "G202", ] +target-version = "py37" line-length = 88 select = [ "ASYNC", @@ -102,6 +167,7 @@ select = [ "PLR0", "W", "RUF100", + "UP032", ] [tool.ruff.isort] diff --git a/setup.py b/setup.py index d73c77b7346..5599ffe142e 100644 --- a/setup.py +++ b/setup.py @@ -1,88 +1,13 @@ -import os import sys -from setuptools import find_packages, setup - - -def read(rel_path: str) -> str: - here = os.path.abspath(os.path.dirname(__file__)) - # intentionally *not* adding an encoding option to open, See: - # https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690 - with open(os.path.join(here, rel_path)) as fp: - return fp.read() - - -def get_version(rel_path: str) -> str: - for line in read(rel_path).splitlines(): - if line.startswith("__version__"): - # __version__ = "0.9" - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - raise RuntimeError("Unable to find version string.") - - -long_description = read("README.rst") +from setuptools import setup setup( - name="pip", - version=get_version("src/pip/__init__.py"), - description="The PyPA recommended tool for installing Python packages.", - long_description=long_description, - license="MIT", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Topic :: Software Development :: Build Tools", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - ], - url="https://pip.pypa.io/", - project_urls={ - "Documentation": "https://pip.pypa.io", - "Source": "https://github.com/pypa/pip", - "Changelog": "https://pip.pypa.io/en/stable/news/", - }, - author="The pip developers", - author_email="distutils-sig@python.org", - package_dir={"": "src"}, - packages=find_packages( - where="src", - exclude=["contrib", "docs", "tests*", "tasks"], - ), - package_data={ - "pip": ["py.typed"], - "pip._vendor": ["vendor.txt"], - "pip._vendor.certifi": ["*.pem"], - "pip._vendor.requests": ["*.pem"], - "pip._vendor.distlib._backport": ["sysconfig.cfg"], - "pip._vendor.distlib": [ - "t32.exe", - "t64.exe", - "t64-arm.exe", - "w32.exe", - "w64.exe", - "w64-arm.exe", - ], - }, entry_points={ "console_scripts": [ "pip=pip._internal.cli.main:main", - "pip{}=pip._internal.cli.main:main".format(sys.version_info[0]), + f"pip{sys.version_info[0]}=pip._internal.cli.main:main", "pip{}.{}=pip._internal.cli.main:main".format(*sys.version_info[:2]), ], }, - zip_safe=False, - # NOTE: python_requires is duplicated in __pip-runner__.py. - # When changing this value, please change the other copy as well. - python_requires=">=3.7", ) diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 00ce8ad456d..46e56014998 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from typing import List, Optional -__version__ = "23.3.dev0" +__version__ = "24.0.dev0" def main(args: Optional[List[str]] = None) -> int: diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 89b02ba3ecb..6066441555d 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -595,10 +595,7 @@ def _handle_python_version( """ version_info, error_msg = _convert_python_version(value) if error_msg is not None: - msg = "invalid --python-version value: {!r}: {}".format( - value, - error_msg, - ) + msg = f"invalid --python-version value: {value!r}: {error_msg}" raise_option_error(parser, option=option, msg=msg) parser.values.python_version = version_info @@ -934,9 +931,9 @@ def _handle_merge_hash( algo, digest = value.split(":", 1) except ValueError: parser.error( - "Arguments to {} must be a hash name " + f"Arguments to {opt_str} must be a hash name " "followed by a value, like --hash=sha256:" - "abcde...".format(opt_str) + "abcde..." ) if algo not in STRONG_HASHES: parser.error( diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py index 64cf9719730..ae554b24cae 100644 --- a/src/pip/_internal/cli/parser.py +++ b/src/pip/_internal/cli/parser.py @@ -229,9 +229,9 @@ def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: val = strtobool(val) except ValueError: self.error( - "{} is not a valid value for {} option, " + f"{val} is not a valid value for {key} option, " "please specify a boolean value like yes/no, " - "true/false or 1/0 instead.".format(val, key) + "true/false or 1/0 instead." ) elif option.action == "count": with suppress(ValueError): @@ -240,10 +240,10 @@ def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: val = int(val) if not isinstance(val, int) or val < 0: self.error( - "{} is not a valid value for {} option, " + f"{val} is not a valid value for {key} option, " "please instead specify either a non-negative integer " "or a boolean value like yes/no or false/true " - "which is equivalent to 1/0.".format(val, key) + "which is equivalent to 1/0." ) elif option.action == "append": val = val.split() diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py index 1f3b5fe142b..328336152cc 100644 --- a/src/pip/_internal/commands/cache.py +++ b/src/pip/_internal/commands/cache.py @@ -175,7 +175,7 @@ def remove_cache_items(self, options: Values, args: List[Any]) -> None: files += self._find_http_files(options) else: # Add the pattern to the log message - no_matching_msg += ' for pattern "{}"'.format(args[0]) + no_matching_msg += f' for pattern "{args[0]}"' if not files: logger.warning(no_matching_msg) diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py index 30233fc7ad2..9e89e279883 100644 --- a/src/pip/_internal/commands/completion.py +++ b/src/pip/_internal/commands/completion.py @@ -23,9 +23,18 @@ """, "zsh": """ #compdef -P pip[0-9.]# - compadd $( COMP_WORDS="$words[*]" \\ - COMP_CWORD=$((CURRENT-1)) \\ - PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ) + __pip() {{ + compadd $( COMP_WORDS="$words[*]" \\ + COMP_CWORD=$((CURRENT-1)) \\ + PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ) + }} + if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + __pip "$@" + else + # eval/source/. command, register function for later + compdef __pip -P 'pip[0-9.]#' + fi """, "fish": """ function __fish_complete_pip diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py index 84b134e490b..1a1dc6b6cd8 100644 --- a/src/pip/_internal/commands/configuration.py +++ b/src/pip/_internal/commands/configuration.py @@ -242,17 +242,15 @@ def open_in_editor(self, options: Values, args: List[str]) -> None: e.filename = editor raise except subprocess.CalledProcessError as e: - raise PipError( - "Editor Subprocess exited with exit code {}".format(e.returncode) - ) + raise PipError(f"Editor Subprocess exited with exit code {e.returncode}") def _get_n_args(self, args: List[str], example: str, n: int) -> Any: """Helper to make sure the command got the right number of arguments""" if len(args) != n: msg = ( - "Got unexpected number of arguments, expected {}. " - '(example: "{} config {}")' - ).format(n, get_prog(), example) + f"Got unexpected number of arguments, expected {n}. " + f'(example: "{get_prog()} config {example}")' + ) raise PipError(msg) if n == 1: diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py index 5dc91bf4950..7e5271c9886 100644 --- a/src/pip/_internal/commands/debug.py +++ b/src/pip/_internal/commands/debug.py @@ -95,7 +95,7 @@ def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: elif parse_version(actual_version) != parse_version(expected_version): extra_message = ( " (CONFLICT: vendor.txt suggests version should" - " be {})".format(expected_version) + f" be {expected_version})" ) logger.info("%s==%s%s", module_name, actual_version, extra_message) @@ -120,7 +120,7 @@ def show_tags(options: Values) -> None: if formatted_target: suffix = f" (target: {formatted_target})" - msg = "Compatible tags: {}{}".format(len(tags), suffix) + msg = f"Compatible tags: {len(tags)}{suffix}" logger.info(msg) if options.verbose < 1 and len(tags) > tag_limit: @@ -134,9 +134,7 @@ def show_tags(options: Values) -> None: logger.info(str(tag)) if tags_limited: - msg = ( - "...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" - ).format(tag_limit=tag_limit) + msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" logger.info(msg) diff --git a/src/pip/_internal/commands/index.py b/src/pip/_internal/commands/index.py index 7267effed24..f55e9e49974 100644 --- a/src/pip/_internal/commands/index.py +++ b/src/pip/_internal/commands/index.py @@ -128,12 +128,12 @@ def get_available_package_versions(self, options: Values, args: List[Any]) -> No if not versions: raise DistributionNotFound( - "No matching distribution found for {}".format(query) + f"No matching distribution found for {query}" ) formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)] latest = formatted_versions[0] - write_output("{} ({})".format(query, latest)) + write_output(f"{query} ({latest})") write_output("Available versions: {}".format(", ".join(formatted_versions))) print_dist_installation_info(query, latest) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 56dd48c511e..927f8e45185 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -608,12 +608,8 @@ def _warn_about_conflicts( version = package_set[project_name][0] for dependency in missing[project_name]: message = ( - "{name} {version} requires {requirement}, " + f"{project_name} {version} requires {dependency[1]}, " "which is not installed." - ).format( - name=project_name, - version=version, - requirement=dependency[1], ) parts.append(message) diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index 96f824955bf..c25273d5f0b 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -59,8 +59,8 @@ def _disassemble_key(name: str) -> List[str]: if "." not in name: error_message = ( "Key does not contain dot separated section and key. " - "Perhaps you wanted to use 'global.{}' instead?" - ).format(name) + f"Perhaps you wanted to use 'global.{name}' instead?" + ) raise ConfigurationError(error_message) return name.split(".", 1) @@ -327,33 +327,35 @@ def get_environ_vars(self) -> Iterable[Tuple[str, str]]: def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]: """Yields variant and configuration files associated with it. - This should be treated like items of a dictionary. + This should be treated like items of a dictionary. The order + here doesn't affect what gets overridden. That is controlled + by OVERRIDE_ORDER. However this does control the order they are + displayed to the user. It's probably most ergononmic to display + things in the same order as OVERRIDE_ORDER """ # SMELL: Move the conditions out of this function - # environment variables have the lowest priority - config_file = os.environ.get("PIP_CONFIG_FILE", None) - if config_file is not None: - yield kinds.ENV, [config_file] - else: - yield kinds.ENV, [] - + env_config_file = os.environ.get("PIP_CONFIG_FILE", None) config_files = get_configuration_files() - # at the base we have any global configuration yield kinds.GLOBAL, config_files[kinds.GLOBAL] - # per-user configuration next + # per-user config is not loaded when env_config_file exists should_load_user_config = not self.isolated and not ( - config_file and os.path.exists(config_file) + env_config_file and os.path.exists(env_config_file) ) if should_load_user_config: # The legacy config file is overridden by the new config file yield kinds.USER, config_files[kinds.USER] - # finally virtualenv configuration first trumping others + # virtualenv config yield kinds.SITE, config_files[kinds.SITE] + if env_config_file is not None: + yield kinds.ENV, [env_config_file] + else: + yield kinds.ENV, [] + def get_values_in_config(self, variant: Kind) -> Dict[str, Any]: """Get values present in a config file""" return self._config[variant] diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index d95fe44b34a..5007a622d82 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -247,10 +247,7 @@ def __init__( def __str__(self) -> str: # Use `dist` in the error message because its stringification # includes more information, like the version and location. - return "None {} metadata found for distribution: {}".format( - self.metadata_name, - self.dist, - ) + return f"None {self.metadata_name} metadata found for distribution: {self.dist}" class UserInstallationInvalid(InstallationError): @@ -594,7 +591,7 @@ def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> N self.gots = gots def body(self) -> str: - return " {}:\n{}".format(self._requirement_name(), self._hash_comparison()) + return f" {self._requirement_name()}:\n{self._hash_comparison()}" def _hash_comparison(self) -> str: """ @@ -616,11 +613,9 @@ def hash_then_or(hash_name: str) -> "chain[str]": lines: List[str] = [] for hash_name, expecteds in self.allowed.items(): prefix = hash_then_or(hash_name) - lines.extend( - (" Expected {} {}".format(next(prefix), e)) for e in expecteds - ) + lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds) lines.append( - " Got {}\n".format(self.gots[hash_name].hexdigest()) + f" Got {self.gots[hash_name].hexdigest()}\n" ) return "\n".join(lines) diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 2121ca327e6..ec9ebc36718 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -533,8 +533,8 @@ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: ) except ValueError: raise UnsupportedWheel( - "{} is not a supported wheel for this platform. It " - "can't be sorted.".format(wheel.filename) + f"{wheel.filename} is not a supported wheel for this platform. It " + "can't be sorted." ) if self._prefer_binary: binary_preference = 1 @@ -939,9 +939,7 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: _format_versions(best_candidate_result.iter_all()), ) - raise DistributionNotFound( - "No matching distribution found for {}".format(req) - ) + raise DistributionNotFound(f"No matching distribution found for {req}") def _should_install_candidate( candidate: Optional[InstallationCandidate], diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 48689f5fbe4..0e18c6e1e14 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -56,8 +56,7 @@ def distutils_scheme( try: d.parse_config_files() except UnicodeDecodeError: - # Typeshed does not include find_config_files() for some reason. - paths = d.find_config_files() # type: ignore + paths = d.find_config_files() logger.warning( "Ignore distutils configs in %s due to encoding errors.", ", ".join(os.path.basename(p) for p in paths), diff --git a/src/pip/_internal/metadata/_json.py b/src/pip/_internal/metadata/_json.py index 336b52f1efd..27362fc726c 100644 --- a/src/pip/_internal/metadata/_json.py +++ b/src/pip/_internal/metadata/_json.py @@ -64,10 +64,10 @@ def sanitise_header(h: Union[Header, str]) -> str: key = json_name(field) if multi: value: Union[str, List[str]] = [ - sanitise_header(v) for v in msg.get_all(field) + sanitise_header(v) for v in msg.get_all(field) # type: ignore ] else: - value = sanitise_header(msg.get(field)) + value = sanitise_header(msg.get(field)) # type: ignore if key == "keywords": # Accept both comma-separated and space-separated # forms, for better compatibility with old data. diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py index 3850ddaf412..048dc55dcb2 100644 --- a/src/pip/_internal/metadata/importlib/_envs.py +++ b/src/pip/_internal/metadata/importlib/_envs.py @@ -151,7 +151,8 @@ def _emit_egg_deprecation(location: Optional[str]) -> None: deprecated( reason=f"Loading egg at {location} is deprecated.", replacement="to use pip for package installation.", - gone_in="23.3", + gone_in="24.3", + issue=12330, ) diff --git a/src/pip/_internal/models/candidate.py b/src/pip/_internal/models/candidate.py index a4963aec638..9184a902aef 100644 --- a/src/pip/_internal/models/candidate.py +++ b/src/pip/_internal/models/candidate.py @@ -27,8 +27,4 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return "{!r} candidate (version {} at {})".format( - self.name, - self.version, - self.link, - ) + return f"{self.name!r} candidate (version {self.version} at {self.link})" diff --git a/src/pip/_internal/models/direct_url.py b/src/pip/_internal/models/direct_url.py index e219d73849b..0af884bd8e3 100644 --- a/src/pip/_internal/models/direct_url.py +++ b/src/pip/_internal/models/direct_url.py @@ -31,9 +31,7 @@ def _get( value = d[key] if not isinstance(value, expected_type): raise DirectUrlValidationError( - "{!r} has unexpected type for {} (expected {})".format( - value, key, expected_type - ) + f"{value!r} has unexpected type for {key} (expected {expected_type})" ) return value diff --git a/src/pip/_internal/models/format_control.py b/src/pip/_internal/models/format_control.py index db3995eac9f..ccd11272c03 100644 --- a/src/pip/_internal/models/format_control.py +++ b/src/pip/_internal/models/format_control.py @@ -33,9 +33,7 @@ def __eq__(self, other: object) -> bool: return all(getattr(self, k) == getattr(other, k) for k in self.__slots__) def __repr__(self) -> str: - return "{}({}, {})".format( - self.__class__.__name__, self.no_binary, self.only_binary - ) + return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})" @staticmethod def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None: diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 4453519ad02..73041b864c3 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -368,9 +368,7 @@ def __str__(self) -> str: else: rp = "" if self.comes_from: - return "{} (from {}){}".format( - redact_auth_from_url(self._url), self.comes_from, rp - ) + return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}" else: return redact_auth_from_url(str(self._url)) diff --git a/src/pip/_internal/network/cache.py b/src/pip/_internal/network/cache.py index a4d13620532..4d0fb545dc2 100644 --- a/src/pip/_internal/network/cache.py +++ b/src/pip/_internal/network/cache.py @@ -33,6 +33,18 @@ class SafeFileCache(SeparateBodyBaseCache): """ A file based cache which is safe to use even when the target directory may not be accessible or writable. + + There is a race condition when two processes try to write and/or read the + same entry at the same time, since each entry consists of two separate + files (https://github.com/psf/cachecontrol/issues/324). We therefore have + additional logic that makes sure that both files to be present before + returning an entry; this fixes the read side of the race condition. + + For the write side, we assume that the server will only ever return the + same data for the same URL, which ought to be the case for files pip is + downloading. PyPI does not have a mechanism to swap out a wheel for + another wheel, for example. If this assumption is not true, the + CacheControl issue will need to be fixed. """ def __init__(self, directory: str) -> None: @@ -49,9 +61,13 @@ def _get_cache_path(self, name: str) -> str: return os.path.join(self.directory, *parts) def get(self, key: str) -> Optional[bytes]: - path = self._get_cache_path(key) + # The cache entry is only valid if both metadata and body exist. + metadata_path = self._get_cache_path(key) + body_path = metadata_path + ".body" + if not (os.path.exists(metadata_path) and os.path.exists(body_path)): + return None with suppressed_cache_errors(): - with open(path, "rb") as f: + with open(metadata_path, "rb") as f: return f.read() def _write(self, path: str, data: bytes) -> None: @@ -77,9 +93,13 @@ def delete(self, key: str) -> None: os.remove(path + ".body") def get_body(self, key: str) -> Optional[BinaryIO]: - path = self._get_cache_path(key) + ".body" + # The cache entry is only valid if both metadata and body exist. + metadata_path = self._get_cache_path(key) + body_path = metadata_path + ".body" + if not (os.path.exists(metadata_path) and os.path.exists(body_path)): + return None with suppressed_cache_errors(): - return open(path, "rb") + return open(body_path, "rb") def set_body(self, key: str, body: bytes) -> None: path = self._get_cache_path(key) + ".body" diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index 79b82a570e5..d1d43541e6b 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -42,7 +42,7 @@ def _prepare_download( logged_url = redact_auth_from_url(url) if total_length: - logged_url = "{} ({})".format(logged_url, format_size(total_length)) + logged_url = f"{logged_url} ({format_size(total_length)})" if is_from_cache(resp): logger.info("Using cached %s", logged_url) diff --git a/src/pip/_internal/network/xmlrpc.py b/src/pip/_internal/network/xmlrpc.py index 4a7d55d0e50..22ec8d2f4a6 100644 --- a/src/pip/_internal/network/xmlrpc.py +++ b/src/pip/_internal/network/xmlrpc.py @@ -13,6 +13,8 @@ if TYPE_CHECKING: from xmlrpc.client import _HostType, _Marshallable + from _typeshed import SizedBuffer + logger = logging.getLogger(__name__) @@ -33,7 +35,7 @@ def request( self, host: "_HostType", handler: str, - request_body: bytes, + request_body: "SizedBuffer", verbose: bool = False, ) -> Tuple["_Marshallable", ...]: assert isinstance(host, str) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index 2610459228f..1b7fd7ab7fd 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -168,7 +168,7 @@ def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None: f"release a version with a conforming version number" ), issue=12063, - gone_in="23.3", + gone_in="24.0", ) for dep in package_details.dependencies: if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier): @@ -183,5 +183,5 @@ def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None: f"release a version with a conforming dependency specifiers" ), issue=12063, - gone_in="23.3", + gone_in="24.0", ) diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index 58a7730597b..f67180c9e65 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -164,16 +164,14 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: for parent_dir, dir_scripts in warn_for.items(): sorted_scripts: List[str] = sorted(dir_scripts) if len(sorted_scripts) == 1: - start_text = "script {} is".format(sorted_scripts[0]) + start_text = f"script {sorted_scripts[0]} is" else: start_text = "scripts {} are".format( ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1] ) msg_lines.append( - "The {} installed in '{}' which is not on PATH.".format( - start_text, parent_dir - ) + f"The {start_text} installed in '{parent_dir}' which is not on PATH." ) last_line_fmt = ( @@ -321,9 +319,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]: scripts_to_generate.append("pip = " + pip_script) if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": - scripts_to_generate.append( - "pip{} = {}".format(sys.version_info[0], pip_script) - ) + scripts_to_generate.append(f"pip{sys.version_info[0]} = {pip_script}") scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}") # Delete any other versioned pip entry points @@ -336,9 +332,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]: scripts_to_generate.append("easy_install = " + easy_install_script) scripts_to_generate.append( - "easy_install-{} = {}".format( - get_major_minor_version(), easy_install_script - ) + f"easy_install-{get_major_minor_version()} = {easy_install_script}" ) # Delete any other versioned easy_install entry points easy_install_ep = [ @@ -408,10 +402,10 @@ def save(self) -> None: class MissingCallableSuffix(InstallationError): def __init__(self, entry_point: str) -> None: super().__init__( - "Invalid script entry point: {} - A callable " + f"Invalid script entry point: {entry_point} - A callable " "suffix is required. Cf https://packaging.python.org/" "specifications/entry-points/#use-for-scripts for more " - "information.".format(entry_point) + "information." ) @@ -712,7 +706,7 @@ def req_error_context(req_description: str) -> Generator[None, None, None]: try: yield except InstallationError as e: - message = "For req: {}. {}".format(req_description, e.args[0]) + message = f"For req: {req_description}. {e.args[0]}" raise InstallationError(message) from e diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 1b32d7eec3e..956717d1e52 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -47,6 +47,7 @@ display_path, hash_file, hide_url, + redact_auth_from_requirement, ) from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.unpacking import unpack_file @@ -277,7 +278,7 @@ def _log_preparing_link(self, req: InstallRequirement) -> None: information = str(display_path(req.link.file_path)) else: message = "Collecting %s" - information = str(req.req or req) + information = redact_auth_from_requirement(req.req) if req.req else str(req) # If we used req.req, inject requirement source if available (this # would already be included if we used req directly) @@ -602,8 +603,8 @@ def _prepare_linked_requirement( ) except NetworkConnectionError as exc: raise InstallationError( - "Could not install requirement {} because of HTTP " - "error {} for URL {}".format(req, exc, link) + f"Could not install requirement {req} because of HTTP " + f"error {exc} for URL {link}" ) else: file_path = self._downloaded[link.url] @@ -683,9 +684,9 @@ def prepare_editable_requirement( with indent_log(): if self.require_hashes: raise InstallationError( - "The editable requirement {} cannot be installed when " + f"The editable requirement {req} cannot be installed when " "requiring hashes, because there is no single file to " - "hash.".format(req) + "hash." ) req.ensure_has_source_dir(self.src_dir) req.update_editable() @@ -713,7 +714,7 @@ def prepare_installed_requirement( assert req.satisfied_by, "req should have been satisfied but isn't" assert skip_reason is not None, ( "did not get skip reason skipped but req.satisfied_by " - "is set to {}".format(req.satisfied_by) + f"is set to {req.satisfied_by}" ) logger.info( "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version diff --git a/src/pip/_internal/pyproject.py b/src/pip/_internal/pyproject.py index eb8e12b2dec..8de36b873ed 100644 --- a/src/pip/_internal/pyproject.py +++ b/src/pip/_internal/pyproject.py @@ -123,7 +123,7 @@ def load_pyproject_toml( # a version of setuptools that supports that backend. build_system = { - "requires": ["setuptools>=40.8.0", "wheel"], + "requires": ["setuptools>=40.8.0"], "build-backend": "setuptools.build_meta:__legacy__", } diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index b52c9a456bb..7e2d0e5b879 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -462,7 +462,7 @@ def install_req_from_req_string( raise InstallationError( "Packages installed from PyPI cannot depend on packages " "which are not also hosted on PyPI.\n" - "{} depends on {} ".format(comes_from.name, req) + f"{comes_from.name} depends on {req} " ) return InstallRequirement( diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index dd8a0db2792..b61a219df68 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -49,6 +49,7 @@ display_path, hide_url, is_installable_dir, + redact_auth_from_requirement, redact_auth_from_url, ) from pip._internal.utils.packaging import safe_extra @@ -188,9 +189,9 @@ def __init__( def __str__(self) -> str: if self.req: - s = str(self.req) + s = redact_auth_from_requirement(self.req) if self.link: - s += " from {}".format(redact_auth_from_url(self.link.url)) + s += f" from {redact_auth_from_url(self.link.url)}" elif self.link: s = redact_auth_from_url(self.link.url) else: @@ -220,7 +221,7 @@ def format_debug(self) -> str: attributes = vars(self) names = sorted(attributes) - state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names)) + state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names)) return "<{name} object: {{{state}}}>".format( name=self.__class__.__name__, state=", ".join(state), @@ -753,8 +754,8 @@ def archive(self, build_dir: Optional[str]) -> None: if os.path.exists(archive_path): response = ask_path_exists( - "The file {} exists. (i)gnore, (w)ipe, " - "(b)ackup, (a)bort ".format(display_path(archive_path)), + f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, " + "(b)ackup, (a)bort ", ("i", "w", "b", "a"), ) if response == "i": diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py index cff67601737..1bf73d595f6 100644 --- a/src/pip/_internal/req/req_set.py +++ b/src/pip/_internal/req/req_set.py @@ -99,7 +99,7 @@ def warn_legacy_versions_and_specifiers(self) -> None: "or contact the package author to fix the version number" ), issue=12063, - gone_in="23.3", + gone_in="24.0", ) for dep in req.get_dist().iter_dependencies(): if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier): @@ -115,5 +115,5 @@ def warn_legacy_versions_and_specifiers(self) -> None: "or contact the package author to fix the version number" ), issue=12063, - gone_in="23.3", + gone_in="24.0", ) diff --git a/src/pip/_internal/req/req_uninstall.py b/src/pip/_internal/req/req_uninstall.py index 861aa4f2286..3ca10098cf9 100644 --- a/src/pip/_internal/req/req_uninstall.py +++ b/src/pip/_internal/req/req_uninstall.py @@ -71,16 +71,16 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]: entries = dist.iter_declared_entries() if entries is None: - msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist) + msg = f"Cannot uninstall {dist}, RECORD file not found." installer = dist.installer if not installer or installer == "pip": - dep = "{}=={}".format(dist.raw_name, dist.version) + dep = f"{dist.raw_name}=={dist.version}" msg += ( " You might be able to recover from this via: " - "'pip install --force-reinstall --no-deps {}'.".format(dep) + f"'pip install --force-reinstall --no-deps {dep}'." ) else: - msg += " Hint: The package was installed by {}.".format(installer) + msg += f" Hint: The package was installed by {installer}." raise UninstallationError(msg) for entry in entries: diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index b17b7e4530b..5ddb848a9bc 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -231,9 +231,7 @@ def _add_requirement_to_set( tags = compatibility_tags.get_supported() if requirement_set.check_supported_wheels and not wheel.supported(tags): raise InstallationError( - "{} is not a supported wheel on this platform.".format( - wheel.filename - ) + f"{wheel.filename} is not a supported wheel on this platform." ) # This next bit is really a sanity check. @@ -287,9 +285,9 @@ def _add_requirement_to_set( ) if does_not_satisfy_constraint: raise InstallationError( - "Could not satisfy constraints for '{}': " + f"Could not satisfy constraints for '{install_req.name}': " "installation from path or url cannot be " - "constrained to a version".format(install_req.name) + "constrained to a version" ) # If we're now installing a constraint, mark the existing # object for real installation. @@ -398,9 +396,9 @@ def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]: # "UnicodeEncodeError: 'ascii' codec can't encode character" # in Python 2 when the reason contains non-ascii characters. "The candidate selected for download or install is a " - "yanked version: {candidate}\n" - "Reason for being yanked: {reason}" - ).format(candidate=best_candidate, reason=reason) + f"yanked version: {best_candidate}\n" + f"Reason for being yanked: {reason}" + ) logger.warning(msg) return link diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 97541655fa0..4125cda2b7c 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -159,10 +159,7 @@ def __str__(self) -> str: return f"{self.name} {self.version}" def __repr__(self) -> str: - return "{class_name}({link!r})".format( - class_name=self.__class__.__name__, - link=str(self._link), - ) + return f"{self.__class__.__name__}({str(self._link)!r})" def __hash__(self) -> int: return hash((self.__class__, self._link)) @@ -354,10 +351,7 @@ def __str__(self) -> str: return str(self.dist) def __repr__(self) -> str: - return "{class_name}({distribution!r})".format( - class_name=self.__class__.__name__, - distribution=self.dist, - ) + return f"{self.__class__.__name__}({self.dist!r})" def __hash__(self) -> int: return hash((self.__class__, self.name, self.version)) @@ -455,11 +449,7 @@ def __str__(self) -> str: return "{}[{}] {}".format(name, ",".join(self.extras), rest) def __repr__(self) -> str: - return "{class_name}(base={base!r}, extras={extras!r})".format( - class_name=self.__class__.__name__, - base=self.base, - extras=self.extras, - ) + return f"{self.__class__.__name__}(base={self.base!r}, extras={self.extras!r})" def __hash__(self) -> int: return hash((self.base, self.extras)) diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 38c199448a1..4adeb4309ce 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -36,7 +36,10 @@ from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.operations.prepare import RequirementPreparer -from pip._internal.req.constructors import install_req_from_link_and_ireq +from pip._internal.req.constructors import ( + install_req_drop_extras, + install_req_from_link_and_ireq, +) from pip._internal.req.req_install import ( InstallRequirement, check_invalid_constraint_type, @@ -176,6 +179,20 @@ def _make_candidate_from_link( name: Optional[NormalizedName], version: Optional[CandidateVersion], ) -> Optional[Candidate]: + base: Optional[BaseCandidate] = self._make_base_candidate_from_link( + link, template, name, version + ) + if not extras or base is None: + return base + return self._make_extras_candidate(base, extras, comes_from=template) + + def _make_base_candidate_from_link( + self, + link: Link, + template: InstallRequirement, + name: Optional[NormalizedName], + version: Optional[CandidateVersion], + ) -> Optional[BaseCandidate]: # TODO: Check already installed candidate, and use it if the link and # editable flag match. @@ -204,7 +221,7 @@ def _make_candidate_from_link( self._build_failures[link] = e return None - base: BaseCandidate = self._editable_candidate_cache[link] + return self._editable_candidate_cache[link] else: if link not in self._link_candidate_cache: try: @@ -224,11 +241,7 @@ def _make_candidate_from_link( ) self._build_failures[link] = e return None - base = self._link_candidate_cache[link] - - if not extras: - return base - return self._make_extras_candidate(base, extras, comes_from=template) + return self._link_candidate_cache[link] def _iter_found_candidates( self, @@ -362,9 +375,8 @@ def _iter_candidates_from_constraints( """ for link in constraint.links: self._fail_if_link_is_unsupported_wheel(link) - candidate = self._make_candidate_from_link( + candidate = self._make_base_candidate_from_link( link, - extras=frozenset(), template=install_req_from_link_and_ireq(link, template), name=canonicalize_name(identifier), version=None, @@ -454,10 +466,10 @@ def _make_requirements_from_install_req( Returns requirement objects associated with the given InstallRequirement. In most cases this will be a single object but the following special cases exist: - the InstallRequirement has markers that do not apply -> result is empty - - the InstallRequirement has both a constraint and extras -> result is split - in two requirement objects: one with the constraint and one with the - extra. This allows centralized constraint handling for the base, - resulting in fewer candidate rejections. + - the InstallRequirement has both a constraint (or link) and extras + -> result is split in two requirement objects: one with the constraint + (or link) and one with the extra. This allows centralized constraint + handling for the base, resulting in fewer candidate rejections. """ if not ireq.match_markers(requested_extras): logger.info( @@ -471,10 +483,13 @@ def _make_requirements_from_install_req( yield SpecifierRequirement(ireq) else: self._fail_if_link_is_unsupported_wheel(ireq.link) - cand = self._make_candidate_from_link( + # Always make the link candidate for the base requirement to make it + # available to `find_candidates` for explicit candidate lookup for any + # set of extras. + # The extras are required separately via a second requirement. + cand = self._make_base_candidate_from_link( ireq.link, - extras=frozenset(ireq.extras), - template=ireq, + template=install_req_drop_extras(ireq) if ireq.extras else ireq, name=canonicalize_name(ireq.name) if ireq.name else None, version=None, ) @@ -489,7 +504,13 @@ def _make_requirements_from_install_req( raise self._build_failures[ireq.link] yield UnsatisfiableRequirement(canonicalize_name(ireq.name)) else: + # require the base from the link yield self.make_requirement_from_candidate(cand) + if ireq.extras: + # require the extras on top of the base candidate + yield self.make_requirement_from_candidate( + self._make_extras_candidate(cand, frozenset(ireq.extras)) + ) def collect_root_requirements( self, root_ireqs: List[InstallRequirement] @@ -753,8 +774,8 @@ def describe_trigger(parent: Candidate) -> str: info = "the requested packages" msg = ( - "Cannot install {} because these package versions " - "have conflicting dependencies.".format(info) + f"Cannot install {info} because these package versions " + "have conflicting dependencies." ) logger.critical(msg) msg = "\nThe conflict is caused by:" diff --git a/src/pip/_internal/resolution/resolvelib/requirements.py b/src/pip/_internal/resolution/resolvelib/requirements.py index 7d1e7bfddd1..4af4a9f25a6 100644 --- a/src/pip/_internal/resolution/resolvelib/requirements.py +++ b/src/pip/_internal/resolution/resolvelib/requirements.py @@ -15,10 +15,7 @@ def __str__(self) -> str: return str(self.candidate) def __repr__(self) -> str: - return "{class_name}({candidate!r})".format( - class_name=self.__class__.__name__, - candidate=self.candidate, - ) + return f"{self.__class__.__name__}({self.candidate!r})" @property def project_name(self) -> NormalizedName: @@ -50,10 +47,7 @@ def __str__(self) -> str: return str(self._ireq.req) def __repr__(self) -> str: - return "{class_name}({requirement!r})".format( - class_name=self.__class__.__name__, - requirement=str(self._ireq.req), - ) + return f"{self.__class__.__name__}({str(self._ireq.req)!r})" @property def project_name(self) -> NormalizedName: @@ -116,10 +110,7 @@ def __str__(self) -> str: return f"Python {self.specifier}" def __repr__(self) -> str: - return "{class_name}({specifier!r})".format( - class_name=self.__class__.__name__, - specifier=str(self.specifier), - ) + return f"{self.__class__.__name__}({str(self.specifier)!r})" @property def project_name(self) -> NormalizedName: @@ -155,10 +146,7 @@ def __str__(self) -> str: return f"{self._name} (unavailable)" def __repr__(self) -> str: - return "{class_name}({name!r})".format( - class_name=self.__class__.__name__, - name=str(self._name), - ) + return f"{self.__class__.__name__}({str(self._name)!r})" @property def project_name(self) -> NormalizedName: diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py index cb18edbed8e..0f64ae0e614 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -39,6 +39,15 @@ def _get_statefile_name(key: str) -> str: return name +def _convert_date(isodate: str) -> datetime.datetime: + """Convert an ISO format string to a date. + + Handles the format 2020-01-22T14:24:01Z (trailing Z) + which is not supported by older versions of fromisoformat. + """ + return datetime.datetime.fromisoformat(isodate.replace("Z", "+00:00")) + + class SelfCheckState: def __init__(self, cache_dir: str) -> None: self._state: Dict[str, Any] = {} @@ -73,7 +82,7 @@ def get(self, current_time: datetime.datetime) -> Optional[str]: return None # Determine if we need to refresh the state - last_check = datetime.datetime.fromisoformat(self._state["last_check"]) + last_check = _convert_date(self._state["last_check"]) time_since_last_check = current_time - last_check if time_since_last_check > _WEEK: return None diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 9a6353fc8d3..1ad3f6162a2 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -35,6 +35,7 @@ cast, ) +from pip._vendor.packaging.requirements import Requirement from pip._vendor.pyproject_hooks import BuildBackendHookCaller from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed @@ -76,11 +77,7 @@ def get_pip_version() -> str: pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") pip_pkg_dir = os.path.abspath(pip_pkg_dir) - return "pip {} from {} (python {})".format( - __version__, - pip_pkg_dir, - get_major_minor_version(), - ) + return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})" def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]: @@ -144,9 +141,9 @@ def rmtree( ) if sys.version_info >= (3, 12): # See https://docs.python.org/3.12/whatsnew/3.12.html#shutil. - shutil.rmtree(dir, onexc=handler) + shutil.rmtree(dir, onexc=handler) # type: ignore else: - shutil.rmtree(dir, onerror=handler) + shutil.rmtree(dir, onerror=handler) # type: ignore def _onerror_ignore(*_args: Any) -> None: @@ -278,13 +275,13 @@ def strtobool(val: str) -> int: def format_size(bytes: float) -> str: if bytes > 1000 * 1000: - return "{:.1f} MB".format(bytes / 1000.0 / 1000) + return f"{bytes / 1000.0 / 1000:.1f} MB" elif bytes > 10 * 1000: - return "{} kB".format(int(bytes / 1000)) + return f"{int(bytes / 1000)} kB" elif bytes > 1000: - return "{:.1f} kB".format(bytes / 1000.0) + return f"{bytes / 1000.0:.1f} kB" else: - return "{} bytes".format(int(bytes)) + return f"{int(bytes)} bytes" def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]: @@ -521,9 +518,7 @@ def redact_netloc(netloc: str) -> str: else: user = urllib.parse.quote(user) password = ":****" - return "{user}{password}@{netloc}".format( - user=user, password=password, netloc=netloc - ) + return f"{user}{password}@{netloc}" def _transform_url( @@ -578,13 +573,20 @@ def redact_auth_from_url(url: str) -> str: return _transform_url(url, _redact_netloc)[0] +def redact_auth_from_requirement(req: Requirement) -> str: + """Replace the password in a given requirement url with ****.""" + if not req.url: + return str(req) + return str(req).replace(req.url, redact_auth_from_url(req.url)) + + class HiddenText: def __init__(self, secret: str, redacted: str) -> None: self.secret = secret self.redacted = redacted def __repr__(self) -> str: - return "".format(str(self)) + return f"" def __str__(self) -> str: return self.redacted diff --git a/src/pip/_internal/utils/wheel.py b/src/pip/_internal/utils/wheel.py index e5e3f34ed81..3551f8f19bc 100644 --- a/src/pip/_internal/utils/wheel.py +++ b/src/pip/_internal/utils/wheel.py @@ -28,7 +28,7 @@ def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]: metadata = wheel_metadata(wheel_zip, info_dir) version = wheel_version(metadata) except UnsupportedWheel as e: - raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e))) + raise UnsupportedWheel(f"{name} has an invalid wheel, {str(e)}") check_compatibility(version, name) @@ -60,9 +60,7 @@ def wheel_dist_info_dir(source: ZipFile, name: str) -> str: canonical_name = canonicalize_name(name) if not info_dir_name.startswith(canonical_name): raise UnsupportedWheel( - ".dist-info directory {!r} does not start with {!r}".format( - info_dir, canonical_name - ) + f".dist-info directory {info_dir!r} does not start with {canonical_name!r}" ) return info_dir diff --git a/src/pip/_internal/vcs/mercurial.py b/src/pip/_internal/vcs/mercurial.py index e440c122169..c183d41d09c 100644 --- a/src/pip/_internal/vcs/mercurial.py +++ b/src/pip/_internal/vcs/mercurial.py @@ -31,7 +31,7 @@ class Mercurial(VersionControl): @staticmethod def get_base_rev_args(rev: str) -> List[str]: - return [f"-r={rev}"] + return [f"--rev={rev}"] def fetch_new( self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py index 02bbf68e7ad..46ca2799b76 100644 --- a/src/pip/_internal/vcs/versioncontrol.py +++ b/src/pip/_internal/vcs/versioncontrol.py @@ -405,9 +405,9 @@ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) if "+" not in scheme: raise ValueError( - "Sorry, {!r} is a malformed VCS url. " + f"Sorry, {url!r} is a malformed VCS url. " "The format is +://, " - "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url) + "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp" ) # Remove the vcs prefix. scheme = scheme.split("+", 1)[1] @@ -417,9 +417,9 @@ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: path, rev = path.rsplit("@", 1) if not rev: raise InstallationError( - "The URL {!r} has an empty revision (after @) " + f"The URL {url!r} has an empty revision (after @) " "which is not supported. Include a revision after @ " - "or remove @ from the URL.".format(url) + "or remove @ from the URL." ) url = urllib.parse.urlunsplit((scheme, netloc, path, query, "")) return url, rev, user_pass @@ -566,7 +566,7 @@ def obtain(self, dest: str, url: HiddenText, verbosity: int) -> None: self.name, url, ) - response = ask_path_exists("What to do? {}".format(prompt[0]), prompt[1]) + response = ask_path_exists(f"What to do? {prompt[0]}", prompt[1]) if response == "a": sys.exit(-1) diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 60d75dd18ef..b1debe3496c 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -140,15 +140,15 @@ def _verify_one(req: InstallRequirement, wheel_path: str) -> None: w = Wheel(os.path.basename(wheel_path)) if canonicalize_name(w.name) != canonical_name: raise InvalidWheelFilename( - "Wheel has unexpected file name: expected {!r}, " - "got {!r}".format(canonical_name, w.name), + f"Wheel has unexpected file name: expected {canonical_name!r}, " + f"got {w.name!r}", ) dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name) dist_verstr = str(dist.version) if canonicalize_version(dist_verstr) != canonicalize_version(w.version): raise InvalidWheelFilename( - "Wheel has unexpected file name: expected {!r}, " - "got {!r}".format(dist_verstr, w.version), + f"Wheel has unexpected file name: expected {dist_verstr!r}, " + f"got {w.version!r}", ) metadata_version_value = dist.metadata_version if metadata_version_value is None: @@ -160,8 +160,7 @@ def _verify_one(req: InstallRequirement, wheel_path: str) -> None: raise UnsupportedWheel(msg) if metadata_version >= Version("1.2") and not isinstance(dist.version, Version): raise UnsupportedWheel( - "Metadata 1.2 mandates PEP 440 version, " - "but {!r} is not".format(dist_verstr) + f"Metadata 1.2 mandates PEP 440 version, but {dist_verstr!r} is not" ) diff --git a/src/pip/_vendor/urllib3/_version.py b/src/pip/_vendor/urllib3/_version.py index d69ca314570..cad75fb5df8 100644 --- a/src/pip/_vendor/urllib3/_version.py +++ b/src/pip/_vendor/urllib3/_version.py @@ -1,2 +1,2 @@ # This file is protected via CODEOWNERS -__version__ = "1.26.16" +__version__ = "1.26.17" diff --git a/src/pip/_vendor/urllib3/request.py b/src/pip/_vendor/urllib3/request.py index 398386a5b9f..3b4cf999225 100644 --- a/src/pip/_vendor/urllib3/request.py +++ b/src/pip/_vendor/urllib3/request.py @@ -1,6 +1,9 @@ from __future__ import absolute_import +import sys + from .filepost import encode_multipart_formdata +from .packages import six from .packages.six.moves.urllib.parse import urlencode __all__ = ["RequestMethods"] @@ -168,3 +171,21 @@ def request_encode_body( extra_kw.update(urlopen_kw) return self.urlopen(method, url, **extra_kw) + + +if not six.PY2: + + class RequestModule(sys.modules[__name__].__class__): + def __call__(self, *args, **kwargs): + """ + If user tries to call this module directly urllib3 v2.x style raise an error to the user + suggesting they may need urllib3 v2 + """ + raise TypeError( + "'module' object is not callable\n" + "urllib3.request() method is not supported in this release, " + "upgrade to urllib3 v2 to use it\n" + "see https://urllib3.readthedocs.io/en/stable/v2-migration-guide.html" + ) + + sys.modules[__name__].__class__ = RequestModule diff --git a/src/pip/_vendor/urllib3/util/retry.py b/src/pip/_vendor/urllib3/util/retry.py index 2490d5e5b63..60ef6c4f3f9 100644 --- a/src/pip/_vendor/urllib3/util/retry.py +++ b/src/pip/_vendor/urllib3/util/retry.py @@ -235,7 +235,7 @@ class Retry(object): RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) #: Default headers to be used for ``remove_headers_on_redirect`` - DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"]) #: Maximum backoff time. DEFAULT_BACKOFF_MAX = 120 diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index 43ced2a4b89..8dbe1341377 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -11,7 +11,7 @@ requests==2.31.0 certifi==2023.7.22 chardet==5.1.0 idna==3.4 - urllib3==1.26.16 + urllib3==1.26.17 rich==13.4.2 pygments==2.15.1 typing_extensions==4.7.1 diff --git a/tests/conftest.py b/tests/conftest.py index 6ae2e6d62d8..c5bf4bb9567 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from pathlib import Path from textwrap import dedent from typing import ( + TYPE_CHECKING, Any, AnyStr, Callable, @@ -58,6 +59,9 @@ from tests.lib.server import MockServer, make_mock_server from tests.lib.venv import VirtualEnvironment, VirtualEnvironmentType +if TYPE_CHECKING: + from pip._vendor.typing_extensions import Self + def pytest_addoption(parser: Parser) -> None: parser.addoption( @@ -141,7 +145,7 @@ def pytest_collection_modifyitems(config: Config, items: List[pytest.Function]) if "script" in item.fixturenames: raise RuntimeError( "Cannot use the ``script`` funcarg in a unit test: " - "(filename = {}, item = {})".format(module_path, item) + f"(filename = {module_path}, item = {item})" ) else: raise RuntimeError(f"Unknown test type (filename = {module_path})") @@ -941,7 +945,7 @@ def html_index_with_onetime_server( """ class InDirectoryServer(http.server.ThreadingHTTPServer): - def finish_request(self, request: Any, client_address: Any) -> None: + def finish_request(self: "Self", request: Any, client_address: Any) -> None: self.RequestHandlerClass( request, client_address, diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py index a1b69b72106..3c3f45d51d1 100644 --- a/tests/functional/test_cli.py +++ b/tests/functional/test_cli.py @@ -23,7 +23,7 @@ def test_entrypoints_work(entrypoint: str, script: PipTestEnvironment) -> None: fake_pkg.mkdir() fake_pkg.joinpath("setup.py").write_text( dedent( - """ + f""" from setuptools import setup setup( @@ -31,13 +31,11 @@ def test_entrypoints_work(entrypoint: str, script: PipTestEnvironment) -> None: version="0.1.0", entry_points={{ "console_scripts": [ - {!r} + {entrypoint!r} ] }} ) - """.format( - entrypoint - ) + """ ) ) diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index 2e3f31729d7..4be033583ca 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -44,9 +44,18 @@ "zsh", """\ #compdef -P pip[0-9.]# -compadd $( COMP_WORDS="$words[*]" \\ - COMP_CWORD=$((CURRENT-1)) \\ - PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )""", +__pip() { + compadd $( COMP_WORDS="$words[*]" \\ + COMP_CWORD=$((CURRENT-1)) \\ + PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ) +} +if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + __pip "$@" +else + # eval/source/. command, register function for later + compdef __pip -P 'pip[0-9.]#' +fi""", ), ( "powershell", @@ -391,7 +400,7 @@ def test_completion_path_after_option( def test_completion_uses_same_executable_name( autocomplete_script: PipTestEnvironment, flag: str, deprecated_python: bool ) -> None: - executable_name = "pip{}".format(sys.version_info[0]) + executable_name = f"pip{sys.version_info[0]}" # Deprecated python versions produce an extra deprecation warning result = autocomplete_script.run( executable_name, diff --git a/tests/functional/test_debug.py b/tests/functional/test_debug.py index 77cd732f9f1..77d4bea335f 100644 --- a/tests/functional/test_debug.py +++ b/tests/functional/test_debug.py @@ -68,7 +68,7 @@ def test_debug__tags(script: PipTestEnvironment, args: List[str]) -> None: stdout = result.stdout tags = compatibility_tags.get_supported() - expected_tag_header = "Compatible tags: {}".format(len(tags)) + expected_tag_header = f"Compatible tags: {len(tags)}" assert expected_tag_header in stdout show_verbose_note = "--verbose" not in args diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py index 9a5937df3de..b2fd1d62982 100644 --- a/tests/functional/test_freeze.py +++ b/tests/functional/test_freeze.py @@ -166,13 +166,11 @@ def fake_install(pkgname: str, dest: str) -> None: with open(egg_info_path, "w") as egg_info_file: egg_info_file.write( textwrap.dedent( - """\ + f"""\ Metadata-Version: 1.0 - Name: {} + Name: {pkgname} Version: 1.0 - """.format( - pkgname - ) + """ ) ) @@ -221,12 +219,10 @@ def test_freeze_editable_not_vcs(script: PipTestEnvironment) -> None: # We need to apply os.path.normcase() to the path since that is what # the freeze code does. expected = textwrap.dedent( - """\ + f"""\ ...# Editable install with no version control (version-pkg==0.1) - -e {} - ...""".format( - os.path.normcase(pkg_path) - ) + -e {os.path.normcase(pkg_path)} + ...""" ) _check_output(result.stdout, expected) @@ -248,12 +244,10 @@ def test_freeze_editable_git_with_no_remote( # We need to apply os.path.normcase() to the path since that is what # the freeze code does. expected = textwrap.dedent( - """\ + f"""\ ...# Editable Git install with no remote (version-pkg==0.1) - -e {} - ...""".format( - os.path.normcase(pkg_path) - ) + -e {os.path.normcase(pkg_path)} + ...""" ) _check_output(result.stdout, expected) @@ -653,9 +647,9 @@ def test_freeze_with_requirement_option_file_url_egg_not_installed( expect_stderr=True, ) expected_err = ( - "WARNING: Requirement file [requirements.txt] contains {}, " + f"WARNING: Requirement file [requirements.txt] contains {url}, " "but package 'Does.Not-Exist' is not installed\n" - ).format(url) + ) if deprecated_python: assert expected_err in result.stderr else: diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 140061a17a3..b18fabc84c9 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -106,10 +106,10 @@ def test_pep518_refuses_conflicting_requires( assert ( result.returncode != 0 and ( - "Some build dependencies for {url} conflict " + f"Some build dependencies for {project_dir.as_uri()} conflict " "with PEP 517/518 supported " "requirements: setuptools==1.0 is incompatible with " - "setuptools>=40.8.0.".format(url=project_dir.as_uri()) + "setuptools>=40.8.0." ) in result.stderr ), str(result) @@ -595,8 +595,8 @@ def test_hashed_install_success( with requirements_file( "simple2==1.0 --hash=sha256:9336af72ca661e6336eb87bc7de3e8844d853e" "3848c2b9bbd2e8bf01db88c2c7\n" - "{simple} --hash=sha256:393043e672415891885c9a2a0929b1af95fb866d6c" - "a016b42d2e6ce53619b653".format(simple=file_url), + f"{file_url} --hash=sha256:393043e672415891885c9a2a0929b1af95fb866d6c" + "a016b42d2e6ce53619b653", tmpdir, ) as reqs_file: script.pip_install_local("-r", reqs_file.resolve()) @@ -1735,7 +1735,7 @@ def test_install_builds_wheels(script: PipTestEnvironment, data: TestData) -> No # into the cache assert wheels != [], str(res) assert wheels == [ - "Upper-2.0-py{}-none-any.whl".format(sys.version_info[0]), + f"Upper-2.0-py{sys.version_info[0]}-none-any.whl", ] @@ -2387,7 +2387,7 @@ def test_install_verify_package_name_normalization( assert "Successfully installed simple-package" in result.stdout result = script.pip("install", package_name) - assert "Requirement already satisfied: {}".format(package_name) in result.stdout + assert f"Requirement already satisfied: {package_name}" in result.stdout def test_install_logs_pip_version_in_debug( diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py index ecaf2f705a2..7f418067f97 100644 --- a/tests/functional/test_install_config.py +++ b/tests/functional/test_install_config.py @@ -184,12 +184,10 @@ def test_config_file_override_stack( config_file.write_text( textwrap.dedent( - """\ + f"""\ [global] - index-url = {}/simple1 - """.format( - base_address - ) + index-url = {base_address}/simple1 + """ ) ) script.pip("install", "-vvv", "INITools", expect_error=True) @@ -197,14 +195,12 @@ def test_config_file_override_stack( config_file.write_text( textwrap.dedent( - """\ + f"""\ [global] - index-url = {address}/simple1 + index-url = {base_address}/simple1 [install] - index-url = {address}/simple2 - """.format( - address=base_address - ) + index-url = {base_address}/simple2 + """ ) ) script.pip("install", "-vvv", "INITools", expect_error=True) diff --git a/tests/functional/test_install_index.py b/tests/functional/test_install_index.py index b73e28f4794..72b0b9db7bd 100644 --- a/tests/functional/test_install_index.py +++ b/tests/functional/test_install_index.py @@ -41,13 +41,11 @@ def test_find_links_requirements_file_relative_path( """Test find-links as a relative path to a reqs file.""" script.scratch_path.joinpath("test-req.txt").write_text( textwrap.dedent( - """ + f""" --no-index - --find-links={} + --find-links={data.packages.as_posix()} parent==0.1 - """.format( - data.packages.as_posix() - ) + """ ) ) result = script.pip( diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index c21b9ba83de..c2d951f2695 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -95,7 +95,7 @@ def test_requirements_file(script: PipTestEnvironment) -> None: result.did_create(script.site_packages / "INITools-0.2.dist-info") result.did_create(script.site_packages / "initools") assert result.files_created[script.site_packages / other_lib_name].dir - fn = "{}-{}.dist-info".format(other_lib_name, other_lib_version) + fn = f"{other_lib_name}-{other_lib_version}.dist-info" assert result.files_created[script.site_packages / fn].dir @@ -260,13 +260,13 @@ def test_respect_order_in_requirements_file( assert ( "parent" in downloaded[0] - ), 'First download should be "parent" but was "{}"'.format(downloaded[0]) + ), f'First download should be "parent" but was "{downloaded[0]}"' assert ( "child" in downloaded[1] - ), 'Second download should be "child" but was "{}"'.format(downloaded[1]) + ), f'Second download should be "child" but was "{downloaded[1]}"' assert ( "simple" in downloaded[2] - ), 'Third download should be "simple" but was "{}"'.format(downloaded[2]) + ), f'Third download should be "simple" but was "{downloaded[2]}"' def test_install_local_editable_with_extras( @@ -671,9 +671,9 @@ def test_install_distribution_union_with_versions( expect_error=(resolver_variant == "resolvelib"), ) if resolver_variant == "resolvelib": - assert "Cannot install localextras[bar]" in result.stderr - assert ("localextras[bar] 0.0.1 depends on localextras 0.0.1") in result.stdout - assert ("localextras[baz] 0.0.2 depends on localextras 0.0.2") in result.stdout + assert "Cannot install localextras" in result.stderr + assert ("The user requested localextras 0.0.1") in result.stdout + assert ("The user requested localextras 0.0.2") in result.stdout else: assert ( "Successfully installed LocalExtras-0.0.1 simple-3.0 singlemodule-0.0.1" diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py index 4221ae76ae2..7e7aeaf7a81 100644 --- a/tests/functional/test_install_wheel.py +++ b/tests/functional/test_install_wheel.py @@ -169,9 +169,9 @@ def get_header_scheme_path_for_script( ) -> Path: command = ( "from pip._internal.locations import get_scheme;" - "scheme = get_scheme({!r});" + f"scheme = get_scheme({dist_name!r});" "print(scheme.headers);" - ).format(dist_name) + ) result = script.run("python", "-c", command).stdout return Path(result.strip()) diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 7b6eadcf5a5..abb2ea1f172 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -1205,7 +1205,7 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot( for index in range(1, N + 1): A_version = f"{index}.0.0" B_version = f"{index}.0.0" - C_version = "{index_minus_one}.0.0".format(index_minus_one=index - 1) + C_version = f"{index - 1}.0.0" depends = ["B == " + B_version] if index != 1: @@ -2315,7 +2315,7 @@ def test_new_resolver_dont_backtrack_on_extra_if_base_constrained_in_requirement script, "pkg", "2.0", extras={"ext1": ["dep"], "ext2": ["dep"]} ) - to_install: tuple[str, str] = ( + to_install: Tuple[str, str] = ( "pkg[ext1]", "pkg[ext2]==1.0" if two_extras else "pkg==1.0", ) @@ -2362,7 +2362,7 @@ def test_new_resolver_dont_backtrack_on_conflicting_constraints_on_extras( script, "pkg", "2.0", extras={"ext1": ["dep"], "ext2": ["dep"]} ) - to_install: tuple[str, str] = ( + to_install: Tuple[str, str] = ( "pkg[ext1]>1", "pkg[ext2]==1.0" if two_extras else "pkg==1.0", ) @@ -2426,6 +2426,51 @@ def test_new_resolver_respect_user_requested_if_extra_is_installed( script.assert_installed(pkg3="1.0", pkg2="2.0", pkg1="1.0") +def test_new_resolver_constraint_on_link_with_extra( + script: PipTestEnvironment, +) -> None: + """ + Verify that installing works from a link with both an extra and a constraint. + """ + wheel: pathlib.Path = create_basic_wheel_for_package( + script, "pkg", "1.0", extras={"ext": []} + ) + + script.pip( + "install", + "--no-cache-dir", + # no index, no --find-links: only the explicit path + "--no-index", + f"{wheel}[ext]", + "pkg==1", + ) + script.assert_installed(pkg="1.0") + + +def test_new_resolver_constraint_on_link_with_extra_indirect( + script: PipTestEnvironment, +) -> None: + """ + Verify that installing works from a link with an extra if there is an indirect + dependency on that same package with the same extra (#12372). + """ + wheel_one: pathlib.Path = create_basic_wheel_for_package( + script, "pkg1", "1.0", extras={"ext": []} + ) + wheel_two: pathlib.Path = create_basic_wheel_for_package( + script, "pkg2", "1.0", depends=["pkg1[ext]==1.0"] + ) + + script.pip( + "install", + "--no-cache-dir", + # no index, no --find-links: only the explicit path + wheel_two, + f"{wheel_one}[ext]", + ) + script.assert_installed(pkg1="1.0", pkg2="1.0") + + def test_new_resolver_do_not_backtrack_on_build_failure( script: PipTestEnvironment, ) -> None: @@ -2481,7 +2526,7 @@ def test_new_resolver_comes_from_with_extra( create_basic_wheel_for_package(script, "dep", "1.0") create_basic_wheel_for_package(script, "pkg", "1.0", extras={"ext": ["dep"]}) - to_install: tuple[str, str] = ("pkg", "pkg[ext]") + to_install: Tuple[str, str] = ("pkg", "pkg[ext]") result = script.pip( "install", diff --git a/tests/functional/test_new_resolver_errors.py b/tests/functional/test_new_resolver_errors.py index 62304131283..5976de52e39 100644 --- a/tests/functional/test_new_resolver_errors.py +++ b/tests/functional/test_new_resolver_errors.py @@ -71,8 +71,8 @@ def test_new_resolver_conflict_constraints_file( def test_new_resolver_requires_python_error(script: PipTestEnvironment) -> None: - compatible_python = ">={0.major}.{0.minor}".format(sys.version_info) - incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info) + compatible_python = f">={sys.version_info.major}.{sys.version_info.minor}" + incompatible_python = f"<{sys.version_info.major}.{sys.version_info.minor}" pkga = create_test_package_with_setup( script, @@ -99,7 +99,7 @@ def test_new_resolver_requires_python_error(script: PipTestEnvironment) -> None: def test_new_resolver_checks_requires_python_before_dependencies( script: PipTestEnvironment, ) -> None: - incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info) + incompatible_python = f"<{sys.version_info.major}.{sys.version_info.minor}" pkg_dep = create_basic_wheel_for_package( script, diff --git a/tests/functional/test_new_resolver_hashes.py b/tests/functional/test_new_resolver_hashes.py index 6db2efd0e4c..d26def14ae9 100644 --- a/tests/functional/test_new_resolver_hashes.py +++ b/tests/functional/test_new_resolver_hashes.py @@ -24,18 +24,11 @@ def _create_find_links(script: PipTestEnvironment) -> _FindLinks: index_html = script.scratch_path / "index.html" index_html.write_text( - """ + f""" - {sdist_path.stem} - {wheel_path.stem} - """.format( - sdist_url=sdist_path.as_uri(), - sdist_hash=sdist_hash, - sdist_path=sdist_path, - wheel_url=wheel_path.as_uri(), - wheel_hash=wheel_hash, - wheel_path=wheel_path, - ).strip() + {sdist_path.stem} + {wheel_path.stem} + """.strip() ) return _FindLinks(index_html, sdist_hash, wheel_hash) @@ -99,9 +92,7 @@ def test_new_resolver_hash_intersect_from_constraint( constraints_txt = script.scratch_path / "constraints.txt" constraints_txt.write_text( - "base==0.1.0 --hash=sha256:{sdist_hash}".format( - sdist_hash=find_links.sdist_hash, - ), + f"base==0.1.0 --hash=sha256:{find_links.sdist_hash}", ) requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( @@ -200,13 +191,10 @@ def test_new_resolver_hash_intersect_empty_from_constraint( constraints_txt = script.scratch_path / "constraints.txt" constraints_txt.write_text( - """ - base==0.1.0 --hash=sha256:{sdist_hash} - base==0.1.0 --hash=sha256:{wheel_hash} - """.format( - sdist_hash=find_links.sdist_hash, - wheel_hash=find_links.wheel_hash, - ), + f""" + base==0.1.0 --hash=sha256:{find_links.sdist_hash} + base==0.1.0 --hash=sha256:{find_links.wheel_hash} + """, ) result = script.pip( @@ -240,19 +228,15 @@ def test_new_resolver_hash_requirement_and_url_constraint_can_succeed( requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( - """ + f""" base==0.1.0 --hash=sha256:{wheel_hash} - """.format( - wheel_hash=wheel_hash, - ), + """, ) constraints_txt = script.scratch_path / "constraints.txt" - constraint_text = "base @ {wheel_url}\n".format(wheel_url=wheel_path.as_uri()) + constraint_text = f"base @ {wheel_path.as_uri()}\n" if constrain_by_hash: - constraint_text += "base==0.1.0 --hash=sha256:{wheel_hash}\n".format( - wheel_hash=wheel_hash, - ) + constraint_text += f"base==0.1.0 --hash=sha256:{wheel_hash}\n" constraints_txt.write_text(constraint_text) script.pip( @@ -280,19 +264,15 @@ def test_new_resolver_hash_requirement_and_url_constraint_can_fail( requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( - """ + f""" base==0.1.0 --hash=sha256:{other_hash} - """.format( - other_hash=other_hash, - ), + """, ) constraints_txt = script.scratch_path / "constraints.txt" - constraint_text = "base @ {wheel_url}\n".format(wheel_url=wheel_path.as_uri()) + constraint_text = f"base @ {wheel_path.as_uri()}\n" if constrain_by_hash: - constraint_text += "base==0.1.0 --hash=sha256:{other_hash}\n".format( - other_hash=other_hash, - ) + constraint_text += f"base==0.1.0 --hash=sha256:{other_hash}\n" constraints_txt.write_text(constraint_text) result = script.pip( @@ -343,17 +323,12 @@ def test_new_resolver_hash_with_extras(script: PipTestEnvironment) -> None: requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( - """ + f""" child[extra]==0.1.0 --hash=sha256:{child_hash} parent_with_extra==0.1.0 --hash=sha256:{parent_with_extra_hash} parent_without_extra==0.1.0 --hash=sha256:{parent_without_extra_hash} extra==0.1.0 --hash=sha256:{extra_hash} - """.format( - child_hash=child_hash, - parent_with_extra_hash=parent_with_extra_hash, - parent_without_extra_hash=parent_without_extra_hash, - extra_hash=extra_hash, - ), + """, ) script.pip( diff --git a/tests/functional/test_new_resolver_target.py b/tests/functional/test_new_resolver_target.py index 811ae935aec..a81cfe5e83d 100644 --- a/tests/functional/test_new_resolver_target.py +++ b/tests/functional/test_new_resolver_target.py @@ -58,12 +58,7 @@ def test_new_resolver_target_checks_compatibility_failure( if platform: args += ["--platform", platform] - args_tag = "{}{}-{}-{}".format( - implementation, - python_version, - abi, - platform, - ) + args_tag = f"{implementation}{python_version}-{abi}-{platform}" wheel_tag_matches = args_tag == fake_wheel_tag result = script.pip(*args, expect_error=(not wheel_tag_matches)) diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py index a642a3f8bfb..78a6c2bbc6c 100644 --- a/tests/functional/test_pep517.py +++ b/tests/functional/test_pep517.py @@ -159,9 +159,9 @@ def test_conflicting_pep517_backend_requirements( expect_error=True, ) msg = ( - "Some build dependencies for {url} conflict with the backend " + f"Some build dependencies for {project_dir.as_uri()} conflict with the backend " "dependencies: simplewheel==1.0 is incompatible with " - "simplewheel==2.0.".format(url=project_dir.as_uri()) + "simplewheel==2.0." ) assert result.returncode != 0 and msg in result.stderr, str(result) @@ -205,8 +205,8 @@ def test_validate_missing_pep517_backend_requirements( expect_error=True, ) msg = ( - "Some build dependencies for {url} are missing: " - "'simplewheel==1.0', 'test_backend'.".format(url=project_dir.as_uri()) + f"Some build dependencies for {project_dir.as_uri()} are missing: " + "'simplewheel==1.0', 'test_backend'." ) assert result.returncode != 0 and msg in result.stderr, str(result) @@ -231,9 +231,9 @@ def test_validate_conflicting_pep517_backend_requirements( expect_error=True, ) msg = ( - "Some build dependencies for {url} conflict with the backend " + f"Some build dependencies for {project_dir.as_uri()} conflict with the backend " "dependencies: simplewheel==2.0 is incompatible with " - "simplewheel==1.0.".format(url=project_dir.as_uri()) + "simplewheel==1.0." ) assert result.returncode != 0 and msg in result.stderr, str(result) diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py index be7fe4c3341..69e340a5675 100644 --- a/tests/functional/test_uninstall.py +++ b/tests/functional/test_uninstall.py @@ -604,9 +604,7 @@ def test_uninstall_without_record_fails( "simple.dist==0.1'." ) elif installer: - expected_error_message += " Hint: The package was installed by {}.".format( - installer - ) + expected_error_message += f" Hint: The package was installed by {installer}." assert result2.stderr.rstrip() == expected_error_message assert_all_changes(result.files_after, result2, ignore_changes) diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 042f5824613..b1183fc830b 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -59,9 +59,7 @@ def test_pip_wheel_success(script: PipTestEnvironment, data: TestData) -> None: wheel_file_path = script.scratch / wheel_file_name assert re.search( r"Created wheel for simple: " - r"filename={filename} size=\d+ sha256=[A-Fa-f0-9]{{64}}".format( - filename=re.escape(wheel_file_name) - ), + rf"filename={re.escape(wheel_file_name)} size=\d+ sha256=[A-Fa-f0-9]{{64}}", result.stdout, ) assert re.search(r"^\s+Stored in directory: ", result.stdout, re.M) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index d27c02e25f0..f14837e24ea 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -747,7 +747,7 @@ def assert_installed(self, **kwargs: str) -> None: for val in json.loads(ret.stdout) } expected = {(canonicalize_name(k), v) for k, v in kwargs.items()} - assert expected <= installed, "{!r} not all in {!r}".format(expected, installed) + assert expected <= installed, f"{expected!r} not all in {installed!r}" def assert_not_installed(self, *args: str) -> None: ret = self.pip("list", "--format=json") @@ -755,9 +755,7 @@ def assert_not_installed(self, *args: str) -> None: # None of the given names should be listed as installed, i.e. their # intersection should be empty. expected = {canonicalize_name(k) for k in args} - assert not (expected & installed), "{!r} contained in {!r}".format( - expected, installed - ) + assert not (expected & installed), f"{expected!r} contained in {installed!r}" # FIXME ScriptTest does something similar, but only within a single @@ -1028,7 +1026,7 @@ def _create_test_package_with_srcdir( pkg_path.joinpath("__init__.py").write_text("") subdir_path.joinpath("setup.py").write_text( textwrap.dedent( - """ + f""" from setuptools import setup, find_packages setup( name="{name}", @@ -1036,9 +1034,7 @@ def _create_test_package_with_srcdir( packages=find_packages(), package_dir={{"": "src"}}, ) - """.format( - name=name - ) + """ ) ) return _vcs_add(dir_path, version_pkg_path, vcs) @@ -1052,7 +1048,7 @@ def _create_test_package( _create_main_file(version_pkg_path, name=name, output="0.1") version_pkg_path.joinpath("setup.py").write_text( textwrap.dedent( - """ + f""" from setuptools import setup, find_packages setup( name="{name}", @@ -1061,9 +1057,7 @@ def _create_test_package( py_modules=["{name}"], entry_points=dict(console_scripts=["{name}={name}:main"]), ) - """.format( - name=name - ) + """ ) ) return _vcs_add(dir_path, version_pkg_path, vcs) @@ -1137,7 +1131,7 @@ def urlsafe_b64encode_nopad(data: bytes) -> str: def create_really_basic_wheel(name: str, version: str) -> bytes: def digest(contents: bytes) -> str: - return "sha256={}".format(urlsafe_b64encode_nopad(sha256(contents).digest())) + return f"sha256={urlsafe_b64encode_nopad(sha256(contents).digest())}" def add_file(path: str, text: str) -> None: contents = text.encode("utf-8") @@ -1153,13 +1147,11 @@ def add_file(path: str, text: str) -> None: add_file( f"{dist_info}/METADATA", dedent( - """\ + f"""\ Metadata-Version: 2.1 - Name: {} - Version: {} - """.format( - name, version - ) + Name: {name} + Version: {version} + """ ), ) z.writestr(record_path, "\n".join(",".join(r) for r in records)) diff --git a/tests/lib/configuration_helpers.py b/tests/lib/configuration_helpers.py index ec824ffd3b8..b6e398c5bf1 100644 --- a/tests/lib/configuration_helpers.py +++ b/tests/lib/configuration_helpers.py @@ -38,7 +38,7 @@ def overridden() -> None: old() # https://github.com/python/mypy/issues/2427 - self.configuration._load_config_files = overridden # type: ignore[assignment] + self.configuration._load_config_files = overridden # type: ignore[method-assign] @contextlib.contextmanager def tmpfile(self, contents: str) -> Iterator[str]: diff --git a/tests/lib/local_repos.py b/tests/lib/local_repos.py index a04d1d0fe58..a8cf4aa6c74 100644 --- a/tests/lib/local_repos.py +++ b/tests/lib/local_repos.py @@ -56,7 +56,7 @@ def local_checkout( assert vcs_backend is not None vcs_backend.obtain(repo_url_path, url=hide_url(remote_repo), verbosity=0) - return "{}+{}".format(vcs_name, Path(repo_url_path).as_uri()) + return f"{vcs_name}+{Path(repo_url_path).as_uri()}" def local_repo(remote_repo: str, temp_path: Path) -> str: diff --git a/tests/lib/server.py b/tests/lib/server.py index 1048a173d40..96ac5930dc9 100644 --- a/tests/lib/server.py +++ b/tests/lib/server.py @@ -152,7 +152,7 @@ def html5_page(text: str) -> str: def package_page(spec: Dict[str, str]) -> "WSGIApplication": def link(name: str, value: str) -> str: - return '{}'.format(value, name) + return f'{name}' links = "".join(link(*kv) for kv in spec.items()) return text_html_response(html5_page(links)) diff --git a/tests/lib/test_lib.py b/tests/lib/test_lib.py index a541a0a204d..c01c1beb883 100644 --- a/tests/lib/test_lib.py +++ b/tests/lib/test_lib.py @@ -107,8 +107,8 @@ def run_with_log_command( """ command = ( "import logging; logging.basicConfig(level='INFO'); " - "logging.getLogger().info('sub: {}', 'foo')" - ).format(sub_string) + f"logging.getLogger().info('sub: {sub_string}', 'foo')" + ) args = [sys.executable, "-c", command] script.run(*args, **kwargs) diff --git a/tests/lib/test_wheel.py b/tests/lib/test_wheel.py index 86994c28e57..ffe96cc4335 100644 --- a/tests/lib/test_wheel.py +++ b/tests/lib/test_wheel.py @@ -19,12 +19,12 @@ def test_message_from_dict_one_value() -> None: message = message_from_dict({"a": "1"}) - assert set(message.get_all("a")) == {"1"} + assert set(message.get_all("a")) == {"1"} # type: ignore def test_message_from_dict_multiple_values() -> None: message = message_from_dict({"a": ["1", "2"]}) - assert set(message.get_all("a")) == {"1", "2"} + assert set(message.get_all("a")) == {"1", "2"} # type: ignore def message_from_bytes(contents: bytes) -> Message: @@ -67,7 +67,7 @@ def test_make_metadata_file_custom_value_list() -> None: f = default_make_metadata(updates={"a": ["1", "2"]}) assert f is not None message = default_metadata_checks(f) - assert set(message.get_all("a")) == {"1", "2"} + assert set(message.get_all("a")) == {"1", "2"} # type: ignore def test_make_metadata_file_custom_value_overrides() -> None: @@ -101,7 +101,7 @@ def default_wheel_metadata_checks(f: File) -> Message: assert message.get_all("Wheel-Version") == ["1.0"] assert message.get_all("Generator") == ["pip-test-suite"] assert message.get_all("Root-Is-Purelib") == ["true"] - assert set(message.get_all("Tag")) == {"py2-none-any", "py3-none-any"} + assert set(message.get_all("Tag")) == {"py2-none-any", "py3-none-any"} # type: ignore return message @@ -122,7 +122,7 @@ def test_make_wheel_metadata_file_custom_value_list() -> None: f = default_make_wheel_metadata(updates={"a": ["1", "2"]}) assert f is not None message = default_wheel_metadata_checks(f) - assert set(message.get_all("a")) == {"1", "2"} + assert set(message.get_all("a")) == {"1", "2"} # type: ignore def test_make_wheel_metadata_file_custom_value_override() -> None: diff --git a/tests/lib/wheel.py b/tests/lib/wheel.py index f2ddfd3b7e1..a4efe53b404 100644 --- a/tests/lib/wheel.py +++ b/tests/lib/wheel.py @@ -190,7 +190,7 @@ def urlsafe_b64encode_nopad(data: bytes) -> str: def digest(contents: bytes) -> str: - return "sha256={}".format(urlsafe_b64encode_nopad(sha256(contents).digest())) + return f"sha256={urlsafe_b64encode_nopad(sha256(contents).digest())}" def record_file_maker_wrapper( diff --git a/tests/unit/metadata/test_metadata.py b/tests/unit/metadata/test_metadata.py index 47093fb54d1..ccc8ceb2e75 100644 --- a/tests/unit/metadata/test_metadata.py +++ b/tests/unit/metadata/test_metadata.py @@ -23,7 +23,7 @@ def test_dist_get_direct_url_no_metadata(mock_read_text: mock.Mock) -> None: class FakeDistribution(BaseDistribution): pass - dist = FakeDistribution() + dist = FakeDistribution() # type: ignore assert dist.direct_url is None mock_read_text.assert_called_once_with(DIRECT_URL_METADATA_NAME) @@ -35,7 +35,7 @@ def test_dist_get_direct_url_invalid_json( class FakeDistribution(BaseDistribution): canonical_name = cast(NormalizedName, "whatever") # Needed for error logging. - dist = FakeDistribution() + dist = FakeDistribution() # type: ignore with caplog.at_level(logging.WARNING): assert dist.direct_url is None @@ -84,7 +84,7 @@ def test_dist_get_direct_url_valid_metadata(mock_read_text: mock.Mock) -> None: class FakeDistribution(BaseDistribution): pass - dist = FakeDistribution() + dist = FakeDistribution() # type: ignore direct_url = dist.direct_url assert direct_url is not None mock_read_text.assert_called_once_with(DIRECT_URL_METADATA_NAME) diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py index daec5fc6c65..44dae384a75 100644 --- a/tests/unit/test_base_command.py +++ b/tests/unit/test_base_command.py @@ -151,7 +151,7 @@ def assert_helpers_set(options: Values, args: List[str]) -> int: c = Command("fake", "fake") # https://github.com/python/mypy/issues/2427 - c.run = Mock(side_effect=assert_helpers_set) # type: ignore[assignment] + c.run = Mock(side_effect=assert_helpers_set) # type: ignore[method-assign] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() @@ -176,7 +176,7 @@ def create_temp_dirs(options: Values, args: List[str]) -> int: c = Command("fake", "fake") # https://github.com/python/mypy/issues/2427 - c.run = Mock(side_effect=create_temp_dirs) # type: ignore[assignment] + c.run = Mock(side_effect=create_temp_dirs) # type: ignore[method-assign] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() assert os.path.exists(Holder.value) == exists @@ -200,6 +200,6 @@ def create_temp_dirs(options: Values, args: List[str]) -> int: c = Command("fake", "fake") # https://github.com/python/mypy/issues/2427 - c.run = Mock(side_effect=create_temp_dirs) # type: ignore[assignment] + c.run = Mock(side_effect=create_temp_dirs) # type: ignore[method-assign] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py index 3c8b81de44d..dae083efee1 100644 --- a/tests/unit/test_collector.py +++ b/tests/unit/test_collector.py @@ -119,8 +119,8 @@ def test_get_index_content_invalid_content_type_archive( assert ( "pip._internal.index.collector", logging.WARNING, - "Skipping page {} because it looks like an archive, and cannot " - "be checked by a HTTP HEAD request.".format(url), + f"Skipping page {url} because it looks like an archive, and cannot " + "be checked by a HTTP HEAD request.", ) in caplog.record_tuples @@ -417,8 +417,8 @@ def _test_parse_links_data_attribute( html = ( "" '' - "{}" - ).format(anchor_html) + f"{anchor_html}" + ) html_bytes = html.encode("utf-8") page = IndexContent( html_bytes, @@ -764,8 +764,8 @@ def test_get_index_content_invalid_scheme( ( "pip._internal.index.collector", logging.WARNING, - "Cannot look at {} URL {} because it does not support " - "lookup as web pages.".format(vcs_scheme, url), + f"Cannot look at {vcs_scheme} URL {url} because it does not support " + "lookup as web pages.", ), ] diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index c6b44d45aad..1a0acb7b411 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -215,7 +215,7 @@ def test_site_modification(self) -> None: # Mock out the method mymock = MagicMock(spec=self.configuration._mark_as_modified) # https://github.com/python/mypy/issues/2427 - self.configuration._mark_as_modified = mymock # type: ignore[assignment] + self.configuration._mark_as_modified = mymock # type: ignore[method-assign] self.configuration.set_value("test.hello", "10") @@ -231,7 +231,7 @@ def test_user_modification(self) -> None: # Mock out the method mymock = MagicMock(spec=self.configuration._mark_as_modified) # https://github.com/python/mypy/issues/2427 - self.configuration._mark_as_modified = mymock # type: ignore[assignment] + self.configuration._mark_as_modified = mymock # type: ignore[method-assign] self.configuration.set_value("test.hello", "10") @@ -250,7 +250,7 @@ def test_global_modification(self) -> None: # Mock out the method mymock = MagicMock(spec=self.configuration._mark_as_modified) # https://github.com/python/mypy/issues/2427 - self.configuration._mark_as_modified = mymock # type: ignore[assignment] + self.configuration._mark_as_modified = mymock # type: ignore[method-assign] self.configuration.set_value("test.hello", "10") diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py index 311be588858..a379d877b2c 100644 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -143,10 +143,7 @@ def test_is_yanked(self, yanked_reason: Optional[str], expected: bool) -> None: def test_is_hash_allowed( self, hash_name: str, hex_digest: str, expected: bool ) -> None: - url = "https://example.com/wheel.whl#{hash_name}={hex_digest}".format( - hash_name=hash_name, - hex_digest=hex_digest, - ) + url = f"https://example.com/wheel.whl#{hash_name}={hex_digest}" link = Link(url) hashes_data = { "sha512": [128 * "a", 128 * "b"], diff --git a/tests/unit/test_network_auth.py b/tests/unit/test_network_auth.py index 5bd85f8cd95..5c12d870156 100644 --- a/tests/unit/test_network_auth.py +++ b/tests/unit/test_network_auth.py @@ -406,7 +406,7 @@ def __call__( stdin: Optional[Any] = None, stdout: Optional[Any] = None, input: Optional[bytes] = None, - check: Optional[bool] = None + check: Optional[bool] = None, ) -> Any: if cmd[1] == "get": assert stdin == -3 # subprocess.DEVNULL diff --git a/tests/unit/test_network_cache.py b/tests/unit/test_network_cache.py index aa849f3b03a..6a816b30090 100644 --- a/tests/unit/test_network_cache.py +++ b/tests/unit/test_network_cache.py @@ -27,6 +27,11 @@ def test_cache_roundtrip(self, cache_tmpdir: Path) -> None: cache = SafeFileCache(os.fspath(cache_tmpdir)) assert cache.get("test key") is None cache.set("test key", b"a test string") + # Body hasn't been stored yet, so the entry isn't valid yet + assert cache.get("test key") is None + + # With a body, the cache entry is valid: + cache.set_body("test key", b"body") assert cache.get("test key") == b"a test string" cache.delete("test key") assert cache.get("test key") is None @@ -35,6 +40,12 @@ def test_cache_roundtrip_body(self, cache_tmpdir: Path) -> None: cache = SafeFileCache(os.fspath(cache_tmpdir)) assert cache.get_body("test key") is None cache.set_body("test key", b"a test string") + # Metadata isn't available, so the entry isn't valid yet (this + # shouldn't happen, but just in case) + assert cache.get_body("test key") is None + + # With metadata, the cache entry is valid: + cache.set("test key", b"metadata") body = cache.get_body("test key") assert body is not None with body: diff --git a/tests/unit/test_network_utils.py b/tests/unit/test_network_utils.py index cdc10b2ba6e..380d5741ff6 100644 --- a/tests/unit/test_network_utils.py +++ b/tests/unit/test_network_utils.py @@ -21,8 +21,8 @@ def test_raise_for_status_raises_exception(status_code: int, error_type: str) -> with pytest.raises(NetworkConnectionError) as excinfo: raise_for_status(resp) assert str(excinfo.value) == ( - "{} {}: Network Error for url:" - " http://www.example.com/whatever.tgz".format(status_code, error_type) + f"{status_code} {error_type}: Network Error for url:" + " http://www.example.com/whatever.tgz" ) diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index 863f070af3c..5e3c640a55e 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -235,8 +235,8 @@ def test_unsupported_hashes(self, data: TestData) -> None: r"file \(line 1\)\)\n" r"Can't verify hashes for these file:// requirements because " r"they point to directories:\n" - r" file://.*{sep}data{sep}packages{sep}FSPkg " - r"\(from -r file \(line 2\)\)".format(sep=sep) + rf" file://.*{sep}data{sep}packages{sep}FSPkg " + r"\(from -r file \(line 2\)\)" ), ): resolver.resolve(reqset.all_requirements, True) diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py index 7a196eb8dd6..94ccfb98d86 100644 --- a/tests/unit/test_req_file.py +++ b/tests/unit/test_req_file.py @@ -297,7 +297,7 @@ def test_yield_pep440_line_requirement(self, line_processor: LineProcessor) -> N def test_yield_line_constraint(self, line_processor: LineProcessor) -> None: line = "SomeProject" filename = "filename" - comes_from = "-c {} (line {})".format(filename, 1) + comes_from = f"-c {filename} (line {1})" req = install_req_from_line(line, comes_from=comes_from, constraint=True) found_req = line_processor(line, filename, 1, constraint=True)[0] assert repr(found_req) == repr(req) @@ -326,7 +326,7 @@ def test_yield_editable_constraint(self, line_processor: LineProcessor) -> None: url = "git+https://url#egg=SomeProject" line = f"-e {url}" filename = "filename" - comes_from = "-c {} (line {})".format(filename, 1) + comes_from = f"-c {filename} (line {1})" req = install_req_from_editable(url, comes_from=comes_from, constraint=True) found_req = line_processor(line, filename, 1, constraint=True)[0] assert repr(found_req) == repr(req) @@ -873,12 +873,10 @@ def test_install_requirements_with_options( ) -> None: global_option = "--dry-run" - content = """ + content = f""" --only-binary :all: INITools==2.0 --global-option="{global_option}" - """.format( - global_option=global_option - ) + """ with requirements_file(content, tmpdir) as reqs_file: req = next( diff --git a/tests/unit/test_resolution_legacy_resolver.py b/tests/unit/test_resolution_legacy_resolver.py index 8b9d1a58a33..b2f93b3d4f5 100644 --- a/tests/unit/test_resolution_legacy_resolver.py +++ b/tests/unit/test_resolution_legacy_resolver.py @@ -252,7 +252,7 @@ class NotWorkingFakeDist(FakeDist): def metadata(self) -> email.message.Message: raise FileNotFoundError(metadata_name) - dist = make_fake_dist(klass=NotWorkingFakeDist) + dist = make_fake_dist(klass=NotWorkingFakeDist) # type: ignore with pytest.raises(NoneMetadataError) as exc: _check_dist_requires_python( @@ -261,8 +261,8 @@ def metadata(self) -> email.message.Message: ignore_requires_python=False, ) assert str(exc.value) == ( - "None {} metadata found for distribution: " - "".format(metadata_name) + f"None {metadata_name} metadata found for distribution: " + "" ) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d3b0d32d12f..1352b766481 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -14,6 +14,7 @@ from unittest.mock import Mock, patch import pytest +from pip._vendor.packaging.requirements import Requirement from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError from pip._internal.utils.deprecation import PipDeprecationWarning, deprecated @@ -37,6 +38,7 @@ normalize_path, normalize_version_info, parse_netloc, + redact_auth_from_requirement, redact_auth_from_url, redact_netloc, remove_auth_from_url, @@ -765,6 +767,30 @@ def test_redact_auth_from_url(auth_url: str, expected_url: str) -> None: assert url == expected_url +@pytest.mark.parametrize( + "req, expected", + [ + ("pkga", "pkga"), + ( + "resolvelib@ " + " git+https://test-user:test-pass@github.com/sarugaku/resolvelib@1.0.1", + "resolvelib@" + " git+https://test-user:****@github.com/sarugaku/resolvelib@1.0.1", + ), + ( + "resolvelib@" + " git+https://test-user:test-pass@github.com/sarugaku/resolvelib@1.0.1" + " ; python_version>='3.6'", + "resolvelib@" + " git+https://test-user:****@github.com/sarugaku/resolvelib@1.0.1" + ' ; python_version >= "3.6"', + ), + ], +) +def test_redact_auth_from_requirement(req: str, expected: str) -> None: + assert redact_auth_from_requirement(Requirement(req)) == expected + + class TestHiddenText: def test_basic(self) -> None: """ diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py index 4a3750f2d36..5291f129cf7 100644 --- a/tests/unit/test_vcs.py +++ b/tests/unit/test_vcs.py @@ -66,7 +66,7 @@ def test_rev_options_repr() -> None: # First check VCS-specific RevOptions behavior. (Bazaar, [], ["-r", "123"], {}), (Git, ["HEAD"], ["123"], {}), - (Mercurial, [], ["-r=123"], {}), + (Mercurial, [], ["--rev=123"], {}), (Subversion, [], ["-r", "123"], {}), # Test extra_args. For this, test using a single VersionControl class. ( diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 6d6d1a3dc87..33329fbce05 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -102,15 +102,13 @@ def test_get_legacy_build_wheel_path__multiple_names( ], ) def test_get_entrypoints(tmp_path: pathlib.Path, console_scripts: str) -> None: - entry_points_text = """ + entry_points_text = f""" [console_scripts] - {} + {console_scripts} [section] common:one = module:func common:two = module:other_func - """.format( - console_scripts - ) + """ distribution = make_wheel( "simple", diff --git a/news/12252.trivial.rst b/tools/__init__.py similarity index 100% rename from news/12252.trivial.rst rename to tools/__init__.py diff --git a/tools/ci/New-RAMDisk.ps1 b/tools/ci/New-RAMDisk.ps1 deleted file mode 100644 index 21b1a573a49..00000000000 --- a/tools/ci/New-RAMDisk.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -[CmdletBinding()] -param( - [Parameter(Mandatory=$true, - HelpMessage="Drive letter to use for the RAMDisk")] - [String]$drive, - [Parameter(HelpMessage="Size to allocate to the RAMDisk")] - [UInt64]$size=1GB -) - -$ErrorActionPreference = "Stop" -Set-StrictMode -Version Latest - -Write-Output "Installing FS-iSCSITarget-Server" -Install-WindowsFeature -Name FS-iSCSITarget-Server - -Write-Output "Starting MSiSCSI" -Start-Service MSiSCSI -$retry = 10 -do { - $service = Get-Service MSiSCSI - if ($service.Status -eq "Running") { - break; - } - $retry-- - Start-Sleep -Milliseconds 500 -} until ($retry -eq 0) - -$service = Get-Service MSiSCSI -if ($service.Status -ne "Running") { - throw "MSiSCSI is not running" -} - -Write-Output "Configuring Firewall" -Get-NetFirewallServiceFilter -Service MSiSCSI | Enable-NetFirewallRule - -Write-Output "Configuring RAMDisk" -# Must use external-facing IP address, otherwise New-IscsiTargetPortal is -# unable to connect. -$ip = ( - Get-NetIPAddress -AddressFamily IPv4 | - Where-Object {$_.IPAddress -ne "127.0.0.1"} -)[0].IPAddress -if ( - -not (Get-IscsiServerTarget -ComputerName localhost | Where-Object {$_.TargetName -eq "ramdisks"}) -) { - New-IscsiServerTarget ` - -ComputerName localhost ` - -TargetName ramdisks ` - -InitiatorId IPAddress:$ip -} - -$newVirtualDisk = New-IscsiVirtualDisk ` - -ComputerName localhost ` - -Path ramdisk:local$drive.vhdx ` - -Size $size -Add-IscsiVirtualDiskTargetMapping ` - -ComputerName localhost ` - -TargetName ramdisks ` - -Path ramdisk:local$drive.vhdx - -Write-Output "Connecting to iSCSI" -New-IscsiTargetPortal -TargetPortalAddress $ip -Get-IscsiTarget | Where-Object {!$_.IsConnected} | Connect-IscsiTarget - -Write-Output "Configuring disk" -$newDisk = Get-IscsiConnection | - Get-Disk | - Where-Object {$_.SerialNumber -eq $newVirtualDisk.SerialNumber} - -Set-Disk -InputObject $newDisk -IsOffline $false -Initialize-Disk -InputObject $newDisk -PartitionStyle MBR -New-Partition -InputObject $newDisk -UseMaximumSize -DriveLetter $drive - -Format-Volume -DriveLetter $drive -NewFileSystemLabel Temp -FileSystem NTFS diff --git a/tools/release/check_version.py b/tools/release/check_version.py index e89d1b5bad9..de3658faacd 100644 --- a/tools/release/check_version.py +++ b/tools/release/check_version.py @@ -27,7 +27,7 @@ def is_this_a_good_version_number(string: str) -> Optional[str]: expected_major = datetime.now().year % 100 if len(release) not in [2, 3]: - return "Not of the form: {0}.N or {0}.N.P".format(expected_major) + return f"Not of the form: {expected_major}.N or {expected_major}.N.P" return None diff --git a/tools/update-rtd-redirects.py b/tools/update-rtd-redirects.py index 8515c026cb7..abb6473e87f 100644 --- a/tools/update-rtd-redirects.py +++ b/tools/update-rtd-redirects.py @@ -6,6 +6,7 @@ import os import sys from pathlib import Path +from typing import Dict, List import httpx import rich @@ -84,8 +85,8 @@ def get_rtd_api() -> httpx.Client: next_step("Compare and determine modifications.") -redirects_to_remove: list[int] = [] -redirects_to_add: dict[str, str] = {} +redirects_to_remove: List[int] = [] +redirects_to_add: Dict[str, str] = {} for redirect in rtd_redirects["results"]: if redirect["type"] != "exact":