Skip to content

Commit 978d435

Browse files
authored
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.
1 parent 605823e commit 978d435

File tree

37 files changed

+676
-118
lines changed

37 files changed

+676
-118
lines changed

docs/cli.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -779,9 +779,12 @@ For example, to add the `pypi-test` source, you can run:
779779
poetry source add pypi-test https://test.pypi.org/simple/
780780
```
781781

782-
{{% note %}}
783-
You cannot use the name `pypi` as it is reserved for use by the default PyPI source.
784-
{{% /note %}}
782+
You cannot use the name `pypi` for a custom repository as it is reserved for use by
783+
the default PyPI source. However, you can set the priority of PyPI:
784+
785+
```bash
786+
poetry source add --priority=explicit pypi
787+
```
785788

786789
#### Options
787790

@@ -808,7 +811,8 @@ poetry source show pypi-test
808811
```
809812

810813
{{% note %}}
811-
This command will only show sources configured via the `pyproject.toml` and does not include PyPI.
814+
This command will only show sources configured via the `pyproject.toml`
815+
and does not include the implicit default PyPI.
812816
{{% /note %}}
813817

814818
### source remove

docs/repositories.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ If `priority` is undefined, the source is considered a primary source that takes
128128
Package sources are considered in the following order:
129129
1. [default source](#default-package-source),
130130
2. primary sources,
131-
3. PyPI (unless disabled by another default source),
131+
3. implicit PyPI (unless disabled by another [default source](#default-package-source) or configured explicitly),
132132
4. [secondary sources](#secondary-package-sources),
133133

134134
[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
137137

138138
{{% note %}}
139139

140-
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).
140+
If you want to change the priority of [PyPI](https://pypi.org), you can set it explicitly, e.g.
141141

142-
If you prefer to specify a package source for a specific dependency, see [Secondary Package Sources](#secondary-package-sources).
143-
144-
{{% /note %}}
145-
146-
147-
{{% warning %}}
142+
```bash
143+
poetry source add --priority=primary PyPI
144+
```
148145

149-
If you do not want any of the custom sources to take precedence over [PyPI](https://pypi.org),
150-
you must declare **all** package sources to be [secondary](#secondary-package-sources).
146+
If you prefer to disable PyPI completely,
147+
you may choose to set one of your package sources to be the [default](#default-package-source)
148+
or configure PyPI as [explicit source](#explicit-package-sources).
151149

152-
{{% /warning %}}
150+
{{% /note %}}
153151

154152

155153
#### Default Package Source
@@ -164,6 +162,21 @@ poetry source add --priority=default foo https://foo.bar/simple/
164162

165163
{{% warning %}}
166164

165+
In a future version of Poetry, PyPI will be disabled automatically
166+
if there is at least one custom source configured with another priority than `explicit`.
167+
If you are using custom sources in addition to PyPI, you should configure PyPI explicitly
168+
with a certain priority, e.g.
169+
170+
```bash
171+
poetry source add --priority=primary PyPI
172+
```
173+
174+
This way, the priority of PyPI can be set in a fine-granular way.
175+
176+
{{% /warning %}}
177+
178+
{{% warning %}}
179+
167180
Configuring a custom package source as default, will effectively disable [PyPI](https://pypi.org)
168181
as a package source for your project.
169182

src/poetry/config/source.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
@dataclasses.dataclass(order=True, eq=True)
1010
class Source:
1111
name: str
12-
url: str
12+
url: str = ""
1313
default: dataclasses.InitVar[bool] = False
1414
secondary: dataclasses.InitVar[bool] = False
1515
priority: Priority = (
@@ -38,6 +38,8 @@ def to_dict(self) -> dict[str, str | bool]:
3838
return dataclasses.asdict(
3939
self,
4040
dict_factory=lambda x: {
41-
k: v if not isinstance(v, Priority) else v.name.lower() for (k, v) in x
41+
k: v if not isinstance(v, Priority) else v.name.lower()
42+
for (k, v) in x
43+
if v
4244
},
4345
)

src/poetry/console/commands/source/add.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ class SourceAddCommand(Command):
1919
"name",
2020
"Source repository name.",
2121
),
22-
argument("url", "Source repository url."),
22+
argument(
23+
"url",
24+
(
25+
"Source repository URL."
26+
" Required, except for PyPI, for which it is not allowed."
27+
),
28+
optional=True,
29+
),
2330
]
2431

2532
options = [
@@ -57,10 +64,24 @@ def handle(self) -> int:
5764
from poetry.utils.source import source_to_table
5865

5966
name: str = self.argument("name")
67+
lower_name = name.lower()
6068
url: str = self.argument("url")
6169
is_default: bool = self.option("default", False)
6270
is_secondary: bool = self.option("secondary", False)
63-
priority: Priority | None = self.option("priority", None)
71+
priority_str: str | None = self.option("priority", None)
72+
73+
if lower_name == "pypi":
74+
name = "PyPI"
75+
if url:
76+
self.line_error(
77+
"<error>The URL of PyPI is fixed and cannot be set.</error>"
78+
)
79+
return 1
80+
elif not url:
81+
self.line_error(
82+
"<error>A custom source cannot be added without a URL.</error>"
83+
)
84+
return 1
6485

6586
if is_default and is_secondary:
6687
self.line_error(
@@ -70,7 +91,7 @@ def handle(self) -> int:
7091
return 1
7192

7293
if is_default or is_secondary:
73-
if priority is not None:
94+
if priority_str is not None:
7495
self.line_error(
7596
"<error>Priority was passed through both --priority and a"
7697
" deprecated flag (--default or --secondary). Please only provide"
@@ -88,34 +109,25 @@ def handle(self) -> int:
88109
priority = Priority.DEFAULT
89110
elif is_secondary:
90111
priority = Priority.SECONDARY
91-
elif priority is None:
112+
elif priority_str is None:
92113
priority = Priority.PRIMARY
93-
94-
new_source = Source(name=name, url=url, priority=priority)
95-
existing_sources = self.poetry.get_sources()
114+
else:
115+
priority = Priority[priority_str.upper()]
96116

97117
sources = AoT([])
98-
118+
new_source = Source(name=name, url=url, priority=priority)
99119
is_new_source = True
100-
for source in existing_sources:
101-
if source == new_source:
102-
self.line(
103-
f"Source with name <c1>{name}</c1> already exists. Skipping"
104-
" addition."
105-
)
106-
return 0
107-
elif (
108-
source.priority is Priority.DEFAULT
109-
and new_source.priority is Priority.DEFAULT
110-
):
120+
121+
for source in self.poetry.get_sources():
122+
if source.priority is Priority.DEFAULT and priority is Priority.DEFAULT:
111123
self.line_error(
112124
f"<error>Source with name <c1>{source.name}</c1> is already set to"
113125
" default. Only one default source can be configured at a"
114126
" time.</error>"
115127
)
116128
return 1
117129

118-
if source.name == name:
130+
if source.name.lower() == lower_name:
119131
source = new_source
120132
is_new_source = False
121133

src/poetry/console/commands/source/remove.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ def handle(self) -> int:
2121
from poetry.utils.source import source_to_table
2222

2323
name = self.argument("name")
24+
lower_name = name.lower()
2425

2526
sources = AoT([])
2627
removed = False
2728

2829
for source in self.poetry.get_sources():
29-
if source.name == name:
30+
if source.name.lower() == lower_name:
3031
self.line(f"Removing source with name <c1>{source.name}</c1>.")
3132
removed = True
3233
continue

src/poetry/console/commands/source/show.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,28 @@ class SourceShowCommand(Command):
2727
def handle(self) -> int:
2828
sources = self.poetry.get_sources()
2929
names = self.argument("source")
30+
lower_names = [name.lower() for name in names]
3031

3132
if not sources:
3233
self.line("No sources configured for this project.")
3334
return 0
3435

35-
if names and not any(s.name in names for s in sources):
36+
if names and not any(s.name.lower() in lower_names for s in sources):
3637
self.line_error(
3738
f"No source found with name(s): {', '.join(names)}",
3839
style="error",
3940
)
4041
return 1
4142

4243
for source in sources:
43-
if names and source.name not in names:
44+
if names and source.name.lower() not in lower_names:
4445
continue
4546

4647
table = self.table(style="compact")
47-
rows: Rows = [
48-
["<info>name</>", f" : <c1>{source.name}</>"],
49-
["<info>url</>", f" : {source.url}"],
50-
[
51-
"<info>priority</>",
52-
f" : {source.priority.name.lower()}",
53-
],
54-
]
48+
rows: Rows = [["<info>name</>", f" : <c1>{source.name}</>"]]
49+
if source.url:
50+
rows.append(["<info>url</>", f" : {source.url}"])
51+
rows.append(["<info>priority</>", f" : {source.priority.name.lower()}"])
5552
table.add_rows(rows)
5653
table.render()
5754
self.line("")

src/poetry/console/commands/source/update.py

Whitespace-only changes.

src/poetry/factory.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from poetry.core.packages.project_package import ProjectPackage
1616

1717
from poetry.config.config import Config
18+
from poetry.exceptions import PoetryException
1819
from poetry.json import validate_object
1920
from poetry.packages.locker import Locker
2021
from poetry.plugins.plugin import Plugin
@@ -32,7 +33,7 @@
3233
from tomlkit.toml_document import TOMLDocument
3334

3435
from poetry.repositories import RepositoryPool
35-
from poetry.repositories.legacy_repository import LegacyRepository
36+
from poetry.repositories.http_repository import HTTPRepository
3637
from poetry.utils.dependency_specification import DependencySpec
3738

3839
logger = logging.getLogger(__name__)
@@ -134,6 +135,7 @@ def create_pool(
134135

135136
pool = RepositoryPool()
136137

138+
explicit_pypi = False
137139
for source in sources:
138140
repository = cls.create_package_source(
139141
source, auth_config, disable_cache=disable_cache
@@ -163,40 +165,71 @@ def create_pool(
163165
io.write_line(message)
164166

165167
pool.add_repository(repository, priority=priority)
168+
if repository.name.lower() == "pypi":
169+
explicit_pypi = True
166170

167171
# Only add PyPI if no default repository is configured
168-
if pool.has_default():
169-
if io.is_debug():
170-
io.write_line("Deactivating the PyPI repository")
171-
else:
172-
from poetry.repositories.pypi_repository import PyPiRepository
173-
174-
if pool.has_primary_repositories():
175-
pypi_priority = Priority.SECONDARY
172+
if not explicit_pypi:
173+
if pool.has_default():
174+
if io.is_debug():
175+
io.write_line("Deactivating the PyPI repository")
176176
else:
177-
pypi_priority = Priority.DEFAULT
177+
from poetry.repositories.pypi_repository import PyPiRepository
178+
179+
if pool.repositories:
180+
io.write_error_line(
181+
"<warning>"
182+
"Warning: In a future version of Poetry, PyPI will be disabled"
183+
" automatically if at least one custom source is configured"
184+
" with another priority than 'explicit'. In order to avoid"
185+
" a breaking change and make your pyproject.toml forward"
186+
" compatible, add PyPI explicitly via 'poetry source add pypi'."
187+
" By the way, this has the advantage that you can set the"
188+
" priority of PyPI as with any other source."
189+
"</warning>"
190+
)
191+
192+
if pool.has_primary_repositories():
193+
pypi_priority = Priority.SECONDARY
194+
else:
195+
pypi_priority = Priority.DEFAULT
178196

179-
pool.add_repository(
180-
PyPiRepository(disable_cache=disable_cache), priority=pypi_priority
197+
pool.add_repository(
198+
PyPiRepository(disable_cache=disable_cache), priority=pypi_priority
199+
)
200+
201+
if not pool.repositories:
202+
raise PoetryException(
203+
"At least one source must not be configured as 'explicit'."
181204
)
182205

183206
return pool
184207

185208
@classmethod
186209
def create_package_source(
187210
cls, source: dict[str, str], auth_config: Config, disable_cache: bool = False
188-
) -> LegacyRepository:
211+
) -> HTTPRepository:
212+
from poetry.repositories.exceptions import InvalidSourceError
189213
from poetry.repositories.legacy_repository import LegacyRepository
214+
from poetry.repositories.pypi_repository import PyPiRepository
190215
from poetry.repositories.single_page_repository import SinglePageRepository
191216

192-
if "url" not in source:
193-
raise RuntimeError("Unsupported source specified")
217+
try:
218+
name = source["name"]
219+
except KeyError:
220+
raise InvalidSourceError("Missing [name] in source.")
221+
222+
if name.lower() == "pypi":
223+
if "url" in source:
224+
raise InvalidSourceError(
225+
"The PyPI repository cannot be configured with a custom url."
226+
)
227+
return PyPiRepository(disable_cache=disable_cache)
194228

195-
# PyPI-like repository
196-
if "name" not in source:
197-
raise RuntimeError("Missing [name] in source.")
198-
name = source["name"]
199-
url = source["url"]
229+
try:
230+
url = source["url"]
231+
except KeyError:
232+
raise InvalidSourceError(f"Missing [url] in source {name!r}.")
200233

201234
repository_class = LegacyRepository
202235

src/poetry/json/schemas/poetry.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
"type": "object",
2121
"additionalProperties": false,
2222
"required": [
23-
"name",
24-
"url"
23+
"name"
2524
],
2625
"properties": {
2726
"name": {

src/poetry/repositories/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ class RepositoryError(Exception):
77

88
class PackageNotFound(Exception):
99
pass
10+
11+
12+
class InvalidSourceError(Exception):
13+
pass

0 commit comments

Comments
 (0)