From 9185c49b13983b2df7a8f282238c2c27509db629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20H=C3=B6fling?= Date: Sat, 8 Feb 2020 02:11:22 +0100 Subject: [PATCH 1/6] fix creation of stub-only packages by adding a check for a stub-only wheel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oleg Höfling --- poetry/masonry/utils/package_include.py | 22 ++++++++++++++----- .../pep_561_stub_only/pkg-stubs/__init__.pyi | 0 .../pep_561_stub_only/pkg-stubs/module.pyi | 4 ++++ .../pkg-stubs/subpkg/__init__.pyi | 0 .../fixtures/pep_561_stub_only/pyproject.toml | 14 ++++++++++++ .../pep_561_stub_only_src/pyproject.toml | 14 ++++++++++++ .../src/pkg-stubs/__init__.pyi | 0 .../src/pkg-stubs/module.pyi | 4 ++++ .../src/pkg-stubs/subpkg/__init__.pyi | 0 tests/masonry/builders/test_wheel.py | 15 +++++++++++++ 10 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/__init__.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/subpkg/__init__.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only/pyproject.toml create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_src/pyproject.toml create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/__init__.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/subpkg/__init__.pyi diff --git a/poetry/masonry/utils/package_include.py b/poetry/masonry/utils/package_include.py index 512c6c48f63..0484faf04d5 100644 --- a/poetry/masonry/utils/package_include.py +++ b/poetry/masonry/utils/package_include.py @@ -33,6 +33,20 @@ def refresh(self): # type: () -> PackageInclude return self.check_elements() + @property + def stub_only(self): # type: () -> bool + # returns `True` if this a PEP 561 stub-only package, + # see [PEP 561](https://www.python.org/dev/peps/pep-0561/#stub-only-packages) + return self.package.endswith("-stubs") and all( + el.suffix == ".pyi" for el in self.elements if el.is_file() + ) + + @property + def has_modules(self): # type: () -> bool + # Packages no longer need an __init__.py in python3, but there must + # at least be one .py file for it to be considered a package + return any(element.suffix == ".py" for element in self.elements) + def check_elements(self): # type: () -> PackageInclude if not self._elements: raise ValueError( @@ -43,20 +57,18 @@ def check_elements(self): # type: () -> PackageInclude if len(self._elements) > 1: # Probably glob self._is_package = True + self._package = root.parent.name - # Packages no longer need an __init__.py in python3, but there must - # at least be one .py file for it to be considered a package - if not any([element.suffix == ".py" for element in self._elements]): + if not self.stub_only and not self.has_modules: raise ValueError("{} is not a package.".format(root.name)) - self._package = root.parent.name else: if root.is_dir(): # If it's a directory, we include everything inside it self._package = root.name self._elements = sorted(list(root.glob("**/*"))) - if not any([element.suffix == ".py" for element in self._elements]): + if not self.stub_only and not self.has_modules: raise ValueError("{} is not a package.".format(root.name)) self._is_package = True diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/__init__.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi new file mode 100644 index 00000000000..d79e6e39ee0 --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/module.pyi @@ -0,0 +1,4 @@ +"""Example module""" +from typing import Tuple + +version_info = Tuple[int, int, int] diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/subpkg/__init__.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only/pkg-stubs/subpkg/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only/pyproject.toml b/tests/masonry/builders/fixtures/pep_561_stub_only/pyproject.toml new file mode 100644 index 00000000000..36a077afd95 --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "pep-561-stubs" +version = "0.1" +description = "PEP 561 stub package example" +authors = [ + "Oleg Höfling " +] +license = "MIT" +packages = [ + {include = "pkg-stubs"} +] + +[tool.poetry.dependencies] +python = "^3.6" diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_src/pyproject.toml b/tests/masonry/builders/fixtures/pep_561_stub_only_src/pyproject.toml new file mode 100644 index 00000000000..666b2b24f6d --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_src/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "pep-561-stubs" +version = "0.1" +description = "PEP 561 stub package example with an src layout" +authors = [ + "Oleg Höfling " +] +license = "MIT" +packages = [ + {include = "pkg-stubs", from = "src"} +] + +[tool.poetry.dependencies] +python = "^3.6" diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/__init__.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi new file mode 100644 index 00000000000..d79e6e39ee0 --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/module.pyi @@ -0,0 +1,4 @@ +"""Example module""" +from typing import Tuple + +version_info = Tuple[int, int, int] diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/subpkg/__init__.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_src/src/pkg-stubs/subpkg/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index ec3de8f7a60..1366c927e15 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -162,3 +162,18 @@ def test_dist_info_file_permissions(): z.getinfo("my_package-1.2.3.dist-info/entry_points.txt").external_attr == 0o644 << 16 ) + + +@pytest.mark.parametrize("package", ["pep_561_stub_only", "pep_561_stub_only_src"]) +def test_wheel_package_pep_561_stub_only(package): + root = fixtures_dir / package + WheelBuilder.make(Factory().create_poetry(root), NullEnv(), NullIO()) + + whl = root / "dist" / "pep_561_stubs-0.1-py3-none-any.whl" + + assert whl.exists() + + with zipfile.ZipFile(str(whl)) as z: + assert "pkg-stubs/__init__.pyi" in z.namelist() + assert "pkg-stubs/module.pyi" in z.namelist() + assert "pkg-stubs/subpkg/__init__.pyi" in z.namelist() From 13877ded13c1dc7cd5f09fc0c0af308803721959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20H=C3=B6fling?= Date: Sat, 8 Feb 2020 02:20:56 +0100 Subject: [PATCH 2/6] add a test for a stub-only source dist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oleg Höfling --- tests/masonry/builders/test_sdist.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/masonry/builders/test_sdist.py b/tests/masonry/builders/test_sdist.py index ed2ded503df..7b55e2acbb2 100644 --- a/tests/masonry/builders/test_sdist.py +++ b/tests/masonry/builders/test_sdist.py @@ -488,3 +488,21 @@ def test_excluded_subpackage(): exec(compile(setup_ast, filename="setup.py", mode="exec"), ns) assert ns["packages"] == ["example"] + + +def test_sdist_package_pep_561_stub_only(): + root = fixtures_dir / "pep_561_stub_only" + poetry = Factory().create_poetry(root) + + builder = SdistBuilder(poetry, NullEnv(), NullIO()) + builder.build() + + sdist = root / "dist" / "pep-561-stubs-0.1.tar.gz" + + assert sdist.exists() + + with tarfile.open(str(sdist), "r") as tar: + names = tar.getnames() + 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 From 49b4cba52897a6c7356ea7750fdf914665fd09f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20H=C3=B6fling?= Date: Sat, 8 Feb 2020 02:33:52 +0100 Subject: [PATCH 3/6] add tests for stub-only package name check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oleg Höfling --- .../fixtures/pep_561_stub_only/bad/__init__.pyi | 0 .../fixtures/pep_561_stub_only/bad/module.pyi | 4 ++++ .../pep_561_stub_only/good-stubs/__init__.pyi | 0 .../pep_561_stub_only/good-stubs/module.pyi | 4 ++++ tests/masonry/utils/test_package_include.py | 17 +++++++++++++++++ 5 files changed, 25 insertions(+) create mode 100644 tests/masonry/utils/fixtures/pep_561_stub_only/bad/__init__.pyi create mode 100644 tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi create mode 100644 tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/__init__.pyi create mode 100644 tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi diff --git a/tests/masonry/utils/fixtures/pep_561_stub_only/bad/__init__.pyi b/tests/masonry/utils/fixtures/pep_561_stub_only/bad/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi b/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi new file mode 100644 index 00000000000..d79e6e39ee0 --- /dev/null +++ b/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi @@ -0,0 +1,4 @@ +"""Example module""" +from typing import Tuple + +version_info = Tuple[int, int, int] diff --git a/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/__init__.pyi b/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi b/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi new file mode 100644 index 00000000000..d79e6e39ee0 --- /dev/null +++ b/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi @@ -0,0 +1,4 @@ +"""Example module""" +from typing import Tuple + +version_info = Tuple[int, int, int] diff --git a/tests/masonry/utils/test_package_include.py b/tests/masonry/utils/test_package_include.py index 36b5985800b..88ce75101d9 100644 --- a/tests/masonry/utils/test_package_include.py +++ b/tests/masonry/utils/test_package_include.py @@ -50,3 +50,20 @@ def test_package_include_with_non_existent_directory(): err_str = str(with_includes / "not_a_dir") + " does not contain any element" assert str(e.value) == err_str + + +def test_pep_561_stub_only_package_good_name_suffix(): + pkg_include = PackageInclude( + base=fixtures_dir / "pep_561_stub_only", include="good-stubs" + ) + assert pkg_include.elements == [ + fixtures_dir / "pep_561_stub_only/good-stubs/__init__.pyi", + fixtures_dir / "pep_561_stub_only/good-stubs/module.pyi", + ] + + +def test_pep_561_stub_only_package_bad_name_suffix(): + with pytest.raises(ValueError) as e: + PackageInclude(base=fixtures_dir / "pep_561_stub_only", include="bad") + + assert str(e.value) == "bad is not a package." From 1503688c5859b76d73f9d7c9bebfccbd48b0dde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oleg=20H=C3=B6fling?= Date: Mon, 2 Mar 2020 19:38:37 +0100 Subject: [PATCH 4/6] include the py.typed marker file if present without checking for content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oleg Höfling --- poetry/masonry/utils/package_include.py | 5 ++++- .../pkg-stubs/__init__.pyi | 0 .../pkg-stubs/module.pyi | 4 ++++ .../pkg-stubs/py.typed | 1 + .../pkg-stubs/subpkg/__init__.pyi | 0 .../pep_561_stub_only_partial/pyproject.toml | 14 ++++++++++++++ tests/masonry/builders/test_wheel.py | 17 ++++++++++++++++- 7 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/__init__.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/py.typed create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/subpkg/__init__.pyi create mode 100644 tests/masonry/builders/fixtures/pep_561_stub_only_partial/pyproject.toml diff --git a/poetry/masonry/utils/package_include.py b/poetry/masonry/utils/package_include.py index 0484faf04d5..15c4a9d6c78 100644 --- a/poetry/masonry/utils/package_include.py +++ b/poetry/masonry/utils/package_include.py @@ -38,7 +38,10 @@ def stub_only(self): # type: () -> bool # returns `True` if this a PEP 561 stub-only package, # see [PEP 561](https://www.python.org/dev/peps/pep-0561/#stub-only-packages) return self.package.endswith("-stubs") and all( - el.suffix == ".pyi" for el in self.elements if el.is_file() + el.suffix == ".pyi" + or (el.parent.name == self.package and el.name == "py.typed") + for el in self.elements + if el.is_file() ) @property diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/__init__.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi new file mode 100644 index 00000000000..d79e6e39ee0 --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/module.pyi @@ -0,0 +1,4 @@ +"""Example module""" +from typing import Tuple + +version_info = Tuple[int, int, int] diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/py.typed b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/py.typed new file mode 100644 index 00000000000..b648ac92333 --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/subpkg/__init__.pyi b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pkg-stubs/subpkg/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pyproject.toml b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pyproject.toml new file mode 100644 index 00000000000..db202c04a1c --- /dev/null +++ b/tests/masonry/builders/fixtures/pep_561_stub_only_partial/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "pep-561-stubs" +version = "0.1" +description = "PEP 561 stub package example with the py.typed marker file" +authors = [ + "Oleg Höfling " +] +license = "MIT" +packages = [ + {include = "pkg-stubs"} +] + +[tool.poetry.dependencies] +python = "^3.6" diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index 1366c927e15..36f8c27170b 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -164,7 +164,10 @@ def test_dist_info_file_permissions(): ) -@pytest.mark.parametrize("package", ["pep_561_stub_only", "pep_561_stub_only_src"]) +@pytest.mark.parametrize( + "package", + ["pep_561_stub_only", "pep_561_stub_only_partial", "pep_561_stub_only_src"], +) def test_wheel_package_pep_561_stub_only(package): root = fixtures_dir / package WheelBuilder.make(Factory().create_poetry(root), NullEnv(), NullIO()) @@ -177,3 +180,15 @@ def test_wheel_package_pep_561_stub_only(package): assert "pkg-stubs/__init__.pyi" in z.namelist() assert "pkg-stubs/module.pyi" in z.namelist() assert "pkg-stubs/subpkg/__init__.pyi" in z.namelist() + + +def test_wheel_package_pep_561_stub_only_includes_typed_marker(): + root = fixtures_dir / "pep_561_stub_only_partial" + WheelBuilder.make(Factory().create_poetry(root), NullEnv(), NullIO()) + + whl = root / "dist" / "pep_561_stubs-0.1-py3-none-any.whl" + + assert whl.exists() + + with zipfile.ZipFile(str(whl)) as z: + assert "pkg-stubs/py.typed" in z.namelist() From 20abaed82d96adcf5bd8e0de11794e8c842ece30 Mon Sep 17 00:00:00 2001 From: "oleg.hoefling" Date: Wed, 22 Apr 2020 19:40:49 +0200 Subject: [PATCH 5/6] apply isort formatting Signed-off-by: oleg.hoefling --- tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi | 1 + .../utils/fixtures/pep_561_stub_only/good-stubs/module.pyi | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi b/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi index d79e6e39ee0..f85a07d465a 100644 --- a/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi +++ b/tests/masonry/utils/fixtures/pep_561_stub_only/bad/module.pyi @@ -1,4 +1,5 @@ """Example module""" from typing import Tuple + version_info = Tuple[int, int, int] diff --git a/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi b/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi index d79e6e39ee0..f85a07d465a 100644 --- a/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi +++ b/tests/masonry/utils/fixtures/pep_561_stub_only/good-stubs/module.pyi @@ -1,4 +1,5 @@ """Example module""" from typing import Tuple + version_info = Tuple[int, int, int] From b12c805a97e54f2eca212fc878debd275d6113ee Mon Sep 17 00:00:00 2001 From: "oleg.hoefling" Date: Wed, 22 Apr 2020 20:02:59 +0200 Subject: [PATCH 6/6] turn is_stub_only and has_modules properties to methods Signed-off-by: oleg.hoefling --- poetry/masonry/utils/package_include.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/poetry/masonry/utils/package_include.py b/poetry/masonry/utils/package_include.py index 15c4a9d6c78..47345765f07 100644 --- a/poetry/masonry/utils/package_include.py +++ b/poetry/masonry/utils/package_include.py @@ -33,8 +33,7 @@ def refresh(self): # type: () -> PackageInclude return self.check_elements() - @property - def stub_only(self): # type: () -> bool + def is_stub_only(self): # type: () -> bool # returns `True` if this a PEP 561 stub-only package, # see [PEP 561](https://www.python.org/dev/peps/pep-0561/#stub-only-packages) return self.package.endswith("-stubs") and all( @@ -44,7 +43,6 @@ def stub_only(self): # type: () -> bool if el.is_file() ) - @property def has_modules(self): # type: () -> bool # Packages no longer need an __init__.py in python3, but there must # at least be one .py file for it to be considered a package @@ -62,7 +60,7 @@ def check_elements(self): # type: () -> PackageInclude self._is_package = True self._package = root.parent.name - if not self.stub_only and not self.has_modules: + if not self.is_stub_only() and not self.has_modules(): raise ValueError("{} is not a package.".format(root.name)) else: @@ -71,7 +69,7 @@ def check_elements(self): # type: () -> PackageInclude self._package = root.name self._elements = sorted(list(root.glob("**/*"))) - if not self.stub_only and not self.has_modules: + if not self.is_stub_only() and not self.has_modules(): raise ValueError("{} is not a package.".format(root.name)) self._is_package = True