Commit fedecf6d by Sébastien Eustace

Merge branch 'master' into develop

parents 3a39e5ac 5e34b9e2
name: Main name: Tests
on: [push, pull_request] on: [push, pull_request]
...@@ -85,12 +85,12 @@ jobs: ...@@ -85,12 +85,12 @@ jobs:
- name: Install Poetry - name: Install Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
SET PATH=%USERPROFILE%\\.poetry\\bin;%PATH% $env:Path += ";$env:Userprofile\.poetry\bin"
- name: Install dependencies - name: Install dependencies
run: | run: |
SET PATH=%USERPROFILE%\\.poetry\\bin;%PATH% $env:Path += ";$env:Userprofile\.poetry\bin"
poetry install poetry install
- name: Test - name: Test
run: | run: |
SET PATH=%USERPROFILE%\\.poetry\\bin;%PATH% $env:Path += ";$env:Userprofile\.poetry\bin"
poetry run pytest -q tests poetry run pytest -q tests
...@@ -3,4 +3,3 @@ repos: ...@@ -3,4 +3,3 @@ repos:
rev: stable rev: stable
hooks: hooks:
- id: black - id: black
language_version: python3.6
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
- Added support for url dependencies ([#1260](https://github.com/sdispater/poetry/pull/1260)). - Added support for url dependencies ([#1260](https://github.com/sdispater/poetry/pull/1260)).
- Publishing to PyPI using [API tokens](https://pypi.org/help/#apitoken) is now supported ([#1275](https://github.com/sdispater/poetry/pull/1275)). - Publishing to PyPI using [API tokens](https://pypi.org/help/#apitoken) is now supported ([#1275](https://github.com/sdispater/poetry/pull/1275)).
- Licenses can now be identified by their full name. - Licenses can now be identified by their full name.
- Added support for support for custom certificate authority and client certificates for private repositories.
- Poetry can now detect and use Conda environments.
### Changed ### Changed
...@@ -35,6 +37,9 @@ ...@@ -35,6 +37,9 @@
- The `add` command will now automatically select the latest prerelease if only prereleases are available. - The `add` command will now automatically select the latest prerelease if only prereleases are available.
- The `add` command can now update a dependencies if an explicit constraint is given ([#1221](https://github.com/sdispater/poetry/pull/1221)). - The `add` command can now update a dependencies if an explicit constraint is given ([#1221](https://github.com/sdispater/poetry/pull/1221)).
- Removed the `--develop` option from the `install` command. - Removed the `--develop` option from the `install` command.
- Improved UX when searching for packages in the `init` command.
- The `shell` has been improved.
- The `poetry run` command now uses `os.execvp()` rather than spawning a new subprocess.
### Fixed ### Fixed
...@@ -42,6 +47,15 @@ ...@@ -42,6 +47,15 @@
- The `pyproject.toml` configuration is now properly validated. - The `pyproject.toml` configuration is now properly validated.
- Fixed installing Poetry-based packages breaking with `pip`. - Fixed installing Poetry-based packages breaking with `pip`.
- Fixed packages with empty markers being added to the lock file. - Fixed packages with empty markers being added to the lock file.
- Fixed invalid lock file generation in some cases.
- Fixed local version identifier handling in wheel file names.
- Fixed packages with invalid metadata triggering an error instead of being skipped.
- Fixed the generation of invalid lock files in some cases.
- Git dependencies are now properly locked to a specific revision when specifying a branch or a tag.
- Fixed the behavior of the `~=` operator.
- Fixed dependency resolution for conditional development dependencies.
- Fixed generated dependency constraints when they contain inequality operators.
- The `run` command now properly handles the `--` separator.
## [0.12.17] - 2019-07-03 ## [0.12.17] - 2019-07-03
...@@ -725,7 +739,7 @@ Initial release ...@@ -725,7 +739,7 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.17...develop [Unreleased]: https://github.com/sdispater/poetry/compare/0.12.17...master
[0.12.17]: https://github.com/sdispater/poetry/releases/tag/0.12.17 [0.12.17]: https://github.com/sdispater/poetry/releases/tag/0.12.17
[0.12.16]: https://github.com/sdispater/poetry/releases/tag/0.12.16 [0.12.16]: https://github.com/sdispater/poetry/releases/tag/0.12.16
[0.12.15]: https://github.com/sdispater/poetry/releases/tag/0.12.15 [0.12.15]: https://github.com/sdispater/poetry/releases/tag/0.12.15
......
...@@ -47,8 +47,8 @@ wheel: ...@@ -47,8 +47,8 @@ wheel:
@poetry build -v @poetry build -v
linux_release: linux_release:
docker pull quay.io/pypa/manylinux1_x86_64 docker pull quay.io/pypa/manylinux2010_x86_64
docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/make-linux-release.sh docker run --rm -t -i -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/make-linux-release.sh
# run tests against all supported python versions # run tests against all supported python versions
tox: tox:
......
...@@ -7,8 +7,7 @@ ensuring you have the right stack everywhere. ...@@ -7,8 +7,7 @@ ensuring you have the right stack everywhere.
It supports Python 2.7 and 3.4+. It supports Python 2.7 and 3.4+.
[![Unix Build Status](https://img.shields.io/travis/sdispater/poetry.svg?label=Unix)](https://travis-ci.org/sdispater/poetry) ![Tests Status](https://github.com/sdispater/poetry/workflows/Tests/badge.svg)
[![Windows Build Status](https://img.shields.io/appveyor/ci/sdispater/poetry.svg?label=Windows)](https://ci.appveyor.com/project/sdispater/poetry)
## Installation ## Installation
......
...@@ -74,6 +74,16 @@ export POETRY_HTTP_BASIC_PYPI_PASSWORD=password ...@@ -74,6 +74,16 @@ export POETRY_HTTP_BASIC_PYPI_PASSWORD=password
See [Using environment variables](/configuration#using-environment-variables) for more information See [Using environment variables](/configuration#using-environment-variables) for more information
on how to configure Poetry with environment variables. on how to configure Poetry with environment variables.
#### Custom certificate authority and mutual TLS authentication
Poetry supports repositories that are secured by a custom certificate authority as well as those that require
certificate-based client authentication. The following will configure the "foo" repository to validate the repository's
certificate using a custom certificate authority and use a client certificate (note that these config variables do not
both need to be set):
```bash
poetry config certificates.foo.cert /path/to/ca.pem
poetry config certificates.foo.client-cert /path/to/client.pem
```
### Install dependencies from a private repository ### Install dependencies from a private repository
Now that you can publish to your private repository, you need to be able to Now that you can publish to your private repository, you need to be able to
...@@ -105,8 +115,10 @@ From now on, Poetry will also look for packages in your private repository. ...@@ -105,8 +115,10 @@ From now on, Poetry will also look for packages in your private repository.
If your private repository requires HTTP Basic Auth be sure to add the username and If your private repository requires HTTP Basic Auth be sure to add the username and
password to your `http-basic` configuration using the example above (be sure to use the password to your `http-basic` configuration using the example above (be sure to use the
same name that is in the `tool.poetry.source` section). Poetry will use these values same name that is in the `tool.poetry.source` section). If your repository requires either
to authenticate to your private repository when downloading or looking for packages. a custom certificate authority or client certificates, similarly refer to the example above to configure the
`certificates` section. Poetry will use these values to authenticate to your private repository when downloading or
looking for packages.
### Disabling the PyPI repository ### Disabling the PyPI repository
......
...@@ -6,7 +6,7 @@ title: {{ page.title|striptags|e }} ...@@ -6,7 +6,7 @@ title: {{ page.title|striptags|e }}
<section class="p-b-50 p-t-50 documentation-content"> <section class="p-b-50 p-t-50 documentation-content">
<div class="container"> <div class="container">
<div class="panel panel-transparent"> <div class="panel panel-transparent">
<div class="bg-white row p-l-20 p-r-20 p-b-20 p-t-5 xs-no-padding"> <div class="row p-l-20 p-r-20 p-b-20 p-t-5 xs-no-padding">
<div class="row"> <div class="row">
<div class="col-md-3 documentation-toc"> <div class="col-md-3 documentation-toc">
<ul class="current"> <ul class="current">
......
{%- if nav_item.url %}
<a class="{% if nav_item.active%}current{%endif%}" href="{{ base_url }}/{{ nav_item.url }}">{{ nav_item.title }}</a> <a class="{% if nav_item.active%}current{%endif%}" href="{{ base_url }}/{{ nav_item.url }}">{{ nav_item.title }}</a>
{%- else %}
<span class="caption-text">{{ nav_item.title }}</span>
{%- endif %}
{%- if nav_item == page or nav_item.children %} {%- if nav_item == page or nav_item.children %}
<ul class="subnav"> <ul class="subnav">
......
...@@ -299,7 +299,9 @@ class Installer: ...@@ -299,7 +299,9 @@ class Installer:
r"(?:\+[^\s]+)?" r"(?:\+[^\s]+)?"
) )
BASE_URL = "https://github.com/sdispater/poetry/releases/download/" REPOSITORY_URL = "https://github.com/python-poetry/poetry"
BASE_URL = REPOSITORY_URL + "/releases/download/"
FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download/"
def __init__( def __init__(
self, self,
...@@ -598,6 +600,9 @@ class Installer: ...@@ -598,6 +600,9 @@ class Installer:
""" """
Tries to update the $PATH automatically. Tries to update the $PATH automatically.
""" """
if not self._modify_path:
return
if WINDOWS: if WINDOWS:
return self.add_to_windows_path() return self.add_to_windows_path()
...@@ -606,7 +611,6 @@ class Installer: ...@@ -606,7 +611,6 @@ class Installer:
addition = "\n{}\n".format(export_string) addition = "\n{}\n".format(export_string)
updated = []
profiles = self.get_unix_profiles() profiles = self.get_unix_profiles()
for profile in profiles: for profile in profiles:
if not os.path.exists(profile): if not os.path.exists(profile):
...@@ -619,8 +623,6 @@ class Installer: ...@@ -619,8 +623,6 @@ class Installer:
with open(profile, "a") as f: with open(profile, "a") as f:
f.write(u(addition)) f.write(u(addition))
updated.append(os.path.relpath(profile, HOME))
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()
...@@ -849,6 +851,15 @@ def main(): ...@@ -849,6 +851,15 @@ def main():
args = parser.parse_args() args = parser.parse_args()
base_url = Installer.BASE_URL
try:
r = urlopen(Installer.REPOSITORY_URL)
except HTTPError as e:
if e.code == 404:
base_url = Installer.FALLBACK_BASE_URL
else:
raise
installer = Installer( installer = Installer(
version=args.version or os.getenv("POETRY_VERSION"), version=args.version or os.getenv("POETRY_VERSION"),
preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")), preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")),
...@@ -856,6 +867,7 @@ def main(): ...@@ -856,6 +867,7 @@ def main():
accept_all=args.accept_all accept_all=args.accept_all
or string_to_bool(os.getenv("POETRY_ACCEPT", "0")) or string_to_bool(os.getenv("POETRY_ACCEPT", "0"))
or not is_interactive(), or not is_interactive(),
base_url=base_url,
) )
if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")): if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")):
......
#!/bin/bash #!/bin/bash
PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m" PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp38"
cd /io cd /io
/opt/python/cp37-cp37m/bin/pip install pip -U
/opt/python/cp37-cp37m/bin/pip install poetry -U /opt/python/cp37-cp37m/bin/pip install poetry -U
/opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false /opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false
/opt/python/cp37-cp37m/bin/poetry install --no-dev /opt/python/cp37-cp37m/bin/poetry install --no-dev
...@@ -10,5 +11,6 @@ cd /io ...@@ -10,5 +11,6 @@ cd /io
-P "3.4:/opt/python/cp34-cp34m/bin/python" \ -P "3.4:/opt/python/cp34-cp34m/bin/python" \
-P "3.5:/opt/python/cp35-cp35m/bin/python" \ -P "3.5:/opt/python/cp35-cp35m/bin/python" \
-P "3.6:/opt/python/cp36-cp36m/bin/python" \ -P "3.6:/opt/python/cp36-cp36m/bin/python" \
-P "3.7:/opt/python/cp37-cp37m/bin/python" -P "3.7:/opt/python/cp37-cp37m/bin/python" \
-P "3.8:/opt/python/cp38-cp38/bin/python"
cd - cd -
__version__ = "1.0.0b1" __version__ = "1.0.0b3"
...@@ -21,7 +21,7 @@ class BuildCommand(EnvCommand): ...@@ -21,7 +21,7 @@ class BuildCommand(EnvCommand):
package = self.poetry.package package = self.poetry.package
self.line( self.line(
"Building <info>{}</> (<comment>{}</>)".format( "Building <c1>{}</c1> (<b>{}</b>)".format(
package.pretty_name, package.version package.pretty_name, package.version
) )
) )
......
...@@ -228,6 +228,27 @@ To remove a repository (repo is a short alias for repositories): ...@@ -228,6 +228,27 @@ To remove a repository (repo is a short alias for repositories):
return 0 return 0
# handle certs
m = re.match(
r"(?:certificates)\.([^.]+)\.(cert|client-cert)", self.argument("key")
)
if m:
if self.option("unset"):
config.auth_config_source.remove_property(
"certificates.{}.{}".format(m.group(1), m.group(2))
)
return 0
if len(values) == 1:
config.auth_config_source.add_property(
"certificates.{}.{}".format(m.group(1), m.group(2)), values[0]
)
else:
raise ValueError("You must pass exactly 1 value")
return 0
raise ValueError("Setting {} does not exist".format(self.argument("key"))) raise ValueError("Setting {} does not exist".format(self.argument("key")))
def _handle_single_value(self, source, key, callbacks, values): def _handle_single_value(self, source, key, callbacks, values):
......
...@@ -5,10 +5,10 @@ from typing import List ...@@ -5,10 +5,10 @@ from typing import List
from cleo import argument from cleo import argument
from cleo import option from cleo import option
from ..command import Command from ..init import InitCommand
class DebugResolveCommand(Command): class DebugResolveCommand(InitCommand):
name = "resolve" name = "resolve"
description = "Debugs dependency resolution." description = "Debugs dependency resolution."
...@@ -43,12 +43,25 @@ class DebugResolveCommand(Command): ...@@ -43,12 +43,25 @@ class DebugResolveCommand(Command):
if not packages: if not packages:
package = self.poetry.package package = self.poetry.package
else: else:
# Using current pool for determine_requirements()
self._pool = self.poetry.pool
package = ProjectPackage( package = ProjectPackage(
self.poetry.package.name, self.poetry.package.version self.poetry.package.name, self.poetry.package.version
) )
requirements = self._format_requirements(packages)
for name, constraint in requirements.items(): # Silencing output
is_quiet = self.io.output.is_quiet()
if not is_quiet:
self.io.output.set_quiet(True)
requirements = self._determine_requirements(packages)
if not is_quiet:
self.io.output.set_quiet(False)
for constraint in requirements:
name = constraint.pop("name")
dep = package.add_dependency(name, constraint) dep = package.add_dependency(name, constraint)
extras = [] extras = []
for extra in self.option("extras"): for extra in self.option("extras"):
...@@ -90,7 +103,7 @@ class DebugResolveCommand(Command): ...@@ -90,7 +103,7 @@ class DebugResolveCommand(Command):
return 0 return 0
env = EnvManager(self.poetry.config).get(self.poetry.file.parent) env = EnvManager(self.poetry).get()
current_python_version = parse_constraint( current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info) ".".join(str(v) for v in env.version_info)
) )
...@@ -103,11 +116,7 @@ class DebugResolveCommand(Command): ...@@ -103,11 +116,7 @@ class DebugResolveCommand(Command):
current_python_version current_python_version
) or not env.is_valid_for_marker(pkg.marker): ) or not env.is_valid_for_marker(pkg.marker):
continue continue
row = [ row = ["<c1>{}</c1>".format(pkg.name), "<b>{}</b>".format(pkg.version), ""]
"<info>{}</info>".format(pkg.name),
"<b>{}</b>".format(pkg.version),
"",
]
if not pkg.marker.is_any(): if not pkg.marker.is_any():
row[2] = str(pkg.marker) row[2] = str(pkg.marker)
...@@ -116,55 +125,3 @@ class DebugResolveCommand(Command): ...@@ -116,55 +125,3 @@ class DebugResolveCommand(Command):
table.set_rows(rows) table.set_rows(rows)
table.render(self.io) table.render(self.io)
def _determine_requirements(self, requires): # type: (List[str]) -> List[str]
from poetry.semver import parse_constraint
if not requires:
return []
requires = self._parse_name_version_pairs(requires)
for requirement in requires:
if "version" in requirement:
parse_constraint(requirement["version"])
return requires
def _parse_name_version_pairs(self, pairs): # type: (list) -> list
result = []
for i in range(len(pairs)):
if pairs[i].startswith("git+https://"):
url = pairs[i].lstrip("git+")
rev = None
if "@" in url:
url, rev = url.split("@")
pair = {"name": url.split("/")[-1].rstrip(".git"), "git": url}
if rev:
pair["rev"] = rev
result.append(pair)
continue
pair = re.sub("^([^=: ]+)[=: ](.*)$", "\\1 \\2", pairs[i].strip())
pair = pair.strip()
if " " in pair:
name, version = pair.split(" ", 2)
result.append({"name": name, "version": version})
else:
result.append({"name": pair, "version": "*"})
return result
def _format_requirements(self, requirements): # type: (List[str]) -> dict
requires = {}
requirements = self._determine_requirements(requirements)
for requirement in requirements:
name = requirement.pop("name")
requires[name] = requirement
return requires
...@@ -13,8 +13,7 @@ class EnvInfoCommand(Command): ...@@ -13,8 +13,7 @@ class EnvInfoCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry env = EnvManager(self.poetry).get()
env = EnvManager(poetry.config).get(cwd=poetry.file.parent)
if self.option("path"): if self.option("path"):
if not env.is_venv(): if not env.is_venv():
......
...@@ -13,11 +13,10 @@ class EnvListCommand(Command): ...@@ -13,11 +13,10 @@ class EnvListCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry manager = EnvManager(self.poetry)
manager = EnvManager(poetry.config) current_env = manager.get()
current_env = manager.get(self.poetry.file.parent)
for venv in manager.list(self.poetry.file.parent): for venv in manager.list():
name = venv.path.name name = venv.path.name
if self.option("full-path"): if self.option("full-path"):
name = str(venv.path) name = str(venv.path)
......
...@@ -15,8 +15,7 @@ class EnvRemoveCommand(Command): ...@@ -15,8 +15,7 @@ class EnvRemoveCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry manager = EnvManager(self.poetry)
manager = EnvManager(poetry.config) venv = manager.remove(self.argument("python"))
venv = manager.remove(self.argument("python"), poetry.file.parent)
self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path)) self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path))
...@@ -13,14 +13,13 @@ class EnvUseCommand(Command): ...@@ -13,14 +13,13 @@ class EnvUseCommand(Command):
def handle(self): def handle(self):
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
poetry = self.poetry manager = EnvManager(self.poetry)
manager = EnvManager(poetry.config)
if self.argument("python") == "system": if self.argument("python") == "system":
manager.deactivate(poetry.file.parent, self._io) manager.deactivate(self._io)
return return
env = manager.activate(self.argument("python"), poetry.file.parent, self._io) env = manager.activate(self.argument("python"), self._io)
self.line("Using virtualenv: <comment>{}</>".format(env.path)) self.line("Using virtualenv: <comment>{}</>".format(env.path))
...@@ -3,6 +3,7 @@ from __future__ import unicode_literals ...@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os import os
import re import re
import sys
from typing import Dict from typing import Dict
from typing import List from typing import List
...@@ -15,7 +16,6 @@ from tomlkit import inline_table ...@@ -15,7 +16,6 @@ from tomlkit import inline_table
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import OrderedDict from poetry.utils._compat import OrderedDict
from poetry.utils._compat import urlparse from poetry.utils._compat import urlparse
from poetry.utils.helpers import temporary_directory
from .command import Command from .command import Command
from .env_command import EnvCommand from .env_command import EnvCommand
...@@ -52,7 +52,7 @@ class InitCommand(Command): ...@@ -52,7 +52,7 @@ class InitCommand(Command):
] ]
help = """\ help = """\
The <info>init</info> command creates a basic <comment>pyproject.toml</> file in the current directory. The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the current directory.
""" """
def __init__(self): def __init__(self):
...@@ -63,7 +63,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -63,7 +63,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
def handle(self): def handle(self):
from poetry.layouts import layout from poetry.layouts import layout
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import EnvManager from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig from poetry.vcs.git import GitConfig
if (Path.cwd() / "pyproject.toml").exists(): if (Path.cwd() / "pyproject.toml").exists():
...@@ -126,7 +126,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -126,7 +126,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
question.set_validator(self._validate_license) question.set_validator(self._validate_license)
license = self.ask(question) license = self.ask(question)
current_env = EnvManager().get(Path.cwd()) current_env = SystemEnv(Path(sys.executable))
default_python = "^{}".format( default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2]) ".".join(str(v) for v in current_env.version_info[:2])
) )
...@@ -209,7 +209,9 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -209,7 +209,9 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
if not requires: if not requires:
requires = [] requires = []
package = self.ask("Add a package:") package = self.ask(
"Search for package to add (or leave blank to continue):"
)
while package is not None: while package is not None:
constraint = self._parse_requirements([package])[0] constraint = self._parse_requirements([package])[0]
if ( if (
...@@ -247,7 +249,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -247,7 +249,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
choices.append(found_package.pretty_name) choices.append(found_package.pretty_name)
self.line( self.line(
"Found <info>{}</info> packages matching <info>{}</info>".format( "Found <info>{}</info> packages matching <c1>{}</c1>".format(
len(matches), package len(matches), package
) )
) )
...@@ -275,7 +277,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -275,7 +277,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
) )
self.line( self.line(
"Using version <info>{}</info> for <info>{}</info>".format( "Using version <b>{}</b> for <c1>{}</c1>".format(
package_constraint, package package_constraint, package
) )
) )
...@@ -304,7 +306,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -304,7 +306,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
requirement["name"] = name requirement["name"] = name
self.line( self.line(
"Using version <info>{}</> for <info>{}</>".format(version, name) "Using version <b>{}</b> for <c1>{}</c1>".format(version, name)
) )
else: else:
# check that the specified version/constraint exists # check that the specified version/constraint exists
......
...@@ -77,7 +77,7 @@ exist it will look for <comment>pyproject.toml</> and do the same. ...@@ -77,7 +77,7 @@ exist it will look for <comment>pyproject.toml</> and do the same.
return 0 return 0
self.line( self.line(
" - Installing <info>{}</info> (<comment>{}</comment>)".format( " - Installing <c1>{}</c1> (<b>{}</b>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version self.poetry.package.pretty_name, self.poetry.package.pretty_version
) )
) )
......
import sys
from cleo import argument from cleo import argument
from cleo import option from cleo import option
...@@ -17,8 +19,9 @@ class NewCommand(Command): ...@@ -17,8 +19,9 @@ class NewCommand(Command):
def handle(self): def handle(self):
from poetry.layouts import layout from poetry.layouts import layout
from poetry.semver import parse_constraint
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import EnvManager from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig from poetry.vcs.git import GitConfig
if self.option("src"): if self.option("src"):
...@@ -49,16 +52,25 @@ class NewCommand(Command): ...@@ -49,16 +52,25 @@ class NewCommand(Command):
if author_email: if author_email:
author += " <{}>".format(author_email) author += " <{}>".format(author_email)
current_env = EnvManager().get(Path.cwd()) current_env = SystemEnv(Path(sys.executable))
default_python = "^{}".format( default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2]) ".".join(str(v) for v in current_env.version_info[:2])
) )
dev_dependencies = {}
python_constraint = parse_constraint(default_python)
if parse_constraint("<3.5").allows_any(python_constraint):
dev_dependencies["pytest"] = "^4.6"
if parse_constraint(">=3.5").allows_all(python_constraint):
dev_dependencies["pytest"] = "^5.2"
layout_ = layout_( layout_ = layout_(
name, name,
"0.1.0", "0.1.0",
author=author, author=author,
readme_format=readme_format, readme_format=readme_format,
python=default_python, python=default_python,
dev_dependencies=dev_dependencies,
) )
layout_.create(path) layout_.create(path)
......
from cleo import option from cleo import option
from poetry.utils._compat import Path
from .command import Command from .command import Command
...@@ -14,6 +16,15 @@ class PublishCommand(Command): ...@@ -14,6 +16,15 @@ class PublishCommand(Command):
), ),
option("username", "u", "The username to access the repository.", flag=False), option("username", "u", "The username to access the repository.", flag=False),
option("password", "p", "The password to access the repository.", flag=False), option("password", "p", "The password to access the repository.", flag=False),
option(
"cert", None, "Certificate authority to access the repository.", flag=False
),
option(
"client-cert",
None,
"Client certificate to access the repository.",
flag=False,
),
option("build", None, "Build the package before publishing."), option("build", None, "Build the package before publishing."),
] ]
...@@ -57,6 +68,15 @@ the config command. ...@@ -57,6 +68,15 @@ the config command.
self.line("") self.line("")
cert = Path(self.option("cert")) if self.option("cert") else None
client_cert = (
Path(self.option("client-cert")) if self.option("client-cert") else None
)
publisher.publish( publisher.publish(
self.option("repository"), self.option("username"), self.option("password") self.option("repository"),
self.option("username"),
self.option("password"),
cert,
client_cert,
) )
...@@ -40,15 +40,13 @@ class RunCommand(EnvCommand): ...@@ -40,15 +40,13 @@ class RunCommand(EnvCommand):
cmd = ["python", "-c"] cmd = ["python", "-c"]
cmd += [ cmd += [
'"import sys; ' "import sys; "
"from importlib import import_module; " "from importlib import import_module; "
"sys.argv = {!r}; {}" "sys.argv = {!r}; {}"
"import_module('{}').{}()\"".format( "import_module('{}').{}()".format(args, src_in_sys_path, module, callable_)
args, src_in_sys_path, module, callable_
)
] ]
return self.env.run(*cmd, shell=True, call=True) return self.env.execute(*cmd)
@property @property
def _module(self): def _module(self):
......
...@@ -29,7 +29,9 @@ class SelfUpdateCommand(Command): ...@@ -29,7 +29,9 @@ class SelfUpdateCommand(Command):
arguments = [argument("version", "The version to update to.", optional=True)] arguments = [argument("version", "The version to update to.", optional=True)]
options = [option("preview", None, "Install prereleases.")] options = [option("preview", None, "Install prereleases.")]
BASE_URL = "https://github.com/sdispater/poetry/releases/download" REPOSITORY_URL = "https://github.com/python-poetry/poetry"
BASE_URL = REPOSITORY_URL + "/releases/download"
FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download"
@property @property
def home(self): def home(self):
...@@ -150,8 +152,17 @@ class SelfUpdateCommand(Command): ...@@ -150,8 +152,17 @@ class SelfUpdateCommand(Command):
checksum = "poetry-{}-{}.sha256sum".format(version, platform) checksum = "poetry-{}-{}.sha256sum".format(version, platform)
base_url = self.BASE_URL
try: try:
r = urlopen(self.BASE_URL + "/{}/{}".format(version, checksum)) urlopen(self.REPOSITORY_URL)
except HTTPError as e:
if e.code == 404:
base_url = self.FALLBACK_BASE_URL
else:
raise
try:
r = urlopen(base_url + "/{}/{}".format(version, checksum))
except HTTPError as e: except HTTPError as e:
if e.code == 404: if e.code == 404:
raise RuntimeError("Could not find {} file".format(checksum)) raise RuntimeError("Could not find {} file".format(checksum))
...@@ -163,7 +174,7 @@ class SelfUpdateCommand(Command): ...@@ -163,7 +174,7 @@ class SelfUpdateCommand(Command):
# We get the payload from the remote host # We get the payload from the remote host
name = "poetry-{}-{}.tar.gz".format(version, platform) name = "poetry-{}-{}.tar.gz".format(version, platform)
try: try:
r = urlopen(self.BASE_URL + "/{}/{}".format(version, name)) r = urlopen(base_url + "/{}/{}".format(version, name))
except HTTPError as e: except HTTPError as e:
if e.code == 404: if e.code == 404:
raise RuntimeError("Could not find {} file".format(name)) raise RuntimeError("Could not find {} file".format(name))
......
...@@ -80,20 +80,20 @@ lists all packages available.""" ...@@ -80,20 +80,20 @@ lists all packages available."""
return 0 return 0
rows = [ rows = [
["<info>name</>", " : <info>{}</>".format(pkg.pretty_name)], ["<info>name</>", " : <c1>{}</>".format(pkg.pretty_name)],
["<info>version</>", " : <comment>{}</>".format(pkg.pretty_version)], ["<info>version</>", " : <b>{}</b>".format(pkg.pretty_version)],
["<info>description</>", " : {}".format(pkg.description)], ["<info>description</>", " : {}".format(pkg.description)],
] ]
table.add_rows(rows) table.add_rows(rows)
table.render() table.render(self.io)
if pkg.requires: if pkg.requires:
self.line("") self.line("")
self.line("<info>dependencies</info>") self.line("<info>dependencies</info>")
for dependency in pkg.requires: for dependency in pkg.requires:
self.line( self.line(
" - {} <comment>{}</>".format( " - <c1>{}</c1> <b>{}</b>".format(
dependency.pretty_name, dependency.pretty_constraint dependency.pretty_name, dependency.pretty_constraint
) )
) )
...@@ -211,7 +211,7 @@ lists all packages available.""" ...@@ -211,7 +211,7 @@ lists all packages available."""
self.line(line) self.line(line)
def display_package_tree(self, io, package, installed_repo): def display_package_tree(self, io, package, installed_repo):
io.write("<info>{}</info>".format(package.pretty_name)) io.write("<c1>{}</c1>".format(package.pretty_name))
description = "" description = ""
if package.description: if package.description:
description = " " + package.description description = " " + package.description
......
...@@ -47,7 +47,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -47,7 +47,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
) )
self.line( self.line(
"Bumping version from <comment>{}</> to <info>{}</>".format( "Bumping version from <b>{}</> to <fg=green>{}</>".format(
self.poetry.package.pretty_version, version self.poetry.package.pretty_version, version
) )
) )
......
import logging import logging
from cleo.config import ApplicationConfig as BaseApplicationConfig from cleo.config import ApplicationConfig as BaseApplicationConfig
from clikit.api.event import ConsoleEvents from clikit.api.event import PRE_HANDLE
from clikit.api.event import PreHandleEvent from clikit.api.event import PreHandleEvent
from clikit.api.formatter import Style from clikit.api.formatter import Style
from clikit.api.io import Input
from clikit.api.io import InputStream
from clikit.api.io import Output
from clikit.api.io import OutputStream
from clikit.api.io.flags import DEBUG
from clikit.api.io.flags import VERBOSE
from clikit.api.io.flags import VERY_VERBOSE
from clikit.formatter import AnsiFormatter
from clikit.formatter import PlainFormatter
from clikit.io.input_stream import StandardInputStream
from clikit.io.output_stream import ErrorOutputStream
from clikit.io.output_stream import StandardOutputStream
from poetry.console.commands.command import Command from poetry.console.commands.command import Command
from poetry.console.commands.env_command import EnvCommand from poetry.console.commands.env_command import EnvCommand
...@@ -16,22 +28,17 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -16,22 +28,17 @@ class ApplicationConfig(BaseApplicationConfig):
super(ApplicationConfig, self).configure() super(ApplicationConfig, self).configure()
self.add_style(Style("c1").fg("cyan")) self.add_style(Style("c1").fg("cyan"))
self.add_style(Style("info").fg("cyan")) self.add_style(Style("info").fg("blue"))
self.add_style(Style("comment").fg("green")) self.add_style(Style("comment").fg("green"))
self.add_style(Style("error").fg("red").bold()) self.add_style(Style("error").fg("red").bold())
self.add_style(Style("warning").fg("yellow")) self.add_style(Style("warning").fg("yellow"))
self.add_style(Style("debug").fg("black").bold()) self.add_style(Style("debug").fg("black").bold())
self.add_event_listener( self.add_event_listener(PRE_HANDLE, self.register_command_loggers)
ConsoleEvents.PRE_HANDLE.value, self.register_command_loggers self.add_event_listener(PRE_HANDLE, self.set_env)
)
self.add_event_listener(ConsoleEvents.PRE_HANDLE.value, self.set_env)
def register_command_loggers( def register_command_loggers(
self, self, event, event_name, _ # type: PreHandleEvent # type: str
event, # type: PreHandleEvent
event_name, # type: str
_,
): # type: (...) -> None ): # type: (...) -> None
command = event.command.config.handler command = event.command.config.handler
if not isinstance(command, Command): if not isinstance(command, Command):
...@@ -70,27 +77,100 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -70,27 +77,100 @@ class ApplicationConfig(BaseApplicationConfig):
io = event.io io = event.io
poetry = command.poetry poetry = command.poetry
env_manager = EnvManager(poetry.config) env_manager = EnvManager(poetry)
env = env_manager.create_venv(io)
# Checking compatibility of the current environment with
# the python dependency specified in pyproject.toml
current_env = env_manager.get(poetry.file.parent)
supported_python = poetry.package.python_constraint
current_python = parse_constraint(
".".join(str(v) for v in current_env.version_info[:3])
)
if not supported_python.allows(current_python):
raise RuntimeError(
"The current Python version ({}) is not supported by the project ({})\n"
"Please activate a compatible Python version.".format(
current_python, poetry.package.python_versions
)
)
env = env_manager.create_venv(poetry.file.parent, io, poetry.package.name)
if env.is_venv() and io.is_verbose(): if env.is_venv() and io.is_verbose():
io.write_line("Using virtualenv: <comment>{}</>".format(env.path)) io.write_line("Using virtualenv: <comment>{}</>".format(env.path))
command.set_env(env) command.set_env(env)
def resolve_help_command(
self, event, event_name, dispatcher
): # type: (PreResolveEvent, str, EventDispatcher) -> None
args = event.raw_args
application = event.application
if args.has_option_token("-h") or args.has_option_token("--help"):
from clikit.api.resolver import ResolvedCommand
resolved_command = self.command_resolver.resolve(args, application)
# If the current command is the run one, skip option
# check and interpret them as part of the executed command
if resolved_command.command.name == "run":
event.set_resolved_command(resolved_command)
return event.stop_propagation()
command = application.get_command("help")
# Enable lenient parsing
parsed_args = command.parse(args, True)
event.set_resolved_command(ResolvedCommand(command, parsed_args))
event.stop_propagation()
def create_io(
self,
application,
args,
input_stream=None,
output_stream=None,
error_stream=None,
): # type: (Application, RawArgs, InputStream, OutputStream, OutputStream) -> IO
if input_stream is None:
input_stream = StandardInputStream()
if output_stream is None:
output_stream = StandardOutputStream()
if error_stream is None:
error_stream = ErrorOutputStream()
style_set = application.config.style_set
if output_stream.supports_ansi():
output_formatter = AnsiFormatter(style_set)
else:
output_formatter = PlainFormatter(style_set)
if error_stream.supports_ansi():
error_formatter = AnsiFormatter(style_set)
else:
error_formatter = PlainFormatter(style_set)
io = self.io_class(
Input(input_stream),
Output(output_stream, output_formatter),
Output(error_stream, error_formatter),
)
resolved_command = application.resolve_command(args)
# If the current command is the run one, skip option
# check and interpret them as part of the executed command
if resolved_command.command.name == "run":
return io
if args.has_option_token("--no-ansi"):
formatter = PlainFormatter(style_set)
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
elif args.has_option_token("--ansi"):
formatter = AnsiFormatter(style_set, True)
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
if args.has_option_token("-vvv") or self.is_debug():
io.set_verbosity(DEBUG)
elif args.has_option_token("-vv"):
io.set_verbosity(VERY_VERBOSE)
elif args.has_option_token("-v"):
io.set_verbosity(VERBOSE)
if args.has_option_token("--quiet") or args.has_option_token("-q"):
io.set_quiet(True)
if args.has_option_token("--no-interaction") or args.has_option_token("-n"):
io.set_interactive(False)
return io
...@@ -233,7 +233,7 @@ class Factory: ...@@ -233,7 +233,7 @@ class Factory:
): # type: (Dict[str, str], Config) -> LegacyRepository ): # type: (Dict[str, str], Config) -> LegacyRepository
from .repositories.auth import Auth from .repositories.auth import Auth
from .repositories.legacy_repository import LegacyRepository from .repositories.legacy_repository import LegacyRepository
from .utils.helpers import get_http_basic_auth from .utils.helpers import get_client_cert, get_cert, get_http_basic_auth
if "url" in source: if "url" in source:
# PyPI-like repository # PyPI-like repository
...@@ -245,12 +245,18 @@ class Factory: ...@@ -245,12 +245,18 @@ class Factory:
name = source["name"] name = source["name"]
url = source["url"] url = source["url"]
credentials = get_http_basic_auth(auth_config, name) credentials = get_http_basic_auth(auth_config, name)
if not credentials: if credentials:
return LegacyRepository(name, url)
auth = Auth(url, credentials[0], credentials[1]) auth = Auth(url, credentials[0], credentials[1])
else:
return LegacyRepository(name, url, auth=auth) auth = None
return LegacyRepository(
name,
url,
auth=auth,
cert=get_cert(auth_config, name),
client_cert=get_client_cert(auth_config, name),
)
@classmethod @classmethod
def validate( def validate(
......
...@@ -306,7 +306,7 @@ class Installer: ...@@ -306,7 +306,7 @@ class Installer:
if operation.skipped: if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()): if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line( self._io.write_line(
" - Skipping <info>{}</> (<comment>{}</>) {}".format( " - Skipping <c1>{}</c1> (<b>{}</b>) {}".format(
operation.package.pretty_name, operation.package.pretty_name,
operation.package.full_pretty_version, operation.package.full_pretty_version,
operation.skip_reason, operation.skip_reason,
...@@ -317,7 +317,7 @@ class Installer: ...@@ -317,7 +317,7 @@ class Installer:
if self._execute_operations or self.is_dry_run(): if self._execute_operations or self.is_dry_run():
self._io.write_line( self._io.write_line(
" - Installing <info>{}</> (<comment>{}</>)".format( " - Installing <c1>{}</c1> (<b>{}</b>)".format(
operation.package.pretty_name, operation.package.full_pretty_version operation.package.pretty_name, operation.package.full_pretty_version
) )
) )
...@@ -334,7 +334,7 @@ class Installer: ...@@ -334,7 +334,7 @@ class Installer:
if operation.skipped: if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()): if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line( self._io.write_line(
" - Skipping <info>{}</> (<comment>{}</>) {}".format( " - Skipping <c1>{}</c1> (<b>{}</b>) {}".format(
target.pretty_name, target.pretty_name,
target.full_pretty_version, target.full_pretty_version,
operation.skip_reason, operation.skip_reason,
...@@ -345,7 +345,7 @@ class Installer: ...@@ -345,7 +345,7 @@ class Installer:
if self._execute_operations or self.is_dry_run(): if self._execute_operations or self.is_dry_run():
self._io.write_line( self._io.write_line(
" - Updating <info>{}</> (<comment>{}</> -> <comment>{}</>)".format( " - Updating <c1>{}</c1> (<b>{}</b> -> <b>{}</b>)".format(
target.pretty_name, target.pretty_name,
source.full_pretty_version, source.full_pretty_version,
target.full_pretty_version, target.full_pretty_version,
...@@ -361,7 +361,7 @@ class Installer: ...@@ -361,7 +361,7 @@ class Installer:
if operation.skipped: if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()): if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line( self._io.write_line(
" - Not removing <info>{}</> (<comment>{}</>) {}".format( " - Not removing <c1>{}</c1> (<b>{}</b>) {}".format(
operation.package.pretty_name, operation.package.pretty_name,
operation.package.full_pretty_version, operation.package.full_pretty_version,
operation.skip_reason, operation.skip_reason,
...@@ -372,7 +372,7 @@ class Installer: ...@@ -372,7 +372,7 @@ class Installer:
if self._execute_operations or self.is_dry_run(): if self._execute_operations or self.is_dry_run():
self._io.write_line( self._io.write_line(
" - Removing <info>{}</> (<comment>{}</>)".format( " - Removing <c1>{}</c1> (<b>{}</b>)".format(
operation.package.pretty_name, operation.package.full_pretty_version operation.package.pretty_name, operation.package.full_pretty_version
) )
) )
......
...@@ -52,6 +52,12 @@ class PipInstaller(BaseInstaller): ...@@ -52,6 +52,12 @@ class PipInstaller(BaseInstaller):
) )
args += ["--trusted-host", parsed.hostname] args += ["--trusted-host", parsed.hostname]
if repository.cert:
args += ["--cert", str(repository.cert)]
if repository.client_cert:
args += ["--client-cert", str(repository.client_cert)]
index_url = repository.authenticated_url index_url = repository.authenticated_url
args += ["--index-url", index_url] args += ["--index-url", index_url]
...@@ -65,7 +71,7 @@ class PipInstaller(BaseInstaller): ...@@ -65,7 +71,7 @@ class PipInstaller(BaseInstaller):
if update: if update:
args.append("-U") args.append("-U")
if package.hashes and not package.source_type: if package.files and not package.source_type:
# Format as a requirements.txt # Format as a requirements.txt
# We need to create a requirements.txt file # We need to create a requirements.txt file
# for each package in order to check hashes. # for each package in order to check hashes.
...@@ -112,8 +118,9 @@ class PipInstaller(BaseInstaller): ...@@ -112,8 +118,9 @@ class PipInstaller(BaseInstaller):
def requirement(self, package, formatted=False): def requirement(self, package, formatted=False):
if formatted and not package.source_type: if formatted and not package.source_type:
req = "{}=={}".format(package.name, package.version) req = "{}=={}".format(package.name, package.version)
for h in package.hashes: for f in package.files:
hash_type = "sha256" hash_type = "sha256"
h = f["hash"]
if ":" in h: if ":" in h:
hash_type, h = h.split(":") hash_type, h = h.split(":")
...@@ -163,7 +170,7 @@ class PipInstaller(BaseInstaller): ...@@ -163,7 +170,7 @@ class PipInstaller(BaseInstaller):
def install_directory(self, package): def install_directory(self, package):
from poetry.masonry.builder import SdistBuilder from poetry.masonry.builder import SdistBuilder
from poetry.poetry import Poetry from poetry.factory import Factory
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.env import NullEnv from poetry.utils.env import NullEnv
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -196,7 +203,9 @@ class PipInstaller(BaseInstaller): ...@@ -196,7 +203,9 @@ class PipInstaller(BaseInstaller):
# file since pip, as of this comment, does not support # file since pip, as of this comment, does not support
# build-system for editable packages # build-system for editable packages
# We also need it for non-PEP-517 packages # We also need it for non-PEP-517 packages
builder = SdistBuilder(Poetry.create(pyproject.parent), NullEnv(), NullIO()) builder = SdistBuilder(
Factory().create_poetry(pyproject.parent), NullEnv(), NullIO()
)
with open(setup, "w", encoding="utf-8") as f: with open(setup, "w", encoding="utf-8") as f:
f.write(decode(builder.build_setup())) f.write(decode(builder.build_setup()))
......
...@@ -31,7 +31,9 @@ def validate_object(obj, schema_name): # type: (dict, str) -> List[str] ...@@ -31,7 +31,9 @@ def validate_object(obj, schema_name): # type: (dict, str) -> List[str]
for error in validation_errors: for error in validation_errors:
message = error.message message = error.message
if error.path: if error.path:
message = "[{}] {}".format(".".join(error.path), message) message = "[{}] {}".format(
".".join(str(x) for x in error.absolute_path), message
)
errors.append(message) errors.append(message)
......
...@@ -63,10 +63,7 @@ class Layout(object): ...@@ -63,10 +63,7 @@ class Layout(object):
self._license = license self._license = license
self._python = python self._python = python
self._dependencies = dependencies or {} self._dependencies = dependencies or {}
if dev_dependencies is None: self._dev_dependencies = dev_dependencies or {}
dev_dependencies = {"pytest": "^3.5"}
self._dev_dependencies = dev_dependencies
if not author: if not author:
author = "Your Name <you@example.com>" author = "Your Name <you@example.com>"
...@@ -131,8 +128,6 @@ class Layout(object): ...@@ -131,8 +128,6 @@ class Layout(object):
readme_file.touch() readme_file.touch()
def _create_tests(self, path): def _create_tests(self, path):
self._dev_dependencies["pytest"] = "^3.0"
tests = path / "tests" tests = path / "tests"
tests_init = tests / "__init__.py" tests_init = tests / "__init__.py"
tests_default = tests / "test_{}.py".format(self._package_name) tests_default = tests / "test_{}.py".format(self._package_name)
......
...@@ -22,7 +22,7 @@ from .builder import Builder ...@@ -22,7 +22,7 @@ from .builder import Builder
SETUP = """\ SETUP = """\
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from distutils.core import setup from setuptools import setup
{before} {before}
setup_kwargs = {{ setup_kwargs = {{
......
...@@ -19,7 +19,7 @@ from poetry.__version__ import __version__ ...@@ -19,7 +19,7 @@ from poetry.__version__ import __version__
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.utils._compat import decode from poetry.utils._compat import decode
from ..utils.helpers import normalize_file_permissions from ..utils.helpers import normalize_file_permissions, escape_name, escape_version
from ..utils.package_include import PackageInclude from ..utils.package_include import PackageInclude
from ..utils.tags import get_abbr_impl from ..utils.tags import get_abbr_impl
from ..utils.tags import get_abi_tag from ..utils.tags import get_abi_tag
...@@ -206,8 +206,8 @@ class WheelBuilder(Builder): ...@@ -206,8 +206,8 @@ class WheelBuilder(Builder):
@property @property
def wheel_filename(self): # type: () -> str def wheel_filename(self): # type: () -> str
return "{}-{}-{}.whl".format( return "{}-{}-{}.whl".format(
re.sub(r"[^\w\d.]+", "_", self._package.pretty_name, flags=re.UNICODE), escape_name(self._package.pretty_name),
re.sub(r"[^\w\d.\+]+", "_", self._meta.version, flags=re.UNICODE), escape_version(self._meta.version),
self.tag, self.tag,
) )
...@@ -217,8 +217,8 @@ class WheelBuilder(Builder): ...@@ -217,8 +217,8 @@ class WheelBuilder(Builder):
) )
def dist_info_name(self, distribution, version): # type: (...) -> str def dist_info_name(self, distribution, version): # type: (...) -> str
escaped_name = re.sub(r"[^\w\d.]+", "_", distribution, flags=re.UNICODE) escaped_name = escape_name(distribution)
escaped_version = re.sub(r"[^\w\d.+]+", "_", version, flags=re.UNICODE) escaped_version = escape_version(version)
return "{}-{}.dist-info".format(escaped_name, escaped_version) return "{}-{}.dist-info".format(escaped_name, escaped_version)
......
import logging import logging
from poetry.utils.helpers import get_http_basic_auth from poetry.utils.helpers import get_client_cert, get_cert, get_http_basic_auth
from .uploader import Uploader from .uploader import Uploader
...@@ -23,11 +23,11 @@ class Publisher: ...@@ -23,11 +23,11 @@ class Publisher:
def files(self): def files(self):
return self._uploader.files return self._uploader.files
def publish(self, repository_name, username, password): def publish(self, repository_name, username, password, cert=None, client_cert=None):
if repository_name: if repository_name:
self._io.write_line( self._io.write_line(
"Publishing <info>{}</info> (<comment>{}</comment>) " "Publishing <c1>{}</c1> (<b>{}</b>) "
"to <fg=cyan>{}</>".format( "to <info>{}</info>".format(
self._package.pretty_name, self._package.pretty_name,
self._package.pretty_version, self._package.pretty_version,
repository_name, repository_name,
...@@ -35,8 +35,8 @@ class Publisher: ...@@ -35,8 +35,8 @@ class Publisher:
) )
else: else:
self._io.write_line( self._io.write_line(
"Publishing <info>{}</info> (<comment>{}</comment>) " "Publishing <c1>{}</c1> (<b>{}</b>) "
"to <fg=cyan>PyPI</>".format( "to <info>PyPI</info>".format(
self._package.pretty_name, self._package.pretty_version self._package.pretty_name, self._package.pretty_version
) )
) )
...@@ -74,15 +74,21 @@ class Publisher: ...@@ -74,15 +74,21 @@ class Publisher:
username = auth[0] username = auth[0]
password = auth[1] password = auth[1]
# Requesting missing credentials resolved_client_cert = client_cert or get_client_cert(
if not username: self._poetry.config, repository_name
)
# Requesting missing credentials but only if there is not a client cert defined.
if not resolved_client_cert:
if username is None:
username = self._io.ask("Username:") username = self._io.ask("Username:")
if password is None: if password is None:
password = self._io.ask_hidden("Password:") password = self._io.ask_hidden("Password:")
# TODO: handle certificates
self._uploader.auth(username, password) self._uploader.auth(username, password)
return self._uploader.upload(url) return self._uploader.upload(
url,
cert=cert or get_cert(self._poetry.config, repository_name),
client_cert=resolved_client_cert,
)
...@@ -3,7 +3,7 @@ import io ...@@ -3,7 +3,7 @@ import io
import math import math
import re import re
from typing import List from typing import List, Optional
import requests import requests
...@@ -14,10 +14,12 @@ from requests_toolbelt import user_agent ...@@ -14,10 +14,12 @@ from requests_toolbelt import user_agent
from requests_toolbelt.multipart import MultipartEncoder, MultipartEncoderMonitor from requests_toolbelt.multipart import MultipartEncoder, MultipartEncoderMonitor
from poetry.__version__ import __version__ from poetry.__version__ import __version__
from poetry.utils._compat import Path
from poetry.utils.helpers import normalize_version from poetry.utils.helpers import normalize_version
from poetry.utils.patterns import wheel_file_re from poetry.utils.patterns import wheel_file_re
from ..metadata import Metadata from ..metadata import Metadata
from ..utils.helpers import escape_name, escape_version
_has_blake2 = hasattr(hashlib, "blake2b") _has_blake2 = hasattr(hashlib, "blake2b")
...@@ -63,10 +65,7 @@ class Uploader: ...@@ -63,10 +65,7 @@ class Uploader:
wheels = list( wheels = list(
dist.glob( dist.glob(
"{}-{}-*.whl".format( "{}-{}-*.whl".format(
re.sub( escape_name(self._package.pretty_name), escape_version(version)
r"[^\w\d.]+", "_", self._package.pretty_name, flags=re.UNICODE
),
re.sub(r"[^\w\d.]+", "_", version, flags=re.UNICODE),
) )
) )
) )
...@@ -94,9 +93,17 @@ class Uploader: ...@@ -94,9 +93,17 @@ class Uploader:
def is_authenticated(self): def is_authenticated(self):
return self._username is not None and self._password is not None return self._username is not None and self._password is not None
def upload(self, url): def upload(
self, url, cert=None, client_cert=None
): # type: (str, Optional[Path], Optional[Path]) -> None
session = self.make_session() session = self.make_session()
if cert:
session.verify = str(cert)
if client_cert:
session.cert = str(client_cert)
try: try:
self._upload(session, url) self._upload(session, url)
finally: finally:
...@@ -222,7 +229,7 @@ class Uploader: ...@@ -222,7 +229,7 @@ class Uploader:
encoder = MultipartEncoder(data_to_send) encoder = MultipartEncoder(data_to_send)
bar = self._io.progress_bar(encoder.len) bar = self._io.progress_bar(encoder.len)
bar.set_format( bar.set_format(
" - Uploading <info>{0}</> <comment>%percent%%</>".format(file.name) " - Uploading <c1>{0}</c1> <b>%percent%%</b>".format(file.name)
) )
monitor = MultipartEncoderMonitor( monitor = MultipartEncoderMonitor(
encoder, lambda monitor: bar.set_progress(monitor.bytes_read) encoder, lambda monitor: bar.set_progress(monitor.bytes_read)
...@@ -238,13 +245,18 @@ class Uploader: ...@@ -238,13 +245,18 @@ class Uploader:
) )
if resp.ok: if resp.ok:
bar.set_format(
" - Uploading <c1>{0}</c1> <fg=green>%percent%%</>".format(
file.name
)
)
bar.finish() bar.finish()
self._io.write_line("") self._io.write_line("")
else: else:
if self._io.output.supports_ansi(): if self._io.output.supports_ansi():
self._io.overwrite( self._io.overwrite(
" - Uploading <info>{0}</> <error>{1}%</>".format( " - Uploading <c1>{0}</c1> <error>{1}%</>".format(
file.name, int(math.floor(bar._percent * 100)) file.name, int(math.floor(bar._percent * 100))
) )
) )
......
import re
def normalize_file_permissions(st_mode): def normalize_file_permissions(st_mode):
""" """
Normalizes the permission bits in the st_mode field from stat to 644/755 Normalizes the permission bits in the st_mode field from stat to 644/755
...@@ -12,3 +15,17 @@ def normalize_file_permissions(st_mode): ...@@ -12,3 +15,17 @@ def normalize_file_permissions(st_mode):
new_mode |= 0o111 # Executable: 644 -> 755 new_mode |= 0o111 # Executable: 644 -> 755
return new_mode return new_mode
def escape_version(version):
"""
Escaped version in wheel filename. Doesn't exactly follow
the escaping specification in :pep:`427#escaping-and-unicode`
because this conflicts with :pep:`440#local-version-identifiers`.
"""
return re.sub(r"[^\w\d.+]+", "_", version, flags=re.UNICODE)
def escape_name(name):
"""Escaped wheel name as specified in :pep:`427#escaping-and-unicode`."""
return re.sub(r"[^\w\d.]+", "_", name, flags=re.UNICODE)
...@@ -144,9 +144,10 @@ class Dependency(object): ...@@ -144,9 +144,10 @@ class Dependency(object):
requirement += "[{}]".format(",".join(self.extras)) requirement += "[{}]".format(",".join(self.extras))
if isinstance(self.constraint, VersionUnion): if isinstance(self.constraint, VersionUnion):
requirement += " ({})".format( if self.constraint.excludes_single_version():
",".join([str(c).replace(" ", "") for c in self.constraint.ranges]) requirement += " ({})".format(str(self.constraint))
) else:
requirement += " ({})".format(self.pretty_constraint)
elif isinstance(self.constraint, Version): elif isinstance(self.constraint, Version):
requirement += " (=={})".format(self.constraint.text) requirement += " (=={})".format(self.constraint.text)
elif not self.constraint.is_any(): elif not self.constraint.is_any():
......
...@@ -6,6 +6,9 @@ import poetry.repositories ...@@ -6,6 +6,9 @@ import poetry.repositories
from hashlib import sha256 from hashlib import sha256
from tomlkit import document from tomlkit import document
from tomlkit import inline_table
from tomlkit import item
from tomlkit import table
from typing import List from typing import List
from poetry.utils._compat import Path from poetry.utils._compat import Path
...@@ -84,7 +87,15 @@ class Locker(object): ...@@ -84,7 +87,15 @@ class Locker(object):
package.description = info.get("description", "") package.description = info.get("description", "")
package.category = info["category"] package.category = info["category"]
package.optional = info["optional"] package.optional = info["optional"]
package.hashes = lock_data["metadata"]["hashes"][info["name"]] if "hashes" in lock_data["metadata"]:
# Old lock so we create dummy files from the hashes
package.files = [
{"name": h, "hash": h}
for h in lock_data["metadata"]["hashes"][info["name"]]
]
else:
package.files = lock_data["metadata"]["files"][info["name"]]
package.python_versions = info["python-versions"] package.python_versions = info["python-versions"]
extras = info.get("extras", {}) extras = info.get("extras", {})
if extras: if extras:
...@@ -135,15 +146,24 @@ class Locker(object): ...@@ -135,15 +146,24 @@ class Locker(object):
return packages return packages
def set_lock_data(self, root, packages): # type: (...) -> bool def set_lock_data(self, root, packages): # type: (...) -> bool
hashes = {} files = table()
packages = self._lock_packages(packages) packages = self._lock_packages(packages)
# Retrieving hashes # Retrieving hashes
for package in packages: for package in packages:
if package["name"] not in hashes: if package["name"] not in files:
hashes[package["name"]] = [] files[package["name"]] = []
for f in package["files"]:
file_metadata = inline_table()
for k, v in sorted(f.items()):
file_metadata[k] = v
files[package["name"]].append(file_metadata)
hashes[package["name"]] += package["hashes"] if files[package["name"]]:
del package["hashes"] files[package["name"]] = item(files[package["name"]]).multiline(True)
del package["files"]
lock = document() lock = document()
lock["package"] = packages lock["package"] = packages
...@@ -157,7 +177,7 @@ class Locker(object): ...@@ -157,7 +177,7 @@ class Locker(object):
lock["metadata"] = { lock["metadata"] = {
"python-versions": root.python_versions, "python-versions": root.python_versions,
"content-hash": self._content_hash, "content-hash": self._content_hash,
"hashes": hashes, "files": files,
} }
if not self.is_locked() or lock != self.lock_data: if not self.is_locked() or lock != self.lock_data:
...@@ -230,11 +250,16 @@ class Locker(object): ...@@ -230,11 +250,16 @@ class Locker(object):
if not dependency.python_constraint.is_any(): if not dependency.python_constraint.is_any():
constraint["python"] = str(dependency.python_constraint) constraint["python"] = str(dependency.python_constraint)
if len(constraint) == 1:
dependencies[dependency.pretty_name].append(constraint["version"])
else:
dependencies[dependency.pretty_name].append(constraint) dependencies[dependency.pretty_name].append(constraint)
# All the constraints should have the same type,
# but we want to simplify them if it's possible
for dependency, constraints in tuple(dependencies.items()):
if all(len(constraint) == 1 for constraint in constraints):
dependencies[dependency] = [
constraint["version"] for constraint in constraints
]
data = { data = {
"name": package.pretty_name, "name": package.pretty_name,
"version": package.pretty_version, "version": package.pretty_version,
...@@ -242,7 +267,7 @@ class Locker(object): ...@@ -242,7 +267,7 @@ class Locker(object):
"category": package.category, "category": package.category,
"optional": package.optional, "optional": package.optional,
"python-versions": package.python_versions, "python-versions": package.python_versions,
"hashes": sorted(package.hashes), "files": sorted(package.files, key=lambda x: x["file"]),
} }
if not package.marker.is_any(): if not package.marker.is_any():
data["marker"] = str(package.marker) data["marker"] = str(package.marker)
......
...@@ -27,7 +27,7 @@ AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+? ...@@ -27,7 +27,7 @@ AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?
class Package(object): class Package(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", "3.8"}
def __init__(self, name, version, pretty_version=None): def __init__(self, name, version, pretty_version=None):
""" """
...@@ -66,7 +66,7 @@ class Package(object): ...@@ -66,7 +66,7 @@ class Package(object):
self.requires_extras = [] self.requires_extras = []
self.category = "main" self.category = "main"
self.hashes = [] self.files = []
self.optional = False self.optional = False
self.classifiers = [] self.classifiers = []
......
...@@ -38,6 +38,7 @@ from poetry.utils.helpers import safe_rmtree ...@@ -38,6 +38,7 @@ from poetry.utils.helpers import safe_rmtree
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.env import VirtualEnv
from poetry.utils.inspector import Inspector from poetry.utils.inspector import Inspector
from poetry.utils.setup_reader import SetupReader from poetry.utils.setup_reader import SetupReader
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -172,9 +173,6 @@ class Provider: ...@@ -172,9 +173,6 @@ class Provider:
name=dependency.name, name=dependency.name,
) )
if dependency.tag or dependency.rev:
package.source_reference = dependency.reference
for extra in dependency.extras: for extra in dependency.extras:
if extra in package.extras: if extra in package.extras:
for dep in package.extras[extra]: for dep in package.extras[extra]:
...@@ -229,7 +227,9 @@ class Provider: ...@@ -229,7 +227,9 @@ class Provider:
) )
package.source_url = dependency.path.as_posix() package.source_url = dependency.path.as_posix()
package.hashes = [dependency.hash()] package.files = [
{"file": dependency.path.name, "hash": "sha256:" + dependency.hash()}
]
for extra in dependency.extras: for extra in dependency.extras:
if extra in package.extras: if extra in package.extras:
...@@ -278,6 +278,9 @@ class Provider: ...@@ -278,6 +278,9 @@ class Provider:
package.source_url = dependency.path.as_posix() package.source_url = dependency.path.as_posix()
if dependency.base != None:
package.root_dir = dependency.base.as_posix()
for extra in dependency.extras: for extra in dependency.extras:
if extra in package.extras: if extra in package.extras:
for dep in package.extras[extra]: for dep in package.extras[extra]:
...@@ -324,8 +327,9 @@ class Provider: ...@@ -324,8 +327,9 @@ class Provider:
os.chdir(str(directory)) os.chdir(str(directory))
try: try:
cwd = directory with temporary_directory() as tmp_dir:
venv = EnvManager().get(cwd) EnvManager.build_venv(tmp_dir)
venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir))
venv.run("python", "setup.py", "egg_info") venv.run("python", "setup.py", "egg_info")
except EnvCommandError: except EnvCommandError:
result = SetupReader.read_from_directory(directory) result = SetupReader.read_from_directory(directory)
...@@ -530,8 +534,8 @@ class Provider: ...@@ -530,8 +534,8 @@ class Provider:
): # type: (DependencyPackage) -> DependencyPackage ): # type: (DependencyPackage) -> DependencyPackage
if package.is_root(): if package.is_root():
package = package.clone() package = package.clone()
requires = package.all_requires
if not package.is_root() and package.source_type not in { elif not package.is_root() and package.source_type not in {
"directory", "directory",
"file", "file",
"url", "url",
...@@ -546,10 +550,13 @@ class Provider: ...@@ -546,10 +550,13 @@ class Provider:
repository=package.dependency.source_name, repository=package.dependency.source_name,
), ),
) )
requires = package.requires
else:
requires = package.requires
dependencies = [ dependencies = [
r r
for r in package.requires for r in requires
if self._package.python_constraint.allows_any(r.python_constraint) if self._package.python_constraint.allows_any(r.python_constraint)
] ]
...@@ -701,44 +708,42 @@ class Provider: ...@@ -701,44 +708,42 @@ class Provider:
m2 = re.match(r"(.+?) \((.+?)\)", m.group(1)) m2 = re.match(r"(.+?) \((.+?)\)", m.group(1))
if m2: if m2:
name = m2.group(1) name = m2.group(1)
version = " (<comment>{}</comment>)".format(m2.group(2)) version = " (<b>{}</b>)".format(m2.group(2))
else: else:
name = m.group(1) name = m.group(1)
version = "" version = ""
message = ( message = (
"<fg=blue>fact</>: <info>{}</info>{} " "<fg=blue>fact</>: <c1>{}</c1>{} "
"depends on <info>{}</info> (<comment>{}</comment>)".format( "depends on <c1>{}</c1> (<b>{}</b>)".format(
name, version, m.group(2), m.group(3) name, version, m.group(2), m.group(3)
) )
) )
elif " is " in message: elif " is " in message:
message = re.sub( message = re.sub(
"fact: (.+) is (.+)", "fact: (.+) is (.+)",
"<fg=blue>fact</>: <info>\\1</info> is <comment>\\2</comment>", "<fg=blue>fact</>: <c1>\\1</c1> is <b>\\2</b>",
message, message,
) )
else: else:
message = re.sub( message = re.sub(
r"(?<=: )(.+?) \((.+?)\)", r"(?<=: )(.+?) \((.+?)\)", "<c1>\\1</c1> (<b>\\2</b>)", message
"<info>\\1</info> (<comment>\\2</comment>)",
message,
) )
message = "<fg=blue>fact</>: {}".format(message.split("fact: ")[1]) message = "<fg=blue>fact</>: {}".format(message.split("fact: ")[1])
elif message.startswith("selecting "): elif message.startswith("selecting "):
message = re.sub( message = re.sub(
r"selecting (.+?) \((.+?)\)", r"selecting (.+?) \((.+?)\)",
"<fg=blue>selecting</> <info>\\1</info> (<comment>\\2</comment>)", "<fg=blue>selecting</> <c1>\\1</c1> (<b>\\2</b>)",
message, message,
) )
elif message.startswith("derived:"): elif message.startswith("derived:"):
m = re.match(r"derived: (.+?) \((.+?)\)$", message) m = re.match(r"derived: (.+?) \((.+?)\)$", message)
if m: if m:
message = "<fg=blue>derived</>: <info>{}</info> (<comment>{}</comment>)".format( message = "<fg=blue>derived</>: <c1>{}</c1> (<b>{}</b>)".format(
m.group(1), m.group(2) m.group(1), m.group(2)
) )
else: else:
message = "<fg=blue>derived</>: <info>{}</info>".format( message = "<fg=blue>derived</>: <c1>{}</c1>".format(
message.split("derived: ")[1] message.split("derived: ")[1]
) )
elif message.startswith("conflict:"): elif message.startswith("conflict:"):
...@@ -747,14 +752,14 @@ class Provider: ...@@ -747,14 +752,14 @@ class Provider:
m2 = re.match(r"(.+?) \((.+?)\)", m.group(1)) m2 = re.match(r"(.+?) \((.+?)\)", m.group(1))
if m2: if m2:
name = m2.group(1) name = m2.group(1)
version = " (<comment>{}</comment>)".format(m2.group(2)) version = " (<b>{}</b>)".format(m2.group(2))
else: else:
name = m.group(1) name = m.group(1)
version = "" version = ""
message = ( message = (
"<fg=red;options=bold>conflict</>: <info>{}</info>{} " "<fg=red;options=bold>conflict</>: <c1>{}</c1>{} "
"depends on <info>{}</info> (<comment>{}</comment>)".format( "depends on <c1>{}</c1> (<b>{}</b>)".format(
name, version, m.group(2), m.group(3) name, version, m.group(2), m.group(3)
) )
) )
......
...@@ -21,6 +21,11 @@ from typing import Generator ...@@ -21,6 +21,11 @@ from typing import Generator
from typing import Optional from typing import Optional
from typing import Union from typing import Union
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
import requests import requests
from cachecontrol import CacheControl from cachecontrol import CacheControl
...@@ -155,8 +160,8 @@ class Page: ...@@ -155,8 +160,8 @@ class Page:
class LegacyRepository(PyPiRepository): class LegacyRepository(PyPiRepository):
def __init__( def __init__(
self, name, url, auth=None, disable_cache=False self, name, url, auth=None, disable_cache=False, cert=None, client_cert=None
): # type: (str, str, Optional[Auth], bool) -> None ): # type: (str, str, Optional[Auth], bool, Optional[Path], Optional[Path]) -> None
if name == "pypi": if name == "pypi":
raise ValueError("The name [pypi] is reserved for repositories") raise ValueError("The name [pypi] is reserved for repositories")
...@@ -164,6 +169,8 @@ class LegacyRepository(PyPiRepository): ...@@ -164,6 +169,8 @@ class LegacyRepository(PyPiRepository):
self._name = name self._name = name
self._url = url.rstrip("/") self._url = url.rstrip("/")
self._auth = auth self._auth = auth
self._client_cert = client_cert
self._cert = cert
self._inspector = Inspector() self._inspector = Inspector()
self._cache_dir = Path(CACHE_DIR) / "cache" / "repositories" / name self._cache_dir = Path(CACHE_DIR) / "cache" / "repositories" / name
self._cache = CacheManager( self._cache = CacheManager(
...@@ -186,9 +193,23 @@ class LegacyRepository(PyPiRepository): ...@@ -186,9 +193,23 @@ class LegacyRepository(PyPiRepository):
if not url_parts.username and self._auth: if not url_parts.username and self._auth:
self._session.auth = self._auth self._session.auth = self._auth
if self._cert:
self._session.verify = str(self._cert)
if self._client_cert:
self._session.cert = str(self._client_cert)
self._disable_cache = disable_cache self._disable_cache = disable_cache
@property @property
def cert(self): # type: () -> Optional[Path]
return self._cert
@property
def client_cert(self): # type: () -> Optional[Path]
return self._client_cert
@property
def authenticated_url(self): # type: () -> str def authenticated_url(self): # type: () -> str
if not self._auth: if not self._auth:
return self.url return self.url
...@@ -197,8 +218,8 @@ class LegacyRepository(PyPiRepository): ...@@ -197,8 +218,8 @@ class LegacyRepository(PyPiRepository):
return "{scheme}://{username}:{password}@{netloc}{path}".format( return "{scheme}://{username}:{password}@{netloc}{path}".format(
scheme=parsed.scheme, scheme=parsed.scheme,
username=self._auth.auth.username, username=quote(self._auth.auth.username),
password=self._auth.auth.password, password=quote(self._auth.auth.password),
netloc=parsed.netloc, netloc=parsed.netloc,
path=parsed.path, path=parsed.path,
) )
...@@ -328,7 +349,7 @@ class LegacyRepository(PyPiRepository): ...@@ -328,7 +349,7 @@ class LegacyRepository(PyPiRepository):
package.description = release_info.get("summary", "") package.description = release_info.get("summary", "")
# Adding hashes information # Adding hashes information
package.hashes = release_info["digests"] package.files = release_info["files"]
# Activate extra dependencies # Activate extra dependencies
for extra in extras: for extra in extras:
...@@ -353,7 +374,7 @@ class LegacyRepository(PyPiRepository): ...@@ -353,7 +374,7 @@ class LegacyRepository(PyPiRepository):
"summary": "", "summary": "",
"requires_dist": [], "requires_dist": [],
"requires_python": None, "requires_python": None,
"digests": [], "files": [],
"_cache_version": str(self.CACHE_VERSION), "_cache_version": str(self.CACHE_VERSION),
} }
...@@ -365,7 +386,7 @@ class LegacyRepository(PyPiRepository): ...@@ -365,7 +386,7 @@ class LegacyRepository(PyPiRepository):
) )
) )
urls = defaultdict(list) urls = defaultdict(list)
hashes = [] files = []
for link in links: for link in links:
if link.is_wheel: if link.is_wheel:
urls["bdist_wheel"].append(link.url) urls["bdist_wheel"].append(link.url)
...@@ -374,13 +395,12 @@ class LegacyRepository(PyPiRepository): ...@@ -374,13 +395,12 @@ class LegacyRepository(PyPiRepository):
): ):
urls["sdist"].append(link.url) urls["sdist"].append(link.url)
hash = link.hash h = link.hash
if link.hash_name == "sha256": if h:
hashes.append(hash) h = link.hash_name + ":" + link.hash
elif hash: files.append({"file": link.filename, "hash": h})
hashes.append(link.hash_name + ":" + hash)
data["digests"] = hashes data["files"] = files
info = self._get_info_from_urls(urls) info = self._get_info_from_urls(urls)
......
...@@ -50,7 +50,7 @@ logger = logging.getLogger(__name__) ...@@ -50,7 +50,7 @@ logger = logging.getLogger(__name__)
class PyPiRepository(Repository): class PyPiRepository(Repository):
CACHE_VERSION = parse_constraint("0.12.0") CACHE_VERSION = parse_constraint("1.0.0b2")
def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True): def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
self._url = url self._url = url
...@@ -210,7 +210,7 @@ class PyPiRepository(Repository): ...@@ -210,7 +210,7 @@ class PyPiRepository(Repository):
package.platform = release_info["platform"] package.platform = release_info["platform"]
# Adding hashes information # Adding hashes information
package.hashes = release_info["digests"] package.files = release_info["files"]
# Activate extra dependencies # Activate extra dependencies
for extra in extras: for extra in extras:
...@@ -311,7 +311,7 @@ class PyPiRepository(Repository): ...@@ -311,7 +311,7 @@ class PyPiRepository(Repository):
"platform": info["platform"], "platform": info["platform"],
"requires_dist": info["requires_dist"], "requires_dist": info["requires_dist"],
"requires_python": info["requires_python"], "requires_python": info["requires_python"],
"digests": [], "files": [],
"_cache_version": str(self.CACHE_VERSION), "_cache_version": str(self.CACHE_VERSION),
} }
...@@ -321,7 +321,12 @@ class PyPiRepository(Repository): ...@@ -321,7 +321,12 @@ class PyPiRepository(Repository):
version_info = [] version_info = []
for file_info in version_info: for file_info in version_info:
data["digests"].append(file_info["digests"]["sha256"]) data["files"].append(
{
"file": file_info["filename"],
"hash": "sha256:" + file_info["digests"]["sha256"],
}
)
if self._fallback and data["requires_dist"] is None: if self._fallback and data["requires_dist"] is None:
self._log("No dependencies found, downloading archives", level="debug") self._log("No dependencies found, downloading archives", level="debug")
......
...@@ -64,7 +64,13 @@ class Repository(BaseRepository): ...@@ -64,7 +64,13 @@ class Repository(BaseRepository):
for package in self.packages: for package in self.packages:
if name == package.name: if name == package.name:
if package.is_prerelease() and not allow_prereleases: if (
package.is_prerelease()
and not allow_prereleases
and not package.source_type
):
# If prereleases are not allowed and the package is a prerelease
# and is a standard package then we skip it
continue continue
if constraint.allows(package.version): if constraint.allows(package.version):
......
...@@ -79,7 +79,7 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint ...@@ -79,7 +79,7 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
low = version low = version
high = version.stable.next_major high = version.stable.next_major
else: else:
low = Version(version.major, version.minor, 0) low = Version(version.major, version.minor, version.patch)
high = version.stable.next_minor high = version.stable.next_minor
return VersionRange( return VersionRange(
......
...@@ -228,7 +228,7 @@ class VersionUnion(VersionConstraint): ...@@ -228,7 +228,7 @@ class VersionUnion(VersionConstraint):
raise ValueError("Unknown VersionConstraint type {}".format(constraint)) raise ValueError("Unknown VersionConstraint type {}".format(constraint))
def _excludes_single_version(self): # type: () -> bool def excludes_single_version(self): # type: () -> bool
from .version import Version from .version import Version
from .version_range import VersionRange from .version_range import VersionRange
...@@ -243,7 +243,7 @@ class VersionUnion(VersionConstraint): ...@@ -243,7 +243,7 @@ class VersionUnion(VersionConstraint):
def __str__(self): def __str__(self):
from .version_range import VersionRange from .version_range import VersionRange
if self._excludes_single_version(): if self.excludes_single_version():
return "!={}".format(VersionRange().difference(self)) return "!={}".format(VersionRange().difference(self))
return " || ".join([str(r) for r in self._ranges]) return " || ".join([str(r) for r in self._ranges])
......
...@@ -107,9 +107,10 @@ class Exporter(object): ...@@ -107,9 +107,10 @@ class Exporter(object):
if package.source_type == "legacy" and package.source_url: if package.source_type == "legacy" and package.source_url:
indexes.append(package.source_url) indexes.append(package.source_url)
if package.hashes and with_hashes: if package.files and with_hashes:
hashes = [] hashes = []
for h in package.hashes: for f in package.files:
h = f["hash"]
algorithm = "sha256" algorithm = "sha256"
if ":" in h: if ":" in h:
algorithm, h = h.split(":") algorithm, h = h.split(":")
...@@ -123,7 +124,7 @@ class Exporter(object): ...@@ -123,7 +124,7 @@ class Exporter(object):
line += " \\\n" line += " \\\n"
for i, h in enumerate(hashes): for i, h in enumerate(hashes):
line += " --hash={}{}".format( line += " --hash={}{}".format(
h, " \\\n" if i < len(package.hashes) - 1 else "" h, " \\\n" if i < len(hashes) - 1 else ""
) )
line += "\n" line += "\n"
......
import collections
import os import os
import re import re
import shutil import shutil
import stat import stat
import tempfile import tempfile
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
from contextlib import contextmanager from contextlib import contextmanager
from typing import List from typing import List
from typing import Optional from typing import Optional
...@@ -14,6 +18,7 @@ from keyring.errors import KeyringError ...@@ -14,6 +18,7 @@ from keyring.errors import KeyringError
from poetry.config.config import Config from poetry.config.config import Config
from poetry.version import Version from poetry.version import Version
from poetry.utils._compat import Path
_canonicalize_regex = re.compile("[-_]+") _canonicalize_regex = re.compile("[-_]+")
...@@ -133,6 +138,22 @@ def get_http_basic_auth( ...@@ -133,6 +138,22 @@ def get_http_basic_auth(
return None return None
def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path]
cert = config.get("certificates.{}.cert".format(repository_name))
if cert:
return Path(cert)
else:
return None
def get_client_cert(config, repository_name): # type: (Config, str) -> Optional[Path]
client_cert = config.get("certificates.{}.client-cert".format(repository_name))
if client_cert:
return Path(client_cert)
else:
return None
def _on_rm_error(func, path, exc_info): def _on_rm_error(func, path, exc_info):
os.chmod(path, stat.S_IWRITE) os.chmod(path, stat.S_IWRITE)
func(path) func(path)
...@@ -144,11 +165,7 @@ def safe_rmtree(path): ...@@ -144,11 +165,7 @@ def safe_rmtree(path):
def merge_dicts(d1, d2): def merge_dicts(d1, d2):
for k, v in d2.items(): for k, v in d2.items():
if ( if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping):
k in d1
and isinstance(d1[k], dict)
and isinstance(d2[k], collections.Mapping)
):
merge_dicts(d1[k], d2[k]) merge_dicts(d1[k], d2[k])
else: else:
d1[k] = d2[k] d1[k] = d2[k]
...@@ -144,11 +144,11 @@ class Inspector: ...@@ -144,11 +144,11 @@ class Inspector:
pyproject = TomlFile(sdist_dir / "pyproject.toml") pyproject = TomlFile(sdist_dir / "pyproject.toml")
if pyproject.exists(): if pyproject.exists():
from poetry.poetry import Poetry from poetry.factory import Factory
pyproject_content = pyproject.read() pyproject_content = pyproject.read()
if "tool" in pyproject_content and "poetry" in pyproject_content["tool"]: if "tool" in pyproject_content and "poetry" in pyproject_content["tool"]:
package = Poetry.create(sdist_dir).package package = Factory().create_poetry(sdist_dir).package
return { return {
"name": package.name, "name": package.name,
"version": package.version.text, "version": package.version.text,
......
[tool.poetry] [tool.poetry]
name = "poetry" name = "poetry"
version = "1.0.0b1" version = "1.0.0b3"
description = "Python dependency management and packaging made easy." description = "Python dependency management and packaging made easy."
authors = [ authors = [
"Sébastien Eustace <sebastien@eustace.io>" "Sébastien Eustace <sebastien@eustace.io>"
...@@ -23,18 +23,19 @@ classifiers = [ ...@@ -23,18 +23,19 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.4" python = "~2.7 || ^3.4"
cleo = "^0.7.5" cleo = "^0.7.6"
clikit = "^0.4.0"
requests = "^2.18" requests = "^2.18"
cachy = "^0.3.0" cachy = "^0.3.0"
requests-toolbelt = "^0.8.0" requests-toolbelt = "^0.8.0"
jsonschema = "^3.0a3" jsonschema = "^3.1"
pyrsistent = "^0.14.2" pyrsistent = "^0.14.2"
pyparsing = "^2.2" pyparsing = "^2.2"
cachecontrol = { version = "^0.12.4", extras = ["filecache"] } cachecontrol = { version = "^0.12.4", extras = ["filecache"] }
pkginfo = "^1.4" pkginfo = "^1.4"
html5lib = "^1.0" html5lib = "^1.0"
shellingham = "^1.1" shellingham = "^1.1"
tomlkit = "^0.5.5" tomlkit = "^0.5.8"
pexpect = "^4.7.0" pexpect = "^4.7.0"
# The typing module is not in the stdlib in Python 2.7 and 3.4 # The typing module is not in the stdlib in Python 2.7 and 3.4
......
...@@ -28,6 +28,7 @@ class MakeReleaseCommand(Command): ...@@ -28,6 +28,7 @@ class MakeReleaseCommand(Command):
"3.5": "python3.5", "3.5": "python3.5",
"3.6": "python3.6", "3.6": "python3.6",
"3.7": "python3.7", "3.7": "python3.7",
"3.8": "python3.8",
} }
def handle(self): def handle(self):
...@@ -46,7 +47,7 @@ class MakeReleaseCommand(Command): ...@@ -46,7 +47,7 @@ class MakeReleaseCommand(Command):
self.check_system(pythons) self.check_system(pythons)
from poetry import __version__ from poetry import __version__
from poetry.poetry import Poetry from poetry.factory import Factory
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
...@@ -59,7 +60,7 @@ class MakeReleaseCommand(Command): ...@@ -59,7 +60,7 @@ class MakeReleaseCommand(Command):
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.vcs import get_vcs from poetry.vcs import get_vcs
project = Poetry.create(Path.cwd()) project = Factory().create_poetry(Path.cwd())
package = project.package package = project.package
del package.dev_requires[:] del package.dev_requires[:]
...@@ -217,6 +218,7 @@ class MakeReleaseCommand(Command): ...@@ -217,6 +218,7 @@ class MakeReleaseCommand(Command):
subprocess.check_output( subprocess.check_output(
[python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS [python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS
) )
subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"])
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise RuntimeError("Python {} is not available".format(version)) raise RuntimeError("Python {} is not available".format(version))
......
...@@ -81,7 +81,7 @@ def mock_clone(_, source, dest): ...@@ -81,7 +81,7 @@ def mock_clone(_, source, dest):
parts = urlparse.urlparse(source) parts = urlparse.urlparse(source)
folder = ( folder = (
Path(__file__).parent.parent Path(__file__).parent
/ "fixtures" / "fixtures"
/ "git" / "git"
/ parts.netloc / parts.netloc
...@@ -91,7 +91,6 @@ def mock_clone(_, source, dest): ...@@ -91,7 +91,6 @@ def mock_clone(_, source, dest):
if dest.exists(): if dest.exists():
shutil.rmtree(str(dest)) shutil.rmtree(str(dest))
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest)) shutil.copytree(str(folder), str(dest))
......
...@@ -11,7 +11,7 @@ def test_none_activated(app, tmp_dir): ...@@ -11,7 +11,7 @@ def test_none_activated(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
...@@ -34,7 +34,7 @@ def test_activated(app, tmp_dir): ...@@ -34,7 +34,7 @@ def test_activated(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......
...@@ -11,7 +11,7 @@ def test_remove_by_python_version(app, tmp_dir, mocker): ...@@ -11,7 +11,7 @@ def test_remove_by_python_version(app, tmp_dir, mocker):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
...@@ -39,7 +39,7 @@ def test_remove_by_name(app, tmp_dir): ...@@ -39,7 +39,7 @@ def test_remove_by_name(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......
...@@ -57,7 +57,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m ...@@ -57,7 +57,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m
tester.execute("3.7") tester.execute("3.7")
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
m.assert_called_with( m.assert_called_with(
...@@ -88,7 +88,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( ...@@ -88,7 +88,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
os.environ["VIRTUAL_ENV"] = "/environment/prefix" os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
current_python = sys.version_info[:3] current_python = sys.version_info[:3]
python_minor = ".".join(str(v) for v in current_python[:2]) python_minor = ".".join(str(v) for v in current_python[:2])
...@@ -130,7 +130,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var( ...@@ -130,7 +130,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var(
os.environ["VIRTUAL_ENV"] = "/environment/prefix" os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name( venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent) "simple-project", str(app.poetry.file.parent)
) )
current_python = sys.version_info[:3] current_python = sys.version_info[:3]
python_minor = ".".join(str(v) for v in current_python[:2]) python_minor = ".".join(str(v) for v in current_python[:2])
......
...@@ -99,3 +99,26 @@ def test_set_pypi_token(app, config, config_source, auth_config_source): ...@@ -99,3 +99,26 @@ def test_set_pypi_token(app, config, config_source, auth_config_source):
tester.execute("--list") tester.execute("--list")
assert "mytoken" == auth_config_source.config["pypi-token"]["pypi"] assert "mytoken" == auth_config_source.config["pypi-token"]["pypi"]
def test_set_client_cert(app, config_source, auth_config_source, mocker):
init = mocker.spy(ConfigSource, "__init__")
command = app.find("config")
tester = CommandTester(command)
tester.execute("certificates.foo.client-cert path/to/cert.pem")
assert (
"path/to/cert.pem"
== auth_config_source.config["certificates"]["foo"]["client-cert"]
)
def test_set_cert(app, config_source, auth_config_source, mocker):
init = mocker.spy(ConfigSource, "__init__")
command = app.find("config")
tester = CommandTester(command)
tester.execute("certificates.foo.cert path/to/ca.pem")
assert "path/to/ca.pem" == auth_config_source.config["certificates"]["foo"]["cert"]
from poetry.utils._compat import Path
def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http): def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http):
http.register_uri( http.register_uri(
http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request" http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request"
...@@ -16,3 +19,22 @@ HTTP Error 400: Bad Request ...@@ -16,3 +19,22 @@ HTTP Error 400: Bad Request
""" """
assert app_tester.io.fetch_output() == expected assert app_tester.io.fetch_output() == expected
def test_publish_with_cert(app_tester, mocker):
publisher_publish = mocker.patch("poetry.masonry.publishing.Publisher.publish")
app_tester.execute("publish --cert path/to/ca.pem")
assert [
(None, None, None, Path("path/to/ca.pem"), None)
] == publisher_publish.call_args
def test_publish_with_client_cert(app_tester, mocker):
publisher_publish = mocker.patch("poetry.masonry.publishing.Publisher.publish")
app_tester.execute("publish --client-cert path/to/client.pem")
assert [
(None, None, None, None, Path("path/to/client.pem"))
] == publisher_publish.call_args
[tool.poetry]
name = "prerelease"
version = "1.0.0.dev0"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
[tool.poetry.dev-dependencies]
...@@ -40,7 +40,7 @@ foo = ["C"] ...@@ -40,7 +40,7 @@ foo = ["C"]
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
......
...@@ -37,7 +37,7 @@ foo = ["D"] ...@@ -37,7 +37,7 @@ foo = ["D"]
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
......
...@@ -26,7 +26,7 @@ python-versions = "*" ...@@ -26,7 +26,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
"B" = [] "B" = []
"C" = [] "C" = []
...@@ -4,4 +4,4 @@ package = [] ...@@ -4,4 +4,4 @@ package = []
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
...@@ -10,5 +10,5 @@ python-versions = "*" ...@@ -10,5 +10,5 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
...@@ -10,5 +10,5 @@ python-versions = "*" ...@@ -10,5 +10,5 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
...@@ -42,7 +42,7 @@ python-versions = "*" ...@@ -42,7 +42,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
"B" = [] "B" = []
"C" = [] "C" = []
......
...@@ -21,6 +21,6 @@ A = "^1.0" ...@@ -21,6 +21,6 @@ A = "^1.0"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
"B" = [] "B" = []
...@@ -24,5 +24,5 @@ python = ">=3.6,<4.0" ...@@ -24,5 +24,5 @@ python = ">=3.6,<4.0"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
...@@ -32,7 +32,7 @@ python-versions = "*" ...@@ -32,7 +32,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
"B" = [] "B" = []
"C" = [] "C" = []
...@@ -18,6 +18,6 @@ python-versions = "*" ...@@ -18,6 +18,6 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
"B" = [] "B" = []
...@@ -35,6 +35,6 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies" ...@@ -35,6 +35,6 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies"
content-hash = "123456789" content-hash = "123456789"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.files]
project-with-extras = [] project-with-extras = []
project-with-transitive-directory-dependencies = [] project-with-transitive-directory-dependencies = []
...@@ -30,6 +30,6 @@ url = "tests/fixtures/project_with_extras" ...@@ -30,6 +30,6 @@ url = "tests/fixtures/project_with_extras"
content-hash = "123456789" content-hash = "123456789"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.files]
project-with-extras = [] project-with-extras = []
pendulum = [] pendulum = []
...@@ -35,7 +35,7 @@ python-versions = "*" ...@@ -35,7 +35,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
cachy = [] cachy = []
my-package = [] my-package = []
pendulum = [] pendulum = []
...@@ -32,7 +32,7 @@ python-versions = "*" ...@@ -32,7 +32,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
...@@ -58,7 +58,7 @@ python-versions = "*" ...@@ -58,7 +58,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
...@@ -46,7 +46,9 @@ url = "tests/fixtures/directory/project_with_transitive_file_dependencies" ...@@ -46,7 +46,9 @@ url = "tests/fixtures/directory/project_with_transitive_file_dependencies"
content-hash = "123456789" content-hash = "123456789"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.files]
demo = ["70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"] demo = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"},
]
pendulum = [] pendulum = []
project-with-transitive-file-dependencies = [] project-with-transitive-file-dependencies = []
...@@ -30,6 +30,8 @@ python-versions = "*" ...@@ -30,6 +30,8 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
demo = ["70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"] demo = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"},
]
pendulum = [] pendulum = []
...@@ -51,7 +51,7 @@ python-versions = "*" ...@@ -51,7 +51,7 @@ python-versions = "*"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
...@@ -34,7 +34,7 @@ foo = ["A"] ...@@ -34,7 +34,7 @@ foo = ["A"]
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
C = [] C = []
D = [] D = []
...@@ -43,7 +43,7 @@ foo = ["A"] ...@@ -43,7 +43,7 @@ foo = ["A"]
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
......
...@@ -18,6 +18,6 @@ python-versions = "*" ...@@ -18,6 +18,6 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
"A" = [] "A" = []
"B" = [] "B" = []
...@@ -86,12 +86,36 @@ python-versions = "*" ...@@ -86,12 +86,36 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
attrs = ["1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", "a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"] attrs = [
colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"] {file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"},
funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"] {file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"},
more-itertools = ["0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", "11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", "c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"] ]
pluggy = ["7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"] colorama = [
py = ["29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", "983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"] {file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"},
pytest = ["6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", "fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"] {file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"},
six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"] ]
funcsigs = [
{file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"},
{file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"},
]
more-itertools = [
{file = "more-itertools-4.1.0.tar.gz", hash = "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"},
{file = "more_itertools-4.1.0-py2-none-any.whl", hash = "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e"},
{file = "more_itertools-4.1.0-py3-none-any.whl", hash = "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea"},
]
pluggy = [
{file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"},
]
py = [
{file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"},
{file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"},
]
pytest = [
{file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"},
{file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"},
]
six = [
{file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"},
{file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"},
]
...@@ -26,7 +26,7 @@ python-versions = "~2.7 || ^3.3" ...@@ -26,7 +26,7 @@ python-versions = "~2.7 || ^3.3"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
...@@ -40,7 +40,7 @@ python-versions = "*" ...@@ -40,7 +40,7 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
A = [] A = []
B = [] B = []
C = [] C = []
......
...@@ -30,6 +30,6 @@ python-versions = "*" ...@@ -30,6 +30,6 @@ python-versions = "*"
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
demo = [] demo = []
pendulum = [] pendulum = []
...@@ -15,5 +15,7 @@ url = "tests/fixtures/wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.wh ...@@ -15,5 +15,7 @@ url = "tests/fixtures/wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.wh
python-versions = "*" python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.files]
demo = ["c25eb81459126848a1788eb3520d1a32014eb51ce3d3bae88c56bfdde4ce02db"] demo = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:c25eb81459126848a1788eb3520d1a32014eb51ce3d3bae88c56bfdde4ce02db"},
]
...@@ -1529,3 +1529,31 @@ def test_run_installs_with_url_file(installer, locker, repo, package): ...@@ -1529,3 +1529,31 @@ def test_run_installs_with_url_file(installer, locker, repo, package):
assert locker.written_data == expected assert locker.written_data == expected
assert len(installer.installer.installs) == 2 assert len(installer.installer.installs) == 2
def test_installer_uses_prereleases_if_they_are_compatible(
installer, locker, package, repo
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency(
"prerelease", {"git": "https://github.com/demo/prerelease.git"}
)
package_b = get_package("b", "2.0.0")
package_b.add_dependency("prerelease", ">=0.19")
repo.add_package(package_b)
installer.run()
del installer.installer.installs[:]
locker.locked(True)
locker.mock_lock_data(locker.written_data)
package.add_dependency("b", "^2.0.0")
installer.whitelist(["b"])
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
...@@ -5,6 +5,7 @@ from poetry.io.null_io import NullIO ...@@ -5,6 +5,7 @@ from poetry.io.null_io import NullIO
from poetry.packages.package import Package from poetry.packages.package import Package
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.utils._compat import Path
from poetry.utils.env import NullEnv from poetry.utils.env import NullEnv
...@@ -29,9 +30,12 @@ def installer(pool): ...@@ -29,9 +30,12 @@ def installer(pool):
def test_requirement(installer): def test_requirement(installer):
package = Package("ipython", "7.5.0") package = Package("ipython", "7.5.0")
package.hashes = [ package.files = [
"md5:dbdc53e3918f28fa335a173432402a00", {"file": "foo-0.1.0.tar.gz", "hash": "md5:dbdc53e3918f28fa335a173432402a00"},
"e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26", {
"file": "foo.0.1.0.whl",
"hash": "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
},
] ]
result = installer.requirement(package, formatted=True) result = installer.requirement(package, formatted=True)
...@@ -86,6 +90,62 @@ def test_install_with_non_pypi_default_repository(pool, installer): ...@@ -86,6 +90,62 @@ def test_install_with_non_pypi_default_repository(pool, installer):
installer.install(bar) installer.install(bar)
def test_install_with_cert():
ca_path = "path/to/cert.pem"
pool = Pool()
default = LegacyRepository("default", "https://foo.bar", cert=Path(ca_path))
pool.add_repository(default, default=True)
null_env = NullEnv()
installer = PipInstaller(null_env, NullIO(), pool)
foo = Package("foo", "0.0.0")
foo.source_type = "legacy"
foo.source_reference = default._name
foo.source_url = default._url
installer.install(foo)
assert len(null_env.executed) == 1
cmd = null_env.executed[0]
assert "--cert" in cmd
cert_index = cmd.index("--cert")
# Need to do the str(Path()) bit because Windows paths get modified by Path
assert cmd[cert_index + 1] == str(Path(ca_path))
def test_install_with_client_cert():
client_path = "path/to/client.pem"
pool = Pool()
default = LegacyRepository(
"default", "https://foo.bar", client_cert=Path(client_path)
)
pool.add_repository(default, default=True)
null_env = NullEnv()
installer = PipInstaller(null_env, NullIO(), pool)
foo = Package("foo", "0.0.0")
foo.source_type = "legacy"
foo.source_reference = default._name
foo.source_url = default._url
installer.install(foo)
assert len(null_env.executed) == 1
cmd = null_env.executed[0]
assert "--client-cert" in cmd
cert_index = cmd.index("--client-cert")
# Need to do the str(Path()) bit because Windows paths get modified by Path
assert cmd[cert_index + 1] == str(Path(client_path))
def test_requirement_git_develop_true(installer, package_git): def test_requirement_git_develop_true(installer, package_git):
package_git.develop = True package_git.develop = True
result = installer.requirement(package_git) result = installer.requirement(package_git)
......
...@@ -88,6 +88,7 @@ def test_get_metadata_content(): ...@@ -88,6 +88,7 @@ def test_get_metadata_content():
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
] ]
......
...@@ -79,7 +79,7 @@ def test_wheel_c_extension(): ...@@ -79,7 +79,7 @@ def test_wheel_c_extension():
Wheel-Version: 1.0 Wheel-Version: 1.0
Generator: poetry {} Generator: poetry {}
Root-Is-Purelib: false Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dmu?-.+ Tag: cp[23]\\d-cp[23]\\dm?u?-.+
$""".format( $""".format(
__version__ __version__
), ),
...@@ -136,7 +136,7 @@ def test_wheel_c_extension_src_layout(): ...@@ -136,7 +136,7 @@ def test_wheel_c_extension_src_layout():
Wheel-Version: 1.0 Wheel-Version: 1.0
Generator: poetry {} Generator: poetry {}
Root-Is-Purelib: false Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dmu?-.+ Tag: cp[23]\\d-cp[23]\\dm?u?-.+
$""".format( $""".format(
__version__ __version__
), ),
...@@ -216,6 +216,7 @@ Classifier: License :: OSI Approved :: MIT License ...@@ -216,6 +216,7 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time Provides-Extra: time
...@@ -318,6 +319,7 @@ Classifier: License :: OSI Approved :: MIT License ...@@ -318,6 +319,7 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time Provides-Extra: time
......
...@@ -7,6 +7,7 @@ from clikit.io import NullIO ...@@ -7,6 +7,7 @@ from clikit.io import NullIO
from poetry.factory import Factory from poetry.factory import Factory
from poetry.masonry.builders import WheelBuilder from poetry.masonry.builders import WheelBuilder
from poetry.masonry.publishing.uploader import Uploader
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import NullEnv from poetry.utils.env import NullEnv
...@@ -99,7 +100,8 @@ def test_wheel_excluded_nested_data(): ...@@ -99,7 +100,8 @@ def test_wheel_excluded_nested_data():
def test_wheel_localversionlabel(): def test_wheel_localversionlabel():
module_path = fixtures_dir / "localversionlabel" module_path = fixtures_dir / "localversionlabel"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) project = Factory().create_poetry(module_path)
WheelBuilder.make(project, NullEnv(), NullIO())
local_version_string = "localversionlabel-0.1b1+gitbranch.buildno.1" local_version_string = "localversionlabel-0.1b1+gitbranch.buildno.1"
whl = module_path / "dist" / (local_version_string + "-py2.py3-none-any.whl") whl = module_path / "dist" / (local_version_string + "-py2.py3-none-any.whl")
...@@ -108,6 +110,9 @@ def test_wheel_localversionlabel(): ...@@ -108,6 +110,9 @@ def test_wheel_localversionlabel():
with zipfile.ZipFile(str(whl)) as z: with zipfile.ZipFile(str(whl)) as z:
assert local_version_string + ".dist-info/METADATA" in z.namelist() assert local_version_string + ".dist-info/METADATA" in z.namelist()
uploader = Uploader(project, NullIO())
assert whl in uploader.files
def test_wheel_package_src(): def test_wheel_package_src():
module_path = fixtures_dir / "source_package" module_path = fixtures_dir / "source_package"
......
...@@ -3,6 +3,7 @@ import pytest ...@@ -3,6 +3,7 @@ import pytest
from poetry.factory import Factory from poetry.factory import Factory
from poetry.io.null_io import NullIO from poetry.io.null_io import NullIO
from poetry.masonry.publishing.publisher import Publisher from poetry.masonry.publishing.publisher import Publisher
from poetry.utils._compat import Path
def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
...@@ -18,7 +19,10 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): ...@@ -18,7 +19,10 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
publisher.publish(None, None, None) publisher.publish(None, None, None)
assert [("foo", "bar")] == uploader_auth.call_args assert [("foo", "bar")] == uploader_auth.call_args
assert [("https://upload.pypi.org/legacy/",)] == uploader_upload.call_args assert [
("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None},
] == uploader_upload.call_args
def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
...@@ -37,7 +41,10 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): ...@@ -37,7 +41,10 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
publisher.publish("my-repo", None, None) publisher.publish("my-repo", None, None)
assert [("foo", "bar")] == uploader_auth.call_args assert [("foo", "bar")] == uploader_auth.call_args
assert [("http://foo.bar",)] == uploader_upload.call_args assert [
("http://foo.bar",),
{"cert": None, "client_cert": None},
] == uploader_upload.call_args
def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, config): def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, config):
...@@ -63,4 +70,52 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config): ...@@ -63,4 +70,52 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config):
publisher.publish(None, None, None) publisher.publish(None, None, None)
assert [("__token__", "my-token")] == uploader_auth.call_args assert [("__token__", "my-token")] == uploader_auth.call_args
assert [("https://upload.pypi.org/legacy/",)] == uploader_upload.call_args assert [
("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None},
] == uploader_upload.call_args
def test_publish_uses_cert(fixture_dir, mocker, config):
cert = "path/to/ca.pem"
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config
poetry.config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar"}},
"http-basic": {"foo": {"username": "foo", "password": "bar"}},
"certificates": {"foo": {"cert": cert}},
}
)
publisher = Publisher(poetry, NullIO())
publisher.publish("foo", None, None)
assert [("foo", "bar")] == uploader_auth.call_args
assert [
("https://foo.bar",),
{"cert": Path(cert), "client_cert": None},
] == uploader_upload.call_args
def test_publish_uses_client_cert(fixture_dir, mocker, config):
client_cert = "path/to/client.pem"
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config
poetry.config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar"}},
"certificates": {"foo": {"client-cert": client_cert}},
}
)
publisher = Publisher(poetry, NullIO())
publisher.publish("foo", None, None)
assert [
("https://foo.bar",),
{"cert": None, "client_cert": Path(client_cert)},
] == uploader_upload.call_args
...@@ -94,6 +94,7 @@ Classifier: License :: OSI Approved :: MIT License ...@@ -94,6 +94,7 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time Provides-Extra: time
......
...@@ -102,3 +102,9 @@ def test_to_pep_508_in_extras(): ...@@ -102,3 +102,9 @@ def test_to_pep_508_in_extras():
") " ") "
'and (extra == "foo" or extra == "bar")' 'and (extra == "foo" or extra == "bar")'
) )
def test_to_pep_508_with_single_version_excluded():
dependency = Dependency("foo", "!=1.2.3")
assert "foo (!=1.2.3)" == dependency.to_pep_508()
...@@ -6,6 +6,7 @@ import tomlkit ...@@ -6,6 +6,7 @@ import tomlkit
from poetry.packages.locker import Locker from poetry.packages.locker import Locker
from poetry.packages.project_package import ProjectPackage from poetry.packages.project_package import ProjectPackage
from ..helpers import get_dependency
from ..helpers import get_package from ..helpers import get_package
...@@ -26,7 +27,7 @@ def root(): ...@@ -26,7 +27,7 @@ def root():
def test_lock_file_data_is_ordered(locker, root): def test_lock_file_data_is_ordered(locker, root):
package_a = get_package("A", "1.0.0") package_a = get_package("A", "1.0.0")
package_a.add_dependency("B", "^1.0") package_a.add_dependency("B", "^1.0")
package_a.hashes = ["456", "123"] package_a.files = [{"file": "foo", "hash": "456"}, {"file": "bar", "hash": "123"}]
packages = [package_a, get_package("B", "1.2")] packages = [package_a, get_package("B", "1.2")]
locker.set_lock_data(root, packages) locker.set_lock_data(root, packages)
...@@ -57,8 +58,11 @@ version = "1.2" ...@@ -57,8 +58,11 @@ version = "1.2"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.files]
A = ["123", "456"] A = [
{file = "bar", hash = "123"},
{file = "foo", hash = "456"},
]
B = [] B = []
""" """
...@@ -91,7 +95,7 @@ redis = ["redis (>=2.10.5)"] ...@@ -91,7 +95,7 @@ redis = ["redis (>=2.10.5)"]
content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77" content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
[metadata.hashes] [metadata.files]
cachecontrol = [] cachecontrol = []
""" """
...@@ -130,8 +134,50 @@ version = "1.0.0" ...@@ -130,8 +134,50 @@ version = "1.0.0"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.files]
A = [] A = []
""" """
assert expected == content assert expected == content
def test_lock_file_should_not_have_mixed_types(locker, root):
package_a = get_package("A", "1.0.0")
package_a.add_dependency("B", "^1.0.0")
package_a.add_dependency("B", {"version": ">=1.0.0", "optional": True})
package_a.requires[-1].activate()
package_a.extras["foo"] = [get_dependency("B", ">=1.0.0")]
locker.set_lock_data(root, [package_a])
expected = """[[package]]
category = "main"
description = ""
name = "A"
optional = false
python-versions = "*"
version = "1.0.0"
[package.dependencies]
[[package.dependencies.B]]
version = "^1.0.0"
[[package.dependencies.B]]
optional = true
version = ">=1.0.0"
[package.extras]
foo = ["B (>=1.0.0)"]
[metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.files]
A = []
"""
with locker.lock.open(encoding="utf-8") as f:
content = f.read()
assert expected == content
...@@ -118,7 +118,10 @@ def test_search_for_vcs_read_setup_with_extras(provider, mocker): ...@@ -118,7 +118,10 @@ def test_search_for_vcs_read_setup_with_extras(provider, mocker):
def test_search_for_vcs_read_setup_raises_error_if_no_version(provider, mocker): def test_search_for_vcs_read_setup_raises_error_if_no_version(provider, mocker):
mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) mocker.patch(
"poetry.utils.env.VirtualEnv.run",
side_effect=EnvCommandError(CalledProcessError(1, "python", output="")),
)
dependency = VCSDependency("demo", "git", "https://github.com/demo/no-version.git") dependency = VCSDependency("demo", "git", "https://github.com/demo/no-version.git")
...@@ -175,6 +178,46 @@ def test_search_for_directory_setup_egg_info_with_extras(provider): ...@@ -175,6 +178,46 @@ def test_search_for_directory_setup_egg_info_with_extras(provider):
} }
@pytest.mark.parametrize("directory", ["demo", "non-canonical-name"])
def test_search_for_directory_setup_with_base(provider, directory):
dependency = DirectoryDependency(
"demo",
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ directory,
base=Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ directory,
)
package = provider.search_for_directory(dependency)[0]
assert package.name == "demo"
assert package.version.text == "0.1.2"
assert package.requires == [get_dependency("pendulum", ">=1.4.4")]
assert package.extras == {
"foo": [get_dependency("cleo")],
"bar": [get_dependency("tomlkit")],
}
assert (
package.root_dir
== (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ directory
).as_posix()
)
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4") @pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_search_for_directory_setup_read_setup(provider, mocker): def test_search_for_directory_setup_read_setup(provider, mocker):
mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv()) mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv())
......
...@@ -928,6 +928,11 @@ def test_solver_can_resolve_git_dependencies(solver, repo, package): ...@@ -928,6 +928,11 @@ def test_solver_can_resolve_git_dependencies(solver, repo, package):
], ],
) )
op = ops[1]
assert op.package.source_type == "git"
assert op.package.source_reference.startswith("9cf87a2")
def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package): def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3") pendulum = get_package("pendulum", "2.0.3")
...@@ -951,6 +956,37 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package): ...@@ -951,6 +956,37 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
) )
@pytest.mark.parametrize(
"ref",
[{"branch": "a-branch"}, {"tag": "a-tag"}, {"rev": "9cf8"}],
ids=["branch", "tag", "rev"],
)
def test_solver_can_resolve_git_dependencies_with_ref(solver, repo, package, ref):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
git_config = {"git": "https://github.com/demo/demo.git"}
git_config.update(ref)
package.add_dependency("demo", git_config)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.2")},
],
)
op = ops[1]
assert op.package.source_type == "git"
assert op.package.source_reference.startswith("9cf87a2")
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible( def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
solver, repo, package solver, repo, package
): ):
...@@ -1775,3 +1811,27 @@ def test_solver_discards_packages_with_empty_markers( ...@@ -1775,3 +1811,27 @@ def test_solver_discards_packages_with_empty_markers(
{"job": "install", "package": package_a}, {"job": "install", "package": package_a},
], ],
) )
def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.5"
package.add_dependency("A", {"version": "^1.0", "python": "~2.7"}, category="dev")
package.add_dependency("A", {"version": "^2.0", "python": "^3.5"}, category="dev")
package_a100 = get_package("A", "1.0.0")
package_a200 = get_package("A", "2.0.0")
repo.add_package(package_a100)
repo.add_package(package_a200)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_a100},
{"job": "install", "package": package_a200},
],
)
...@@ -7,6 +7,7 @@ except ImportError: ...@@ -7,6 +7,7 @@ except ImportError:
import urlparse import urlparse
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.repositories.auth import Auth
from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.exceptions import PackageNotFound
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.legacy_repository import Page from poetry.repositories.legacy_repository import Page
...@@ -18,9 +19,9 @@ class MockRepository(LegacyRepository): ...@@ -18,9 +19,9 @@ class MockRepository(LegacyRepository):
FIXTURES = Path(__file__).parent / "fixtures" / "legacy" FIXTURES = Path(__file__).parent / "fixtures" / "legacy"
def __init__(self): def __init__(self, auth=None):
super(MockRepository, self).__init__( super(MockRepository, self).__init__(
"legacy", url="http://foo.bar", disable_cache=True "legacy", url="http://foo.bar", auth=auth, disable_cache=True
) )
def _get(self, endpoint): def _get(self, endpoint):
...@@ -242,11 +243,17 @@ def test_get_package_retrieves_non_sha256_hashes(): ...@@ -242,11 +243,17 @@ def test_get_package_retrieves_non_sha256_hashes():
package = repo.package("ipython", "7.5.0") package = repo.package("ipython", "7.5.0")
expected = [ expected = [
"md5:dbdc53e3918f28fa335a173432402a00", {
"e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26", "file": "ipython-7.5.0-py3-none-any.whl",
"hash": "md5:dbdc53e3918f28fa335a173432402a00",
},
{
"file": "ipython-7.5.0.tar.gz",
"hash": "sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
},
] ]
assert expected == package.hashes assert expected == package.files
def test_get_package_retrieves_packages_with_no_hashes(): def test_get_package_retrieves_packages_with_no_hashes():
...@@ -254,4 +261,11 @@ def test_get_package_retrieves_packages_with_no_hashes(): ...@@ -254,4 +261,11 @@ def test_get_package_retrieves_packages_with_no_hashes():
package = repo.package("jupyter", "1.0.0") package = repo.package("jupyter", "1.0.0")
assert [] == package.hashes assert [] == package.files
def test_username_password_special_chars():
auth = Auth("http://foo.bar", "user:", "p@ssword")
repo = MockRepository(auth=auth)
assert "http://user%3A:p%40ssword@foo.bar" == repo.authenticated_url
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