pyproject-external 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyproject_external/__init__.py +35 -0
- pyproject_external/__main__.py +9 -0
- pyproject_external/_cli/__init__.py +4 -0
- pyproject_external/_cli/_utils.py +39 -0
- pyproject_external/_cli/app.py +36 -0
- pyproject_external/_cli/build.py +120 -0
- pyproject_external/_cli/install.py +97 -0
- pyproject_external/_cli/prepare.py +66 -0
- pyproject_external/_cli/show.py +105 -0
- pyproject_external/_config.py +36 -0
- pyproject_external/_constants.py +30 -0
- pyproject_external/_external.py +368 -0
- pyproject_external/_registry.py +370 -0
- pyproject_external/_sdist.py +86 -0
- pyproject_external/_system.py +183 -0
- pyproject_external/_url.py +85 -0
- pyproject_external/_version.py +21 -0
- pyproject_external/py.typed +2 -0
- pyproject_external-0.1.0.dist-info/METADATA +223 -0
- pyproject_external-0.1.0.dist-info/RECORD +22 -0
- pyproject_external-0.1.0.dist-info/WHEEL +4 -0
- pyproject_external-0.1.0.dist-info/licenses/LICENSE +20 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2023 Quansight Labs
|
|
3
|
+
"""
|
|
4
|
+
pyproject-external - Utilities to work with PEP 725 `[external]` metadata
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._config import Config # noqa
|
|
8
|
+
from ._external import External # noqa
|
|
9
|
+
from ._registry import Registry, Ecosystems, Mapping, default_ecosystems, remote_mapping # noqa
|
|
10
|
+
from ._system import ( # noqa
|
|
11
|
+
find_ecosystem_for_package_manager,
|
|
12
|
+
detect_ecosystem_and_package_manager,
|
|
13
|
+
activated_conda_env,
|
|
14
|
+
)
|
|
15
|
+
from ._url import DepURL # noqa
|
|
16
|
+
from ._version import __version__
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"__version__",
|
|
20
|
+
"Config",
|
|
21
|
+
"DepURL",
|
|
22
|
+
"Ecosystems",
|
|
23
|
+
"External",
|
|
24
|
+
"Mapping",
|
|
25
|
+
"Registry",
|
|
26
|
+
"activated_conda_env",
|
|
27
|
+
"find_ecosystem_for_package_manager",
|
|
28
|
+
"detect_ecosystem_and_package_manager",
|
|
29
|
+
"default_ecosystems",
|
|
30
|
+
"remote_mapping",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __dir__() -> list[str]:
|
|
35
|
+
return __all__
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import tarfile
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _read_pyproject_from_sdist(path: Path) -> str:
|
|
9
|
+
with tarfile.open(path) as tar:
|
|
10
|
+
for info in tar.getmembers():
|
|
11
|
+
name = info.name
|
|
12
|
+
if "/" in name and name.split("/")[-1] == "pyproject.toml":
|
|
13
|
+
return tar.extractfile(info).read().decode()
|
|
14
|
+
raise ValueError("Could not read pyproject.toml file from sdist")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _pyproject_text(package: Path) -> str:
|
|
18
|
+
if package.is_file():
|
|
19
|
+
if not package.name.lower().endswith(".tar.gz"):
|
|
20
|
+
raise typer.BadParameter(f"Given package '{package}' is a file, but not a sdist.")
|
|
21
|
+
return _read_pyproject_from_sdist(package)
|
|
22
|
+
if package.is_dir():
|
|
23
|
+
return (package / "pyproject.toml").read_text()
|
|
24
|
+
raise typer.BadParameter(f"Package {package} is not a valid path.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _Installers(str, Enum):
|
|
28
|
+
pip = "pip"
|
|
29
|
+
uv = "uv"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NotOnCIError(RuntimeError):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super().__init__(
|
|
35
|
+
"This tool should only be used in CI or ephemeral environments!\n\n"
|
|
36
|
+
"It will likely install system packages as a side effect of providing the "
|
|
37
|
+
"external dependencies required to build the wheels.\n\n"
|
|
38
|
+
"If you understand the risks, set CI=1 to override."
|
|
39
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Quansight Labs
|
|
3
|
+
"""
|
|
4
|
+
CLI to work with PEP 725 external metadata.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.logging import RichHandler
|
|
12
|
+
|
|
13
|
+
from .build import app as _install
|
|
14
|
+
from .install import app as _build
|
|
15
|
+
from .prepare import app as _prepare
|
|
16
|
+
from .show import app as _show
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(
|
|
19
|
+
help=__doc__,
|
|
20
|
+
no_args_is_help=True,
|
|
21
|
+
add_completion=False,
|
|
22
|
+
)
|
|
23
|
+
app.add_typer(_show)
|
|
24
|
+
app.add_typer(_build)
|
|
25
|
+
app.add_typer(_install)
|
|
26
|
+
app.add_typer(_prepare)
|
|
27
|
+
|
|
28
|
+
logging.basicConfig(
|
|
29
|
+
level=logging.INFO,
|
|
30
|
+
format="%(message)s",
|
|
31
|
+
datefmt="[%X]",
|
|
32
|
+
handlers=[RichHandler(console=Console(stderr=True))],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
app()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Quansight Labs
|
|
3
|
+
"""
|
|
4
|
+
Build a wheel for the given sdist or project.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tarfile
|
|
14
|
+
from contextlib import nullcontext
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from tempfile import TemporaryDirectory
|
|
17
|
+
from typing import Annotated
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import tomllib
|
|
21
|
+
except ImportError:
|
|
22
|
+
import tomli as tomllib
|
|
23
|
+
|
|
24
|
+
import typer
|
|
25
|
+
|
|
26
|
+
from .. import (
|
|
27
|
+
Config,
|
|
28
|
+
External,
|
|
29
|
+
activated_conda_env,
|
|
30
|
+
detect_ecosystem_and_package_manager,
|
|
31
|
+
find_ecosystem_for_package_manager,
|
|
32
|
+
)
|
|
33
|
+
from ._utils import NotOnCIError, _Installers, _pyproject_text
|
|
34
|
+
|
|
35
|
+
log = logging.getLogger(__name__)
|
|
36
|
+
app = typer.Typer()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command(
|
|
40
|
+
help=__doc__,
|
|
41
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
42
|
+
)
|
|
43
|
+
def build(
|
|
44
|
+
package: Annotated[
|
|
45
|
+
str,
|
|
46
|
+
typer.Argument(
|
|
47
|
+
help="Package to build wheel for."
|
|
48
|
+
"It can be a path to a pyproject.toml-containing directory, "
|
|
49
|
+
"or a source distribution."
|
|
50
|
+
),
|
|
51
|
+
],
|
|
52
|
+
package_manager: Annotated[
|
|
53
|
+
str,
|
|
54
|
+
typer.Option(
|
|
55
|
+
help="If given, use this package manager to install the external dependencies "
|
|
56
|
+
"rather than the auto-detected one."
|
|
57
|
+
),
|
|
58
|
+
] = Config.load_user_config().preferred_package_manager or "",
|
|
59
|
+
outdir: Annotated[
|
|
60
|
+
str | None,
|
|
61
|
+
typer.Option(help="Output directory for the wheel. Defaults to working directory"),
|
|
62
|
+
] = None,
|
|
63
|
+
build_installer: Annotated[
|
|
64
|
+
_Installers,
|
|
65
|
+
typer.Option(
|
|
66
|
+
help="Which installer tool should be used to provide the isolated 'build' venv"
|
|
67
|
+
),
|
|
68
|
+
] = _Installers.pip,
|
|
69
|
+
unknown_args: typer.Context = typer.Option(()),
|
|
70
|
+
) -> None:
|
|
71
|
+
if not os.environ.get("CI"):
|
|
72
|
+
raise NotOnCIError()
|
|
73
|
+
|
|
74
|
+
package = Path(package)
|
|
75
|
+
pyproject_text = _pyproject_text(package)
|
|
76
|
+
pyproject = tomllib.loads(pyproject_text)
|
|
77
|
+
external = External.from_pyproject_data(pyproject)
|
|
78
|
+
external.validate()
|
|
79
|
+
|
|
80
|
+
if package_manager:
|
|
81
|
+
ecosystem = find_ecosystem_for_package_manager(package_manager)
|
|
82
|
+
else:
|
|
83
|
+
ecosystem, package_manager = detect_ecosystem_and_package_manager()
|
|
84
|
+
log.info("Detected ecosystem '%s' for package manager '%s'", ecosystem, package_manager)
|
|
85
|
+
|
|
86
|
+
install_external_cmd = external.install_command(ecosystem, package_manager=package_manager)
|
|
87
|
+
build_cmd = [
|
|
88
|
+
sys.executable,
|
|
89
|
+
"-m",
|
|
90
|
+
"build",
|
|
91
|
+
"--wheel",
|
|
92
|
+
"--outdir",
|
|
93
|
+
outdir or os.getcwd(),
|
|
94
|
+
"--installer",
|
|
95
|
+
build_installer,
|
|
96
|
+
*unknown_args.args,
|
|
97
|
+
]
|
|
98
|
+
try:
|
|
99
|
+
# 1. Install external dependencies
|
|
100
|
+
subprocess.run(install_external_cmd, check=True)
|
|
101
|
+
# 2. Build wheel
|
|
102
|
+
with (
|
|
103
|
+
activated_conda_env(package_manager=package_manager)
|
|
104
|
+
if ecosystem == "conda-forge"
|
|
105
|
+
else nullcontext(os.environ) as env
|
|
106
|
+
):
|
|
107
|
+
if package.is_file():
|
|
108
|
+
with TemporaryDirectory() as tmp:
|
|
109
|
+
with tarfile.open(package) as tar:
|
|
110
|
+
tar.extractall(tmp, filter="data")
|
|
111
|
+
tmp = Path(tmp)
|
|
112
|
+
if (tmp / "pyproject.toml").is_file():
|
|
113
|
+
extracted_package = tmp
|
|
114
|
+
else:
|
|
115
|
+
extracted_package = next(tmp.glob("*"))
|
|
116
|
+
subprocess.run([*build_cmd, extracted_package], check=True, env=env)
|
|
117
|
+
else:
|
|
118
|
+
subprocess.run([*build_cmd, package], check=True, env=env)
|
|
119
|
+
except subprocess.CalledProcessError as exc:
|
|
120
|
+
sys.exit(exc.returncode) # avoid unnecessary typer pretty traceback
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Quansight Labs
|
|
3
|
+
"""
|
|
4
|
+
Install a project in the given location. Wheels will be built as needed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from contextlib import nullcontext
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Annotated
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import tomllib
|
|
19
|
+
except ImportError:
|
|
20
|
+
import tomli as tomllib
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
|
|
24
|
+
from .. import (
|
|
25
|
+
Config,
|
|
26
|
+
External,
|
|
27
|
+
activated_conda_env,
|
|
28
|
+
detect_ecosystem_and_package_manager,
|
|
29
|
+
find_ecosystem_for_package_manager,
|
|
30
|
+
)
|
|
31
|
+
from ._utils import NotOnCIError, _Installers, _pyproject_text
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger(__name__)
|
|
34
|
+
app = typer.Typer()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command(
|
|
38
|
+
help=__doc__,
|
|
39
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
40
|
+
)
|
|
41
|
+
def install(
|
|
42
|
+
package: Annotated[
|
|
43
|
+
str,
|
|
44
|
+
typer.Argument(
|
|
45
|
+
help="Package to build wheel for."
|
|
46
|
+
"It can be a path to a pyproject.toml-containing directory, "
|
|
47
|
+
"or a source distribution."
|
|
48
|
+
),
|
|
49
|
+
],
|
|
50
|
+
package_manager: Annotated[
|
|
51
|
+
str,
|
|
52
|
+
typer.Option(
|
|
53
|
+
help="If given, use this package manager to install the external dependencies "
|
|
54
|
+
"rather than the auto-detected one."
|
|
55
|
+
),
|
|
56
|
+
] = Config.load_user_config().preferred_package_manager or "",
|
|
57
|
+
installer: Annotated[
|
|
58
|
+
_Installers,
|
|
59
|
+
typer.Option(help="Which tool should be used to install the package"),
|
|
60
|
+
] = _Installers.pip,
|
|
61
|
+
unknown_args: typer.Context = typer.Option(()),
|
|
62
|
+
) -> None:
|
|
63
|
+
if not os.environ.get("CI"):
|
|
64
|
+
raise NotOnCIError()
|
|
65
|
+
|
|
66
|
+
package = Path(package)
|
|
67
|
+
pyproject_text = _pyproject_text(package)
|
|
68
|
+
pyproject = tomllib.loads(pyproject_text)
|
|
69
|
+
external = External.from_pyproject_data(pyproject)
|
|
70
|
+
external.validate()
|
|
71
|
+
|
|
72
|
+
if package_manager:
|
|
73
|
+
ecosystem = find_ecosystem_for_package_manager(package_manager)
|
|
74
|
+
else:
|
|
75
|
+
ecosystem, package_manager = detect_ecosystem_and_package_manager()
|
|
76
|
+
log.info("Detected ecosystem '%s' for package manager '%s'", ecosystem, package_manager)
|
|
77
|
+
|
|
78
|
+
install_external_cmd = external.install_command(ecosystem, package_manager=package_manager)
|
|
79
|
+
if installer == _Installers.pip:
|
|
80
|
+
install_cmd = [sys.executable, "-m", "pip", "install"]
|
|
81
|
+
elif installer == _Installers.uv:
|
|
82
|
+
install_cmd = ["uv", "pip", "install", "--python", sys.executable]
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError(f"Unrecognized 'installer': {installer}")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# 1. Install external dependencies
|
|
88
|
+
subprocess.run(install_external_cmd, check=True)
|
|
89
|
+
# 2. Build wheel
|
|
90
|
+
with (
|
|
91
|
+
activated_conda_env(package_manager=package_manager)
|
|
92
|
+
if ecosystem == "conda-forge"
|
|
93
|
+
else nullcontext(os.environ) as env
|
|
94
|
+
):
|
|
95
|
+
subprocess.run([*install_cmd, *unknown_args.args, package], check=True, env=env)
|
|
96
|
+
except subprocess.CalledProcessError as exc:
|
|
97
|
+
sys.exit(exc.returncode) # avoid unnecessary typer pretty traceback
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Quansight Labs
|
|
3
|
+
"""
|
|
4
|
+
Prepare a package for building with [external] metadata
|
|
5
|
+
by downloading and patching its most recent sdist.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from tempfile import TemporaryDirectory
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
|
|
16
|
+
import typer
|
|
17
|
+
|
|
18
|
+
from .._sdist import (
|
|
19
|
+
append_external_metadata,
|
|
20
|
+
apply_patches,
|
|
21
|
+
create_new_sdist,
|
|
22
|
+
download_sdist,
|
|
23
|
+
untar_sdist,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
log = logging.getLogger(__name__)
|
|
27
|
+
app = typer.Typer()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command(help=__doc__)
|
|
31
|
+
def prepare(
|
|
32
|
+
package_name: Annotated[
|
|
33
|
+
str,
|
|
34
|
+
typer.Argument(help="PyPI package name to download and patch."),
|
|
35
|
+
],
|
|
36
|
+
external_metadata_dir: Annotated[
|
|
37
|
+
str,
|
|
38
|
+
typer.Option(
|
|
39
|
+
help="Search this directory to find a '<package_name>.toml' "
|
|
40
|
+
"file that contains an '[external]' table.",
|
|
41
|
+
),
|
|
42
|
+
] = "external_metadata",
|
|
43
|
+
patches_dir: Annotated[
|
|
44
|
+
str,
|
|
45
|
+
typer.Option(
|
|
46
|
+
help="Search this directory to find a '<package_name>.py' "
|
|
47
|
+
"script that will run additional patches on the sdist contents.",
|
|
48
|
+
),
|
|
49
|
+
] = "patches",
|
|
50
|
+
out_dir: Annotated[
|
|
51
|
+
str,
|
|
52
|
+
typer.Option(help="Directory where the patched sdist will be written to."),
|
|
53
|
+
] = "sdist",
|
|
54
|
+
) -> None:
|
|
55
|
+
with TemporaryDirectory() as tmp:
|
|
56
|
+
tmp = Path(tmp)
|
|
57
|
+
fname_sdist = download_sdist(package_name, tmp)
|
|
58
|
+
fname_pyproject_toml = untar_sdist(fname_sdist, tmp)
|
|
59
|
+
append_external_metadata(
|
|
60
|
+
fname_pyproject_toml,
|
|
61
|
+
package_name,
|
|
62
|
+
patches_dir=external_metadata_dir,
|
|
63
|
+
)
|
|
64
|
+
apply_patches(package_name, fname_pyproject_toml.parent, patches_dir=patches_dir)
|
|
65
|
+
Path(out_dir).mkdir(parents=True, exist_ok=True)
|
|
66
|
+
create_new_sdist(fname_sdist, tmp, out_dir or os.getcwd())
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2023 Quansight Labs
|
|
3
|
+
"""
|
|
4
|
+
Query PEP 725 [external] metadata from pyproject.toml or source distributions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import shlex
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Annotated
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import tomllib
|
|
15
|
+
except ImportError:
|
|
16
|
+
import tomli as tomllib
|
|
17
|
+
|
|
18
|
+
import tomli_w
|
|
19
|
+
import typer
|
|
20
|
+
from rich import print as rprint
|
|
21
|
+
from rich.markup import escape
|
|
22
|
+
|
|
23
|
+
# Only import from __init__ to make sure the only uses the public interface
|
|
24
|
+
from .. import (
|
|
25
|
+
Config,
|
|
26
|
+
External,
|
|
27
|
+
detect_ecosystem_and_package_manager,
|
|
28
|
+
find_ecosystem_for_package_manager,
|
|
29
|
+
)
|
|
30
|
+
from ._utils import _pyproject_text
|
|
31
|
+
|
|
32
|
+
log = logging.getLogger(__name__)
|
|
33
|
+
app = typer.Typer()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _OutputChoices(Enum):
|
|
37
|
+
RAW = "raw"
|
|
38
|
+
NORMALIZED = "normalized"
|
|
39
|
+
MAPPED_TABLE = "mapped"
|
|
40
|
+
MAPPED_LIST = "mapped-list"
|
|
41
|
+
COMMAND = "command"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command(help=__doc__)
|
|
45
|
+
def show(
|
|
46
|
+
package: Annotated[
|
|
47
|
+
str,
|
|
48
|
+
typer.Argument(
|
|
49
|
+
help="Package to analyze. It can be a path to a pyproject.toml-containing directory,"
|
|
50
|
+
" or a source distribution."
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
validate: Annotated[
|
|
54
|
+
bool,
|
|
55
|
+
typer.Option(help="Validate external dependencies against central registry."),
|
|
56
|
+
] = False,
|
|
57
|
+
output: Annotated[
|
|
58
|
+
_OutputChoices,
|
|
59
|
+
typer.Option(
|
|
60
|
+
help="Choose output format. 'raw' prints the TOML table as is. "
|
|
61
|
+
"'normalized' processes the 'dep:' URLs before printing them. "
|
|
62
|
+
"'mapped' prints the dependencies mapped to the given ecosystem. "
|
|
63
|
+
"'command' prints the install command for the given package manager."
|
|
64
|
+
),
|
|
65
|
+
] = _OutputChoices.RAW.value,
|
|
66
|
+
package_manager: Annotated[
|
|
67
|
+
str,
|
|
68
|
+
typer.Option(help="If given, use this package manager rather than the auto-detected one."),
|
|
69
|
+
] = Config.load_user_config().preferred_package_manager or "",
|
|
70
|
+
) -> None:
|
|
71
|
+
package = Path(package)
|
|
72
|
+
pyproject_text = _pyproject_text(package)
|
|
73
|
+
pyproject = tomllib.loads(pyproject_text)
|
|
74
|
+
raw_external = pyproject.get("external")
|
|
75
|
+
if not raw_external:
|
|
76
|
+
raise typer.BadParameter("Package's pyproject.toml does not contain an 'external' table.")
|
|
77
|
+
|
|
78
|
+
external = External.from_pyproject_data(pyproject)
|
|
79
|
+
if validate:
|
|
80
|
+
external.validate()
|
|
81
|
+
|
|
82
|
+
if output == _OutputChoices.RAW:
|
|
83
|
+
rprint(escape(tomli_w.dumps({"external": raw_external}).rstrip()))
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
if output == _OutputChoices.NORMALIZED:
|
|
87
|
+
rprint(escape(tomli_w.dumps(external.to_dict())))
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if package_manager:
|
|
91
|
+
ecosystem = find_ecosystem_for_package_manager(package_manager)
|
|
92
|
+
else:
|
|
93
|
+
ecosystem, package_manager = detect_ecosystem_and_package_manager()
|
|
94
|
+
log.info("Detected ecosystem '%s' for package manager '%s'", ecosystem, package_manager)
|
|
95
|
+
if output == _OutputChoices.MAPPED_TABLE:
|
|
96
|
+
mapped_dict = external.to_dict(mapped_for=ecosystem, package_manager=package_manager)
|
|
97
|
+
rprint(escape(tomli_w.dumps(mapped_dict)))
|
|
98
|
+
# The following outputs might be used in shell substitutions like $(), so use print()
|
|
99
|
+
# directly. rich's print will hard-wrap the line and break the output.
|
|
100
|
+
elif output == _OutputChoices.COMMAND:
|
|
101
|
+
print(shlex.join(external.install_command(ecosystem, package_manager=package_manager)))
|
|
102
|
+
elif output == _OutputChoices.MAPPED_LIST:
|
|
103
|
+
print(shlex.join(external.map_dependencies(ecosystem, package_manager=package_manager)))
|
|
104
|
+
else:
|
|
105
|
+
raise typer.BadParameter(f"Unknown value for --output: {output}")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Quansight Labs
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import tomllib
|
|
9
|
+
except ImportError:
|
|
10
|
+
import tomli as tomllib
|
|
11
|
+
|
|
12
|
+
from platformdirs import user_config_dir
|
|
13
|
+
|
|
14
|
+
from ._constants import APP_AUTHOR, APP_CONFIG_FILENAME, APP_NAME
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_config_directory() -> Path:
|
|
18
|
+
if pyproject_external_config := os.environ.get("PYPROJECT_EXTERNAL_CONFIG"):
|
|
19
|
+
return Path(pyproject_external_config)
|
|
20
|
+
return Path(user_config_dir(appname=APP_NAME, appauthor=APP_AUTHOR))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_config_file() -> Path:
|
|
24
|
+
return _get_config_directory() / APP_CONFIG_FILENAME
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, kw_only=True)
|
|
28
|
+
class Config:
|
|
29
|
+
preferred_package_manager: str | None = None
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def load_user_config(cls) -> "Config":
|
|
33
|
+
config_file = _get_config_file()
|
|
34
|
+
if config_file.is_file():
|
|
35
|
+
return cls(**tomllib.loads(_get_config_file().read_text()))
|
|
36
|
+
return cls()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2025 Quansight Labs
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
APP_NAME: Final[str] = "pyproject-external"
|
|
6
|
+
APP_AUTHOR: Final[str] = "pyproject-external"
|
|
7
|
+
APP_CONFIG_FILENAME: Final[str] = "config.toml"
|
|
8
|
+
DEFAULT_ECOSYSTEMS_SCHEMA_URL: Final[str] = (
|
|
9
|
+
"https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/"
|
|
10
|
+
"schemas/known-ecosystems.schema.json"
|
|
11
|
+
)
|
|
12
|
+
DEFAULT_ECOSYSTEMS_URL: Final[str] = (
|
|
13
|
+
"https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/"
|
|
14
|
+
"data/known-ecosystems.json"
|
|
15
|
+
)
|
|
16
|
+
DEFAULT_MAPPING_SCHEMA_URL: Final[str] = (
|
|
17
|
+
"https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/"
|
|
18
|
+
"schemas/external-mapping.schema.json"
|
|
19
|
+
)
|
|
20
|
+
DEFAULT_MAPPING_URL_TEMPLATE: Final[str] = (
|
|
21
|
+
"https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/"
|
|
22
|
+
"data/{}.mapping.json"
|
|
23
|
+
)
|
|
24
|
+
DEFAULT_REGISTRY_SCHEMA_URL: Final[str] = (
|
|
25
|
+
"https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/"
|
|
26
|
+
"schemas/central-registry.schema.json"
|
|
27
|
+
)
|
|
28
|
+
DEFAULT_REGISTRY_URL: Final[str] = (
|
|
29
|
+
"https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/data/registry.json"
|
|
30
|
+
)
|