diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 000000000..6a1c942df --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,25 @@ +name: Integration + +on: [push, pull_request] + +jobs: + Tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.8] + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Install tox + shell: bash + run: pip install --upgrade tox + + - name: Execute integration tests + shell: bash + run: tox -e integration diff --git a/poetry.lock b/poetry.lock index 30c7b485d..aa1a5fbce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -52,6 +52,27 @@ version = "1.6.1" docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"] +[[package]] +category = "dev" +description = "Backport of new features in Python's tempfile module" +marker = "python_version >= \"2.7\" and python_version < \"2.8\"" +name = "backports.tempfile" +optional = false +python-versions = "*" +version = "1.0" + +[package.dependencies] +"backports.weakref" = "*" + +[[package]] +category = "dev" +description = "Backport of new features in Python's weakref module" +marker = "python_version >= \"2.7\" and python_version < \"2.8\"" +name = "backports.weakref" +optional = false +python-versions = "*" +version = "1.0.post1" + [[package]] category = "dev" description = "Python package for providing Mozilla's CA Bundle." @@ -225,7 +246,7 @@ description = "File identification library for Python" name = "identify" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.14" +version = "1.4.15" [package.extras] license = ["editdistance"] @@ -432,6 +453,25 @@ six = "*" python = "<3.5" version = "*" +[[package]] +category = "dev" +description = "Wrappers to build Python packages using PEP 517 hooks" +name = "pep517" +optional = false +python-versions = "*" +version = "0.8.2" + +[package.dependencies] +toml = "*" + +[package.dependencies.importlib_metadata] +python = "<3.8" +version = "*" + +[package.dependencies.zipp] +python = "<3.8" +version = "*" + [[package]] category = "dev" description = "plugin and hook calling mechanisms for python" @@ -810,7 +850,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] -content-hash = "f8aaed4e553f8abf43fed9da327d34677cf7ec6f8f95f3c30980ecf950c673ec" +content-hash = "b469a2ac37329dddb2b7029a732711fb431100f468624974148c481f79ecaf0c" python-versions = "~2.7 || ^3.5" [metadata.files] @@ -834,6 +874,14 @@ attrs = [ {file = "backports.functools_lru_cache-1.6.1-py2.py3-none-any.whl", hash = "sha256:0bada4c2f8a43d533e4ecb7a12214d9420e66eb206d54bf2d682581ca4b80848"}, {file = "backports.functools_lru_cache-1.6.1.tar.gz", hash = "sha256:8fde5f188da2d593bd5bc0be98d9abc46c95bb8a9dde93429570192ee6cc2d4a"}, ] +"backports.tempfile" = [ + {file = "backports.tempfile-1.0-py2.py3-none-any.whl", hash = "sha256:05aa50940946f05759696156a8c39be118169a0e0f94a49d0bb106503891ff54"}, + {file = "backports.tempfile-1.0.tar.gz", hash = "sha256:1c648c452e8770d759bdc5a5e2431209be70d25484e1be24876cf2168722c762"}, +] +"backports.weakref" = [ + {file = "backports.weakref-1.0.post1-py2.py3-none-any.whl", hash = "sha256:81bc9b51c0abc58edc76aefbbc68c62a787918ffe943a37947e162c3f8e19e82"}, + {file = "backports.weakref-1.0.post1.tar.gz", hash = "sha256:bc4170a29915f8b22c9e7c4939701859650f2eb84184aee80da329ac0b9825c2"}, +] certifi = [ {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, @@ -928,8 +976,8 @@ futures = [ {file = "futures-3.3.0.tar.gz", hash = "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"}, ] identify = [ - {file = "identify-1.4.14-py2.py3-none-any.whl", hash = "sha256:2bb8760d97d8df4408f4e805883dad26a2d076f04be92a10a3e43f09c6060742"}, - {file = "identify-1.4.14.tar.gz", hash = "sha256:faffea0fd8ec86bb146ac538ac350ed0c73908326426d387eded0bcc9d077522"}, + {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, + {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, ] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, @@ -977,6 +1025,10 @@ pathlib2 = [ {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, ] +pep517 = [ + {file = "pep517-0.8.2-py2.py3-none-any.whl", hash = "sha256:576c480be81f3e1a70a16182c762311eb80d1f8a7b0d11971e5234967d7a342c"}, + {file = "pep517-0.8.2.tar.gz", hash = "sha256:8e6199cf1288d48a0c44057f112acf18aa5ebabbf73faa242f598fbe145ba29e"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, diff --git a/poetry/core/factory.py b/poetry/core/factory.py index 22b892720..323481aa1 100644 --- a/poetry/core/factory.py +++ b/poetry/core/factory.py @@ -109,7 +109,10 @@ def create_poetry(self, cwd=None): # type: (Optional[Path]) -> Poetry break if "build" in local_config: - package.build = local_config["build"] + build = local_config["build"] + if not isinstance(build, dict): + build = {"script": build} + package.build_config = build or {} if "include" in local_config: package.include = local_config["include"] diff --git a/poetry/core/json/schemas/poetry-schema.json b/poetry/core/json/schemas/poetry-schema.json index 8397ba8ab..e3e7341b8 100644 --- a/poetry/core/json/schemas/poetry-schema.json +++ b/poetry/core/json/schemas/poetry-schema.json @@ -131,8 +131,7 @@ } }, "build": { - "type": "string", - "description": "The file used to build extensions." + "$ref": "#/definitions/build-section" }, "source": { "type": "array", @@ -526,6 +525,31 @@ "description": "Declare this repository as secondary, i.e. it will only be looked up last for packages." } } + }, + "build-script": { + "type": "string", + "description": "The python script file used to build extensions." + }, + "build-config": { + "type": "object", + "description": "Build specific configurations.", + "additionalProperties": false, + "properties": { + "generate-setup-file": { + "type": "boolean", + "description": "Generate and include a setup.py file in sdist.", + "default": true + }, + "script": { + "$ref": "#/definitions/build-script" + } + } + }, + "build-section": { + "oneOf": [ + {"$ref": "#/definitions/build-script"}, + {"$ref": "#/definitions/build-config"} + ] } } } diff --git a/poetry/core/masonry/builders/builder.py b/poetry/core/masonry/builders/builder.py index 7f174610a..d903b3a6a 100644 --- a/poetry/core/masonry/builders/builder.py +++ b/poetry/core/masonry/builders/builder.py @@ -161,8 +161,8 @@ def find_files_to_add(self, exclude_build=True): # type: (bool) -> list # If a build script is specified and explicitely required # we add it to the list of files - if self._package.build and not exclude_build: - to_add.append(Path(self._package.build)) + if self._package.build_script and not exclude_build: + to_add.append(Path(self._package.build_script)) return sorted(to_add) diff --git a/poetry/core/masonry/builders/sdist.py b/poetry/core/masonry/builders/sdist.py index b2ebd1373..eb456c378 100644 --- a/poetry/core/masonry/builders/sdist.py +++ b/poetry/core/masonry/builders/sdist.py @@ -87,11 +87,12 @@ def build(self, target_dir=None): # type: (Path) -> Path else: tar.addfile(tar_info) # Symlinks & ? - setup = self.build_setup() - tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py")) - tar_info.size = len(setup) - tar_info.mtime = time.time() - tar.addfile(tar_info, BytesIO(setup)) + if not self._poetry.package.build_config.get("generate-setup-file", False): + setup = self.build_setup() + tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py")) + tar_info.size = len(setup) + tar_info.mtime = time.time() + tar.addfile(tar_info, BytesIO(setup)) pkg_info = self.build_pkg_info() @@ -112,9 +113,9 @@ def build_setup(self): # type: () -> bytes package_dir = {} # If we have a build script, use it - if self._package.build: + if self._package.build_script: after += [ - "from {} import *".format(self._package.build.split(".")[0]), + "from {} import *".format(self._package.build_script.split(".")[0]), "build(setup_kwargs)", ] diff --git a/poetry/core/masonry/builders/wheel.py b/poetry/core/masonry/builders/wheel.py index 257ac67f5..16eec2c2a 100644 --- a/poetry/core/masonry/builders/wheel.py +++ b/poetry/core/masonry/builders/wheel.py @@ -91,7 +91,7 @@ def build(self): logger.info(" - Built {}".format(self.wheel_filename)) def _build(self, wheel): - if self._package.build: + if self._package.build_script: with SdistBuilder(poetry=self._poetry).setup_py() as setup: # We need to place ourselves in the temporary # directory in order to build the package @@ -223,7 +223,7 @@ def dist_info_name(self, distribution, version): # type: (...) -> str @property def tag(self): - if self._package.build: + if self._package.build_script: tag = next(sys_tags()) tag = (tag.interpreter, tag.abi, tag.platform) else: @@ -305,7 +305,7 @@ def _write_wheel_file(self, fp): fp.write( wheel_file_template.format( version=__version__, - pure_lib="true" if self._package.build is None else "false", + pure_lib="true" if self._package.build_script is None else "false", tag=self.tag, ) ) diff --git a/poetry/core/packages/project_package.py b/poetry/core/packages/project_package.py index e8201080b..d90aebbf1 100644 --- a/poetry/core/packages/project_package.py +++ b/poetry/core/packages/project_package.py @@ -1,3 +1,5 @@ +from typing import Optional + from poetry.core.semver import VersionRange from poetry.core.semver import parse_constraint from poetry.core.version.markers import parse_marker @@ -10,7 +12,7 @@ class ProjectPackage(Package): def __init__(self, name, version, pretty_version=None): super(ProjectPackage, self).__init__(name, version, pretty_version) - self.build = None + self.build_config = dict() self.packages = [] self.include = [] self.exclude = [] @@ -19,6 +21,10 @@ def __init__(self, name, version, pretty_version=None): if self._python_versions == "*": self._python_constraint = parse_constraint("~2.7 || >=3.4") + @property + def build_script(self): # type: () -> Optional[str] + return self.build_config.get("script") + def is_root(self): return True diff --git a/poetry/core/poetry.py b/poetry/core/poetry.py index 0c2e33d73..6182b93ed 100644 --- a/poetry/core/poetry.py +++ b/poetry/core/poetry.py @@ -1,6 +1,8 @@ from __future__ import absolute_import from __future__ import unicode_literals +from typing import Any + from .packages import ProjectPackage from .utils._compat import Path from .utils.toml_file import TomlFile @@ -25,3 +27,6 @@ def package(self): # type: () -> ProjectPackage @property def local_config(self): # type: () -> dict return self._local_config + + def get_project_config(self, config, default=None): # type: (str, Any) -> Any + return self._local_config.get("config", {}).get(config, default) diff --git a/pyproject.toml b/pyproject.toml index d02f19a91..117ea9b8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,8 @@ cleo = "^0.7.6" tomlkit = "^0.5.8" vendoring = {version = "^0.2.2", python = "~3.8"} isort = "^4.3.21" +pep517 = "^0.8.2" +"backports.tempfile" = {version = "^1.0", python = "~2.7"} [tool.intreehooks] build-backend = "poetry.core.masonry.api" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..1269d5bab --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,71 @@ +from typing import Callable + +import pytest + +from poetry.core.utils._compat import Path +from tests.utils import tempfile + + +def pytest_addoption(parser): + parser.addoption( + "--integration", + action="store_true", + dest="integration", + default=False, + help="enable integration tests", + ) + + +def pytest_configure(config): + config.addinivalue_line("markers", "integration: mark integration tests") + + if not config.option.integration: + setattr(config.option, "markexpr", "not integration") + + +def get_project_from_dir(base_directory): # type: (Path) -> Callable[[str], Path] + def get(name): # type: (str) -> Path + path = base_directory / name + if not path.exists(): + raise FileNotFoundError(str(path)) + return path + + return get + + +@pytest.fixture(scope="session") +def project_source_root(): # type: () -> Path + return Path(__file__).parent.parent + + +@pytest.fixture(scope="session") +def project_source_test_root(): # type: () -> Path + return Path(__file__).parent + + +@pytest.fixture(scope="session") +def common_fixtures_directory(project_source_test_root): # type: (Path) -> Path + return project_source_test_root / "fixtures" + + +@pytest.fixture(scope="session") +def common_project(common_fixtures_directory): # type: (Path) -> Callable[[str], Path] + return get_project_from_dir(common_fixtures_directory) + + +@pytest.fixture(scope="session") +def masonry_fixtures_directory(project_source_test_root): # type: (Path) -> Path + return project_source_test_root / "masonry" / "builders" / "fixtures" + + +@pytest.fixture(scope="session") +def masonry_project( + masonry_fixtures_directory, +): # type: (Path) -> Callable[[str], Path] + return get_project_from_dir(masonry_fixtures_directory) + + +@pytest.fixture +def temporary_directory(): # type: () -> Path + with tempfile.TemporaryDirectory(prefix="poetry-core") as tmp: + yield Path(tmp) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/test_pep517.py b/tests/integration/test_pep517.py new file mode 100644 index 000000000..0ef13e21d --- /dev/null +++ b/tests/integration/test_pep517.py @@ -0,0 +1,80 @@ +import sys + +import pytest + +from pep517.build import build +from pep517.check import check +from poetry.core.utils._compat import PY35 +from poetry.core.utils._compat import Path +from tests.utils import subprocess_run +from tests.utils import temporary_project_directory + + +pytestmark = pytest.mark.integration + + +@pytest.mark.parametrize( + "getter, project", + [ + ("common_project", "simple_project"), + ("masonry_project", "src_extended"), + ("masonry_project", "disable_setup_py"), + ], +) +def test_pep517_check_poetry_managed(request, getter, project): + with temporary_project_directory(request.getfixturevalue(getter)(project)) as path: + assert check(path) + + +def test_pep517_check(project_source_root): + assert check(str(project_source_root)) + + +def test_pep517_build_sdist(temporary_directory, project_source_root): + build( + source_dir=str(project_source_root), dist="sdist", dest=str(temporary_directory) + ) + distributions = list(temporary_directory.glob("poetry-core-*.tar.gz")) + assert len(distributions) == 1 + + +def test_pep517_build_wheel(temporary_directory, project_source_root): + build( + source_dir=str(project_source_root), dist="wheel", dest=str(temporary_directory) + ) + distributions = list(temporary_directory.glob("poetry_core-*-none-any.whl")) + assert len(distributions) == 1 + + +def test_pip_wheel_build(temporary_directory, project_source_root): + tmp = str(temporary_directory) + pip = subprocess_run( + "pip", "wheel", "--use-pep517", "-w", tmp, str(project_source_root) + ) + assert "Successfully built poetry-core" in pip.stdout + + assert pip.returncode == 0 + + wheels = list(Path(tmp).glob("poetry_core-*-none-any.whl")) + assert len(wheels) == 1 + + +@pytest.mark.skipif(not PY35, reason="This test uses the python built-in venv module.") +def test_pip_install_no_binary(temporary_directory, project_source_root): + venv_dir = temporary_directory / ".venv" + venv_python = venv_dir / "bin" / "python" + + subprocess_run(sys.executable, "-m", "venv", str(venv_dir)) + subprocess_run(str(venv_python), "-m", "pip", "install", "--upgrade", "pip") + subprocess_run( + str(venv_python), + "-m", + "pip", + "install", + "--no-binary", + ":all:", + str(project_source_root), + ) + + pip_show = subprocess_run(str(venv_python), "-m", "pip", "show", "poetry-core") + assert "Name: poetry-core" in pip_show.stdout diff --git a/tests/masonry/builders/fixtures/disable_setup_py/README.rst b/tests/masonry/builders/fixtures/disable_setup_py/README.rst new file mode 100644 index 000000000..f7fe15470 --- /dev/null +++ b/tests/masonry/builders/fixtures/disable_setup_py/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/masonry/builders/fixtures/disable_setup_py/my_package/__init__.py b/tests/masonry/builders/fixtures/disable_setup_py/my_package/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/masonry/builders/fixtures/disable_setup_py/pyproject.toml b/tests/masonry/builders/fixtures/disable_setup_py/pyproject.toml new file mode 100644 index 000000000..efc4c8baf --- /dev/null +++ b/tests/masonry/builders/fixtures/disable_setup_py/pyproject.toml @@ -0,0 +1,35 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Poetry Team " +] +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" +] + +[tool.poetry.build] +generate-setup-file = true + +# Requirements +[tool.poetry.dependencies] +python = "~2.7 || ^3.6" + +[tool.poetry.extras] + +[tool.poetry.dev-dependencies] + +[tool.poetry.scripts] +my-script = "my_package:main" diff --git a/tests/masonry/builders/fixtures/extended/pyproject.toml b/tests/masonry/builders/fixtures/extended/pyproject.toml index f8e8bd810..6266092b7 100644 --- a/tests/masonry/builders/fixtures/extended/pyproject.toml +++ b/tests/masonry/builders/fixtures/extended/pyproject.toml @@ -11,4 +11,5 @@ readme = "README.rst" homepage = "https://python-poetry.org/" -build = "build.py" +[tool.poetry.build] +script = "build.py" diff --git a/tests/masonry/builders/test_sdist.py b/tests/masonry/builders/test_sdist.py index cdec918b0..3af32d252 100644 --- a/tests/masonry/builders/test_sdist.py +++ b/tests/masonry/builders/test_sdist.py @@ -298,7 +298,8 @@ def test_prelease(): assert sdist.exists() -def test_with_c_extensions(): +@pytest.mark.parametrize("directory", [("extended"), ("extended_legacy_config")]) +def test_with_c_extensions(directory): poetry = Factory().create_poetry(project("extended")) builder = SdistBuilder(poetry) @@ -524,3 +525,23 @@ def test_sdist_package_pep_561_stub_only(): assert "pep-561-stubs-0.1/pkg-stubs/__init__.pyi" in names assert "pep-561-stubs-0.1/pkg-stubs/module.pyi" in names assert "pep-561-stubs-0.1/pkg-stubs/subpkg/__init__.pyi" in names + + +def test_sdist_disable_setup_py(): + module_path = fixtures_dir / "disable_setup_py" + poetry = Factory().create_poetry(module_path) + + builder = SdistBuilder(poetry) + builder.build() + + sdist = module_path / "dist" / "my-package-1.2.3.tar.gz" + + assert sdist.exists() + + with tarfile.open(str(sdist), "r") as tar: + assert set(tar.getnames()) == { + "my-package-1.2.3/README.rst", + "my-package-1.2.3/pyproject.toml", + "my-package-1.2.3/PKG-INFO", + "my-package-1.2.3/my_package/__init__.py", + } diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 000000000..b18ae6a5f --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,64 @@ +import shutil +import subprocess + +from contextlib import contextmanager +from typing import Any +from typing import ContextManager +from typing import Dict +from typing import Optional + +from poetry.core.utils._compat import Path +from poetry.core.utils.toml_file import TomlFile + + +try: + from backports import tempfile +except ImportError: + import tempfile + + +__toml_build_backend_patch__ = { + "build-system": { + "requires": [str(Path(__file__).parent.parent)], + "build-backend": "poetry.core.masonry.api", + } +} + + +@contextmanager +def temporary_project_directory( + path, toml_patch=None +): # type: (Path, Optional[Dict[str, Any]]) -> ContextManager[str] + """ + Context manager that takes a project source directory, copies content to a temporary + directory, patches the `pyproject.toml` using the provided patch, or using the default + patch if one is not given. The default path replaces `build-system` section in order + to use the working copy of poetry-core as the backend. + + Once the context, exists, the temporary directory is cleaned up. + + :param path: Source project root directory to copy from. + :param toml_patch: Patch to use for the pyproject.toml, defaults to build system patching. + :return: A temporary copy + """ + assert (path / "pyproject.toml").exists() + + with tempfile.TemporaryDirectory(prefix="poetry-core-pep517") as tmp: + dst = Path(tmp) / path.name + shutil.copytree(str(path), dst) + toml = TomlFile(str(dst / "pyproject.toml")) + data = toml.read() + data.update(toml_patch or __toml_build_backend_patch__) + toml.write(data) + yield str(dst) + + +def subprocess_run(*args, **kwargs): # type: (str, Any) -> subprocess.CompletedProcess + """ + Helper method to run a subprocess. Asserts for success. + """ + result = subprocess.run( + args, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs + ) + assert result.returncode == 0 + return result diff --git a/tox.ini b/tox.ini index 958f69b89..655fb5430 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,20 @@ [tox] -skipsdist = true -envlist = py27, py35, py36, py37, py38, pypy, pypy3 +minversion = 3.3.0 +isolated_build = True +envlist = py27, py35, py36, py37, py38, pypy, pypy3, integration [testenv] whitelist_externals = poetry skip_install = true commands = poetry install -v - poetry run pytest tests/ + poetry run pytest {posargs} tests/ + + +[testenv:integration] +basepython = python3 +deps = + pytest + pep517 +commands = + pytest --integration {posargs} tests/integration