Commit 5a2a3e62 by Sébastien Eustace

Merge branch 'master' into develop

# Conflicts:
#	CHANGELOG.md
#	poetry.lock
#	poetry/__version__.py
#	poetry/installation/installer.py
#	pyproject.toml
#	tests/console/conftest.py
#	tests/installation/test_installer.py
#	tests/masonry/builders/test_builder.py
#	tests/masonry/builders/test_wheel.py
parents 56503817 f2e2ed47
......@@ -3,7 +3,7 @@
# Packages
*.egg
*.egg-info
dist
/dist/*
build
_build
.cache
......
......@@ -24,6 +24,24 @@
- Fixed transitive extra dependencies being removed when updating a specific dependency.
## [0.12.12] - 2019-04-11
### Fixed
- Fix lock idempotency.
- Fix markers evaluation for `python_version` with precision < 3.
- Fix permissions of the `dist-info` files.
- Fix `prepare_metadata_for_build_wheel()` missing in the build backend.
- Fix metadata inconsistency between wheels and sdists.
- Fix parsing of `platform_release` markers.
- Fix metadata information when the project has git dependencies.
- Fix error reporting when publishing fails.
- Fix retrieval of `extras_require` in some `setup.py` files. (Thanks to [@asodeur](https://github.com/asodeur))
- Fix wheel compression when building. (Thanks to [@ccosby](https://github.com/ccosby))
- Improve retrieval of information for packages with two python specific wheels.
- Fix request authentication when credentials are included in URLs. (Thanks to [@connorbrinton](https://github.com/connorbrinton))
## [0.12.11] - 2019-01-13
### Fixed
......@@ -643,7 +661,8 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.11...develop
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.12...develop
[0.12.12]: https://github.com/sdispater/poetry/releases/tag/0.12.12
[0.12.11]: https://github.com/sdispater/poetry/releases/tag/0.12.11
[0.12.10]: https://github.com/sdispater/poetry/releases/tag/0.12.10
[0.12.9]: https://github.com/sdispater/poetry/releases/tag/0.12.9
......
# Contributing to Poetry
First off, thank for taking the time to contribute!
First off, thanks for taking the time to contribute!
The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request.
#### Table of Contents
#### Table of contents
[How to contribute](#how-to-contribute)
......@@ -29,7 +29,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**.
#### How do I submit a bug report
#### How do I submit a bug report?
Bugs are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md).
......@@ -38,7 +38,7 @@ Explain the problem and include additional details to help maintainers reproduce
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible.
* **Provide your pyproject.toml file** in a [Gist](https://gist.github.com) after removing potential private information (like private package repositories).
* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples.
* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy-paste-able snippets, which you use in those examples.
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **If the problem is an unexpected error being raised**, execute the corresponding command in **debug** mode (the `-vvv` option).
......@@ -115,5 +115,5 @@ will not be merged.
#### Pull requests
* Fill in [the required template](https://github.com/sdispater/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
* Be sure that you pull request contains tests that cover the changed or added code.
* If you changes warrant a documentation change, the pull request must also update the documentation.
* Be sure that your pull request contains tests that cover the changed or added code.
* If your changes warrant a documentation change, the pull request must also update the documentation.
......@@ -588,9 +588,9 @@ More identifiers are listed at the [SPDX Open Source License Registry](https://w
#### authors
The authors of the package. This is a list of authors and should contain at least one author.
The authors of the package. **Required**
Authors must be in the form `name <email>`.
This is a list of authors and should contain at least one author. Authors must be in the form `name <email>`.
#### readme
......
......@@ -196,6 +196,16 @@ poetry add my-package --path ../my-package/dist/my-package-0.1.0.tar.gz
poetry add my-package --path ../my-package/dist/my_package-0.1.0.whl
```
Path dependencies pointing to a local directory will be installed in editable mode (i.e. setuptools "develop mode").
It means that changes in the local directory will be reflected directly in environment.
If you don't want the dependency to be installed in editable mode you can specify it in the `pyproject.toml` file:
```
[tool.poetry.dependencies]
my-package = {path = "../my/path", develop = false}
```
### Options
* `--dev (-D)`: Add package as development dependency.
......
......@@ -90,17 +90,23 @@ pip install --user poetry
Be aware that it will also install Poetry's dependencies
which might cause conflicts with other packages.
#### Installing with `pipsi`
#### Installing with `pipx`
Using [`pipsi`](https://github.com/mitsuhiko/pipsi) to install Poetry is also possible.
Using [`pipx`](https://github.com/cs01/pipx) to install Poetry is also possible. [pipx] is used to install Python CLI applications globally while still isolating them in virtual environments. This allows for clean upgrades and uninstalls. pipx supports Python 3.6 and later. If using an earlier version of Python, consider [pipsi](https://github.com/mitsuhiko/pipsi).
```bash
pipsi install poetry
pipx install poetry
```
Make sure your installed version of `pipsi` is at least version `0.10`,
otherwise Poetry will not function properly. You can get it from its
[Github repository](https://github.com/mitsuhiko/pipsi).
```bash
pipx upgrade poetry
```
```bash
pipx uninstall poetry
```
[Github repository](https://github.com/cs01/pipx).
## Updating `poetry`
......
......@@ -42,9 +42,9 @@ More identifiers are listed at the [SPDX Open Source License Registry](https://w
## authors
The authors of the package. This is a list of authors and should contain at least one author.
The authors of the package. **Required**
Authors must be in the form `name <email>`.
This is a list of authors and should contain at least one author. Authors must be in the form `name <email>`.
## readme
......
......@@ -195,7 +195,7 @@ import glob
import sys
import os
lib = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "lib"))
lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib"))
sys.path.insert(0, lib)
......
......@@ -2,7 +2,7 @@
PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m"
cd /io
/opt/python/cp37-cp37m/bin/pip install poetry --pre -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 install --no-dev
/opt/python/cp37-cp37m/bin/python sonnet make release \
......
......@@ -23,7 +23,6 @@ the current directory, processes it, and locks the depdencies in the <comment>po
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
)
installer.update(True)
installer.execute_operations(False)
installer.lock()
return installer.run()
......@@ -45,6 +45,7 @@ class Installer:
self._dev_mode = True
self._develop = []
self._execute_operations = True
self._lock = False
self._whitelist = []
......@@ -109,6 +110,16 @@ class Installer:
return self
def lock(self): # type: () -> Installer
"""
Prepare the installer for locking only.
"""
self.update()
self.execute_operations(False)
self._lock = True
return self
def is_updating(self): # type: () -> bool
return self._update
......@@ -130,7 +141,7 @@ class Installer:
def _do_install(self, local_repo):
locked_repository = Repository()
if self._update:
if self._locker.is_locked():
if self._locker.is_locked() and not self._lock:
locked_repository = self._locker.locked_repository(True)
# If no packages have been whitelisted (The ones we want to update),
......@@ -178,7 +189,14 @@ class Installer:
# currently installed
ops = self._get_operations_from_lock(locked_repository)
self._populate_local_repo(local_repo, ops, locked_repository)
self._populate_local_repo(local_repo, ops)
if self._update:
self._write_lock_file(local_repo)
if self._lock:
# If we are only in lock mode, no need to go any further
return 0
root = self._package
if not self.is_dev_mode():
......@@ -270,20 +288,18 @@ class Installer:
)
)
# Writing lock before installing
self._io.write_line("")
for op in ops:
self._execute(op)
def _write_lock_file(self, repo): # type: (Repository) -> None
if self._update and self._write_lock:
updated_lock = self._locker.set_lock_data(
self._package, local_repo.packages
)
updated_lock = self._locker.set_lock_data(self._package, repo.packages)
if updated_lock:
self._io.write_line("")
self._io.write_line("<info>Writing lock file</>")
self._io.write_line("")
for op in ops:
self._execute(op)
def _execute(self, operation): # type: (Operation) -> None
"""
Execute a given operation.
......@@ -372,36 +388,15 @@ class Installer:
self._installer.remove(operation.package)
def _populate_local_repo(self, local_repo, ops, locked_repository):
# We walk through all operations and add/remove/update accordingly
def _populate_local_repo(self, local_repo, ops):
for op in ops:
if isinstance(op, Update):
if isinstance(op, Uninstall):
continue
elif isinstance(op, Update):
package = op.target_package
else:
package = op.package
acted_on = False
for pkg in locked_repository.packages:
if pkg.name == package.name:
# The package we operate on is in the local repo
if op.job_type == "update":
if pkg.version == package.version:
break
local_repo.remove_package(pkg)
local_repo.add_package(op.target_package)
elif op.job_type == "uninstall":
local_repo.remove_package(op.package)
else:
# Even though the package already exists
# in the lock file we will prefer the new one
# to force updates
local_repo.remove_package(pkg)
local_repo.add_package(package)
acted_on = True
if not acted_on:
if not local_repo.has_package(package):
local_repo.add_package(package)
......
from cleo.io.io_mixin import IOMixin
from clikit.io import NullIO as BaseNullIO
class NullIO(IOMixin, BaseNullIO):
"""
A wrapper around CliKit's NullIO.
"""
def __init__(self, *args, **kwargs):
super(NullIO, self).__init__(*args, **kwargs)
......@@ -32,6 +32,26 @@ def get_requires_for_build_wheel(config_settings=None):
get_requires_for_build_sdist = get_requires_for_build_wheel
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
poetry = Poetry.create(".")
builder = WheelBuilder(poetry, SystemEnv(Path(sys.prefix)), NullIO())
dist_info = Path(metadata_directory, builder.dist_info)
dist_info.mkdir()
if "scripts" in poetry.local_config or "plugins" in poetry.local_config:
with (dist_info / "entry_points.txt").open("w") as f:
builder._write_entry_points(f)
with (dist_info / "WHEEL").open("w") as f:
builder._write_wheel_file(f)
with (dist_info / "METADATA").open("w") as f:
builder._write_metadata_file(f)
return dist_info.name
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
"""Builds a wheel, places it in wheel_directory"""
poetry = Poetry.create(".")
......
......@@ -15,6 +15,7 @@ from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils._compat import glob
from poetry.utils._compat import lru_cache
from poetry.utils._compat import to_str
from poetry.vcs import get_vcs
from ..metadata import Metadata
......@@ -24,6 +25,13 @@ from ..utils.package_include import PackageInclude
AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+) <(?P<email>.+?)>$")
METADATA_BASE = """\
Metadata-Version: 2.1
Name: {name}
Version: {version}
Summary: {summary}
"""
class Builder(object):
......@@ -147,6 +155,54 @@ class Builder(object):
return sorted(to_add)
def get_metadata_content(self): # type: () -> bytes
content = METADATA_BASE.format(
name=self._meta.name,
version=self._meta.version,
summary=to_str(self._meta.summary),
)
# Optional fields
if self._meta.home_page:
content += "Home-page: {}\n".format(self._meta.home_page)
if self._meta.license:
content += "License: {}\n".format(self._meta.license)
if self._meta.keywords:
content += "Keywords: {}\n".format(self._meta.keywords)
if self._meta.author:
content += "Author: {}\n".format(to_str(self._meta.author))
if self._meta.author_email:
content += "Author-email: {}\n".format(to_str(self._meta.author_email))
if self._meta.requires_python:
content += "Requires-Python: {}\n".format(self._meta.requires_python)
for classifier in self._meta.classifiers:
content += "Classifier: {}\n".format(classifier)
for extra in sorted(self._meta.provides_extra):
content += "Provides-Extra: {}\n".format(extra)
for dep in sorted(self._meta.requires_dist):
content += "Requires-Dist: {}\n".format(dep)
for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
content += "Project-URL: {}\n".format(to_str(url))
if self._meta.description_content_type:
content += "Description-Content-Type: {}\n".format(
self._meta.description_content_type
)
if self._meta.description is not None:
content += "\n" + to_str(self._meta.description) + "\n"
return content
def convert_entry_points(self): # type: () -> dict
result = defaultdict(list)
......
......@@ -41,17 +41,6 @@ setup(**setup_kwargs)
"""
PKG_INFO = """\
Metadata-Version: 2.1
Name: {name}
Version: {version}
Summary: {summary}
Home-page: {home_page}
Author: {author}
Author-email: {author_email}
"""
class SdistBuilder(Builder):
def build(self, target_dir=None): # type: (Path) -> Path
self._io.write_line(" - Building <info>sdist</info>")
......@@ -195,34 +184,7 @@ class SdistBuilder(Builder):
)
def build_pkg_info(self):
pkg_info = PKG_INFO.format(
name=self._meta.name,
version=self._meta.version,
summary=self._meta.summary,
home_page=self._meta.home_page,
author=to_str(self._meta.author),
author_email=to_str(self._meta.author_email),
)
if self._meta.keywords:
pkg_info += "Keywords: {}\n".format(self._meta.keywords)
if self._meta.requires_python:
pkg_info += "Requires-Python: {}\n".format(self._meta.requires_python)
for classifier in self._meta.classifiers:
pkg_info += "Classifier: {}\n".format(classifier)
for extra in sorted(self._meta.provides_extra):
pkg_info += "Provides-Extra: {}\n".format(extra)
for dep in sorted(self._meta.requires_dist):
pkg_info += "Requires-Dist: {}\n".format(dep)
for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
pkg_info += "Project-URL: {}\n".format(url)
return encode(pkg_info)
return encode(self.get_metadata_content())
def find_packages(self, include):
"""
......
......@@ -17,6 +17,7 @@ from clikit.api.io.flags import VERY_VERBOSE
from poetry.__version__ import __version__
from poetry.semver import parse_constraint
from poetry.utils._compat import decode
from ..utils.helpers import normalize_file_permissions
from ..utils.package_include import PackageInclude
......@@ -259,7 +260,7 @@ class WheelBuilder(Builder):
hashsum.update(buf)
src.seek(0)
wheel.writestr(zinfo, src.read())
wheel.writestr(zinfo, src.read(), compress_type=zipfile.ZIP_DEFLATED)
size = os.stat(full_path).st_size
hash_digest = urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
......@@ -276,6 +277,7 @@ class WheelBuilder(Builder):
# give you the exact same result.
date_time = (2016, 1, 1, 0, 0, 0)
zi = zipfile.ZipInfo(rel_path, date_time)
zi.external_attr = (0o644 & 0xFFFF) << 16 # Unix attributes
b = sio.getvalue().encode("utf-8")
hashsum = hashlib.sha256(b)
hash_digest = urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
......@@ -309,43 +311,4 @@ class WheelBuilder(Builder):
"""
Write out metadata in the 2.x format (email like)
"""
fp.write("Metadata-Version: 2.1\n")
fp.write("Name: {}\n".format(self._meta.name))
fp.write("Version: {}\n".format(self._meta.version))
fp.write("Summary: {}\n".format(self._meta.summary))
fp.write("Home-page: {}\n".format(self._meta.home_page or "UNKNOWN"))
fp.write("License: {}\n".format(self._meta.license or "UNKNOWN"))
# Optional fields
if self._meta.keywords:
fp.write("Keywords: {}\n".format(self._meta.keywords))
if self._meta.author:
fp.write("Author: {}\n".format(self._meta.author))
if self._meta.author_email:
fp.write("Author-email: {}\n".format(self._meta.author_email))
if self._meta.requires_python:
fp.write("Requires-Python: {}\n".format(self._meta.requires_python))
for classifier in self._meta.classifiers:
fp.write("Classifier: {}\n".format(classifier))
for extra in sorted(self._meta.provides_extra):
fp.write("Provides-Extra: {}\n".format(extra))
for dep in sorted(self._meta.requires_dist):
fp.write("Requires-Dist: {}\n".format(dep))
for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
fp.write("Project-URL: {}\n".format(url))
if self._meta.description_content_type:
fp.write(
"Description-Content-Type: "
"{}\n".format(self._meta.description_content_type)
)
if self._meta.description is not None:
fp.write("\n" + self._meta.description + "\n")
fp.write(decode(self.get_metadata_content()))
import hashlib
import io
import math
import re
from typing import List
......@@ -22,6 +23,15 @@ from ..metadata import Metadata
_has_blake2 = hasattr(hashlib, "blake2b")
class UploadError(Exception):
def __init__(self, error): # type: (HTTPError) -> None
super(UploadError, self).__init__(
"HTTP Error {}: {}".format(
error.response.status_code, error.response.reason
)
)
class Uploader:
def __init__(self, poetry, io):
self._poetry = poetry
......@@ -175,18 +185,15 @@ class Uploader:
self._do_upload(session, url)
except HTTPError as e:
if (
e.response.status_code not in (403, 400)
or e.response.status_code == 400
and "was ever registered" not in e.response.text
e.response.status_code == 400
and "was ever registered" in e.response.text
):
raise
# It may be the first time we publish the package
# We'll try to register it and go from there
try:
self._register(session, url)
except HTTPError:
raise
except HTTPError as e:
raise UploadError(e)
raise UploadError(e)
def _do_upload(self, session, url):
for file in self.files:
......@@ -235,7 +242,14 @@ class Uploader:
self._io.write_line("")
else:
self._io.overwrite("")
if self._io.output.supports_ansi():
self._io.overwrite(
" - Uploading <info>{0}</> <error>{1}%</>".format(
file.name, int(math.floor(bar._percent * 100))
)
)
else:
self._io.write_line("")
return resp
......@@ -263,6 +277,8 @@ class Uploader:
headers={"Content-Type": encoder.content_type},
)
resp.raise_for_status()
return resp
def _prepare_data(self, data):
......
import os
import re
from poetry.semver import Version
from poetry.version.requirements import Requirement
from .dependency import Dependency
......@@ -105,6 +106,22 @@ def dependency_from_pep_508(name):
op = ""
elif op == "!=":
version += ".*"
elif op in ("<=", ">"):
parsed_version = Version.parse(version)
if parsed_version.precision == 1:
if op == "<=":
op = "<"
version = parsed_version.next_major.text
elif op == ">":
op = ">="
version = parsed_version.next_major.text
elif parsed_version.precision == 2:
if op == "<=":
op = "<"
version = parsed_version.next_minor.text
elif op == ">":
op = ">="
version = parsed_version.next_minor.text
elif op in ("in", "not in"):
versions = []
for v in re.split("[ ,]+", version):
......
......@@ -127,6 +127,24 @@ class Dependency(object):
def in_extras(self): # type: () -> list
return self._in_extras
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
if isinstance(self.constraint, VersionUnion):
requirement += " ({})".format(
",".join([str(c).replace(" ", "") for c in self.constraint.ranges])
)
elif isinstance(self.constraint, Version):
requirement += " (=={})".format(self.constraint.text)
elif not self.constraint.is_any():
requirement += " ({})".format(str(self.constraint).replace(" ", ""))
return requirement
def allows_prereleases(self):
return self._allows_prereleases
......@@ -156,19 +174,7 @@ class Dependency(object):
)
def to_pep_508(self, with_extras=True): # type: (bool) -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
if isinstance(self.constraint, VersionUnion):
requirement += " ({})".format(
",".join([str(c).replace(" ", "") for c in self.constraint.ranges])
)
elif isinstance(self.constraint, Version):
requirement += " (=={})".format(self.constraint.text)
elif not self.constraint.is_any():
requirement += " ({})".format(str(self.constraint).replace(" ", ""))
requirement = self.base_pep_508_name
markers = []
if not self.marker.is_any():
......
......@@ -62,6 +62,17 @@ class VCSDependency(Dependency):
return "{} {}".format(what, version)
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference)
return requirement
def is_vcs(self): # type: () -> bool
return True
......
......@@ -282,6 +282,10 @@ class SetupReader(object):
if isinstance(value, ast.Dict):
for key, val in zip(value.keys, value.values):
if isinstance(val, ast.Name):
val = self._find_variable_in_body(body, val.id)
if isinstance(val, ast.List):
extras_require[key.s] = [e.s for e in val.elts]
elif isinstance(value, ast.Name):
variable = self._find_variable_in_body(body, value.id)
......@@ -290,6 +294,10 @@ class SetupReader(object):
return extras_require
for key, val in zip(variable.keys, variable.values):
if isinstance(val, ast.Name):
val = self._find_variable_in_body(body, val.id)
if isinstance(val, ast.List):
extras_require[key.s] = [e.s for e in val.elts]
return extras_require
......
......@@ -230,6 +230,7 @@ class EmptyMarker(BaseMarker):
class SingleMarker(BaseMarker):
_CONSTRAINT_RE = re.compile(r"(?i)^(~=|!=|>=?|<=?|==?|in|not in)?\s*(.+)$")
_VERSION_LIKE_MARKER_NAME = {"python_version", "platform_release"}
def __init__(self, name, constraint):
from poetry.packages.constraints import (
......@@ -248,10 +249,10 @@ class SingleMarker(BaseMarker):
self._value = m.group(2)
self._parser = parse_generic_constraint
if self._name == "python_version":
if name in self._VERSION_LIKE_MARKER_NAME:
self._parser = parse_constraint
if name == "python_version":
if self._operator in {"in", "not in"}:
versions = []
for v in re.split("[ ,]+", self._value):
......
......@@ -174,8 +174,8 @@ MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction(
lambda s, l, t: parse_marker(s[t._original_start : t._original_end])
)
MARKER_SEPERATOR = SEMICOLON
MARKER = MARKER_SEPERATOR + MARKER_EXPR
MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER)
......
......@@ -60,6 +60,7 @@ black = { version = "^18.3-alpha.0", python = "^3.6" }
pre-commit = "^1.10"
tox = "^3.0"
pytest-sugar = "^0.9.2"
httpretty = "^0.9.6"
[tool.poetry.scripts]
......
......@@ -68,6 +68,7 @@ class MakeReleaseCommand(Command):
else:
vcs_excluded = []
created_files = []
with temporary_directory() as tmp_dir:
# Copy poetry to tmp dir
poetry_dir = os.path.join(tmp_dir, "poetry")
......@@ -78,6 +79,13 @@ class MakeReleaseCommand(Command):
set([os.path.join(dir_, name) for name in names])
),
)
created_files += [
p.relative_to(Path(tmp_dir))
for p in Path(poetry_dir).glob("**/*")
if p.is_file()
and p.suffix != ".pyc"
and str(p.relative_to(Path(tmp_dir))) not in vcs_excluded
]
for version, python in sorted(pythons.items()):
self.line(
"<info>Preparing files for Python <comment>{}</comment></info>".format(
......@@ -91,6 +99,16 @@ class MakeReleaseCommand(Command):
self.vendorize_for_python(
python, [op.package for op in ops], poetry_dir, version
)
vendor_dir = Path(
os.path.join(poetry_dir, "_vendor", "py{}".format(python))
)
created_files += [
p.relative_to(Path(tmp_dir))
for p in vendor_dir.glob("**/*")
if p.is_file()
and p.suffix != ".pyc"
and str(p.relative_to(Path(tmp_dir))) not in vcs_excluded
]
self.line("")
......@@ -130,6 +148,22 @@ class MakeReleaseCommand(Command):
finally:
gz.close()
self.line("<info>Checking release file</info>")
missing_files = []
with tarfile.open(os.path.join(tmp_dir2, name), "r") as tar:
names = tar.getnames()
for created_file in created_files:
if created_file.as_posix() not in names:
missing_files.append(created_file.as_posix())
if missing_files:
self.line("<error>Some files are missing:</error>")
for missing_file in missing_files:
self.line("<error> - {}</error>".format(missing_file))
return 1
releases_dir = os.path.join(os.path.dirname(__file__), "releases")
if not os.path.exists(releases_dir):
os.mkdir(releases_dir)
......
import httpretty
import os
import pytest
import shutil
......@@ -72,3 +73,12 @@ def git_mock(mocker):
mocker.patch("poetry.vcs.git.Git.checkout", new=lambda *_: None)
p = mocker.patch("poetry.vcs.git.Git.rev_parse")
p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
@pytest.fixture
def http():
httpretty.enable()
yield httpretty
httpretty.disable()
......@@ -21,10 +21,10 @@ Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
......@@ -53,10 +53,10 @@ def test_add_constraint(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.1.0)
"""
......@@ -85,11 +85,11 @@ def test_add_constraint_dependencies(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file
- Installing msgpack-python (0.5.3)
- Installing cachy (0.2.0)
"""
......@@ -113,10 +113,10 @@ def test_add_git_constraint(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
......@@ -147,10 +147,10 @@ def test_add_git_constraint_with_poetry(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
......@@ -174,11 +174,11 @@ def test_add_file_constraint_wheel(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file
- Installing pendulum (1.4.4)
- Installing demo (0.1.0 ../distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
......@@ -208,10 +208,10 @@ def test_add_file_constraint_sdist(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.0 ../distributions/demo-0.1.0.tar.gz)
......@@ -249,10 +249,10 @@ def test_add_constraint_with_extras(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing msgpack-python (0.5.3)
- Installing cachy (0.2.0)
......@@ -287,11 +287,11 @@ def test_add_constraint_with_python(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
Writing lock file
- Installing cachy (0.2.0)
"""
......@@ -322,10 +322,10 @@ def test_add_constraint_with_platform(app, repo, installer):
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
......@@ -358,10 +358,10 @@ Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
......@@ -391,11 +391,11 @@ Using version ^3.13 for pyyaml
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
Writing lock file
- Installing pyyaml (3.13)
"""
......
def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http):
http.register_uri(
http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request"
)
exit_code = app_tester.execute("publish --username foo --password bar")
assert 1 == exit_code
expected = """
Publishing simple-project (1.2.3) to PyPI
[UploadError]
HTTP Error 400: Bad Request
"""
assert app_tester.io.fetch_output() == expected
......@@ -7,6 +7,7 @@ try:
except ImportError:
import urlparse
from cleo import ApplicationTester
from tomlkit import document
from poetry.config import Config as BaseConfig
......@@ -139,6 +140,7 @@ class Poetry(BasePoetry):
self._local_config = local_config
self._locker = Locker(locker.lock.path, locker._local_config)
self._config = Config.create("config.toml")
self._auth_config = Config.create("auth.toml")
# Configure sources
self._pool = Pool()
......@@ -184,4 +186,12 @@ def poetry(repo, project_directory):
@pytest.fixture
def app(poetry):
return Application(poetry)
app_ = Application(poetry)
app_.config.set_terminate_after_run(False)
return app_
@pytest.fixture
def app_tester(app):
return ApplicationTester(app)
[[package]]
name = "A"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
B = ">=1.0.1"
C = [
{version = "^1.0", python = ">=2.7,<2.8"},
{version = "^2.0", python = ">=3.4,<4.0"},
]
[[package]]
name = "B"
version = "1.0.1"
description = ""
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
[[package]]
name = "B"
version = "1.1.0"
description = ""
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
[[package]]
name = "C"
version = "1.0"
description = ""
category = "main"
optional = false
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
python-versions = "*"
[[package]]
name = "C"
version = "2.0"
description = ""
category = "main"
optional = false
marker = "python_version >= \"3.4\" and python_version < \"4.0\""
python-versions = "*"
[metadata]
python-versions = "~2.7 || ^3.4"
content-hash = "123456789"
[metadata.hashes]
A = []
B = []
C = []
......@@ -1396,3 +1396,78 @@ def test_installer_required_extras_should_be_installed(
assert len(installer.installer.installs) == 2
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
def test_update_multiple_times_with_split_dependencies_is_idempotent(
installer, locker, repo, package
):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"B": ">=1.0"},
},
{
"name": "B",
"version": "1.0.1",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
"checksum": [],
"dependencies": {},
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": []},
},
}
)
package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", "^1.0")
a = get_package("A", "1.0")
a.add_dependency("B", ">=1.0.1")
a.add_dependency("C", {"version": "^1.0", "python": "~2.7"})
a.add_dependency("C", {"version": "^2.0", "python": "^3.4"})
b101 = get_package("B", "1.0.1")
b101.python_versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
b110 = get_package("B", "1.1.0")
b110.python_versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
repo.add_package(a)
repo.add_package(b101)
repo.add_package(b110)
repo.add_package(get_package("C", "1.0"))
repo.add_package(get_package("C", "2.0"))
expected = fixture("with-multiple-updates")
installer.update(True)
installer.run()
assert expected == locker.written_data
locker.mock_lock_data(locker.written_data)
installer.update(True)
installer.run()
assert expected == locker.written_data
locker.mock_lock_data(locker.written_data)
installer.update(True)
installer.run()
assert expected == locker.written_data
......@@ -5,12 +5,9 @@ description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io/"
[tool.poetry.dependencies]
python = "3.6"
[tool.poetry]
name = "with-vcs-dependency"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
homepage = "https://poetry.eustace.io/"
repository = "https://github.com/sdispater/poetry"
documentation = "https://poetry.eustace.io/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" }
# -*- coding: utf-8 -*-
from clikit.io import NullIO
from email.parser import Parser
from poetry.masonry.builders.builder import Builder
from poetry.poetry import Poetry
......@@ -53,3 +55,79 @@ def test_builder_find_invalid_case_sensitive_excluded_files(mocker):
)
assert {"my_package/Bar/foo/bar/Foo.py"} == builder.find_excluded_files()
def test_get_metadata_content():
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "complete"),
NullEnv(),
NullIO(),
)
metadata = builder.get_metadata_content()
p = Parser()
parsed = p.parsestr(metadata)
assert parsed["Metadata-Version"] == "2.1"
assert parsed["Name"] == "my-package"
assert parsed["Version"] == "1.2.3"
assert parsed["Summary"] == "Some description."
assert parsed["Author"] == "Sébastien Eustace"
assert parsed["Author-email"] == "sebastien@eustace.io"
assert parsed["Keywords"] == "packaging,dependency,poetry"
assert parsed["Requires-Python"] == ">=3.6,<4.0"
assert parsed["License"] == "MIT"
assert parsed["Home-page"] == "https://poetry.eustace.io/"
classifiers = parsed.get_all("Classifier")
assert classifiers == [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
extras = parsed.get_all("Provides-Extra")
assert extras == ["time"]
requires = parsed.get_all("Requires-Dist")
assert requires == [
"cachy[msgpack] (>=0.2.0,<0.3.0)",
"cleo (>=0.6,<0.7)",
'pendulum (>=1.4,<2.0); extra == "time"',
]
urls = parsed.get_all("Project-URL")
assert urls == [
"Documentation, https://poetry.eustace.io/docs",
"Repository, https://github.com/sdispater/poetry",
]
def test_metadata_homepage_default():
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "simple_version"),
NullEnv(),
NullIO(),
)
metadata = Parser().parsestr(builder.get_metadata_content())
assert metadata["Home-page"] is None
def test_metadata_with_vcs_dependencies():
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "with_vcs_dependency"),
NullEnv(),
NullIO(),
)
metadata = Parser().parsestr(builder.get_metadata_content())
requires_dist = metadata["Requires-Dist"]
assert "cleo @ git+https://github.com/sdispater/cleo.git@master" == requires_dist
......@@ -11,6 +11,7 @@ from clikit.io import NullIO
from poetry.masonry.builders.sdist import SdistBuilder
from poetry.masonry.utils.package_include import PackageInclude
from poetry.packages import Package
from poetry.packages.vcs_dependency import VCSDependency
from poetry.poetry import Poetry
from poetry.utils._compat import Path
from poetry.utils._compat import to_str
......@@ -49,9 +50,15 @@ def test_convert_dependencies():
get_dependency("A", "^1.0"),
get_dependency("B", "~1.0"),
get_dependency("C", "1.2.3"),
VCSDependency("D", "git", "https://github.com/sdispater/d.git"),
],
)
main = ["A>=1.0,<2.0", "B>=1.0,<1.1", "C==1.2.3"]
main = [
"A>=1.0,<2.0",
"B>=1.0,<1.1",
"C==1.2.3",
"D @ git+https://github.com/sdispater/d.git@master",
]
extras = {}
assert result == (main, extras)
......@@ -129,48 +136,16 @@ def test_make_setup():
assert ns["extras_require"] == {"time": ["pendulum>=1.4,<2.0"]}
def test_make_pkg_info():
def test_make_pkg_info(mocker):
get_metadata_content = mocker.patch(
"poetry.masonry.builders.builder.Builder.get_metadata_content"
)
poetry = Poetry.create(project("complete"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))
assert parsed["Metadata-Version"] == "2.1"
assert parsed["Name"] == "my-package"
assert parsed["Version"] == "1.2.3"
assert parsed["Summary"] == "Some description."
assert parsed["Author"] == "Sébastien Eustace"
assert parsed["Author-email"] == "sebastien@eustace.io"
assert parsed["Keywords"] == "packaging,dependency,poetry"
assert parsed["Requires-Python"] == ">=3.6,<4.0"
classifiers = parsed.get_all("Classifier")
assert classifiers == [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
builder.build_pkg_info()
extras = parsed.get_all("Provides-Extra")
assert extras == ["time"]
requires = parsed.get_all("Requires-Dist")
assert requires == [
"cachy[msgpack] (>=0.2.0,<0.3.0)",
"cleo (>=0.6,<0.7)",
'pendulum (>=1.4,<2.0); extra == "time"',
]
urls = parsed.get_all("Project-URL")
assert urls == [
"Documentation, https://poetry.eustace.io/docs",
"Repository, https://github.com/sdispater/poetry",
]
assert get_metadata_content.called
def test_make_pkg_info_any_python():
......
# -*- coding: utf-8 -*-
import pytest
import shutil
import zipfile
......@@ -8,7 +9,6 @@ from poetry.masonry.builders import WheelBuilder
from poetry.poetry import Poetry
from poetry.utils._compat import Path
from poetry.utils.env import NullEnv
from poetry.packages import ProjectPackage
fixtures_dir = Path(__file__).parent / "fixtures"
......@@ -127,21 +127,24 @@ def test_package_with_include(mocker):
assert "package_with_include/__init__.py" in names
def test_write_metadata_file_license_homepage_default(mocker):
# Preparation
mocked_poetry = mocker.Mock()
mocked_poetry.file.parent = Path(".")
mocked_poetry.package = ProjectPackage("pkg_name", "1.0.0")
mocked_file = mocker.Mock()
mocked_venv = mocker.Mock()
mocked_io = mocker.Mock()
# patch Module init inside Builder class
mocker.patch("poetry.masonry.builders.builder.Module")
w = WheelBuilder(mocked_poetry, mocked_venv, mocked_io)
# Action
w._write_metadata_file(mocked_file)
# Assertion
mocked_file.write.assert_any_call("Home-page: UNKNOWN\n")
mocked_file.write.assert_any_call("License: UNKNOWN\n")
def test_dist_info_file_permissions():
module_path = fixtures_dir / "complete"
WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO())
whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl"
with zipfile.ZipFile(str(whl)) as z:
assert (
z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr == 0o644 << 16
)
assert (
z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr
== 0o644 << 16
)
assert (
z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr == 0o644 << 16
)
assert (
z.getinfo("my_package-1.2.3.dist-info/entry_points.txt").external_attr
== 0o644 << 16
)
import pytest
from poetry.io.null_io import NullIO
from poetry.masonry.publishing.uploader import UploadError
from poetry.masonry.publishing.uploader import Uploader
from poetry.poetry import Poetry
from poetry.utils._compat import Path
fixtures_dir = Path(__file__).parent.parent.parent / "fixtures"
def project(name):
return fixtures_dir / name
def test_uploader_properly_handles_400_errors(http):
http.register_uri(http.POST, "https://foo.com", status=400, body="Bad request")
uploader = Uploader(Poetry.create(project("simple_project")), NullIO())
with pytest.raises(UploadError) as e:
uploader.upload("https://foo.com")
assert "HTTP Error 400: Bad Request" == str(e.value)
def test_uploader_properly_handles_403_errors(http):
http.register_uri(http.POST, "https://foo.com", status=403, body="Unauthorized")
uploader = Uploader(Poetry.create(project("simple_project")), NullIO())
with pytest.raises(UploadError) as e:
uploader.upload("https://foo.com")
assert "HTTP Error 403: Forbidden" == str(e.value)
def test_uploader_registers_for_appropriate_400_errors(mocker, http):
register = mocker.patch("poetry.masonry.publishing.uploader.Uploader._register")
http.register_uri(
http.POST, "https://foo.com", status=400, body="No package was ever registered"
)
uploader = Uploader(Poetry.create(project("simple_project")), NullIO())
with pytest.raises(UploadError):
uploader.upload("https://foo.com")
assert 1 == register.call_count
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import tarfile
import zipfile
from contextlib import contextmanager
from poetry import __version__
from poetry.masonry import api
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils.helpers import temporary_directory
......@@ -53,3 +59,69 @@ def test_build_sdist():
namelist = tar.getnames()
assert "my-package-1.2.3/LICENSE" in namelist
def test_prepare_metadata_for_build_wheel():
entry_points = """\
[console_scripts]
extra-script=my_package.extra:main[time]
my-2nd-script=my_package:main2
my-script=my_package:main
"""
wheel_data = """\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: true
Tag: py3-none-any
""".format(
__version__
)
metadata = """\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://poetry.eustace.io/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); extra == "time"
Project-URL: Documentation, https://poetry.eustace.io/docs
Project-URL: Repository, https://github.com/sdispater/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")):
dirname = api.prepare_metadata_for_build_wheel(tmp_dir)
assert "my_package-1.2.3.dist-info" == dirname
dist_info = Path(tmp_dir, dirname)
assert (dist_info / "entry_points.txt").exists()
assert (dist_info / "WHEEL").exists()
assert (dist_info / "METADATA").exists()
with (dist_info / "entry_points.txt").open() as f:
assert entry_points == decode(f.read())
with (dist_info / "WHEEL").open() as f:
assert wheel_data == decode(f.read())
with (dist_info / "METADATA").open() as f:
assert metadata == decode(f.read())
from poetry.packages.vcs_dependency import VCSDependency
def test_to_pep_508():
dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git"
)
expected = "poetry @ git+https://github.com/sdispater/poetry.git@master"
assert expected == dependency.to_pep_508()
def test_to_pep_508_with_extras():
dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git"
)
dependency.extras.append("foo")
expected = "poetry[foo] @ git+https://github.com/sdispater/poetry.git@master"
assert expected == dependency.to_pep_508()
from setuptools import setup
tests_require = ["pytest"]
setup(
name="extras_require_with_vars",
version="0.0.1",
description="test setup_reader.py",
install_requires=[],
extras_require={"test": tests_require},
)
......@@ -149,3 +149,20 @@ def test_setup_reader_read_setup_call_in_main(setup):
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_read_extras_require_with_variables(setup):
result = SetupReader.read_from_directory(setup("extras_require_with_vars"))
expected_name = "extras_require_with_vars"
expected_version = "0.0.1"
expected_install_requires = []
expected_extras_require = {"test": ["pytest"]}
expected_python_requires = None
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
......@@ -423,3 +423,18 @@ def test_validate(marker_string, environment, expected):
m = parse_marker(marker_string)
assert m.validate(environment) is expected
@pytest.mark.parametrize(
"marker, env",
[
(
'platform_release >= "9.0" and platform_release < "11.0"',
{"platform_release": "10.0"},
)
],
)
def test_parse_version_like_markers(marker, env):
m = parse_marker(marker)
assert m.validate(env)
......@@ -6,5 +6,5 @@ envlist = py27, py34, py35, py36, py37
whitelist_externals = poetry
skip_install = true
commands =
poetry install -v
poetry install -vvv
poetry run pytest tests/
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