Skip to content

Commit

Permalink
add: allow specification of PEP 508 dependencies
Browse files Browse the repository at this point in the history
With this change, users can now add dependencies using valid PEP 508
strings.
  • Loading branch information
abn committed May 7, 2022
1 parent b63c513 commit 49dda75
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 2 deletions.
52 changes: 51 additions & 1 deletion src/poetry/utils/dependency_specification.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import contextlib
import os
import re
import urllib.parse
Expand All @@ -9,6 +10,10 @@
from typing import Dict
from typing import List
from typing import Union
from typing import cast

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.vcs_dependency import VCSDependency

from poetry.puzzle.provider import Provider

Expand Down Expand Up @@ -53,7 +58,7 @@ def _parse_dependency_specification_url(

if url_parsed.scheme in ["http", "https"]:
package = Provider.get_package_from_url(requirement)
return {"name": package.name, "url": package.source_url}
return {"name": package.name, "url": cast(str, package.source_url)}

return None

Expand Down Expand Up @@ -131,12 +136,57 @@ def _parse_dependency_specification_simple(
return require


def dependency_to_specification(dependency: Dependency) -> DependencySpec:
specification: DependencySpec = {}

if dependency.is_vcs():
dependency = cast(VCSDependency, dependency)
specification[dependency.vcs] = cast(str, dependency.source_url)
if dependency.reference:
specification["rev"] = dependency.reference
elif dependency.is_file() or dependency.is_directory():
specification["path"] = cast(str, dependency.source_url)
elif dependency.is_url():
specification["url"] = cast(str, dependency.source_url)
elif dependency.pretty_constraint != "*" and not dependency.constraint.is_empty():
specification["version"] = dependency.pretty_constraint

if not dependency.marker.is_any():
specification["markers"] = str(dependency.marker)

if dependency.extras:
specification["extras"] = sorted(dependency.extras)

return specification


def pep508_to_dependency_specification(requirement: str) -> DependencySpec | None:
if " ; " not in requirement and re.search(r"@[\^~!=<>\d]", requirement):
# this is of the form package@<semver>, do not attempt to parse it
return None

with contextlib.suppress(ValueError):
dependency = Dependency.create_from_pep_508(requirement)
specification = dependency_to_specification(dependency)

if specification:
specification["name"] = dependency.name
return specification

return None


def parse_dependency_specification(
requirement: str, env: Env | None = None, cwd: Path | None = None
) -> DependencySpec:
requirement = requirement.strip()
cwd = cwd or Path.cwd()

specification = pep508_to_dependency_specification(requirement)

if specification is not None:
return specification

extras = []
extras_m = re.search(r"\[([\w\d,-_ ]+)\]$", requirement)
if extras_m:
Expand Down
48 changes: 47 additions & 1 deletion tests/utils/test_dependency_specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
("demo", {"name": "demo"}),
("demo@1.0.0", {"name": "demo", "version": "1.0.0"}),
("demo@^1.0.0", {"name": "demo", "version": "^1.0.0"}),
("demo@==1.0.0", {"name": "demo", "version": "==1.0.0"}),
("demo@!=1.0.0", {"name": "demo", "version": "!=1.0.0"}),
("demo@~1.0.0", {"name": "demo", "version": "~1.0.0"}),
("demo[a,b]@1.0.0", {"name": "demo", "version": "1.0.0", "extras": ["a", "b"]}),
("demo[a,b]", {"name": "demo", "extras": ["a", "b"]}),
("../demo", {"name": "demo", "path": "../demo"}),
Expand All @@ -46,6 +49,47 @@
"https://example.com/packages/demo-0.1.0.tar.gz",
{"name": "demo", "url": "https://example.com/packages/demo-0.1.0.tar.gz"},
),
# PEP 508 inputs
(
"poetry-core (>=1.0.7,<1.1.0)",
{"name": "poetry-core", "version": ">=1.0.7,<1.1.0"},
),
(
'requests [security,tests] >= 2.8.1, == 2.8.* ; python_version < "2.7"',
{
"name": "requests",
"markers": 'python_version < "2.7"',
"version": ">=2.8.1,<2.9.0",
"extras": ["security", "tests"],
},
),
("name (>=3,<4)", {"name": "name", "version": ">=3,<4"}),
(
"name@http://foo.com",
{"name": "name", "url": "http://foo.com"},
),
(
"name [fred,bar] @ http://foo.com ; python_version=='2.7'",
{
"name": "name",
"markers": 'python_version == "2.7"',
"url": "http://foo.com",
# This is commented out as there is a bug in
# Dependency.create_from_pep_508 that leads to incorrect
# URL Dependency creation.
# should be: "extras": ["fred", "bar"],
},
),
(
'cachecontrol[filecache] (>=0.12.9,<0.13.0); python_version >= "3.6" and'
' python_version < "4.0"',
{
"version": ">=0.12.9,<0.13.0",
"markers": 'python_version >= "3.6" and python_version < "4.0"',
"extras": ["filecache"],
"name": "cachecontrol",
},
),
],
)
def test_parse_dependency_specification(
Expand All @@ -60,4 +104,6 @@ def _mock(self: Path) -> bool:

mocker.patch("pathlib.Path.exists", _mock)

assert not DeepDiff(parse_dependency_specification(requirement), specification)
assert not DeepDiff(
parse_dependency_specification(requirement), specification, ignore_order=True
)

0 comments on commit 49dda75

Please sign in to comment.