diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ad7f7b39b..04a13bcded8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Added a `env use` command to control the Python version used by the project. - Added a `env list` command to list the virtualenvs associated with the current project. - Added a `env remove` command to delete virtualenvs associated with the current project. +- Added support for `POETRY_HOME` declaration within `get-poetry.py`. - Added support for declaring a specific source for dependencies. - Added support for disabling PyPI and making another repository the default one. - Added support for declaring private repositories as secondary. diff --git a/docs/docs/index.md b/docs/docs/index.md index 2d4d12a0bf6..9cbf2d60736 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -46,6 +46,12 @@ python get-poetry.py --uninstall POETRY_UNINSTALL=1 python get-poetry.py ``` +By default, Poetry is installed into the user's platform-specific home directory. If you wish to change this, you may define the `POETRY_HOME` environment variable: + +```bash +POETRY_HOME=/etc/poetry python get-poetry.py +``` + If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py` or by using the `POETRY_PREVIEW` environment variable: @@ -111,28 +117,28 @@ pipx uninstall poetry ## Updating `poetry` -Updating poetry to the latest stable version is as simple as calling the `self:update` command. +Updating Poetry to the latest stable version is as simple as calling the `self update` command. ```bash -poetry self:update +poetry self update ``` -If you want to install prerelease versions, you can use the `--preview` option. +If you want to install pre-release versions, you can use the `--preview` option. ```bash -poetry self:update --preview +poetry self update --preview ``` -And finally, if you want to install a specific version you can pass it as an argument -to `self:update`. +And finally, if you want to install a specific version, you can pass it as an argument +to `self update`. ```bash -poetry self:update 0.8.0 +poetry self update 0.8.0 ``` !!!note - The `self:update` command will only work if you used the recommended + The `self update` command will only work if you used the recommended installer to install Poetry. diff --git a/get-poetry.py b/get-poetry.py index 601342574cf..9df2be8ac4e 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -1,5 +1,5 @@ """ -This script will install poetry and its dependencies +This script will install Poetry and its dependencies in isolation from the rest of the system. It does, in order: @@ -65,6 +65,7 @@ except NameError: u = str +SHELL = os.getenv("SHELL", "") WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") @@ -189,7 +190,7 @@ def expanduser(path): HOME = expanduser("~") -POETRY_HOME = os.path.join(HOME, ".poetry") +POETRY_HOME = os.environ.get("POETRY_HOME") or os.path.join(HOME, ".poetry") POETRY_BIN = os.path.join(POETRY_HOME, "bin") POETRY_ENV = os.path.join(POETRY_HOME, "env") POETRY_LIB = os.path.join(POETRY_HOME, "lib") @@ -249,6 +250,9 @@ def expanduser(path): {rcfiles}""" +PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by +modifying the `fish_user_paths` universal variable.""" + PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" @@ -264,6 +268,12 @@ def expanduser(path): To configure your current shell run `source {poetry_home_env}` """ +POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great! + +{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH` +environment variable by modifying the `fish_user_paths` universal variable. +""" + POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` @@ -279,6 +289,15 @@ def expanduser(path): To configure your current shell run `source {poetry_home_env}` """ +POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) +in your `PATH` environment variable, which you can add by running +the following command: + + set -U fish_user_paths {poetry_home_bin} $fish_user_paths +""" + POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` @@ -605,6 +624,9 @@ def update_path(self): if not self._modify_path: return + if "fish" in SHELL: + return self.add_to_fish_path() + if WINDOWS: return self.add_to_windows_path() @@ -625,6 +647,40 @@ def update_path(self): with open(profile, "a") as f: f.write(u(addition)) + def add_to_fish_path(self): + """ + Ensure POETRY_BIN directory is on Fish shell $PATH + """ + current_path = os.environ.get("PATH", None) + if current_path is None: + print( + colorize( + "warning", + "\nUnable to get the PATH value. It will not be updated automatically.", + ) + ) + self._modify_path = False + + return + + if POETRY_BIN not in current_path: + fish_user_paths = subprocess.check_output( + ["fish", "-c", "echo $fish_user_paths"] + ).decode("utf-8") + if POETRY_BIN not in fish_user_paths: + cmd = "set -U fish_user_paths {} $fish_user_paths".format(POETRY_BIN) + set_fish_user_path = ["fish", "-c", "{}".format(cmd)] + subprocess.check_output(set_fish_user_path) + else: + print( + colorize( + "warning", + "\nPATH already contains {} and thus was not modified.".format( + POETRY_BIN + ), + ) + ) + def add_to_windows_path(self): try: old_path = self.get_windows_path_var() @@ -685,11 +741,25 @@ def set_windows_path_var(self, value): ) def remove_from_path(self): - if WINDOWS: + if "fish" in SHELL: + return self.remove_from_fish_path() + + elif WINDOWS: return self.remove_from_windows_path() return self.remove_from_unix_path() + def remove_from_fish_path(self): + fish_user_paths = subprocess.check_output( + ["fish", "-c", "echo $fish_user_paths"] + ).decode("utf-8") + if POETRY_BIN in fish_user_paths: + cmd = "set -U fish_user_paths (string match -v {} $fish_user_paths)".format( + POETRY_BIN + ) + set_fish_user_path = ["fish", "-c", "{}".format(cmd)] + subprocess.check_output(set_fish_user_path) + def remove_from_windows_path(self): path = self.get_windows_path_var() @@ -741,8 +811,7 @@ def get_export_string(self): def get_unix_profiles(self): profiles = [os.path.join(HOME, ".profile")] - shell = os.getenv("SHELL", "") - if "zsh" in shell: + if "zsh" in SHELL: zdotdir = os.getenv("ZDOTDIR", HOME) profiles.append(os.path.join(zdotdir, ".zprofile")) @@ -766,7 +835,9 @@ def display_pre_message(self): if not self._modify_path: kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH else: - if WINDOWS: + if "fish" in SHELL: + kwargs["platform_msg"] = PRE_MESSAGE_FISH + elif WINDOWS: kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS else: profiles = [ @@ -809,6 +880,12 @@ def display_post_message(self, version): poetry_home_bin = POETRY_BIN.replace( os.getenv("USERPROFILE", ""), "%USERPROFILE%" ) + elif "fish" in SHELL: + message = POST_MESSAGE_FISH + if not self._modify_path: + message = POST_MESSAGE_FISH_NO_MODIFY_PATH + + poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") else: message = POST_MESSAGE_UNIX if not self._modify_path: diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index d5d37c7f736..df19b7e3359 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -4,7 +4,6 @@ from clikit.api.io import IO from clikit.io import NullIO -from poetry.packages import Dependency from poetry.packages import Locker from poetry.packages import Package from poetry.puzzle import Solver @@ -16,6 +15,7 @@ from poetry.repositories import Repository from poetry.repositories.installed_repository import InstalledRepository from poetry.semver import parse_constraint +from poetry.utils.extras import get_extra_package_names from poetry.utils.helpers import canonicalize_name from .base_installer import BaseInstaller @@ -399,7 +399,7 @@ def _get_operations_from_lock( installed_repo = self._installed_repository ops = [] - extra_packages = [p.name for p in self._get_extra_packages(locked_repository)] + extra_packages = self._get_extra_packages(locked_repository) for locked in locked_repository.packages: is_installed = False for installed in installed_repo.packages: @@ -429,7 +429,7 @@ def _get_operations_from_lock( def _filter_operations( self, ops, repo ): # type: (List[Operation], Repository) -> None - extra_packages = [p.name for p in self._get_extra_packages(repo)] + extra_packages = self._get_extra_packages(repo) for op in ops: if isinstance(op, Update): package = op.target_package @@ -468,9 +468,9 @@ def _filter_operations( if package.category == "dev" and not self.is_dev_mode(): op.skip("Dev dependencies not requested") - def _get_extra_packages(self, repo): + def _get_extra_packages(self, repo): # type: (Repository) -> List[str] """ - Returns all packages required by extras. + Returns all package names required by extras. Maybe we just let the solver handle it? """ @@ -479,26 +479,7 @@ def _get_extra_packages(self, repo): else: extras = self._locker.lock_data.get("extras", {}) - extra_packages = [] - for extra_name, packages in extras.items(): - if extra_name not in self._extras: - continue - - extra_packages += [Dependency(p, "*") for p in packages] - - def _extra_packages(packages): - pkgs = [] - for package in packages: - for pkg in repo.packages: - if pkg.name == package.name: - pkgs.append(package) - pkgs += _extra_packages(pkg.requires) - - break - - return pkgs - - return _extra_packages(extra_packages) + return list(get_extra_package_names(repo.packages, extras, self._extras)) def _get_installer(self): # type: () -> BaseInstaller return PipInstaller(self._env, self._io, self._pool) diff --git a/poetry/masonry/builders/builder.py b/poetry/masonry/builders/builder.py index b5b147ccc7e..af1a05a29d4 100644 --- a/poetry/masonry/builders/builder.py +++ b/poetry/masonry/builders/builder.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import os import re import shutil import tempfile @@ -34,7 +33,6 @@ class Builder(object): - AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"} format = None @@ -86,8 +84,18 @@ def find_excluded_files(self): # type: () -> Set[str] explicitely_excluded = set() for excluded_glob in self._package.exclude: + excluded_path = Path(self._path, excluded_glob) + + try: + is_dir = excluded_path.is_dir() + except OSError: + # On Windows, testing if a path with a glob is a directory will raise an OSError + is_dir = False + if is_dir: + excluded_glob = Path(excluded_glob, "**/*") + for excluded in glob( - os.path.join(self._path.as_posix(), str(excluded_glob)), recursive=True + Path(self._path, excluded_glob).as_posix(), recursive=True ): explicitely_excluded.add( Path(excluded).relative_to(self._path).as_posix() diff --git a/poetry/masonry/builders/wheel.py b/poetry/masonry/builders/wheel.py index 1293ff39781..058679edb4e 100644 --- a/poetry/masonry/builders/wheel.py +++ b/poetry/masonry/builders/wheel.py @@ -10,7 +10,6 @@ from base64 import urlsafe_b64encode from io import StringIO -from typing import Set from clikit.api.io.flags import VERY_VERBOSE @@ -154,7 +153,7 @@ def _copy_module(self, wheel): else: rel_file = file.relative_to(self._path) - if file in excluded: + if rel_file.as_posix() in excluded: continue if file.suffix == ".pyc": @@ -200,10 +199,6 @@ def _write_record(self, wheel): # RECORD itself is recorded with no hash or size f.write(self.dist_info + "/RECORD,,\n") - def find_excluded_files(self): # type: () -> Set - # Checking VCS - return set() - @property def dist_info(self): # type: () -> str return self.dist_info_name(self._package.name, self._meta.version) diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index e5b97c9af88..d64510a09e3 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -9,6 +9,7 @@ from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils._compat import decode +from poetry.utils.extras import get_extra_package_names class Exporter(object): @@ -55,20 +56,16 @@ def _export_requirements_txt( ): # type: (Path, Union[IO, str], bool, bool, bool) -> None indexes = [] content = "" + packages = self._poetry.locker.locked_repository(dev).packages - # Generate a list of package names we have opted into via `extras` - extras_set = frozenset(extras or ()) - extra_package_names = set() - if extras: - for extra_name, extra_packages in self._poetry.locker.lock_data.get( - "extras", {} - ).items(): - if extra_name in extras_set: - extra_package_names.update(extra_packages) - - for package in sorted( - self._poetry.locker.locked_repository(dev).packages, key=lambda p: p.name - ): + # Build a set of all packages required by our selected extras + extra_package_names = set( + get_extra_package_names( + packages, self._poetry.locker.lock_data.get("extras", {}), extras or () + ) + ) + + for package in sorted(packages, key=lambda p: p.name): # If a package is optional and we haven't opted in to it, continue if package.optional and package.name not in extra_package_names: continue diff --git a/poetry/utils/extras.py b/poetry/utils/extras.py new file mode 100644 index 00000000000..2181f03966b --- /dev/null +++ b/poetry/utils/extras.py @@ -0,0 +1,51 @@ +from typing import Iterator +from typing import List +from typing import Mapping +from typing import Sequence + +from poetry.packages import Package +from poetry.utils.helpers import canonicalize_name + + +def get_extra_package_names( + packages, # type: Sequence[Package] + extras, # type: Mapping[str, List[str]] + extra_names, # type: Sequence[str] +): # type: (...) -> Iterator[str] + """ + Returns all package names required by the given extras. + + :param packages: A collection of packages, such as from Repository.packages + :param extras: A mapping of `extras` names to lists of package names, as defined + in the `extras` section of `poetry.lock`. + :param extra_names: A list of strings specifying names of extra groups to resolve. + """ + if not extra_names: + return [] + + # lookup for packages by name, faster than looping over packages repeatedly + packages_by_name = {package.name: package for package in packages} + + # get and flatten names of packages we've opted into as extras + extra_package_names = [ + canonicalize_name(extra_package_name) + for extra_name in extra_names + for extra_package_name in extras.get(extra_name, ()) + ] + + def _extra_packages(package_names): + """Recursively find dependencies for packages names""" + # for each extra pacakge name + for package_name in package_names: + # Find the actual Package object. A missing key indicates an implicit + # dependency (like setuptools), which should be ignored + package = packages_by_name.get(canonicalize_name(package_name)) + if package: + yield package.name + # Recurse for dependencies + for dependency_package_name in _extra_packages( + dependency.name for dependency in package.requires + ): + yield dependency_package_name + + return _extra_packages(extra_package_names) diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/LICENSE b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/LICENSE new file mode 100644 index 00000000000..44cf2b30e68 --- /dev/null +++ b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2018 Sébastien Eustace + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/README.rst b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/__init__.py b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/data/data1.txt b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/data/data1.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/data/sub_data/data2.txt b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/data/sub_data/data2.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/data/sub_data/data3.txt b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/my_package/data/sub_data/data3.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/default_with_excluded_data_toml/pyproject.toml b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/pyproject.toml new file mode 100644 index 00000000000..80c284ecee1 --- /dev/null +++ b/tests/masonry/builders/fixtures/default_with_excluded_data_toml/pyproject.toml @@ -0,0 +1,41 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +exclude = ["my_package/data/data1.txt"] + +homepage = "https://poetry.eustace.io/" +repository = "https://github.com/sdispater/poetry" +documentation = "https://poetry.eustace.io/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "^3.6" +cleo = "^0.6" +cachy = { version = "^0.2.0", extras = ["msgpack"] } + +pendulum = { version = "^1.4", optional = true } + +[tool.poetry.dev-dependencies] +pytest = "~3.4" + +[tool.poetry.extras] +time = ["pendulum"] + +[tool.poetry.scripts] +my-script = "my_package:main" +my-2nd-script = "my_package:main2" diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/LICENSE b/tests/masonry/builders/fixtures/exclude_nested_data_toml/LICENSE new file mode 100644 index 00000000000..44cf2b30e68 --- /dev/null +++ b/tests/masonry/builders/fixtures/exclude_nested_data_toml/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2018 Sébastien Eustace + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/README.rst b/tests/masonry/builders/fixtures/exclude_nested_data_toml/README.rst new file mode 100644 index 00000000000..f7fe15470f9 --- /dev/null +++ b/tests/masonry/builders/fixtures/exclude_nested_data_toml/README.rst @@ -0,0 +1,2 @@ +My Package +========== diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/__init__.py b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/data/data1.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/data/data1.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/data/sub_data/data2.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/data/sub_data/data2.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/data/sub_data/data3.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/data/sub_data/data3.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/item1/itemdata1.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/item1/itemdata1.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/item1/subitem/subitemdata.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/item1/subitem/subitemdata.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/item2/itemdata2.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/item2/itemdata2.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/publicdata.txt b/tests/masonry/builders/fixtures/exclude_nested_data_toml/my_package/puplic/publicdata.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/exclude_nested_data_toml/pyproject.toml b/tests/masonry/builders/fixtures/exclude_nested_data_toml/pyproject.toml new file mode 100644 index 00000000000..21efcba30eb --- /dev/null +++ b/tests/masonry/builders/fixtures/exclude_nested_data_toml/pyproject.toml @@ -0,0 +1,41 @@ +[tool.poetry] +name = "my-package" +version = "1.2.3" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +exclude = ["my_package/data/", "**/*/item*"] + +homepage = "https://poetry.eustace.io/" +repository = "https://github.com/sdispater/poetry" +documentation = "https://poetry.eustace.io/docs" + +keywords = ["packaging", "dependency", "poetry"] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +[tool.poetry.dependencies] +python = "^3.6" +cleo = "^0.6" +cachy = { version = "^0.2.0", extras = ["msgpack"] } + +pendulum = { version = "^1.4", optional = true } + +[tool.poetry.dev-dependencies] +pytest = "~3.4" + +[tool.poetry.extras] +time = ["pendulum"] + +[tool.poetry.scripts] +my-script = "my_package:main" +my-2nd-script = "my_package:main2" diff --git a/tests/masonry/builders/test_sdist.py b/tests/masonry/builders/test_sdist.py index d8f6bd789c7..5221228bf40 100644 --- a/tests/masonry/builders/test_sdist.py +++ b/tests/masonry/builders/test_sdist.py @@ -410,6 +410,38 @@ def test_default_with_excluded_data(mocker): assert "my-package-1.2.3/PKG-INFO" in names +def test_src_excluded_nested_data(): + module_path = fixtures_dir / "exclude_nested_data_toml" + poetry = Factory().create_poetry(module_path) + + builder = SdistBuilder(poetry, NullEnv(), NullIO()) + builder.build() + + sdist = module_path / "dist" / "my-package-1.2.3.tar.gz" + + assert sdist.exists() + + with tarfile.open(str(sdist), "r") as tar: + names = tar.getnames() + assert len(names) == len(set(names)) + assert "my-package-1.2.3/LICENSE" in names + assert "my-package-1.2.3/README.rst" in names + assert "my-package-1.2.3/pyproject.toml" in names + assert "my-package-1.2.3/setup.py" in names + assert "my-package-1.2.3/PKG-INFO" in names + assert "my-package-1.2.3/my_package/__init__.py" in names + assert "my-package-1.2.3/my_package/data/sub_data/data2.txt" not in names + assert "my-package-1.2.3/my_package/data/sub_data/data3.txt" not in names + assert "my-package-1.2.3/my_package/data/data1.txt" not in names + assert "my-package-1.2.3/my_package/puplic/publicdata.txt" in names + assert "my-package-1.2.3/my_package/public/item1/itemdata1.txt" not in names + assert ( + "my-package-1.2.3/my_package/public/item1/subitem/subitemdata.txt" + not in names + ) + assert "my-package-1.2.3/my_package/public/item2/itemdata2.txt" not in names + + def test_proper_python_requires_if_two_digits_precision_version_specified(): poetry = Factory().create_poetry(project("simple_version")) diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index 9e49277505c..346da5256ae 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -64,6 +64,41 @@ def test_wheel_prerelease(): assert whl.exists() +def test_wheel_excluded_data(): + module_path = fixtures_dir / "default_with_excluded_data_toml" + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) + + whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl" + + assert whl.exists() + + with zipfile.ZipFile(str(whl)) as z: + assert "my_package/__init__.py" in z.namelist() + assert "my_package/data/sub_data/data2.txt" in z.namelist() + assert "my_package/data/sub_data/data3.txt" in z.namelist() + assert "my_package/data/data1.txt" not in z.namelist() + + +def test_wheel_excluded_nested_data(): + module_path = fixtures_dir / "exclude_nested_data_toml" + poetry = Factory().create_poetry(module_path) + WheelBuilder.make(poetry, NullEnv(), NullIO()) + + whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl" + + assert whl.exists() + + with zipfile.ZipFile(str(whl)) as z: + assert "my_package/__init__.py" in z.namelist() + assert "my_package/data/sub_data/data2.txt" not in z.namelist() + assert "my_package/data/sub_data/data3.txt" not in z.namelist() + assert "my_package/data/data1.txt" not in z.namelist() + assert "my_package/puplic/publicdata.txt" in z.namelist() + assert "my_package/public/item1/itemdata1.txt" not in z.namelist() + assert "my_package/public/item1/subitem/subitemdata.txt" not in z.namelist() + assert "my_package/public/item2/itemdata2.txt" not in z.namelist() + + def test_wheel_localversionlabel(): module_path = fixtures_dir / "localversionlabel" project = Factory().create_poetry(module_path) diff --git a/tests/utils/fixtures/setups/pendulum/setup.py b/tests/utils/fixtures/setups/pendulum/setup.py index 702e08f4ce1..3a6323fbbdf 100644 --- a/tests/utils/fixtures/setups/pendulum/setup.py +++ b/tests/utils/fixtures/setups/pendulum/setup.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from distutils.core import setup +from build import * + packages = [ "pendulum", "pendulum._extensions", @@ -45,7 +47,6 @@ "extras_require": extras_require, "python_requires": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", } -from build import * build(setup_kwargs) diff --git a/tests/utils/fixtures/setups/with-setup-cfg/setup.py b/tests/utils/fixtures/setups/with-setup-cfg/setup.py index b024da80e9c..606849326a4 100644 --- a/tests/utils/fixtures/setups/with-setup-cfg/setup.py +++ b/tests/utils/fixtures/setups/with-setup-cfg/setup.py @@ -1,4 +1,3 @@ from setuptools import setup - setup() diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index ad5be296bf4..ccb8ecf248b 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -367,7 +367,15 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( { "name": "bar", "version": "4.5.6", - "category": "dev", + "category": "main", + "optional": True, + "python-versions": "*", + "dependencies": {"spam": ">=0.1"}, + }, + { + "name": "spam", + "version": "0.1.0", + "category": "main", "optional": True, "python-versions": "*", }, @@ -375,7 +383,7 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( "metadata": { "python-versions": "*", "content-hash": "123456789", - "hashes": {"foo": ["12345"], "bar": ["67890"]}, + "hashes": {"foo": ["12345"], "bar": ["67890"], "spam": ["abcde"]}, }, "extras": {"feature_bar": ["bar"]}, } @@ -398,6 +406,8 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( --hash=sha256:67890 foo==1.2.3 \\ --hash=sha256:12345 +spam==0.1.0 \\ + --hash=sha256:abcde """ assert expected == content diff --git a/tests/utils/test_extras.py b/tests/utils/test_extras.py new file mode 100644 index 00000000000..83a184d97ae --- /dev/null +++ b/tests/utils/test_extras.py @@ -0,0 +1,50 @@ +import pytest + +from poetry.packages import Package +from poetry.utils.extras import get_extra_package_names + + +_PACKAGE_FOO = Package("foo", "0.1.0") +_PACKAGE_SPAM = Package("spam", "0.2.0") +_PACKAGE_BAR = Package("bar", "0.3.0") +_PACKAGE_BAR.add_dependency("foo") + + +@pytest.mark.parametrize( + "packages,extras,extra_names,expected_extra_package_names", + [ + # Empty edge case + ([], {}, [], []), + # Selecting no extras is fine + ([_PACKAGE_FOO], {}, [], []), + # An empty extras group should return an empty list + ([_PACKAGE_FOO], {"group0": []}, ["group0"], []), + # Selecting an extras group should return the contained packages + ( + [_PACKAGE_FOO, _PACKAGE_SPAM, _PACKAGE_BAR], + {"group0": ["foo"]}, + ["group0"], + ["foo"], + ), + # If a package has dependencies, we should also get their names + ( + [_PACKAGE_FOO, _PACKAGE_SPAM, _PACKAGE_BAR], + {"group0": ["bar"], "group1": ["spam"]}, + ["group0"], + ["bar", "foo"], + ), + # Selecting multpile extras should get us the union of all package names + ( + [_PACKAGE_FOO, _PACKAGE_SPAM, _PACKAGE_BAR], + {"group0": ["bar"], "group1": ["spam"]}, + ["group0", "group1"], + ["bar", "foo", "spam"], + ), + ], +) +def test_get_extra_package_names( + packages, extras, extra_names, expected_extra_package_names +): + assert expected_extra_package_names == list( + get_extra_package_names(packages, extras, extra_names) + )