From 33d2628d7b1539521b761623523c0bb4e2e3dee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 21 May 2021 17:27:45 +0200 Subject: [PATCH] Port the existing export command to a plugin --- .flake8 | 19 + .gitignore | 39 + .pre-commit-config.yaml | 29 + README.md | 48 + poetry.lock | 1130 ++++++++++++ pyproject.toml | 61 + src/poetry_export_plugin/__init__.py | 0 src/poetry_export_plugin/console/__init__.py | 0 .../console/commands/__init__.py | 0 .../console/commands/export.py | 73 + src/poetry_export_plugin/exporter.py | 170 ++ src/poetry_export_plugin/plugins.py | 42 + tests/__init__.py | 0 tests/conftest.py | 192 ++ tests/console/__init__.py | 0 tests/console/conftest.py | 71 + tests/console/test_export.py | 115 ++ .../demo-0.1.0-py2.py3-none-any.whl | Bin 0 -> 1116 bytes .../fixtures/distributions/demo-0.1.0.tar.gz | Bin 0 -> 961 bytes .../bar/pyproject.toml | 11 + .../foo/pyproject.toml | 11 + .../project_with_nested_local/pyproject.toml | 12 + .../quix/pyproject.toml | 10 + .../project_with_setup/my_package/__init__.py | 0 .../project_with_setup.egg-info/PKG-INFO | 10 + .../project_with_setup.egg-info/SOURCES.txt | 7 + .../dependency_links.txt | 0 .../project_with_setup.egg-info/requires.txt | 2 + .../project_with_setup.egg-info/top_level.txt | 1 + tests/fixtures/project_with_setup/setup.py | 19 + tests/fixtures/sample_project/README.rst | 2 + tests/fixtures/sample_project/pyproject.toml | 58 + tests/fixtures/simple_project/README.rst | 2 + .../dist/simple-project-1.2.3.tar.gz | Bin 0 -> 1106 bytes .../simple_project-1.2.3-py2.py3-none-any.whl | Bin 0 -> 1320 bytes tests/fixtures/simple_project/pyproject.toml | 35 + .../simple_project/simple_project/__init__.py | 0 tests/helpers.py | 94 + tests/test_exporter.py | 1605 +++++++++++++++++ 39 files changed, 3868 insertions(+) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 README.md create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 src/poetry_export_plugin/__init__.py create mode 100644 src/poetry_export_plugin/console/__init__.py create mode 100644 src/poetry_export_plugin/console/commands/__init__.py create mode 100644 src/poetry_export_plugin/console/commands/export.py create mode 100644 src/poetry_export_plugin/exporter.py create mode 100644 src/poetry_export_plugin/plugins.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/console/__init__.py create mode 100644 tests/console/conftest.py create mode 100644 tests/console/test_export.py create mode 100644 tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl create mode 100644 tests/fixtures/distributions/demo-0.1.0.tar.gz create mode 100644 tests/fixtures/project_with_nested_local/bar/pyproject.toml create mode 100644 tests/fixtures/project_with_nested_local/foo/pyproject.toml create mode 100644 tests/fixtures/project_with_nested_local/pyproject.toml create mode 100644 tests/fixtures/project_with_nested_local/quix/pyproject.toml create mode 100644 tests/fixtures/project_with_setup/my_package/__init__.py create mode 100644 tests/fixtures/project_with_setup/project_with_setup.egg-info/PKG-INFO create mode 100644 tests/fixtures/project_with_setup/project_with_setup.egg-info/SOURCES.txt create mode 100644 tests/fixtures/project_with_setup/project_with_setup.egg-info/dependency_links.txt create mode 100644 tests/fixtures/project_with_setup/project_with_setup.egg-info/requires.txt create mode 100644 tests/fixtures/project_with_setup/project_with_setup.egg-info/top_level.txt create mode 100644 tests/fixtures/project_with_setup/setup.py create mode 100644 tests/fixtures/sample_project/README.rst create mode 100644 tests/fixtures/sample_project/pyproject.toml create mode 100644 tests/fixtures/simple_project/README.rst create mode 100644 tests/fixtures/simple_project/dist/simple-project-1.2.3.tar.gz create mode 100644 tests/fixtures/simple_project/dist/simple_project-1.2.3-py2.py3-none-any.whl create mode 100644 tests/fixtures/simple_project/pyproject.toml create mode 100644 tests/fixtures/simple_project/simple_project/__init__.py create mode 100644 tests/helpers.py create mode 100644 tests/test_exporter.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000000..be7d44f3549 --- /dev/null +++ b/.flake8 @@ -0,0 +1,19 @@ +[flake8] +max-line-length = 88 +ignore = E501, E203, W503 +per-file-ignores = + __init__.py:F401 +exclude = + .git + __pycache__ + setup.py + build + dist + releases + .venv + .tox + .mypy_cache + .pytest_cache + .vscode + .github + tests/fixtures/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..39b83c5084f --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*.pyc + +# Packages +*.egg +!/tests/**/*.egg +/*.egg-info +/dist/* +build +_build +.cache +*.so + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +.pytest_cache + +.DS_Store +.idea/* +.python-version +.vscode/* + +/test.py +/test_*.* + +/setup.cfg +MANIFEST.in +/setup.py +/docs/site/* +/tests/fixtures/simple_project/setup.py +.mypy_cache + +.venv +/releases/* +pip-wheel-metadata +/poetry.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..8f0e43b0b51 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + + - repo: https://github.com/timothycrosley/isort + rev: 5.7.0 + hooks: + - id: isort + additional_dependencies: [toml] + exclude: ^.*/?setup\.py$ + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: trailing-whitespace + exclude: | + (?x)( + ^tests/.*/fixtures/.* + ) + - id: end-of-file-fixer + exclude: ^tests/.*/fixtures/.* + - id: debug-statements diff --git a/README.md b/README.md new file mode 100644 index 00000000000..cd4568c1929 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Poetry export plugin + +This package is a plugin that allows the export of locked packages to various formats. + +**Note**: For now, only the `requirements.txt` format is available. + +This plugin provides the same features as the existing `export` command of POetry which it will eventually replace. + + +## Installation + +The easiest way to install the `export` plugin is via the `plugin add` command of Poetry. + +```bash +poetry plugin add poetry-export-plugin +``` + +If you used `pipx` to install Poetry you can add the plugin via the `pipx inject` command. + +```bash +pipx inject poetry poetry-export-plugin +``` + +Otherwise, if you used `pip` to install Poetry you can add the plugin packages via the `pip install` command. + +```bash +pip install poetry-export-plugin +``` + + +## Usage + +The plugin provides an `export` command to export to the desired format. + +```bash +poetry export -f requirements.txt --output requirements.txt +``` + +**Note**: Only the `requirements.txt` format is currently supported. + +### Available options + +* `--format (-f)`: The format to export to (default: `requirements.txt`). Currently, only `requirements.txt` is supported. +* `--output (-o)`: The name of the output file. If omitted, print to standard output. +* `--dev`: Include development dependencies. +* `--extras (-E)`: Extra sets of dependencies to include. +* `--without-hashes`: Exclude hashes from the exported file. +* `--with-credentials`: Include credentials for extra indices. diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000000..fe7b428a38e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1130 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "21.5b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +mypy-extensions = ">=0.4.3" +pathspec = ">=0.8.1,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors"] +python2 = ["typed-ast (>=1.4.2)"] + +[[package]] +name = "cachecontrol" +version = "0.12.6" +description = "httplib2 caching for requests" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2" +requests = "*" + +[package.extras] +filecache = ["lockfile (>=0.9)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "cachy" +version = "0.3.0" +description = "Cachy provides a simple yet effective caching library." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +redis = ["redis (>=3.3.6,<4.0.0)"] +memcached = ["python-memcached (>=1.59,<2.0)"] +msgpack = ["msgpack-python (>=0.5,<0.6)"] + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.3.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cleo" +version = "1.0.0a3" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +crashtest = ">=0.3.1,<0.4.0" +pylev = ">=1.3.0,<2.0.0" + +[[package]] +name = "click" +version = "8.0.1" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "crashtest" +version = "0.3.1" +description = "Manage Python errors with ease" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "cryptography" +version = "3.4.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "dataclasses" +version = "0.8" +description = "A backport of the dataclasses module for Python 3.6" +category = "main" +optional = false +python-versions = ">=3.6, <3.7" + +[[package]] +name = "distlib" +version = "0.3.1" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "entrypoints" +version = "0.3" +description = "Discover and load entry points from installed packages." +category = "main" +optional = false +python-versions = ">=2.7" + +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["genshi", "chardet (>=2.2)", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "identify" +version = "2.2.4" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.extras] +license = ["editdistance-s"] + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "1.7.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] + +[[package]] +name = "importlib-resources" +version = "5.1.3" +description = "Read resources from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "jeepney" +version = "0.6.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "keyring" +version = "22.3.0" +description = "Store and access your passwords safely." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=1", markers = "python_version < \"3.8\""} +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "msgpack" +version = "1.0.2" +description = "MessagePack (de)serializer." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pkginfo" +version = "1.7.0" +description = "Query metadatdata from sdists / bdists / installed packages." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +testing = ["nose", "coverage"] + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "poetry" +version = "1.2.0a1" +description = "Python dependency management and packaging made easy." +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +cachecontrol = {version = ">=0.12.4,<0.13.0", extras = ["filecache"]} +cachy = ">=0.3.0,<0.4.0" +cleo = ">=1.0.0a1,<2.0.0" +crashtest = ">=0.3.0,<0.4.0" +dataclasses = {version = ">=0.8,<0.9", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} +entrypoints = ">=0.3,<0.4" +html5lib = ">=1.0,<2.0" +importlib-metadata = {version = ">=1.6.0,<2.0.0", markers = "python_version < \"3.8\""} +keyring = ">=21.2.0" +packaging = ">=20.4,<21.0" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.5,<2.0" +poetry-core = ">=1.1.0a5,<1.2.0" +requests = ">=2.18,<3.0" +requests-toolbelt = ">=0.9.1,<0.10.0" +shellingham = ">=1.1,<2.0" +tomlkit = ">=0.7.0,<1.0.0" +virtualenv = ">=20.4.3,<20.4.5" + +[[package]] +name = "poetry-core" +version = "1.1.0a5" +description = "Poetry PEP 517 Build Backend" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +dataclasses = {version = ">=0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} + +[[package]] +name = "pre-commit" +version = "2.12.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = "*", markers = "python_version < \"3.7\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pylev" +version = "1.3.0" +description = "A pure Python Levenshtein implementation that's not freaking GPL'd." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.4" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-mock" +version = "3.6.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "tox", "pytest-asyncio"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "requests-toolbelt" +version = "0.9.1" +description = "A utility belt for advanced users of python-requests" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "secretstorage" +version = "3.3.1" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "shellingham" +version = "1.4.0" +description = "Tool to Detect Surrounding Shell" +category = "main" +optional = false +python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,>=2.6" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomlkit" +version = "0.7.2" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] + +[[package]] +name = "virtualenv" +version = "20.4.4" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.4.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "213650f880bbbd88076afcb13895ce880996f763fb2acc14d1e00fbfe03fd372" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +black = [ + {file = "black-21.5b1-py3-none-any.whl", hash = "sha256:8a60071a0043876a4ae96e6c69bd3a127dad2c1ca7c8083573eb82f92705d008"}, + {file = "black-21.5b1.tar.gz", hash = "sha256:23695358dbcb3deafe7f0a3ad89feee5999a46be5fec21f4f1d108be0bcdb3b1"}, +] +cachecontrol = [ + {file = "CacheControl-0.12.6-py2.py3-none-any.whl", hash = "sha256:10d056fa27f8563a271b345207402a6dcce8efab7e5b377e270329c62471b10d"}, + {file = "CacheControl-0.12.6.tar.gz", hash = "sha256:be9aa45477a134aee56c8fac518627e1154df063e85f67d4f83ce0ccc23688e8"}, +] +cachy = [ + {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, + {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] +cfgv = [ + {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, + {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +cleo = [ + {file = "cleo-1.0.0a3-py3-none-any.whl", hash = "sha256:46b2f970d06caa311d1e12a1013b0ce2a8149502669ac82cbedafb9e0bfdbccd"}, + {file = "cleo-1.0.0a3.tar.gz", hash = "sha256:9c1c8dd06635c936f45e4649aa2f7581517b4d52c7a9414d1b42586e63c2fe5d"}, +] +click = [ + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +crashtest = [ + {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, + {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, +] +cryptography = [ + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, +] +dataclasses = [ + {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, + {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, +] +distlib = [ + {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, + {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, +] +entrypoints = [ + {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, + {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +html5lib = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] +identify = [ + {file = "identify-2.2.4-py2.py3-none-any.whl", hash = "sha256:ad9f3fa0c2316618dc4d840f627d474ab6de106392a4f00221820200f490f5a8"}, + {file = "identify-2.2.4.tar.gz", hash = "sha256:9bcc312d4e2fa96c7abebcdfb1119563b511b5e3985ac52f60d9116277865b2e"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, + {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, +] +importlib-resources = [ + {file = "importlib_resources-5.1.3-py3-none-any.whl", hash = "sha256:3b9c774e0e7e8d9c069eb2fe6aee7e9ae71759a381dec02eb45249fba7f38713"}, + {file = "importlib_resources-5.1.3.tar.gz", hash = "sha256:0786b216556e53b34156263ab654406e543a8b0d9b1381019e25a36a09263c36"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] +jeepney = [ + {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, + {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, +] +keyring = [ + {file = "keyring-22.3.0-py3-none-any.whl", hash = "sha256:2bc8363ebdd63886126a012057a85c8cb6e143877afa02619ac7dbc9f38a207b"}, + {file = "keyring-22.3.0.tar.gz", hash = "sha256:16927a444b2c73f983520a48dec79ddab49fe76429ea05b8d528d778c8339522"}, +] +lockfile = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] +msgpack = [ + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841"}, + {file = "msgpack-1.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f"}, + {file = "msgpack-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2"}, + {file = "msgpack-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc"}, + {file = "msgpack-1.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1"}, + {file = "msgpack-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a"}, + {file = "msgpack-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b"}, + {file = "msgpack-1.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83"}, + {file = "msgpack-1.0.2-cp38-cp38-win32.whl", hash = "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9"}, + {file = "msgpack-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009"}, + {file = "msgpack-1.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e"}, + {file = "msgpack-1.0.2-cp39-cp39-win32.whl", hash = "sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33"}, + {file = "msgpack-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f"}, + {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pkginfo = [ + {file = "pkginfo-1.7.0-py2.py3-none-any.whl", hash = "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"}, + {file = "pkginfo-1.7.0.tar.gz", hash = "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +poetry = [ + {file = "poetry-1.2.0a1-py3-none-any.whl", hash = "sha256:1038d7b10750f707fc953e02e9d648807736f63c0ea9f5e0a089fb7d6419aff9"}, + {file = "poetry-1.2.0a1.tar.gz", hash = "sha256:ef72ae8017bf866df55d35c5217116264b886c834bb9539fddbdf1d55f132600"}, +] +poetry-core = [ + {file = "poetry-core-1.1.0a5.tar.gz", hash = "sha256:1b886de26026865325eae86a5d12eb154b80c0add8067c106eb706757594d85f"}, + {file = "poetry_core-1.1.0a5-py3-none-any.whl", hash = "sha256:b347525c1417e9b5c6aee52967eff98c0886853a9e8ab1b9dfb2659913dd37bc"}, +] +pre-commit = [ + {file = "pre_commit-2.12.1-py2.py3-none-any.whl", hash = "sha256:70c5ec1f30406250b706eda35e868b87e3e4ba099af8787e3e8b4b01e84f4712"}, + {file = "pre_commit-2.12.1.tar.gz", hash = "sha256:900d3c7e1bf4cf0374bb2893c24c23304952181405b4d88c9c40b72bda1bb8a9"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pylev = [ + {file = "pylev-1.3.0-py2.py3-none-any.whl", hash = "sha256:1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"}, + {file = "pylev-1.3.0.tar.gz", hash = "sha256:063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] +pytest-mock = [ + {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, + {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, +] +pywin32-ctypes = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +requests-toolbelt = [ + {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, + {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, +] +secretstorage = [ + {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, + {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, +] +shellingham = [ + {file = "shellingham-1.4.0-py2.py3-none-any.whl", hash = "sha256:536b67a0697f2e4af32ab176c00a50ac2899c5a05e0d8e2dadac8e58888283f9"}, + {file = "shellingham-1.4.0.tar.gz", hash = "sha256:4855c2458d6904829bd34c299f11fdeed7cfefbf8a2c522e4caea6cd76b3171e"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomlkit = [ + {file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"}, + {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +urllib3 = [ + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, +] +virtualenv = [ + {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"}, + {file = "virtualenv-20.4.4.tar.gz", hash = "sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2"}, +] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] +zipp = [ + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..e48bb3106d7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[tool.poetry] +name = "poetry-export-plugin" +version = "0.1.0" +description = "Poetry plugin to export the dependencies to various formats" +authors = ["Sébastien Eustace "] +license = "MIT" +readme = "README.md" + +packages = [ + { include = "poetry_export_plugin", from = "src" } +] + +include = [ + { path = "tests", format = "sdist" } +] + +[tool.poetry.dependencies] +python = "^3.6" +poetry = "^1.2.0a1" + +[tool.poetry.dev-dependencies] +black = { version = "^21.4b1", python = "^3.6.2"} +isort = "^5.8.0" +pre-commit = { version = "^2.6", python = "^3.6.1" } +pytest = "^6.2.3" +pytest-mock = "^3.6.1" + +[tool.poetry.plugins."poetry.application.plugin"] +export = "poetry_export_plugin.plugins:ExportApplicationPlugin" + +[tool.black] +line-length = 88 +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' + +[tool.isort] +profile = "black" +force_single_line = true +atomic = true +include_trailing_comma = true +lines_after_imports = 2 +lines_between_types = 1 +use_parentheses = true + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/src/poetry_export_plugin/__init__.py b/src/poetry_export_plugin/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/poetry_export_plugin/console/__init__.py b/src/poetry_export_plugin/console/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/poetry_export_plugin/console/commands/__init__.py b/src/poetry_export_plugin/console/commands/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/poetry_export_plugin/console/commands/export.py b/src/poetry_export_plugin/console/commands/export.py new file mode 100644 index 00000000000..1ffb35644e9 --- /dev/null +++ b/src/poetry_export_plugin/console/commands/export.py @@ -0,0 +1,73 @@ +from cleo.helpers import option +from poetry.console.commands.command import Command + +from poetry_export_plugin.exporter import Exporter + + +class ExportCommand(Command): + + name = "export" + description = "Exports the lock file to alternative formats." + + options = [ + option( + "format", + "f", + "Format to export to. Currently, only requirements.txt is supported.", + flag=False, + default=Exporter.FORMAT_REQUIREMENTS_TXT, + ), + option("output", "o", "The name of the output file.", flag=False), + option("without-hashes", None, "Exclude hashes from the exported file."), + option("dev", None, "Include development dependencies."), + option( + "extras", + "E", + "Extra sets of dependencies to include.", + flag=False, + multiple=True, + ), + option("with-credentials", None, "Include credentials for extra indices."), + ] + + def handle(self) -> None: + fmt = self.option("format") + + if fmt not in Exporter.ACCEPTED_FORMATS: + raise ValueError("Invalid export format: {}".format(fmt)) + + output = self.option("output") + + locker = self.poetry.locker + if not locker.is_locked(): + self.line("The lock file does not exist. Locking.") + options = [] + if self.io.is_debug(): + options.append(("-vvv", None)) + elif self.io.is_very_verbose(): + options.append(("-vv", None)) + elif self.io.is_verbose(): + options.append(("-v", None)) + + self.call("lock", " ".join(options)) + + if not locker.is_fresh(): + self.line( + "" + "Warning: The lock file is not up to date with " + "the latest changes in pyproject.toml. " + "You may be getting outdated dependencies. " + "Run update to update them." + "" + ) + + exporter = Exporter(self.poetry) + exporter.export( + fmt, + self.poetry.file.parent, + output or self.io, + with_hashes=not self.option("without-hashes"), + dev=self.option("dev"), + extras=self.option("extras"), + with_credentials=self.option("with-credentials"), + ) diff --git a/src/poetry_export_plugin/exporter.py b/src/poetry_export_plugin/exporter.py new file mode 100644 index 00000000000..8fa20351d62 --- /dev/null +++ b/src/poetry_export_plugin/exporter.py @@ -0,0 +1,170 @@ +import urllib.parse + +from pathlib import Path +from typing import TYPE_CHECKING +from typing import Optional +from typing import Sequence +from typing import Union + + +if TYPE_CHECKING: + from cleo.io.io import IO + from poetry.poetry import Poetry + + +class Exporter: + """ + Exporter class to export a lock file to alternative formats. + """ + + FORMAT_REQUIREMENTS_TXT = "requirements.txt" + #: The names of the supported export formats. + ACCEPTED_FORMATS = (FORMAT_REQUIREMENTS_TXT,) + ALLOWED_HASH_ALGORITHMS = ("sha256", "sha384", "sha512") + + def __init__(self, poetry: "Poetry") -> None: + self._poetry = poetry + + def export( + self, + fmt: str, + cwd: Path, + output: Union["IO", str], + with_hashes: bool = True, + dev: bool = False, + extras: Optional[Union[bool, Sequence[str]]] = None, + with_credentials: bool = False, + ) -> None: + if fmt not in self.ACCEPTED_FORMATS: + raise ValueError(f"Invalid export format: {fmt}") + + getattr(self, "_export_{}".format(fmt.replace(".", "_")))( + cwd, + output, + with_hashes=with_hashes, + dev=dev, + extras=extras, + with_credentials=with_credentials, + ) + + def _export_requirements_txt( + self, + cwd: Path, + output: Union["IO", str], + with_hashes: bool = True, + dev: bool = False, + extras: Optional[Union[bool, Sequence[str]]] = None, + with_credentials: bool = False, + ) -> None: + from poetry.core.packages.utils.utils import path_to_url + + indexes = set() + content = "" + dependency_lines = set() + + for dependency_package in self._poetry.locker.get_project_dependency_packages( + project_requires=self._poetry.package.all_requires, dev=dev, extras=extras + ): + line = "" + + dependency = dependency_package.dependency + package = dependency_package.package + + if package.develop: + line += "-e " + + requirement = dependency.to_pep_508(with_extras=False) + is_direct_local_reference = ( + dependency.is_file() or dependency.is_directory() + ) + is_direct_remote_reference = dependency.is_vcs() or dependency.is_url() + + if is_direct_remote_reference: + line = requirement + elif is_direct_local_reference: + dependency_uri = path_to_url(dependency.source_url) + line = f"{dependency.name} @ {dependency_uri}" + else: + line = f"{package.name}=={package.version}" + + if not is_direct_remote_reference: + if ";" in requirement: + markers = requirement.split(";", 1)[1].strip() + if markers: + line += f"; {markers}" + + if ( + not is_direct_remote_reference + and not is_direct_local_reference + and package.source_url + ): + indexes.add(package.source_url) + + if package.files and with_hashes: + hashes = [] + for f in package.files: + h = f["hash"] + algorithm = "sha256" + if ":" in h: + algorithm, h = h.split(":") + + if algorithm not in self.ALLOWED_HASH_ALGORITHMS: + continue + + hashes.append(f"{algorithm}:{h}") + + if hashes: + line += " \\\n" + for i, h in enumerate(hashes): + line += " --hash={}{}".format( + h, " \\\n" if i < len(hashes) - 1 else "" + ) + dependency_lines.add(line) + + content += "\n".join(sorted(dependency_lines)) + content += "\n" + + if indexes: + # If we have extra indexes, we add them to the beginning of the output + indexes_header = "" + for index in sorted(indexes): + repositories = [ + r + for r in self._poetry.pool.repositories + if r.url == index.rstrip("/") + ] + if not repositories: + continue + repository = repositories[0] + if ( + self._poetry.pool.has_default() + and repository is self._poetry.pool.repositories[0] + ): + url = ( + repository.authenticated_url + if with_credentials + else repository.url + ) + indexes_header = f"--index-url {url}\n" + continue + + url = ( + repository.authenticated_url if with_credentials else repository.url + ) + parsed_url = urllib.parse.urlsplit(url) + if parsed_url.scheme == "http": + indexes_header += f"--trusted-host {parsed_url.netloc}\n" + indexes_header += f"--extra-index-url {url}\n" + + content = indexes_header + "\n" + content + + self._output(content, cwd, output) + + def _output(self, content: str, cwd: Path, output: Union["IO", str]) -> None: + decoded = content.decode() + try: + output.write(decoded) + except AttributeError: + filepath = cwd / output + with filepath.open("w", encoding="utf-8") as f: + f.write(decoded) diff --git a/src/poetry_export_plugin/plugins.py b/src/poetry_export_plugin/plugins.py new file mode 100644 index 00000000000..a8cf116971c --- /dev/null +++ b/src/poetry_export_plugin/plugins.py @@ -0,0 +1,42 @@ +from importlib import import_module +from typing import TYPE_CHECKING +from typing import Callable +from typing import Type + +from poetry.plugins.application_plugin import ApplicationPlugin + + +if TYPE_CHECKING: + from poetry.console.application import Application + from poetry.console.commands.command import Command + + +def load_command(name: str) -> Callable: + def _load() -> Type["Command"]: + module = import_module( + "poetry_export_plugin.console.commands.{}".format(".".join(name.split(" "))) + ) + command_class = getattr( + module, "{}Command".format("".join(c.title() for c in name.split(" "))) + ) + + return command_class() + + return _load + + +COMMANDS = ["export"] + + +class ExportApplicationPlugin(ApplicationPlugin): + def activate(self, application: "Application"): + # Removing the existing export command to avoid an error + # until Poetry removes the export command + # and uses this plugin instead. + + # If you're checking this code out to get inspiration + # for your own plugins: DON'T DO THIS! + del application.command_loader._factories["export"] + + for command in COMMANDS: + application.command_loader.register_factory(command, load_command(command)) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000000..ef14c631345 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,192 @@ +import shutil +import sys +import tempfile + +from pathlib import Path +from typing import Any +from typing import Dict + +import pytest + +from poetry.config.config import Config as BaseConfig +from poetry.config.dict_config_source import DictConfigSource +from poetry.core.packages.package import Package +from poetry.factory import Factory +from poetry.layouts import layout +from poetry.repositories.pool import Pool +from poetry.repositories.repository import Repository +from poetry.utils.env import SystemEnv + +from tests.helpers import TestLocker + + +class Config(BaseConfig): + def get(self, setting_name: str, default: Any = None) -> Any: + self.merge(self._config_source.config) + self.merge(self._auth_config_source.config) + + return super(Config, self).get(setting_name, default=default) + + def raw(self) -> Dict[str, Any]: + self.merge(self._config_source.config) + self.merge(self._auth_config_source.config) + + return super(Config, self).raw() + + def all(self) -> Dict[str, Any]: + self.merge(self._config_source.config) + self.merge(self._auth_config_source.config) + + return super(Config, self).all() + + +@pytest.fixture +def config_cache_dir(tmp_dir): + path = Path(tmp_dir) / ".cache" / "pypoetry" + path.mkdir(parents=True) + return path + + +@pytest.fixture +def config_virtualenvs_path(config_cache_dir): + return config_cache_dir / "virtualenvs" + + +@pytest.fixture +def config_source(config_cache_dir): + source = DictConfigSource() + source.add_property("cache-dir", str(config_cache_dir)) + + return source + + +@pytest.fixture +def auth_config_source(): + source = DictConfigSource() + + return source + + +@pytest.fixture +def config(config_source, auth_config_source, mocker): + import keyring + + from keyring.backends.fail import Keyring + + keyring.set_keyring(Keyring()) + + c = Config() + c.merge(config_source.config) + c.set_config_source(config_source) + c.set_auth_config_source(auth_config_source) + + mocker.patch("poetry.factory.Factory.create_config", return_value=c) + mocker.patch("poetry.config.config.Config.set_config_source") + + return c + + +@pytest.fixture +def tmp_dir(): + dir_ = tempfile.mkdtemp(prefix="poetry_") + + yield dir_ + + shutil.rmtree(dir_) + + +@pytest.fixture +def fixture_dir(): + def _fixture_dir(fixture): + return Path(__file__).parent.joinpath("fixtures").joinpath(fixture) + + return _fixture_dir + + +@pytest.fixture() +def repo(): + return Repository() + + +@pytest.fixture +def installed(): + return Repository() + + +@pytest.fixture(scope="session") +def current_env(): + return SystemEnv(Path(sys.executable)) + + +@pytest.fixture(scope="session") +def current_python(current_env): + return current_env.version_info[:3] + + +@pytest.fixture(scope="session") +def default_python(current_python): + return "^{}".format(".".join(str(v) for v in current_python[:2])) + + +@pytest.fixture +def project_factory(tmp_dir, config, repo, installed, default_python): + workspace = Path(tmp_dir) + + def _factory( + name=None, + dependencies=None, + dev_dependencies=None, + pyproject_content=None, + poetry_lock_content=None, + install_deps=True, + ): + project_dir = workspace / "poetry-fixture-{}".format(name) + dependencies = dependencies or {} + dev_dependencies = dev_dependencies or {} + + if pyproject_content: + project_dir.mkdir(parents=True, exist_ok=True) + with project_dir.joinpath("pyproject.toml").open( + "w", encoding="utf-8" + ) as f: + f.write(pyproject_content) + else: + layout("src")( + name, + "0.1.0", + author="PyTest Tester ", + readme_format="md", + python=default_python, + dependencies=dependencies, + dev_dependencies=dev_dependencies, + ).create(project_dir, with_tests=False) + + if poetry_lock_content: + lock_file = project_dir / "poetry.lock" + lock_file.write_text(data=poetry_lock_content, encoding="utf-8") + + poetry = Factory().create_poetry(project_dir) + + locker = TestLocker( + poetry.locker.lock.path, poetry.locker._local_config + ) # noqa + locker.write() + + poetry.set_locker(locker) + poetry.set_config(config) + + pool = Pool() + pool.add_repository(repo) + + poetry.set_pool(pool) + + if install_deps: + for deps in [dependencies, dev_dependencies]: + for name, version in deps.items(): + pkg = Package(name, version) + repo.add_package(pkg) + installed.add_package(pkg) + + return poetry + + return _factory diff --git a/tests/console/__init__.py b/tests/console/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/console/conftest.py b/tests/console/conftest.py new file mode 100644 index 00000000000..2f1c070ed56 --- /dev/null +++ b/tests/console/conftest.py @@ -0,0 +1,71 @@ +from pathlib import Path + +import pytest + +from cleo.testers.command_tester import CommandTester +from poetry.installation import Installer +from poetry.utils.env import MockEnv + +from tests.helpers import TestApplication +from tests.helpers import TestExecutor + + +@pytest.fixture +def app(poetry): + app_ = TestApplication(poetry) + + return app_ + + +@pytest.fixture +def env(tmp_dir): + path = Path(tmp_dir) / ".venv" + path.mkdir(parents=True) + return MockEnv(path=path, is_venv=True) + + +@pytest.fixture +def command_tester_factory(app, env): + def _tester(command, poetry=None, installer=None, executor=None, environment=None): + command = app.find(command) + tester = CommandTester(command) + + # Setting the formatter from the application + # TODO: Find a better way to do this in Cleo + app_io = app.create_io() + formatter = app_io.output.formatter + tester.io.output.set_formatter(formatter) + tester.io.error_output.set_formatter(formatter) + + if poetry: + app._poetry = poetry + + poetry = app.poetry + command._pool = poetry.pool + + if hasattr(command, "set_env"): + command.set_env(environment or env) + + if hasattr(command, "set_installer"): + installer = installer or Installer( + tester.io, + env, + poetry.package, + poetry.locker, + poetry.pool, + poetry.config, + executor=executor + or TestExecutor(env, poetry.pool, poetry.config, tester.io), + ) + installer.use_executor(True) + command.set_installer(installer) + + return tester + + return _tester + + +@pytest.fixture +def do_lock(command_tester_factory, poetry): + command_tester_factory("lock").execute() + assert poetry.locker.lock.exists() diff --git a/tests/console/test_export.py b/tests/console/test_export.py new file mode 100644 index 00000000000..008e09c6fae --- /dev/null +++ b/tests/console/test_export.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import pytest + +from poetry.core.packages.package import Package + + +PYPROJECT_CONTENT = """\ +[tool.poetry] +name = "simple-project" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +foo = "^1.0" +bar = { version = "^1.1", optional = true } + +[tool.poetry.extras] +feature_bar = ["bar"] +""" + + +@pytest.fixture(autouse=True) +def setup(repo): + repo.add_package(Package("foo", "1.0.0")) + repo.add_package(Package("bar", "1.1.0")) + + +@pytest.fixture +def poetry(project_factory): + return project_factory(name="export", pyproject_content=PYPROJECT_CONTENT) + + +@pytest.fixture +def tester(command_tester_factory, poetry): + return command_tester_factory("export", poetry=poetry) + + +def _export_requirements(tester, poetry): + tester.execute("--format requirements.txt --output requirements.txt") + + requirements = poetry.file.parent / "requirements.txt" + assert requirements.exists() + + with requirements.open(encoding="utf-8") as f: + content = f.read() + + assert poetry.locker.lock.exists() + + expected = """\ +foo==1.0.0 +""" + + assert expected == content + + +def test_export_exports_requirements_txt_file_locks_if_no_lock_file(tester, poetry): + assert not poetry.locker.lock.exists() + _export_requirements(tester, poetry) + assert "The lock file does not exist. Locking." in tester.io.fetch_output() + + +def test_export_exports_requirements_txt_uses_lock_file(tester, poetry, do_lock): + _export_requirements(tester, poetry) + assert "The lock file does not exist. Locking." not in tester.io.fetch_output() + + +def test_export_fails_on_invalid_format(tester, do_lock): + with pytest.raises(ValueError): + tester.execute("--format invalid") + + +def test_export_prints_to_stdout_by_default(tester, do_lock): + tester.execute("--format requirements.txt") + expected = """\ +foo==1.0.0 +""" + assert expected == tester.io.fetch_output() + + +def test_export_uses_requirements_txt_format_by_default(tester, do_lock): + tester.execute() + expected = """\ +foo==1.0.0 +""" + assert expected == tester.io.fetch_output() + + +def test_export_includes_extras_by_flag(tester, do_lock): + tester.execute("--format requirements.txt --extras feature_bar") + expected = """\ +bar==1.1.0 +foo==1.0.0 +""" + assert expected == tester.io.fetch_output() diff --git a/tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl b/tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..9e0805593b652818425370fb6973486b80c689ba GIT binary patch literal 1116 zcmWIWW@Zs#fB;2?Ca%JlVn7ZE3jlFSYHq%Me0*kJW=VX!UO{Did|7Hyab|vAe7u6K zg1Uj8p`L*{R{+!u4xkQ24~MBd?BPIG1Q1I>&Cms_)Jw@MF44`*OUu^}_i%Of;XUQZ z)u152dSUPK4@}Bw7Y=c|?Cw~x_2BZB8J(Wu_x9cU^;W##GF$EDf-Q4Dr?EH(3-*2! z6v@4|TW`lf9i`S~)1R%{@mVnUBG=|+pZj%pEdP4s(1IgpYfzoTp?0=Fijjfg7!w17 zEH3Bxx`sHqIEFY*op{&lu!BJB`|p~^;xY}rC%JKJYj%mTWb251t-9s&ILR#fyZamE zpECPqMBnyW5x&!U&gZ!^8+WR=t6KHUI~lQ3no-j~qOpr*$`X^`pOfc(m~nPbP{uW; z8*9Tv)^6Z>5L_5;vqvOHN&L!*qz%?eFx1YRk zJ&Vcu=J7sGYSYCM*&79iH2i;l5b68G*k4(%J!!AU_oM^A-|TYBei{@MVQ0F_XvLHq ztr-cyKMRaI9{l&ayJCy>`F2_9=5;pbt<28>k+pf7PJsL-9)HPtt7fKW8UHH1?^fOG z-Qss(kyEPp@<8Uo~0L;%gRjE z+Pb~5clO8^JbgWT9{0j?r#KS#{%^{>VZHAA4!{28CWoI@pZI_5{HiMl&+QJF|E*x- zSMjE`leWyY@Sf+#TQFzlpL~T)Ioy#!zHFg6S-39y>E~IgG0h4)%9v2b8sN>yB*Kh4w*UhI43;#4DD=F8(2YHxK=d*& zG%&hAbz{#p=;oj&aD+LRahU^4?&wCLM=Qc84Pfp8c?iQvkVp>jW@Q5@W&uJ_X47E? F@c^7EgWUiC literal 0 HcmV?d00001 diff --git a/tests/fixtures/distributions/demo-0.1.0.tar.gz b/tests/fixtures/distributions/demo-0.1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..133b64421f86ad26448e0b0f27be33df6808c14c GIT binary patch literal 961 zcmV;y13vs8iwFpsLE~Ej|72xtZ!It`F)lDJbYXG;?N{Ay+cp^WHJ^ghF4P(kBhj*) zXmik^>(*gg8ni%fGEZP_#^WN18bxI>((Qfr9DA{Tq~yeQ(jgvF@25OBCJ#UUisyVZ z*(du?PT1vh&SqToJ_y1V@B0nIL3~p-d=$k)+QXN<9iXW-Q!xEQZ}L9^dVmWp3OLwZOCG)j_$qMpP59y4CZAS4k9_f2s>%S0)3mXT`lp!!eZ`2G5)$%g(1NtE<( z==A@=k!2TL)k2n8hR4{qH^Fzx|7bv5{&)Gm&Hr(f&@iGQIT(gA2Ca7cuhi8CiZiR=smw_Sd4e`*7 z*Ha}b-LW2Gjs8ih8Y-vWt3UsE!)h&fiJzHMopStn%{%G|Zz&?=kvBnTbzU;#6)$JJ zoC;n~P_=+D?fAb_GQw});vWYj_K=Bb@}@8?N8^aZhMeYH0&%sn*1orgrClS-p{`ZK ze?#UP$+zOEV#C;0;r@+Kqe1mM(%@+v2rZfCt-wv;5cTzvjBB z9`fCPPyI)fh8y~)bQC)M?*gCVpda9=oQZOt;zpkZ2ZljsN)~t~YTam&*JLUc$D*jD z(%9Y;geo>YXLD|}{{uSp%|-Gj*K{^xn%Sl+-%s(W-}fgVEm6X8=Xs|1FO5*wotM(? zWNpT2b+-)cMgrB@!8+CXlRfN%FfC_1m4sq+maz^XXtHb$6V{q#|{K**Hjr7|>v@FnNVLmJG0 zJw^zkE(9dJ;F-w*DTefWi-Yug^YOlf2SbWYCjP9=8+Z)wT?p|uW3C_bkGh=ycr&8Q j@v8&D{GQ7Y4h{|u4h{|u4h{|ue+%yb(`BL(04M+eDE0Bh literal 0 HcmV?d00001 diff --git a/tests/fixtures/project_with_nested_local/bar/pyproject.toml b/tests/fixtures/project_with_nested_local/bar/pyproject.toml new file mode 100644 index 00000000000..bf058b8813a --- /dev/null +++ b/tests/fixtures/project_with_nested_local/bar/pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "bar" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +quix = { path = "../quix", develop = true } diff --git a/tests/fixtures/project_with_nested_local/foo/pyproject.toml b/tests/fixtures/project_with_nested_local/foo/pyproject.toml new file mode 100644 index 00000000000..1aba06effe1 --- /dev/null +++ b/tests/fixtures/project_with_nested_local/foo/pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "foo" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +bar = { path = "../bar", develop = true } diff --git a/tests/fixtures/project_with_nested_local/pyproject.toml b/tests/fixtures/project_with_nested_local/pyproject.toml new file mode 100644 index 00000000000..c6eb2c6b46c --- /dev/null +++ b/tests/fixtures/project_with_nested_local/pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +name = "project-with-nested-local" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" +foo = { path = "./foo", develop = true } +bar = { path = "./bar", develop = true } diff --git a/tests/fixtures/project_with_nested_local/quix/pyproject.toml b/tests/fixtures/project_with_nested_local/quix/pyproject.toml new file mode 100644 index 00000000000..a90d9bcc41f --- /dev/null +++ b/tests/fixtures/project_with_nested_local/quix/pyproject.toml @@ -0,0 +1,10 @@ +[tool.poetry] +name = "quix" +version = "1.2.3" +description = "Some description." +authors = ["Poetry Maintainer "] +license = "MIT" + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" diff --git a/tests/fixtures/project_with_setup/my_package/__init__.py b/tests/fixtures/project_with_setup/my_package/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/project_with_setup/project_with_setup.egg-info/PKG-INFO b/tests/fixtures/project_with_setup/project_with_setup.egg-info/PKG-INFO new file mode 100644 index 00000000000..1204da0c96c --- /dev/null +++ b/tests/fixtures/project_with_setup/project_with_setup.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: project-with-setup +Version: 0.1.2 +Summary: Demo project. +Home-page: https://github.com/demo/demo +Author: Sébastien Eustace +Author-email: sebastien@eustace.io +License: MIT +Description: UNKNOWN +Platform: UNKNOWN diff --git a/tests/fixtures/project_with_setup/project_with_setup.egg-info/SOURCES.txt b/tests/fixtures/project_with_setup/project_with_setup.egg-info/SOURCES.txt new file mode 100644 index 00000000000..f5255dc9dad --- /dev/null +++ b/tests/fixtures/project_with_setup/project_with_setup.egg-info/SOURCES.txt @@ -0,0 +1,7 @@ +setup.py +my_package/__init__.py +my_package.egg-info/PKG-INFO +my_package.egg-info/SOURCES.txt +my_package.egg-info/dependency_links.txt +my_package.egg-info/requires.txt +my_package.egg-info/top_level.txt diff --git a/tests/fixtures/project_with_setup/project_with_setup.egg-info/dependency_links.txt b/tests/fixtures/project_with_setup/project_with_setup.egg-info/dependency_links.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fixtures/project_with_setup/project_with_setup.egg-info/requires.txt b/tests/fixtures/project_with_setup/project_with_setup.egg-info/requires.txt new file mode 100644 index 00000000000..eeb0a4815ce --- /dev/null +++ b/tests/fixtures/project_with_setup/project_with_setup.egg-info/requires.txt @@ -0,0 +1,2 @@ +pendulum>=1.4.4 +cachy[msgpack]>=0.2.0 diff --git a/tests/fixtures/project_with_setup/project_with_setup.egg-info/top_level.txt b/tests/fixtures/project_with_setup/project_with_setup.egg-info/top_level.txt new file mode 100644 index 00000000000..5d04b540bc9 --- /dev/null +++ b/tests/fixtures/project_with_setup/project_with_setup.egg-info/top_level.txt @@ -0,0 +1 @@ +my_package diff --git a/tests/fixtures/project_with_setup/setup.py b/tests/fixtures/project_with_setup/setup.py new file mode 100644 index 00000000000..0f9e0d095a0 --- /dev/null +++ b/tests/fixtures/project_with_setup/setup.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from setuptools import setup + + +kwargs = dict( + name="project-with-setup", + license="MIT", + version="0.1.2", + description="Demo project.", + author="Sébastien Eustace", + author_email="sebastien@eustace.io", + url="https://github.com/demo/demo", + packages=["my_package"], + install_requires=["pendulum>=1.4.4", "cachy[msgpack]>=0.2.0"], +) + + +setup(**kwargs) diff --git a/tests/fixtures/sample_project/README.rst b/tests/fixtures/sample_project/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/fixtures/sample_project/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/fixtures/sample_project/pyproject.toml b/tests/fixtures/sample_project/pyproject.toml new file mode 100644 index 00000000000..aff10e12c04 --- /dev/null +++ b/tests/fixtures/sample_project/pyproject.toml @@ -0,0 +1,58 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" +cleo = "^0.6" +pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } +requests = { version = "^2.18", optional = true, extras=[ "security" ] } +pathlib2 = { version = "^2.2", python = "~2.7" } + +orator = { version = "^0.9", optional = true } + +# File dependency +demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" } + +# Dir dependency with setup.py +my-package = { path = "../project_with_setup/" } + +# Dir dependency with pyproject.toml +simple-project = { path = "../simple_project/" } + +# Dependency with markers +functools32 = { version = "^3.2.3", markers = "python_version ~= '2.7' and sys_platform == 'win32' or python_version in '3.4 3.5'" } + + +[tool.poetry.extras] +db = [ "orator" ] + +[tool.poetry.dev-dependencies] +pytest = "~3.4" + + +[tool.poetry.scripts] +my-script = "my_package:main" + + +[tool.poetry.plugins."blogtool.parsers"] +".rst" = "some_module::SomeClass" diff --git a/tests/fixtures/simple_project/README.rst b/tests/fixtures/simple_project/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/fixtures/simple_project/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/fixtures/simple_project/dist/simple-project-1.2.3.tar.gz b/tests/fixtures/simple_project/dist/simple-project-1.2.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..149aa9527c577a1828a5a3114377960fd1e41a88 GIT binary patch literal 1106 zcmV-Y1g-lYiwFpPU6EV@|8r?=aBO8QaB^>IWn*+LF)lJLGcI&tascgF+j84B5cM;@ z0%ac9YD9<^QSxxAOx)UOJh2ju<@Uu^IW!5&s7ZnaKqs1{{hxkEf2;s6x;gSxX{=6c z?u&rME{N;y!4c}9&Wlm-{uK@)%=R8c%u+V}nx@lTl$GAHx~{zk-|y`}axMZUKmVmC z`IU&h4--KWJg{8b?OS%&>7m}fZS}2vu-C|=7fzBa#zw~IZ#Wc&g>2+>PLB=_hewEU z@x|_cuiM>x|Lxw|{qNen-X1iY`~ToFoWW5L-UL$&26Jg{{*w{ZPpUtW|5;Tb)I{#aiWv|_T*R{+h|L;Q11*I{{C>Crs0cnt6IDp#f zsnEbJW}MJeH5DgB0}VkiFL0?sgg)261jHo7DK7>yJU-I^U@}3KgIl0eslJvU&LnzK4c9_s-0O78*`P&_ zn)J>nCxWusvf-47>--XhH0kh&@GKCRRo-%jG|Us6ilCn3#_l`Pl*_T);MqIMqGHNg zR-h`9PPI1FBAnqg!f7~DwdxBK0Kz!noLmvSyiR8{BO&xWIHp(PUBECr#J4!6nHr7K z|B#b7f@gB$xYqucJ}2ao1&m;>Dx;a)niLLcl*gE>>`Ahv9$L z%T~W{^&9;6|Ls2(dG@)x{TcZG&i;4hQ?%LtUC1+NvRf|b-_g~IU>1V<%WIRYAtWvcMV2(jQM}``X}S~ z&C9ntivO;){{N5DH?1cA??Q%H1S)bEzm&1ngEq3j8ySW@xOz6g+@d%nV4NpOz-Eea zSPQq{mDCuDmGR)lI0p`jpefz&gw?9aDB`Ic#wVgFcwKQVb^Md*tG!@Z+U4g!#c6P( zWIR0hAHpE`aZc7KI;EtiJP*DdAH#!8F48TIRDF1SRyVJ>j2NA=AW7s%;YpBAbH!RM z$QW6&x3x$8Ej^rV?NM)QkJT}4LuPH~cn_lmMpZcb`Siqths#7>8{SXjt-7AhX~Tuz YJ)~ii4I62sk*`Sp0Yvm`6-N>XJOp~G}oyk*lXPObG5#*WWk@{`yV1s9qYzEQ?elykxsK41Y&2W}A^T~6C!g167 z-2T=U|EnbV`-0v7x7-e>Q;lR_)3n9x?aAqvLVb6w>AN5OI>2z6{y8_TwB(}*Z(W==luI(_)Q!&nz;>Z%q|0?*9aKZs)VCC$ko|D$ffn% z-l8T028IWB(>(=}7aEj2Q1@DSfk}{WnvBPUB~6x0wy!Uj7rl7GUGrVj?gaPZqFr2N zv#wA1H1YhDtWV-A_PMqF{PyY9V;3&%g3VGvpX@{~aM*5B(fKRaV=*Z)@a^{6ZEyM< z;#bzo-O2E{ouwOY{3N+NR-Exzdwb7wySHxP5`Hn;uGXHK-nC<=#2e^+p@?tBR)>y!lzn9LWgCBBW%jf$D$`EwD8_v2D|n5o-6X;SpSZ5 z!^O*$U3<@8VNdu9O#F;YBFwmRG%#quU`Zp0!kxd-%)!Xt5K|Z!8W^oG%)y@9(M?3p ymusw3=9CGeCVeD literal 0 HcmV?d00001 diff --git a/tests/fixtures/simple_project/pyproject.toml b/tests/fixtures/simple_project/pyproject.toml new file mode 100644 index 00000000000..41a062fc09a --- /dev/null +++ b/tests/fixtures/simple_project/pyproject.toml @@ -0,0 +1,35 @@ +[tool.poetry] +name = "simple-project" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.4" + +[tool.poetry.scripts] +foo = "foo:bar" +baz = "bar:baz.boom.bim" +fox = "fuz.foo:bar.baz" + + +[build-system] +requires = ["poetry-core>=1.0.2"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/simple_project/simple_project/__init__.py b/tests/fixtures/simple_project/simple_project/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 00000000000..eef9000dd64 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,94 @@ +from poetry.console.application import Application +from poetry.core.toml.file import TOMLFile +from poetry.factory import Factory +from poetry.installation.executor import Executor +from poetry.packages import Locker + + +class TestApplication(Application): + def __init__(self, poetry): + super().__init__() + self._poetry = poetry + + def reset_poetry(self): + poetry = self._poetry + self._poetry = Factory().create_poetry(self._poetry.file.path.parent) + self._poetry.set_pool(poetry.pool) + self._poetry.set_config(poetry.config) + self._poetry.set_locker( + TestLocker(poetry.locker.lock.path, self._poetry.local_config) + ) + + +class TestLocker(Locker): + def __init__(self, lock, local_config): # noqa + self._lock = TOMLFile(lock) + self._local_config = local_config + self._lock_data = None + self._content_hash = self._get_content_hash() + self._locked = False + self._lock_data = None + self._write = False + + def write(self, write=True): + self._write = write + + def is_locked(self): + return self._locked + + def locked(self, is_locked=True): + self._locked = is_locked + + return self + + def mock_lock_data(self, data): + self.locked() + + self._lock_data = data + + def is_fresh(self): + return True + + def _write_lock_data(self, data): + if self._write: + super()._write_lock_data(data) + self._locked = True + return + + self._lock_data = data + + +class TestExecutor(Executor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._installs = [] + self._updates = [] + self._uninstalls = [] + + @property + def installations(self): + return self._installs + + @property + def updates(self): + return self._updates + + @property + def removals(self): + return self._uninstalls + + def _do_execute_operation(self, operation): + super()._do_execute_operation(operation) + + if not operation.skipped: + getattr(self, "_{}s".format(operation.job_type)).append(operation.package) + + def _execute_install(self, operation): + return 0 + + def _execute_update(self, operation): + return 0 + + def _execute_remove(self, operation): + return 0 diff --git a/tests/test_exporter.py b/tests/test_exporter.py new file mode 100644 index 00000000000..9aa8044c651 --- /dev/null +++ b/tests/test_exporter.py @@ -0,0 +1,1605 @@ +import sys + +from pathlib import Path + +import pytest + +from poetry.core.packages.dependency import Dependency +from poetry.core.toml.file import TOMLFile +from poetry.factory import Factory +from poetry.packages.locker import Locker as BaseLocker +from poetry.repositories.legacy_repository import LegacyRepository +from poetry.utils.exporter import Exporter + + +class Locker(BaseLocker): + def __init__(self): + self._lock = TOMLFile(Path.cwd().joinpath("poetry.lock")) + self._locked = True + self._content_hash = self._get_content_hash() + + def locked(self, is_locked=True): + self._locked = is_locked + + return self + + def mock_lock_data(self, data): + self._lock_data = data + + def is_locked(self): + return self._locked + + def is_fresh(self): + return True + + def _get_content_hash(self): + return "123456789" + + +@pytest.fixture +def working_directory(): + return Path(__file__).parent.parent + + +@pytest.fixture(autouse=True) +def mock_path_cwd(mocker, working_directory): + yield mocker.patch("pathlib.Path.cwd", return_value=working_directory) + + +@pytest.fixture() +def locker(): + return Locker() + + +@pytest.fixture +def poetry(fixture_dir, locker): + p = Factory().create_poetry(fixture_dir("sample_project")) + p._locker = locker + + return p + + +def set_package_requires(poetry, skip=None): + skip = skip or set() + packages = poetry.locker.locked_repository(with_dev_reqs=True).packages + poetry.package.requires = [ + pkg.to_dependency() + for pkg in packages + if pkg.category == "main" and pkg.name not in skip + ] + poetry.package.dev_requires = [ + pkg.to_dependency() + for pkg in packages + if pkg.category == "dev" and pkg.name not in skip + ] + + +def test_exporter_can_export_requirements_txt_with_standard_packages( + tmp_dir, poetry, mocker +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +foo==1.2.3 +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "python_version < '3.7'", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "extra =='foo'", + }, + { + "name": "baz", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "sys_platform == 'win32'", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +baz==7.8.9; sys_platform == "win32" +foo==1.2.3; python_version < "3.7" +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_poetry(tmp_dir, poetry): + """Regression test for #3254""" + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "poetry", + "version": "1.1.4", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"keyring": "*"}, + }, + { + "name": "junit-xml", + "version": "1.9", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"six": "*"}, + }, + { + "name": "keyring", + "version": "21.8.0", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "SecretStorage": { + "version": "*", + "markers": "sys_platform == 'linux'", + } + }, + }, + { + "name": "secretstorage", + "version": "3.3.0", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"cryptography": "*"}, + }, + { + "name": "cryptography", + "version": "3.2", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"six": "*"}, + }, + { + "name": "six", + "version": "1.15.0", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": { + "poetry": [], + "keyring": [], + "secretstorage": [], + "cryptography": [], + "six": [], + "junit-xml": [], + }, + }, + } + ) + set_package_requires( + poetry, skip={"keyring", "secretstorage", "cryptography", "six"} + ) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + # The dependency graph: + # junit-xml 1.9 Creates JUnit XML test result documents that can be read by tools such as Jenkins + # └── six * + # poetry 1.1.4 Python dependency management and packaging made easy. + # ├── keyring >=21.2.0,<22.0.0 + # │ ├── importlib-metadata >=1 + # │ │ └── zipp >=0.5 + # │ ├── jeepney >=0.4.2 + # │ ├── pywin32-ctypes <0.1.0 || >0.1.0,<0.1.1 || >0.1.1 + # │ └── secretstorage >=3.2 -- On linux only + # │ ├── cryptography >=2.0 + # │ │ └── six >=1.4.1 + # │ └── jeepney >=0.6 (circular dependency aborted here) + expected = { + "poetry": Dependency.create_from_pep_508("poetry==1.1.4"), + "junit-xml": Dependency.create_from_pep_508("junit-xml==1.9"), + "keyring": Dependency.create_from_pep_508("keyring==21.8.0"), + "secretstorage": Dependency.create_from_pep_508( + "secretstorage==3.3.0; sys_platform=='linux'" + ), + "cryptography": Dependency.create_from_pep_508( + "cryptography==3.2; sys_platform=='linux'" + ), + "six": Dependency.create_from_pep_508("six==1.15.0"), + } + + for line in content.strip().split("\n"): + dependency = Dependency.create_from_pep_508(line) + assert dependency.name in expected + expected_dependency = expected.pop(dependency.name) + assert dependency == expected_dependency + assert dependency.marker == expected_dependency.marker + + +def test_exporter_can_export_requirements_txt_pyinstaller(tmp_dir, poetry): + """Regression test for #3254""" + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "pyinstaller", + "version": "4.0", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "altgraph": "*", + "macholib": { + "version": "*", + "markers": "sys_platform == 'darwin'", + }, + }, + }, + { + "name": "altgraph", + "version": "0.17", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "macholib", + "version": "1.8", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"altgraph": ">=0.15"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"pyinstaller": [], "altgraph": [], "macholib": []}, + }, + } + ) + set_package_requires(poetry, skip={"altgraph", "macholib"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + # Rationale for the results: + # * PyInstaller has an explicit dependency on altgraph, so it must always be installed. + # * PyInstaller requires macholib on Darwin, which in turn requires altgraph. + # The dependency graph: + # pyinstaller 4.0 PyInstaller bundles a Python application and all its dependencies into a single package. + # ├── altgraph * + # ├── macholib >=1.8 -- only on Darwin + # │ └── altgraph >=0.15 + expected = { + "pyinstaller": Dependency.create_from_pep_508("pyinstaller==4.0"), + "altgraph": Dependency.create_from_pep_508("altgraph==0.17"), + "macholib": Dependency.create_from_pep_508( + "macholib==1.8; sys_platform == 'darwin'" + ), + } + + for line in content.strip().split("\n"): + dependency = Dependency.create_from_pep_508(line) + assert dependency.name in expected + expected_dependency = expected.pop(dependency.name) + assert dependency == expected_dependency + assert dependency.marker == expected_dependency.marker + + +def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "a", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "python_version < '3.7'", + "dependencies": {"b": ">=0.0.0", "c": ">=0.0.0"}, + }, + { + "name": "b", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "platform_system == 'Windows'", + "dependencies": {"d": ">=0.0.0"}, + }, + { + "name": "c", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "sys_platform == 'win32'", + "dependencies": {"d": ">=0.0.0"}, + }, + { + "name": "d", + "version": "0.0.1", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"a": [], "b": [], "c": [], "d": []}, + }, + } + ) + set_package_requires(poetry, skip={"b", "c", "d"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = { + "a": Dependency.create_from_pep_508("a==1.2.3; python_version < '3.7'"), + "b": Dependency.create_from_pep_508( + "b==4.5.6; platform_system == 'Windows' and python_version < '3.7'" + ), + "c": Dependency.create_from_pep_508( + "c==7.8.9; sys_platform == 'win32' and python_version < '3.7'" + ), + "d": Dependency.create_from_pep_508( + "d==0.0.1; platform_system == 'Windows' and python_version < '3.7' or sys_platform == 'win32' and python_version < '3.7'" + ), + } + + for line in content.strip().split("\n"): + dependency = Dependency.create_from_pep_508(line) + assert dependency.name in expected + expected_dependency = expected.pop(dependency.name) + assert dependency == expected_dependency + assert dependency.marker == expected_dependency.marker + + assert expected == {} + + +@pytest.mark.parametrize( + "dev,lines", + [(False, ['a==1.2.3; python_version < "3.8"']), (True, ["a==1.2.3", "b==4.5.6"])], +) +def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_any( + tmp_dir, poetry, dev, lines +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "a", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "b", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "dependencies": {"a": ">=1.2.3"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"a": [], "b": []}, + }, + } + ) + + poetry.package.requires = [ + Factory.create_dependency( + name="a", constraint=dict(version="^1.2.3", python="<3.8") + ), + ] + poetry.package.dev_requires = [ + Factory.create_dependency( + name="b", constraint=dict(version="^4.5.6"), category="dev" + ), + ] + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + assert content.strip() == "\n".join(lines) + + +def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 \\ + --hash=sha256:67890 +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes_disabled( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export( + "requirements.txt", Path(tmp_dir), "requirements.txt", with_hashes=False + ) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +foo==1.2.3 +""" + + assert expected == content + + +def test_exporter_exports_requirements_txt_without_dev_packages_by_default( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +def test_exporter_exports_requirements_txt_with_dev_packages_if_opted_in( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 \\ + --hash=sha256:67890 +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +def test_exporter_exports_requirements_txt_without_optional_packages(tmp_dir, poetry): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": True, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +@pytest.mark.parametrize( + "extras,lines", + [ + (None, ["foo==1.2.3"]), + (False, ["foo==1.2.3"]), + (True, ["bar==4.5.6", "foo==1.2.3", "spam==0.1.0"]), + (["feature_bar"], ["bar==4.5.6", "foo==1.2.3", "spam==0.1.0"]), + ], +) +def test_exporter_exports_requirements_txt_with_optional_packages( + tmp_dir, poetry, extras, lines +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": True, + "python-versions": "*", + "dependencies": {"spam": ">=0.1"}, + }, + { + "name": "spam", + "version": "0.1.0", + "category": "main", + "optional": True, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"], "spam": ["abcde"]}, + }, + "extras": {"feature_bar": ["bar"]}, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export( + "requirements.txt", + Path(tmp_dir), + "requirements.txt", + dev=True, + with_hashes=False, + extras=extras, + ) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = "\n".join(lines) + + assert content.strip() == expected + + +def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "git", + "url": "https://github.com/foo/foo.git", + "reference": "123456", + }, + } + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo @ git+https://github.com/foo/foo.git@123456 +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_nested_packages(tmp_dir, poetry): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "git", + "url": "https://github.com/foo/foo.git", + "reference": "123456", + }, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"foo": "rev 123456"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": []}, + }, + } + ) + set_package_requires(poetry, skip={"foo"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +foo @ git+https://github.com/foo/foo.git@123456 +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_nested_packages_cyclic( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"bar": {"version": "4.5.6"}}, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"baz": {"version": "7.8.9"}}, + }, + { + "name": "baz", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"foo": {"version": "1.2.3"}}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry, skip={"bar", "baz"}) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar==4.5.6 +baz==7.8.9 +foo==1.2.3 +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( + tmp_dir, poetry +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "python_version < '3.7'", + "source": { + "type": "git", + "url": "https://github.com/foo/foo.git", + "reference": "123456", + }, + } + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo @ git+https://github.com/foo/foo.git@123456 ; python_version < "3.7" +""" + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_directory_packages( + tmp_dir, poetry, working_directory +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project", + "reference": "", + }, + } + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo @ {}/tests/fixtures/sample_project +""".format( + working_directory.as_uri() + ) + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_nested_directory_packages( + tmp_dir, poetry, working_directory +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project", + "reference": "", + }, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project/../project_with_nested_local/bar", + "reference": "", + }, + }, + { + "name": "baz", + "version": "7.8.9", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project/../project_with_nested_local/bar/..", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +bar @ {}/tests/fixtures/project_with_nested_local/bar +baz @ {}/tests/fixtures/project_with_nested_local +foo @ {}/tests/fixtures/sample_project +""".format( + working_directory.as_uri(), + working_directory.as_uri(), + working_directory.as_uri(), + ) + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_directory_packages_and_markers( + tmp_dir, poetry, working_directory +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "python_version < '3.7'", + "source": { + "type": "directory", + "url": "tests/fixtures/sample_project", + "reference": "", + }, + } + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo @ {}/tests/fixtures/sample_project; python_version < "3.7" +""".format( + working_directory.as_uri() + ) + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_file_packages( + tmp_dir, poetry, working_directory +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "file", + "url": "tests/fixtures/distributions/demo-0.1.0.tar.gz", + "reference": "", + }, + } + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo @ {}/tests/fixtures/distributions/demo-0.1.0.tar.gz +""".format( + working_directory.as_uri() + ) + + assert expected == content + + +def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( + tmp_dir, poetry, working_directory +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "marker": "python_version < '3.7'", + "source": { + "type": "file", + "url": "tests/fixtures/distributions/demo-0.1.0.tar.gz", + "reference": "", + }, + } + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +foo @ {}/tests/fixtures/distributions/demo-0.1.0.tar.gz; python_version < "3.7" +""".format( + working_directory.as_uri() + ) + + assert expected == content + + +def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry): + poetry.pool.add_repository( + LegacyRepository( + "custom", + "https://example.com/simple", + ) + ) + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://example.com/simple", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +--extra-index-url https://example.com/simple + +bar==4.5.6 \\ + --hash=sha256:67890 +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +def test_exporter_exports_requirements_txt_with_legacy_packages_trusted_host( + tmp_dir, poetry +): + poetry.pool.add_repository( + LegacyRepository( + "custom", + "http://example.com/simple", + ) + ) + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "http://example.com/simple", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +--trusted-host example.com +--extra-index-url http://example.com/simple + +bar==4.5.6 \\ + --hash=sha256:67890 +""" + + assert expected == content + + +@pytest.mark.parametrize( + ("dev", "expected"), + [ + (True, ["bar==1.2.2", "baz==1.2.3", "foo==1.2.1"]), + (False, ["bar==1.2.2", "foo==1.2.1"]), + ], +) +def test_exporter_exports_requirements_txt_with_dev_extras( + tmp_dir, poetry, dev, expected +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.1", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "1.2.2", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "baz": { + "version": ">=0.1.0", + "optional": True, + "markers": "extra == 'baz'", + } + }, + "extras": {"baz": ["baz (>=0.1.0)"]}, + }, + { + "name": "baz", + "version": "1.2.3", + "category": "dev", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + assert content == "{}\n".format("\n".join(expected)) + + +def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_sources( + tmp_dir, poetry +): + poetry.pool.add_repository( + LegacyRepository( + "custom", + "https://example.com/simple", + ) + ) + poetry.pool.add_repository( + LegacyRepository( + "custom", + "https://foobaz.com/simple", + ) + ) + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://example.com/simple", + "reference": "", + }, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://example.com/simple", + "reference": "", + }, + }, + { + "name": "baz", + "version": "7.8.9", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://foobaz.com/simple", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"], "baz": ["24680"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +--extra-index-url https://example.com/simple +--extra-index-url https://foobaz.com/simple + +bar==4.5.6 \\ + --hash=sha256:67890 +baz==7.8.9 \\ + --hash=sha256:24680 +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials( + tmp_dir, poetry, config +): + poetry.config.merge( + { + "repositories": {"custom": {"url": "https://example.com/simple"}}, + "http-basic": {"custom": {"username": "foo", "password": "bar"}}, + } + ) + poetry.pool.add_repository( + LegacyRepository("custom", "https://example.com/simple", config=poetry.config) + ) + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "dev", + "optional": False, + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://example.com/simple", + "reference": "", + }, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": ["12345"], "bar": ["67890"]}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export( + "requirements.txt", + Path(tmp_dir), + "requirements.txt", + dev=True, + with_credentials=True, + ) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + expected = """\ +--extra-index-url https://foo:bar@example.com/simple + +bar==4.5.6 \\ + --hash=sha256:67890 +foo==1.2.3 \\ + --hash=sha256:12345 +""" + + assert expected == content + + +def test_exporter_exports_requirements_txt_to_standard_output(tmp_dir, poetry, capsys): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), sys.stdout) + + out, err = capsys.readouterr() + expected = """\ +bar==4.5.6 +foo==1.2.3 +""" + + assert out == expected