Commit a030a5f2 by Justin Mayer Committed by Steph Samson

Support installing and uninstalling on Fish shell (#1536)

Support installing and uninstalling on Fish shell
parent 128ebf5e
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
- Added a `env use` command to control the Python version used by the project. - 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 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 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 declaring a specific source for dependencies.
- Added support for disabling PyPI and making another repository the default one. - Added support for disabling PyPI and making another repository the default one.
- Added support for declaring private repositories as secondary. - Added support for declaring private repositories as secondary.
......
...@@ -46,6 +46,12 @@ python get-poetry.py --uninstall ...@@ -46,6 +46,12 @@ python get-poetry.py --uninstall
POETRY_UNINSTALL=1 python get-poetry.py 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` 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: or by using the `POETRY_PREVIEW` environment variable:
...@@ -111,28 +117,28 @@ pipx uninstall poetry ...@@ -111,28 +117,28 @@ pipx uninstall poetry
## Updating `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 ```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 ```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 And finally, if you want to install a specific version, you can pass it as an argument
to `self:update`. to `self update`.
```bash ```bash
poetry self:update 0.8.0 poetry self update 0.8.0
``` ```
!!!note !!!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. installer to install Poetry.
......
""" """
This script will install poetry and its dependencies This script will install Poetry and its dependencies
in isolation from the rest of the system. in isolation from the rest of the system.
It does, in order: It does, in order:
...@@ -65,6 +65,7 @@ try: ...@@ -65,6 +65,7 @@ try:
except NameError: except NameError:
u = str u = str
SHELL = os.getenv("SHELL", "")
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
...@@ -189,7 +190,7 @@ def expanduser(path): ...@@ -189,7 +190,7 @@ def expanduser(path):
HOME = expanduser("~") 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_BIN = os.path.join(POETRY_HOME, "bin")
POETRY_ENV = os.path.join(POETRY_HOME, "env") POETRY_ENV = os.path.join(POETRY_HOME, "env")
POETRY_LIB = os.path.join(POETRY_HOME, "lib") POETRY_LIB = os.path.join(POETRY_HOME, "lib")
...@@ -249,6 +250,9 @@ modifying the profile file{plural} located at: ...@@ -249,6 +250,9 @@ modifying the profile file{plural} located at:
{rcfiles}""" {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 PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."""
...@@ -264,6 +268,12 @@ automatically. ...@@ -264,6 +268,12 @@ automatically.
To configure your current shell run `source {poetry_home_env}` 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! POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
...@@ -279,6 +289,15 @@ environment variable. ...@@ -279,6 +289,15 @@ environment variable.
To configure your current shell run `source {poetry_home_env}` 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! 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` To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
...@@ -605,6 +624,9 @@ class Installer: ...@@ -605,6 +624,9 @@ class Installer:
if not self._modify_path: if not self._modify_path:
return return
if "fish" in SHELL:
return self.add_to_fish_path()
if WINDOWS: if WINDOWS:
return self.add_to_windows_path() return self.add_to_windows_path()
...@@ -625,6 +647,40 @@ class Installer: ...@@ -625,6 +647,40 @@ class Installer:
with open(profile, "a") as f: with open(profile, "a") as f:
f.write(u(addition)) 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): def add_to_windows_path(self):
try: try:
old_path = self.get_windows_path_var() old_path = self.get_windows_path_var()
...@@ -685,11 +741,25 @@ class Installer: ...@@ -685,11 +741,25 @@ class Installer:
) )
def remove_from_path(self): 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_windows_path()
return self.remove_from_unix_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): def remove_from_windows_path(self):
path = self.get_windows_path_var() path = self.get_windows_path_var()
...@@ -741,8 +811,7 @@ class Installer: ...@@ -741,8 +811,7 @@ class Installer:
def get_unix_profiles(self): def get_unix_profiles(self):
profiles = [os.path.join(HOME, ".profile")] profiles = [os.path.join(HOME, ".profile")]
shell = os.getenv("SHELL", "") if "zsh" in SHELL:
if "zsh" in shell:
zdotdir = os.getenv("ZDOTDIR", HOME) zdotdir = os.getenv("ZDOTDIR", HOME)
profiles.append(os.path.join(zdotdir, ".zprofile")) profiles.append(os.path.join(zdotdir, ".zprofile"))
...@@ -766,7 +835,9 @@ class Installer: ...@@ -766,7 +835,9 @@ class Installer:
if not self._modify_path: if not self._modify_path:
kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH
else: else:
if WINDOWS: if "fish" in SHELL:
kwargs["platform_msg"] = PRE_MESSAGE_FISH
elif WINDOWS:
kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS
else: else:
profiles = [ profiles = [
...@@ -809,6 +880,12 @@ class Installer: ...@@ -809,6 +880,12 @@ class Installer:
poetry_home_bin = POETRY_BIN.replace( poetry_home_bin = POETRY_BIN.replace(
os.getenv("USERPROFILE", ""), "%USERPROFILE%" 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: else:
message = POST_MESSAGE_UNIX message = POST_MESSAGE_UNIX
if not self._modify_path: if not self._modify_path:
......
...@@ -4,7 +4,6 @@ from typing import Union ...@@ -4,7 +4,6 @@ from typing import Union
from clikit.api.io import IO from clikit.api.io import IO
from clikit.io import NullIO from clikit.io import NullIO
from poetry.packages import Dependency
from poetry.packages import Locker from poetry.packages import Locker
from poetry.packages import Package from poetry.packages import Package
from poetry.puzzle import Solver from poetry.puzzle import Solver
...@@ -16,6 +15,7 @@ from poetry.repositories import Pool ...@@ -16,6 +15,7 @@ from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.utils.extras import get_extra_package_names
from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import canonicalize_name
from .base_installer import BaseInstaller from .base_installer import BaseInstaller
...@@ -399,7 +399,7 @@ class Installer: ...@@ -399,7 +399,7 @@ class Installer:
installed_repo = self._installed_repository installed_repo = self._installed_repository
ops = [] 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: for locked in locked_repository.packages:
is_installed = False is_installed = False
for installed in installed_repo.packages: for installed in installed_repo.packages:
...@@ -429,7 +429,7 @@ class Installer: ...@@ -429,7 +429,7 @@ class Installer:
def _filter_operations( def _filter_operations(
self, ops, repo self, ops, repo
): # type: (List[Operation], Repository) -> None ): # 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: for op in ops:
if isinstance(op, Update): if isinstance(op, Update):
package = op.target_package package = op.target_package
...@@ -468,9 +468,9 @@ class Installer: ...@@ -468,9 +468,9 @@ class Installer:
if package.category == "dev" and not self.is_dev_mode(): if package.category == "dev" and not self.is_dev_mode():
op.skip("Dev dependencies not requested") 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? Maybe we just let the solver handle it?
""" """
...@@ -479,26 +479,7 @@ class Installer: ...@@ -479,26 +479,7 @@ class Installer:
else: else:
extras = self._locker.lock_data.get("extras", {}) extras = self._locker.lock_data.get("extras", {})
extra_packages = [] return list(get_extra_package_names(repo.packages, extras, self._extras))
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)
def _get_installer(self): # type: () -> BaseInstaller def _get_installer(self): # type: () -> BaseInstaller
return PipInstaller(self._env, self._io, self._pool) return PipInstaller(self._env, self._io, self._pool)
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import re import re
import shutil import shutil
import tempfile import tempfile
...@@ -34,7 +33,6 @@ Summary: {summary} ...@@ -34,7 +33,6 @@ Summary: {summary}
class Builder(object): class Builder(object):
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"} AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"}
format = None format = None
...@@ -86,8 +84,18 @@ class Builder(object): ...@@ -86,8 +84,18 @@ class Builder(object):
explicitely_excluded = set() explicitely_excluded = set()
for excluded_glob in self._package.exclude: 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( 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( explicitely_excluded.add(
Path(excluded).relative_to(self._path).as_posix() Path(excluded).relative_to(self._path).as_posix()
......
...@@ -10,7 +10,6 @@ import zipfile ...@@ -10,7 +10,6 @@ import zipfile
from base64 import urlsafe_b64encode from base64 import urlsafe_b64encode
from io import StringIO from io import StringIO
from typing import Set
from clikit.api.io.flags import VERY_VERBOSE from clikit.api.io.flags import VERY_VERBOSE
...@@ -154,7 +153,7 @@ class WheelBuilder(Builder): ...@@ -154,7 +153,7 @@ class WheelBuilder(Builder):
else: else:
rel_file = file.relative_to(self._path) rel_file = file.relative_to(self._path)
if file in excluded: if rel_file.as_posix() in excluded:
continue continue
if file.suffix == ".pyc": if file.suffix == ".pyc":
...@@ -200,10 +199,6 @@ class WheelBuilder(Builder): ...@@ -200,10 +199,6 @@ class WheelBuilder(Builder):
# RECORD itself is recorded with no hash or size # RECORD itself is recorded with no hash or size
f.write(self.dist_info + "/RECORD,,\n") f.write(self.dist_info + "/RECORD,,\n")
def find_excluded_files(self): # type: () -> Set
# Checking VCS
return set()
@property @property
def dist_info(self): # type: () -> str def dist_info(self): # type: () -> str
return self.dist_info_name(self._package.name, self._meta.version) return self.dist_info_name(self._package.name, self._meta.version)
......
...@@ -9,6 +9,7 @@ from poetry.packages.vcs_dependency import VCSDependency ...@@ -9,6 +9,7 @@ from poetry.packages.vcs_dependency import VCSDependency
from poetry.poetry import Poetry from poetry.poetry import Poetry
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.extras import get_extra_package_names
class Exporter(object): class Exporter(object):
...@@ -55,20 +56,16 @@ class Exporter(object): ...@@ -55,20 +56,16 @@ class Exporter(object):
): # type: (Path, Union[IO, str], bool, bool, bool) -> None ): # type: (Path, Union[IO, str], bool, bool, bool) -> None
indexes = [] indexes = []
content = "" content = ""
packages = self._poetry.locker.locked_repository(dev).packages
# Generate a list of package names we have opted into via `extras` # Build a set of all packages required by our selected extras
extras_set = frozenset(extras or ()) extra_package_names = set(
extra_package_names = set() get_extra_package_names(
if extras: packages, self._poetry.locker.lock_data.get("extras", {}), extras or ()
for extra_name, extra_packages in self._poetry.locker.lock_data.get( )
"extras", {} )
).items():
if extra_name in extras_set: for package in sorted(packages, key=lambda p: p.name):
extra_package_names.update(extra_packages)
for package in sorted(
self._poetry.locker.locked_repository(dev).packages, key=lambda p: p.name
):
# If a package is optional and we haven't opted in to it, continue # 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: if package.optional and package.name not in extra_package_names:
continue continue
......
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)
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.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
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"
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.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
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"
...@@ -410,6 +410,38 @@ def test_default_with_excluded_data(mocker): ...@@ -410,6 +410,38 @@ def test_default_with_excluded_data(mocker):
assert "my-package-1.2.3/PKG-INFO" in names 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(): def test_proper_python_requires_if_two_digits_precision_version_specified():
poetry = Factory().create_poetry(project("simple_version")) poetry = Factory().create_poetry(project("simple_version"))
......
...@@ -64,6 +64,41 @@ def test_wheel_prerelease(): ...@@ -64,6 +64,41 @@ def test_wheel_prerelease():
assert whl.exists() 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(): def test_wheel_localversionlabel():
module_path = fixtures_dir / "localversionlabel" module_path = fixtures_dir / "localversionlabel"
project = Factory().create_poetry(module_path) project = Factory().create_poetry(module_path)
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from distutils.core import setup from distutils.core import setup
from build import *
packages = [ packages = [
"pendulum", "pendulum",
"pendulum._extensions", "pendulum._extensions",
...@@ -45,7 +47,6 @@ setup_kwargs = { ...@@ -45,7 +47,6 @@ setup_kwargs = {
"extras_require": extras_require, "extras_require": extras_require,
"python_requires": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", "python_requires": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
} }
from build import *
build(setup_kwargs) build(setup_kwargs)
......
from setuptools import setup from setuptools import setup
setup() setup()
...@@ -367,7 +367,15 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( ...@@ -367,7 +367,15 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in(
{ {
"name": "bar", "name": "bar",
"version": "4.5.6", "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, "optional": True,
"python-versions": "*", "python-versions": "*",
}, },
...@@ -375,7 +383,7 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( ...@@ -375,7 +383,7 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in(
"metadata": { "metadata": {
"python-versions": "*", "python-versions": "*",
"content-hash": "123456789", "content-hash": "123456789",
"hashes": {"foo": ["12345"], "bar": ["67890"]}, "hashes": {"foo": ["12345"], "bar": ["67890"], "spam": ["abcde"]},
}, },
"extras": {"feature_bar": ["bar"]}, "extras": {"feature_bar": ["bar"]},
} }
...@@ -398,6 +406,8 @@ bar==4.5.6 \\ ...@@ -398,6 +406,8 @@ bar==4.5.6 \\
--hash=sha256:67890 --hash=sha256:67890
foo==1.2.3 \\ foo==1.2.3 \\
--hash=sha256:12345 --hash=sha256:12345
spam==0.1.0 \\
--hash=sha256:abcde
""" """
assert expected == content assert expected == content
......
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)
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment