diff options
| author | Bernát Gábor <gaborjbernat@gmail.com> | 2022-07-25 00:33:42 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-25 00:33:42 -0700 |
| commit | b85542c31ca8afcff317e618da434f59fa06d122 (patch) | |
| tree | 450066fe494d366c03da70b0de10899d5738d425 | |
| parent | 3c57468470c292b235a67b27cec9468cc913f268 (diff) | |
| download | virtualenv-b85542c31ca8afcff317e618da434f59fa06d122.tar.gz | |
Drop support of running under Python 2.7 (#2382)
148 files changed, 1271 insertions, 2077 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6c29282..22b6353 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,11 +1,11 @@ -# Contributing to ``virtualenv`` +# Contributing to `virtualenv` -Thank you for your interest in contributing to virtualenv! There are many ways to contribute, and we appreciate all of them. -As a reminder, all contributors are expected to follow the [PSF Code of Conduct][coc]. +Thank you for your interest in contributing to virtualenv! There are many ways to contribute, and we appreciate all of +them. As a reminder, all contributors are expected to follow the [PSF Code of Conduct][coc]. [coc]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md ## Development Documentation Our [development documentation](https://virtualenv.pypa.io/en/latest/development.html#development) contains details on -how to get started with contributing to ``virtualenv``, and details of our development processes. +how to get started with contributing to `virtualenv`, and details of our development processes. diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 8b576e1..674d248 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -5,8 +5,9 @@ Describe what's the expected behaviour and what you're observing. **Environment** Provide at least: + - OS: -- ``pip list`` of the host python where ``virtualenv`` is installed: +- `pip list` of the host python where `virtualenv` is installed: ```console diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3a8b35e..10d03bf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "" labels: bug -assignees: '' - +assignees: "" --- **Issue** @@ -14,8 +13,9 @@ Describe what's the expected behaviour and what you're observing. **Environment** Provide at least: + - OS: -- ``pip list`` of the host python where ``virtualenv`` is installed: +- `pip list` of the host python where `virtualenv` is installed: ```console diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8143f9d..1cc9cf6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,13 +1,13 @@ # Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser -blank_issues_enabled: true # default +blank_issues_enabled: true # default contact_links: -- name: '💬 pypa/virtualenv @ Discord' - url: https://discord.gg/pypa - about: Chat with the devs -- name: 🤷💻🤦 Discourse - url: https://discuss.python.org/c/packaging - about: | - Please ask typical Q&A here: general ideas for Python packaging, questions about structuring projects and so on -- name: 📝 PSF Code of Conduct - url: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md - about: ❤ Be nice to other members of the community. ☮ Behave. + - name: "💬 pypa/virtualenv @ Discord" + url: https://discord.gg/pypa + about: Chat with the devs + - name: 🤷💻🤦 Discourse + url: https://discuss.python.org/c/packaging + about: | + Please ask typical Q&A here: general ideas for Python packaging, questions about structuring projects and so on + - name: 📝 PSF Code of Conduct + url: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + about: ❤ Be nice to other members of the community. ☮ Behave. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index ef66da0..c5c3864 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,22 +1,25 @@ --- name: Feature request about: Suggest an enhancement for this project -title: '' +title: "" labels: enhancement -assignees: '' - +assignees: "" --- **What's the problem this feature will solve?** + <!-- What are you trying to do, that you are unable to achieve with virtualenv as it currently stands? --> **Describe the solution you'd like** + <!-- Clear and concise description of what you want to happen. --> <!-- Provide examples of real world use cases that this would enable and how it solves the problem described above. --> **Alternative Solutions** + <!-- Have you tried to workaround the problem using virtualenv or other tools? Or a different approach to solving this issue? Please elaborate here. --> **Additional context** + <!-- Add any other context, links, etc. about the feature here. --> diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d4a1cd8..07f3a76 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,9 @@ ### Thanks for contributing, make sure you address all the checklists (for details on how see + [development documentation](https://virtualenv.pypa.io/en/latest/development.html#development))! -- [ ] ran the linter to address style issues (``tox -e fix_lint``) +- [ ] ran the linter to address style issues (`tox -e fix_lint`) - [ ] wrote descriptive pull request text - [ ] ensured there are test(s) validating the fix -- [ ] added news fragment in ``docs/changelog`` folder +- [ ] added news fragment in `docs/changelog` folder - [ ] updated/extended the documentation diff --git a/.github/SECURITY.md b/.github/SECURITY.md index fb6dfe0..9083642 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,10 +2,10 @@ ## Supported Versions -| Version | Supported | -| ----------- | ------------------ | -| 20.15.1 + | :white_check_mark: | -| < 20.15.1 | :x: | +| Version | Supported | +| --------- | ------------------ | +| 20.15.1 + | :white_check_mark: | +| < 20.15.1 | :x: | ## Reporting a Vulnerability diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 097d904..0da3678 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -10,14 +10,6 @@ concurrency: cancel-in-progress: true jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - uses: pre-commit/action@v3.0.0 test: name: test ${{ matrix.py }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -30,18 +22,19 @@ jobs: - "3.8" - "3.7" - "3.6" - - "3.5" + - pypy-3.9-v7.3.9 + - pypy-3.8-v7.3.9 + - pypy-3.7-v7.3.9 - pypy-3.6-v7.3.3 - - pypy-3.7-v7.3.7 - - pypy-3.8-v7.3.7 - - "2.7" - - pypy-2.7 os: - ubuntu-20.04 - - macos-10.15 + - macos-12 - windows-2022 + exclude: + - { os: macos-12, py: "pypy-3.6-v7.3.3" } # PyPy 3.6 does not support macOS 11/12 include: - - { os: macos-10.15, py: brew@py3 } + - { os: macos-12, py: "brew@3.9" } + - { os: macos-12, py: "brew@3.8" } steps: - name: Install OS dependencies run: | @@ -54,7 +47,7 @@ jobs: ${{ runner.os == 'Linux' && 'tar -zxf nushell.tar.gz' || true }} && \ ${{ runner.os == 'Linux' && 'sudo cp nu /usr/bin' || true }} && \ ${{ runner.os == 'Windows' && 'choco install nushell' || true }} && \ - ${{ runner.os == 'macOS' && 'brew update && brew install fish tcsh nushell' || true }} && \ + ${{ runner.os == 'macOS' && 'brew install fish tcsh nushell' || true }} && \ exit 0 || true; done exit 1 @@ -63,40 +56,38 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.10" - - name: Install tox - run: python -m pip install tox - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Use local virtualenv for tox - run: python -m pip install . - name: Install Python 2 for cross test uses: actions/setup-python@v4 with: python-version: "2.7" + - name: Setup brew python for test ${{ matrix.py }} + if: startsWith(matrix.py,'brew@') + run: | + set -e + PY=$(echo '${{ matrix.py }}' | cut -c 6-) + brew upgrade python@$PY || brew install python@$PY + echo "/usr/local/opt/python@$PY/libexec/bin" >>"${GITHUB_PATH}" + shell: bash - name: Setup python for test ${{ matrix.py }} - if: "!( startsWith(matrix.py,'brew@py') || endsWith(matrix.py, '-dev') )" + if: "!( startsWith(matrix.py,'brew@') || endsWith(matrix.py, '-dev') )" uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} - - name: Setup brew python for test ${{ matrix.py }} - if: startsWith(matrix.py,'brew@py') - run: | - import subprocess; import codecs; import os - subprocess.check_call(["bash", "-c", "brew upgrade python@3 || brew install python@3"]) - with codecs.open(os.environ["GITHUB_PATH"], "a", "utf-8") as file_handler: - file_handler.write("/usr/local/opt/python@3") - shell: python - name: Pick environment to run run: | - import platform; import os; import sys; import codecs - cpy = platform.python_implementation() == "CPython" - base =("{}{}{}" if cpy else "{}{}").format("py" if cpy else "pypy", *sys.version_info[0:2]) - env = "TOXENV={}\n".format(base) - print("Picked:\n{}for{}".format(env, sys.version)) - with codecs.open(os.environ["GITHUB_ENV"], "a", "utf-8") as file_handler: - file_handler.write(env) + import os; import platform; import sys; from pathlib import Path + env = f'TOXENV=py{"" if platform.python_implementation() == "CPython" else "py"}3{sys.version_info.minor}' + print(f"Picked: {env} for {sys.version} based of {sys.executable}") + with Path(os.environ["GITHUB_ENV"]).open("ta") as file_handler: + file_handler.write(env) shell: python + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install tox + run: python -m pip install tox + - name: Use local virtualenv for tox + run: python -m pip install . - name: Setup test suite run: tox -vv --notest - name: Run test suite @@ -106,7 +97,9 @@ jobs: CI_RUN: "yes" DIFF_AGAINST: HEAD - name: Rename coverage report file - run: import os; import sys; os.rename(".tox/.coverage.{}".format(os.environ['TOXENV']), ".tox/.coverage.{}-{}".format(os.environ['TOXENV'], sys.platform)) + run: | + import os; import sys + os.rename(f".tox/.coverage.{os.environ['TOXENV']}", f".tox/.coverage.{os.environ['TOXENV']}-{sys.platform}") shell: python - name: Upload coverage data uses: actions/upload-artifact@v3 @@ -116,7 +109,7 @@ jobs: coverage: name: Combine coverage - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: test steps: - uses: actions/checkout@v3 @@ -153,7 +146,7 @@ jobs: fail-fast: false matrix: os: - - ubuntu-20.04 + - ubuntu-22.04 - windows-2022 tox_env: - dev @@ -181,8 +174,8 @@ jobs: publish: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - needs: [check, coverage, lint] - runs-on: ubuntu-20.04 + needs: [check, coverage] + runs-on: ubuntu-22.04 steps: - name: Setup python to build package uses: actions/setup-python@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46e30f1..81c5b72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black args: [--safe] @@ -28,7 +28,7 @@ repos: rev: v1.12.1 hooks: - id: blacken-docs - additional_dependencies: [black==21.12b0] + additional_dependencies: [black==22.6.0] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.9.0 hooks: diff --git a/docs/changelog/2382.feature.rst b/docs/changelog/2382.feature.rst new file mode 100644 index 0000000..fa980a5 --- /dev/null +++ b/docs/changelog/2382.feature.rst @@ -0,0 +1 @@ +Drop support for running under Python 2 (still can generate Python 2 environments) - by :user:`gaborbernat`. diff --git a/docs/installation.rst b/docs/installation.rst index 19a37df..46956fe 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -97,7 +97,8 @@ supported on a best effort approach. CPython is shipped in multiple forms, and each OS repackages it, often applying some customization along the way. Therefore we cannot say universally that we support all platforms, but rather specify some we test against. In case of ones not specified here the support is unknown, though likely will work. If you find some cases please open a feature -request on our issue tracker. +request on our issue tracker. Note, as of ``20.16.0`` we no longer support running under Python less than 3.6, however +we still support creating environments for 2.7 and 3.5. Linux ~~~~~ diff --git a/docs/render_cli.py b/docs/render_cli.py index f905206..73bb8c6 100644 --- a/docs/render_cli.py +++ b/docs/render_cli.py @@ -35,7 +35,7 @@ class CliTable(SphinxDirective): for i in core_result["action_groups"]: content.append(self._build_table(i["options"], i["title"], i["description"])) for key, name_to_class in CUSTOM.items(): - section = n.section("", ids=["section-{}".format(key)]) + section = n.section("", ids=[f"section-{key}"]) title = n.title("", key) section += title self.state.document.note_implicit_target(title) @@ -44,7 +44,7 @@ class CliTable(SphinxDirective): for name, class_n in name_to_class.items(): with self._run_parser(class_n, key, name): - cmd = ["--{}".format(key), name] + cmd = [f"--{key}", name] parser_result = parse_parser(parser_creator(cmd)) opt_group = next(i["options"] for i in parser_result["action_groups"] if i["title"] == key) results[name] = opt_group @@ -52,13 +52,13 @@ class CliTable(SphinxDirective): if core_names: rows = [i for i in next(iter(results.values())) if tuple(i["name"]) in core_names] content.append( - self._build_table(rows, title="core", description="options shared across all {}".format(key)), + self._build_table(rows, title="core", description=f"options shared across all {key}"), ) for name, group in results.items(): rows = [i for i in group if tuple(i["name"]) not in core_names] if rows: content.append( - self._build_table(rows, title=name, description="options specific to {} {}".format(key, name)), + self._build_table(rows, title=name, description=f"options specific to {key} {name}"), ) return content @@ -43,10 +43,8 @@ install_requires = distlib>=0.3.1,<1 filelock>=3.2,<4 platformdirs>=2,<3 - six>=1.9.0,<2 # keep it >=1.9.0 as it may cause problems on LTS platforms importlib-metadata>=0.12;python_version<"3.8" importlib-resources>=1.0;python_version<"3.7" - pathlib2>=2.3.3,<3;python_version < '3.4' and sys.platform != 'win32' python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* package_dir = =src @@ -96,13 +94,13 @@ testing = coverage>=4 coverage-enable-subprocess>=1 flaky>=3 + packaging>=20.0 pytest>=4 pytest-env>=0.6.2 pytest-freezegun>=0.4.1 pytest-mock>=2 pytest-randomly>=1 pytest-timeout>=1 - packaging>=20.0;python_version>"3.4" [options.package_data] virtualenv.activation.bash = *.sh diff --git a/src/virtualenv/__init__.py b/src/virtualenv/__init__.py index 5f74e3e..e40e8b7 100644 --- a/src/virtualenv/__init__.py +++ b/src/virtualenv/__init__.py @@ -1,10 +1,8 @@ -from __future__ import absolute_import, unicode_literals - from .run import cli_run, session_via_cli from .version import __version__ -__all__ = ( +__all__ = [ "__version__", "cli_run", "session_via_cli", -) +] diff --git a/src/virtualenv/__main__.py b/src/virtualenv/__main__.py index 3b06fd7..c3c5adf 100644 --- a/src/virtualenv/__main__.py +++ b/src/virtualenv/__main__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, unicode_literals - import logging import os import sys @@ -18,41 +16,32 @@ def run(args=None, options=None, env=None): session = cli_run(args, options, env) logging.warning(LogSession(session, start)) except ProcessCallFailed as exception: - print("subprocess call failed for {} with code {}".format(exception.cmd, exception.code)) + print(f"subprocess call failed for {exception.cmd} with code {exception.code}") print(exception.out, file=sys.stdout, end="") print(exception.err, file=sys.stderr, end="") raise SystemExit(exception.code) -class LogSession(object): +class LogSession: def __init__(self, session, start): self.session = session self.start = start def __str__(self): - from virtualenv.util.six import ensure_text - spec = self.session.creator.interpreter.spec elapsed = (datetime.now() - self.start).total_seconds() * 1000 lines = [ - "created virtual environment {} in {:.0f}ms".format(spec, elapsed), - " creator {}".format(ensure_text(str(self.session.creator))), + f"created virtual environment {spec} in {elapsed:.0f}ms", + f" creator {str(self.session.creator)}", ] if self.session.seeder.enabled: - lines += ( - " seeder {}".format(ensure_text(str(self.session.seeder))), - " added seed packages: {}".format( - ", ".join( - sorted( - "==".join(i.stem.split("-")) - for i in self.session.creator.purelib.iterdir() - if i.suffix == ".dist-info" - ), - ), - ), - ) + lines.append(f" seeder {str(self.session.seeder)}") + path = self.session.creator.purelib.iterdir() + packages = sorted("==".join(i.stem.split("-")) for i in path if i.suffix == ".dist-info") + lines.append(f" added seed packages: {', '.join(packages)}") + if self.session.activators: - lines.append(" activators {}".format(",".join(i.__class__.__name__ for i in self.session.activators))) + lines.append(f" activators {','.join(i.__class__.__name__ for i in self.session.activators)}") return "\n".join(lines) diff --git a/src/virtualenv/activation/__init__.py b/src/virtualenv/activation/__init__.py index e9296d8..99984bc 100644 --- a/src/virtualenv/activation/__init__.py +++ b/src/virtualenv/activation/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from .bash import BashActivator from .batch import BatchActivator from .cshell import CShellActivator diff --git a/src/virtualenv/activation/activator.py b/src/virtualenv/activation/activator.py index 80d7e47..ad9d9e0 100644 --- a/src/virtualenv/activation/activator.py +++ b/src/virtualenv/activation/activator.py @@ -1,14 +1,9 @@ -from __future__ import absolute_import, unicode_literals - import os from abc import ABCMeta, abstractmethod -from six import add_metaclass - -@add_metaclass(ABCMeta) -class Activator(object): - """Generates an activate script for the virtual environment""" +class Activator(metaclass=ABCMeta): + """Generates activate script for the virtual environment""" def __init__(self, options): """Create a new activator generator. @@ -37,9 +32,14 @@ class Activator(object): @abstractmethod def generate(self, creator): - """Generate the activate script for the given creator. + """Generate activate script for the given creator. :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \ virtual environment """ raise NotImplementedError + + +__all__ = [ + "Activator", +] diff --git a/src/virtualenv/activation/bash/__init__.py b/src/virtualenv/activation/bash/__init__.py index 22c90c3..e704126 100644 --- a/src/virtualenv/activation/bash/__init__.py +++ b/src/virtualenv/activation/bash/__init__.py @@ -1,6 +1,4 @@ -from __future__ import absolute_import, unicode_literals - -from virtualenv.util.path import Path +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -11,3 +9,8 @@ class BashActivator(ViaTemplateActivator): def as_name(self, template): return template.stem + + +__all__ = [ + "BashActivator", +] diff --git a/src/virtualenv/activation/batch/__init__.py b/src/virtualenv/activation/batch/__init__.py index 4149712..6c35337 100644 --- a/src/virtualenv/activation/batch/__init__.py +++ b/src/virtualenv/activation/batch/__init__.py @@ -1,8 +1,5 @@ -from __future__ import absolute_import, unicode_literals - import os - -from virtualenv.util.path import Path +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -19,5 +16,10 @@ class BatchActivator(ViaTemplateActivator): def instantiate_template(self, replacements, template, creator): # ensure the text has all newlines as \r\n - required by batch - base = super(BatchActivator, self).instantiate_template(replacements, template, creator) + base = super().instantiate_template(replacements, template, creator) return base.replace(os.linesep, "\n").replace("\n", os.linesep) + + +__all__ = [ + "BatchActivator", +] diff --git a/src/virtualenv/activation/cshell/__init__.py b/src/virtualenv/activation/cshell/__init__.py index b25c602..b225ba3 100644 --- a/src/virtualenv/activation/cshell/__init__.py +++ b/src/virtualenv/activation/cshell/__init__.py @@ -1,6 +1,4 @@ -from __future__ import absolute_import, unicode_literals - -from virtualenv.util.path import Path +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -12,3 +10,8 @@ class CShellActivator(ViaTemplateActivator): def templates(self): yield Path("activate.csh") + + +__all__ = [ + "CShellActivator", +] diff --git a/src/virtualenv/activation/fish/__init__.py b/src/virtualenv/activation/fish/__init__.py index 8d0e19c..4b24042 100644 --- a/src/virtualenv/activation/fish/__init__.py +++ b/src/virtualenv/activation/fish/__init__.py @@ -1,6 +1,4 @@ -from __future__ import absolute_import, unicode_literals - -from virtualenv.util.path import Path +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -8,3 +6,8 @@ from ..via_template import ViaTemplateActivator class FishActivator(ViaTemplateActivator): def templates(self): yield Path("activate.fish") + + +__all__ = [ + "FishActivator", +] diff --git a/src/virtualenv/activation/nushell/__init__.py b/src/virtualenv/activation/nushell/__init__.py index 994c1fb..839c19c 100644 --- a/src/virtualenv/activation/nushell/__init__.py +++ b/src/virtualenv/activation/nushell/__init__.py @@ -1,9 +1,5 @@ -from __future__ import absolute_import, unicode_literals - import os - -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -20,9 +16,14 @@ class NushellActivator(ViaTemplateActivator): return { "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, - "__VIRTUAL_ENV__": ensure_text(str(creator.dest)), + "__VIRTUAL_ENV__": str(creator.dest), "__VIRTUAL_NAME__": creator.env_name, - "__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))), - "__PATH_SEP__": ensure_text(os.pathsep), - "__DEACTIVATE_PATH__": ensure_text(str(Path(dest_folder) / "deactivate.nu")), + "__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)), + "__PATH_SEP__": os.pathsep, + "__DEACTIVATE_PATH__": str(Path(dest_folder) / "deactivate.nu"), } + + +__all__ = [ + "NushellActivator", +] diff --git a/src/virtualenv/activation/powershell/__init__.py b/src/virtualenv/activation/powershell/__init__.py index 4fadc63..6d561bf 100644 --- a/src/virtualenv/activation/powershell/__init__.py +++ b/src/virtualenv/activation/powershell/__init__.py @@ -1,6 +1,4 @@ -from __future__ import absolute_import, unicode_literals - -from virtualenv.util.path import Path +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -8,3 +6,8 @@ from ..via_template import ViaTemplateActivator class PowerShellActivator(ViaTemplateActivator): def templates(self): yield Path("activate.ps1") + + +__all__ = [ + "PowerShellActivator", +] diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py index 9e57912..ad38201 100644 --- a/src/virtualenv/activation/python/__init__.py +++ b/src/virtualenv/activation/python/__init__.py @@ -1,11 +1,7 @@ -from __future__ import absolute_import, unicode_literals - import os import sys from collections import OrderedDict - -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text +from pathlib import Path from ..via_template import ViaTemplateActivator @@ -15,12 +11,12 @@ class PythonActivator(ViaTemplateActivator): yield Path("activate_this.py") def replacements(self, creator, dest_folder): - replacements = super(PythonActivator, self).replacements(creator, dest_folder) + replacements = super().replacements(creator, dest_folder) lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs) win_py2 = creator.interpreter.platform == "win32" and creator.interpreter.version_info.major == 2 replacements.update( { - "__LIB_FOLDERS__": ensure_text(os.pathsep.join(lib_folders.keys())), + "__LIB_FOLDERS__": os.pathsep.join(lib_folders.keys()), "__DECODE_PATH__": ("yes" if win_py2 else ""), }, ) @@ -31,5 +27,10 @@ class PythonActivator(ViaTemplateActivator): py2 = creator.interpreter.version_info.major == 2 if py2: # on Python 2 we need to encode this into explicit utf-8, py3 supports unicode literals start = 2 if sys.version_info[0] == 3 else 1 - value = ensure_text(repr(value.encode("utf-8"))[start:-1]) + value = repr(value.encode("utf-8"))[start:-1] return value + + +__all__ = [ + "PythonActivator", +] diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py index 29debe3..e8eeb84 100644 --- a/src/virtualenv/activation/python/activate_this.py +++ b/src/virtualenv/activation/python/activate_this.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Activate virtualenv for current interpreter: Use exec(open(this_file).read(), {'__file__': this_file}). diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py index 14f0979..75ecd83 100644 --- a/src/virtualenv/activation/via_template.py +++ b/src/virtualenv/activation/via_template.py @@ -1,13 +1,7 @@ -from __future__ import absolute_import, unicode_literals - import os import sys from abc import ABCMeta, abstractmethod -from six import add_metaclass - -from virtualenv.util.six import ensure_text - from .activator import Activator if sys.version_info >= (3, 7): @@ -16,8 +10,7 @@ else: from importlib_resources import read_binary -@add_metaclass(ABCMeta) -class ViaTemplateActivator(Activator): +class ViaTemplateActivator(Activator, metaclass=ABCMeta): @abstractmethod def templates(self): raise NotImplementedError @@ -33,10 +26,10 @@ class ViaTemplateActivator(Activator): def replacements(self, creator, dest_folder): return { "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, - "__VIRTUAL_ENV__": ensure_text(str(creator.dest)), + "__VIRTUAL_ENV__": str(creator.dest), "__VIRTUAL_NAME__": creator.env_name, - "__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))), - "__PATH_SEP__": ensure_text(os.pathsep), + "__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)), + "__PATH_SEP__": os.pathsep, } def _generate(self, replacements, templates, to_folder, creator): @@ -63,5 +56,9 @@ class ViaTemplateActivator(Activator): @staticmethod def _repr_unicode(creator, value): - # by default we just let it be unicode - return value + return value # by default we just let it be unicode + + +__all__ = [ + "ViaTemplateActivator", +] diff --git a/src/virtualenv/app_data/__init__.py b/src/virtualenv/app_data/__init__.py index e56e63d..262ac07 100644 --- a/src/virtualenv/app_data/__init__.py +++ b/src/virtualenv/app_data/__init__.py @@ -1,7 +1,6 @@ """ Application data stored by virtualenv. """ -from __future__ import absolute_import, unicode_literals import logging import os @@ -15,7 +14,7 @@ from .via_tempdir import TempAppData def _default_app_data_dir(env): - key = str("VIRTUALENV_OVERRIDE_APP_DATA") + key = "VIRTUALENV_OVERRIDE_APP_DATA" if key in env: return env[key] else: @@ -23,7 +22,7 @@ def _default_app_data_dir(env): def make_app_data(folder, **kwargs): - read_only = kwargs.pop("read_only") + is_read_only = kwargs.pop("read_only") env = kwargs.pop("env") if kwargs: # py3+ kwonly raise TypeError("unexpected keywords: {}") @@ -32,7 +31,7 @@ def make_app_data(folder, **kwargs): folder = _default_app_data_dir(env) folder = os.path.abspath(folder) - if read_only: + if is_read_only: return ReadOnlyAppData(folder) if not os.path.isdir(folder): diff --git a/src/virtualenv/app_data/base.py b/src/virtualenv/app_data/base.py index 4ea54d9..bc28b23 100644 --- a/src/virtualenv/app_data/base.py +++ b/src/virtualenv/app_data/base.py @@ -1,18 +1,14 @@ """ Application data stored by virtualenv. """ -from __future__ import absolute_import, unicode_literals from abc import ABCMeta, abstractmethod from contextlib import contextmanager -import six - from virtualenv.info import IS_ZIPAPP -@six.add_metaclass(ABCMeta) -class AppData(object): +class AppData(metaclass=ABCMeta): """Abstract storage interface for the virtualenv application""" @abstractmethod @@ -71,8 +67,7 @@ class AppData(object): raise NotImplementedError -@six.add_metaclass(ABCMeta) -class ContentStore(object): +class ContentStore(metaclass=ABCMeta): @abstractmethod def exists(self): raise NotImplementedError @@ -93,3 +88,9 @@ class ContentStore(object): @contextmanager def locked(self): pass + + +__all__ = [ + "ContentStore", + "AppData", +] diff --git a/src/virtualenv/app_data/na.py b/src/virtualenv/app_data/na.py index d589787..da45f58 100644 --- a/src/virtualenv/app_data/na.py +++ b/src/virtualenv/app_data/na.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from contextlib import contextmanager from .base import AppData, ContentStore @@ -44,7 +42,7 @@ class AppDataDisabled(AppData): raise self.error def py_info_clear(self): - """ """ + """nothing to clear""" class ContentStoreNA(ContentStore): @@ -52,15 +50,21 @@ class ContentStoreNA(ContentStore): return False def read(self): - """ """ + """nothing to read""" return None def write(self, content): - """ """ + """nothing to write""" def remove(self): - """ """ + """nothing to remove""" @contextmanager def locked(self): yield + + +__all__ = [ + "AppDataDisabled", + "ContentStoreNA", +] diff --git a/src/virtualenv/app_data/read_only.py b/src/virtualenv/app_data/read_only.py index 858978c..2663731 100644 --- a/src/virtualenv/app_data/read_only.py +++ b/src/virtualenv/app_data/read_only.py @@ -8,15 +8,16 @@ from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk class ReadOnlyAppData(AppDataDiskFolder): can_update = False - def __init__(self, folder): # type: (str) -> None + def __init__(self, folder: str) -> None: if not os.path.isdir(folder): - raise RuntimeError("read-only app data directory {} does not exist".format(folder)) + raise RuntimeError(f"read-only app data directory {folder} does not exist") + super().__init__(folder) self.lock = NoOpFileLock(folder) - def reset(self): # type: () -> None + def reset(self) -> None: raise RuntimeError("read-only app data does not support reset") - def py_info_clear(self): # type: () -> None + def py_info_clear(self) -> None: raise NotImplementedError def py_info(self, path): @@ -31,4 +32,6 @@ class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk): raise RuntimeError("read-only app data python info cannot be updated") -__all__ = ("ReadOnlyAppData",) +__all__ = [ + "ReadOnlyAppData", +] diff --git a/src/virtualenv/app_data/via_disk_folder.py b/src/virtualenv/app_data/via_disk_folder.py index 3f6afd5..94c4def 100644 --- a/src/virtualenv/app_data/via_disk_folder.py +++ b/src/virtualenv/app_data/via_disk_folder.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- """ A rough layout of the current storage goes as: virtualenv-app-data ├── py - <version> <cache information about python interpreters> -│ └── *.json/lock +│ └── *.json/lock ├── wheel <cache wheels used for seeding> -│ ├── house +│ ├── house │ │ └── *.whl <wheels downloaded go here> │ └── <python major.minor> -> 3.9 │ ├── img-<version> @@ -22,7 +21,6 @@ virtualenv-app-data ├── debug.py └── _virtualenv.py """ -from __future__ import absolute_import, unicode_literals import json import logging @@ -30,11 +28,8 @@ from abc import ABCMeta from contextlib import contextmanager from hashlib import sha256 -import six - from virtualenv.util.lock import ReentrantFileLock from virtualenv.util.path import safe_delete -from virtualenv.util.six import ensure_text from virtualenv.util.zipapp import extract from virtualenv.version import __version__ @@ -53,7 +48,7 @@ class AppDataDiskFolder(AppData): self.lock = ReentrantFileLock(folder) def __repr__(self): - return "{}({})".format(type(self).__name__, self.lock.path) + return f"{type(self).__name__}({self.lock.path})" def __str__(self): return str(self.lock.path) @@ -113,8 +108,7 @@ class AppDataDiskFolder(AppData): return self.lock.path / "wheel" / for_py_version / "image" / "1" / name -@six.add_metaclass(ABCMeta) -class JSONStoreDisk(ContentStore): +class JSONStoreDisk(ContentStore, metaclass=ABCMeta): def __init__(self, in_folder, key, msg, msg_args): self.in_folder = in_folder self.key = key @@ -123,7 +117,7 @@ class JSONStoreDisk(ContentStore): @property def file(self): - return self.in_folder.path / "{}.json".format(self.key) + return self.in_folder.path / f"{self.key}.json" def exists(self): return self.file.exists() @@ -132,7 +126,7 @@ class JSONStoreDisk(ContentStore): data, bad_format = None, False try: data = json.loads(self.file.read_text()) - logging.debug("got {} from %s".format(self.msg), *self.msg_args) + logging.debug(f"got {self.msg} from %s", *self.msg_args) return data except ValueError: bad_format = True @@ -147,7 +141,7 @@ class JSONStoreDisk(ContentStore): def remove(self): self.file.unlink() - logging.debug("removed {} at %s".format(self.msg), *self.msg_args) + logging.debug(f"removed {self.msg} at %s", *self.msg_args) @contextmanager def locked(self): @@ -157,21 +151,28 @@ class JSONStoreDisk(ContentStore): def write(self, content): folder = self.file.parent folder.mkdir(parents=True, exist_ok=True) - self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2))) - logging.debug("wrote {} at %s".format(self.msg), *self.msg_args) + self.file.write_text(json.dumps(content, sort_keys=True, indent=2)) + logging.debug(f"wrote {self.msg} at %s", *self.msg_args) class PyInfoStoreDisk(JSONStoreDisk): def __init__(self, in_folder, path): - key = sha256(str(path).encode("utf-8") if six.PY3 else str(path)).hexdigest() - super(PyInfoStoreDisk, self).__init__(in_folder, key, "python info of %s", (path,)) + key = sha256(str(path).encode("utf-8")).hexdigest() + super().__init__(in_folder, key, "python info of %s", (path,)) class EmbedDistributionUpdateStoreDisk(JSONStoreDisk): def __init__(self, in_folder, distribution): - super(EmbedDistributionUpdateStoreDisk, self).__init__( + super().__init__( in_folder, distribution, "embed update of distribution %s", (distribution,), ) + + +__all__ = [ + "AppDataDiskFolder", + "JSONStoreDisk", + "PyInfoStoreDisk", +] diff --git a/src/virtualenv/app_data/via_tempdir.py b/src/virtualenv/app_data/via_tempdir.py index 112a3fe..c15741a 100644 --- a/src/virtualenv/app_data/via_tempdir.py +++ b/src/virtualenv/app_data/via_tempdir.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import logging from tempfile import mkdtemp @@ -13,7 +11,7 @@ class TempAppData(AppDataDiskFolder): can_update = False def __init__(self): - super(TempAppData, self).__init__(folder=mkdtemp()) + super().__init__(folder=mkdtemp()) logging.debug("created temporary app data folder %s", self.lock.path) def reset(self): @@ -25,3 +23,8 @@ class TempAppData(AppDataDiskFolder): def embed_update_log(self, distribution, for_py_version): raise NotImplementedError + + +__all__ = [ + "TempAppData", +] diff --git a/src/virtualenv/config/__init__.py b/src/virtualenv/config/__init__.py index 01e6d4f..e69de29 100644 --- a/src/virtualenv/config/__init__.py +++ b/src/virtualenv/config/__init__.py @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals diff --git a/src/virtualenv/config/cli/__init__.py b/src/virtualenv/config/cli/__init__.py index 01e6d4f..e69de29 100644 --- a/src/virtualenv/config/cli/__init__.py +++ b/src/virtualenv/config/cli/__init__.py @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals diff --git a/src/virtualenv/config/cli/parser.py b/src/virtualenv/config/cli/parser.py index c8e2f55..5ab4fbd 100644 --- a/src/virtualenv/config/cli/parser.py +++ b/src/virtualenv/config/cli/parser.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import os from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace from collections import OrderedDict @@ -12,7 +10,7 @@ from ..ini import IniConfig class VirtualEnvOptions(Namespace): def __init__(self, **kwargs): - super(VirtualEnvOptions, self).__init__(**kwargs) + super().__init__(**kwargs) self._src = None self._sources = {} @@ -25,7 +23,7 @@ class VirtualEnvOptions(Namespace): def __setattr__(self, key, value): if getattr(self, "_src", None) is not None: self._sources[key] = self._src - super(VirtualEnvOptions, self).__setattr__(key, value) + super().__setattr__(key, value) def get_source(self, key): return self._sources.get(key) @@ -37,10 +35,7 @@ class VirtualEnvOptions(Namespace): return max(self.verbose - self.quiet, 0) def __repr__(self): - return "{}({})".format( - type(self).__name__, - ", ".join("{}={}".format(k, v) for k, v in vars(self).items() if not k.startswith("_")), - ) + return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})" class VirtualEnvConfigParser(ArgumentParser): @@ -57,7 +52,7 @@ class VirtualEnvConfigParser(ArgumentParser): kwargs["add_help"] = False kwargs["formatter_class"] = HelpFormatter kwargs["prog"] = "virtualenv" - super(VirtualEnvConfigParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._fixed = set() if options is not None and not isinstance(options, VirtualEnvOptions): raise TypeError("options must be of type VirtualEnvOptions") @@ -105,20 +100,27 @@ class VirtualEnvConfigParser(ArgumentParser): self.options._src = "cli" try: namespace.env = self.env - return super(VirtualEnvConfigParser, self).parse_known_args(args, namespace=namespace) + return super().parse_known_args(args, namespace=namespace) finally: self.options._src = None class HelpFormatter(ArgumentDefaultsHelpFormatter): def __init__(self, prog): - super(HelpFormatter, self).__init__(prog, max_help_position=32, width=240) + super().__init__(prog, max_help_position=32, width=240) def _get_help_string(self, action): - # noinspection PyProtectedMember - text = super(HelpFormatter, self)._get_help_string(action) + + text = super()._get_help_string(action) if hasattr(action, "default_source"): default = " (default: %(default)s)" if text.endswith(default): - text = "{} (default: %(default)s -> from %(default_source)s)".format(text[: -len(default)]) + text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)" return text + + +__all__ = [ + "HelpFormatter", + "VirtualEnvConfigParser", + "VirtualEnvOptions", +] diff --git a/src/virtualenv/config/convert.py b/src/virtualenv/config/convert.py index df40866..52f722e 100644 --- a/src/virtualenv/config/convert.py +++ b/src/virtualenv/config/convert.py @@ -1,16 +1,14 @@ -from __future__ import absolute_import, unicode_literals - import logging import os -class TypeData(object): +class TypeData: def __init__(self, default_type, as_type): self.default_type = default_type self.as_type = as_type def __repr__(self): - return "{}(base={}, as={})".format(self.__class__.__name__, self.default_type, self.as_type) + return f"{self.__class__.__name__}(base={self.default_type}, as={self.as_type})" def convert(self, value): return self.default_type(value) @@ -30,7 +28,7 @@ class BoolType(TypeData): def convert(self, value): if value.lower() not in self.BOOLEAN_STATES: - raise ValueError("Not a boolean: %s" % value) + raise ValueError(f"Not a boolean: {value}") return self.BOOLEAN_STATES[value.lower()] @@ -92,7 +90,7 @@ def get_type(action): return _CONVERT.get(default_type, TypeData)(default_type, as_type) -__all__ = ( +__all__ = [ "convert", "get_type", -) +] diff --git a/src/virtualenv/config/env_var.py b/src/virtualenv/config/env_var.py index 8f6211c..5dc0c1d 100644 --- a/src/virtualenv/config/env_var.py +++ b/src/virtualenv/config/env_var.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, unicode_literals - -from virtualenv.util.six import ensure_str, ensure_text - from .convert import convert @@ -13,16 +9,18 @@ def get_env_var(key, as_type, env): :param env: environment variables to use :return: """ - environ_key = ensure_str("VIRTUALENV_{}".format(key.upper())) + environ_key = f"VIRTUALENV_{key.upper()}" if env.get(environ_key): value = env[environ_key] - # noinspection PyBroadException + try: - source = "env var {}".format(ensure_text(environ_key)) + source = f"env var {environ_key}" as_type = convert(value, as_type, source) return as_type, source except Exception: # note the converter already logs a warning when failures happen pass -__all__ = ("get_env_var",) +__all__ = [ + "get_env_var", +] diff --git a/src/virtualenv/config/ini.py b/src/virtualenv/config/ini.py index 0d945ee..50da884 100644 --- a/src/virtualenv/config/ini.py +++ b/src/virtualenv/config/ini.py @@ -1,20 +1,15 @@ -from __future__ import absolute_import, unicode_literals - import logging import os +from configparser import ConfigParser +from pathlib import Path from platformdirs import user_config_dir -from virtualenv.info import PY3 -from virtualenv.util import ConfigParser -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_str - from .convert import convert -class IniConfig(object): - VIRTUALENV_CONFIG_FILE_ENV_VAR = ensure_str("VIRTUALENV_CONFIG_FILE") +class IniConfig: + VIRTUALENV_CONFIG_FILE_ENV_VAR = "VIRTUALENV_CONFIG_FILE" STATE = {None: "failed to parse", True: "active", False: "missing"} section = "virtualenv" @@ -23,11 +18,10 @@ class IniConfig(object): env = os.environ if env is None else env config_file = env.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None) self.is_env_var = config_file is not None - config_file = ( - Path(config_file) - if config_file is not None - else Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini" - ) + if config_file is None: + config_file = Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini" + else: + config_file = Path(config_file) self.config_file = config_file self._cache = {} @@ -40,7 +34,7 @@ class IniConfig(object): else: if self.has_config_file: self.config_file = self.config_file.resolve() - self.config_parser = ConfigParser.ConfigParser() + self.config_parser = ConfigParser() try: self._load() self.has_virtualenv_section = self.config_parser.has_section(self.section) @@ -51,14 +45,12 @@ class IniConfig(object): def _load(self): with self.config_file.open("rt") as file_handler: - reader = getattr(self.config_parser, "read_file" if PY3 else "readfp") - reader(file_handler) + return self.config_parser.read_file(file_handler) def get(self, key, as_type): cache_key = key, as_type if cache_key in self._cache: return self._cache[cache_key] - # noinspection PyBroadException try: source = "file" raw_value = self.config_parser.get(self.section, key.lower()) @@ -74,11 +66,7 @@ class IniConfig(object): @property def epilog(self): - msg = "{}config file {} {} (change{} via env var {})" - return msg.format( - "\n", - self.config_file, - self.STATE[self.has_config_file], - "d" if self.is_env_var else "", - self.VIRTUALENV_CONFIG_FILE_ENV_VAR, + return ( + f"\nconfig file {self.config_file} {self.STATE[self.has_config_file]} " + f"(change{'d' if self.is_env_var else ''} via env var {self.VIRTUALENV_CONFIG_FILE_ENV_VAR})" ) diff --git a/src/virtualenv/create/__init__.py b/src/virtualenv/create/__init__.py index 01e6d4f..e69de29 100644 --- a/src/virtualenv/create/__init__.py +++ b/src/virtualenv/create/__init__.py @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals diff --git a/src/virtualenv/create/creator.py b/src/virtualenv/create/creator.py index f7b8554..ad41bfe 100644 --- a/src/virtualenv/create/creator.py +++ b/src/virtualenv/create/creator.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function, unicode_literals - import json import logging import os @@ -8,14 +6,10 @@ from abc import ABCMeta, abstractmethod from argparse import ArgumentTypeError from ast import literal_eval from collections import OrderedDict -from textwrap import dedent - -from six import add_metaclass +from pathlib import Path from virtualenv.discovery.cached_py_info import LogCmd -from virtualenv.info import WIN_CPYTHON_2 -from virtualenv.util.path import Path, safe_delete -from virtualenv.util.six import ensure_str, ensure_text +from virtualenv.util.path import safe_delete from virtualenv.util.subprocess import run_cmd from virtualenv.version import __version__ @@ -25,13 +19,12 @@ HERE = Path(os.path.abspath(__file__)).parent DEBUG_SCRIPT = HERE / "debug.py" -class CreatorMeta(object): +class CreatorMeta: def __init__(self): self.error = None -@add_metaclass(ABCMeta) -class Creator(object): +class Creator(metaclass=ABCMeta): """A class that given a python Interpreter creates a virtual environment""" def __init__(self, options, interpreter): @@ -50,14 +43,11 @@ class Creator(object): self.env = options.env def __repr__(self): - return ensure_str(self.__unicode__()) - - def __unicode__(self): - return "{}({})".format(self.__class__.__name__, ", ".join("{}={}".format(k, v) for k, v in self._args())) + return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})" def _args(self): return [ - ("dest", ensure_text(str(self.dest))), + ("dest", str(self.dest)), ("clear", self.clear), ("no_vcs_ignore", self.no_vcs_ignore), ] @@ -112,16 +102,14 @@ class Creator(object): def non_write_able(dest, value): common = Path(*os.path.commonprefix([value.parts, dest.parts])) - raise ArgumentTypeError( - "the destination {} is not write-able at {}".format(dest.relative_to(common), common), - ) + raise ArgumentTypeError(f"the destination {dest.relative_to(common)} is not write-able at {common}") # the file system must be able to encode # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/ encoding = sys.getfilesystemencoding() refused = OrderedDict() kwargs = {"errors": "ignore"} if encoding != "mbcs" else {} - for char in ensure_text(raw_value): + for char in str(raw_value): try: trip = char.encode(encoding, **kwargs).decode(encoding) if trip == char: @@ -130,23 +118,17 @@ class Creator(object): except ValueError: refused[char] = None if refused: - raise ArgumentTypeError( - "the file system codec ({}) cannot handle characters {!r} within {!r}".format( - encoding, - "".join(refused.keys()), - raw_value, - ), - ) + bad = "".join(refused.keys()) + msg = f"the file system codec ({encoding}) cannot handle characters {bad!r} within {raw_value!r}" + raise ArgumentTypeError(msg) if os.pathsep in raw_value: - raise ArgumentTypeError( - "destination {!r} must not contain the path separator ({}) as this would break " - "the activation scripts".format(raw_value, os.pathsep), - ) + msg = f"destination {raw_value!r} must not contain the path separator ({os.pathsep})" + raise ArgumentTypeError(f"{msg} as this would break the activation scripts") value = Path(raw_value) if value.exists() and value.is_file(): - raise ArgumentTypeError("the destination {} already exists and is a file".format(value)) - if (3, 3) <= sys.version_info <= (3, 6): + raise ArgumentTypeError(f"the destination {value} already exists and is a file") + if sys.version_info <= (3, 6): # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation dest = Path(os.path.realpath(raw_value)) else: @@ -154,7 +136,7 @@ class Creator(object): value = dest while dest: if dest.exists(): - if os.access(ensure_text(str(dest)), os.W_OK): + if os.access(str(dest), os.W_OK): break else: non_write_able(dest, value) @@ -185,14 +167,7 @@ class Creator(object): # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs git_ignore = self.dest / ".gitignore" if not git_ignore.exists(): - git_ignore.write_text( - dedent( - """ - # created by virtualenv automatically - * - """, - ).lstrip(), - ) + git_ignore.write_text("# created by virtualenv automatically\n*\n") # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the # subinclude directive from root, at which point on might as well ignore the directory itself, see # https://www.selenic.com/mercurial/hgignore.5.html for more details @@ -208,23 +183,20 @@ class Creator(object): self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data, self.env) return self._debug - # noinspection PyMethodMayBeStatic - def debug_script(self): + @staticmethod + def debug_script(): return DEBUG_SCRIPT def get_env_debug_info(env_exe, debug_script, app_data, env): env = env.copy() - env.pop(str("PYTHONPATH"), None) + env.pop("PYTHONPATH", None) with app_data.ensure_extracted(debug_script) as debug_script: cmd = [str(env_exe), str(debug_script)] - if WIN_CPYTHON_2: - cmd = [ensure_text(i) for i in cmd] - logging.debug(str("debug via %r"), LogCmd(cmd)) + logging.debug("debug via %r", LogCmd(cmd)) code, out, err = run_cmd(cmd) - # noinspection PyBroadException try: if code != 0: if out: @@ -243,3 +215,9 @@ def get_env_debug_info(env_exe, debug_script, app_data, env): if "sys" in result and "path" in result["sys"]: del result["sys"]["path"][0] return result + + +__all__ = [ + "Creator", + "CreatorMeta", +] diff --git a/src/virtualenv/create/describe.py b/src/virtualenv/create/describe.py index 6f05ff1..fade5c4 100644 --- a/src/virtualenv/create/describe.py +++ b/src/virtualenv/create/describe.py @@ -1,17 +1,11 @@ -from __future__ import absolute_import, print_function, unicode_literals - from abc import ABCMeta from collections import OrderedDict - -from six import add_metaclass +from pathlib import Path from virtualenv.info import IS_WIN -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text -@add_metaclass(ABCMeta) -class Describe(object): +class Describe(metaclass=ABCMeta): """Given a host interpreter tell us information about what the created interpreter might look like""" suffix = ".exe" if IS_WIN else "" @@ -59,13 +53,12 @@ class Describe(object): @property def _config_vars(self): if self._conf_vars is None: - self._conf_vars = self._calc_config_vars(ensure_text(str(self.dest))) + self._conf_vars = self._calc_config_vars(self.dest) return self._conf_vars def _calc_config_vars(self, to): - return { - k: (to if v.startswith(self.interpreter.prefix) else v) for k, v in self.interpreter.sysconfig_vars.items() - } + sys_vars = self.interpreter.sysconfig_vars + return {k: (to if v.startswith(self.interpreter.prefix) else v) for k, v in sys_vars.items()} @classmethod def can_describe(cls, interpreter): @@ -74,11 +67,11 @@ class Describe(object): @property def env_name(self): - return ensure_text(self.dest.parts[-1]) + return self.dest.parts[-1] @property def exe(self): - return self.bin_dir / "{}{}".format(self.exe_stem(), self.suffix) + return self.bin_dir / f"{self.exe_stem()}{self.suffix}" @classmethod def exe_stem(cls): @@ -86,32 +79,37 @@ class Describe(object): raise NotImplementedError def script(self, name): - return self.script_dir / "{}{}".format(name, self.suffix) + return self.script_dir / f"{name}{self.suffix}" -@add_metaclass(ABCMeta) -class Python2Supports(Describe): +class Python2Supports(Describe, metaclass=ABCMeta): @classmethod def can_describe(cls, interpreter): - return interpreter.version_info.major == 2 and super(Python2Supports, cls).can_describe(interpreter) + return interpreter.version_info.major == 2 and super().can_describe(interpreter) -@add_metaclass(ABCMeta) -class Python3Supports(Describe): +class Python3Supports(Describe, metaclass=ABCMeta): @classmethod def can_describe(cls, interpreter): - return interpreter.version_info.major == 3 and super(Python3Supports, cls).can_describe(interpreter) + return interpreter.version_info.major == 3 and super().can_describe(interpreter) -@add_metaclass(ABCMeta) -class PosixSupports(Describe): +class PosixSupports(Describe, metaclass=ABCMeta): @classmethod def can_describe(cls, interpreter): - return interpreter.os == "posix" and super(PosixSupports, cls).can_describe(interpreter) + return interpreter.os == "posix" and super().can_describe(interpreter) -@add_metaclass(ABCMeta) -class WindowsSupports(Describe): +class WindowsSupports(Describe, metaclass=ABCMeta): @classmethod def can_describe(cls, interpreter): - return interpreter.os == "nt" and super(WindowsSupports, cls).can_describe(interpreter) + return interpreter.os == "nt" and super().can_describe(interpreter) + + +__all__ = [ + "Describe", + "Python2Supports", + "Python3Supports", + "PosixSupports", + "WindowsSupports", +] diff --git a/src/virtualenv/create/pyenv_cfg.py b/src/virtualenv/create/pyenv_cfg.py index 1a8d824..9193a28 100644 --- a/src/virtualenv/create/pyenv_cfg.py +++ b/src/virtualenv/create/pyenv_cfg.py @@ -1,12 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import logging from collections import OrderedDict -from virtualenv.util.six import ensure_text - -class PyEnvCfg(object): +class PyEnvCfg: def __init__(self, content, path): self.content = content self.path = path @@ -31,10 +27,10 @@ class PyEnvCfg(object): return content def write(self): - logging.debug("write %s", ensure_text(str(self.path))) + logging.debug("write %s", self.path) text = "" for key, value in self.content.items(): - line = "{} = {}".format(key, value) + line = f"{key} = {value}" logging.debug("\t%s", line) text += line text += "\n" @@ -58,4 +54,9 @@ class PyEnvCfg(object): return self def __repr__(self): - return "{}(path={})".format(self.__class__.__name__, self.path) + return f"{self.__class__.__name__}(path={self.path})" + + +__all__ = [ + "PyEnvCfg", +] diff --git a/src/virtualenv/create/via_global_ref/api.py b/src/virtualenv/create/via_global_ref/api.py index 6f296f4..7a4086f 100644 --- a/src/virtualenv/create/via_global_ref/api.py +++ b/src/virtualenv/create/via_global_ref/api.py @@ -1,21 +1,16 @@ -from __future__ import absolute_import, unicode_literals - import logging import os from abc import ABCMeta - -from six import add_metaclass +from pathlib import Path from virtualenv.info import fs_supports_symlink -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text from ..creator import Creator, CreatorMeta class ViaGlobalRefMeta(CreatorMeta): def __init__(self): - super(ViaGlobalRefMeta, self).__init__() + super().__init__() self.copy_error = None self.symlink_error = None if not fs_supports_symlink(): @@ -30,10 +25,9 @@ class ViaGlobalRefMeta(CreatorMeta): return not self.symlink_error -@add_metaclass(ABCMeta) -class ViaGlobalRefApi(Creator): +class ViaGlobalRefApi(Creator, metaclass=ABCMeta): def __init__(self, options, interpreter): - super(ViaGlobalRefApi, self).__init__(options, interpreter) + super().__init__(options, interpreter) self.symlinks = self._should_symlink(options) self.enable_system_site_package = options.system_site @@ -56,7 +50,7 @@ class ViaGlobalRefApi(Creator): @classmethod def add_parser_arguments(cls, parser, interpreter, meta, app_data): - super(ViaGlobalRefApi, cls).add_parser_arguments(parser, interpreter, meta, app_data) + super().add_parser_arguments(parser, interpreter, meta, app_data) parser.add_argument( "--system-site-packages", default=False, @@ -92,10 +86,10 @@ class ViaGlobalRefApi(Creator): text = self.env_patch_text() if text: pth = self.purelib / "_virtualenv.pth" - logging.debug("create virtualenv import hook file %s", ensure_text(str(pth))) + logging.debug("create virtualenv import hook file %s", pth) pth.write_text("import _virtualenv") dest_path = self.purelib / "_virtualenv.py" - logging.debug("create %s", ensure_text(str(dest_path))) + logging.debug("create %s", dest_path) dest_path.write_text(text) def env_patch_text(self): @@ -105,8 +99,14 @@ class ViaGlobalRefApi(Creator): return text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib)))) def _args(self): - return super(ViaGlobalRefApi, self)._args() + [("global", self.enable_system_site_package)] + return super()._args() + [("global", self.enable_system_site_package)] def set_pyenv_cfg(self): - super(ViaGlobalRefApi, self).set_pyenv_cfg() + super().set_pyenv_cfg() self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false" + + +__all__ = [ + "ViaGlobalRefMeta", + "ViaGlobalRefApi", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/builtin_way.py b/src/virtualenv/create/via_global_ref/builtin/builtin_way.py index 279ee80..e321593 100644 --- a/src/virtualenv/create/via_global_ref/builtin/builtin_way.py +++ b/src/virtualenv/create/via_global_ref/builtin/builtin_way.py @@ -1,17 +1,17 @@ -from __future__ import absolute_import, unicode_literals - from abc import ABCMeta -from six import add_metaclass - from virtualenv.create.creator import Creator from virtualenv.create.describe import Describe -@add_metaclass(ABCMeta) -class VirtualenvBuiltin(Creator, Describe): +class VirtualenvBuiltin(Creator, Describe, metaclass=ABCMeta): """A creator that does operations itself without delegation, if we can create it we can also describe it""" def __init__(self, options, interpreter): Creator.__init__(self, options, interpreter) Describe.__init__(self, self.dest, interpreter) + + +__all__ = [ + "VirtualenvBuiltin", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/__init__.py b/src/virtualenv/create/via_global_ref/builtin/cpython/__init__.py index 01e6d4f..e69de29 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/__init__.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/__init__.py @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/common.py b/src/virtualenv/create/via_global_ref/builtin/cpython/common.py index c93f9f3..b2f7944 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/common.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/common.py @@ -1,45 +1,36 @@ -from __future__ import absolute_import, unicode_literals - from abc import ABCMeta from collections import OrderedDict - -from six import add_metaclass +from pathlib import Path from virtualenv.create.describe import PosixSupports, WindowsSupports from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen -from virtualenv.util.path import Path from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin -@add_metaclass(ABCMeta) -class CPython(ViaGlobalRefVirtualenvBuiltin): +class CPython(ViaGlobalRefVirtualenvBuiltin, metaclass=ABCMeta): @classmethod def can_describe(cls, interpreter): - return interpreter.implementation == "CPython" and super(CPython, cls).can_describe(interpreter) + return interpreter.implementation == "CPython" and super().can_describe(interpreter) @classmethod def exe_stem(cls): return "python" -@add_metaclass(ABCMeta) -class CPythonPosix(CPython, PosixSupports): +class CPythonPosix(CPython, PosixSupports, metaclass=ABCMeta): """Create a CPython virtual environment on POSIX platforms""" @classmethod def _executables(cls, interpreter): host_exe = Path(interpreter.system_executable) major, minor = interpreter.version_info.major, interpreter.version_info.minor - targets = OrderedDict( - (i, None) for i in ["python", "python{}".format(major), "python{}.{}".format(major, minor), host_exe.name] - ) + targets = OrderedDict((i, None) for i in ["python", f"python{major}", f"python{major}.{minor}", host_exe.name]) must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA yield host_exe, list(targets.keys()), must, RefWhen.ANY -@add_metaclass(ABCMeta) -class CPythonWindows(CPython, WindowsSupports): +class CPythonWindows(CPython, WindowsSupports, metaclass=ABCMeta): @classmethod def _executables(cls, interpreter): # symlink of the python executables does not work reliably, copy always instead @@ -63,3 +54,11 @@ def is_mac_os_framework(interpreter): value = "Python3" if interpreter.version_info.major == 3 else "Python" return framework_var == value return False + + +__all__ = [ + "CPython", + "CPythonPosix", + "CPythonWindows", + "is_mac_os_framework", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py index dc822bc..5a9eb85 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py @@ -1,25 +1,19 @@ -from __future__ import absolute_import, unicode_literals - import abc import logging - -from six import add_metaclass +from pathlib import Path from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest -from virtualenv.util.path import Path from ..python2.python2 import Python2 from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework -@add_metaclass(abc.ABCMeta) -class CPython2(CPython, Python2): +class CPython2(CPython, Python2, metaclass=abc.ABCMeta): """Create a CPython version 2 virtual environment""" @classmethod def sources(cls, interpreter): - for src in super(CPython2, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # include folder needed on Python 2 as we don't have pyenv.cfg host_include_marker = cls.host_include_marker(interpreter) if host_include_marker.exists(): @@ -40,12 +34,10 @@ class CPython2(CPython, Python2): @classmethod def modules(cls): - return [ - "os", # landmark to set sys.prefix - ] + return ["os"] # landmark to set sys.prefix def ensure_directories(self): - dirs = super(CPython2, self).ensure_directories() + dirs = super().ensure_directories() host_include_marker = self.host_include_marker(self.interpreter) if host_include_marker.exists(): dirs.add(self.include.parent) @@ -54,14 +46,12 @@ class CPython2(CPython, Python2): return dirs -@add_metaclass(abc.ABCMeta) -class CPython2PosixBase(CPython2, CPythonPosix): +class CPython2PosixBase(CPython2, CPythonPosix, metaclass=abc.ABCMeta): """common to macOs framework builds and other posix CPython2""" @classmethod def sources(cls, interpreter): - for src in super(CPython2PosixBase, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # check if the makefile exists and if so make it available under the virtual environment make_file = Path(interpreter.sysconfig["makefile_filename"]) @@ -75,12 +65,11 @@ class CPython2Posix(CPython2PosixBase): @classmethod def can_describe(cls, interpreter): - return is_mac_os_framework(interpreter) is False and super(CPython2Posix, cls).can_describe(interpreter) + return is_mac_os_framework(interpreter) is False and super().can_describe(interpreter) @classmethod def sources(cls, interpreter): - for src in super(CPython2Posix, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # landmark for exec_prefix exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload") yield PathRefToDest(exec_marker_file, dest=to_path) @@ -91,8 +80,7 @@ class CPython2Windows(CPython2, CPythonWindows): @classmethod def sources(cls, interpreter): - for src in super(CPython2Windows, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) py27_dll = Path(interpreter.system_executable).parent / "python27.dll" if py27_dll.exists(): # this might be global in the Windows folder in which case it's alright to be missing yield PathRefToDest(py27_dll, dest=cls.to_bin) @@ -100,3 +88,11 @@ class CPython2Windows(CPython2, CPythonWindows): libs = Path(interpreter.system_prefix) / "libs" if libs.exists(): yield PathRefToDest(libs, dest=lambda self, s: self.dest / s.name) + + +__all__ = [ + "CPython2", + "CPython2PosixBase", + "CPython2Posix", + "CPython2Windows", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py index 03d3097..433dc4a 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py @@ -1,33 +1,28 @@ -from __future__ import absolute_import, unicode_literals - import abc import fnmatch from itertools import chain from operator import methodcaller as method +from pathlib import Path from textwrap import dedent -from six import add_metaclass - from virtualenv.create.describe import Python3Supports from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest from virtualenv.create.via_global_ref.store import is_store_python -from virtualenv.util.path import Path from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework -@add_metaclass(abc.ABCMeta) -class CPython3(CPython, Python3Supports): - """ """ +class CPython3(CPython, Python3Supports, metaclass=abc.ABCMeta): + """CPython 3 or later""" class CPython3Posix(CPythonPosix, CPython3): @classmethod def can_describe(cls, interpreter): - return is_mac_os_framework(interpreter) is False and super(CPython3Posix, cls).can_describe(interpreter) + return is_mac_os_framework(interpreter) is False and super().can_describe(interpreter) def env_patch_text(self): - text = super(CPython3Posix, self).env_patch_text() + text = super().env_patch_text() if self.pyvenv_launch_patch_active(self.interpreter): text += dedent( """ @@ -52,7 +47,7 @@ class CPython3Windows(CPythonWindows, CPython3): def setup_meta(cls, interpreter): if is_store_python(interpreter): # store python is not supported here return None - return super(CPython3Windows, cls).setup_meta(interpreter) + return super().setup_meta(interpreter) @classmethod def sources(cls, interpreter): @@ -64,12 +59,11 @@ class CPython3Windows(CPythonWindows, CPython3): cls.dll_and_pyd(interpreter), cls.python_zip(interpreter), ) - for ref in refs: - yield ref + yield from refs @classmethod def executables(cls, interpreter): - return super(CPython3Windows, cls).sources(interpreter) + return super().sources(interpreter) @classmethod def has_shim(cls, interpreter): @@ -88,7 +82,7 @@ class CPython3Windows(CPythonWindows, CPython3): # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies # it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013 return cls.shim(interpreter) - return super(CPython3Windows, cls).host_python(interpreter) + return super().host_python(interpreter) @classmethod def dll_and_pyd(cls, interpreter): @@ -119,10 +113,17 @@ class CPython3Windows(CPythonWindows, CPython3): move/rename *zip* file and edit `sys.path` by editing *_pth* file. Here the `pattern` is used only for the default *zip* file name! """ - pattern = "*python{}.zip".format(interpreter.version_nodot) + pattern = f"*python{interpreter.version_nodot}.zip" matches = fnmatch.filter(interpreter.path, pattern) matched_paths = map(Path, matches) existing_paths = filter(method("exists"), matched_paths) path = next(existing_paths, None) if path is not None: yield PathRefToDest(path, cls.to_bin) + + +__all__ = [ + "CPython3", + "CPython3Posix", + "CPython3Windows", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py b/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py index d64f0d9..16da661 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py @@ -1,32 +1,27 @@ -# -*- coding: utf-8 -*- """The Apple Framework builds require their own customization""" import logging import os import struct import subprocess from abc import ABCMeta, abstractmethod +from pathlib import Path from textwrap import dedent -from six import add_metaclass, text_type - from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefMust from virtualenv.info import IS_MAC_ARM64 -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text from .common import CPython, CPythonPosix, is_mac_os_framework from .cpython2 import CPython2PosixBase from .cpython3 import CPython3 -@add_metaclass(ABCMeta) -class CPythonmacOsFramework(CPython): +class CPythonmacOsFramework(CPython, metaclass=ABCMeta): @classmethod def can_describe(cls, interpreter): - return is_mac_os_framework(interpreter) and super(CPythonmacOsFramework, cls).can_describe(interpreter) + return is_mac_os_framework(interpreter) and super().can_describe(interpreter) def create(self): - super(CPythonmacOsFramework, self).create() + super().create() # change the install_name of the copied python executables target = self.desired_mach_o_image_path() @@ -42,7 +37,7 @@ class CPythonmacOsFramework(CPython): @classmethod def _executables(cls, interpreter): - for _, targets, must, when in super(CPythonmacOsFramework, cls)._executables(interpreter): + for _, targets, must, when in super()._executables(interpreter): # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the # stub executable in ${sys.prefix}/bin. # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951 @@ -61,8 +56,8 @@ class CPythonmacOsFramework(CPython): class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase): @classmethod def can_create(cls, interpreter): - if not IS_MAC_ARM64 and super(CPython2macOsFramework, cls).can_describe(interpreter): - return super(CPython2macOsFramework, cls).can_create(interpreter) + if not IS_MAC_ARM64 and super().can_describe(interpreter): + return super().can_create(interpreter) return False def current_mach_o_image_path(self): @@ -73,8 +68,7 @@ class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase): @classmethod def sources(cls, interpreter): - for src in super(CPython2macOsFramework, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # landmark for exec_prefix exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload") yield PathRefToDest(exec_marker_file, dest=to_path) @@ -89,21 +83,19 @@ class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase): @property def reload_code(self): - result = super(CPython2macOsFramework, self).reload_code + result = super().reload_code result = dedent( - """ + f""" # the bundled site.py always adds the global site package if we're on python framework build, escape this import sysconfig config = sysconfig.get_config_vars() before = config["PYTHONFRAMEWORK"] try: config["PYTHONFRAMEWORK"] = "" - {} + {result} finally: config["PYTHONFRAMEWORK"] = before - """.format( - result, - ), + """, ) return result @@ -132,11 +124,11 @@ class CPython2macOsArmFramework(CPython2macOsFramework, CPythonmacOsFramework, C # Reset the signing on Darwin since the exe has been modified. # Note codesign fails on the original exe, it needs to be copied and moved back. bak_dir.mkdir(parents=True, exist_ok=True) - subprocess.check_call(["cp", text_type(exe), text_type(bak_dir)]) - subprocess.check_call(["mv", text_type(bak_dir / exe.name), text_type(exe)]) + subprocess.check_call(["cp", str(exe), str(bak_dir)]) + subprocess.check_call(["mv", str(bak_dir / exe.name), str(exe)]) bak_dir.rmdir() metadata = "--preserve-metadata=identifier,entitlements,flags,runtime" - cmd = ["codesign", "-s", "-", metadata, "-f", text_type(exe)] + cmd = ["codesign", "-s", "-", metadata, "-f", str(exe)] logging.debug("Changing Signature: %s", cmd) subprocess.check_call(cmd) except Exception: @@ -153,8 +145,7 @@ class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix): @classmethod def sources(cls, interpreter): - for src in super(CPython3macOsFramework, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # add a symlink to the host python image exe = Path(interpreter.prefix) / "Python3" @@ -162,20 +153,18 @@ class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix): @property def reload_code(self): - result = super(CPython3macOsFramework, self).reload_code + result = super().reload_code result = dedent( - """ + f""" # the bundled site.py always adds the global site package if we're on python framework build, escape this import sys before = sys._framework try: sys._framework = None - {} + {result} finally: sys._framework = before - """.format( - result, - ), + """, ) return result @@ -205,7 +194,7 @@ def fix_mach_o(exe, current, new, max_size): unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format. """ try: - logging.debug("change Mach-O for %s from %s to %s", ensure_text(exe), current, ensure_text(new)) + logging.debug("change Mach-O for %s from %s to %s", exe, current, new) _builtin_change_mach_o(max_size)(exe, current, new) except Exception as e: logging.warning("Could not call _builtin_change_mac_o: %s. " "Trying to call install_name_tool instead.", e) @@ -227,7 +216,7 @@ def _builtin_change_mach_o(maxint): LITTLE_ENDIAN = "<" LC_LOAD_DYLIB = 0xC - class FileView(object): + class FileView: """A proxy for file-like objects that exposes a given view of a file. Modified from macholib.""" def __init__(self, file_obj, start=0, size=maxint): @@ -240,15 +229,15 @@ def _builtin_change_mach_o(maxint): self._pos = 0 def __repr__(self): - return "<fileview [{:d}, {:d}] {!r}>".format(self._start, self._end, self._file_obj) + return f"<fileview [{self._start:d}, {self._end:d}] {self._file_obj!r}>" def tell(self): return self._pos def _checkwindow(self, seek_to, op): if not (self._start <= seek_to <= self._end): - msg = "{} to offset {:d} is outside window [{:d}, {:d}]".format(op, seek_to, self._start, self._end) - raise IOError(msg) + msg = f"{op} to offset {seek_to:d} is outside window [{self._start:d}, {self._end:d}]" + raise OSError(msg) def seek(self, offset, whence=0): seek_to = offset @@ -259,7 +248,7 @@ def _builtin_change_mach_o(maxint): elif whence == os.SEEK_END: seek_to += self._end else: - raise IOError("Invalid whence argument to seek: {!r}".format(whence)) + raise OSError(f"Invalid whence argument to seek: {whence!r}") self._checkwindow(seek_to, "seek") self._file_obj.seek(seek_to) self._pos = seek_to - self._start @@ -345,3 +334,10 @@ def _builtin_change_mach_o(maxint): do_file(f) return mach_o_change + + +__all__ = [ + "CPythonmacOsFramework", + "CPython2macOsFramework", + "CPython3macOsFramework", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/pypy/common.py b/src/virtualenv/create/via_global_ref/builtin/pypy/common.py index 4504d81..97fa963 100644 --- a/src/virtualenv/create/via_global_ref/builtin/pypy/common.py +++ b/src/virtualenv/create/via_global_ref/builtin/pypy/common.py @@ -1,46 +1,39 @@ -from __future__ import absolute_import, unicode_literals - import abc - -from six import add_metaclass +from pathlib import Path from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen -from virtualenv.util.path import Path from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin -@add_metaclass(abc.ABCMeta) -class PyPy(ViaGlobalRefVirtualenvBuiltin): +class PyPy(ViaGlobalRefVirtualenvBuiltin, metaclass=abc.ABCMeta): @classmethod def can_describe(cls, interpreter): - return interpreter.implementation == "PyPy" and super(PyPy, cls).can_describe(interpreter) + return interpreter.implementation == "PyPy" and super().can_describe(interpreter) @classmethod def _executables(cls, interpreter): host = Path(interpreter.system_executable) - targets = sorted("{}{}".format(name, PyPy.suffix) for name in cls.exe_names(interpreter)) + targets = sorted(f"{name}{PyPy.suffix}" for name in cls.exe_names(interpreter)) must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA yield host, targets, must, RefWhen.ANY @classmethod def executables(cls, interpreter): - for src in super(PyPy, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) @classmethod def exe_names(cls, interpreter): return { cls.exe_stem(), "python", - "python{}".format(interpreter.version_info.major), - "python{}.{}".format(*interpreter.version_info), + f"python{interpreter.version_info.major}", + f"python{interpreter.version_info.major}.{interpreter.version_info.minor}", } @classmethod def sources(cls, interpreter): - for exe in cls.executables(interpreter): - yield exe + yield from cls.executables(interpreter) for host in cls._add_shared_libs(interpreter): yield PathRefToDest(host, dest=lambda self, s: self.bin_dir / s.name) @@ -48,9 +41,13 @@ class PyPy(ViaGlobalRefVirtualenvBuiltin): def _add_shared_libs(cls, interpreter): # https://bitbucket.org/pypy/pypy/issue/1922/future-proofing-virtualenv python_dir = Path(interpreter.system_executable).resolve().parent - for src in cls._shared_libs(python_dir): - yield src + yield from cls._shared_libs(python_dir) @classmethod def _shared_libs(cls, python_dir): raise NotImplementedError + + +__all__ = [ + "PyPy", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py b/src/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py index 78349d4..9f2343f 100644 --- a/src/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py +++ b/src/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py @@ -1,22 +1,17 @@ -from __future__ import absolute_import, unicode_literals - import abc import logging import os - -from six import add_metaclass +from pathlib import Path from virtualenv.create.describe import PosixSupports, WindowsSupports from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest -from virtualenv.util.path import Path from ..python2.python2 import Python2 from .common import PyPy -@add_metaclass(abc.ABCMeta) -class PyPy2(PyPy, Python2): - """ """ +class PyPy2(PyPy, Python2, metaclass=abc.ABCMeta): + """PyPy 2""" @classmethod def exe_stem(cls): @@ -24,8 +19,7 @@ class PyPy2(PyPy, Python2): @classmethod def sources(cls, interpreter): - for src in super(PyPy2, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # include folder needed on Python 2 as we don't have pyenv.cfg host_include_marker = cls.host_include_marker(interpreter) if host_include_marker.exists(): @@ -46,7 +40,7 @@ class PyPy2(PyPy, Python2): @classmethod def modules(cls): # pypy2 uses some modules before the site.py loads, so we need to include these too - return super(PyPy2, cls).modules() + [ + return super().modules() + [ "os", "copy_reg", "genericpath", @@ -61,7 +55,7 @@ class PyPy2(PyPy, Python2): return self.dest / "lib_pypy" def ensure_directories(self): - dirs = super(PyPy2, self).ensure_directories() + dirs = super().ensure_directories() dirs.add(self.lib_pypy) host_include_marker = self.host_include_marker(self.interpreter) if host_include_marker.exists(): @@ -76,7 +70,7 @@ class PyPy2(PyPy, Python2): PyPy2 built-in imports are handled by this path entry, don't overwrite to not disable it see: https://github.com/pypa/virtualenv/issues/1652 """ - return 'or path.endswith("lib_pypy{}__extensions__") # PyPy2 built-in import marker'.format(os.sep) + return f'or path.endswith("lib_pypy{os.sep}__extensions__") # PyPy2 built-in import marker' class PyPy2Posix(PyPy2, PosixSupports): @@ -84,7 +78,7 @@ class PyPy2Posix(PyPy2, PosixSupports): @classmethod def modules(cls): - return super(PyPy2Posix, cls).modules() + ["posixpath"] + return super().modules() + ["posixpath"] @classmethod def _shared_libs(cls, python_dir): @@ -96,8 +90,7 @@ class PyPy2Posix(PyPy2, PosixSupports): @classmethod def sources(cls, interpreter): - for src in super(PyPy2Posix, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) host_lib = Path(interpreter.system_prefix) / "lib" if host_lib.exists(): yield PathRefToDest(host_lib, dest=lambda self, _: self.lib) @@ -108,7 +101,7 @@ class Pypy2Windows(PyPy2, WindowsSupports): @classmethod def modules(cls): - return super(Pypy2Windows, cls).modules() + ["ntpath"] + return super().modules() + ["ntpath"] @classmethod def _shared_libs(cls, python_dir): @@ -120,6 +113,12 @@ class Pypy2Windows(PyPy2, WindowsSupports): @classmethod def sources(cls, interpreter): - for src in super(Pypy2Windows, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) yield PathRefToDest(Path(interpreter.system_prefix) / "libs", dest=lambda self, s: self.dest / s.name) + + +__all__ = [ + "PyPy2", + "PyPy2Posix", + "Pypy2Windows", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py b/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py index 06e15a5..ca5778c 100644 --- a/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py +++ b/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py @@ -1,25 +1,20 @@ -from __future__ import absolute_import, unicode_literals - import abc - -from six import add_metaclass +from pathlib import Path from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest -from virtualenv.util.path import Path from .common import PyPy -@add_metaclass(abc.ABCMeta) -class PyPy3(PyPy, Python3Supports): +class PyPy3(PyPy, Python3Supports, metaclass=abc.ABCMeta): @classmethod def exe_stem(cls): return "pypy3" @classmethod def exe_names(cls, interpreter): - return super(PyPy3, cls).exe_names(interpreter) | {"pypy"} + return super().exe_names(interpreter) | {"pypy"} class PyPy3Posix(PyPy3, PosixSupports): @@ -28,7 +23,7 @@ class PyPy3Posix(PyPy3, PosixSupports): @property def stdlib(self): """PyPy3 respects sysconfig only for the host python, virtual envs is instead lib/pythonx.y/site-packages""" - return self.dest / "lib" / "pypy{}".format(self.interpreter.version_release_str) / "site-packages" + return self.dest / "lib" / f"pypy{self.interpreter.version_release_str}" / "site-packages" @classmethod def _shared_libs(cls, python_dir): @@ -40,8 +35,7 @@ class PyPy3Posix(PyPy3, PosixSupports): @classmethod def sources(cls, interpreter): - for src in super(PyPy3Posix, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # PyPy >= 3.8 supports a standard prefix installation, where older # versions always used a portable/development style installation. # If this is a standard prefix installation, skip the below: @@ -87,5 +81,11 @@ class Pypy3Windows(PyPy3, WindowsSupports): # glob for libpypy*.dll and libffi*.dll for pattern in ["libpypy*.dll", "libffi*.dll"]: srcs = python_dir.glob(pattern) - for src in srcs: - yield src + yield from srcs + + +__all__ = [ + "PyPy3", + "PyPy3Posix", + "Pypy3Windows", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/python2/python2.py b/src/virtualenv/create/via_global_ref/builtin/python2/python2.py index cacd56e..9b963b3 100644 --- a/src/virtualenv/create/via_global_ref/builtin/python2/python2.py +++ b/src/virtualenv/create/via_global_ref/builtin/python2/python2.py @@ -1,16 +1,11 @@ -from __future__ import absolute_import, unicode_literals - import abc import json import os - -from six import add_metaclass +from pathlib import Path from virtualenv.create.describe import Python2Supports from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest from virtualenv.info import IS_ZIPAPP -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text from virtualenv.util.zipapp import read as read_from_zipapp from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin @@ -18,11 +13,10 @@ from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin HERE = Path(os.path.abspath(__file__)).parent -@add_metaclass(abc.ABCMeta) -class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports): +class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports, metaclass=abc.ABCMeta): def create(self): """Perform operations needed to make the created environment work on Python 2""" - super(Python2, self).create() + super().create() # install a patched site-package, the default Python 2 site.py is not smart enough to understand pyvenv.cfg, # so we inject a small shim that can do this, the location of this depends where it's on host sys_std_plat = Path(self.interpreter.system_stdlib_platform) @@ -38,14 +32,14 @@ class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports): custom_site_text = read_from_zipapp(custom_site) else: custom_site_text = custom_site.read_text() - expected = json.dumps([os.path.relpath(ensure_text(str(i)), ensure_text(str(site_py))) for i in self.libs]) + expected = json.dumps([os.path.relpath(str(i), str(site_py)) for i in self.libs]) custom_site_text = custom_site_text.replace("___EXPECTED_SITE_PACKAGES___", expected) - reload_code = os.linesep.join(" {}".format(i) for i in self.reload_code.splitlines()).lstrip() + reload_code = os.linesep.join(f" {i}" for i in self.reload_code.splitlines()).lstrip() custom_site_text = custom_site_text.replace("# ___RELOAD_CODE___", reload_code) - skip_rewrite = os.linesep.join(" {}".format(i) for i in self.skip_rewrite.splitlines()).lstrip() + skip_rewrite = os.linesep.join(f" {i}" for i in self.skip_rewrite.splitlines()).lstrip() custom_site_text = custom_site_text.replace("# ___SKIP_REWRITE____", skip_rewrite) site_py.write_text(custom_site_text) @@ -60,8 +54,7 @@ class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports): @classmethod def sources(cls, interpreter): - for src in super(Python2, cls).sources(interpreter): - yield src + yield from super().sources(interpreter) # install files needed to run site.py, either from stdlib or stdlib_platform, at least pyc, but both if exists # if neither exists return the module file to trigger failure mappings, needs_py_module = ( @@ -69,8 +62,8 @@ class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports): cls.needs_stdlib_py_module(), ) for req in cls.modules(): - module_file, to_module, module_exists = cls.from_stdlib(mappings, "{}.py".format(req)) - compiled_file, to_compiled, compiled_exists = cls.from_stdlib(mappings, "{}.pyc".format(req)) + module_file, to_module, module_exists = cls.from_stdlib(mappings, f"{req}.py") + compiled_file, to_compiled, compiled_exists = cls.from_stdlib(mappings, f"{req}.pyc") if needs_py_module or module_exists or not compiled_exists: yield PathRefToDest(module_file, dest=to_module) if compiled_exists: diff --git a/src/virtualenv/create/via_global_ref/builtin/ref.py b/src/virtualenv/create/via_global_ref/builtin/ref.py index 69f243b..fb1cf2e 100644 --- a/src/virtualenv/create/via_global_ref/builtin/ref.py +++ b/src/virtualenv/create/via_global_ref/builtin/ref.py @@ -3,34 +3,29 @@ Virtual environments in the traditional sense are built as reference to the host references to elements on the file system, allowing our system to automatically detect what modes it can support given the constraints: e.g. can the file system symlink, can the files be read, executed, etc. """ -from __future__ import absolute_import, unicode_literals import os from abc import ABCMeta, abstractmethod from collections import OrderedDict from stat import S_IXGRP, S_IXOTH, S_IXUSR -from six import add_metaclass - from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink from virtualenv.util.path import copy, make_exe, symlink -from virtualenv.util.six import ensure_text -class RefMust(object): +class RefMust: NA = "NA" COPY = "copy" SYMLINK = "symlink" -class RefWhen(object): +class RefWhen: ANY = "ANY" COPY = "copy" SYMLINK = "symlink" -@add_metaclass(ABCMeta) -class PathRef(object): +class PathRef(metaclass=ABCMeta): """Base class that checks if a file reference can be symlink/copied""" FS_SUPPORTS_SYMLINK = fs_supports_symlink() @@ -49,7 +44,7 @@ class PathRef(object): self._can_symlink = None if self.exists else False def __repr__(self): - return "{}(src={})".format(self.__class__.__name__, self.src) + return f"{self.__class__.__name__}(src={self.src})" @property def can_read(self): @@ -61,7 +56,7 @@ class PathRef(object): except OSError: self._can_read = False else: - self._can_read = os.access(ensure_text(str(self.src)), os.R_OK) + self._can_read = os.access(str(self.src), os.R_OK) return self._can_read @property @@ -94,12 +89,11 @@ class PathRef(object): return symlink if symlinks else copy -@add_metaclass(ABCMeta) -class ExePathRef(PathRef): +class ExePathRef(PathRef, metaclass=ABCMeta): """Base class that checks if a executable can be references via symlink/copy""" def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): - super(ExePathRef, self).__init__(src, must, when) + super().__init__(src, must, when) self._can_run = None @property @@ -125,7 +119,7 @@ class PathRefToDest(PathRef): """Link a path on the file system""" def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY): - super(PathRefToDest, self).__init__(src, must, when) + super().__init__(src, must, when) self.dest = dest def run(self, creator, symlinks): @@ -169,4 +163,14 @@ class ExePathRefToDest(PathRefToDest, ExePathRef): make_exe(link_file) def __repr__(self): - return "{}(src={}, alias={})".format(self.__class__.__name__, self.src, self.aliases) + return f"{self.__class__.__name__}(src={self.src}, alias={self.aliases})" + + +__all__ = [ + "ExePathRef", + "ExePathRefToDest", + "PathRefToDest", + "PathRef", + "RefWhen", + "RefMust", +] diff --git a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py index 863ae16..2d491cc 100644 --- a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py +++ b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py @@ -1,9 +1,5 @@ -from __future__ import absolute_import, unicode_literals - from abc import ABCMeta -from six import add_metaclass - from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, RefMust, RefWhen from virtualenv.util.path import ensure_dir @@ -13,19 +9,18 @@ from .builtin_way import VirtualenvBuiltin class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta): def __init__(self): - super(BuiltinViaGlobalRefMeta, self).__init__() + super().__init__() self.sources = [] -@add_metaclass(ABCMeta) -class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin): +class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin, metaclass=ABCMeta): def __init__(self, options, interpreter): - super(ViaGlobalRefVirtualenvBuiltin, self).__init__(options, interpreter) + super().__init__(options, interpreter) self._sources = getattr(options.meta, "sources", None) # if we're created as a describer this might be missing @classmethod def can_create(cls, interpreter): - """By default all built-in methods assume that if we can describe it we can create it""" + """By default, all built-in methods assume that if we can describe it we can create it""" # first we must be able to describe it if not cls.can_describe(interpreter): return None @@ -39,11 +34,11 @@ class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin): for src in cls.sources(interpreter): if src.exists: if meta.can_copy and not src.can_copy: - meta.copy_error = "cannot copy {}".format(src) + meta.copy_error = f"cannot copy {src}" if meta.can_symlink and not src.can_symlink: - meta.symlink_error = "cannot symlink {}".format(src) + meta.symlink_error = f"cannot symlink {src}" else: - msg = "missing required file {}".format(src) + msg = f"missing required file {src}" if src.when == RefMust.NA: meta.error = msg elif src.when == RefMust.COPY: @@ -51,10 +46,7 @@ class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin): elif src.when == RefMust.SYMLINK: meta.symlink_error = msg if not meta.can_copy and not meta.can_symlink: - meta.error = "neither copy or symlink supported, copy: {} symlink: {}".format( - meta.copy_error, - meta.symlink_error, - ) + meta.error = f"neither copy or symlink supported, copy: {meta.copy_error} symlink: {meta.symlink_error}" if meta.error: break meta.sources.append(src) @@ -98,7 +90,7 @@ class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin): finally: if true_system_site != self.enable_system_site_package: self.enable_system_site_package = true_system_site - super(ViaGlobalRefVirtualenvBuiltin, self).create() + super().create() def ensure_directories(self): return {self.dest, self.bin_dir, self.script_dir, self.stdlib} | set(self.libs) @@ -108,7 +100,13 @@ class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin): We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these from home (which usually is done within the interpreter itself) """ - super(ViaGlobalRefVirtualenvBuiltin, self).set_pyenv_cfg() + super().set_pyenv_cfg() self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix self.pyenv_cfg["base-executable"] = self.interpreter.system_executable + + +__all__ = [ + "BuiltinViaGlobalRefMeta", + "ViaGlobalRefVirtualenvBuiltin", +] diff --git a/src/virtualenv/create/via_global_ref/store.py b/src/virtualenv/create/via_global_ref/store.py index 134a535..a9c559c 100644 --- a/src/virtualenv/create/via_global_ref/store.py +++ b/src/virtualenv/create/via_global_ref/store.py @@ -1,6 +1,4 @@ -from __future__ import absolute_import, unicode_literals - -from virtualenv.util.path import Path +from pathlib import Path def handle_store_python(meta, interpreter): @@ -20,7 +18,7 @@ def is_store_python(interpreter): ) -__all__ = ( +__all__ = [ "handle_store_python", "is_store_python", -) +] diff --git a/src/virtualenv/create/via_global_ref/venv.py b/src/virtualenv/create/via_global_ref/venv.py index d9fe9eb..071375a 100644 --- a/src/virtualenv/create/via_global_ref/venv.py +++ b/src/virtualenv/create/via_global_ref/venv.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import logging from copy import copy @@ -16,14 +14,13 @@ from .builtin.pypy.pypy3 import Pypy3Windows class Venv(ViaGlobalRefApi): def __init__(self, options, interpreter): self.describe = options.describe - super(Venv, self).__init__(options, interpreter) - self.can_be_inline = ( - interpreter is PythonInfo.current() and interpreter.executable == interpreter.system_executable - ) + super().__init__(options, interpreter) + current = PythonInfo.current() + self.can_be_inline = interpreter is current and interpreter.executable == interpreter.system_executable self._context = None def _args(self): - return super(Venv, self)._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else []) + return super()._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else []) @classmethod def can_create(cls, interpreter): @@ -41,7 +38,7 @@ class Venv(ViaGlobalRefApi): self.create_via_sub_process() for lib in self.libs: ensure_dir(lib) - super(Venv, self).create() + super().create() self.executables_for_win_pypy_less_v37() def executables_for_win_pypy_less_v37(self): @@ -84,7 +81,7 @@ class Venv(ViaGlobalRefApi): def set_pyenv_cfg(self): # prefer venv options over ours, but keep our extra venv_content = copy(self.pyenv_cfg.refresh()) - super(Venv, self).set_pyenv_cfg() + super().set_pyenv_cfg() self.pyenv_cfg.update(venv_content) def __getattribute__(self, item): @@ -94,3 +91,8 @@ class Venv(ViaGlobalRefApi): if not callable(element) or item in ("script",): return element return object.__getattribute__(self, item) + + +__all__ = [ + "Venv", +] diff --git a/src/virtualenv/discovery/__init__.py b/src/virtualenv/discovery/__init__.py index 01e6d4f..e69de29 100644 --- a/src/virtualenv/discovery/__init__.py +++ b/src/virtualenv/discovery/__init__.py @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py index 52f7398..40320d3 100644 --- a/src/virtualenv/discovery/builtin.py +++ b/src/virtualenv/discovery/builtin.py @@ -1,11 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import logging import os import sys from virtualenv.info import IS_WIN -from virtualenv.util.six import ensure_str, ensure_text from .discover import Discover from .py_info import PythonInfo @@ -14,7 +11,7 @@ from .py_spec import PythonSpec class Builtin(Discover): def __init__(self, options): - super(Builtin, self).__init__(options) + super().__init__(options) self.python_spec = options.python if options.python else [sys.executable] self.app_data = options.app_data self.try_first_with = options.try_first_with @@ -50,11 +47,8 @@ class Builtin(Discover): return None def __repr__(self): - return ensure_str(self.__unicode__()) - - def __unicode__(self): spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec - return "{} discover of python_spec={!r}".format(self.__class__.__name__, spec) + return f"{self.__class__.__name__} discover of python_spec={spec!r}" def get_interpreter(key, try_first_with, app_data=None, env=None): @@ -110,10 +104,10 @@ def propose_interpreters(spec, try_first_with, app_data, env=None): paths = get_paths(env) tested_exes = set() for pos, path in enumerate(paths): - path = ensure_text(path) - logging.debug(LazyPathDump(pos, path, env)) + path_str = str(path) + logging.debug(LazyPathDump(pos, path_str, env)) for candidate, match in possible_specs(spec): - found = check_path(candidate, path) + found = check_path(candidate, path_str) if found is not None: exe = os.path.abspath(found) if exe not in tested_exes: @@ -124,7 +118,7 @@ def propose_interpreters(spec, try_first_with, app_data, env=None): def get_paths(env): - path = env.get(str("PATH"), None) + path = env.get("PATH", None) if path is None: try: path = os.confstr("CS_PATH") @@ -137,18 +131,15 @@ def get_paths(env): return paths -class LazyPathDump(object): +class LazyPathDump: def __init__(self, pos, path, env): self.pos = pos self.path = path self.env = env def __repr__(self): - return ensure_str(self.__unicode__()) - - def __unicode__(self): - content = "discover PATH[{}]={}".format(self.pos, self.path) - if self.env.get(str("_VIRTUALENV_DEBUG")): # this is the over the board debug + content = f"discover PATH[{self.pos}]={self.path}" + if self.env.get("_VIRTUALENV_DEBUG"): # this is the over the board debug content += " with =>" for file_name in os.listdir(self.path): try: @@ -178,9 +169,15 @@ def possible_specs(spec): # 4. then maybe it's something exact on PATH - if it was direct lookup implementation no longer counts yield spec.str_spec, False # 5. or from the spec we can deduce a name on path that matches - for exe, match in spec.generate_names(): - yield exe, match + yield from spec.generate_names() class PathPythonInfo(PythonInfo): - """ """ + """python info from path""" + + +__all__ = [ + "get_interpreter", + "Builtin", + "PathPythonInfo", +] diff --git a/src/virtualenv/discovery/cached_py_info.py b/src/virtualenv/discovery/cached_py_info.py index 7eb4161..22ad249 100644 --- a/src/virtualenv/discovery/cached_py_info.py +++ b/src/virtualenv/discovery/cached_py_info.py @@ -4,26 +4,20 @@ We acquire the python information by running an interrogation script via subproc cheap, especially not on Windows. To not have to pay this hefty cost every time we apply multiple levels of caching. """ -from __future__ import absolute_import, unicode_literals import logging import os import random import sys from collections import OrderedDict +from pathlib import Path +from shlex import quote from string import ascii_lowercase, ascii_uppercase, digits +from subprocess import Popen from virtualenv.app_data import AppDataDisabled from virtualenv.discovery.py_info import PythonInfo -from virtualenv.info import PY2 -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text -from virtualenv.util.subprocess import Popen, subprocess - -if PY2: - from pipes import quote -else: - from shlex import quote +from virtualenv.util.subprocess import subprocess _CACHE = OrderedDict() _CACHE[Path(sys.executable)] = PythonInfo() @@ -36,7 +30,7 @@ def from_exe(cls, app_data, exe, env=None, raise_on_error=True, ignore_cache=Fal if raise_on_error: raise result else: - logging.info("%s", str(result)) + logging.info("%s", result) result = None return result @@ -57,7 +51,7 @@ def _get_from_cache(cls, app_data, exe, env, ignore_cache=True): def _get_via_file_cache(cls, app_data, path, exe, env): - path_text = ensure_text(str(path)) + path_text = str(path) try: path_modified = path.stat().st_mtime except OSError: @@ -87,7 +81,7 @@ def _get_via_file_cache(cls, app_data, path, exe, env): return py_info -COOKIE_LENGTH = 32 # type: int +COOKIE_LENGTH: int = 32 def gen_cookie(): @@ -150,46 +144,30 @@ def _run_subprocess(cls, exe, app_data, env): result = cls._from_json(out) result.executable = exe # keep original executable as this may contain initialization code else: - msg = "failed to query {} with code {}{}{}".format( - exe, - code, - " out: {!r}".format(out) if out else "", - " err: {!r}".format(err) if err else "", - ) - failure = RuntimeError(msg) + msg = f"{exe} with code {code}{f' out: {out!r}' if out else ''}{f' err: {err!r}' if err else ''}" + failure = RuntimeError(f"failed to query {msg}") return failure, result -class LogCmd(object): +class LogCmd: def __init__(self, cmd, env=None): self.cmd = cmd self.env = env def __repr__(self): - def e(v): - return v.decode("utf-8") if isinstance(v, bytes) else v - - cmd_repr = e(" ").join(quote(e(c)) for c in self.cmd) + cmd_repr = " ".join(quote(str(c)) for c in self.cmd) if self.env is not None: - cmd_repr += e(" env of {!r}").format(self.env) - if PY2: - return cmd_repr.encode("utf-8") + cmd_repr = f"{cmd_repr} env of {self.env!r}" return cmd_repr - def __unicode__(self): - raw = repr(self) - if PY2: - return raw.decode("utf-8") - return raw - def clear(app_data): app_data.py_info_clear() _CACHE.clear() -___all___ = ( +___all___ = [ "from_exe", "clear", "LogCmd", -) +] diff --git a/src/virtualenv/discovery/discover.py b/src/virtualenv/discovery/discover.py index 72748c3..8950a55 100644 --- a/src/virtualenv/discovery/discover.py +++ b/src/virtualenv/discovery/discover.py @@ -1,12 +1,7 @@ -from __future__ import absolute_import, unicode_literals - from abc import ABCMeta, abstractmethod -from six import add_metaclass - -@add_metaclass(ABCMeta) -class Discover(object): +class Discover(metaclass=ABCMeta): """Discover and provide the requested Python interpreter""" @classmethod @@ -17,7 +12,6 @@ class Discover(object): """ raise NotImplementedError - # noinspection PyUnusedLocal def __init__(self, options): """Create a new discovery mechanism. @@ -31,7 +25,6 @@ class Discover(object): def run(self): """Discovers an interpreter. - :return: the interpreter ready to use for virtual environment creation """ raise NotImplementedError @@ -45,3 +38,8 @@ class Discover(object): self._interpreter = self.run() self._has_run = True return self._interpreter + + +__all__ = [ + "Discover", +] diff --git a/src/virtualenv/discovery/py_spec.py b/src/virtualenv/discovery/py_spec.py index cb63e15..058d8be 100644 --- a/src/virtualenv/discovery/py_spec.py +++ b/src/virtualenv/discovery/py_spec.py @@ -1,19 +1,15 @@ -"""A Python specification is an abstract requirement definition of a interpreter""" -from __future__ import absolute_import, unicode_literals +"""A Python specification is an abstract requirement definition of an interpreter""" import os import re -import sys from collections import OrderedDict from virtualenv.info import fs_is_case_sensitive -from virtualenv.util.six import ensure_str PATTERN = re.compile(r"^(?P<impl>[a-zA-Z]+)?(?P<version>[0-9.]+)?(?:-(?P<arch>32|64))?$") -IS_WIN = sys.platform == "win32" -class PythonSpec(object): +class PythonSpec: """Contains specification about a Python Interpreter""" def __init__(self, str_spec, implementation, major, minor, micro, architecture, path): @@ -87,7 +83,7 @@ class PythonSpec(object): for impl, match in impls.items(): for at in range(len(version), -1, -1): cur_ver = version[0:at] - spec = "{}{}".format(impl, ".".join(str(i) for i in cur_ver)) + spec = f"{impl}{'.'.join(str(i) for i in cur_ver)}" yield spec, match @property @@ -108,15 +104,12 @@ class PythonSpec(object): return False return True - def __unicode__(self): - return "{}({})".format( - type(self).__name__, - ", ".join( - "{}={}".format(k, getattr(self, k)) - for k in ("implementation", "major", "minor", "micro", "architecture", "path") - if getattr(self, k) is not None - ), - ) - def __repr__(self): - return ensure_str(self.__unicode__()) + name = type(self).__name__ + params = "implementation", "major", "minor", "micro", "architecture", "path" + return f"{name}({', '.join(f'{k}={getattr(self, k)}' for k in params if getattr(self, k) is not None)})" + + +__all__ = [ + "PythonSpec", +] diff --git a/src/virtualenv/discovery/windows/__init__.py b/src/virtualenv/discovery/windows/__init__.py index 259be97..4663a3a 100644 --- a/src/virtualenv/discovery/windows/__init__.py +++ b/src/virtualenv/discovery/windows/__init__.py @@ -1,12 +1,10 @@ -from __future__ import absolute_import, unicode_literals - from ..py_info import PythonInfo from ..py_spec import PythonSpec from .pep514 import discover_pythons class Pep514PythonInfo(PythonInfo): - """ """ + """A Python information acquired from PEP-514""" def propose_interpreters(spec, cache_dir, env): @@ -29,3 +27,9 @@ def propose_interpreters(spec, cache_dir, env): if interpreter is not None: if interpreter.satisfies(spec, impl_must_match=True): yield interpreter + + +__all__ = [ + "Pep514PythonInfo", + "propose_interpreters", +] diff --git a/src/virtualenv/discovery/windows/pep514.py b/src/virtualenv/discovery/windows/pep514.py index 048436a..beb1d81 100644 --- a/src/virtualenv/discovery/windows/pep514.py +++ b/src/virtualenv/discovery/windows/pep514.py @@ -1,18 +1,10 @@ """Implement https://www.python.org/dev/peps/pep-0514/ to discover interpreters - Windows only""" -from __future__ import absolute_import, print_function, unicode_literals import os import re +import winreg from logging import basicConfig, getLogger -import six - -if six.PY3: - import winreg -else: - # noinspection PyUnresolvedReferences - import _winreg as winreg - LOGGER = getLogger(__name__) @@ -39,8 +31,7 @@ def discover_pythons(): (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_64KEY, 64), (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_32KEY, 32), ]: - for spec in process_set(hive, hive_name, key, flags, default_arch): - yield spec + yield from process_set(hive, hive_name, key, flags, default_arch) def process_set(hive, hive_name, key, flags, default_arch): @@ -49,8 +40,7 @@ def process_set(hive, hive_name, key, flags, default_arch): for company in enum_keys(root_key): if company == "PyLauncher": # reserved continue - for spec in process_company(hive_name, company, root_key, default_arch): - yield spec + yield from process_company(hive_name, company, root_key, default_arch) except OSError: pass @@ -77,9 +67,9 @@ def process_tag(hive_name, company, company_key, tag, default_arch): def load_exe(hive_name, company, company_key, tag): - key_path = "{}/{}/{}".format(hive_name, company, tag) + key_path = f"{hive_name}/{company}/{tag}" try: - with winreg.OpenKeyEx(company_key, r"{}\InstallPath".format(tag)) as ip_key: + with winreg.OpenKeyEx(company_key, rf"{tag}\InstallPath") as ip_key: with ip_key: exe = get_value(ip_key, "ExecutablePath") if exe is None: @@ -88,21 +78,21 @@ def load_exe(hive_name, company, company_key, tag): msg(key_path, "no ExecutablePath or default for it") else: - exe = os.path.join(ip, str("python.exe")) + exe = os.path.join(ip, "python.exe") if exe is not None and os.path.exists(exe): args = get_value(ip_key, "ExecutableArguments") return exe, args else: - msg(key_path, "could not load exe with value {}".format(exe)) + msg(key_path, f"could not load exe with value {exe}") except OSError: - msg("{}/{}".format(key_path, "InstallPath"), "missing") + msg(f"{key_path}/InstallPath", "missing") return None def load_arch_data(hive_name, company, tag, tag_key, default_arch): arch_str = get_value(tag_key, "SysArchitecture") if arch_str is not None: - key_path = "{}/{}/{}/SysArchitecture".format(hive_name, company, tag) + key_path = f"{hive_name}/{company}/{tag}/SysArchitecture" try: return parse_arch(arch_str) except ValueError as sys_arch: @@ -111,20 +101,20 @@ def load_arch_data(hive_name, company, tag, tag_key, default_arch): def parse_arch(arch_str): - if isinstance(arch_str, six.string_types): + if isinstance(arch_str, str): match = re.match(r"^(\d+)bit$", arch_str) if match: return int(next(iter(match.groups()))) - error = "invalid format {}".format(arch_str) + error = f"invalid format {arch_str}" else: - error = "arch is not string: {}".format(repr(arch_str)) + error = f"arch is not string: {repr(arch_str)}" raise ValueError(error) def load_version_data(hive_name, company, tag, tag_key): for candidate, key_path in [ - (get_value(tag_key, "SysVersion"), "{}/{}/{}/SysVersion".format(hive_name, company, tag)), - (tag, "{}/{}/{}".format(hive_name, company, tag)), + (get_value(tag_key, "SysVersion"), f"{hive_name}/{company}/{tag}/SysVersion"), + (tag, f"{hive_name}/{company}/{tag}"), ]: if candidate is not None: try: @@ -135,18 +125,18 @@ def load_version_data(hive_name, company, tag, tag_key): def parse_version(version_str): - if isinstance(version_str, six.string_types): + if isinstance(version_str, str): match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?$", version_str) if match: return tuple(int(i) if i is not None else None for i in match.groups()) - error = "invalid format {}".format(version_str) + error = f"invalid format {version_str}" else: - error = "version is not string: {}".format(repr(version_str)) + error = f"version is not string: {version_str!r}" raise ValueError(error) def msg(path, what): - LOGGER.warning("PEP-514 violation in Windows Registry at {} error: {}".format(path, what)) + LOGGER.warning(f"PEP-514 violation in Windows Registry at {path} error: {what}") def _run(): diff --git a/src/virtualenv/info.py b/src/virtualenv/info.py index 7d5e86d..a4fc4bf 100644 --- a/src/virtualenv/info.py +++ b/src/virtualenv/info.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import logging import os import platform @@ -9,13 +7,10 @@ import tempfile IMPLEMENTATION = platform.python_implementation() IS_PYPY = IMPLEMENTATION == "PyPy" IS_CPYTHON = IMPLEMENTATION == "CPython" -PY3 = sys.version_info[0] == 3 -PY2 = sys.version_info[0] == 2 IS_WIN = sys.platform == "win32" IS_MAC_ARM64 = sys.platform == "darwin" and platform.machine() == "arm64" ROOT = os.path.realpath(os.path.join(os.path.abspath(__file__), os.path.pardir, os.path.pardir)) IS_ZIPAPP = os.path.isfile(ROOT) -WIN_CPYTHON_2 = IS_CPYTHON and IS_WIN and PY2 _CAN_SYMLINK = _FS_CASE_SENSITIVE = _CFG_DIR = _DATA_DIR = None @@ -39,7 +34,7 @@ def fs_supports_symlink(): if IS_WIN: with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file: temp_dir = os.path.dirname(tmp_file.name) - dest = os.path.join(temp_dir, "{}-{}".format(tmp_file.name, "b")) + dest = os.path.join(temp_dir, f"{tmp_file.name}-{'b'}") try: os.symlink(tmp_file.name, dest) can = True @@ -55,12 +50,10 @@ def fs_supports_symlink(): __all__ = ( "IS_PYPY", "IS_CPYTHON", - "PY3", - "PY2", "IS_WIN", "fs_is_case_sensitive", "fs_supports_symlink", "ROOT", "IS_ZIPAPP", - "WIN_CPYTHON_2", + "IS_MAC_ARM64", ) diff --git a/src/virtualenv/report.py b/src/virtualenv/report.py index 2a2954f..0236f21 100644 --- a/src/virtualenv/report.py +++ b/src/virtualenv/report.py @@ -1,10 +1,6 @@ -from __future__ import absolute_import, unicode_literals - import logging import sys -from virtualenv.util.six import ensure_str - LEVELS = { 0: logging.CRITICAL, 1: logging.ERROR, @@ -26,10 +22,10 @@ def setup_report(verbosity, show_pid=False): msg_format = "%(message)s" if level <= logging.DEBUG: locate = "module" - msg_format = "%(relativeCreated)d {} [%(levelname)s %({})s:%(lineno)d]".format(msg_format, locate) + msg_format = f"%(relativeCreated)d {msg_format} [%(levelname)s %({locate})s:%(lineno)d]" if show_pid: - msg_format = "[%(process)d] " + msg_format - formatter = logging.Formatter(ensure_str(msg_format)) + msg_format = f"[%(process)d] {msg_format}" + formatter = logging.Formatter(msg_format) stream_handler = logging.StreamHandler(stream=sys.stdout) stream_handler.setLevel(level) LOGGER.setLevel(logging.NOTSET) @@ -46,8 +42,8 @@ def _clean_handlers(log): log.removeHandler(log_handler) -__all__ = ( +__all__ = [ "LEVELS", "MAX_LEVEL", "setup_report", -) +] diff --git a/src/virtualenv/run/__init__.py b/src/virtualenv/run/__init__.py index e8e7ab1..8f18cd9 100644 --- a/src/virtualenv/run/__init__.py +++ b/src/virtualenv/run/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import logging import os from functools import partial @@ -69,7 +67,7 @@ def build_parser(args=None, options=None, setup_logging=True, env=None): discover = get_discover(parser, args) parser._interpreter = interpreter = discover.interpreter if interpreter is None: - raise RuntimeError("failed to find interpreter for {}".format(discover)) + raise RuntimeError(f"failed to find interpreter for {discover}") elements = [ CreatorSelector(interpreter, parser), SeederSelector(interpreter, parser), @@ -130,13 +128,13 @@ def add_version_flag(parser): parser.add_argument( "--version", action="version", - version="%(prog)s {} from {}".format(__version__, virtualenv.__file__), + version=f"%(prog)s {__version__} from {virtualenv.__file__}", help="display the version of the virtualenv package and its location, then exit", ) def _do_report_setup(parser, args, setup_logging): - level_map = ", ".join("{}={}".format(logging.getLevelName(l), c) for c, l in sorted(list(LEVELS.items()))) + level_map = ", ".join(f"{logging.getLevelName(l)}={c}" for c, l in sorted(list(LEVELS.items()))) msg = "verbosity = verbose - quiet, default {}, mapping => {}" verbosity_group = parser.add_argument_group( title="verbosity", @@ -150,7 +148,7 @@ def _do_report_setup(parser, args, setup_logging): setup_report(option.verbosity) -__all__ = ( +__all__ = [ "cli_run", "session_via_cli", -) +] diff --git a/src/virtualenv/run/plugin/activators.py b/src/virtualenv/run/plugin/activators.py index 8180981..320cae7 100644 --- a/src/virtualenv/run/plugin/activators.py +++ b/src/virtualenv/run/plugin/activators.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from argparse import ArgumentTypeError from collections import OrderedDict @@ -12,14 +10,14 @@ class ActivationSelector(ComponentBuilder): possible = OrderedDict( (k, v) for k, v in self.options("virtualenv.activate").items() if v.supports(interpreter) ) - super(ActivationSelector, self).__init__(interpreter, parser, "activators", possible) + super().__init__(interpreter, parser, "activators", possible) self.parser.description = "options for activation scripts" self.active = None def add_selector_arg_parse(self, name, choices): self.default = ",".join(choices) self.parser.add_argument( - "--{}".format(name), + f"--{name}", default=self.default, metavar="comma_sep_list", required=False, @@ -31,7 +29,7 @@ class ActivationSelector(ComponentBuilder): elements = [e.strip() for e in entered_str.split(",") if e.strip()] missing = [e for e in elements if e not in self.possible] if missing: - raise ArgumentTypeError("the following activators are not available {}".format(",".join(missing))) + raise ArgumentTypeError(f"the following activators are not available {','.join(missing)}") return elements def handle_selected_arg_parse(self, options): @@ -54,3 +52,8 @@ class ActivationSelector(ComponentBuilder): def create(self, options): return [activator_class(options) for activator_class in self.active.values()] + + +__all__ = [ + "ActivationSelector", +] diff --git a/src/virtualenv/run/plugin/base.py b/src/virtualenv/run/plugin/base.py index 048c76a..5fbf81b 100644 --- a/src/virtualenv/run/plugin/base.py +++ b/src/virtualenv/run/plugin/base.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import sys from collections import OrderedDict @@ -13,7 +11,7 @@ else: importlib_metadata_version = tuple(int(i) for i in version("importlib_metadata").split(".")[:2]) -class PluginLoader(object): +class PluginLoader: _OPTIONS = None _ENTRY_POINTS = None @@ -52,14 +50,20 @@ class ComponentBuilder(PluginLoader): def handle_selected_arg_parse(self, options): selected = getattr(options, self.name) if selected not in self.possible: - raise RuntimeError("No implementation for {}".format(self.interpreter)) + raise RuntimeError(f"No implementation for {self.interpreter}") self._impl_class = self.possible[selected] self.populate_selected_argparse(selected, options.app_data) return selected def populate_selected_argparse(self, selected, app_data): - self.parser.description = "options for {} {}".format(self.name, selected) + self.parser.description = f"options for {self.name} {selected}" self._impl_class.add_parser_arguments(self.parser, self.interpreter, app_data) def create(self, options): return self._impl_class(options, self.interpreter) + + +__all__ = [ + "PluginLoader", + "ComponentBuilder", +] diff --git a/src/virtualenv/run/plugin/creators.py b/src/virtualenv/run/plugin/creators.py index ef4177a..8953064 100644 --- a/src/virtualenv/run/plugin/creators.py +++ b/src/virtualenv/run/plugin/creators.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from collections import OrderedDict, defaultdict, namedtuple from virtualenv.create.describe import Describe @@ -13,7 +11,7 @@ CreatorInfo = namedtuple("CreatorInfo", ["key_to_class", "key_to_meta", "describ class CreatorSelector(ComponentBuilder): def __init__(self, interpreter, parser): creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter) - super(CreatorSelector, self).__init__(interpreter, parser, "creator", creators) + super().__init__(interpreter, parser, "creator", creators) @classmethod def for_interpreter(cls, interpreter): @@ -37,10 +35,10 @@ class CreatorSelector(ComponentBuilder): describe = creator_class if not key_to_meta: if errors: - rows = ["{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errors.items()] + rows = [f"{k} for creators {', '.join(i.__name__ for i in v)}" for k, v in errors.items()] raise RuntimeError("\n".join(rows)) else: - raise RuntimeError("No virtualenv implementation for {}".format(interpreter)) + raise RuntimeError(f"No virtualenv implementation for {interpreter}") return CreatorInfo( key_to_class=key_to_class, key_to_meta=key_to_meta, @@ -53,13 +51,11 @@ class CreatorSelector(ComponentBuilder): choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1) default_value = self._get_default(choices) self.parser.add_argument( - "--{}".format(name), + f"--{name}", choices=choices, default=default_value, required=False, - help="create environment via{}".format( - "" if self.builtin_key is None else " (builtin = {})".format(self.builtin_key), - ), + help=f"create environment via{'' if self.builtin_key is None else f' (builtin = {self.builtin_key})'}", ) @staticmethod @@ -67,11 +63,17 @@ class CreatorSelector(ComponentBuilder): return next(iter(choices)) def populate_selected_argparse(self, selected, app_data): - self.parser.description = "options for {} {}".format(self.name, selected) + self.parser.description = f"options for {self.name} {selected}" self._impl_class.add_parser_arguments(self.parser, self.interpreter, self.key_to_meta[selected], app_data) def create(self, options): options.meta = self.key_to_meta[getattr(options, self.name)] if not issubclass(self._impl_class, Describe): options.describe = self.describe(options, self.interpreter) - return super(CreatorSelector, self).create(options) + return super().create(options) + + +__all__ = [ + "CreatorSelector", + "CreatorInfo", +] diff --git a/src/virtualenv/run/plugin/discovery.py b/src/virtualenv/run/plugin/discovery.py index ac9b7f5..13f39ed 100644 --- a/src/virtualenv/run/plugin/discovery.py +++ b/src/virtualenv/run/plugin/discovery.py @@ -1,10 +1,8 @@ -from __future__ import absolute_import, unicode_literals - from .base import PluginLoader class Discovery(PluginLoader): - """ """ + """Discovery plugins""" def get_discover(parser, args): @@ -33,3 +31,9 @@ def get_discover(parser, args): def _get_default_discovery(discover_types): return list(discover_types.keys()) + + +__all__ = [ + "get_discover", + "Discovery", +] diff --git a/src/virtualenv/run/plugin/seeders.py b/src/virtualenv/run/plugin/seeders.py index d182c6f..1a51d2e 100644 --- a/src/virtualenv/run/plugin/seeders.py +++ b/src/virtualenv/run/plugin/seeders.py @@ -1,16 +1,14 @@ -from __future__ import absolute_import, unicode_literals - from .base import ComponentBuilder class SeederSelector(ComponentBuilder): def __init__(self, interpreter, parser): possible = self.options("virtualenv.seed") - super(SeederSelector, self).__init__(interpreter, parser, "seeder", possible) + super().__init__(interpreter, parser, "seeder", possible) def add_selector_arg_parse(self, name, choices): self.parser.add_argument( - "--{}".format(name), + f"--{name}", choices=choices, default=self._get_default(), required=False, @@ -29,7 +27,12 @@ class SeederSelector(ComponentBuilder): return "app-data" def handle_selected_arg_parse(self, options): - return super(SeederSelector, self).handle_selected_arg_parse(options) + return super().handle_selected_arg_parse(options) def create(self, options): return self._impl_class(options) + + +__all__ = [ + "SeederSelector", +] diff --git a/src/virtualenv/run/session.py b/src/virtualenv/run/session.py index 24836d2..84777fe 100644 --- a/src/virtualenv/run/session.py +++ b/src/virtualenv/run/session.py @@ -1,12 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import json import logging -from virtualenv.util.six import ensure_text - -class Session(object): +class Session: """Represents a virtual environment creation session""" def __init__(self, verbosity, app_data, interpreter, creator, seeder, activators): @@ -49,7 +45,7 @@ class Session(object): self.creator.pyenv_cfg.write() def _create(self): - logging.info("create virtual environment via %s", ensure_text(str(self.creator))) + logging.info("create virtual environment via %s", self.creator) self.creator.run() logging.debug(_DEBUG_MARKER) logging.debug("%s", _Debug(self.creator)) @@ -61,10 +57,8 @@ class Session(object): def _activate(self): if self.activators: - logging.info( - "add activators for %s", - ", ".join(type(i).__name__.replace("Activator", "") for i in self.activators), - ) + active = ", ".join(type(i).__name__.replace("Activator", "") for i in self.activators) + logging.info("add activators for %s", active) for activator in self.activators: activator.generate(self.creator) @@ -78,14 +72,16 @@ class Session(object): _DEBUG_MARKER = "=" * 30 + " target debug " + "=" * 30 -class _Debug(object): +class _Debug: """lazily populate debug""" def __init__(self, creator): self.creator = creator - def __unicode__(self): - return ensure_text(repr(self)) - def __repr__(self): return json.dumps(self.creator.debug, indent=2) + + +__all__ = [ + "Session", +] diff --git a/src/virtualenv/seed/__init__.py b/src/virtualenv/seed/__init__.py index 01e6d4f..e69de29 100644 --- a/src/virtualenv/seed/__init__.py +++ b/src/virtualenv/seed/__init__.py @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals diff --git a/src/virtualenv/seed/embed/base_embed.py b/src/virtualenv/seed/embed/base_embed.py index c794e83..b0bcbc3 100644 --- a/src/virtualenv/seed/embed/base_embed.py +++ b/src/virtualenv/seed/embed/base_embed.py @@ -1,11 +1,5 @@ -from __future__ import absolute_import, unicode_literals - from abc import ABCMeta - -from six import add_metaclass - -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_str, ensure_text +from pathlib import Path from ..seeder import Seeder from ..wheels import Version @@ -13,10 +7,9 @@ from ..wheels import Version PERIODIC_UPDATE_ON_BY_DEFAULT = True -@add_metaclass(ABCMeta) -class BaseEmbed(Seeder): +class BaseEmbed(Seeder, metaclass=ABCMeta): def __init__(self, options): - super(BaseEmbed, self).__init__(options, enabled=options.no_seed is False) + super().__init__(options, enabled=options.no_seed is False) self.download = options.download self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()] @@ -44,9 +37,9 @@ class BaseEmbed(Seeder): def distribution_to_versions(self): return { - distribution: getattr(self, "{}_version".format(distribution)) + distribution: getattr(self, f"{distribution}_version") for distribution in self.distributions() - if getattr(self, "no_{}".format(distribution)) is False + if getattr(self, f"no_{distribution}") is False } @classmethod @@ -57,14 +50,14 @@ class BaseEmbed(Seeder): "--never-download", dest="download", action="store_false", - help="pass to disable download of the latest {} from PyPI".format("/".join(cls.distributions())), + help=f"pass to disable download of the latest {'/'.join(cls.distributions())} from PyPI", default=True, ) group.add_argument( "--download", dest="download", action="store_true", - help="pass to enable download of the latest {} from PyPI".format("/".join(cls.distributions())), + help=f"pass to enable download of the latest {'/'.join(cls.distributions())} from PyPI", default=False, ) parser.add_argument( @@ -77,18 +70,18 @@ class BaseEmbed(Seeder): ) for distribution, default in cls.distributions().items(): parser.add_argument( - "--{}".format(distribution), + f"--{distribution}", dest=distribution, metavar="version", - help="version of {} to install as seed: embed, bundle or exact version".format(distribution), + help=f"version of {distribution} to install as seed: embed, bundle or exact version", default=default, ) for distribution in cls.distributions(): parser.add_argument( - "--no-{}".format(distribution), - dest="no_{}".format(distribution), + f"--no-{distribution}", + dest=f"no_{distribution}", action="store_true", - help="do not install {}".format(distribution), + help=f"do not install {distribution}", default=False, ) parser.add_argument( @@ -99,20 +92,20 @@ class BaseEmbed(Seeder): default=not PERIODIC_UPDATE_ON_BY_DEFAULT, ) - def __unicode__(self): + def __repr__(self): result = self.__class__.__name__ result += "(" if self.extra_search_dir: - result += "extra_search_dir={},".format(", ".join(ensure_text(str(i)) for i in self.extra_search_dir)) - result += "download={},".format(self.download) + result += f"extra_search_dir={', '.join(str(i) for i in self.extra_search_dir)}," + result += f"download={self.download}," for distribution in self.distributions(): - if getattr(self, "no_{}".format(distribution)): + if getattr(self, f"no_{distribution}"): continue - result += " {}{},".format( - distribution, - "={}".format(getattr(self, "{}_version".format(distribution), None) or "latest"), - ) + ver = f"={getattr(self, f'{distribution}_version', None) or 'latest'}" + result += f" {distribution}{ver}," return result[:-1] + ")" - def __repr__(self): - return ensure_str(self.__unicode__()) + +__all__ = [ + "BaseEmbed", +] diff --git a/src/virtualenv/seed/embed/pip_invoke.py b/src/virtualenv/seed/embed/pip_invoke.py index c935c02..2ca9438 100644 --- a/src/virtualenv/seed/embed/pip_invoke.py +++ b/src/virtualenv/seed/embed/pip_invoke.py @@ -1,18 +1,16 @@ -from __future__ import absolute_import, unicode_literals - import logging from contextlib import contextmanager +from subprocess import Popen from virtualenv.discovery.cached_py_info import LogCmd from virtualenv.seed.embed.base_embed import BaseEmbed -from virtualenv.util.subprocess import Popen from ..wheels import Version, get_wheel, pip_wheel_env_run class PipInvoke(BaseEmbed): def __init__(self, options): - super(PipInvoke, self).__init__(options) + super().__init__(options) def run(self, creator): if not self.enabled: @@ -28,7 +26,7 @@ class PipInvoke(BaseEmbed): process = Popen(cmd, env=env) process.communicate() if process.returncode != 0: - raise RuntimeError("failed seed with code {}".format(process.returncode)) + raise RuntimeError(f"failed seed with code {process.returncode}") return process @contextmanager @@ -49,9 +47,14 @@ class PipInvoke(BaseEmbed): env=self.env, ) if wheel is None: - raise RuntimeError("could not get wheel for distribution {}".format(dist)) + raise RuntimeError(f"could not get wheel for distribution {dist}") folders.add(str(wheel.path.parent)) cmd.append(Version.as_pip_req(dist, wheel.version)) for folder in sorted(folders): cmd.extend(["--find-links", str(folder)]) yield cmd + + +__all__ = [ + "PipInvoke", +] diff --git a/src/virtualenv/seed/embed/via_app_data/pip_install/base.py b/src/virtualenv/seed/embed/via_app_data/pip_install/base.py index 35e0cca..3c44b77 100644 --- a/src/virtualenv/seed/embed/via_app_data/pip_install/base.py +++ b/src/virtualenv/seed/embed/via_app_data/pip_install/base.py @@ -1,23 +1,19 @@ -from __future__ import absolute_import, unicode_literals - import logging import os import re import zipfile from abc import ABCMeta, abstractmethod +from configparser import ConfigParser from itertools import chain +from pathlib import Path from tempfile import mkdtemp from distlib.scripts import ScriptMaker, enquote_executable -from six import PY3, add_metaclass -from virtualenv.util import ConfigParser -from virtualenv.util.path import Path, safe_delete -from virtualenv.util.six import ensure_text +from virtualenv.util.path import safe_delete -@add_metaclass(ABCMeta) -class PipInstall(object): +class PipInstall(metaclass=ABCMeta): def __init__(self, wheel, creator, image_folder): self._wheel = wheel self._creator = creator @@ -71,10 +67,7 @@ class PipInstall(object): self._image_dir = Path(to_folder) def _records_text(self, files): - record_data = "\n".join( - "{},,".format(os.path.relpath(ensure_text(str(rec)), ensure_text(str(self._image_dir)))) for rec in files - ) - return record_data + return "\n".join(f"{os.path.relpath(str(rec), str(self._image_dir))},," for rec in files) def _generate_new_files(self): new_files = set() @@ -82,17 +75,17 @@ class PipInstall(object): installer.write_text("pip\n") new_files.add(installer) # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226 - marker = self._image_dir / "{}.virtualenv".format(self._dist_info.stem) + marker = self._image_dir / f"{self._dist_info.stem}.virtualenv" marker.write_text("") new_files.add(marker) folder = mkdtemp() try: to_folder = Path(folder) - rel = os.path.relpath(ensure_text(str(self._creator.script_dir)), ensure_text(str(self._creator.purelib))) + rel = os.path.relpath(str(self._creator.script_dir), str(self._creator.purelib)) version_info = self._creator.interpreter.version_info for name, module in self._console_scripts.items(): new_files.update( - Path(os.path.normpath(ensure_text(str(self._image_dir / rel / i.name)))) + Path(os.path.normpath(str(self._image_dir / rel / i.name))) for i in self._create_console_entry_point(name, module, to_folder, version_info) ) finally: @@ -111,8 +104,7 @@ class PipInstall(object): self.__dist_info = filename break else: - msg = "no .dist-info at {}, has {}".format(self._image_dir, ", ".join(files)) # pragma: no cover - raise RuntimeError(msg) # pragma: no cover + raise RuntimeError(f"no .dist-info at {self._image_dir}, has {', '.join(files)}") # pragma: no cover return self.__dist_info @abstractmethod @@ -127,10 +119,9 @@ class PipInstall(object): self._console_entry_points = {} entry_points = self._dist_info / "entry_points.txt" if entry_points.exists(): - parser = ConfigParser.ConfigParser() + parser = ConfigParser() with entry_points.open() as file_handler: - reader = getattr(parser, "read_file" if PY3 else "readfp") - reader(file_handler) + parser.read_file(file_handler) if "console_scripts" in parser.sections(): for name, value in parser.items("console_scripts"): match = re.match(r"(.*?)-?\d\.?\d*", name) @@ -142,7 +133,7 @@ class PipInstall(object): def _create_console_entry_point(self, name, value, to_folder, version_info): result = [] maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name) - specification = "{} = {}".format(name, value) + specification = f"{name} = {value}" new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result @@ -187,7 +178,7 @@ class PipInstall(object): class ScriptMakerCustom(ScriptMaker): def __init__(self, target_dir, version_info, executable, name): - super(ScriptMakerCustom, self).__init__(None, str(target_dir)) + super().__init__(None, str(target_dir)) self.clobber = True # overwrite self.set_mode = True # ensure they are executable self.executable = enquote_executable(str(executable)) @@ -196,5 +187,10 @@ class ScriptMakerCustom(ScriptMaker): self._name = name def _write_script(self, names, shebang, script_bytes, filenames, ext): - names.add("{}{}.{}".format(self._name, *self.version_info)) - super(ScriptMakerCustom, self)._write_script(names, shebang, script_bytes, filenames, ext) + names.add(f"{self._name}{self.version_info[0]}.{self.version_info[1]}") + super()._write_script(names, shebang, script_bytes, filenames, ext) + + +__all__ = [ + "PipInstall", +] diff --git a/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py b/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py index 29d0bc8..f5717e1 100644 --- a/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py +++ b/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py @@ -1,9 +1,7 @@ -from __future__ import absolute_import, unicode_literals - import os +from pathlib import Path -from virtualenv.util.path import Path, copy -from virtualenv.util.six import ensure_text +from virtualenv.util.path import copy from .base import PipInstall @@ -14,22 +12,27 @@ class CopyPipInstall(PipInstall): def _generate_new_files(self): # create the pyc files - new_files = super(CopyPipInstall, self)._generate_new_files() + new_files = super()._generate_new_files() new_files.update(self._cache_files()) return new_files def _cache_files(self): version = self._creator.interpreter.version_info - py_c_ext = ".{}-{}{}.pyc".format(self._creator.interpreter.implementation.lower(), version.major, version.minor) - for root, dirs, files in os.walk(ensure_text(str(self._image_dir)), topdown=True): + py_c_ext = f".{self._creator.interpreter.implementation.lower()}-{version.major}{version.minor}.pyc" + for root, dirs, files in os.walk(str(self._image_dir), topdown=True): root_path = Path(root) for name in files: if name.endswith(".py"): - yield root_path / "{}{}".format(name[:-3], py_c_ext) + yield root_path / f"{name[:-3]}{py_c_ext}" for name in dirs: yield root_path / name / "__pycache__" def _fix_records(self, new_files): extra_record_data_str = self._records_text(new_files) - with open(ensure_text(str(self._dist_info / "RECORD")), "ab") as file_handler: + with (self._dist_info / "RECORD").open("ab") as file_handler: file_handler.write(extra_record_data_str.encode("utf-8")) + + +__all__ = [ + "CopyPipInstall", +] diff --git a/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py b/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py index f958b65..4695de5 100644 --- a/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py +++ b/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py @@ -1,29 +1,20 @@ -from __future__ import absolute_import, unicode_literals - import os -import subprocess from stat import S_IREAD, S_IRGRP, S_IROTH +from subprocess import PIPE, Popen from virtualenv.util.path import safe_delete, set_tree -from virtualenv.util.six import ensure_text -from virtualenv.util.subprocess import Popen from .base import PipInstall class SymlinkPipInstall(PipInstall): def _sync(self, src, dst): - src_str = ensure_text(str(src)) - dest_str = ensure_text(str(dst)) - os.symlink(src_str, dest_str) + os.symlink(str(src), str(dst)) def _generate_new_files(self): # create the pyc files, as the build image will be R/O - process = Popen( - [ensure_text(str(self._creator.exe)), "-m", "compileall", ensure_text(str(self._image_dir))], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + cmd = [str(self._creator.exe), "-m", "compileall", str(self._image_dir)] + process = Popen(cmd, stdout=PIPE, stderr=PIPE) process.communicate() # the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close root_py_cache = self._image_dir / "__pycache__" @@ -32,7 +23,7 @@ class SymlinkPipInstall(PipInstall): new_files.update(root_py_cache.iterdir()) new_files.add(root_py_cache) safe_delete(root_py_cache) - core_new_files = super(SymlinkPipInstall, self)._generate_new_files() + core_new_files = super()._generate_new_files() # remove files that are within the image folder deeper than one level (as these will be not linked directly) for file in core_new_files: try: @@ -47,15 +38,20 @@ class SymlinkPipInstall(PipInstall): def _fix_records(self, new_files): new_files.update(i for i in self._image_dir.iterdir()) extra_record_data_str = self._records_text(sorted(new_files, key=str)) - with open(ensure_text(str(self._dist_info / "RECORD")), "wb") as file_handler: + with open(str(self._dist_info / "RECORD"), "wb") as file_handler: file_handler.write(extra_record_data_str.encode("utf-8")) def build_image(self): - super(SymlinkPipInstall, self).build_image() + super().build_image() # protect the image by making it read only set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH) def clear(self): if self._image_dir.exists(): safe_delete(self._image_dir) - super(SymlinkPipInstall, self).clear() + super().clear() + + +__all__ = [ + "SymlinkPipInstall", +] diff --git a/src/virtualenv/seed/embed/via_app_data/via_app_data.py b/src/virtualenv/seed/embed/via_app_data/via_app_data.py index 9a98a70..743ed15 100644 --- a/src/virtualenv/seed/embed/via_app_data/via_app_data.py +++ b/src/virtualenv/seed/embed/via_app_data/via_app_data.py @@ -1,17 +1,16 @@ """Bootstrap""" -from __future__ import absolute_import, unicode_literals import logging import sys import traceback from contextlib import contextmanager +from pathlib import Path from subprocess import CalledProcessError from threading import Lock, Thread from virtualenv.info import fs_supports_symlink from virtualenv.seed.embed.base_embed import BaseEmbed from virtualenv.seed.wheels import get_wheel -from virtualenv.util.path import Path from .pip_install.copy import CopyPipInstall from .pip_install.symlink import SymlinkPipInstall @@ -19,20 +18,19 @@ from .pip_install.symlink import SymlinkPipInstall class FromAppData(BaseEmbed): def __init__(self, options): - super(FromAppData, self).__init__(options) + super().__init__(options) self.symlinks = options.symlink_app_data @classmethod def add_parser_arguments(cls, parser, interpreter, app_data): - super(FromAppData, cls).add_parser_arguments(parser, interpreter, app_data) + super().add_parser_arguments(parser, interpreter, app_data) can_symlink = app_data.transient is False and fs_supports_symlink() + sym = "" if can_symlink else "not supported - " parser.add_argument( "--symlink-app-data", dest="symlink_app_data", action="store_true" if can_symlink else "store_false", - help="{} symlink the python packages from the app-data folder (requires seed pip>=19.3)".format( - "" if can_symlink else "not supported - ", - ), + help=f"{sym} symlink the python packages from the app-data folder (requires seed pip>=19.3)", default=False, ) @@ -64,7 +62,7 @@ class FromAppData(BaseEmbed): for thread in threads: thread.join() if exceptions: - messages = ["failed to build image {} because:".format(", ".join(exceptions.keys()))] + messages = [f"failed to build image {', '.join(exceptions.keys())} because:"] for value in exceptions.values(): exc_type, exc_value, exc_traceback = value messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback))) @@ -98,11 +96,11 @@ class FromAppData(BaseEmbed): failure = exception if failure: if isinstance(failure, CalledProcessError): - msg = "failed to download {}".format(distribution) + msg = f"failed to download {distribution}" if version is not None: - msg += " version {}".format(version) - msg += ", pip download exit code {}".format(failure.returncode) - output = failure.output if sys.version_info < (3, 5) else (failure.output + failure.stderr) + msg += f" version {version}" + msg += f", pip download exit code {failure.returncode}" + output = failure.output + failure.stderr if output: msg += "\n" msg += output @@ -124,7 +122,7 @@ class FromAppData(BaseEmbed): for thread in threads: thread.join() if fail: - raise RuntimeError("seed failed due to failing to download wheels {}".format(", ".join(fail.keys()))) + raise RuntimeError(f"seed failed due to failing to download wheels {', '.join(fail.keys())}") yield name_to_whl def installer_class(self, pip_version_tuple): @@ -134,7 +132,12 @@ class FromAppData(BaseEmbed): return SymlinkPipInstall return CopyPipInstall - def __unicode__(self): - base = super(FromAppData, self).__unicode__() - msg = ", via={}, app_data_dir={}".format("symlink" if self.symlinks else "copy", self.app_data) - return base[:-1] + msg + base[-1] + def __repr__(self): + msg = f", via={'symlink' if self.symlinks else 'copy'}, app_data_dir={self.app_data}" + base = super().__repr__() + return f"{base[:-1]}{msg}{base[-1]}" + + +__all__ = [ + "FromAppData", +] diff --git a/src/virtualenv/seed/seeder.py b/src/virtualenv/seed/seeder.py index 852e852..a14e483 100644 --- a/src/virtualenv/seed/seeder.py +++ b/src/virtualenv/seed/seeder.py @@ -1,15 +1,9 @@ -from __future__ import absolute_import, unicode_literals - from abc import ABCMeta, abstractmethod -from six import add_metaclass - -@add_metaclass(ABCMeta) -class Seeder(object): +class Seeder(metaclass=ABCMeta): """A seeder will install some seed packages into a virtual environment.""" - # noinspection PyUnusedLocal def __init__(self, options, enabled): """ @@ -38,3 +32,8 @@ class Seeder(object): virtual environment """ raise NotImplementedError + + +__all__ = [ + "Seeder", +] diff --git a/src/virtualenv/seed/wheels/__init__.py b/src/virtualenv/seed/wheels/__init__.py index dbffe2e..c563181 100644 --- a/src/virtualenv/seed/wheels/__init__.py +++ b/src/virtualenv/seed/wheels/__init__.py @@ -1,11 +1,9 @@ -from __future__ import absolute_import, unicode_literals - from .acquire import get_wheel, pip_wheel_env_run from .util import Version, Wheel -__all__ = ( +__all__ = [ "get_wheel", "pip_wheel_env_run", "Version", "Wheel", -) +] diff --git a/src/virtualenv/seed/wheels/acquire.py b/src/virtualenv/seed/wheels/acquire.py index 3744026..d8e8d1e 100644 --- a/src/virtualenv/seed/wheels/acquire.py +++ b/src/virtualenv/seed/wheels/acquire.py @@ -1,13 +1,10 @@ """Bootstrap""" -from __future__ import absolute_import, unicode_literals import logging import sys from operator import eq, lt - -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_str -from virtualenv.util.subprocess import Popen, subprocess +from pathlib import Path +from subprocess import PIPE, CalledProcessError, Popen from .bundle import from_bundle from .periodic_update import add_wheel_to_update_log @@ -43,7 +40,7 @@ def get_wheel(distribution, version, for_py_version, search_dirs, download, app_ def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_data, to_folder, env): - to_download = "{}{}".format(distribution, version_spec or "") + to_download = f"{distribution}{version_spec or ''}" logging.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder) cmd = [ sys.executable, @@ -63,15 +60,11 @@ def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_ ] # pip has no interface in python - must be a new sub-process env = pip_wheel_env_run(search_dirs, app_data, env) - process = Popen(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = process.communicate() if process.returncode != 0: - kwargs = {"output": out} - if sys.version_info < (3, 5): - kwargs["output"] += err - else: - kwargs["stderr"] = err - raise subprocess.CalledProcessError(process.returncode, cmd, **kwargs) + kwargs = {"output": out, "stderr": err} + raise CalledProcessError(process.returncode, cmd, **kwargs) result = _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out) logging.debug("downloaded wheel %s", result.name) return result @@ -83,7 +76,7 @@ def _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder for marker in ("Saved ", "File was already downloaded "): if line.startswith(marker): return Wheel(Path(line[len(marker) :]).absolute()) - # if for some reason the output does not match fallback to latest version with that spec + # if for some reason the output does not match fallback to the latest version with that spec return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder) @@ -104,18 +97,12 @@ def find_compatible_in_house(distribution, version_spec, for_py_version, in_fold def pip_wheel_env_run(search_dirs, app_data, env): - for_py_version = "{}.{}".format(*sys.version_info[0:2]) env = env.copy() - env.update( - { - ensure_str(k): str(v) # python 2 requires these to be string only (non-unicode) - for k, v in {"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1"}.items() - }, - ) + env.update({"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1"}) wheel = get_wheel( distribution="pip", version=None, - for_py_version=for_py_version, + for_py_version=f"{sys.version_info.major}.{sys.version_info.minor}", search_dirs=search_dirs, download=False, app_data=app_data, @@ -124,5 +111,12 @@ def pip_wheel_env_run(search_dirs, app_data, env): ) if wheel is None: raise RuntimeError("could not find the embedded pip") - env[str("PYTHONPATH")] = str(wheel.path) + env["PYTHONPATH"] = str(wheel.path) return env + + +__all__ = [ + "get_wheel", + "download_wheel", + "pip_wheel_env_run", +] diff --git a/src/virtualenv/seed/wheels/bundle.py b/src/virtualenv/seed/wheels/bundle.py index 39cd3d3..66bbe56 100644 --- a/src/virtualenv/seed/wheels/bundle.py +++ b/src/virtualenv/seed/wheels/bundle.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from ..wheels.embed import get_embed_wheel from .periodic_update import periodic_update from .util import Version, Wheel, discover_wheels @@ -15,9 +13,8 @@ def from_bundle(distribution, version, for_py_version, search_dirs, app_data, do if version != Version.embed: # 2. check if we have upgraded embed if app_data.can_update: - wheel = periodic_update( - distribution, of_version, for_py_version, wheel, search_dirs, app_data, do_periodic_update, env - ) + per = do_periodic_update + wheel = periodic_update(distribution, of_version, for_py_version, wheel, search_dirs, app_data, per, env) # 3. acquire from extra search dir found_wheel = from_dir(distribution, of_version, for_py_version, search_dirs) @@ -49,3 +46,9 @@ def from_dir(distribution, version, for_py_version, directories): for wheel in discover_wheels(folder, distribution, version, for_py_version): return wheel return None + + +__all__ = [ + "load_embed_wheel", + "from_bundle", +] diff --git a/src/virtualenv/seed/wheels/embed/__init__.py b/src/virtualenv/seed/wheels/embed/__init__.py index 3ae80b5..3062176 100644 --- a/src/virtualenv/seed/wheels/embed/__init__.py +++ b/src/virtualenv/seed/wheels/embed/__init__.py @@ -1,7 +1,6 @@ -from __future__ import absolute_import, unicode_literals +from pathlib import Path from virtualenv.seed.wheels.util import Wheel -from virtualenv.util.path import Path BUNDLE_FOLDER = Path(__file__).absolute().parent BUNDLE_SUPPORT = { @@ -54,9 +53,9 @@ def get_embed_wheel(distribution, for_py_version): return Wheel.from_path(path) -__all__ = ( +__all__ = [ "get_embed_wheel", "BUNDLE_SUPPORT", "MAX", "BUNDLE_FOLDER", -) +] diff --git a/src/virtualenv/seed/wheels/periodic_update.py b/src/virtualenv/seed/wheels/periodic_update.py index 4f0336b..ec95bf5 100644 --- a/src/virtualenv/seed/wheels/periodic_update.py +++ b/src/virtualenv/seed/wheels/periodic_update.py @@ -2,40 +2,28 @@ Periodically update bundled versions. """ -from __future__ import absolute_import, unicode_literals import json import logging import os import ssl -import subprocess import sys from datetime import datetime, timedelta from itertools import groupby +from pathlib import Path from shutil import copy2 +from subprocess import PIPE, Popen from textwrap import dedent from threading import Thread - -from six.moves.urllib.error import URLError -from six.moves.urllib.request import urlopen +from urllib.error import URLError +from urllib.request import urlopen from virtualenv.app_data import AppDataDiskFolder -from virtualenv.info import PY2 -from virtualenv.util.path import Path -from virtualenv.util.subprocess import CREATE_NO_WINDOW, Popen +from virtualenv.util.subprocess import CREATE_NO_WINDOW from ..wheels.embed import BUNDLE_SUPPORT from ..wheels.util import Wheel -if PY2: - # on Python 2 datetime.strptime throws the error below if the import did not trigger on main thread - # Failed to import _strptime because the import lock is held by - try: - import _strptime # noqa - except ImportError: # pragma: no cov - pass # pragma: no cov - - GRACE_PERIOD_CI = timedelta(hours=1) # prevent version switch in the middle of a CI run GRACE_PERIOD_MINOR = timedelta(days=28) UPDATE_PERIOD = timedelta(days=14) @@ -106,7 +94,7 @@ def load_datetime(value): return None if value is None else datetime.strptime(value, DATETIME_FMT) -class NewVersion(object): +class NewVersion: def __init__(self, filename, found_date, release_date, source): self.filename = filename self.found_date = found_date @@ -142,12 +130,9 @@ class NewVersion(object): return False def __repr__(self): - return "{}(filename={}), found_date={}, release_date={}, source={})".format( - self.__class__.__name__, - self.filename, - self.found_date, - self.release_date, - self.source, + return ( + f"{self.__class__.__name__}(filename={self.filename}), found_date={self.found_date}, " + f"release_date={self.release_date}, source={self.source})" ) def __eq__(self, other): @@ -163,7 +148,7 @@ class NewVersion(object): return Wheel(Path(self.filename)) -class UpdateLog(object): +class UpdateLog: def __init__(self, started, completed, versions, periodic): self.started = started self.completed = completed @@ -224,8 +209,8 @@ def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, e .strip() .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic), ] - debug = env.get(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE")) == str("1") - pipe = None if debug else subprocess.PIPE + debug = env.get("_VIRTUALENV_PERIODIC_UPDATE_INLINE") == "1" + pipe = None if debug else PIPE kwargs = {"stdout": pipe, "stderr": pipe} if not debug and sys.platform == "win32": kwargs["creationflags"] = CREATE_NO_WINDOW @@ -233,7 +218,7 @@ def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, e logging.info( "triggered periodic upgrade of %s%s (for python %s) via background process having PID %d", distribution, - "" if wheel is None else "=={}".format(wheel.version), + "" if wheel is None else f"=={wheel.version}", for_py_version, process.pid, ) @@ -286,7 +271,7 @@ def _run_do_update(app_data, distribution, embed_filename, for_py_version, perio download_time = datetime.now() dest = acquire.download_wheel( distribution=distribution, - version_spec=None if last_version is None else "<{}".format(last_version), + version_spec=None if last_version is None else f"<{last_version}", for_py_version=for_py_version, search_dirs=search_dirs, app_data=app_data, @@ -346,7 +331,7 @@ def _pypi_get_distribution_info_cached(distribution): def _pypi_get_distribution_info(distribution): - content, url = None, "https://pypi.org/pypi/{}/json".format(distribution) + content, url = None, f"https://pypi.org/pypi/{distribution}/json" try: for context in _request_context(): try: @@ -401,20 +386,19 @@ def _run_manual_upgrade(app_data, distribution, for_py_version, env): search_dirs=[], periodic=False, ) - msg = "upgraded %s for python %s in %s {}".format( - "new entries found:\n%s" if versions else "no new versions found", - ) + args = [ distribution, for_py_version, datetime.now() - start, ] if versions: - args.append("\n".join("\t{}".format(v) for v in versions)) - logging.warning(msg, *args) + args.append("\n".join(f"\t{v}" for v in versions)) + ver_update = "new entries found:\n%s" if versions else "no new versions found" + logging.warning(f"upgraded %s for python %s in %s {ver_update}", *args) -__all__ = ( +__all__ = [ "add_wheel_to_update_log", "periodic_update", "do_update", @@ -425,4 +409,4 @@ __all__ = ( "dump_datetime", "trigger_update", "release_date_for_wheel_path", -) +] diff --git a/src/virtualenv/seed/wheels/util.py b/src/virtualenv/seed/wheels/util.py index 1240eb2..f09d873 100644 --- a/src/virtualenv/seed/wheels/util.py +++ b/src/virtualenv/seed/wheels/util.py @@ -1,12 +1,8 @@ -from __future__ import absolute_import, unicode_literals - from operator import attrgetter from zipfile import ZipFile -from virtualenv.util.six import ensure_text - -class Wheel(object): +class Wheel: def __init__(self, path): # https://www.python.org/dev/peps/pep-0427/#file-name-convention # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl @@ -48,8 +44,8 @@ class Wheel(object): return self.path.name def support_py(self, py_version): - name = "{}.dist-info/METADATA".format("-".join(self.path.stem.split("-")[0:2])) - with ZipFile(ensure_text(str(self.path)), "r") as zip_file: + name = f"{'-'.join(self.path.stem.split('-')[0:2])}.dist-info/METADATA" + with ZipFile(str(self.path), "r") as zip_file: metadata = zip_file.read(name).decode("utf-8") marker = "Requires-Python:" requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None) @@ -75,7 +71,7 @@ class Wheel(object): return True def __repr__(self): - return "{}({})".format(self.__class__.__name__, self.path) + return f"{self.__class__.__name__}({self.path})" def __str__(self): return str(self.path) @@ -97,10 +93,7 @@ class Version: bundle = "bundle" embed = "embed" #: custom version handlers - non_version = ( - bundle, - embed, - ) + non_version = (bundle, embed) @staticmethod def of_version(value): @@ -108,9 +101,16 @@ class Version: @staticmethod def as_pip_req(distribution, version): - return "{}{}".format(distribution, Version.as_version_spec(version)) + return f"{distribution}{Version.as_version_spec(version)}" @staticmethod def as_version_spec(version): of_version = Version.of_version(version) - return "" if of_version is None else "=={}".format(of_version) + return "" if of_version is None else f"=={of_version}" + + +__all__ = [ + "discover_wheels", + "Version", + "Wheel", +] diff --git a/src/virtualenv/util/__init__.py b/src/virtualenv/util/__init__.py index 32d0292..e69de29 100644 --- a/src/virtualenv/util/__init__.py +++ b/src/virtualenv/util/__init__.py @@ -1,11 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import sys - -if sys.version_info[0] == 3: - import configparser as ConfigParser -else: - import ConfigParser - - -__all__ = ("ConfigParser",) diff --git a/src/virtualenv/util/error.py b/src/virtualenv/util/error.py index ac5aa50..945a25e 100644 --- a/src/virtualenv/util/error.py +++ b/src/virtualenv/util/error.py @@ -1,12 +1,11 @@ """Errors""" -from __future__ import absolute_import, unicode_literals class ProcessCallFailed(RuntimeError): """Failed a process call""" def __init__(self, code, out, err, cmd): - super(ProcessCallFailed, self).__init__(code, out, err, cmd) + super().__init__(code, out, err, cmd) self.code = code self.out = out self.err = err diff --git a/src/virtualenv/util/lock.py b/src/virtualenv/util/lock.py index 1cf968b..04c9f9c 100644 --- a/src/virtualenv/util/lock.py +++ b/src/virtualenv/util/lock.py @@ -1,16 +1,13 @@ """holds locking functionality that works across processes""" -from __future__ import absolute_import, unicode_literals import logging import os from abc import ABCMeta, abstractmethod from contextlib import contextmanager +from pathlib import Path from threading import Lock, RLock from filelock import FileLock, Timeout -from six import add_metaclass - -from virtualenv.util.path import Path class _CountedFileLock(FileLock): @@ -21,20 +18,20 @@ class _CountedFileLock(FileLock): os.makedirs(parent) except OSError: pass - super(_CountedFileLock, self).__init__(lock_file) + super().__init__(lock_file) self.count = 0 self.thread_safe = RLock() def acquire(self, timeout=None, poll_interval=0.05): with self.thread_safe: if self.count == 0: - super(_CountedFileLock, self).acquire(timeout, poll_interval) + super().acquire(timeout, poll_interval) self.count += 1 def release(self, force=False): with self.thread_safe: if self.count == 1: - super(_CountedFileLock, self).release(force=force) + super().release(force=force) self.count = max(self.count - 1, 0) @@ -42,14 +39,13 @@ _lock_store = {} _store_lock = Lock() -@add_metaclass(ABCMeta) -class PathLockBase(object): +class PathLockBase(metaclass=ABCMeta): def __init__(self, folder): path = Path(folder) self.path = path.resolve() if path.exists() else path def __repr__(self): - return "{}({})".format(self.__class__.__name__, self.path) + return f"{self.__class__.__name__}({self.path})" def __div__(self, other): return type(self)(self.path / other) @@ -78,11 +74,11 @@ class PathLockBase(object): class ReentrantFileLock(PathLockBase): def __init__(self, folder): - super(ReentrantFileLock, self).__init__(folder) + super().__init__(folder) self._lock = None def _create_lock(self, name=""): - lock_file = str(self.path / "{}.lock".format(name)) + lock_file = str(self.path / f"{name}.lock") with _store_lock: if lock_file not in _lock_store: _lock_store[lock_file] = _CountedFileLock(lock_file) @@ -144,7 +140,7 @@ class ReentrantFileLock(PathLockBase): @contextmanager def non_reentrant_lock_for_key(self, name): - with _CountedFileLock(str(self.path / "{}.lock".format(name))): + with _CountedFileLock(str(self.path / f"{name}.lock")): yield @@ -164,8 +160,8 @@ class NoOpFileLock(PathLockBase): yield -__all__ = ( +__all__ = [ "NoOpFileLock", "ReentrantFileLock", "Timeout", -) +] diff --git a/src/virtualenv/util/path/__init__.py b/src/virtualenv/util/path/__init__.py index dc628de..39a8db7 100644 --- a/src/virtualenv/util/path/__init__.py +++ b/src/virtualenv/util/path/__init__.py @@ -1,18 +1,14 @@ -from __future__ import absolute_import, unicode_literals - -from ._pathlib import Path from ._permission import make_exe, set_tree from ._sync import copy, copytree, ensure_dir, safe_delete, symlink from ._win import get_short_path_name -__all__ = ( +__all__ = [ "ensure_dir", "symlink", "copy", "copytree", - "Path", "make_exe", "set_tree", "safe_delete", "get_short_path_name", -) +] diff --git a/src/virtualenv/util/path/_pathlib/__init__.py b/src/virtualenv/util/path/_pathlib/__init__.py deleted file mode 100644 index 746c8ae..0000000 --- a/src/virtualenv/util/path/_pathlib/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import sys - -import six - -if six.PY3: - from pathlib import Path -else: - if sys.platform == "win32": - # workaround for https://github.com/mcmtroffaes/pathlib2/issues/56 - from .via_os_path import Path - else: - from pathlib2 import Path - - -__all__ = ("Path",) diff --git a/src/virtualenv/util/path/_pathlib/via_os_path.py b/src/virtualenv/util/path/_pathlib/via_os_path.py deleted file mode 100644 index c2b8a0d..0000000 --- a/src/virtualenv/util/path/_pathlib/via_os_path.py +++ /dev/null @@ -1,160 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import fnmatch -import os -import platform -from contextlib import contextmanager - -from virtualenv.util.six import ensure_str, ensure_text - -IS_PYPY = platform.python_implementation() == "PyPy" - - -class Path(object): - def __init__(self, path=""): - if isinstance(path, Path): - _path = path._path - else: - _path = ensure_text(path) - if IS_PYPY: - _path = _path.encode("utf-8") - self._path = _path - - def __repr__(self): - return ensure_str("Path({})".format(ensure_text(self._path))) - - def __unicode__(self): - return ensure_text(self._path) - - def __str__(self): - return ensure_str(self._path) - - def __div__(self, other): - if isinstance(other, Path): - right = other._path - else: - right = ensure_text(other) - if IS_PYPY: - right = right.encode("utf-8") - return Path(os.path.join(self._path, right)) - - def __truediv__(self, other): - return self.__div__(other) - - def __eq__(self, other): - return self._path == (other._path if isinstance(other, Path) else None) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self._path) - - def as_posix(self): - return str(self).replace(os.sep, "/") - - def exists(self): - return os.path.exists(self._path) - - @property - def parent(self): - return Path(os.path.abspath(os.path.join(self._path, os.path.pardir))) - - def resolve(self): - return Path(os.path.realpath(self._path)) - - @property - def name(self): - return os.path.basename(self._path) - - @property - def parts(self): - return self._path.split(os.sep) - - def is_file(self): - return os.path.isfile(self._path) - - def is_dir(self): - return os.path.isdir(self._path) - - def mkdir(self, parents=True, exist_ok=True): - try: - os.makedirs(self._path) - except OSError: - if not exist_ok: - raise - - def read_text(self, encoding="utf-8"): - return self.read_bytes().decode(encoding) - - def read_bytes(self): - with open(self._path, "rb") as file_handler: - return file_handler.read() - - def write_bytes(self, content): - with open(self._path, "wb") as file_handler: - file_handler.write(content) - - def write_text(self, text, encoding="utf-8"): - self.write_bytes(text.encode(encoding)) - - def iterdir(self): - for p in os.listdir(self._path): - yield Path(os.path.join(self._path, p)) - - @property - def suffix(self): - _, ext = os.path.splitext(self.name) - return ext - - @property - def stem(self): - base, _ = os.path.splitext(self.name) - return base - - @contextmanager - def open(self, mode="r"): - with open(self._path, mode) as file_handler: - yield file_handler - - @property - def parents(self): - result = [] - parts = self.parts - for i in range(len(parts) - 1): - result.append(Path(os.sep.join(parts[0 : i + 1]))) - return result[::-1] - - def unlink(self): - os.remove(self._path) - - def with_name(self, name): - return self.parent / name - - def is_symlink(self): - return os.path.islink(self._path) - - def relative_to(self, other): - if not self._path.startswith(other._path): - raise ValueError("{} does not start with {}".format(self._path, other._path)) - return Path(os.sep.join(self.parts[len(other.parts) :])) - - def stat(self): - return os.stat(self._path) - - def chmod(self, mode): - os.chmod(self._path, mode) - - def absolute(self): - return Path(os.path.abspath(self._path)) - - def rglob(self, pattern): - """ - Rough emulation of the origin method. Just for searching fixture files. - """ - for root, _dirs, files in os.walk(self._path): - for filename in fnmatch.filter(files, pattern): - yield Path(os.path.join(root, filename)) - - -__all__ = ("Path",) diff --git a/src/virtualenv/util/path/_permission.py b/src/virtualenv/util/path/_permission.py index 73bb6e8..ca92314 100644 --- a/src/virtualenv/util/path/_permission.py +++ b/src/virtualenv/util/path/_permission.py @@ -1,10 +1,6 @@ -from __future__ import absolute_import, unicode_literals - import os from stat import S_IXGRP, S_IXOTH, S_IXUSR -from virtualenv.util.six import ensure_text - def make_exe(filename): original_mode = filename.stat().st_mode @@ -21,7 +17,7 @@ def make_exe(filename): def set_tree(folder, stat): - for root, _, files in os.walk(ensure_text(str(folder))): + for root, _, files in os.walk(str(folder)): for filename in files: os.chmod(os.path.join(root, filename), stat) diff --git a/src/virtualenv/util/path/_sync.py b/src/virtualenv/util/path/_sync.py index 05f19d0..10c793d 100644 --- a/src/virtualenv/util/path/_sync.py +++ b/src/virtualenv/util/path/_sync.py @@ -1,33 +1,18 @@ -from __future__ import absolute_import, unicode_literals - import logging import os import shutil from stat import S_IWUSR -from six import PY2 - -from virtualenv.info import IS_CPYTHON, IS_WIN -from virtualenv.util.six import ensure_text - -if PY2 and IS_CPYTHON and IS_WIN: # CPython2 on Windows supports unicode paths if passed as unicode - - def norm(src): - return ensure_text(str(src)) - -else: - norm = str - def ensure_dir(path): if not path.exists(): - logging.debug("create folder %s", ensure_text(str(path))) - os.makedirs(norm(path)) + logging.debug("create folder %s", str(path)) + os.makedirs(str(path)) def ensure_safe_to_do(src, dest): if src == dest: - raise ValueError("source and destination is the same {}".format(src)) + raise ValueError(f"source and destination is the same {src}") if not dest.exists(): return if dest.is_dir() and not dest.is_symlink(): @@ -49,7 +34,7 @@ def copy(src, dest): is_dir = src.is_dir() method = copytree if is_dir else shutil.copy logging.debug("copy %s", _Debug(src, dest)) - method(norm(src), norm(dest)) + method(str(src), str(dest)) def copytree(src, dest): @@ -71,27 +56,23 @@ def safe_delete(dest): else: raise - shutil.rmtree(ensure_text(str(dest)), ignore_errors=True, onerror=onerror) + shutil.rmtree(str(dest), ignore_errors=True, onerror=onerror) -class _Debug(object): +class _Debug: def __init__(self, src, dest): self.src = src self.dest = dest def __str__(self): - return "{}{} to {}".format( - "directory " if self.src.is_dir() else "", - ensure_text(str(self.src)), - ensure_text(str(self.dest)), - ) + return f"{'directory ' if self.src.is_dir() else ''}{str(self.src)} to {str(self.dest)}" -__all__ = ( +__all__ = [ "ensure_dir", "symlink", "copy", "symlink", "copytree", "safe_delete", -) +] diff --git a/src/virtualenv/util/path/_win.py b/src/virtualenv/util/path/_win.py index 02e16d0..8dd81c9 100644 --- a/src/virtualenv/util/path/_win.py +++ b/src/virtualenv/util/path/_win.py @@ -17,3 +17,8 @@ def get_short_path_name(long_name): return output_buf.value else: output_buf_size = needed + + +__all__ = [ + "get_short_path_name", +] diff --git a/src/virtualenv/util/six.py b/src/virtualenv/util/six.py deleted file mode 100644 index 199cbed..0000000 --- a/src/virtualenv/util/six.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Backward compatibility layer with older version of six. - -This is used to avoid virtualenv requiring a version of six newer than what -the system may have. -""" -from __future__ import absolute_import - -from six import PY2, PY3, binary_type, text_type - -try: - from six import ensure_text -except ImportError: - - def ensure_text(s, encoding="utf-8", errors="strict"): - """Coerce *s* to six.text_type. - For Python 2: - - `unicode` -> `unicode` - - `str` -> `unicode` - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if isinstance(s, binary_type): - return s.decode(encoding, errors) - elif isinstance(s, text_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) - - -try: - from six import ensure_str -except ImportError: - - def ensure_str(s, encoding="utf-8", errors="strict"): - """Coerce *s* to `str`. - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if not isinstance(s, (text_type, binary_type)): - raise TypeError("not expecting type '%s'" % type(s)) - if PY2 and isinstance(s, text_type): - s = s.encode(encoding, errors) - elif PY3 and isinstance(s, binary_type): - s = s.decode(encoding, errors) - return s diff --git a/src/virtualenv/util/subprocess/__init__.py b/src/virtualenv/util/subprocess/__init__.py index 9f6ffd7..bc6ec4d 100644 --- a/src/virtualenv/util/subprocess/__init__.py +++ b/src/virtualenv/util/subprocess/__init__.py @@ -1,24 +1,11 @@ -from __future__ import absolute_import, unicode_literals - import subprocess -import sys - -import six - -if six.PY2 and sys.platform == "win32": - from . import _win_subprocess - - Popen = _win_subprocess.Popen -else: - Popen = subprocess.Popen - CREATE_NO_WINDOW = 0x80000000 def run_cmd(cmd): try: - process = Popen( + process = subprocess.Popen( cmd, universal_newlines=True, stdin=subprocess.PIPE, @@ -30,14 +17,11 @@ def run_cmd(cmd): except OSError as error: code, out, err = error.errno, "", error.strerror if code == 2 and "file" in err: - # FileNotFoundError in Python >= 3.3 - err = str(error) + err = str(error) # FileNotFoundError in Python >= 3.3 return code, out, err __all__ = ( - "subprocess", - "Popen", "run_cmd", "CREATE_NO_WINDOW", ) diff --git a/src/virtualenv/util/subprocess/_win_subprocess.py b/src/virtualenv/util/subprocess/_win_subprocess.py deleted file mode 100644 index ce53197..0000000 --- a/src/virtualenv/util/subprocess/_win_subprocess.py +++ /dev/null @@ -1,176 +0,0 @@ -# flake8: noqa -# fmt: off -## issue: https://bugs.python.org/issue19264 - -import ctypes -import os -import platform -import subprocess -from ctypes import Structure, WinError, byref, c_char_p, c_void_p, c_wchar, c_wchar_p, sizeof, windll -from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR, WORD - -import _subprocess - -## -## Types -## - -CREATE_UNICODE_ENVIRONMENT = 0x00000400 -LPCTSTR = c_char_p -LPTSTR = c_wchar_p -LPSECURITY_ATTRIBUTES = c_void_p -LPBYTE = ctypes.POINTER(BYTE) - -class STARTUPINFOW(Structure): - _fields_ = [ - ("cb", DWORD), ("lpReserved", LPWSTR), - ("lpDesktop", LPWSTR), ("lpTitle", LPWSTR), - ("dwX", DWORD), ("dwY", DWORD), - ("dwXSize", DWORD), ("dwYSize", DWORD), - ("dwXCountChars", DWORD), ("dwYCountChars", DWORD), - ("dwFillAtrribute", DWORD), ("dwFlags", DWORD), - ("wShowWindow", WORD), ("cbReserved2", WORD), - ("lpReserved2", LPBYTE), ("hStdInput", HANDLE), - ("hStdOutput", HANDLE), ("hStdError", HANDLE), - ] - -LPSTARTUPINFOW = ctypes.POINTER(STARTUPINFOW) - - -class PROCESS_INFORMATION(Structure): - _fields_ = [ - ("hProcess", HANDLE), ("hThread", HANDLE), - ("dwProcessId", DWORD), ("dwThreadId", DWORD), - ] - -LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION) - - -class DUMMY_HANDLE(ctypes.c_void_p): - - def __init__(self, *a, **kw): - super(DUMMY_HANDLE, self).__init__(*a, **kw) - self.closed = False - - def Close(self): - if not self.closed: - windll.kernel32.CloseHandle(self) - self.closed = True - - def __int__(self): - return self.value - - -CreateProcessW = windll.kernel32.CreateProcessW -CreateProcessW.argtypes = [ - LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, - LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, - LPSTARTUPINFOW, LPPROCESS_INFORMATION, -] -CreateProcessW.restype = BOOL - - -## -## Patched functions/classes -## - -def CreateProcess( - executable, args, _p_attr, _t_attr, - inherit_handles, creation_flags, env, cwd, - startup_info, -): - """Create a process supporting unicode executable and args for win32 - - Python implementation of CreateProcess using CreateProcessW for Win32 - - """ - - si = STARTUPINFOW( - dwFlags=startup_info.dwFlags, - wShowWindow=startup_info.wShowWindow, - cb=sizeof(STARTUPINFOW), - ## XXXvlab: not sure of the casting here to ints. - hStdInput=startup_info.hStdInput if startup_info.hStdInput is None else int(startup_info.hStdInput), - hStdOutput=startup_info.hStdOutput if startup_info.hStdOutput is None else int(startup_info.hStdOutput), - hStdError=startup_info.hStdError if startup_info.hStdError is None else int(startup_info.hStdError), - ) - - wenv = None - if env is not None: - ## LPCWSTR seems to be c_wchar_p, so let's say CWSTR is c_wchar - env = ( - unicode("").join([ - unicode("%s=%s\0") % (k, v) - for k, v in env.items() - ]) - ) + unicode("\0") - wenv = (c_wchar * len(env))() - wenv.value = env - - wcwd = None - if cwd is not None: - wcwd = unicode(cwd) - - pi = PROCESS_INFORMATION() - creation_flags |= CREATE_UNICODE_ENVIRONMENT - - if CreateProcessW( - executable, args, None, None, - inherit_handles, creation_flags, - wenv, wcwd, byref(si), byref(pi), - ): - return ( - DUMMY_HANDLE(pi.hProcess), DUMMY_HANDLE(pi.hThread), - pi.dwProcessId, pi.dwThreadId, - ) - raise WinError() - - -class Popen(subprocess.Popen): - """This superseeds Popen and corrects a bug in cPython 2.7 implem""" - - def _execute_child( - self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, to_close, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite, - ): - """Code from part of _execute_child from Python 2.7 (9fbb65e) - - There are only 2 little changes concerning the construction of - the the final string in shell mode: we preempt the creation of - the command string when shell is True, because original function - will try to encode unicode args which we want to avoid to be able to - sending it as-is to ``CreateProcess``. - - """ - if startupinfo is None: - startupinfo = subprocess.STARTUPINFO() - if not isinstance(args, subprocess.types.StringTypes): - args = [i if isinstance(i, bytes) else i.encode('utf-8') for i in args] - args = subprocess.list2cmdline(args) - if platform.python_implementation() == "CPython": - args = args.decode('utf-8') - startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = _subprocess.SW_HIDE - env = os.environ if env is None else env - comspec = env.get("COMSPEC", unicode("cmd.exe")) - if ( - _subprocess.GetVersion() >= 0x80000000 or - os.path.basename(comspec).lower() == "command.com" - ): - w9xpopen = self._find_w9xpopen() - args = unicode('"%s" %s') % (w9xpopen, args) - creationflags |= _subprocess.CREATE_NEW_CONSOLE - - super(Popen, self)._execute_child( - args, executable, - preexec_fn, close_fds, cwd, env, universal_newlines, - startupinfo, creationflags, False, to_close, p2cread, - p2cwrite, c2pread, c2pwrite, errread, errwrite, - ) - -_subprocess.CreateProcess = CreateProcess -# fmt: on diff --git a/src/virtualenv/util/zipapp.py b/src/virtualenv/util/zipapp.py index 85d9294..e7578c4 100644 --- a/src/virtualenv/util/zipapp.py +++ b/src/virtualenv/util/zipapp.py @@ -1,11 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import logging import os import zipfile from virtualenv.info import IS_WIN, ROOT -from virtualenv.util.six import ensure_text def read(full_path): @@ -21,7 +18,7 @@ def extract(full_path, dest): with zipfile.ZipFile(ROOT, "r") as zip_file: info = zip_file.getinfo(sub_file) info.filename = dest.name - zip_file.extract(info, ensure_text(str(dest.parent))) + zip_file.extract(info, str(dest.parent)) def _get_path_within_zip(full_path): @@ -31,3 +28,9 @@ def _get_path_within_zip(full_path): # paths are always UNIX separators, even on Windows, though __file__ still follows platform default sub_file = sub_file.replace(os.sep, "/") return sub_file + + +__all__ = [ + "read", + "extract", +] diff --git a/tasks/make_zipapp.py b/tasks/make_zipapp.py index 8ff2998..061cb53 100644 --- a/tasks/make_zipapp.py +++ b/tasks/make_zipapp.py @@ -11,20 +11,16 @@ import zipfile from collections import defaultdict, deque from email import message_from_string from pathlib import Path, PurePosixPath +from shlex import quote from stat import S_IWUSR from tempfile import TemporaryDirectory from packaging.markers import Marker from packaging.requirements import Requirement -if sys.version_info[0] == 2: - from pipes import quote -else: - from shlex import quote - HERE = Path(__file__).parent.absolute() -VERSIONS = ["3.{}".format(i) for i in range(10, 4, -1)] + ["2.7"] +VERSIONS = [f"3.{i}" for i in range(10, 4, -1)] + ["2.7"] def main(): @@ -50,7 +46,7 @@ def create_zipapp(dest, packages): zip_app.writestr("__main__.py", (HERE / "__main__zipapp.py").read_bytes()) bio.seek(0) zipapp.create_archive(bio, dest) - print("zipapp created at {}".format(dest)) + print(f"zipapp created at {dest}") def write_packages_to_zipapp(base, dist, modules, packages, zip_app): @@ -86,7 +82,7 @@ def write_packages_to_zipapp(base, dist, modules, packages, zip_app): del content -class WheelDownloader(object): +class WheelDownloader: def __init__(self, into): if into.exists(): shutil.rmtree(into) @@ -108,7 +104,7 @@ class WheelDownloader(object): whl = self._get_wheel(dep, platform[2:] if platform and platform.startswith("==") else None, version) if whl is None: if dep_str not in wheel_store: - raise RuntimeError("failed to get {}, have {}".format(dep_str, wheel_store)) + raise RuntimeError(f"failed to get {dep_str}, have {wheel_store}") whl = wheel_store[dep_str] else: wheel_store[dep_str] = whl @@ -143,7 +139,7 @@ class WheelDownloader(object): @staticmethod def get_dependencies(whl, version): with zipfile.ZipFile(str(whl), "r") as zip_file: - name = "/".join(["{}.dist-info".format("-".join(whl.name.split("-")[0:2])), "METADATA"]) + name = "/".join([f"{'-'.join(whl.name.split('-')[0:2])}.dist-info", "METADATA"]) with zip_file.open(name) as file_handler: metadata = message_from_string(file_handler.read().decode("utf-8")) deps = metadata.get_all("Requires-Dist") @@ -171,7 +167,7 @@ class WheelDownloader(object): platform_positions = WheelDownloader._marker_at(markers, "sys_platform") deleted = 0 for pos in platform_positions: # can only be ore meaningfully - platform = "{}{}".format(markers[pos][1].value, markers[pos][2].value) + platform = f"{markers[pos][1].value}{markers[pos][2].value}" deleted += WheelDownloader._del_marker_at(markers, pos - deleted) platforms.append(platform) if not platforms: @@ -231,7 +227,7 @@ def run_suppress_output(cmd, stop_print_on_fail=False): process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) out, err = process.communicate() if stop_print_on_fail and process.returncode != 0: - print("exit with {} of {}".format(process.returncode, " ".join(quote(i) for i in cmd)), file=sys.stdout) + print(f"exit with {process.returncode} of {' '.join(quote(i) for i in cmd)}", file=sys.stdout) if out: print(out, file=sys.stdout) if err: @@ -254,19 +250,19 @@ def get_wheels_for_support_versions(folder): wheel_versions.wheel = wheel for name, p_w_v in packages.items(): for platform, w_v in p_w_v.items(): - print("{} - {}".format(name, platform)) + print(f"{name} - {platform}") for wheel, wheel_versions in w_v.items(): - print("{} of {} (use {})".format(" ".join(wheel_versions.versions), wheel, wheel_versions.wheel)) + print(f"{' '.join(wheel_versions.versions)} of {wheel} (use {wheel_versions.wheel})") return packages -class WheelForVersion(object): +class WheelForVersion: def __init__(self, wheel=None, versions=None): self.wheel = wheel self.versions = versions if versions else [] def __repr__(self): - return "{}({!r}, {!r})".format(self.__class__.__name__, self.wheel, self.versions) + return f"{self.__class__.__name__}({self.wheel!r}, {self.versions!r})" if __name__ == "__main__": diff --git a/tasks/release.py b/tasks/release.py index fa797d3..8724bf2 100644 --- a/tasks/release.py +++ b/tasks/release.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Handles creating a release PR""" from pathlib import Path from subprocess import check_call @@ -60,9 +59,9 @@ def tag_release_commit(release_commit, repo, version) -> TagReference: print("tag release commit") existing_tags = [x.name for x in repo.tags] if version in existing_tags: - print("delete existing tag {}".format(version)) + print(f"delete existing tag {version}") repo.delete_tag(version) - print("create tag {}".format(version)) + print(f"create tag {version}") tag = repo.create_tag(version, ref=release_commit, force=True) return tag diff --git a/tasks/update_embedded.py b/tasks/update_embedded.py index 8144559..ba43b30 100755 --- a/tasks/update_embedded.py +++ b/tasks/update_embedded.py @@ -1,19 +1,10 @@ -#!/usr/bin/env python -""" -Helper script to rebuild virtualenv.py from virtualenv_support -""" -from __future__ import print_function, unicode_literals +"""Helper script to rebuild virtualenv.py from virtualenv_support""" import codecs import os import re -import sys from zlib import crc32 as _crc32 -if sys.version_info < (3,): - print("requires Python 3 (use tox from Python 3 if invoked via tox)") - raise SystemExit(1) - def crc32(data): """Python version idempotent""" @@ -31,7 +22,7 @@ file_template = '# file {filename}\n{variable} = convert(\n """\n{data}"""\n) def rebuild(script_path): - with open(script_path, "rt") as current_fh: + with open(script_path) as current_fh: script_content = current_fh.read() script_parts = [] match_end = 0 @@ -53,21 +44,21 @@ def rebuild(script_path): def handle_file(previous_content, filename, variable_name, previous_encoded): - print("Found file {}".format(filename)) + print(f"Found file {filename}") current_path = os.path.realpath(os.path.join(here, "..", "src", "virtualenv_embedded", filename)) _, file_type = os.path.splitext(current_path) keep_line_ending = file_type in (".bat",) - with open(current_path, "rt", encoding="utf-8", newline="" if keep_line_ending else None) as current_fh: + with open(current_path, encoding="utf-8", newline="" if keep_line_ending else None) as current_fh: current_text = current_fh.read() current_crc = crc32(current_text) current_encoded = b64.encode(gzip.encode(current_text.encode())[0])[0].decode() if current_encoded == previous_encoded: - print(" File up to date (crc: {:08x})".format(current_crc)) + print(f" File up to date (crc: {current_crc:08x})") return False, previous_content # Else: content has changed previous_text = gzip.decode(b64.decode(previous_encoded.encode())[0])[0].decode() previous_crc = crc32(previous_text) - print(" Content changed (crc: {:08x} -> {:08x})".format(previous_crc, current_crc)) + print(f" Content changed (crc: {previous_crc:08x} -> {current_crc:08x})") new_part = file_template.format(filename=filename, variable=variable_name, data=current_encoded) return True, new_part diff --git a/tasks/upgrade_wheels.py b/tasks/upgrade_wheels.py index 6bc70d0..a30a28b 100644 --- a/tasks/upgrade_wheels.py +++ b/tasks/upgrade_wheels.py @@ -1,7 +1,6 @@ """ Helper script to rebuild virtualenv_support. Downloads the wheel files using pip """ -from __future__ import absolute_import, unicode_literals import os import shutil @@ -70,13 +69,13 @@ def run(): outcome = (1 if STRICT else 0) if (added or removed) else 0 for key, versions in added.items(): - text = "* upgrade embedded {} to {}".format(key, fmt_version(versions)) + text = f"* upgrade embedded {key} to {fmt_version(versions)}" if key in removed: - text += " from {}".format(removed[key]) + text += f" from {removed[key]}" del removed[key] print(text) for key, versions in removed.items(): - print("* removed embedded {} of {}".format(key, fmt_version(versions))) + print(f"* removed embedded {key} of {fmt_version(versions)}") support_table = OrderedDict((".".join(str(j) for j in i), list()) for i in SUPPORT) for package in sorted(new_batch.keys()): @@ -84,17 +83,19 @@ def run(): if (folder / package).exists(): support_table[version].append(package) support_table = {k: OrderedDict((i.split("-")[0], i) for i in v) for k, v in support_table.items()} - + bundle = ",".join( + f"{v!r}: {{ {','.join(f'{p!r}: {f!r}' for p, f in l.items())} }}" for v, l in support_table.items() + ) msg = dedent( - """ + f""" from __future__ import absolute_import, unicode_literals from virtualenv.seed.wheels.util import Wheel from virtualenv.util.path import Path BUNDLE_FOLDER = Path(__file__).absolute().parent - BUNDLE_SUPPORT = {{ {0} }} - MAX = {1} + BUNDLE_SUPPORT = {{ {bundle} }} + MAX = {repr(next(iter(support_table.keys())))} def get_embed_wheel(distribution, for_py_version): @@ -109,13 +110,7 @@ def run(): "BUNDLE_FOLDER", ) - """.format( - ",".join( - "{!r}: {{ {} }}".format(v, ",".join("{!r}: {!r}".format(p, f) for p, f in l.items())) - for v, l in support_table.items() - ), - repr(next(iter(support_table.keys()))), - ), + """, ) dest_target = DEST / "__init__.py" dest_target.write_text(msg) @@ -126,7 +121,7 @@ def run(): def fmt_version(versions): - return ", ".join("``{}``".format(v) for v in versions) + return ", ".join(f"``{v}``" for v in versions) def collect_package_versions(new_packages): diff --git a/tests/conftest.py b/tests/conftest.py index c22bace..87f4b6f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,23 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - import logging import os import shutil import sys from contextlib import contextmanager from functools import partial +from pathlib import Path import pytest -import six from virtualenv.app_data import AppDataDiskFolder from virtualenv.discovery.builtin import get_interpreter from virtualenv.discovery.py_info import PythonInfo -from virtualenv.info import IS_PYPY, IS_WIN, PY2, fs_supports_symlink +from virtualenv.info import IS_WIN, fs_supports_symlink from virtualenv.report import LOGGER -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_str, ensure_text def pytest_addoption(parser): @@ -119,7 +114,7 @@ def check_cwd_not_changed_by_test(): yield new = os.getcwd() if old != new: - pytest.fail("tests changed cwd: {!r} => {!r}".format(old, new)) + pytest.fail(f"tests changed cwd: {old!r} => {new!r}") @pytest.fixture(autouse=True) @@ -144,14 +139,14 @@ def change_os_environ(key, value): @pytest.fixture(autouse=True, scope="session") def ignore_global_config(tmp_path_factory): filename = str(tmp_path_factory.mktemp("folder") / "virtualenv-test-suite.ini") - with change_os_environ(ensure_str("VIRTUALENV_CONFIG_FILE"), filename): + with change_os_environ("VIRTUALENV_CONFIG_FILE", filename): yield @pytest.fixture(autouse=True, scope="session") def pip_cert(tmp_path_factory): # workaround for https://github.com/pypa/pip/issues/8984 - if the certificate is explicitly set no error can happen - key = ensure_str("PIP_CERT") + key = "PIP_CERT" if key in os.environ: yield else: @@ -169,9 +164,7 @@ def check_os_environ_stable(): old = os.environ.copy() # ensure we don't inherit parent env variables to_clean = { - k - for k in os.environ.keys() - if k.startswith(str("VIRTUALENV_")) or str("VIRTUAL_ENV") in k or k.startswith(str("TOX_")) + k for k in os.environ.keys() if k.startswith("VIRTUALENV_") or "VIRTUAL_ENV" in k or k.startswith("TOX_") } cleaned = {k: os.environ[k] for k, v in os.environ.items()} override = { @@ -195,18 +188,18 @@ def check_os_environ_stable(): extra = {k: new[k] for k in set(new) - set(old)} miss = {k: old[k] for k in set(old) - set(new) - to_clean} diff = { - "{} = {} vs {}".format(k, old[k], new[k]) + f"{k} = {old[k]} vs {new[k]}" for k in set(old) & set(new) - if old[k] != new[k] and not k.startswith(str("PYTEST_")) + if old[k] != new[k] and not k.startswith("PYTEST_") } if extra or miss or diff: msg = "tests changed environ" if extra: - msg += " extra {}".format(extra) + msg += f" extra {extra}" if miss: - msg += " miss {}".format(miss) + msg += f" miss {miss}" if diff: - msg += " diff {}".format(diff) + msg += f" diff {diff}" pytest.fail(msg) finally: os.environ.update(cleaned) @@ -268,7 +261,7 @@ def no_coverage(): if COVERAGE_RUN: import coverage - class EnableCoverage(object): + class EnableCoverage: _COV_FILE = Path(coverage.__file__) _ROOT_COV_FILES_AND_FOLDERS = [i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith("coverage")] @@ -294,7 +287,7 @@ if COVERAGE_RUN: @pytest.fixture(scope="session") def is_inside_ci(): - yield bool(os.environ.get(str("CI_RUN"))) + yield bool(os.environ.get("CI_RUN")) @pytest.fixture(scope="session") @@ -319,8 +312,6 @@ def special_char_name(): def special_name_dir(tmp_path, special_char_name): dest = Path(str(tmp_path)) / special_char_name yield dest - if six.PY2 and sys.platform == "win32" and not IS_PYPY: # pytest python2 windows does not support unicode delete - shutil.rmtree(ensure_text(str(dest))) @pytest.fixture(scope="session") @@ -337,7 +328,7 @@ def current_fastest(current_creators): def session_app_data(tmp_path_factory): temp_folder = tmp_path_factory.mktemp("session-app-data") app_data = AppDataDiskFolder(folder=str(temp_folder)) - with change_env_var(str("VIRTUALENV_OVERRIDE_APP_DATA"), str(app_data.lock.path)): + with change_env_var("VIRTUALENV_OVERRIDE_APP_DATA", str(app_data.lock.path)): yield app_data @@ -362,7 +353,7 @@ def change_env_var(key, value): @pytest.fixture() def temp_app_data(monkeypatch, tmp_path): app_data = tmp_path / "app-data" - monkeypatch.setenv(str("VIRTUALENV_OVERRIDE_APP_DATA"), str(app_data)) + monkeypatch.setenv("VIRTUALENV_OVERRIDE_APP_DATA", str(app_data)) return app_data @@ -371,7 +362,7 @@ def cross_python(is_inside_ci, session_app_data): spec = str(2 if sys.version_info[0] == 3 else 3) interpreter = get_interpreter(spec, [], session_app_data) if interpreter is None: - msg = "could not find {}".format(spec) + msg = f"could not find {spec}" if is_inside_ci: raise RuntimeError(msg) pytest.skip(msg=msg) @@ -380,7 +371,7 @@ def cross_python(is_inside_ci, session_app_data): @pytest.fixture(scope="session") def for_py_version(): - return "{}.{}".format(*sys.version_info[0:2]) + return f"{sys.version_info.major}.{sys.version_info.minor}" @pytest.fixture() @@ -388,11 +379,3 @@ def skip_if_test_in_system(session_app_data): current = PythonInfo.current(session_app_data) if current.system_executable is not None: pytest.skip("test not valid if run under system") - - -def pytest_ignore_collect(path): - """ - We can't just skip these tests due to syntax errors that occurs during - collecting tests under a Python 2 host. - """ - return PY2 and str(path).endswith("test_cpython3_win.py") diff --git a/tests/integration/test_run_int.py b/tests/integration/test_run_int.py index 632d625..d41dc1e 100644 --- a/tests/integration/test_run_int.py +++ b/tests/integration/test_run_int.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import, unicode_literals - import sys import pytest from virtualenv import cli_run from virtualenv.info import IS_PYPY -from virtualenv.util.six import ensure_text from virtualenv.util.subprocess import run_cmd -@pytest.mark.skipif(IS_PYPY, reason="setuptools distutil1s patching does not work") +@pytest.mark.skipif(IS_PYPY, reason="setuptools distutils patching does not work") def test_app_data_pinning(tmp_path): version = "19.1.1" if sys.version_info[0:2] == (3, 4) else "19.3.1" - result = cli_run([ensure_text(str(tmp_path)), "--pip", version, "--activators", "", "--seeder", "app-data"]) + result = cli_run([str(tmp_path), "--pip", version, "--activators", "", "--seeder", "app-data"]) code, out, err = run_cmd([str(result.creator.script("pip")), "list", "--disable-pip-version-check"]) assert not code assert not err diff --git a/tests/integration/test_zipapp.py b/tests/integration/test_zipapp.py index e519884..fa7a520 100644 --- a/tests/integration/test_zipapp.py +++ b/tests/integration/test_zipapp.py @@ -1,16 +1,13 @@ -from __future__ import absolute_import, unicode_literals - import shutil import subprocess import sys +from pathlib import Path import pytest from flaky import flaky from virtualenv.discovery.py_info import PythonInfo from virtualenv.run import cli_run -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text HERE = Path(__file__).parent CURRENT = PythonInfo.current_system() @@ -33,7 +30,7 @@ def zipapp_build_env(tmp_path_factory): [ "-vvv", "-p", - "{}3.{}".format(impl, version), + f"{impl}3.{version}", "--activators", "", str(create_env_path), @@ -60,7 +57,7 @@ def zipapp_build_env(tmp_path_factory): @pytest.fixture(scope="session") def zipapp(zipapp_build_env, tmp_path_factory): into = tmp_path_factory.mktemp("zipapp") - path = Path(HERE).parent.parent / "tasks" / "make_zipapp.py" + path = HERE.parent.parent / "tasks" / "make_zipapp.py" filename = into / "virtualenv.pyz" cmd = [zipapp_build_env, str(path), "--dest", str(filename)] subprocess.check_call(cmd) @@ -79,7 +76,7 @@ def zipapp_test_env(tmp_path_factory): @pytest.fixture() def call_zipapp(zipapp, monkeypatch, tmp_path, zipapp_test_env, temp_app_data): def _run(*args): - cmd = [str(zipapp_test_env), str(zipapp), "-vv", ensure_text(str(tmp_path / "env"))] + list(args) + cmd = [str(zipapp_test_env), str(zipapp), "-vv", str(tmp_path / "env")] + list(args) subprocess.check_call(cmd) return _run diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index 5c4ae56..3bb6431 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -1,28 +1,18 @@ -from __future__ import absolute_import, unicode_literals - import os import re -import shutil import subprocess import sys from os.path import dirname, normcase +from pathlib import Path +from shlex import quote +from subprocess import Popen import pytest -import six -from virtualenv.info import IS_PYPY, PY2, WIN_CPYTHON_2 from virtualenv.run import cli_run -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_str, ensure_text -from virtualenv.util.subprocess import Popen - -if PY2: - from pipes import quote -else: - from shlex import quote -class ActivationTester(object): +class ActivationTester: def __init__(self, of_class, session, cmd, activate_script, extension): self.of_class = of_class self._creator = session.creator @@ -55,19 +45,14 @@ class ActivationTester(object): self._version = exception if raise_on_fail: raise - return RuntimeError("{} is not available due {}".format(self, exception)) + return RuntimeError(f"{self} is not available due {exception}") return self._version - def __unicode__(self): - return "{}(\nversion={!r},\ncreator={},\ninterpreter={})".format( - self.__class__.__name__, - self._version, - six.text_type(self._creator), - six.text_type(self._creator.interpreter), - ) - def __repr__(self): - return ensure_str(self.__unicode__()) + return ( + f"{self.__class__.__name__}(\nversion={self._version!r},\ncreator={self._creator},\n" + f"interpreter={self._creator.interpreter})" + ) def __call__(self, monkeypatch, tmp_path): activate_script = self._creator.bin_dir / self.activate_script @@ -75,24 +60,23 @@ class ActivationTester(object): # check line endings are correct type script_content = activate_script.read_bytes() for line in script_content.split(b"\n")[:-1]: - cr = b"\r" if sys.version_info.major == 2 else 13 if self.unix_line_ending: - assert line == b"" or line[-1] != cr, script_content.decode("utf-8") + assert line == b"" or line[-1] != 13, script_content.decode("utf-8") else: - assert line[-1] == cr, script_content.decode("utf-8") + assert line[-1] == 13, script_content.decode("utf-8") test_script = self._generate_test_script(activate_script, tmp_path) - monkeypatch.chdir(ensure_text(str(tmp_path))) + monkeypatch.chdir(tmp_path) - monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) - invoke, env = self._invoke_script + [ensure_text(str(test_script))], self.env(tmp_path) + monkeypatch.delenv("VIRTUAL_ENV", raising=False) + invoke, env = self._invoke_script + [str(test_script)], self.env(tmp_path) try: process = Popen(invoke, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) - _raw, _ = process.communicate() - raw = _raw.decode("utf-8") + raw_, _ = process.communicate() + raw = raw_.decode() except subprocess.CalledProcessError as exception: - output = ensure_text((exception.output + exception.stderr) if six.PY3 else exception.output) + output = exception.output + exception.stderr assert not exception.returncode, output return @@ -108,18 +92,18 @@ class ActivationTester(object): env = os.environ.copy() # add the current python executable folder to the path so we already have another python on the path # also keep the path so the shells (fish, bash, etc can be discovered) - env[str("PYTHONIOENCODING")] = str("utf-8") - env[str("PATH")] = os.pathsep.join([dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep)) + env["PYTHONIOENCODING"] = "utf-8" + env["PATH"] = os.pathsep.join([dirname(sys.executable)] + env.get("PATH", "").split(os.pathsep)) # clear up some environment variables so they don't affect the tests - for key in [k for k in env.keys() if k.startswith(str("_OLD")) or k.startswith(str("VIRTUALENV_"))]: + for key in [k for k in env.keys() if k.startswith("_OLD") or k.startswith("VIRTUALENV_")]: del env[key] return env def _generate_test_script(self, activate_script, tmp_path): commands = self._get_test_lines(activate_script) - script = ensure_text(os.linesep).join(commands) - test_script = tmp_path / "script.{}".format(self.extension) - with open(ensure_text(str(test_script)), "wb") as file_handler: + script = os.linesep.join(commands) + test_script = tmp_path / f"script.{self.extension}" + with test_script.open("wb") as file_handler: file_handler.write(script.encode(self.script_encoding)) return test_script @@ -150,7 +134,7 @@ class ActivationTester(object): assert self.norm_path(out[3]) == self.norm_path(self._creator.dest).replace("\\\\", "\\"), raw # Some attempts to test the prompt output print more than 1 line. # So we need to check if the prompt exists on any of them. - prompt_text = "({}) ".format(self._creator.env_name) + prompt_text = f"({self._creator.env_name}) " assert any(prompt_text in line for line in out[4:-3]), raw assert out[-3] == "wrote pydoc_test.html", raw @@ -164,38 +148,29 @@ class ActivationTester(object): return quote(s) def python_cmd(self, cmd): - return "{} -c {}".format(os.path.basename(sys.executable), self.quote(cmd)) + return f"{os.path.basename(sys.executable)} -c {self.quote(cmd)}" def print_python_exe(self): - return self.python_cmd( - "import sys; print(sys.executable{})".format( - "" if six.PY3 or IS_PYPY else ".decode(sys.getfilesystemencoding())", - ), - ) + return self.python_cmd("import sys; print(sys.executable)") def print_os_env_var(self, var): - val = '"{}"'.format(var) - return self.python_cmd( - "import os; import sys; v = os.environ.get({}); print({})".format( - val, - "v" if six.PY3 or IS_PYPY else "None if v is None else v.decode(sys.getfilesystemencoding())", - ), - ) + val = f'"{var}"' + return self.python_cmd(f"import os; import sys; v = os.environ.get({val}); print(v)") def print_prompt(self): return NotImplemented def activate_call(self, script): - cmd = self.quote(ensure_text(str(self.activate_cmd))) - scr = self.quote(ensure_text(str(script))) - return "{} {}".format(cmd, scr).strip() + cmd = self.quote(str(self.activate_cmd)) + scr = self.quote(str(script)) + return f"{cmd} {scr}".strip() @staticmethod def norm_path(path): # python may return Windows short paths, normalize if not isinstance(path, Path): path = Path(path) - path = ensure_text(str(path.resolve())) + path = str(path.resolve()) if sys.platform != "win32": result = path else: @@ -203,18 +178,18 @@ class ActivationTester(object): buffer_cont = create_unicode_buffer(256) get_long_path_name = windll.kernel32.GetLongPathNameW - get_long_path_name(six.text_type(path), buffer_cont, 256) + get_long_path_name(str(path), buffer_cont, 256) result = buffer_cont.value or path return normcase(result) class RaiseOnNonSourceCall(ActivationTester): def __init__(self, of_class, session, cmd, activate_script, extension, non_source_fail_message): - super(RaiseOnNonSourceCall, self).__init__(of_class, session, cmd, activate_script, extension) + super().__init__(of_class, session, cmd, activate_script, extension) self.non_source_fail_message = non_source_fail_message def __call__(self, monkeypatch, tmp_path): - env, activate_script = super(RaiseOnNonSourceCall, self).__call__(monkeypatch, tmp_path) + env, activate_script = super().__call__(monkeypatch, tmp_path) process = Popen( self.non_source_activate(activate_script), stdout=subprocess.PIPE, @@ -239,7 +214,7 @@ def raise_on_non_source_class(): @pytest.fixture(scope="session", params=[True, False], ids=["with_prompt", "no_prompt"]) def activation_python(request, tmp_path_factory, special_char_name, current_fastest): - dest = os.path.join(ensure_text(str(tmp_path_factory.mktemp("activation-tester-env"))), special_char_name) + dest = os.path.join(str(tmp_path_factory.mktemp("activation-tester-env")), special_char_name) cmd = ["--without-pip", dest, "--creator", current_fastest, "-vv", "--no-periodic-update"] if request.param: cmd += ["--prompt", special_char_name] @@ -247,8 +222,6 @@ def activation_python(request, tmp_path_factory, special_char_name, current_fast pydoc_test = session.creator.purelib / "pydoc_test.py" pydoc_test.write_text('"""This is pydoc_test.py"""') yield session - if WIN_CPYTHON_2: # PY2 windows does not support unicode delete - shutil.rmtree(dest) @pytest.fixture() @@ -256,10 +229,10 @@ def activation_tester(activation_python, monkeypatch, tmp_path, is_inside_ci): def _tester(tester_class): tester = tester_class(activation_python) if not tester.of_class.supports(activation_python.creator.interpreter): - pytest.skip("{} not supported".format(tester.of_class.__name__)) + pytest.skip(f"{tester.of_class.__name__} not supported") version = tester.get_version(raise_on_fail=is_inside_ci) - if not isinstance(version, six.string_types): - pytest.skip(msg=six.text_type(version)) + if not isinstance(version, str): + pytest.skip(msg=str(version)) return tester(monkeypatch, tmp_path) return _tester diff --git a/tests/unit/activation/test_activation_support.py b/tests/unit/activation/test_activation_support.py index d493c23..4bca363 100644 --- a/tests/unit/activation/test_activation_support.py +++ b/tests/unit/activation/test_activation_support.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from argparse import Namespace import pytest diff --git a/tests/unit/activation/test_activator.py b/tests/unit/activation/test_activator.py index 4a8a51c..f5f863f 100644 --- a/tests/unit/activation/test_activator.py +++ b/tests/unit/activation/test_activator.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from argparse import Namespace from virtualenv.activation.activator import Activator diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py index 612ad37..ae56896 100644 --- a/tests/unit/activation/test_bash.py +++ b/tests/unit/activation/test_bash.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import pytest from virtualenv.activation import BashActivator @@ -10,7 +8,7 @@ from virtualenv.info import IS_WIN def test_bash(raise_on_non_source_class, activation_tester): class Bash(raise_on_non_source_class): def __init__(self, session): - super(Bash, self).__init__( + super().__init__( BashActivator, session, "bash", diff --git a/tests/unit/activation/test_batch.py b/tests/unit/activation/test_batch.py index 985d9ff..b789bbe 100644 --- a/tests/unit/activation/test_batch.py +++ b/tests/unit/activation/test_batch.py @@ -1,12 +1,6 @@ -from __future__ import absolute_import, unicode_literals +from shlex import quote from virtualenv.activation import BatchActivator -from virtualenv.info import PY2 - -if PY2: - from pipes import quote -else: - from shlex import quote def test_batch(activation_tester_class, activation_tester, tmp_path, activation_python): @@ -15,17 +9,17 @@ def test_batch(activation_tester_class, activation_tester, tmp_path, activation_ class Batch(activation_tester_class): def __init__(self, session): - super(Batch, self).__init__(BatchActivator, session, None, "activate.bat", "bat") + super().__init__(BatchActivator, session, None, "activate.bat", "bat") self._version_cmd = [str(version_script)] self._invoke_script = [] self.deactivate = "call deactivate" self.activate_cmd = "call" - self.pydoc_call = "call {}".format(self.pydoc_call) + self.pydoc_call = f"call {self.pydoc_call}" self.unix_line_ending = False def _get_test_lines(self, activate_script): # for BATCH utf-8 support need change the character code page to 650001 - return ["@echo off", "", "chcp 65001 1>NUL"] + super(Batch, self)._get_test_lines(activate_script) + return ["@echo off", "", "chcp 65001 1>NUL"] + super()._get_test_lines(activate_script) def quote(self, s): """double quotes needs to be single, and single need to be double""" diff --git a/tests/unit/activation/test_csh.py b/tests/unit/activation/test_csh.py index 1fa5146..f9539c5 100644 --- a/tests/unit/activation/test_csh.py +++ b/tests/unit/activation/test_csh.py @@ -1,12 +1,10 @@ -from __future__ import absolute_import, unicode_literals - from virtualenv.activation import CShellActivator def test_csh(activation_tester_class, activation_tester): class Csh(activation_tester_class): def __init__(self, session): - super(Csh, self).__init__(CShellActivator, session, "csh", "activate.csh", "csh") + super().__init__(CShellActivator, session, "csh", "activate.csh", "csh") def print_prompt(self): return "echo 'source \"$VIRTUAL_ENV/bin/activate.csh\"; echo $prompt' | csh -i" diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py index 7b229e0..450b89c 100644 --- a/tests/unit/activation/test_fish.py +++ b/tests/unit/activation/test_fish.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import pytest from virtualenv.activation import FishActivator @@ -8,14 +6,14 @@ from virtualenv.info import IS_WIN @pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet") def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path): - monkeypatch.setenv(str("HOME"), str(tmp_path)) + monkeypatch.setenv("HOME", str(tmp_path)) fish_conf_dir = tmp_path / ".config" / "fish" fish_conf_dir.mkdir(parents=True) (fish_conf_dir / "config.fish").write_text("") class Fish(activation_tester_class): def __init__(self, session): - super(Fish, self).__init__(FishActivator, session, "fish", "activate.fish", "fish") + super().__init__(FishActivator, session, "fish", "activate.fish", "fish") def print_prompt(self): return "fish_prompt" diff --git a/tests/unit/activation/test_nushell.py b/tests/unit/activation/test_nushell.py index 65c4028..a778d49 100644 --- a/tests/unit/activation/test_nushell.py +++ b/tests/unit/activation/test_nushell.py @@ -1,12 +1,4 @@ -from __future__ import absolute_import, unicode_literals - -import sys - -if sys.version_info > (3,): - from shutil import which -else: - from distutils.spawn import find_executable as which - +from shutil import which from virtualenv.activation import NushellActivator from virtualenv.info import IS_WIN @@ -19,7 +11,7 @@ def test_nushell(activation_tester_class, activation_tester): if cmd is None and IS_WIN: cmd = "c:\\program files\\nu\\bin\\nu.exe" - super(Nushell, self).__init__(NushellActivator, session, cmd, "activate.nu", "nu") + super().__init__(NushellActivator, session, cmd, "activate.nu", "nu") self.unix_line_ending = not IS_WIN diff --git a/tests/unit/activation/test_powershell.py b/tests/unit/activation/test_powershell.py index 72ac1bd..8d6b5f2 100644 --- a/tests/unit/activation/test_powershell.py +++ b/tests/unit/activation/test_powershell.py @@ -1,16 +1,9 @@ -from __future__ import absolute_import, unicode_literals - import sys +from shlex import quote import pytest from virtualenv.activation import PowerShellActivator -from virtualenv.info import PY2 - -if PY2: - from pipes import quote -else: - from shlex import quote @pytest.mark.slow @@ -20,19 +13,19 @@ def test_powershell(activation_tester_class, activation_tester, monkeypatch): class PowerShell(activation_tester_class): def __init__(self, session): cmd = "powershell.exe" if sys.platform == "win32" else "pwsh" - super(PowerShell, self).__init__(PowerShellActivator, session, cmd, "activate.ps1", "ps1") + super().__init__(PowerShellActivator, session, cmd, "activate.ps1", "ps1") self._version_cmd = [cmd, "-c", "$PSVersionTable"] self._invoke_script = [cmd, "-ExecutionPolicy", "ByPass", "-File"] self.activate_cmd = "." self.script_encoding = "utf-16" def quote(self, s): - """powershell double double quote needed for quotes within single quotes""" + """powershell double quote needed for quotes within single quotes""" return quote(s).replace('"', '""') def _get_test_lines(self, activate_script): # for BATCH utf-8 support need change the character code page to 650001 - return super(PowerShell, self)._get_test_lines(activate_script) + return super()._get_test_lines(activate_script) def invoke_script(self): return [self.cmd, "-File"] diff --git a/tests/unit/activation/test_python_activator.py b/tests/unit/activation/test_python_activator.py index 0b42e11..5c56798 100644 --- a/tests/unit/activation/test_python_activator.py +++ b/tests/unit/activation/test_python_activator.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import, unicode_literals - import os import sys from ast import literal_eval from textwrap import dedent from virtualenv.activation import PythonActivator -from virtualenv.info import IS_WIN, WIN_CPYTHON_2 -from virtualenv.util.six import ensure_text +from virtualenv.info import IS_WIN def test_python(raise_on_non_source_class, activation_tester): class Python(raise_on_non_source_class): def __init__(self, session): - super(Python, self).__init__( + super().__init__( PythonActivator, session, sys.executable, @@ -25,15 +22,15 @@ def test_python(raise_on_non_source_class, activation_tester): def env(self, tmp_path): env = os.environ.copy() - env[str("PYTHONIOENCODING")] = str("utf-8") + env["PYTHONIOENCODING"] = "utf-8" for key in {"VIRTUAL_ENV", "PYTHONPATH"}: env.pop(str(key), None) - env[str("PATH")] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")]) + env["PATH"] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")]) return env @staticmethod def _get_test_lines(activate_script): - raw = """ + raw = f""" import os import sys import platform @@ -45,9 +42,9 @@ def test_python(raise_on_non_source_class, activation_tester): print_r(os.environ.get("PATH").split(os.pathsep)) print_r(sys.path) - file_at = {!r} + file_at = {str(activate_script)!r} # CPython 2 requires non-ascii path open to be unicode - with open(file_at{}, "r") as file_handler: + with open(file_at, "r") as file_handler: content = file_handler.read() exec(content, {{"__file__": file_at}}) @@ -57,10 +54,7 @@ def test_python(raise_on_non_source_class, activation_tester): import pydoc_test print_r(pydoc_test.__file__) - """.format( - str(activate_script), - ".decode('utf-8')" if WIN_CPYTHON_2 else "", - ) + """ result = dedent(raw).splitlines() return result @@ -78,23 +72,18 @@ def test_python(raise_on_non_source_class, activation_tester): # sys path contains the site package at its start new_sys_path = out[5] - new_lib_paths = {ensure_text(j) if WIN_CPYTHON_2 else j for j in {str(i) for i in self._creator.libs}} + new_lib_paths = {j for j in {str(i) for i in self._creator.libs}} assert prev_sys_path == new_sys_path[len(new_lib_paths) :] assert new_lib_paths == set(new_sys_path[: len(new_lib_paths)]) # manage to import from activate site package dest = self.norm_path(self._creator.purelib / "pydoc_test.py") - found = self.norm_path(out[6].decode(sys.getfilesystemencoding()) if WIN_CPYTHON_2 else out[6]) + found = self.norm_path(out[6]) assert found.startswith(dest) def non_source_activate(self, activate_script): act = str(activate_script) - if WIN_CPYTHON_2: - act = ensure_text(act) - cmd = self._invoke_script + [ - "-c", - "exec(open({}).read())".format(repr(act)), - ] + cmd = self._invoke_script + ["-c", f"exec(open({act!r}).read())"] return cmd activation_tester(Python) diff --git a/tests/unit/config/cli/test_parser.py b/tests/unit/config/cli/test_parser.py index a2cd4e0..a12fb3f 100644 --- a/tests/unit/config/cli/test_parser.py +++ b/tests/unit/config/cli/test_parser.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import os from contextlib import contextmanager @@ -12,7 +10,7 @@ from virtualenv.run import session_via_cli @pytest.fixture() def gen_parser_no_conf_env(monkeypatch, tmp_path): - keys_to_delete = {key for key in os.environ if key.startswith(str("VIRTUALENV_"))} + keys_to_delete = {key for key in os.environ if key.startswith("VIRTUALENV_")} for key in keys_to_delete: monkeypatch.delenv(key) monkeypatch.setenv(IniConfig.VIRTUALENV_CONFIG_FILE_ENV_VAR, str(tmp_path / "missing")) diff --git a/tests/unit/config/test___main__.py b/tests/unit/config/test___main__.py index b974712..a29b105 100644 --- a/tests/unit/config/test___main__.py +++ b/tests/unit/config/test___main__.py @@ -1,17 +1,15 @@ -from __future__ import absolute_import, unicode_literals - import re import sys +from subprocess import PIPE, Popen, check_output import pytest from virtualenv.__main__ import run_with_catch from virtualenv.util.error import ProcessCallFailed -from virtualenv.util.subprocess import Popen, subprocess def test_main(): - process = Popen([sys.executable, "-m", "virtualenv", "--help"], universal_newlines=True, stdout=subprocess.PIPE) + process = Popen([sys.executable, "-m", "virtualenv", "--help"], universal_newlines=True, stdout=PIPE) out, _ = process.communicate() assert not process.returncode assert out @@ -39,7 +37,7 @@ def test_fail_no_traceback(raise_on_session_done, tmp_path, capsys): run_with_catch([str(tmp_path)]) assert context.value.code == 2 out, err = capsys.readouterr() - assert out == "subprocess call failed for [{}] with code 2\nout\nSystemExit: 2\n".format(repr("something")) + assert out == f"subprocess call failed for [{'something'!r}] with code 2\nout\nSystemExit: 2\n" assert err == "err\n" @@ -70,7 +68,7 @@ def test_session_report_full(session_app_data, tmp_path, capsys): def _match_regexes(lines, regexes): for line, regex in zip(lines, regexes): - comp_regex = re.compile(r"^{}$".format(regex)) + comp_regex = re.compile(rf"^{regex}$") assert comp_regex.match(line), line @@ -88,7 +86,7 @@ def test_session_report_minimal(session_app_data, tmp_path, capsys): def test_session_report_subprocess(session_app_data, tmp_path): # when called via a subprocess the logging framework should flush and POSIX line normalization happen - out = subprocess.check_output( + out = check_output( [sys.executable, "-m", "virtualenv", str(tmp_path), "--activators", "powershell", "--without-pip"], universal_newlines=True, ) diff --git a/tests/unit/config/test_env_var.py b/tests/unit/config/test_env_var.py index 34b216f..ce33a51 100644 --- a/tests/unit/config/test_env_var.py +++ b/tests/unit/config/test_env_var.py @@ -1,13 +1,11 @@ -from __future__ import absolute_import, unicode_literals - import os +from pathlib import Path import pytest from virtualenv.config.cli.parser import VirtualEnvOptions from virtualenv.config.ini import IniConfig from virtualenv.run import session_via_cli -from virtualenv.util.path import Path @pytest.fixture() @@ -18,13 +16,13 @@ def empty_conf(tmp_path, monkeypatch): def test_value_ok(monkeypatch, empty_conf): - monkeypatch.setenv(str("VIRTUALENV_VERBOSE"), str("5")) + monkeypatch.setenv("VIRTUALENV_VERBOSE", "5") result = session_via_cli(["venv"]) assert result.verbosity == 5 def test_value_bad(monkeypatch, caplog, empty_conf): - monkeypatch.setenv(str("VIRTUALENV_VERBOSE"), str("a")) + monkeypatch.setenv("VIRTUALENV_VERBOSE", "a") result = session_via_cli(["venv"]) assert result.verbosity == 2 assert len(caplog.messages) == 1 @@ -34,36 +32,36 @@ def test_value_bad(monkeypatch, caplog, empty_conf): def test_python_via_env_var(monkeypatch): options = VirtualEnvOptions() - monkeypatch.setenv(str("VIRTUALENV_PYTHON"), str("python3")) + monkeypatch.setenv("VIRTUALENV_PYTHON", "python3") session_via_cli(["venv"], options=options) assert options.python == ["python3"] def test_python_multi_value_via_env_var(monkeypatch): options = VirtualEnvOptions() - monkeypatch.setenv(str("VIRTUALENV_PYTHON"), str("python3,python2")) + monkeypatch.setenv("VIRTUALENV_PYTHON", "python3,python2") session_via_cli(["venv"], options=options) assert options.python == ["python3", "python2"] def test_python_multi_value_newline_via_env_var(monkeypatch): options = VirtualEnvOptions() - monkeypatch.setenv(str("VIRTUALENV_PYTHON"), str("python3\npython2")) + monkeypatch.setenv("VIRTUALENV_PYTHON", "python3\npython2") session_via_cli(["venv"], options=options) assert options.python == ["python3", "python2"] def test_python_multi_value_prefer_newline_via_env_var(monkeypatch): options = VirtualEnvOptions() - monkeypatch.setenv(str("VIRTUALENV_PYTHON"), str("python3\npython2,python27")) + monkeypatch.setenv("VIRTUALENV_PYTHON", "python3\npython2,python27") session_via_cli(["venv"], options=options) assert options.python == ["python3", "python2,python27"] def test_extra_search_dir_via_env_var(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) - value = "a{}0{}b{}c".format(os.linesep, os.linesep, os.pathsep) - monkeypatch.setenv(str("VIRTUALENV_EXTRA_SEARCH_DIR"), str(value)) + value = f"a{os.linesep}0{os.linesep}b{os.pathsep}c" + monkeypatch.setenv("VIRTUALENV_EXTRA_SEARCH_DIR", str(value)) (tmp_path / "a").mkdir() (tmp_path / "b").mkdir() (tmp_path / "c").mkdir() @@ -85,8 +83,8 @@ def test_value_alias(monkeypatch, mocker, empty_conf): mocker.patch("virtualenv.run.VirtualEnvConfigParser._fix_default", side_effect=func, autospec=True) - monkeypatch.delenv(str("SYMLINKS"), raising=False) - monkeypatch.delenv(str("VIRTUALENV_COPIES"), raising=False) - monkeypatch.setenv(str("VIRTUALENV_ALWAYS_COPY"), str("1")) + monkeypatch.delenv("SYMLINKS", raising=False) + monkeypatch.delenv("VIRTUALENV_COPIES", raising=False) + monkeypatch.setenv("VIRTUALENV_ALWAYS_COPY", "1") result = session_via_cli(["venv"]) assert result.creator.symlinks is False diff --git a/tests/unit/config/test_ini.py b/tests/unit/config/test_ini.py index 0f87256..dc0ce93 100644 --- a/tests/unit/config/test_ini.py +++ b/tests/unit/config/test_ini.py @@ -1,12 +1,9 @@ -from __future__ import unicode_literals - from textwrap import dedent import pytest from virtualenv.info import fs_supports_symlink from virtualenv.run import session_via_cli -from virtualenv.util.six import ensure_str @pytest.mark.skipif(not fs_supports_symlink(), reason="symlink is not supported") @@ -20,7 +17,7 @@ def test_ini_can_be_overwritten_by_flag(tmp_path, monkeypatch): """, ), ) - monkeypatch.setenv(ensure_str("VIRTUALENV_CONFIG_FILE"), str(custom_ini)) + monkeypatch.setenv("VIRTUALENV_CONFIG_FILE", str(custom_ini)) result = session_via_cli(["venv", "--symlinks"]) diff --git a/tests/unit/create/conftest.py b/tests/unit/create/conftest.py index c709ade..2825df6 100644 --- a/tests/unit/create/conftest.py +++ b/tests/unit/create/conftest.py @@ -6,18 +6,17 @@ It's possible to use multiple types of host pythons to create virtual environmen - invoking from an old style virtualenv (<17.0.0) - invoking from our own venv """ -from __future__ import absolute_import, unicode_literals import subprocess import sys +from pathlib import Path +from subprocess import Popen import pytest from virtualenv.discovery.py_info import PythonInfo from virtualenv.info import IS_WIN from virtualenv.run import cli_run -from virtualenv.util.path import Path -from virtualenv.util.subprocess import Popen CURRENT = PythonInfo.current_system() @@ -84,7 +83,7 @@ def old_virtualenv(tmp_path_factory, session_app_data): exe_path = CURRENT.discover_exe(session_app_data, prefix=str(old_virtualenv_at)).original_executable return exe_path except Exception as exception: - return RuntimeError("failed to create old virtualenv {}".format(exception)) + return RuntimeError(f"failed to create old virtualenv {exception}") PYTHON = { @@ -98,7 +97,7 @@ PYTHON = { def python(request, tmp_path_factory, session_app_data): result = request.param(tmp_path_factory, session_app_data) if isinstance(result, Exception): - pytest.skip("could not resolve interpreter based on {} because {}".format(request.param.__name__, result)) + pytest.skip(f"could not resolve interpreter based on {request.param.__name__} because {result}") if result is None: - pytest.skip("requires interpreter with {}".format(request.param.__name__)) + pytest.skip(f"requires interpreter with {request.param.__name__}") return result diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py index 424a9b3..d18098c 100644 --- a/tests/unit/create/test_creator.py +++ b/tests/unit/create/test_creator.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import ast import difflib import gc @@ -14,6 +12,7 @@ import sys import zipfile from collections import OrderedDict from itertools import product +from pathlib import Path from stat import S_IREAD, S_IRGRP, S_IROTH from textwrap import dedent from threading import Thread @@ -27,20 +26,18 @@ from virtualenv.create.via_global_ref.builtin.cpython.cpython2 import CPython2Po from virtualenv.create.via_global_ref.builtin.cpython.cpython3 import CPython3Posix from virtualenv.create.via_global_ref.builtin.python2.python2 import Python2 from virtualenv.discovery.py_info import PythonInfo -from virtualenv.info import IS_PYPY, IS_WIN, PY2, PY3, fs_is_case_sensitive +from virtualenv.info import IS_PYPY, IS_WIN, fs_is_case_sensitive from virtualenv.run import cli_run, session_via_cli -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_str, ensure_text CURRENT = PythonInfo.current_system() def test_os_path_sep_not_allowed(tmp_path, capsys): - target = str(tmp_path / "a{}b".format(os.pathsep)) + target = str(tmp_path / f"a{os.pathsep}b") err = _non_success_exit_code(capsys, target) msg = ( - "destination {!r} must not contain the path separator ({}) as this" - " would break the activation scripts".format(target, os.pathsep) + f"destination {target!r} must not contain the path separator ({os.pathsep})" + f" as this would break the activation scripts" ) assert msg in err, err @@ -58,7 +55,7 @@ def test_destination_exists_file(tmp_path, capsys): target = tmp_path / "out" target.write_text("") err = _non_success_exit_code(capsys, str(target)) - msg = "the destination {} already exists and is a file".format(str(target)) + msg = f"the destination {str(target)} already exists and is a file" assert msg in err, err @@ -73,7 +70,7 @@ def test_destination_not_write_able(tmp_path, capsys): target.chmod(S_IREAD | S_IRGRP | S_IROTH) try: err = _non_success_exit_code(capsys, str(target)) - msg = "the destination . is not write-able at {}".format(str(target)) + msg = f"the destination . is not write-able at {str(target)}" assert msg in err, err finally: target.chmod(prev_mod) @@ -84,8 +81,8 @@ def cleanup_sys_path(paths): paths = [p.resolve() for p in (Path(os.path.abspath(i)) for i in paths) if p.exists()] to_remove = [Path(HERE)] - if os.environ.get(str("PYCHARM_HELPERS_DIR")): - to_remove.append(Path(os.environ[str("PYCHARM_HELPERS_DIR")]).parent) + if os.environ.get("PYCHARM_HELPERS_DIR"): + to_remove.append(Path(os.environ["PYCHARM_HELPERS_DIR"]).parent) to_remove.append(Path(os.path.expanduser("~")) / ".PyCharm") result = [i for i in paths if not any(str(i).startswith(str(t)) for t in to_remove)] return result @@ -120,7 +117,7 @@ _VENV_BUG_ON = ( marks=pytest.mark.xfail( reason="https://bitbucket.org/pypy/pypy/issues/3159/pypy36-730-venv-fails-with-copies-on-linux", strict=True, - ) + ), ) if _VENV_BUG_ON and i[0][0] == "venv" and i[0][1] == "copies" else i @@ -135,14 +132,14 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special "-v", "-v", "-p", - ensure_text(python), - ensure_text(str(dest)), + str(python), + str(dest), "--without-pip", "--activators", "", "--creator", creator_key, - "--{}".format(method), + f"--{method}", ] if isolated == "global": cmd.append("--system-site-packages") @@ -154,27 +151,24 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special # force a close of these on system where the limit is low-ish (e.g. MacOS 256) gc.collect() purelib = creator.purelib - patch_files = {purelib / "{}.{}".format("_virtualenv", i) for i in ("py", "pyc", "pth")} + patch_files = {purelib / f"{'_virtualenv'}.{i}" for i in ("py", "pyc", "pth")} patch_files.add(purelib / "__pycache__") content = set(creator.purelib.iterdir()) - patch_files - assert not content, "\n".join(ensure_text(str(i)) for i in content) - assert creator.env_name == ensure_text(dest.name) + assert not content, "\n".join(str(i) for i in content) + assert creator.env_name == str(dest.name) debug = creator.debug - assert "exception" not in debug, "{}\n{}\n{}".format(debug.get("exception"), debug.get("out"), debug.get("err")) + assert "exception" not in debug, f"{debug.get('exception')}\n{debug.get('out')}\n{debug.get('err')}" sys_path = cleanup_sys_path(debug["sys"]["path"]) system_sys_path = cleanup_sys_path(system["sys"]["path"]) our_paths = set(sys_path) - set(system_sys_path) - our_paths_repr = "\n".join(ensure_text(repr(i)) for i in our_paths) + our_paths_repr = "\n".join(repr(i) for i in our_paths) # ensure we have at least one extra path added assert len(our_paths) >= 1, our_paths_repr # ensure all additional paths are related to the virtual environment for path in our_paths: - msg = "\n{}\ndoes not start with {}\nhas:\n{}".format( - ensure_text(str(path)), - ensure_text(str(dest)), - "\n".join(ensure_text(str(p)) for p in system_sys_path), - ) + msg = "\n".join(str(p) for p in system_sys_path) + msg = f"\n{str(path)}\ndoes not start with {str(dest)}\nhas:\n{msg}" assert str(path).startswith(str(dest)), msg # ensure there's at least a site-packages folder as part of the virtual environment added assert any(p for p in our_paths if p.parts[-1] == "site-packages"), our_paths_repr @@ -182,10 +176,8 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special # ensure the global site package is added or not, depending on flag global_sys_path = system_sys_path[-1] if isolated == "isolated": - msg = "global sys path {} is in virtual environment sys path:\n{}".format( - ensure_text(str(global_sys_path)), - "\n".join(ensure_text(str(j)) for j in sys_path), - ) + msg = "\n".join(str(j) for j in sys_path) + msg = f"global sys path {str(global_sys_path)} is in virtual environment sys path:\n{msg}" assert global_sys_path not in sys_path, msg else: common = [] @@ -196,7 +188,7 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special break def list_to_str(iterable): - return [ensure_text(str(i)) for i in iterable] + return [str(i) for i in iterable] assert common, "\n".join(difflib.unified_diff(list_to_str(sys_path), list_to_str(system_sys_path))) @@ -207,7 +199,7 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special if sys.platform == "win32": exes = ("python.exe",) else: - exes = ("python", "python{}".format(*sys.version_info), "python{}.{}".format(*sys.version_info)) + exes = ("python", f"python{sys.version_info.major}", f"python{sys.version_info.major}.{sys.version_info.minor}") if creator_key == "venv": # for venv some repackaging does not includes the pythonx.y exes = exes[:-1] @@ -276,7 +268,7 @@ def test_venv_fails_not_inline(tmp_path, capsys, mocker): mocker.patch("virtualenv.run.session_via_cli", side_effect=_session_via_cli) before = tmp_path.stat().st_mode cfg_path = tmp_path / "pyvenv.cfg" - cfg_path.write_text(ensure_text("")) + cfg_path.write_text("") cfg = str(cfg_path) try: os.chmod(cfg, stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH) @@ -291,22 +283,6 @@ def test_venv_fails_not_inline(tmp_path, capsys, mocker): assert "Error:" in err, err -@pytest.mark.skipif(not sys.version_info[0] == 2, reason="python 2 only tests") -def test_debug_bad_virtualenv(tmp_path): - cmd = [str(tmp_path), "--without-pip"] - result = cli_run(cmd) - # if the site.py is removed/altered the debug should fail as no one is around to fix the paths - site_py = result.creator.stdlib / "site.py" - site_py.unlink() - # insert something that writes something on the stdout - site_py.write_text('import sys; sys.stdout.write(repr("std-out")); sys.stderr.write("std-err"); raise ValueError') - debug_info = result.creator.debug - assert debug_info["returncode"] - assert debug_info["err"].startswith("std-err") - assert "std-out" in debug_info["out"] - assert debug_info["exception"] - - @pytest.mark.parametrize("creator", CURRENT_CREATORS) @pytest.mark.parametrize("clear", [True, False], ids=["clear", "no_clear"]) def test_create_clear_resets(tmp_path, creator, clear, caplog): @@ -346,8 +322,8 @@ def test_prompt_set(tmp_path, creator, prompt): def test_cross_major(cross_python, coverage_env, tmp_path, session_app_data, current_fastest): cmd = [ "-p", - ensure_text(cross_python.executable), - ensure_text(str(tmp_path)), + cross_python.executable, + str(tmp_path), "--no-setuptools", "--no-wheel", "--activators", @@ -358,9 +334,9 @@ def test_cross_major(cross_python, coverage_env, tmp_path, session_app_data, cur major, minor = cross_python.version_info[0:2] assert pip_scripts == { "pip", - "pip{}".format(major), - "pip-{}.{}".format(major, minor), - "pip{}.{}".format(major, minor), + f"pip{major}", + f"pip-{major}.{minor}", + f"pip{major}.{minor}", } coverage_env() env = PythonInfo.from_exe(str(result.creator.exe), session_app_data) @@ -370,7 +346,7 @@ def test_cross_major(cross_python, coverage_env, tmp_path, session_app_data, cur def test_create_parallel(tmp_path, monkeypatch, temp_app_data): def create(count): subprocess.check_call( - [sys.executable, "-m", "virtualenv", "-vvv", str(tmp_path / "venv{}".format(count)), "--without-pip"], + [sys.executable, "-m", "virtualenv", "-vvv", str(tmp_path / f"venv{count}"), "--without-pip"], ) threads = [Thread(target=create, args=(i,)) for i in range(1, 4)] @@ -410,7 +386,7 @@ def test_create_long_path(current_fastest, tmp_path): @pytest.mark.parametrize("creator", sorted(set(PythonInfo.current_system().creators().key_to_class) - {"builtin"})) def test_create_distutils_cfg(creator, tmp_path, monkeypatch, session_app_data): - result = cli_run([ensure_text(str(tmp_path / "venv")), "--activators", "", "--creator", creator]) + result = cli_run([str(tmp_path / "venv"), "--activators", "", "--creator", creator]) app = Path(__file__).parent / "console_app" dest = tmp_path / "console_app" @@ -418,18 +394,15 @@ def test_create_distutils_cfg(creator, tmp_path, monkeypatch, session_app_data): setup_cfg = dest / "setup.cfg" conf = dedent( - """ + f""" [install] - prefix={0}{1}prefix - install_purelib={0}{1}purelib - install_platlib={0}{1}platlib - install_headers={0}{1}headers - install_scripts={0}{1}scripts - install_data={0}{1}data - """.format( - tmp_path, - os.sep, - ), + prefix={tmp_path}{os.sep}prefix + install_purelib={tmp_path}{os.sep}purelib + install_platlib={tmp_path}{os.sep}platlib + install_headers={tmp_path}{os.sep}headers + install_scripts={tmp_path}{os.sep}scripts + install_data={tmp_path}{os.sep}data + """, ) setup_cfg.write_text(setup_cfg.read_text() + conf) @@ -457,105 +430,13 @@ def list_files(path): for root, _, files in os.walk(path): level = root.replace(path, "").count(os.sep) indent = " " * 4 * level - result += "{}{}/\n".format(indent, os.path.basename(root)) + result += f"{indent}{os.path.basename(root)}/\n" sub = " " * 4 * (level + 1) for f in files: - result += "{}{}\n".format(sub, f) + result += f"{sub}{f}\n" return result -@pytest.mark.parametrize("python_path_on", [True, False], ids=["on", "off"]) -@pytest.mark.skipif(PY3, reason="we rewrite sys.path only on PY2") -def test_python_path(monkeypatch, tmp_path, python_path_on): - result = cli_run([ensure_text(str(tmp_path)), "--without-pip", "--activators", ""]) - monkeypatch.chdir(tmp_path) - case_sensitive = fs_is_case_sensitive() - - def _get_sys_path(flag=None): - cmd = [str(result.creator.exe)] - if flag: - cmd.append(flag) - cmd.extend(["-c", "import json; import sys; print(json.dumps(sys.path))"]) - return [i if case_sensitive else i.lower() for i in json.loads(subprocess.check_output(cmd))] - - monkeypatch.delenv(str("PYTHONPATH"), raising=False) - base = _get_sys_path() - - # note the value result.creator.interpreter.system_stdlib cannot be set, as that would disable our custom site.py - python_paths = [ - str(Path(result.creator.interpreter.prefix)), - str(Path(result.creator.interpreter.system_stdlib) / "b"), - str(result.creator.purelib / "a"), - str(result.creator.purelib), - str(result.creator.bin_dir), - str(tmp_path / "base"), - str(tmp_path / "base_sep") + os.sep, - "name", - "name{}".format(os.sep), - str(tmp_path.parent / (ensure_text(tmp_path.name) + "_suffix")), - ".", - "..", - "", - ] - python_path_env = os.pathsep.join(ensure_str(i) for i in python_paths) - monkeypatch.setenv(str("PYTHONPATH"), python_path_env) - - extra_all = _get_sys_path(None if python_path_on else "-E") - if python_path_on: - assert extra_all[0] == "" # the cwd is always injected at start as '' - extra_all = extra_all[1:] - assert base[0] == "" - base = base[1:] - - assert not (set(base) - set(extra_all)) # all base paths are present - abs_python_paths = list(OrderedDict((os.path.abspath(ensure_text(i)), None) for i in python_paths).keys()) - abs_python_paths = [i if case_sensitive else i.lower() for i in abs_python_paths] - - extra_as_python_path = extra_all[: len(abs_python_paths)] - assert abs_python_paths == extra_as_python_path # python paths are there at the start - - non_python_path = extra_all[len(abs_python_paths) :] - assert non_python_path == [i for i in base if i not in extra_as_python_path] - else: - assert base == extra_all - - -@pytest.mark.skipif( - not (CURRENT.implementation == "CPython" and PY2), - reason="stdlib components without py files only possible on CPython2", -) -@pytest.mark.parametrize( - "py, pyc", - list( - product( - [True, False] if Python2.from_stdlib(Python2.mappings(CURRENT), "os.py")[2] else [False], - [True, False] if Python2.from_stdlib(Python2.mappings(CURRENT), "os.pyc")[2] else [False], - ), - ), -) -def test_py_pyc_missing(tmp_path, mocker, session_app_data, py, pyc): - """Ensure that creation can succeed if os.pyc exists (even if os.py has been deleted)""" - previous = Python2.from_stdlib - - def from_stdlib(mappings, name): - path, to, exists = previous(mappings, name) - if name.endswith("py"): - exists = py - elif name.endswith("pyc"): - exists = pyc - return path, to, exists - - mocker.patch.object(Python2, "from_stdlib", side_effect=from_stdlib) - - result = cli_run([ensure_text(str(tmp_path)), "--without-pip", "--activators", "", "-vv"]) - py_at = Python2.from_stdlib(Python2.mappings(CURRENT), "os.py")[1](result.creator, Path("os.py")) - py = pyc is False or py # if pyc is False we fallback to serve the py, which will exist (as we only mock the check) - assert py_at.exists() is py - - pyc_at = Python2.from_stdlib(Python2.mappings(CURRENT), "osc.py")[1](result.creator, Path("os.pyc")) - assert pyc_at.exists() is pyc - - def test_zip_importer_can_import_setuptools(tmp_path): """We're patching the loaders so might fail on r/o loaders, such as zipimporter on CPython<3.8""" result = cli_run([str(tmp_path / "venv"), "--activators", "", "--no-pip", "--no-wheel", "--copies"]) @@ -574,7 +455,7 @@ def test_zip_importer_can_import_setuptools(tmp_path): else: folder.unlink() env = os.environ.copy() - env[str("PYTHONPATH")] = str(zip_path) + env["PYTHONPATH"] = str(zip_path) subprocess.check_call([str(result.creator.exe), "-c", "from setuptools.dist import Distribution"], env=env) @@ -583,11 +464,11 @@ def test_zip_importer_can_import_setuptools(tmp_path): # # coverage is disabled, because when coverage is active, it imports threading in default mode. @pytest.mark.xfail( - IS_PYPY and PY3 and sys.platform.startswith("darwin"), + IS_PYPY and sys.platform.startswith("darwin"), reason="https://foss.heptapod.net/pypy/pypy/-/issues/3269", ) def test_no_preimport_threading(tmp_path, no_coverage): - session = cli_run([ensure_text(str(tmp_path))]) + session = cli_run([str(tmp_path)]) out = subprocess.check_output( [str(session.creator.exe), "-c", r"import sys; print('\n'.join(sorted(sys.modules)))"], universal_newlines=True, @@ -598,7 +479,7 @@ def test_no_preimport_threading(tmp_path, no_coverage): # verify that .pth files in site-packages/ are always processed even if $PYTHONPATH points to it. def test_pth_in_site_vs_PYTHONPATH(tmp_path): - session = cli_run([ensure_text(str(tmp_path))]) + session = cli_run([str(tmp_path)]) site_packages = str(session.creator.purelib) # install test.pth that sets sys.testpth='ok' with open(os.path.join(site_packages, "test.pth"), "w") as f: @@ -625,7 +506,7 @@ def test_pth_in_site_vs_PYTHONPATH(tmp_path): def test_getsitepackages_system_site(tmp_path): # Test without --system-site-packages - session = cli_run([ensure_text(str(tmp_path))]) + session = cli_run([str(tmp_path)]) system_site_packages = get_expected_system_site_packages(session) @@ -639,15 +520,15 @@ def test_getsitepackages_system_site(tmp_path): assert system_site_package not in site_packages # Test with --system-site-packages - session = cli_run([ensure_text(str(tmp_path)), "--system-site-packages"]) + session = cli_run([str(tmp_path), "--system-site-packages"]) - system_site_packages = get_expected_system_site_packages(session) + system_site_packages = [str(Path(i).resolve()) for i in get_expected_system_site_packages(session)] out = subprocess.check_output( [str(session.creator.exe), "-c", r"import site; print(site.getsitepackages())"], universal_newlines=True, ) - site_packages = ast.literal_eval(out) + site_packages = [str(Path(i).resolve()) for i in ast.literal_eval(out)] for system_site_package in system_site_packages: assert system_site_package in site_packages @@ -666,7 +547,7 @@ def get_expected_system_site_packages(session): def test_get_site_packages(tmp_path): case_sensitive = fs_is_case_sensitive() - session = cli_run([ensure_text(str(tmp_path))]) + session = cli_run([str(tmp_path)]) env_site_packages = [str(session.creator.purelib), str(session.creator.platlib)] out = subprocess.check_output( [str(session.creator.exe), "-c", r"import site; print(site.getsitepackages())"], @@ -680,3 +561,103 @@ def test_get_site_packages(tmp_path): for env_site_package in env_site_packages: assert env_site_package in site_packages + + +def test_debug_bad_virtualenv(tmp_path): + cmd = [str(tmp_path), "--without-pip"] + result = cli_run(cmd) + # if the site.py is removed/altered the debug should fail as no one is around to fix the paths + cust = result.creator.purelib / "_a.pth" + cust.write_text('import sys; sys.stdout.write("std-out"); sys.stderr.write("std-err"); raise SystemExit(1)') + debug_info = result.creator.debug + assert debug_info["returncode"] == 1 + assert "std-err" in debug_info["err"] + assert "std-out" in debug_info["out"] + assert debug_info["exception"] + + +@pytest.mark.parametrize("python_path_on", [True, False], ids=["on", "off"]) +def test_python_path(monkeypatch, tmp_path, python_path_on): + result = cli_run([str(tmp_path), "--without-pip", "--activators", ""]) + monkeypatch.chdir(tmp_path) + case_sensitive = fs_is_case_sensitive() + + def _get_sys_path(flag=None): + cmd = [str(result.creator.exe)] + if flag: + cmd.append(flag) + cmd.extend(["-c", "import json; import sys; print(json.dumps(sys.path))"]) + return [i if case_sensitive else i.lower() for i in json.loads(subprocess.check_output(cmd))] + + monkeypatch.delenv("PYTHONPATH", raising=False) + base = _get_sys_path() + + # note the value result.creator.interpreter.system_stdlib cannot be set, as that would disable our custom site.py + python_paths = [ + str(Path(result.creator.interpreter.prefix)), + str(Path(result.creator.interpreter.system_stdlib) / "b"), + str(result.creator.purelib / "a"), + str(result.creator.purelib), + str(result.creator.bin_dir), + str(tmp_path / "base"), + f"{str(tmp_path / 'base_sep')}{os.sep}", + "name", + f"name{os.sep}", + f"{tmp_path.parent}{f'{tmp_path.name}_suffix'}", + ".", + "..", + "", + ] + python_path_env = os.pathsep.join(python_paths) + monkeypatch.setenv("PYTHONPATH", python_path_env) + + extra_all = _get_sys_path(None if python_path_on else "-E") + if python_path_on: + assert extra_all[0] == "" # the cwd is always injected at start as '' + extra_all = extra_all[1:] + assert base[0] == "" + base = base[1:] + + assert not (set(base) - set(extra_all)) # all base paths are present + abs_python_paths = list(OrderedDict((os.path.abspath(str(i)), None) for i in python_paths).keys()) + abs_python_paths = [i if case_sensitive else i.lower() for i in abs_python_paths] + + extra_as_python_path = extra_all[: len(abs_python_paths)] + assert abs_python_paths == extra_as_python_path # python paths are there at the start + + non_python_path = extra_all[len(abs_python_paths) :] + assert non_python_path == [i for i in base if i not in extra_as_python_path] + else: + assert base == extra_all + + +@pytest.mark.parametrize( + "py, pyc", + list( + product( + [True, False] if Python2.from_stdlib(Python2.mappings(CURRENT), "os.py")[2] else [False], + [True, False] if Python2.from_stdlib(Python2.mappings(CURRENT), "os.pyc")[2] else [False], + ), + ), +) +def test_py_pyc_missing(tmp_path, mocker, session_app_data, py, pyc): + """Ensure that creation can succeed if os.pyc exists (even if os.py has been deleted)""" + previous = Python2.from_stdlib + + def from_stdlib(mappings, name): + path, to, exists = previous(mappings, name) + if name.endswith("py"): + exists = py + elif name.endswith("pyc"): + exists = pyc + return path, to, exists + + mocker.patch.object(Python2, "from_stdlib", side_effect=from_stdlib) + + result = cli_run([str(tmp_path), "--without-pip", "--activators", "", "-vv", "-p", "2"]) + py_at = Python2.from_stdlib(Python2.mappings(CURRENT), "os.py")[1](result.creator, Path("os.py")) + py = pyc is False or py # if pyc is False we fallback to serve the py, which will exist (as we only mock the check) + assert py_at.exists() is py + + pyc_at = Python2.from_stdlib(Python2.mappings(CURRENT), "osc.py")[1](result.creator, Path("os.pyc")) + assert pyc_at.exists() is pyc diff --git a/tests/unit/create/test_interpreters.py b/tests/unit/create/test_interpreters.py index 59a0991..b73964c 100644 --- a/tests/unit/create/test_interpreters.py +++ b/tests/unit/create/test_interpreters.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import sys from uuid import uuid4 @@ -14,7 +12,7 @@ def test_failed_to_find_bad_spec(): of_id = uuid4().hex with pytest.raises(RuntimeError) as context: cli_run(["-p", of_id]) - msg = repr(RuntimeError("failed to find interpreter for Builtin discover of python_spec={!r}".format(of_id))) + msg = repr(RuntimeError(f"failed to find interpreter for Builtin discover of python_spec={of_id!r}")) assert repr(context.value) == msg @@ -29,6 +27,4 @@ def test_failed_to_find_implementation(of_id, mocker): mocker.patch("virtualenv.run.plugin.creators.CreatorSelector._OPTIONS", return_value={}) with pytest.raises(RuntimeError) as context: cli_run(["-p", of_id]) - assert repr(context.value) == repr( - RuntimeError("No virtualenv implementation for {}".format(PythonInfo.current_system())), - ) + assert repr(context.value) == repr(RuntimeError(f"No virtualenv implementation for {PythonInfo.current_system()}")) diff --git a/tests/unit/create/via_global_ref/builtin/conftest.py b/tests/unit/create/via_global_ref/builtin/conftest.py index 3c10636..1c48efd 100644 --- a/tests/unit/create/via_global_ref/builtin/conftest.py +++ b/tests/unit/create/via_global_ref/builtin/conftest.py @@ -1,11 +1,10 @@ import sys +from pathlib import Path import pytest from testing import path from testing.py_info import read_fixture -from virtualenv.util.path import Path - # Allows to import from `testing` into test submodules. sys.path.append(str(Path(__file__).parent)) diff --git a/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py b/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py index 087d590..90fdb38 100644 --- a/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py +++ b/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py @@ -69,7 +69,7 @@ def test_exe_dll_pyd_without_shim(py_info, mock_files): @pytest.mark.parametrize("py_info_name", ["cpython3_win_embed"]) def test_python_zip_if_exists_and_set_in_path(py_info, mock_files): - python_zip_name = "python{}.zip".format(py_info.version_nodot) + python_zip_name = f"python{py_info.version_nodot}.zip" python_zip = path(py_info.prefix, python_zip_name) mock_files(CPYTHON3_PATH, [python_zip]) sources = tuple(CPython3Windows.sources(interpreter=py_info)) @@ -79,7 +79,7 @@ def test_python_zip_if_exists_and_set_in_path(py_info, mock_files): @pytest.mark.parametrize("py_info_name", ["cpython3_win_embed"]) def test_no_python_zip_if_exists_and_not_set_in_path(py_info, mock_files): - python_zip_name = "python{}.zip".format(py_info.version_nodot) + python_zip_name = f"python{py_info.version_nodot}.zip" python_zip = path(py_info.prefix, python_zip_name) py_info.path.remove(python_zip) mock_files(CPYTHON3_PATH, [python_zip]) @@ -90,7 +90,7 @@ def test_no_python_zip_if_exists_and_not_set_in_path(py_info, mock_files): @pytest.mark.parametrize("py_info_name", ["cpython3_win_embed"]) def test_no_python_zip_if_not_exists(py_info, mock_files): - python_zip_name = "python{}.zip".format(py_info.version_nodot) + python_zip_name = f"python{py_info.version_nodot}.zip" python_zip = path(py_info.prefix, python_zip_name) # No `python_zip`, just python.exe file. mock_files(CPYTHON3_PATH, [py_info.system_executable]) diff --git a/tests/unit/create/via_global_ref/builtin/pypy/test_pypy3.py b/tests/unit/create/via_global_ref/builtin/pypy/test_pypy3.py index eb35d7f..4903801 100644 --- a/tests/unit/create/via_global_ref/builtin/pypy/test_pypy3.py +++ b/tests/unit/create/via_global_ref/builtin/pypy/test_pypy3.py @@ -1,11 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import pytest from testing.helpers import contains_exe, contains_ref from testing.path import join as path from virtualenv.create.via_global_ref.builtin.pypy.pypy3 import PyPy3Posix -from virtualenv.info import IS_WIN, PY2 PYPY3_PATH = ( "virtualenv.create.via_global_ref.builtin.pypy.common.Path", @@ -15,7 +12,6 @@ PYPY3_PATH = ( # In `PyPy3Posix.sources()` `host_lib` will be broken in Python 2 for Windows, # so `py_file` will not be in sources. -@pytest.mark.skipif(PY2 and IS_WIN, reason="Can't convert PosixPath") @pytest.mark.parametrize("py_info_name", ["portable_pypy38"]) def test_portable_pypy3_virtualenvs_get_their_libs(py_info, mock_files, mock_pypy_libs): py_file = path(py_info.prefix, "lib/libgdbm.so.4") diff --git a/tests/unit/create/via_global_ref/builtin/testing/helpers.py b/tests/unit/create/via_global_ref/builtin/testing/helpers.py index c42dd39..5ae1df8 100644 --- a/tests/unit/create/via_global_ref/builtin/testing/helpers.py +++ b/tests/unit/create/via_global_ref/builtin/testing/helpers.py @@ -1,7 +1,7 @@ from functools import reduce +from pathlib import Path from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRef -from virtualenv.util.path import Path def is_ref(source): diff --git a/tests/unit/create/via_global_ref/builtin/testing/path.py b/tests/unit/create/via_global_ref/builtin/testing/path.py index 93ebea5..6edfb10 100644 --- a/tests/unit/create/via_global_ref/builtin/testing/path.py +++ b/tests/unit/create/via_global_ref/builtin/testing/path.py @@ -1,18 +1,14 @@ from abc import ABCMeta, abstractmethod from itertools import chain from operator import attrgetter as attr - -from six import add_metaclass - -from virtualenv.util.path import Path +from pathlib import Path def is_name(path): return str(path) == path.name -@add_metaclass(ABCMeta) -class FakeDataABC(object): +class FakeDataABC(metaclass=ABCMeta): """Provides data to mock the `Path`""" @property @@ -67,9 +63,8 @@ class PathMockABC(FakeDataABC, Path): def iterdir(self): if not self.is_dir(): - raise FileNotFoundError("No such mocked dir: '{}'".format(self)) - for path in map(self.joinpath, self.contained_fake_names): - yield path + raise FileNotFoundError(f"No such mocked dir: '{self}'") + yield from map(self.joinpath, self.contained_fake_names) def MetaPathMock(filelist): diff --git a/tests/unit/create/via_global_ref/builtin/testing/py_info.py b/tests/unit/create/via_global_ref/builtin/testing/py_info.py index 20dafb6..f8a7814 100644 --- a/tests/unit/create/via_global_ref/builtin/testing/py_info.py +++ b/tests/unit/create/via_global_ref/builtin/testing/py_info.py @@ -1,16 +1,16 @@ +from pathlib import Path + from virtualenv.discovery.py_info import PythonInfo -from virtualenv.info import PY2 -from virtualenv.util.path import Path def fixture_file(fixture_name): - file_mask = "*{}.json".format(fixture_name) + file_mask = f"*{fixture_name}.json" files = Path(__file__).parent.parent.rglob(file_mask) try: return next(files) except StopIteration: # Fixture file was not found in the testing root and its subdirs. - error = NameError if PY2 else FileNotFoundError + error = FileNotFoundError raise error(file_mask) diff --git a/tests/unit/create/via_global_ref/greet/setup.py b/tests/unit/create/via_global_ref/greet/setup.py index c1b48f6..7206137 100644 --- a/tests/unit/create/via_global_ref/greet/setup.py +++ b/tests/unit/create/via_global_ref/greet/setup.py @@ -8,7 +8,7 @@ setup( ext_modules=[ Extension( "greet", - ["greet{}.c".format(sys.version_info[0])], # extension to package + [f"greet{sys.version_info[0]}.c"], # extension to package ), # C code to compile to run as extension ], ) diff --git a/tests/unit/create/via_global_ref/test_build_c_ext.py b/tests/unit/create/via_global_ref/test_build_c_ext.py index 0086cd1..c26a67b 100644 --- a/tests/unit/create/via_global_ref/test_build_c_ext.py +++ b/tests/unit/create/via_global_ref/test_build_c_ext.py @@ -1,15 +1,13 @@ -from __future__ import absolute_import, unicode_literals - import os import shutil import subprocess +from pathlib import Path +from subprocess import Popen import pytest from virtualenv.discovery.py_info import PythonInfo from virtualenv.run import cli_run -from virtualenv.util.path import Path -from virtualenv.util.subprocess import Popen CURRENT = PythonInfo.current_system() CREATOR_CLASSES = CURRENT.creators().key_to_class @@ -27,7 +25,7 @@ def builtin_shows_marker_missing(): @pytest.mark.xfail( - condition=bool(os.environ.get(str("CI_RUN"))), + condition=bool(os.environ.get("CI_RUN")), strict=False, reason="did not manage to setup CI to run with VC 14.1 C++ compiler, but passes locally", ) diff --git a/tests/unit/discovery/py_info/test_py_info.py b/tests/unit/discovery/py_info/test_py_info.py index 5bbdb31..bb843d1 100644 --- a/tests/unit/discovery/py_info/test_py_info.py +++ b/tests/unit/discovery/py_info/test_py_info.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import copy import itertools import json @@ -8,6 +6,7 @@ import os import sys import sysconfig from collections import namedtuple +from pathlib import Path from textwrap import dedent import pytest @@ -16,7 +15,6 @@ from virtualenv.discovery import cached_py_info from virtualenv.discovery.py_info import PythonInfo, VersionInfo from virtualenv.discovery.py_spec import PythonSpec from virtualenv.info import IS_PYPY, fs_supports_symlink -from virtualenv.util.path import Path CURRENT = PythonInfo.current_system() @@ -58,7 +56,7 @@ def test_bad_exe_py_info_no_raise(tmp_path, caplog, capsys, session_app_data): itertools.chain( [sys.executable], list( - "{}{}{}".format(impl, ".".join(str(i) for i in ver), arch) + f"{impl}{'.'.join(str(i) for i in ver)}{arch}" for impl, ver, arch in itertools.product( ( [CURRENT.implementation] @@ -70,7 +68,7 @@ def test_bad_exe_py_info_no_raise(tmp_path, caplog, capsys, session_app_data): ) ), [sys.version_info[0 : i + 1] for i in range(3)], - ["", "-{}".format(CURRENT.architecture)], + ["", f"-{CURRENT.architecture}"], ) ), ), @@ -83,7 +81,7 @@ def test_satisfy_py_info(spec): def test_satisfy_not_arch(): parsed_spec = PythonSpec.from_string_spec( - "{}-{}".format(CURRENT.implementation, 64 if CURRENT.architecture == 32 else 32), + f"{CURRENT.implementation}-{64 if CURRENT.architecture == 32 else 32}", ) matches = CURRENT.satisfies(parsed_spec, True) assert matches is False @@ -106,7 +104,7 @@ _NON_MATCH_VER = _generate_not_match_current_interpreter_version() @pytest.mark.parametrize("spec", _NON_MATCH_VER) def test_satisfy_not_version(spec): - parsed_spec = PythonSpec.from_string_spec("{}{}".format(CURRENT.implementation, spec)) + parsed_spec = PythonSpec.from_string_spec(f"{CURRENT.implementation}{spec}") matches = CURRENT.satisfies(parsed_spec, True) assert matches is False @@ -251,31 +249,25 @@ def test_system_executable_no_exact_match(target, discovered, position, tmp_path def test_py_info_ignores_distutils_config(monkeypatch, tmp_path): - (tmp_path / "setup.cfg").write_text( - dedent( - """ - [install] - prefix={0}{1}prefix - install_purelib={0}{1}purelib - install_platlib={0}{1}platlib - install_headers={0}{1}headers - install_scripts={0}{1}scripts - install_data={0}{1}data - """.format( - tmp_path, - os.sep, - ), - ), - ) + raw = f""" + [install] + prefix={tmp_path}{os.sep}prefix + install_purelib={tmp_path}{os.sep}purelib + install_platlib={tmp_path}{os.sep}platlib + install_headers={tmp_path}{os.sep}headers + install_scripts={tmp_path}{os.sep}scripts + install_data={tmp_path}{os.sep}data + """ + (tmp_path / "setup.cfg").write_text(dedent(raw)) monkeypatch.chdir(tmp_path) py_info = PythonInfo.from_exe(sys.executable) distutils = py_info.distutils_install for key, value in distutils.items(): - assert not value.startswith(str(tmp_path)), "{}={}".format(key, value) + assert not value.startswith(str(tmp_path)), f"{key}={value}" def test_discover_exe_on_path_non_spec_name_match(mocker): - suffixed_name = "python{}.{}m".format(CURRENT.version_info.major, CURRENT.version_info.minor) + suffixed_name = f"python{CURRENT.version_info.major}.{CURRENT.version_info.minor}m" if sys.platform == "win32": suffixed_name += Path(CURRENT.original_executable).suffix spec = PythonSpec.from_string_spec(suffixed_name) @@ -284,14 +276,14 @@ def test_discover_exe_on_path_non_spec_name_match(mocker): def test_discover_exe_on_path_non_spec_name_not_match(mocker): - suffixed_name = "python{}.{}m".format(CURRENT.version_info.major, CURRENT.version_info.minor) + suffixed_name = f"python{CURRENT.version_info.major}.{CURRENT.version_info.minor}m" if sys.platform == "win32": suffixed_name += Path(CURRENT.original_executable).suffix spec = PythonSpec.from_string_spec(suffixed_name) mocker.patch.object( CURRENT, "original_executable", - str(Path(CURRENT.executable).parent / "e{}".format(suffixed_name)), + str(Path(CURRENT.executable).parent / f"e{suffixed_name}"), ) assert CURRENT.satisfies(spec, impl_must_match=True) is False @@ -311,7 +303,7 @@ def test_py_info_to_system_raises(session_app_data, mocker, caplog, skip_if_test assert result is None log = caplog.records[-1] assert log.levelno == logging.INFO - expected = "ignore {} due cannot resolve system due to RuntimeError('failed to detect ".format(sys.executable) + expected = f"ignore {sys.executable} due cannot resolve system due to RuntimeError('failed to detect " assert expected in log.message @@ -379,6 +371,6 @@ def test_custom_venv_install_scheme_is_prefered(mocker): mocker.patch("sysconfig._INSTALL_SCHEMES", sysconfig_install_schemes) pyinfo = PythonInfo() - pyver = "{}.{}".format(pyinfo.version_info.major, pyinfo.version_info.minor) + pyver = f"{pyinfo.version_info.major}.{pyinfo.version_info.minor}" assert pyinfo.install_path("scripts") == "bin" - assert pyinfo.install_path("purelib").replace(os.sep, "/") == "lib/python{}/site-packages".format(pyver) + assert pyinfo.install_path("purelib").replace(os.sep, "/") == f"lib/python{pyver}/site-packages" diff --git a/tests/unit/discovery/py_info/test_py_info_exe_based_of.py b/tests/unit/discovery/py_info/test_py_info_exe_based_of.py index f232e7a..fffa793 100644 --- a/tests/unit/discovery/py_info/test_py_info_exe_based_of.py +++ b/tests/unit/discovery/py_info/test_py_info_exe_based_of.py @@ -1,13 +1,11 @@ -from __future__ import absolute_import, unicode_literals - import logging import os +from pathlib import Path import pytest from virtualenv.discovery.py_info import EXTENSIONS, PythonInfo from virtualenv.info import IS_WIN, fs_is_case_sensitive, fs_supports_symlink -from virtualenv.util.path import Path CURRENT = PythonInfo.current() @@ -30,9 +28,9 @@ def test_discover_ok(tmp_path, monkeypatch, suffix, impl, version, arch, into, c caplog.set_level(logging.DEBUG) folder = tmp_path / into folder.mkdir(parents=True, exist_ok=True) - name = "{}{}".format(impl, version) + name = f"{impl}{version}" if arch: - name += "-{}".format(arch) + name += f"-{arch}" name += suffix dest = folder / name os.symlink(CURRENT.executable, str(dest)) diff --git a/tests/unit/discovery/test_discovery.py b/tests/unit/discovery/test_discovery.py index c04caea..1e41217 100644 --- a/tests/unit/discovery/test_discovery.py +++ b/tests/unit/discovery/test_discovery.py @@ -1,9 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import logging import os import sys from argparse import Namespace +from pathlib import Path from uuid import uuid4 import pytest @@ -11,8 +10,6 @@ import pytest from virtualenv.discovery.builtin import Builtin, get_interpreter from virtualenv.discovery.py_info import PythonInfo from virtualenv.info import fs_supports_symlink -from virtualenv.util.path import Path -from virtualenv.util.six import ensure_text @pytest.mark.skipif(not fs_supports_symlink(), reason="symlink not supported") @@ -20,29 +17,29 @@ from virtualenv.util.six import ensure_text def test_discovery_via_path(monkeypatch, case, tmp_path, caplog, session_app_data): caplog.set_level(logging.DEBUG) current = PythonInfo.current_system(session_app_data) - core = "somethingVeryCryptic{}".format(".".join(str(i) for i in current.version_info[0:3])) + core = f"somethingVeryCryptic{'.'.join(str(i) for i in current.version_info[0:3])}" name = "somethingVeryCryptic" if case == "lower": name = name.lower() elif case == "upper": name = name.upper() - exe_name = "{}{}{}".format(name, current.version_info.major, ".exe" if sys.platform == "win32" else "") + exe_name = f"{name}{current.version_info.major}{'.exe' if sys.platform == 'win32' else ''}" target = tmp_path / current.install_path("scripts") target.mkdir(parents=True) executable = target / exe_name - os.symlink(sys.executable, ensure_text(str(executable))) + os.symlink(sys.executable, str(executable)) pyvenv_cfg = Path(sys.executable).parents[1] / "pyvenv.cfg" if pyvenv_cfg.exists(): (target / pyvenv_cfg.name).write_bytes(pyvenv_cfg.read_bytes()) - new_path = os.pathsep.join([str(target)] + os.environ.get(str("PATH"), str("")).split(os.pathsep)) - monkeypatch.setenv(str("PATH"), new_path) + new_path = os.pathsep.join([str(target)] + os.environ.get("PATH", "").split(os.pathsep)) + monkeypatch.setenv("PATH", new_path) interpreter = get_interpreter(core, []) assert interpreter is not None def test_discovery_via_path_not_found(tmp_path, monkeypatch): - monkeypatch.setenv(str("PATH"), str(tmp_path)) + monkeypatch.setenv("PATH", str(tmp_path)) interpreter = get_interpreter(uuid4().hex, []) assert interpreter is None diff --git a/tests/unit/discovery/test_py_spec.py b/tests/unit/discovery/test_py_spec.py index 52154f1..430768f 100644 --- a/tests/unit/discovery/test_py_spec.py +++ b/tests/unit/discovery/test_py_spec.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import itertools import sys from copy import copy @@ -16,8 +14,8 @@ def test_bad_py_spec(): assert spec.str_spec == text assert spec.path == text content = vars(spec) - del content[str("str_spec")] - del content[str("path")] + del content["str_spec"] + del content["path"] assert all(v is None for v in content.values()) @@ -79,8 +77,8 @@ def _version_satisfies_pairs(): @pytest.mark.parametrize("req, spec", _version_satisfies_pairs()) def test_version_satisfies_ok(req, spec): - req_spec = PythonSpec.from_string_spec("python{}".format(req)) - sat_spec = PythonSpec.from_string_spec("python{}".format(spec)) + req_spec = PythonSpec.from_string_spec(f"python{req}") + sat_spec = PythonSpec.from_string_spec(f"python{spec}") assert sat_spec.satisfies(req_spec) is True @@ -104,8 +102,8 @@ def _version_not_satisfies_pairs(): @pytest.mark.parametrize("req, spec", _version_not_satisfies_pairs()) def test_version_satisfies_nok(req, spec): - req_spec = PythonSpec.from_string_spec("python{}".format(req)) - sat_spec = PythonSpec.from_string_spec("python{}".format(spec)) + req_spec = PythonSpec.from_string_spec(f"python{req}") + sat_spec = PythonSpec.from_string_spec(f"python{spec}") assert sat_spec.satisfies(req_spec) is False diff --git a/tests/unit/discovery/windows/test_windows_pep514.py b/tests/unit/discovery/windows/test_windows_pep514.py index 70c8509..08ce141 100644 --- a/tests/unit/discovery/windows/test_windows_pep514.py +++ b/tests/unit/discovery/windows/test_windows_pep514.py @@ -1,14 +1,10 @@ -from __future__ import absolute_import, unicode_literals - import sys import textwrap from collections import defaultdict from contextlib import contextmanager +from pathlib import Path import pytest -import six - -from virtualenv.util.path import Path @pytest.mark.skipif(sys.platform != "win32", reason="no Windows registry") @@ -54,14 +50,14 @@ def test_pep514_run(_mock_registry, capsys, caplog): assert not err prefix = "PEP-514 violation in Windows Registry at " expected_logs = [ - "{}HKEY_CURRENT_USER/PythonCore/3.1/SysArchitecture error: invalid format magic".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.2/SysArchitecture error: arch is not string: 100".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.3 error: no ExecutablePath or default for it".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.3 error: could not load exe with value None".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.8/InstallPath error: missing".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.9/SysVersion error: invalid format magic".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.X/SysVersion error: version is not string: 2778".format(prefix), - "{}HKEY_CURRENT_USER/PythonCore/3.X error: invalid format 3.X".format(prefix), + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.1/SysArchitecture error: invalid format magic", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.2/SysArchitecture error: arch is not string: 100", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.3 error: no ExecutablePath or default for it", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.3 error: could not load exe with value None", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.8/InstallPath error: missing", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.9/SysVersion error: invalid format magic", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.X/SysVersion error: version is not string: 2778", + f"{prefix}HKEY_CURRENT_USER/PythonCore/3.X error: invalid format 3.X", ] assert caplog.messages == expected_logs @@ -72,7 +68,7 @@ def _mock_registry(mocker): loc, glob = {}, {} mock_value_str = (Path(__file__).parent / "winreg-mock-values.py").read_text() - six.exec_(mock_value_str, glob, loc) + exec(mock_value_str, glob, loc) enum_collect = loc["enum_collect"] value_collect = loc["value_collect"] key_open = loc["key_open"] @@ -96,7 +92,7 @@ def _mock_registry(mocker): mocker.patch.object(winreg, "QueryValueEx", side_effect=_v) - class Key(object): + class Key: def __init__(self, value): self.value = value @@ -127,12 +123,9 @@ def _mock_registry(mocker): @pytest.fixture() def _collect_winreg_access(mocker): - if six.PY3: - # noinspection PyUnresolvedReferences - from winreg import EnumKey, OpenKeyEx, QueryValueEx - else: - # noinspection PyUnresolvedReferences - from _winreg import EnumKey, OpenKeyEx, QueryValueEx + # noinspection PyUnresolvedReferences + from winreg import EnumKey, OpenKeyEx, QueryValueEx + from virtualenv.discovery.windows.pep514 import winreg hive_open = {} @@ -190,7 +183,7 @@ def _collect_winreg_access(mocker): yield print("") - print("hive_open = {}".format(hive_open)) - print("key_open = {}".format(dict(key_open.items()))) - print("value_collect = {}".format(dict(value_collect.items()))) - print("enum_collect = {}".format(dict(enum_collect.items()))) + print(f"hive_open = {hive_open}") + print(f"key_open = {dict(key_open.items())}") + print(f"value_collect = {dict(value_collect.items())}") + print(f"enum_collect = {dict(enum_collect.items())}") diff --git a/tests/unit/discovery/windows/winreg-mock-values.py b/tests/unit/discovery/windows/winreg-mock-values.py index 4e50549..2ac362f 100644 --- a/tests/unit/discovery/windows/winreg-mock-values.py +++ b/tests/unit/discovery/windows/winreg-mock-values.py @@ -1,10 +1,4 @@ -import six - -if six.PY3: - import winreg -else: - # noinspection PyUnresolvedReferences - import _winreg as winreg +import winreg hive_open = { (winreg.HKEY_CURRENT_USER, "Software\\Python", 0, winreg.KEY_READ): 78701856, diff --git a/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py b/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py index fdbd4d6..6d55f1d 100644 --- a/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py +++ b/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py @@ -1,10 +1,8 @@ -from __future__ import absolute_import, unicode_literals - import contextlib import os -import subprocess import sys from stat import S_IWGRP, S_IWOTH, S_IWUSR +from subprocess import Popen, check_call from threading import Thread import pytest @@ -15,8 +13,6 @@ from virtualenv.info import fs_supports_symlink from virtualenv.run import cli_run from virtualenv.seed.wheels.embed import BUNDLE_FOLDER, BUNDLE_SUPPORT from virtualenv.util.path import safe_delete -from virtualenv.util.six import ensure_text -from virtualenv.util.subprocess import Popen @pytest.mark.slow @@ -25,12 +21,12 @@ def test_seed_link_via_app_data(tmp_path, coverage_env, current_fastest, copies) current = PythonInfo.current_system() bundle_ver = BUNDLE_SUPPORT[current.version_release_str] create_cmd = [ - ensure_text(str(tmp_path / "en v")), # space in the name to ensure generated scripts work when path has space + str(tmp_path / "en v"), # space in the name to ensure generated scripts work when path has space "--no-periodic-update", "--seeder", "app-data", "--extra-search-dir", - ensure_text(str(BUNDLE_FOLDER)), + str(BUNDLE_FOLDER), "--download", "--pip", bundle_ver["pip"].split("-")[1], @@ -56,16 +52,16 @@ def test_seed_link_via_app_data(tmp_path, coverage_env, current_fastest, copies) assert pip in files_post_first_create assert setuptools in files_post_first_create for pip_exe in [ - result.creator.script_dir / "pip{}{}".format(suffix, result.creator.exe.suffix) + result.creator.script_dir / f"pip{suffix}{result.creator.exe.suffix}" for suffix in ( "", - "{}".format(current.version_info.major), - "{}.{}".format(current.version_info.major, current.version_info.minor), - "-{}.{}".format(current.version_info.major, current.version_info.minor), + f"{current.version_info.major}", + f"{current.version_info.major}.{current.version_info.minor}", + f"-{current.version_info.major}.{current.version_info.minor}", ) ]: assert pip_exe.exists() - process = Popen([ensure_text(str(pip_exe)), "--version", "--disable-pip-version-check"]) + process = Popen([str(pip_exe), "--version", "--disable-pip-version-check"]) _, __ = process.communicate() assert not process.returncode @@ -87,7 +83,7 @@ def test_seed_link_via_app_data(tmp_path, coverage_env, current_fastest, copies) assert setuptools not in files_post_first_uninstall # install a different setuptools to test that virtualenv removes this before installing new - version = "setuptools<{}".format(bundle_ver["setuptools"].split("-")[1]) + version = f"setuptools<{bundle_ver['setuptools'].split('-')[1]}" install_cmd = [str(result.creator.script("pip")), "--verbose", "--disable-pip-version-check", "install", version] process = Popen(install_cmd) process.communicate() @@ -112,13 +108,13 @@ def test_seed_link_via_app_data(tmp_path, coverage_env, current_fastest, copies) # pip is greedy here, removing all packages removes the site-package too if site_package.exists(): purelib = result.creator.purelib - patch_files = {purelib / "{}.{}".format("_virtualenv", i) for i in ("py", "pyc", "pth")} + patch_files = {purelib / f"{'_virtualenv'}.{i}" for i in ("py", "pyc", "pth")} patch_files.add(purelib / "__pycache__") post_run = set(site_package.iterdir()) - patch_files assert not post_run, "\n".join(str(i) for i in post_run) - if sys.version_info[0:2] == (3, 4) and os.environ.get(str("PIP_REQ_TRACKER")): - os.environ.pop(str("PIP_REQ_TRACKER")) + if sys.version_info[0:2] == (3, 4) and os.environ.get("PIP_REQ_TRACKER"): + os.environ.pop("PIP_REQ_TRACKER") @contextlib.contextmanager @@ -167,7 +163,7 @@ def test_populated_read_only_cache_and_symlinked_app_data(tmp_path, current_fast ] assert cli_run(cmd) - subprocess.check_call((str(dest.joinpath("bin/python")), "-c", "import pip")) + check_call((str(dest.joinpath("bin/python")), "-c", "import pip")) cached_py_info._CACHE.clear() # necessary to re-trigger py info discovery safe_delete(dest) @@ -175,7 +171,7 @@ def test_populated_read_only_cache_and_symlinked_app_data(tmp_path, current_fast # should succeed with special flag when read-only with read_only_dir(temp_app_data): assert cli_run(["--read-only-app-data"] + cmd) - subprocess.check_call((str(dest.joinpath("bin/python")), "-c", "import pip")) + check_call((str(dest.joinpath("bin/python")), "-c", "import pip")) @pytest.mark.skipif(sys.platform == "win32", reason="Windows only applies R/O to files") @@ -205,7 +201,7 @@ def test_populated_read_only_cache_and_copied_app_data(tmp_path, current_fastest @pytest.mark.slow @pytest.mark.parametrize("pkg", ["pip", "setuptools", "wheel"]) def test_base_bootstrap_link_via_app_data_no(tmp_path, coverage_env, current_fastest, session_app_data, pkg): - create_cmd = [str(tmp_path), "--seeder", "app-data", "--no-{}".format(pkg)] + create_cmd = [str(tmp_path), "--seeder", "app-data", f"--no-{pkg}"] result = cli_run(create_cmd) assert not (result.creator.purelib / pkg).exists() for key in {"pip", "setuptools", "wheel"} - {pkg}: @@ -236,7 +232,7 @@ def _run_parallel_threads(tmp_path): as_str = str(exception) exceptions.append(as_str) - threads = [Thread(target=_run, args=("env{}".format(i),)) for i in range(1, 3)] + threads = [Thread(target=_run, args=(f"env{i}",)) for i in range(1, 3)] for thread in threads: thread.start() for thread in threads: diff --git a/tests/unit/seed/embed/test_pip_invoke.py b/tests/unit/seed/embed/test_pip_invoke.py index f2e7a33..a297115 100644 --- a/tests/unit/seed/embed/test_pip_invoke.py +++ b/tests/unit/seed/embed/test_pip_invoke.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import itertools import sys from shutil import copy2 @@ -17,7 +15,7 @@ from virtualenv.seed.wheels.embed import BUNDLE_FOLDER, BUNDLE_SUPPORT def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env, mocker, current_fastest, no): extra_search_dir = tmp_path / "extra" extra_search_dir.mkdir() - for_py_version = "{}.{}".format(*sys.version_info[0:2]) + for_py_version = f"{sys.version_info.major}.{sys.version_info.minor}" new = BUNDLE_SUPPORT[for_py_version] for wheel_filename in BUNDLE_SUPPORT[for_py_version].values(): copy2(str(BUNDLE_FOLDER / wheel_filename), str(extra_search_dir)) @@ -66,9 +64,9 @@ def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env, mocker, current_f str(tmp_path / "app-data"), ] for dist, version in versions.items(): - create_cmd.extend(["--{}".format(dist), version]) + create_cmd.extend([f"--{dist}", version]) if no: - create_cmd.append("--no-{}".format(no)) + create_cmd.append(f"--no-{no}") result = cli_run(create_cmd) coverage_env() diff --git a/tests/unit/seed/wheels/test_acquire.py b/tests/unit/seed/wheels/test_acquire.py index dcce0f6..c4f4338 100644 --- a/tests/unit/seed/wheels/test_acquire.py +++ b/tests/unit/seed/wheels/test_acquire.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import, unicode_literals - import os import sys from datetime import datetime +from pathlib import Path from subprocess import CalledProcessError import pytest from virtualenv.app_data import AppDataDiskFolder -from virtualenv.info import IS_PYPY, PY2 from virtualenv.seed.wheels.acquire import download_wheel, get_wheel, pip_wheel_env_run from virtualenv.seed.wheels.embed import BUNDLE_FOLDER, get_embed_wheel from virtualenv.seed.wheels.periodic_update import dump_datetime from virtualenv.seed.wheels.util import Wheel, discover_wheels -from virtualenv.util.path import Path @pytest.fixture(autouse=True) @@ -41,7 +38,7 @@ def test_download_wheel_bad_output(mocker, for_py_version, session_app_data): as_path.iterdir.return_value = [i.path for i in available] result = download_wheel( - distribution, "=={}".format(embed.version), for_py_version, [], session_app_data, as_path, os.environ + distribution, f"=={embed.version}", for_py_version, [], session_app_data, as_path, os.environ ) assert result.path == embed.path @@ -56,11 +53,8 @@ def test_download_fails(mocker, for_py_version, session_app_data): with pytest.raises(CalledProcessError) as context: download_wheel("pip", "==1", for_py_version, [], session_app_data, as_path, os.environ), exc = context.value - if sys.version_info < (3, 5): - assert exc.output == "outerr" - else: - assert exc.output == "out" - assert exc.stderr == "err" + assert exc.output == "out" + assert exc.stderr == "err" assert exc.returncode == 1 assert [ sys.executable, @@ -111,7 +105,6 @@ def test_get_wheel_download_not_called(mocker, for_py_version, session_app_data, assert write.call_count == 0 -@pytest.mark.skipif(IS_PYPY and PY2, reason="mocker.spy failing on PyPy 2.x") def test_get_wheel_download_cached(tmp_path, freezer, mocker, for_py_version, downloaded_wheel): from virtualenv.app_data.via_disk_folder import JSONStoreDisk diff --git a/tests/unit/seed/wheels/test_acquire_find_wheel.py b/tests/unit/seed/wheels/test_acquire_find_wheel.py index 18c4692..de3efa0 100644 --- a/tests/unit/seed/wheels/test_acquire_find_wheel.py +++ b/tests/unit/seed/wheels/test_acquire_find_wheel.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import pytest from virtualenv.seed.wheels.acquire import find_compatible_in_house @@ -14,13 +12,13 @@ def test_find_latest(for_py_version): def test_find_exact(for_py_version): expected = get_embed_wheel("setuptools", for_py_version) - result = find_compatible_in_house("setuptools", "=={}".format(expected.version), for_py_version, BUNDLE_FOLDER) + result = find_compatible_in_house("setuptools", f"=={expected.version}", for_py_version, BUNDLE_FOLDER) assert result.path == expected.path def test_find_less_than(for_py_version): latest = get_embed_wheel("setuptools", MAX) - result = find_compatible_in_house("setuptools", "<{}".format(latest.version), MAX, BUNDLE_FOLDER) + result = find_compatible_in_house("setuptools", f"<{latest.version}", MAX, BUNDLE_FOLDER) assert result is not None assert result.path != latest.path diff --git a/tests/unit/seed/wheels/test_bundle.py b/tests/unit/seed/wheels/test_bundle.py index 767a2b4..d528e3c 100644 --- a/tests/unit/seed/wheels/test_bundle.py +++ b/tests/unit/seed/wheels/test_bundle.py @@ -1,7 +1,6 @@ -from __future__ import absolute_import, unicode_literals - import os from datetime import datetime +from pathlib import Path import pytest @@ -10,7 +9,6 @@ from virtualenv.seed.wheels.bundle import from_bundle from virtualenv.seed.wheels.embed import get_embed_wheel from virtualenv.seed.wheels.periodic_update import dump_datetime from virtualenv.seed.wheels.util import Version, Wheel -from virtualenv.util.path import Path @pytest.fixture(scope="module") diff --git a/tests/unit/seed/wheels/test_periodic_update.py b/tests/unit/seed/wheels/test_periodic_update.py index 7adb359..8827076 100644 --- a/tests/unit/seed/wheels/test_periodic_update.py +++ b/tests/unit/seed/wheels/test_periodic_update.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import json import os import subprocess @@ -7,12 +5,13 @@ import sys from collections import defaultdict from contextlib import contextmanager from datetime import datetime, timedelta +from io import StringIO +from itertools import zip_longest +from pathlib import Path from textwrap import dedent +from urllib.error import URLError import pytest -from six import StringIO -from six.moves import zip_longest -from six.moves.urllib.error import URLError from virtualenv import cli_run from virtualenv.app_data import AppDataDiskFolder @@ -29,7 +28,6 @@ from virtualenv.seed.wheels.periodic_update import ( release_date_for_wheel_path, trigger_update, ) -from virtualenv.util.path import Path from virtualenv.util.subprocess import CREATE_NO_WINDOW @@ -82,7 +80,7 @@ def test_pick_periodic_update(tmp_path, session_app_data, mocker, for_py_version assert read_dict.call_count == 1 installed = list(i.name for i in result.creator.purelib.iterdir() if i.suffix == ".dist-info") - assert "setuptools-{}.dist-info".format(current.version) in installed + assert f"setuptools-{current.version}.dist-info" in installed def test_periodic_update_stops_at_current(mocker, session_app_data, for_py_version): @@ -251,7 +249,7 @@ def test_periodic_update_trigger(u_log, mocker, for_py_version, session_app_data def test_trigger_update_no_debug(for_py_version, session_app_data, tmp_path, mocker, monkeypatch): - monkeypatch.delenv(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE"), raising=False) + monkeypatch.delenv("_VIRTUALENV_PERIODIC_UPDATE_INLINE", raising=False) current = get_embed_wheel("setuptools", for_py_version) process = mocker.MagicMock() process.communicate.return_value = None, None @@ -292,7 +290,7 @@ def test_trigger_update_no_debug(for_py_version, session_app_data, tmp_path, moc def test_trigger_update_debug(for_py_version, session_app_data, tmp_path, mocker, monkeypatch): - monkeypatch.setenv(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE"), str("1")) + monkeypatch.setenv("_VIRTUALENV_PERIODIC_UPDATE_INLINE", "1") current = get_embed_wheel("pip", for_py_version) process = mocker.MagicMock() diff --git a/tests/unit/seed/wheels/test_wheels_util.py b/tests/unit/seed/wheels/test_wheels_util.py index e487797..765dbbd 100644 --- a/tests/unit/seed/wheels/test_wheels_util.py +++ b/tests/unit/seed/wheels/test_wheels_util.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import pytest from virtualenv.seed.wheels.embed import MAX, get_embed_wheel diff --git a/tests/unit/test_run.py b/tests/unit/test_run.py index 53849f5..513a76e 100644 --- a/tests/unit/test_run.py +++ b/tests/unit/test_run.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import logging import pytest diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index ca3f31f..d2a5906 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -1,11 +1,3 @@ -from __future__ import absolute_import, unicode_literals - -import subprocess -import sys - -import pytest - -from virtualenv.info import IS_WIN, PY2 from virtualenv.util.subprocess import run_cmd @@ -14,14 +6,3 @@ def test_run_fail(tmp_path): assert err assert not out assert code - - -@pytest.mark.skipif(not (PY2 and IS_WIN), reason="subprocess patch only applied on Windows python2") -def test_windows_py2_cwd_works(tmp_path): - cwd = str(tmp_path) - result = subprocess.check_output( - [sys.executable, "-c", "import os; print(os.getcwd())"], - cwd=cwd, - universal_newlines=True, - ) - assert result == "{}\n".format(cwd) @@ -6,9 +6,7 @@ envlist = py37 py36 py35 - py27 pypy3 - pypy2 coverage readme docs @@ -29,7 +27,7 @@ setenv = COVERAGE_PROCESS_START = {toxinidir}/.coveragerc PYTHONIOENCODING = utf-8 _COVERAGE_SRC = {envsitepackagesdir}/virtualenv - {py27,pypy2, upgrade}: PYTHONWARNINGS = ignore:DEPRECATION::pip._internal.cli.base_command + {upgrade}: PYTHONWARNINGS = ignore:DEPRECATION::pip._internal.cli.base_command extras = testing commands = |
