Skip to content

Commit

Permalink
sources: deprecate default option and introduce possibility to disa…
Browse files Browse the repository at this point in the history
…ble the implicit default source PyPI explicitly
  • Loading branch information
radoering committed Mar 5, 2023
1 parent f27308f commit bbeae93
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 24 deletions.
15 changes: 14 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ You cannot use the name `pypi` as it is reserved for use by the default PyPI sou

#### Options

* `--default`: Set this source as the [default]({{< relref "repositories#disabling-the-pypi-repository" >}}) (disable PyPI).
* `--default`: Set this source as the default (**Deprecated**, use TODO to disable PyPI).
* `--secondary`: Set this source as a [secondary]({{< relref "repositories#install-dependencies-from-a-private-repository" >}}) source.

{{% note %}}
Expand Down Expand Up @@ -807,6 +807,19 @@ The `source remove` command removes a configured source from your `pyproject.tom
poetry source remove pypi-test
```

### source default

The `source default` command enables or disables the implicit default source PyPI for the project.

```bash
poetry source default --disable-pypi
```

### Options

* `--disable-pypi`: Disable PyPI as implicit default source.
* `--enable-pypi`: Enable PyPI as implicit default source.

## about

The `about` command displays global information about Poetry, including the current version and version of `poetry-core`.
Expand Down
44 changes: 34 additions & 10 deletions docs/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ This will generate the following configuration snippet in your
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
default = false
secondary = false
```

Expand All @@ -129,7 +128,9 @@ Any package source not marked as `secondary` will take precedence over [PyPI](ht

{{% 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 prefer to disable [PyPI](https://pypi.org) completely,
you can run `poetry source default --disable-pypi`.
If you disable PyPI, you have to configure at least one other source.

If you prefer to specify a package source for a specific dependency, see [Secondary Package Sources](#secondary-package-sources).

Expand All @@ -146,18 +147,39 @@ you must declare **all** package sources to be [secondary](#secondary-package-so

#### Default Package Source

By default, Poetry configures [PyPI](https://pypi.org) as the default package source for your
project. You can alter this behaviour and exclusively look up packages only from the configured
package sources by adding a **single** source with `default = true`.
By default, Poetry configures [PyPI](https://pypi.org) as the default package source for
your project. If you configure additional sources, these are preferred to PyPI unless
they are configured as secondary. Nevertheless, packages are looked up on PyPI.

If you want to exclusively look up packages only from the configured package sources,
you can disable the implicit default source PyPI:

```bash
poetry source add --default foo https://foo.bar/simple/
poetry source default --disable-pypi
```

This will generate the following entry in your `pyproject.toml` file:

```toml
[tool.poetry]
...
default-source-pypi = false
```

{{% warning %}}

Configuring a custom package source as default, will effectively disable [PyPI](https://pypi.org)
as a package source for your project.
Configuring a custom package source as default (`default = true`, **deprecated**),
will effectively disable [PyPI](https://pypi.org) as a package source for your project.
Whereas the implicit default source PyPI is looked up before secondary but after other
repositories, an explicit default source will be looked up before all other repositories.

{{% /warning %}}

{{% warning %}}

Strictly speaking, PyPI is only configured as default package source if there is at
least one other non-secondary source. Otherwise PyPI is configured as last secondary
source.

{{% /warning %}}

Expand Down Expand Up @@ -229,10 +251,12 @@ This will generate the following configuration snippet in your `pyproject.toml`
httpx = {version = "^0.22.0", source = "pypi"}
```

If you don't want to disable PyPI completely, see [Default Package Source](#default-package-source).

{{% warning %}}

If any source within a project is configured with `default = true`, The implicit `pypi` source will
be disabled and not used for any packages.
If any source within a project is configured with `default = true` (**deprecated**),
the implicit `pypi` source will be disabled and not used for any packages.

{{% /warning %}}

Expand Down
1 change: 1 addition & 0 deletions src/poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def _load() -> Command:
"self show plugins",
# Source commands
"source add",
"source default",
"source remove",
"source show",
]
Expand Down
3 changes: 1 addition & 2 deletions src/poetry/console/commands/source/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from cleo.helpers import argument
from cleo.helpers import option
from cleo.io.null_io import NullIO
from tomlkit.items import AoT

from poetry.config.source import Source
Expand Down Expand Up @@ -85,7 +84,7 @@ def handle(self) -> int:

# ensure new source is valid. eg: invalid name etc.
try:
pool = Factory.create_pool(self.poetry.config, sources, NullIO())
pool = Factory.create_pool(self.poetry.config, sources, self._io)
pool.repository(name)
except ValueError as e:
self.line_error(
Expand Down
37 changes: 37 additions & 0 deletions src/poetry/console/commands/source/default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from cleo.helpers import option

from poetry.console.commands.command import Command


class SourceDefaultCommand(Command):
name = "source default"
description = "Enable or disable the implicit default source PyPI for the project."

options = [
option("enable-pypi", None, "Enable PyPI as implicit default source."),
option("disable-pypi", None, "Disable PyPI as implicit default source."),
]

def handle(self) -> int:
enable_pypi = self.option("enable-pypi")
disable_pypi = self.option("disable-pypi")

if enable_pypi and disable_pypi:
self.line_error("Cannot enable and disable PyPI.")
return 1

if enable_pypi or disable_pypi:
self.poetry.pyproject.poetry_config["default-source-pypi"] = enable_pypi
self.poetry.pyproject.save()

else:
state = (
"enabled"
if self.poetry.pyproject.poetry_config.get("default-source-pypi", True)
else "disabled"
)
self.line(f"PyPI is {state} as implicit default source.")

return 0
7 changes: 7 additions & 0 deletions src/poetry/console/commands/source/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,12 @@ def handle(self) -> int:
table.add_rows(rows)
table.render()
self.line("")
if not names:
state = (
"enabled"
if self.poetry.pyproject.poetry_config.get("default-source-pypi", True)
else "disabled"
)
self.line(f"PyPI is {state} as implicit default source.")

return 0
Empty file.
24 changes: 22 additions & 2 deletions src/poetry/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ def create_poetry(
poetry.local_config.get("source", []),
io,
disable_cache=disable_cache,
default_source_pypi=poetry.local_config.get(
"default-source-pypi", True
),
)
)

Expand All @@ -117,12 +120,13 @@ def create_pool(
sources: Iterable[dict[str, Any]] = (),
io: IO | None = None,
disable_cache: bool = False,
*,
default_source_pypi: bool = True,
) -> RepositoryPool:
from poetry.repositories import RepositoryPool

if io is None:
io = NullIO()

if disable_cache:
logger.debug("Disabling source caches")

Expand All @@ -142,13 +146,29 @@ def create_pool(
message += " and setting it as secondary"

io.write_line(message)
if is_default:
# TODO: replace command
io.write_error_line(
"<warning>"
"The 'default' option is deprecated.\n"
" If you want to disable PyPI, TODO command\n"
" If you want to set a source as the first source to be searched"
" for packages, just make it the first source in your"
" pyproject.toml."
"</warning>"
)

pool.add_repository(repository, is_default, secondary=is_secondary)

# Put PyPI last to prefer private repositories
# unless we have no default source AND no primary sources
# (default = false, secondary = false)
if pool.has_default():
if pool.has_default() or not default_source_pypi:
if not sources:
raise RuntimeError(
"If no sources are configured,"
' "default-source-pypi" must not be set to false!'
)
if io.is_debug():
io.write_line("Deactivating the PyPI repository")
else:
Expand Down
4 changes: 4 additions & 0 deletions src/poetry/json/schemas/poetry.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"type": "object",
"required": [],
"properties": {
"default-source-pypi": {
"type": "boolean",
"description": "Whether PyPI is implicitly used to search for packages."
},
"source": {
"type": "array",
"description": "A set of additional repositories where packages can be found.",
Expand Down
3 changes: 3 additions & 0 deletions src/poetry/utils/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def source_to_table(source: Source) -> Table:

source_table: Table = table()
for key, value in source.to_dict().items():
if key == "default" and not value:
# default is deprecated, so we don't add it if it is not set
continue
source_table.add(key, value)
source_table.add(nl())
return source_table
4 changes: 3 additions & 1 deletion tests/console/commands/self/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ def pool(repo: TestRepository) -> RepositoryPool:

def create_pool_factory(
repo: Repository,
) -> Callable[[Config, Iterable[dict[str, Any]], IO, bool], RepositoryPool]:
) -> Callable[[Config, Iterable[dict[str, Any]], IO, bool, bool], RepositoryPool]:
def _create_pool(
config: Config,
sources: Iterable[dict[str, Any]] = (),
io: IO | None = None,
disable_cache: bool = False,
*,
default_source_pypi: bool = True,
) -> RepositoryPool:
pool = RepositoryPool()
pool.add_repository(repo)
Expand Down
5 changes: 5 additions & 0 deletions tests/console/commands/source/test_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def assert_source_added(
== f"Adding source with name {source_added.name}."
)
poetry.pyproject.reload()
for source_table in poetry.pyproject.poetry_config["source"]:
# "default" is deprecated and should only be written if set to True
assert "default" not in source_table or source_table["default"] is True
sources = poetry.get_sources()
assert sources == [source_existing, source_added]
assert tester.status_code == 0
Expand All @@ -55,6 +58,7 @@ def test_source_add_default(
poetry_with_source: Poetry,
):
tester.execute(f"--default {source_default.name} {source_default.url}")
assert "deprecated" in tester.io.fetch_error()
assert_source_added(tester, poetry_with_source, source_existing, source_default)


Expand Down Expand Up @@ -95,6 +99,7 @@ def test_source_add_existing(
tester.io.fetch_output().strip()
== f"Source with name {source_existing.name} already exists. Updating."
)
assert "deprecated" in tester.io.fetch_error()

poetry_with_source.pyproject.reload()
sources = poetry_with_source.get_sources()
Expand Down
52 changes: 52 additions & 0 deletions tests/console/commands/source/test_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import pytest


if TYPE_CHECKING:
from cleo.testers.command_tester import CommandTester

from poetry.config.source import Source
from poetry.poetry import Poetry
from tests.types import CommandTesterFactory


@pytest.fixture
def tester(
command_tester_factory: CommandTesterFactory, poetry_with_source: Poetry
) -> CommandTester:
return command_tester_factory("source default", poetry=poetry_with_source)


def test_source_default_enabled_by_default(
tester: CommandTester,
source_existing: Source,
source_default: Source,
poetry_with_source: Poetry,
) -> None:
tester.execute("")
assert "enabled" in tester.io.fetch_output()
poetry_with_source.pyproject.reload()
assert "default-source-pypi" not in poetry_with_source.pyproject.poetry_config


@pytest.mark.parametrize("enable", [True, False])
def test_source_default_disable(
tester: CommandTester,
source_existing: Source,
source_default: Source,
poetry_with_source: Poetry,
enable: bool,
) -> None:
tester.execute("--enable-pypi" if enable else "--disable-pypi")
poetry_with_source.pyproject.reload()
assert poetry_with_source.pyproject.poetry_config["default-source-pypi"] is enable

tester.execute("")
output = tester.io.fetch_output()
assert ("enabled" in output) is enable
assert ("disabled" in output) is not enable
poetry_with_source.pyproject.reload()
assert poetry_with_source.pyproject.poetry_config["default-source-pypi"] is enable
26 changes: 26 additions & 0 deletions tests/console/commands/source/test_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ def test_source_show_simple(tester: CommandTester):
url : https://two.com
default : no
secondary : no
PyPI is enabled as implicit default source.
""".splitlines()
assert [
line.strip() for line in tester.io.fetch_output().strip().splitlines()
] == expected
assert tester.status_code == 0


def test_source_show_default_pypi_disabled(
command_tester_factory: CommandTesterFactory,
poetry_with_source: Poetry,
) -> None:
tester = command_tester_factory("source default", poetry=poetry_with_source)
tester.execute("--disable-pypi")

tester = command_tester_factory("source show", poetry=poetry_with_source)
tester.execute("")

expected = """\
name : existing
url : https://existing.com
default : no
secondary : no
PyPI is disabled as implicit default source.
""".splitlines()
assert [
line.strip() for line in tester.io.fetch_output().strip().splitlines()
Expand Down
Loading

0 comments on commit bbeae93

Please sign in to comment.