csspin-python 4.1.0rc2__py3-none-any.whl → 5.0.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.
- csspin_python/_predict_wheel_filename.py +68 -0
- csspin_python/pytest.py +0 -1
- csspin_python/python.py +108 -39
- csspin_python/python_sbom.py +93 -9
- csspin_python/python_sbom_schema.yaml +5 -0
- {csspin_python-4.1.0rc2.dist-info → csspin_python-5.0.0.dist-info}/METADATA +1 -1
- {csspin_python-4.1.0rc2.dist-info → csspin_python-5.0.0.dist-info}/RECORD +10 -11
- {csspin_python-4.1.0rc2.dist-info → csspin_python-5.0.0.dist-info}/WHEEL +1 -1
- csspin_python-4.1.0rc2.dist-info/scm_file_list.json +0 -78
- csspin_python-4.1.0rc2.dist-info/scm_version.json +0 -8
- {csspin_python-4.1.0rc2.dist-info → csspin_python-5.0.0.dist-info}/licenses/LICENSE +0 -0
- {csspin_python-4.1.0rc2.dist-info → csspin_python-5.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# -*- mode: python; coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2026 CONTACT Software GmbH
|
|
4
|
+
# https://www.contact-software.com/
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
Predict the wheel filename ``setup.py bdist_wheel`` would produce, without
|
|
20
|
+
building it::
|
|
21
|
+
|
|
22
|
+
python _predict_wheel_filename.py /path/to/project NAME VERSION
|
|
23
|
+
|
|
24
|
+
Must run in the subprocess environment to have setuptools in the environment and
|
|
25
|
+
to have the correct Python version and platform tags that the wheel is built
|
|
26
|
+
for.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import io
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
import warnings
|
|
33
|
+
from contextlib import redirect_stdout
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def predict_wheel_filename(project_path: str, name: str, version: str) -> str:
|
|
37
|
+
"""Return the predicted wheel filename for ``project_path``."""
|
|
38
|
+
os.chdir(project_path)
|
|
39
|
+
|
|
40
|
+
# setup.py is exec'd here and free to print, so keep its chatter off the
|
|
41
|
+
# stdout the caller parses. stderr stays, so tracebacks still surface.
|
|
42
|
+
with warnings.catch_warnings(), redirect_stdout(io.StringIO()):
|
|
43
|
+
warnings.simplefilter("ignore")
|
|
44
|
+
if os.path.exists("setup.py"):
|
|
45
|
+
from distutils.core import run_setup # pylint: disable=deprecated-module
|
|
46
|
+
|
|
47
|
+
dist = run_setup(
|
|
48
|
+
"setup.py", script_args=["bdist_wheel"], stop_after="commandline"
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
from setuptools.dist import Distribution
|
|
52
|
+
|
|
53
|
+
dist = Distribution({"script_args": ["bdist_wheel"]})
|
|
54
|
+
dist.parse_config_files()
|
|
55
|
+
|
|
56
|
+
dist.metadata.name = name
|
|
57
|
+
dist.metadata.version = version
|
|
58
|
+
|
|
59
|
+
cmd = dist.get_command_obj("bdist_wheel")
|
|
60
|
+
cmd.ensure_finalized()
|
|
61
|
+
python_tag, abi_tag, plat_tag = cmd.get_tag() # type: ignore[attr-defined]
|
|
62
|
+
dist_name = cmd.wheel_dist_name # type: ignore[attr-defined]
|
|
63
|
+
|
|
64
|
+
return f"{dist_name}-{python_tag}-{abi_tag}-{plat_tag}.whl"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
print(predict_wheel_filename(*sys.argv[1:4]))
|
csspin_python/pytest.py
CHANGED
csspin_python/python.py
CHANGED
|
@@ -191,7 +191,7 @@ def wheel(
|
|
|
191
191
|
for build_path in {Path(path).absolute() for path in search_paths}:
|
|
192
192
|
try:
|
|
193
193
|
echo("Building PEP 517-like wheel")
|
|
194
|
-
|
|
194
|
+
cmd = [
|
|
195
195
|
"python",
|
|
196
196
|
"-m",
|
|
197
197
|
"build",
|
|
@@ -199,20 +199,28 @@ def wheel(
|
|
|
199
199
|
build_path,
|
|
200
200
|
"-o",
|
|
201
201
|
"{spin.project_root}/dist",
|
|
202
|
-
|
|
202
|
+
]
|
|
203
|
+
if cfg.verbosity <= Verbosity.NORMAL:
|
|
204
|
+
cmd.append("--config-setting=quiet=true")
|
|
205
|
+
sh(*cmd)
|
|
203
206
|
except Abort:
|
|
204
207
|
echo("Building does not seem to work, use legacy setup.py style")
|
|
205
208
|
with cd(build_path):
|
|
206
|
-
|
|
209
|
+
cmd = [
|
|
207
210
|
"python",
|
|
208
211
|
"setup.py",
|
|
209
|
-
|
|
212
|
+
]
|
|
213
|
+
if cfg.verbosity <= Verbosity.NORMAL:
|
|
214
|
+
cmd.append("-q")
|
|
215
|
+
cmd += [
|
|
216
|
+
"build",
|
|
210
217
|
"-b",
|
|
211
218
|
"{spin.project_root}/build",
|
|
212
219
|
"bdist_wheel",
|
|
213
220
|
"-d",
|
|
214
221
|
"{spin.project_root}/dist",
|
|
215
|
-
|
|
222
|
+
]
|
|
223
|
+
sh(*cmd)
|
|
216
224
|
|
|
217
225
|
|
|
218
226
|
@task()
|
|
@@ -297,8 +305,13 @@ def _check_venv( # pylint: disable=too-many-return-statements
|
|
|
297
305
|
cfg: ConfigTree,
|
|
298
306
|
) -> bool:
|
|
299
307
|
"""
|
|
300
|
-
Checks whether the venv is actually a venv compatible
|
|
301
|
-
|
|
308
|
+
Checks whether the venv is actually a venv compatible with our configuration
|
|
309
|
+
and not just some dir.
|
|
310
|
+
|
|
311
|
+
Uses absolute interpreter paths and runs *outside* the subprocess environment
|
|
312
|
+
on purpose: it must work while inspecting an existing venv to decide whether a
|
|
313
|
+
``python.version`` change requires rebuilding it -- i.e. before the new
|
|
314
|
+
interpreter is provisioned.
|
|
302
315
|
"""
|
|
303
316
|
try:
|
|
304
317
|
python_version = (
|
|
@@ -402,6 +415,11 @@ def configure(cfg: ConfigTree) -> None:
|
|
|
402
415
|
if cfg.python.aws_auth.enabled:
|
|
403
416
|
_check_aws_token_validity(cfg)
|
|
404
417
|
|
|
418
|
+
# Provide spin's subprocess environment (see python_env). Registered here
|
|
419
|
+
# rather than in init so it is also available to the provision and
|
|
420
|
+
# finalize_provision hooks of plugins depending on this one.
|
|
421
|
+
cfg.spin.subprocess_environment = lambda: python_env(cfg)
|
|
422
|
+
|
|
405
423
|
|
|
406
424
|
def init(cfg: ConfigTree) -> None:
|
|
407
425
|
"""Initialize the python plugin"""
|
|
@@ -413,32 +431,85 @@ def init(cfg: ConfigTree) -> None:
|
|
|
413
431
|
" project. You might want to run spin with the 'provision'"
|
|
414
432
|
" task."
|
|
415
433
|
)
|
|
416
|
-
venv_init(cfg)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
# We won't activate more than once.
|
|
420
|
-
ACTIVATED = False
|
|
421
434
|
|
|
422
435
|
|
|
423
436
|
def venv_init(cfg: ConfigTree) -> None:
|
|
424
437
|
"""Activate the virtual environment"""
|
|
425
|
-
|
|
426
|
-
if
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
438
|
+
activate_this = cfg.python.scriptdir / "activate_this.py"
|
|
439
|
+
if not exists(activate_this):
|
|
440
|
+
die(
|
|
441
|
+
f"{cfg.python.venv} does not exist. You may want to provision"
|
|
442
|
+
" it using 'spin provision'"
|
|
443
|
+
)
|
|
444
|
+
if sys.platform == "win32":
|
|
445
|
+
echo(f"{cfg.python.scriptdir}\\activate.ps1")
|
|
446
|
+
else:
|
|
447
|
+
echo(f". {cfg.python.scriptdir}/activate")
|
|
448
|
+
with open(activate_this, encoding="utf-8") as file:
|
|
449
|
+
exec( # pylint: disable=exec-used # nosec
|
|
450
|
+
file.read(), {"__file__": activate_this}
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@contextmanager
|
|
455
|
+
def python_env(cfg: ConfigTree) -> Generator:
|
|
456
|
+
"""
|
|
457
|
+
Context manager to activate the subprocess/python virtual environment and
|
|
458
|
+
deactivate it once done.
|
|
459
|
+
|
|
460
|
+
Registered as ``spin.subprocess_environment`` by the plugin's
|
|
461
|
+
:py:func:`configure` hook, so ``csspin.sh`` activates the virtual environment
|
|
462
|
+
around the commands it spawns. Activation is conditional: while the virtual
|
|
463
|
+
environment has not been created yet (e.g. early during ``spin provision``)
|
|
464
|
+
this is a no-op and the command runs in spin's in-process environment; once
|
|
465
|
+
the venv exists it is activated. Saving and restoring
|
|
466
|
+
``os.environ``, ``sys.path`` and the ``sys`` prefix attributes makes nesting
|
|
467
|
+
safe -- an explicit ``with python_env(cfg):`` block whose inner ``csspin.sh``
|
|
468
|
+
re-enters this context.
|
|
469
|
+
|
|
470
|
+
See the "The subprocess environment" section of csspin's plugin development
|
|
471
|
+
guide for the general concept.
|
|
472
|
+
"""
|
|
473
|
+
if not exists(cfg.python.scriptdir / "activate_this.py"):
|
|
474
|
+
# The virtual environment has not been provisioned (yet); run the command
|
|
475
|
+
# in spin's in-process environment instead of activating.
|
|
476
|
+
yield
|
|
477
|
+
else:
|
|
478
|
+
|
|
479
|
+
def interpolate_environ_value(value: str) -> str:
|
|
480
|
+
if not value:
|
|
481
|
+
return ""
|
|
482
|
+
value = str(value)
|
|
483
|
+
keys = re.findall(r"{(?P<key>\w+?)}", value)
|
|
484
|
+
for key in keys:
|
|
485
|
+
if key in os.environ:
|
|
486
|
+
value = value.replace(f"{{{key}}}", os.environ[key])
|
|
487
|
+
return value
|
|
488
|
+
|
|
489
|
+
missing = object()
|
|
490
|
+
old_env = os.environ.copy()
|
|
491
|
+
old_sys_path = sys.path.copy()
|
|
492
|
+
old_sys_prefix = sys.prefix
|
|
493
|
+
old_real_prefix = getattr(sys, "real_prefix", missing)
|
|
494
|
+
try:
|
|
495
|
+
for name, value in EXPORTS:
|
|
496
|
+
os.environ[name] = interpolate_environ_value(value)
|
|
497
|
+
|
|
498
|
+
venv_init(cfg)
|
|
499
|
+
yield
|
|
500
|
+
finally:
|
|
501
|
+
echo(f"deactivate {cfg.python.venv}")
|
|
502
|
+
os.environ.clear()
|
|
503
|
+
os.environ.update(old_env)
|
|
504
|
+
sys.path = old_sys_path
|
|
505
|
+
sys.prefix = old_sys_prefix
|
|
506
|
+
# activate_this.py sets sys.real_prefix on activation; restore it (or
|
|
507
|
+
# remove it) so the attribute does not leak past this context.
|
|
508
|
+
if old_real_prefix is missing:
|
|
509
|
+
if hasattr(sys, "real_prefix"):
|
|
510
|
+
delattr(sys, "real_prefix")
|
|
511
|
+
else:
|
|
512
|
+
setattr(sys, "real_prefix", old_real_prefix)
|
|
442
513
|
|
|
443
514
|
|
|
444
515
|
class ActivateScriptPatcher(abc.ABC):
|
|
@@ -975,7 +1046,6 @@ def venv_provision( # pylint: disable=too-many-branches,missing-function-docstr
|
|
|
975
1046
|
info("Provisioning venv '{python.venv}'")
|
|
976
1047
|
cfg.python.provisioner.provision_venv(cfg)
|
|
977
1048
|
|
|
978
|
-
# This sets PATH to the venv
|
|
979
1049
|
init(cfg)
|
|
980
1050
|
|
|
981
1051
|
_configure_pipconf(cfg)
|
|
@@ -1085,15 +1155,15 @@ def _check_aws_token_validity( # pylint: disable=too-many-locals
|
|
|
1085
1155
|
|
|
1086
1156
|
import time
|
|
1087
1157
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1158
|
+
client_secret = interpolate1(cfg.python.aws_auth.client_secret) or os.getenv(
|
|
1159
|
+
"CS_AWS_OIDC_CLIENT_SECRET"
|
|
1160
|
+
)
|
|
1161
|
+
static_oidc = interpolate1(cfg.python.aws_auth.static_oidc).lower() == "true"
|
|
1162
|
+
|
|
1163
|
+
if static_oidc and not client_secret:
|
|
1094
1164
|
die(
|
|
1095
1165
|
"Please provide a client secret for CodeArtifact access via"
|
|
1096
|
-
" 'python.aws_auth.client_secret'."
|
|
1166
|
+
" 'python.aws_auth.client_secret' when using static OIDC."
|
|
1097
1167
|
)
|
|
1098
1168
|
|
|
1099
1169
|
current_time = int(time.time())
|
|
@@ -1126,8 +1196,7 @@ def _check_aws_token_validity( # pylint: disable=too-many-locals
|
|
|
1126
1196
|
|
|
1127
1197
|
opts = {
|
|
1128
1198
|
"client_secret": client_secret,
|
|
1129
|
-
"static_oidc":
|
|
1130
|
-
== "true",
|
|
1199
|
+
"static_oidc": static_oidc,
|
|
1131
1200
|
}
|
|
1132
1201
|
if cfg.python.aws_auth.client_id:
|
|
1133
1202
|
opts["client_id"] = interpolate1(cfg.python.aws_auth.client_id)
|
csspin_python/python_sbom.py
CHANGED
|
@@ -25,6 +25,7 @@ from importlib.metadata import metadata as _pkg_metadata
|
|
|
25
25
|
from importlib.metadata import version as _pkg_version
|
|
26
26
|
from subprocess import DEVNULL, PIPE
|
|
27
27
|
from tempfile import TemporaryDirectory
|
|
28
|
+
from urllib.parse import quote, urlencode, urlparse
|
|
28
29
|
|
|
29
30
|
from csspin import (
|
|
30
31
|
Verbosity,
|
|
@@ -40,12 +41,14 @@ from csspin import (
|
|
|
40
41
|
)
|
|
41
42
|
from csspin.tree import ConfigTree
|
|
42
43
|
from packaging.requirements import Requirement
|
|
44
|
+
from packaging.utils import canonicalize_name
|
|
43
45
|
from path import Path
|
|
44
46
|
|
|
45
47
|
defaults = config(
|
|
46
48
|
cyclonedx_bom_version="7.3.0",
|
|
47
49
|
project_paths=["{spin.project_root}"],
|
|
48
|
-
|
|
50
|
+
repository_url="{python.index_url}",
|
|
51
|
+
requires=config(spin=["csspin_python.python"], python=["setuptools"]),
|
|
49
52
|
)
|
|
50
53
|
|
|
51
54
|
|
|
@@ -69,12 +72,23 @@ def sbom(cfg: ConfigTree) -> None:
|
|
|
69
72
|
|
|
70
73
|
project_path = Path(project_path).absolute()
|
|
71
74
|
metadata = get_project_metadata(project_path, cfg.python.index_url)
|
|
75
|
+
project_name, project_version = metadata.get("name"), metadata.get("version")
|
|
76
|
+
|
|
77
|
+
file_name = _predict_wheel_filename(
|
|
78
|
+
project_path, project_name, project_version, cfg.python.python
|
|
79
|
+
)
|
|
72
80
|
third_party_deps = _collect_thirdparty_deps(
|
|
73
81
|
metadata.get("requires_dist", set()), python_version=cfg.python.version
|
|
74
82
|
)
|
|
75
83
|
sbom_json = json.loads(_run_cyclonedx(cfg, third_party_deps, stderr))
|
|
76
|
-
_enrich_sbom(
|
|
77
|
-
|
|
84
|
+
_enrich_sbom(
|
|
85
|
+
sbom_json,
|
|
86
|
+
metadata,
|
|
87
|
+
third_party_deps,
|
|
88
|
+
cfg.python_sbom.repository_url,
|
|
89
|
+
file_name,
|
|
90
|
+
)
|
|
91
|
+
_write_sbom(cfg, sbom_json, project_name)
|
|
78
92
|
|
|
79
93
|
|
|
80
94
|
def cleanup(cfg: ConfigTree) -> None:
|
|
@@ -114,7 +128,7 @@ def _ensure_cyclonedx_venv(cfg: ConfigTree, binary_dir: str, quiet: str | None)
|
|
|
114
128
|
)
|
|
115
129
|
rmtree(venv_cdx)
|
|
116
130
|
|
|
117
|
-
sh(cfg.python.interpreter, "-m", "venv", venv_cdx)
|
|
131
|
+
sh(cfg.python.interpreter, "-m", "venv", venv_cdx, use_subprocess_environment=False)
|
|
118
132
|
sh(
|
|
119
133
|
interpreter_cdx,
|
|
120
134
|
"-m",
|
|
@@ -125,6 +139,7 @@ def _ensure_cyclonedx_venv(cfg: ConfigTree, binary_dir: str, quiet: str | None)
|
|
|
125
139
|
"--index-url",
|
|
126
140
|
cfg.python.index_url,
|
|
127
141
|
"cyclonedx-bom==" + requested_version,
|
|
142
|
+
use_subprocess_environment=False,
|
|
128
143
|
)
|
|
129
144
|
with memoizer(memo_file) as memo:
|
|
130
145
|
memo.add(memo_key)
|
|
@@ -143,7 +158,7 @@ def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) ->
|
|
|
143
158
|
with TemporaryDirectory() as tmp_dir:
|
|
144
159
|
venv = Path(tmp_dir) / "venv"
|
|
145
160
|
interpreter = venv / binary_dir / "python" + cfg.platform.exe
|
|
146
|
-
sh(cfg.python.interpreter, "-m", "venv", venv)
|
|
161
|
+
sh(cfg.python.interpreter, "-m", "venv", venv, use_subprocess_environment=False)
|
|
147
162
|
if third_party_deps:
|
|
148
163
|
sh(
|
|
149
164
|
interpreter,
|
|
@@ -158,10 +173,28 @@ def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) ->
|
|
|
158
173
|
for constraint in cfg.python.constraints
|
|
159
174
|
],
|
|
160
175
|
*third_party_deps,
|
|
176
|
+
use_subprocess_environment=False,
|
|
161
177
|
stderr=stderr,
|
|
162
178
|
)
|
|
163
|
-
sh(
|
|
164
|
-
|
|
179
|
+
sh(
|
|
180
|
+
interpreter,
|
|
181
|
+
"-m",
|
|
182
|
+
"pip",
|
|
183
|
+
quiet,
|
|
184
|
+
"uninstall",
|
|
185
|
+
"-y",
|
|
186
|
+
"pip",
|
|
187
|
+
use_subprocess_environment=False,
|
|
188
|
+
)
|
|
189
|
+
return backtick( # type: ignore[no-any-return]
|
|
190
|
+
interpreter_cdx,
|
|
191
|
+
"-m",
|
|
192
|
+
"cyclonedx_py",
|
|
193
|
+
"environment",
|
|
194
|
+
venv,
|
|
195
|
+
use_subprocess_environment=False,
|
|
196
|
+
stderr=stderr,
|
|
197
|
+
)
|
|
165
198
|
|
|
166
199
|
|
|
167
200
|
def _write_sbom(cfg: ConfigTree, sbom_json: dict, project_name: str) -> None:
|
|
@@ -197,7 +230,52 @@ def _parse_authors(author_name: str, author_email: str) -> str:
|
|
|
197
230
|
return ", ".join(f"{name} ({addr})" for name, addr in entries)
|
|
198
231
|
|
|
199
232
|
|
|
200
|
-
def
|
|
233
|
+
def _repository_base_url(index_url: str) -> str:
|
|
234
|
+
"""Reduce a pip index URL to the repository base URL a purl expects."""
|
|
235
|
+
parsed = urlparse(index_url)
|
|
236
|
+
segments = [segment for segment in parsed.path.split("/") if segment]
|
|
237
|
+
if segments and segments[-1] in ("simple", "+simple"):
|
|
238
|
+
segments.pop()
|
|
239
|
+
return parsed._replace(
|
|
240
|
+
netloc=parsed.netloc.rsplit("@", 1)[-1], # drop basic-auth credentials
|
|
241
|
+
path="/".join([""] + segments),
|
|
242
|
+
).geturl()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _build_purl(name: str, version: str, repository_url: str, file_name: str) -> str:
|
|
246
|
+
"""Build the pypi Package URL for a project, per tn-0058.
|
|
247
|
+
|
|
248
|
+
``repository_url`` is only added when the package isn't published to
|
|
249
|
+
public pypi.org, since type, name and version are unique there already.
|
|
250
|
+
"""
|
|
251
|
+
purl = f"pkg:pypi/{canonicalize_name(name)}@{quote(version, safe=':')}"
|
|
252
|
+
|
|
253
|
+
qualifiers = {"file_name": file_name}
|
|
254
|
+
if urlparse(repository_url).hostname != "pypi.org":
|
|
255
|
+
qualifiers["repository_url"] = _repository_base_url(repository_url)
|
|
256
|
+
|
|
257
|
+
return f"{purl}?{urlencode(sorted(qualifiers.items()), safe=':')}"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _predict_wheel_filename(
|
|
261
|
+
project_path: str, name: str, version: str, interpreter: str
|
|
262
|
+
) -> str:
|
|
263
|
+
"""
|
|
264
|
+
Predict the wheel filename ``python:wheel`` will produce for
|
|
265
|
+
``project_path``, without building it.
|
|
266
|
+
|
|
267
|
+
``name`` and ``version`` come from :func:`get_project_metadata`.
|
|
268
|
+
``interpreter`` must be ``project_path``'s own, since it owns the tags.
|
|
269
|
+
Cached since the prediction is identical for every caller within the same
|
|
270
|
+
process.
|
|
271
|
+
"""
|
|
272
|
+
script = Path(__file__).parent / "_predict_wheel_filename.py"
|
|
273
|
+
return backtick(interpreter, script, project_path, name, version).strip() # type: ignore[no-any-return] # noqa: E501
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _build_primary_component(
|
|
277
|
+
metadata: dict, repository_url: str, file_name: str
|
|
278
|
+
) -> tuple[dict, str]:
|
|
201
279
|
"""Build the CycloneDX primary component dict and its bom-ref from project metadata."""
|
|
202
280
|
if not (name := metadata.get("name")):
|
|
203
281
|
die("Project metadata is missing 'name'.")
|
|
@@ -206,6 +284,7 @@ def _build_primary_component(metadata: dict) -> tuple[dict, str]:
|
|
|
206
284
|
if not (license_id := metadata.get("license")):
|
|
207
285
|
die("Project metadata is missing 'license'.")
|
|
208
286
|
|
|
287
|
+
purl = _build_purl(str(name), str(version), repository_url, file_name)
|
|
209
288
|
authors = _parse_authors(
|
|
210
289
|
author_name=metadata.get("author", "").strip(),
|
|
211
290
|
author_email=metadata.get("author_email", "").strip(),
|
|
@@ -216,6 +295,7 @@ def _build_primary_component(metadata: dict) -> tuple[dict, str]:
|
|
|
216
295
|
"bom-ref": primary_ref,
|
|
217
296
|
"licenses": [{"expression": license_id}],
|
|
218
297
|
"name": name,
|
|
298
|
+
"purl": purl,
|
|
219
299
|
"type": "application",
|
|
220
300
|
"version": version,
|
|
221
301
|
}
|
|
@@ -226,9 +306,13 @@ def _enrich_sbom(
|
|
|
226
306
|
sbom_json: dict,
|
|
227
307
|
metadata: dict,
|
|
228
308
|
third_party_deps: set[str],
|
|
309
|
+
repository_url: str,
|
|
310
|
+
file_name: str,
|
|
229
311
|
) -> None:
|
|
230
312
|
"""Add the primary component and its dependency entry to the CycloneDX document."""
|
|
231
|
-
component, primary_ref = _build_primary_component(
|
|
313
|
+
component, primary_ref = _build_primary_component(
|
|
314
|
+
metadata, repository_url, file_name
|
|
315
|
+
)
|
|
232
316
|
existing_metadata = sbom_json.get("metadata", {})
|
|
233
317
|
existing_tools = existing_metadata.get("tools", {})
|
|
234
318
|
existing_tool_components = (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csspin-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Plugin-package for csspin providing Python related plugins
|
|
5
5
|
Author-email: CONTACT Software GmbH <info@contact-software.com>
|
|
6
6
|
Maintainer-email: Waleri Enns <waleri.enns@contact-software.com>, Benjamin Thomas Schwertfeger <benjaminthomas.schwertfeger@contact-software.com>, Fabian Hafer <fabian.hafer@contact-software.com>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
csspin_python/_predict_wheel_filename.py,sha256=sz09QpDOPQesG-QAANjH607QSkS3PFo0qxPifG1y85o,2399
|
|
1
2
|
csspin_python/behave.py,sha256=iJZeyIqB7V_NzTdLTZldNY9W_GGwCWkXe6WY69wpDqs,4997
|
|
2
3
|
csspin_python/behave_schema.yaml,sha256=8qoOCK-uTmwgRRW29urgK0X_kgn0zO0X34v89bvii2w,1241
|
|
3
4
|
csspin_python/debugpy.py,sha256=v0ZZopv5TNoSaFf2kiePsw9OmhBpjfOBFh0u71jTcnQ,962
|
|
@@ -6,20 +7,18 @@ csspin_python/devpi.py,sha256=o7O06Dw-xt47y-M74TShcl9MUMaW78aFmw6i6ISYwoU,2351
|
|
|
6
7
|
csspin_python/devpi_schema.yaml,sha256=2gPATWjVcfvCTrGZX2FK6wH8hh9KS0XzZ35JvZeJGEU,487
|
|
7
8
|
csspin_python/playwright.py,sha256=oFfphLqa4AB6K9vasCUFHN0kFXu63n3ocrsqVuRp4-0,5102
|
|
8
9
|
csspin_python/playwright_schema.yaml,sha256=TSeR16YHa7m7bfO59F2eMV-jXcglluTJdEpUeL16saY,1178
|
|
9
|
-
csspin_python/pytest.py,sha256=
|
|
10
|
+
csspin_python/pytest.py,sha256=vSLtmS-kNO0NPo_tszfEm5zGwJb6Yb5IGS-hA7CNguo,4544
|
|
10
11
|
csspin_python/pytest_schema.yaml,sha256=tzXtdF6MvGC9v59EVRJFfLeMMHqPsXcFXy2zJtRECBI,1535
|
|
11
|
-
csspin_python/python.py,sha256=
|
|
12
|
-
csspin_python/python_sbom.py,sha256=
|
|
13
|
-
csspin_python/python_sbom_schema.yaml,sha256=
|
|
12
|
+
csspin_python/python.py,sha256=SO5RkNdpZPcux7KnuDHVTaNIVQAmU9xjwTtJG4v97NE,40725
|
|
13
|
+
csspin_python/python_sbom.py,sha256=FYx9DMDSeooB4lXuIisUsMwBMEj1Hl-QVl1AlDdTmZk,13381
|
|
14
|
+
csspin_python/python_sbom_schema.yaml,sha256=QysysjGJ6n1WEoLBqYHcdbojuEomUvL8QG4ZSWQW2_8,739
|
|
14
15
|
csspin_python/python_schema.yaml,sha256=pgVVjByUYjxQWek7aFmjQzRwmq2ROLvHYgwGPMrT9sM,6351
|
|
15
16
|
csspin_python/radon.py,sha256=uFqm6FEi5oWj-_XVaAm3s9cam0cUmr1_FwRf40K6xWs,1876
|
|
16
17
|
csspin_python/radon_schema.yaml,sha256=rlRzXw5z4XbjOVznRiUxWGP4E9hx1Jm-gGw1iQiYzE0,548
|
|
17
18
|
csspin_python/uv_provisioner.py,sha256=1e-_Sb39JrqNWyaUNeBX59R5tutXLJ1ZsT7urCN1U0I,6044
|
|
18
19
|
csspin_python/uv_provisioner_schema.yaml,sha256=Y8ZNC2OMnhR8Us3WUXAXK9hMjqGWAKFJB2puX4X5XNQ,727
|
|
19
|
-
csspin_python-
|
|
20
|
-
csspin_python-
|
|
21
|
-
csspin_python-
|
|
22
|
-
csspin_python-
|
|
23
|
-
csspin_python-
|
|
24
|
-
csspin_python-4.1.0rc2.dist-info/top_level.txt,sha256=QSeglMEGbFu1z4L6MCQYwo01NgL0KojWvC4rzgMQ8gU,14
|
|
25
|
-
csspin_python-4.1.0rc2.dist-info/RECORD,,
|
|
20
|
+
csspin_python-5.0.0.dist-info/licenses/LICENSE,sha256=4MAecetnRTQw5DlHtiikDSzKWO1xVLwzM5_DsPMYlnE,10172
|
|
21
|
+
csspin_python-5.0.0.dist-info/METADATA,sha256=a4GiqeHydRsKdxlj_uH-B9bAfoCfbQBSowJJrnCMaJI,5254
|
|
22
|
+
csspin_python-5.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
23
|
+
csspin_python-5.0.0.dist-info/top_level.txt,sha256=QSeglMEGbFu1z4L6MCQYwo01NgL0KojWvC4rzgMQ8gU,14
|
|
24
|
+
csspin_python-5.0.0.dist-info/RECORD,,
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"files": [
|
|
3
|
-
"spinfile.yaml",
|
|
4
|
-
"README.rst",
|
|
5
|
-
".pre-commit-config.yaml",
|
|
6
|
-
"CONTRIBUTING.md",
|
|
7
|
-
"requirements-dev.txt",
|
|
8
|
-
"LICENSE",
|
|
9
|
-
".gitignore",
|
|
10
|
-
".gitlab-ci.yml",
|
|
11
|
-
".prettierrc.yaml",
|
|
12
|
-
"pyproject.toml",
|
|
13
|
-
".readthedocs.yaml",
|
|
14
|
-
"doc/virtual_environment.rst",
|
|
15
|
-
"doc/development.rst",
|
|
16
|
-
"doc/relnotes.rst",
|
|
17
|
-
"doc/index.rst",
|
|
18
|
-
"doc/links.rst",
|
|
19
|
-
"doc/conf.py",
|
|
20
|
-
"doc/installation.rst",
|
|
21
|
-
"doc/plugins/playwright.rst",
|
|
22
|
-
"doc/plugins/uv_provisioner.rst",
|
|
23
|
-
"doc/plugins/debugpy.rst",
|
|
24
|
-
"doc/plugins/devpi.rst",
|
|
25
|
-
"doc/plugins/python.rst",
|
|
26
|
-
"doc/plugins/python_sbom.rst",
|
|
27
|
-
"doc/plugins/pytest.rst",
|
|
28
|
-
"doc/plugins/behave.rst",
|
|
29
|
-
"doc/plugins/radon.rst",
|
|
30
|
-
"tests/conftest.py",
|
|
31
|
-
"tests/unit/test_uv_provisioner.py",
|
|
32
|
-
"tests/unit/test_python_sbom.py",
|
|
33
|
-
"tests/unit/test_python.py",
|
|
34
|
-
"tests/integration/test_provisioning.py",
|
|
35
|
-
"tests/integration/conftest.py",
|
|
36
|
-
"tests/integration/fixtures/activation_script/test_env.ps1",
|
|
37
|
-
"tests/integration/fixtures/activation_script/test_env.sh",
|
|
38
|
-
"tests/integration/fixtures/activation_script/plugins/dummy.py",
|
|
39
|
-
"tests/integration/yamls/pytest.yaml",
|
|
40
|
-
"tests/integration/yamls/uv_provisioner_use.yaml",
|
|
41
|
-
"tests/integration/yamls/pytest_with_playwright.yaml",
|
|
42
|
-
"tests/integration/yamls/python_version.yaml",
|
|
43
|
-
"tests/integration/yamls/playwright.yaml",
|
|
44
|
-
"tests/integration/yamls/devpi.yaml",
|
|
45
|
-
"tests/integration/yamls/python_use.yaml",
|
|
46
|
-
"tests/integration/yamls/behave.yaml",
|
|
47
|
-
"tests/integration/yamls/radon.yaml",
|
|
48
|
-
"tests/integration/yamls/python_activation_scripts.yaml",
|
|
49
|
-
"tests/integration/yamls/uv_provisioner.yaml",
|
|
50
|
-
"tests/acceptance/test_aws_auth.py",
|
|
51
|
-
"tests/acceptance/python_sbom/spinfile.yaml",
|
|
52
|
-
"tests/acceptance/python_sbom/test_sbom.py",
|
|
53
|
-
"tests/acceptance/python_sbom/pyproject.toml",
|
|
54
|
-
"tests/acceptance/python_constraints/test_constraints.py",
|
|
55
|
-
"tests/acceptance/python_constraints/spinfile.yaml",
|
|
56
|
-
"tests/acceptance/python_constraints/constraints.txt",
|
|
57
|
-
"tests/acceptance/yamls/python_aws_auth.yaml",
|
|
58
|
-
"src/csspin_python/debugpy_schema.yaml",
|
|
59
|
-
"src/csspin_python/devpi_schema.yaml",
|
|
60
|
-
"src/csspin_python/uv_provisioner_schema.yaml",
|
|
61
|
-
"src/csspin_python/pytest.py",
|
|
62
|
-
"src/csspin_python/playwright_schema.yaml",
|
|
63
|
-
"src/csspin_python/behave.py",
|
|
64
|
-
"src/csspin_python/pytest_schema.yaml",
|
|
65
|
-
"src/csspin_python/uv_provisioner.py",
|
|
66
|
-
"src/csspin_python/radon.py",
|
|
67
|
-
"src/csspin_python/python_sbom_schema.yaml",
|
|
68
|
-
"src/csspin_python/python_sbom.py",
|
|
69
|
-
"src/csspin_python/devpi.py",
|
|
70
|
-
"src/csspin_python/debugpy.py",
|
|
71
|
-
"src/csspin_python/playwright.py",
|
|
72
|
-
"src/csspin_python/behave_schema.yaml",
|
|
73
|
-
"src/csspin_python/python.py",
|
|
74
|
-
"src/csspin_python/python_schema.yaml",
|
|
75
|
-
"src/csspin_python/radon_schema.yaml",
|
|
76
|
-
".gitlab/merge_request_templates/default.md"
|
|
77
|
-
]
|
|
78
|
-
}
|
|
File without changes
|
|
File without changes
|