Commit 978d435b by Randy Döring Committed by GitHub

sources: allow to configure the priority of PyPI (#7801)

Add a warning that PyPI will be disabled automatically in a future version of Poetry if there is at least one custom source configured with another priority than `explicit` and that it should be configured explicitly with a certain priority for forward compatibility.
parent 605823e4
...@@ -779,9 +779,12 @@ For example, to add the `pypi-test` source, you can run: ...@@ -779,9 +779,12 @@ For example, to add the `pypi-test` source, you can run:
poetry source add pypi-test https://test.pypi.org/simple/ poetry source add pypi-test https://test.pypi.org/simple/
``` ```
{{% note %}} You cannot use the name `pypi` for a custom repository as it is reserved for use by
You cannot use the name `pypi` as it is reserved for use by the default PyPI source. the default PyPI source. However, you can set the priority of PyPI:
{{% /note %}}
```bash
poetry source add --priority=explicit pypi
```
#### Options #### Options
...@@ -808,7 +811,8 @@ poetry source show pypi-test ...@@ -808,7 +811,8 @@ poetry source show pypi-test
``` ```
{{% note %}} {{% note %}}
This command will only show sources configured via the `pyproject.toml` and does not include PyPI. This command will only show sources configured via the `pyproject.toml`
and does not include the implicit default PyPI.
{{% /note %}} {{% /note %}}
### source remove ### source remove
......
...@@ -128,7 +128,7 @@ If `priority` is undefined, the source is considered a primary source that takes ...@@ -128,7 +128,7 @@ If `priority` is undefined, the source is considered a primary source that takes
Package sources are considered in the following order: Package sources are considered in the following order:
1. [default source](#default-package-source), 1. [default source](#default-package-source),
2. primary sources, 2. primary sources,
3. PyPI (unless disabled by another default source), 3. implicit PyPI (unless disabled by another [default source](#default-package-source) or configured explicitly),
4. [secondary sources](#secondary-package-sources), 4. [secondary sources](#secondary-package-sources),
[Explicit sources](#explicit-package-sources) are considered only for packages that explicitly [indicate their source](#package-source-constraint). [Explicit sources](#explicit-package-sources) are considered only for packages that explicitly [indicate their source](#package-source-constraint).
...@@ -137,19 +137,17 @@ Within each priority class, package sources are considered in order of appearanc ...@@ -137,19 +137,17 @@ Within each priority class, package sources are considered in order of appearanc
{{% note %}} {{% note %}}
If you prefer to disable [PyPI](https://pypi.org) completely, you may choose to set one of your package sources to be the [default](#default-package-source). If you want to change the priority of [PyPI](https://pypi.org), you can set it explicitly, e.g.
If you prefer to specify a package source for a specific dependency, see [Secondary Package Sources](#secondary-package-sources). ```bash
poetry source add --priority=primary PyPI
{{% /note %}} ```
{{% warning %}}
If you do not want any of the custom sources to take precedence over [PyPI](https://pypi.org), If you prefer to disable PyPI completely,
you must declare **all** package sources to be [secondary](#secondary-package-sources). you may choose to set one of your package sources to be the [default](#default-package-source)
or configure PyPI as [explicit source](#explicit-package-sources).
{{% /warning %}} {{% /note %}}
#### Default Package Source #### Default Package Source
...@@ -164,6 +162,21 @@ poetry source add --priority=default foo https://foo.bar/simple/ ...@@ -164,6 +162,21 @@ poetry source add --priority=default foo https://foo.bar/simple/
{{% warning %}} {{% warning %}}
In a future version of Poetry, PyPI will be disabled automatically
if there is at least one custom source configured with another priority than `explicit`.
If you are using custom sources in addition to PyPI, you should configure PyPI explicitly
with a certain priority, e.g.
```bash
poetry source add --priority=primary PyPI
```
This way, the priority of PyPI can be set in a fine-granular way.
{{% /warning %}}
{{% warning %}}
Configuring a custom package source as default, will effectively disable [PyPI](https://pypi.org) Configuring a custom package source as default, will effectively disable [PyPI](https://pypi.org)
as a package source for your project. as a package source for your project.
......
...@@ -9,7 +9,7 @@ from poetry.repositories.repository_pool import Priority ...@@ -9,7 +9,7 @@ from poetry.repositories.repository_pool import Priority
@dataclasses.dataclass(order=True, eq=True) @dataclasses.dataclass(order=True, eq=True)
class Source: class Source:
name: str name: str
url: str url: str = ""
default: dataclasses.InitVar[bool] = False default: dataclasses.InitVar[bool] = False
secondary: dataclasses.InitVar[bool] = False secondary: dataclasses.InitVar[bool] = False
priority: Priority = ( priority: Priority = (
...@@ -38,6 +38,8 @@ class Source: ...@@ -38,6 +38,8 @@ class Source:
return dataclasses.asdict( return dataclasses.asdict(
self, self,
dict_factory=lambda x: { dict_factory=lambda x: {
k: v if not isinstance(v, Priority) else v.name.lower() for (k, v) in x k: v if not isinstance(v, Priority) else v.name.lower()
for (k, v) in x
if v
}, },
) )
...@@ -19,7 +19,14 @@ class SourceAddCommand(Command): ...@@ -19,7 +19,14 @@ class SourceAddCommand(Command):
"name", "name",
"Source repository name.", "Source repository name.",
), ),
argument("url", "Source repository url."), argument(
"url",
(
"Source repository URL."
" Required, except for PyPI, for which it is not allowed."
),
optional=True,
),
] ]
options = [ options = [
...@@ -57,10 +64,24 @@ class SourceAddCommand(Command): ...@@ -57,10 +64,24 @@ class SourceAddCommand(Command):
from poetry.utils.source import source_to_table from poetry.utils.source import source_to_table
name: str = self.argument("name") name: str = self.argument("name")
lower_name = name.lower()
url: str = self.argument("url") url: str = self.argument("url")
is_default: bool = self.option("default", False) is_default: bool = self.option("default", False)
is_secondary: bool = self.option("secondary", False) is_secondary: bool = self.option("secondary", False)
priority: Priority | None = self.option("priority", None) priority_str: str | None = self.option("priority", None)
if lower_name == "pypi":
name = "PyPI"
if url:
self.line_error(
"<error>The URL of PyPI is fixed and cannot be set.</error>"
)
return 1
elif not url:
self.line_error(
"<error>A custom source cannot be added without a URL.</error>"
)
return 1
if is_default and is_secondary: if is_default and is_secondary:
self.line_error( self.line_error(
...@@ -70,7 +91,7 @@ class SourceAddCommand(Command): ...@@ -70,7 +91,7 @@ class SourceAddCommand(Command):
return 1 return 1
if is_default or is_secondary: if is_default or is_secondary:
if priority is not None: if priority_str is not None:
self.line_error( self.line_error(
"<error>Priority was passed through both --priority and a" "<error>Priority was passed through both --priority and a"
" deprecated flag (--default or --secondary). Please only provide" " deprecated flag (--default or --secondary). Please only provide"
...@@ -88,26 +109,17 @@ class SourceAddCommand(Command): ...@@ -88,26 +109,17 @@ class SourceAddCommand(Command):
priority = Priority.DEFAULT priority = Priority.DEFAULT
elif is_secondary: elif is_secondary:
priority = Priority.SECONDARY priority = Priority.SECONDARY
elif priority is None: elif priority_str is None:
priority = Priority.PRIMARY priority = Priority.PRIMARY
else:
new_source = Source(name=name, url=url, priority=priority) priority = Priority[priority_str.upper()]
existing_sources = self.poetry.get_sources()
sources = AoT([]) sources = AoT([])
new_source = Source(name=name, url=url, priority=priority)
is_new_source = True is_new_source = True
for source in existing_sources:
if source == new_source: for source in self.poetry.get_sources():
self.line( if source.priority is Priority.DEFAULT and priority is Priority.DEFAULT:
f"Source with name <c1>{name}</c1> already exists. Skipping"
" addition."
)
return 0
elif (
source.priority is Priority.DEFAULT
and new_source.priority is Priority.DEFAULT
):
self.line_error( self.line_error(
f"<error>Source with name <c1>{source.name}</c1> is already set to" f"<error>Source with name <c1>{source.name}</c1> is already set to"
" default. Only one default source can be configured at a" " default. Only one default source can be configured at a"
...@@ -115,7 +127,7 @@ class SourceAddCommand(Command): ...@@ -115,7 +127,7 @@ class SourceAddCommand(Command):
) )
return 1 return 1
if source.name == name: if source.name.lower() == lower_name:
source = new_source source = new_source
is_new_source = False is_new_source = False
......
...@@ -21,12 +21,13 @@ class SourceRemoveCommand(Command): ...@@ -21,12 +21,13 @@ class SourceRemoveCommand(Command):
from poetry.utils.source import source_to_table from poetry.utils.source import source_to_table
name = self.argument("name") name = self.argument("name")
lower_name = name.lower()
sources = AoT([]) sources = AoT([])
removed = False removed = False
for source in self.poetry.get_sources(): for source in self.poetry.get_sources():
if source.name == name: if source.name.lower() == lower_name:
self.line(f"Removing source with name <c1>{source.name}</c1>.") self.line(f"Removing source with name <c1>{source.name}</c1>.")
removed = True removed = True
continue continue
......
...@@ -27,12 +27,13 @@ class SourceShowCommand(Command): ...@@ -27,12 +27,13 @@ class SourceShowCommand(Command):
def handle(self) -> int: def handle(self) -> int:
sources = self.poetry.get_sources() sources = self.poetry.get_sources()
names = self.argument("source") names = self.argument("source")
lower_names = [name.lower() for name in names]
if not sources: if not sources:
self.line("No sources configured for this project.") self.line("No sources configured for this project.")
return 0 return 0
if names and not any(s.name in names for s in sources): if names and not any(s.name.lower() in lower_names for s in sources):
self.line_error( self.line_error(
f"No source found with name(s): {', '.join(names)}", f"No source found with name(s): {', '.join(names)}",
style="error", style="error",
...@@ -40,18 +41,14 @@ class SourceShowCommand(Command): ...@@ -40,18 +41,14 @@ class SourceShowCommand(Command):
return 1 return 1
for source in sources: for source in sources:
if names and source.name not in names: if names and source.name.lower() not in lower_names:
continue continue
table = self.table(style="compact") table = self.table(style="compact")
rows: Rows = [ rows: Rows = [["<info>name</>", f" : <c1>{source.name}</>"]]
["<info>name</>", f" : <c1>{source.name}</>"], if source.url:
["<info>url</>", f" : {source.url}"], rows.append(["<info>url</>", f" : {source.url}"])
[ rows.append(["<info>priority</>", f" : {source.priority.name.lower()}"])
"<info>priority</>",
f" : {source.priority.name.lower()}",
],
]
table.add_rows(rows) table.add_rows(rows)
table.render() table.render()
self.line("") self.line("")
......
...@@ -15,6 +15,7 @@ from poetry.core.packages.dependency_group import MAIN_GROUP ...@@ -15,6 +15,7 @@ from poetry.core.packages.dependency_group import MAIN_GROUP
from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.project_package import ProjectPackage
from poetry.config.config import Config from poetry.config.config import Config
from poetry.exceptions import PoetryException
from poetry.json import validate_object from poetry.json import validate_object
from poetry.packages.locker import Locker from poetry.packages.locker import Locker
from poetry.plugins.plugin import Plugin from poetry.plugins.plugin import Plugin
...@@ -32,7 +33,7 @@ if TYPE_CHECKING: ...@@ -32,7 +33,7 @@ if TYPE_CHECKING:
from tomlkit.toml_document import TOMLDocument from tomlkit.toml_document import TOMLDocument
from poetry.repositories import RepositoryPool from poetry.repositories import RepositoryPool
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.http_repository import HTTPRepository
from poetry.utils.dependency_specification import DependencySpec from poetry.utils.dependency_specification import DependencySpec
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -134,6 +135,7 @@ class Factory(BaseFactory): ...@@ -134,6 +135,7 @@ class Factory(BaseFactory):
pool = RepositoryPool() pool = RepositoryPool()
explicit_pypi = False
for source in sources: for source in sources:
repository = cls.create_package_source( repository = cls.create_package_source(
source, auth_config, disable_cache=disable_cache source, auth_config, disable_cache=disable_cache
...@@ -163,21 +165,42 @@ class Factory(BaseFactory): ...@@ -163,21 +165,42 @@ class Factory(BaseFactory):
io.write_line(message) io.write_line(message)
pool.add_repository(repository, priority=priority) pool.add_repository(repository, priority=priority)
if repository.name.lower() == "pypi":
explicit_pypi = True
# Only add PyPI if no default repository is configured # Only add PyPI if no default repository is configured
if pool.has_default(): if not explicit_pypi:
if io.is_debug(): if pool.has_default():
io.write_line("Deactivating the PyPI repository") if io.is_debug():
else: io.write_line("Deactivating the PyPI repository")
from poetry.repositories.pypi_repository import PyPiRepository
if pool.has_primary_repositories():
pypi_priority = Priority.SECONDARY
else: else:
pypi_priority = Priority.DEFAULT from poetry.repositories.pypi_repository import PyPiRepository
if pool.repositories:
io.write_error_line(
"<warning>"
"Warning: In a future version of Poetry, PyPI will be disabled"
" automatically if at least one custom source is configured"
" with another priority than 'explicit'. In order to avoid"
" a breaking change and make your pyproject.toml forward"
" compatible, add PyPI explicitly via 'poetry source add pypi'."
" By the way, this has the advantage that you can set the"
" priority of PyPI as with any other source."
"</warning>"
)
if pool.has_primary_repositories():
pypi_priority = Priority.SECONDARY
else:
pypi_priority = Priority.DEFAULT
pool.add_repository( pool.add_repository(
PyPiRepository(disable_cache=disable_cache), priority=pypi_priority PyPiRepository(disable_cache=disable_cache), priority=pypi_priority
)
if not pool.repositories:
raise PoetryException(
"At least one source must not be configured as 'explicit'."
) )
return pool return pool
...@@ -185,18 +208,28 @@ class Factory(BaseFactory): ...@@ -185,18 +208,28 @@ class Factory(BaseFactory):
@classmethod @classmethod
def create_package_source( def create_package_source(
cls, source: dict[str, str], auth_config: Config, disable_cache: bool = False cls, source: dict[str, str], auth_config: Config, disable_cache: bool = False
) -> LegacyRepository: ) -> HTTPRepository:
from poetry.repositories.exceptions import InvalidSourceError
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.repositories.single_page_repository import SinglePageRepository from poetry.repositories.single_page_repository import SinglePageRepository
if "url" not in source: try:
raise RuntimeError("Unsupported source specified") name = source["name"]
except KeyError:
raise InvalidSourceError("Missing [name] in source.")
if name.lower() == "pypi":
if "url" in source:
raise InvalidSourceError(
"The PyPI repository cannot be configured with a custom url."
)
return PyPiRepository(disable_cache=disable_cache)
# PyPI-like repository try:
if "name" not in source: url = source["url"]
raise RuntimeError("Missing [name] in source.") except KeyError:
name = source["name"] raise InvalidSourceError(f"Missing [url] in source {name!r}.")
url = source["url"]
repository_class = LegacyRepository repository_class = LegacyRepository
......
...@@ -20,8 +20,7 @@ ...@@ -20,8 +20,7 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"name", "name"
"url"
], ],
"properties": { "properties": {
"name": { "name": {
......
...@@ -7,3 +7,7 @@ class RepositoryError(Exception): ...@@ -7,3 +7,7 @@ class RepositoryError(Exception):
class PackageNotFound(Exception): class PackageNotFound(Exception):
pass pass
class InvalidSourceError(Exception):
pass
...@@ -58,6 +58,16 @@ def source_explicit() -> Source: ...@@ -58,6 +58,16 @@ def source_explicit() -> Source:
) )
@pytest.fixture
def source_pypi() -> Source:
return Source(name="PyPI")
@pytest.fixture
def source_pypi_explicit() -> Source:
return Source(name="PyPI", priority=Priority.EXPLICIT)
_existing_source = Source(name="existing", url="https://existing.com") _existing_source = Source(name="existing", url="https://existing.com")
...@@ -88,6 +98,20 @@ url = "{_existing_source.url}" ...@@ -88,6 +98,20 @@ url = "{_existing_source.url}"
""" """
PYPROJECT_WITH_PYPI = f"""{PYPROJECT_WITHOUT_SOURCES}
[[tool.poetry.source]]
name = "PyPI"
"""
PYPROJECT_WITH_PYPI_AND_OTHER = f"""{PYPROJECT_WITH_SOURCES}
[[tool.poetry.source]]
name = "PyPI"
"""
@pytest.fixture @pytest.fixture
def poetry_without_source(project_factory: ProjectFactory) -> Poetry: def poetry_without_source(project_factory: ProjectFactory) -> Poetry:
return project_factory(pyproject_content=PYPROJECT_WITHOUT_SOURCES) return project_factory(pyproject_content=PYPROJECT_WITHOUT_SOURCES)
...@@ -99,6 +123,16 @@ def poetry_with_source(project_factory: ProjectFactory) -> Poetry: ...@@ -99,6 +123,16 @@ def poetry_with_source(project_factory: ProjectFactory) -> Poetry:
@pytest.fixture @pytest.fixture
def poetry_with_pypi(project_factory: ProjectFactory) -> Poetry:
return project_factory(pyproject_content=PYPROJECT_WITH_PYPI)
@pytest.fixture
def poetry_with_pypi_and_other(project_factory: ProjectFactory) -> Poetry:
return project_factory(pyproject_content=PYPROJECT_WITH_PYPI_AND_OTHER)
@pytest.fixture
def add_multiple_sources( def add_multiple_sources(
command_tester_factory: CommandTesterFactory, command_tester_factory: CommandTesterFactory,
poetry_with_source: Poetry, poetry_with_source: Poetry,
......
...@@ -166,16 +166,47 @@ def test_source_add_error_priority_and_deprecated_legacy(tester: CommandTester) ...@@ -166,16 +166,47 @@ def test_source_add_error_priority_and_deprecated_legacy(tester: CommandTester)
assert tester.status_code == 1 assert tester.status_code == 1
def test_source_add_error_no_url(tester: CommandTester) -> None:
tester.execute("foo")
assert (
tester.io.fetch_error().strip()
== "A custom source cannot be added without a URL."
)
assert tester.status_code == 1
def test_source_add_error_pypi(tester: CommandTester) -> None: def test_source_add_error_pypi(tester: CommandTester) -> None:
tester.execute("pypi https://test.pypi.org/simple/") tester.execute("pypi https://test.pypi.org/simple/")
assert ( assert (
tester.io.fetch_error().strip() tester.io.fetch_error().strip() == "The URL of PyPI is fixed and cannot be set."
== "Failed to validate addition of pypi: The name [pypi] is reserved for"
" repositories"
) )
assert tester.status_code == 1 assert tester.status_code == 1
@pytest.mark.parametrize("name", ["pypi", "PyPI"])
def test_source_add_pypi(
name: str,
tester: CommandTester,
source_existing: Source,
source_pypi: Source,
poetry_with_source: Poetry,
) -> None:
tester.execute(name)
assert_source_added(tester, poetry_with_source, source_existing, source_pypi)
def test_source_add_pypi_explicit(
tester: CommandTester,
source_existing: Source,
source_pypi_explicit: Source,
poetry_with_source: Poetry,
) -> None:
tester.execute("--priority=explicit PyPI")
assert_source_added(
tester, poetry_with_source, source_existing, source_pypi_explicit
)
def test_source_add_existing_legacy( def test_source_add_existing_legacy(
tester: CommandTester, source_existing: Source, poetry_with_source: Poetry tester: CommandTester, source_existing: Source, poetry_with_source: Poetry
) -> None: ) -> None:
...@@ -202,29 +233,41 @@ def test_source_add_existing_legacy( ...@@ -202,29 +233,41 @@ def test_source_add_existing_legacy(
assert sources[0] == expected_source assert sources[0] == expected_source
def test_source_add_existing_no_change( @pytest.mark.parametrize("modifier", ["lower", "upper"])
tester: CommandTester, source_existing: Source, poetry_with_source: Poetry def test_source_add_existing_no_change_except_case_of_name(
modifier: str,
tester: CommandTester,
source_existing: Source,
poetry_with_source: Poetry,
) -> None: ) -> None:
tester.execute(f"--priority=primary {source_existing.name} {source_existing.url}") name = getattr(source_existing.name, modifier)()
tester.execute(f"--priority=primary {name} {source_existing.url}")
assert ( assert (
tester.io.fetch_output().strip() tester.io.fetch_output().strip()
== f"Source with name {source_existing.name} already exists. Skipping addition." == f"Source with name {name} already exists. Updating."
) )
poetry_with_source.pyproject.reload() poetry_with_source.pyproject.reload()
sources = poetry_with_source.get_sources() sources = poetry_with_source.get_sources()
assert len(sources) == 1 assert len(sources) == 1
assert sources[0] == source_existing assert sources[0].name == getattr(source_existing.name, modifier)()
assert sources[0].url == source_existing.url
assert sources[0].priority == source_existing.priority
@pytest.mark.parametrize("modifier", ["lower", "upper"])
def test_source_add_existing_updating( def test_source_add_existing_updating(
tester: CommandTester, source_existing: Source, poetry_with_source: Poetry modifier: str,
tester: CommandTester,
source_existing: Source,
poetry_with_source: Poetry,
) -> None: ) -> None:
tester.execute(f"--priority=default {source_existing.name} {source_existing.url}") name = getattr(source_existing.name, modifier)()
tester.execute(f"--priority=default {name} {source_existing.url}")
assert ( assert (
tester.io.fetch_output().strip() tester.io.fetch_output().strip()
== f"Source with name {source_existing.name} already exists. Updating." == f"Source with name {name} already exists. Updating."
) )
poetry_with_source.pyproject.reload() poetry_with_source.pyproject.reload()
...@@ -233,12 +276,14 @@ def test_source_add_existing_updating( ...@@ -233,12 +276,14 @@ def test_source_add_existing_updating(
assert len(sources) == 1 assert len(sources) == 1
assert sources[0] != source_existing assert sources[0] != source_existing
expected_source = Source( expected_source = Source(
name=source_existing.name, url=source_existing.url, priority=Priority.DEFAULT name=name, url=source_existing.url, priority=Priority.DEFAULT
) )
assert sources[0] == expected_source assert sources[0] == expected_source
@pytest.mark.parametrize("modifier", ["lower", "upper"])
def test_source_add_existing_fails_due_to_other_default( def test_source_add_existing_fails_due_to_other_default(
modifier: str,
tester: CommandTester, tester: CommandTester,
source_existing: Source, source_existing: Source,
source_default: Source, source_default: Source,
...@@ -247,7 +292,8 @@ def test_source_add_existing_fails_due_to_other_default( ...@@ -247,7 +292,8 @@ def test_source_add_existing_fails_due_to_other_default(
tester.execute(f"--priority=default {source_default.name} {source_default.url}") tester.execute(f"--priority=default {source_default.name} {source_default.url}")
tester.io.fetch_output() tester.io.fetch_output()
tester.execute(f"--priority=default {source_existing.name} {source_existing.url}") name = getattr(source_existing.name, modifier)()
tester.execute(f"--priority=default {name} {source_existing.url}")
assert ( assert (
tester.io.fetch_error().strip() tester.io.fetch_error().strip()
......
...@@ -22,14 +22,32 @@ def tester( ...@@ -22,14 +22,32 @@ def tester(
return command_tester_factory("source remove", poetry=poetry_with_source) return command_tester_factory("source remove", poetry=poetry_with_source)
@pytest.fixture
def tester_pypi(
command_tester_factory: CommandTesterFactory,
poetry_with_pypi: Poetry,
) -> CommandTester:
return command_tester_factory("source remove", poetry=poetry_with_pypi)
@pytest.fixture
def tester_pypi_and_other(
command_tester_factory: CommandTesterFactory,
poetry_with_pypi_and_other: Poetry,
) -> CommandTester:
return command_tester_factory("source remove", poetry=poetry_with_pypi_and_other)
@pytest.mark.parametrize("modifier", ["lower", "upper"])
def test_source_remove_simple( def test_source_remove_simple(
tester: CommandTester, tester: CommandTester,
poetry_with_source: Poetry, poetry_with_source: Poetry,
source_existing: Source, source_existing: Source,
source_one: Source, source_one: Source,
source_two: Source, source_two: Source,
modifier: str,
) -> None: ) -> None:
tester.execute(f"{source_existing.name}") tester.execute(getattr(f"{source_existing.name}", modifier)())
assert ( assert (
tester.io.fetch_output().strip() tester.io.fetch_output().strip()
== f"Removing source with name {source_existing.name}." == f"Removing source with name {source_existing.name}."
...@@ -42,7 +60,42 @@ def test_source_remove_simple( ...@@ -42,7 +60,42 @@ def test_source_remove_simple(
assert tester.status_code == 0 assert tester.status_code == 0
def test_source_remove_error(tester: CommandTester) -> None: @pytest.mark.parametrize("name", ["pypi", "PyPI"])
tester.execute("error") def test_source_remove_pypi(
assert tester.io.fetch_error().strip() == "Source with name error was not found." name: str, tester_pypi: CommandTester, poetry_with_pypi: Poetry
) -> None:
tester_pypi.execute(name)
assert tester_pypi.io.fetch_output().strip() == "Removing source with name PyPI."
poetry_with_pypi.pyproject.reload()
sources = poetry_with_pypi.get_sources()
assert sources == []
assert tester_pypi.status_code == 0
@pytest.mark.parametrize("name", ["pypi", "PyPI"])
def test_source_remove_pypi_and_other(
name: str,
tester_pypi_and_other: CommandTester,
poetry_with_pypi_and_other: Poetry,
source_existing: Source,
) -> None:
tester_pypi_and_other.execute(name)
assert (
tester_pypi_and_other.io.fetch_output().strip()
== "Removing source with name PyPI."
)
poetry_with_pypi_and_other.pyproject.reload()
sources = poetry_with_pypi_and_other.get_sources()
assert sources == [source_existing]
assert tester_pypi_and_other.status_code == 0
@pytest.mark.parametrize("name", ["foo", "pypi", "PyPI"])
def test_source_remove_error(name: str, tester: CommandTester) -> None:
tester.execute(name)
assert tester.io.fetch_error().strip() == f"Source with name {name} was not found."
assert tester.status_code == 1 assert tester.status_code == 1
...@@ -31,6 +31,22 @@ def tester_no_sources( ...@@ -31,6 +31,22 @@ def tester_no_sources(
@pytest.fixture @pytest.fixture
def tester_pypi(
command_tester_factory: CommandTesterFactory,
poetry_with_pypi: Poetry,
) -> CommandTester:
return command_tester_factory("source show", poetry=poetry_with_pypi)
@pytest.fixture
def tester_pypi_and_other(
command_tester_factory: CommandTesterFactory,
poetry_with_pypi_and_other: Poetry,
) -> CommandTester:
return command_tester_factory("source show", poetry=poetry_with_pypi_and_other)
@pytest.fixture
def tester_all_types( def tester_all_types(
command_tester_factory: CommandTesterFactory, command_tester_factory: CommandTesterFactory,
poetry_with_source: Poetry, poetry_with_source: Poetry,
...@@ -61,8 +77,11 @@ priority : primary ...@@ -61,8 +77,11 @@ priority : primary
assert tester.status_code == 0 assert tester.status_code == 0
def test_source_show_one(tester: CommandTester, source_one: Source) -> None: @pytest.mark.parametrize("modifier", ["lower", "upper"])
tester.execute(f"{source_one.name}") def test_source_show_one(
tester: CommandTester, source_one: Source, modifier: str
) -> None:
tester.execute(getattr(f"{source_one.name}", modifier)())
expected = """\ expected = """\
name : one name : one
...@@ -75,10 +94,11 @@ priority : primary ...@@ -75,10 +94,11 @@ priority : primary
assert tester.status_code == 0 assert tester.status_code == 0
@pytest.mark.parametrize("modifier", ["lower", "upper"])
def test_source_show_two( def test_source_show_two(
tester: CommandTester, source_one: Source, source_two: Source tester: CommandTester, source_one: Source, source_two: Source, modifier: str
) -> None: ) -> None:
tester.execute(f"{source_one.name} {source_two.name}") tester.execute(getattr(f"{source_one.name} {source_two.name}", modifier)())
expected = """\ expected = """\
name : one name : one
...@@ -121,6 +141,35 @@ priority : {source.name} ...@@ -121,6 +141,35 @@ priority : {source.name}
assert tester_all_types.status_code == 0 assert tester_all_types.status_code == 0
def test_source_show_pypi(tester_pypi: CommandTester) -> None:
tester_pypi.execute("")
expected = """\
name : PyPI
priority : primary
""".splitlines()
assert [
line.strip() for line in tester_pypi.io.fetch_output().strip().splitlines()
] == expected
assert tester_pypi.status_code == 0
def test_source_show_pypi_and_other(tester_pypi_and_other: CommandTester) -> None:
tester_pypi_and_other.execute("")
expected = """\
name : existing
url : https://existing.com
priority : primary
name : PyPI
priority : primary
""".splitlines()
assert [
line.strip()
for line in tester_pypi_and_other.io.fetch_output().strip().splitlines()
] == expected
assert tester_pypi_and_other.status_code == 0
def test_source_show_no_sources(tester_no_sources: CommandTester) -> None: def test_source_show_no_sources(tester_no_sources: CommandTester) -> None:
tester_no_sources.execute("error") tester_no_sources.execute("error")
assert ( assert (
......
...@@ -22,11 +22,11 @@ classifiers = [ ...@@ -22,11 +22,11 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
cleo = "^0.6" cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] } requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" } pathlib2 = { version = "^2.2", python = "~3.6" }
orator = { version = "^0.9", optional = true } orator = { version = "^0.9", optional = true }
......
[tool.poetry]
name = "with-default-source-and-pypi"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~3.6" }
orator = { version = "^0.9", optional = true }
# File dependency
demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" }
# Dir dependency with setup.py
my-package = { path = "../project_with_setup/" }
# Dir dependency with pyproject.toml
simple-project = { path = "../simple_project/" }
[tool.poetry.extras]
db = [ "orator" ]
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.scripts]
my-script = "my_package:main"
[tool.poetry.plugins."blogtool.parsers"]
".rst" = "some_module::SomeClass"
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
priority = "default"
[[tool.poetry.source]]
name = "PyPI"
...@@ -22,11 +22,11 @@ classifiers = [ ...@@ -22,11 +22,11 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
cleo = "^0.6" cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] } requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" } pathlib2 = { version = "^2.2", python = "~3.6" }
orator = { version = "^0.9", optional = true } orator = { version = "^0.9", optional = true }
......
[tool.poetry]
name = "with-default-source-pypi"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~3.6" }
orator = { version = "^0.9", optional = true }
# File dependency
demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" }
# Dir dependency with setup.py
my-package = { path = "../project_with_setup/" }
# Dir dependency with pyproject.toml
simple-project = { path = "../simple_project/" }
[tool.poetry.extras]
db = [ "orator" ]
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.scripts]
my-script = "my_package:main"
[tool.poetry.plugins."blogtool.parsers"]
".rst" = "some_module::SomeClass"
[[tool.poetry.source]]
name = "PyPI"
priority = "default"
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Your Name <you@example.com>"
]
license = "MIT"
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
[[tool.poetry.source]]
name = "PyPI"
priority = "explicit"
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Your Name <you@example.com>"
]
license = "MIT"
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
[[tool.poetry.source]]
name = "explicit"
url = "https://explicit.com/simple/"
priority = "explicit"
[[tool.poetry.source]]
name = "PyPI"
priority = "explicit"
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Your Name <you@example.com>"
]
license = "MIT"
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
[[tool.poetry.source]]
name = "PyPI"
priority = "explicit"
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Your Name <you@example.com>"
]
license = "MIT"
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
priority = "secondary"
[[tool.poetry.source]]
name = "bar"
url = "https://bar.baz/simple/"
[[tool.poetry.source]]
name = "PyPI"
[[tool.poetry.source]]
name = "baz"
url = "https://baz.bar/simple/"
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
...@@ -22,11 +22,11 @@ classifiers = [ ...@@ -22,11 +22,11 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
cleo = "^0.6" cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] } requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" } pathlib2 = { version = "^2.2", python = "~3.6" }
orator = { version = "^0.9", optional = true } orator = { version = "^0.9", optional = true }
......
...@@ -22,11 +22,11 @@ classifiers = [ ...@@ -22,11 +22,11 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.6" python = "^3.6"
cleo = "^0.6" cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] } requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" } pathlib2 = { version = "^2.2", python = "~3.6" }
orator = { version = "^0.9", optional = true } orator = { version = "^0.9", optional = true }
......
...@@ -21,15 +21,6 @@ def test_pyproject_toml_valid() -> None: ...@@ -21,15 +21,6 @@ def test_pyproject_toml_valid() -> None:
assert Factory.validate(content) == {"errors": [], "warnings": []} assert Factory.validate(content) == {"errors": [], "warnings": []}
def test_pyproject_toml_invalid_url() -> None:
toml = TOMLFile(FIXTURE_DIR / "complete_invalid_url.toml").read()
content = toml["tool"]["poetry"]
assert Factory.validate(content) == {
"errors": ["[source.0] 'url' is a required property"],
"warnings": [],
}
def test_pyproject_toml_invalid_priority() -> None: def test_pyproject_toml_invalid_priority() -> None:
toml = TOMLFile(FIXTURE_DIR / "complete_invalid_priority.toml").read() toml = TOMLFile(FIXTURE_DIR / "complete_invalid_priority.toml").read()
content = toml["tool"]["poetry"] content = toml["tool"]["poetry"]
......
...@@ -9,8 +9,10 @@ from deepdiff import DeepDiff ...@@ -9,8 +9,10 @@ from deepdiff import DeepDiff
from packaging.utils import canonicalize_name from packaging.utils import canonicalize_name
from poetry.core.constraints.version import parse_constraint from poetry.core.constraints.version import parse_constraint
from poetry.exceptions import PoetryException
from poetry.factory import Factory from poetry.factory import Factory
from poetry.plugins.plugin import Plugin from poetry.plugins.plugin import Plugin
from poetry.repositories.exceptions import InvalidSourceError
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
from poetry.repositories.repository_pool import Priority from poetry.repositories.repository_pool import Priority
...@@ -22,6 +24,7 @@ if TYPE_CHECKING: ...@@ -22,6 +24,7 @@ if TYPE_CHECKING:
from cleo.io.io import IO from cleo.io.io import IO
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from poetry.config.config import Config
from poetry.poetry import Poetry from poetry.poetry import Poetry
from tests.types import FixtureDirGetter from tests.types import FixtureDirGetter
...@@ -231,6 +234,31 @@ def test_poetry_with_default_source( ...@@ -231,6 +234,31 @@ def test_poetry_with_default_source(
assert io.fetch_error() == "" assert io.fetch_error() == ""
def test_poetry_with_default_source_and_pypi(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
io = BufferedIO()
poetry = Factory().create_poetry(fixture_dir("with_default_source_and_pypi"), io=io)
assert len(poetry.pool.repositories) == 2
assert poetry.pool.has_repository("PyPI")
assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository)
assert poetry.pool.get_priority("PyPI") is Priority.PRIMARY
assert "Warning: Found deprecated key" not in io.fetch_error()
def test_poetry_with_default_source_pypi(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
io = BufferedIO()
poetry = Factory().create_poetry(fixture_dir("with_default_source_pypi"), io=io)
assert len(poetry.pool.repositories) == 1
assert poetry.pool.has_repository("PyPI")
assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository)
assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT
@pytest.mark.parametrize( @pytest.mark.parametrize(
"project", "project",
("with_non_default_source_implicit", "with_non_default_source_explicit"), ("with_non_default_source_implicit", "with_non_default_source_explicit"),
...@@ -238,7 +266,8 @@ def test_poetry_with_default_source( ...@@ -238,7 +266,8 @@ def test_poetry_with_default_source(
def test_poetry_with_non_default_source( def test_poetry_with_non_default_source(
project: str, fixture_dir: FixtureDirGetter, with_simple_keyring: None project: str, fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None: ) -> None:
poetry = Factory().create_poetry(fixture_dir(project)) io = BufferedIO()
poetry = Factory().create_poetry(fixture_dir(project), io=io)
assert not poetry.pool.has_default() assert not poetry.pool.has_default()
assert poetry.pool.has_repository("PyPI") assert poetry.pool.has_repository("PyPI")
...@@ -248,6 +277,8 @@ def test_poetry_with_non_default_source( ...@@ -248,6 +277,8 @@ def test_poetry_with_non_default_source(
assert poetry.pool.get_priority("foo") is Priority.PRIMARY assert poetry.pool.get_priority("foo") is Priority.PRIMARY
assert isinstance(poetry.pool.repository("foo"), LegacyRepository) assert isinstance(poetry.pool.repository("foo"), LegacyRepository)
assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo"} assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "foo"}
error = io.fetch_error()
assert "Warning: In a future version of Poetry, PyPI will be disabled" in error
def test_poetry_with_non_default_secondary_source_legacy( def test_poetry_with_non_default_secondary_source_legacy(
...@@ -347,6 +378,26 @@ def test_poetry_with_non_default_multiple_sources( ...@@ -347,6 +378,26 @@ def test_poetry_with_non_default_multiple_sources(
assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "bar", "foo"} assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "bar", "foo"}
def test_poetry_with_non_default_multiple_sources_pypi(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
io = BufferedIO()
poetry = Factory().create_poetry(
fixture_dir("with_non_default_multiple_sources_pypi"), io=io
)
assert len(poetry.pool.repositories) == 4
assert not poetry.pool.has_default()
assert poetry.pool.has_repository("PyPI")
assert isinstance(poetry.pool.repository("PyPI"), PyPiRepository)
assert poetry.pool.get_priority("PyPI") is Priority.PRIMARY
# PyPI must be between bar and baz!
expected = ["bar", "PyPI", "baz", "foo"]
assert [repo.name for repo in poetry.pool.repositories] == expected
error = io.fetch_error()
assert error == ""
def test_poetry_with_no_default_source(fixture_dir: FixtureDirGetter) -> None: def test_poetry_with_no_default_source(fixture_dir: FixtureDirGetter) -> None:
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
...@@ -371,6 +422,29 @@ def test_poetry_with_explicit_source( ...@@ -371,6 +422,29 @@ def test_poetry_with_explicit_source(
assert [repo.name for repo in poetry.pool.repositories] == ["PyPI"] assert [repo.name for repo in poetry.pool.repositories] == ["PyPI"]
def test_poetry_with_explicit_pypi_and_other(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
io = BufferedIO()
poetry = Factory().create_poetry(fixture_dir("with_explicit_pypi_and_other"), io=io)
assert len(poetry.pool.repositories) == 1
assert len(poetry.pool.all_repositories) == 2
error = io.fetch_error()
assert error == ""
@pytest.mark.parametrize(
"project", ["with_explicit_pypi_no_other", "with_explicit_pypi_and_other_explicit"]
)
def test_poetry_with_pypi_explicit_only(
project: str, fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
with pytest.raises(PoetryException) as e:
Factory().create_poetry(fixture_dir(project))
assert str(e.value) == "At least one source must not be configured as 'explicit'."
def test_poetry_with_two_default_sources_legacy( def test_poetry_with_two_default_sources_legacy(
fixture_dir: FixtureDirGetter, with_simple_keyring: None fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None: ) -> None:
...@@ -442,3 +516,27 @@ def test_create_poetry_with_plugins( ...@@ -442,3 +516,27 @@ def test_create_poetry_with_plugins(
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
assert poetry.package.readmes == ("README.md",) assert poetry.package.readmes == ("README.md",)
@pytest.mark.parametrize(
("source", "expected"),
[
({}, "Missing [name] in source."),
({"name": "foo"}, "Missing [url] in source 'foo'."),
(
{"name": "PyPI", "url": "https://example.com"},
"The PyPI repository cannot be configured with a custom url.",
),
],
)
def test_create_package_source_invalid(
source: dict[str, str],
expected: str,
config: Config,
fixture_dir: FixtureDirGetter,
) -> None:
with pytest.raises(InvalidSourceError) as e:
Factory.create_package_source(source, auth_config=config)
Factory().create_poetry(fixture_dir("with_source_pypi_url"))
assert str(e.value) == expected
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