csspin-python 4.1.0rc2__py3-none-any.whl → 6.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 +199 -94
- csspin_python/python_sbom.py +98 -10
- csspin_python/python_sbom_schema.yaml +5 -0
- csspin_python/python_schema.yaml +13 -11
- csspin_python/uv_provisioner.py +23 -5
- {csspin_python-4.1.0rc2.dist-info → csspin_python-6.0.0.dist-info}/METADATA +2 -2
- csspin_python-6.0.0.dist-info/RECORD +24 -0
- {csspin_python-4.1.0rc2.dist-info → csspin_python-6.0.0.dist-info}/WHEEL +1 -1
- csspin_python-4.1.0rc2.dist-info/RECORD +0 -25
- 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-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {csspin_python-4.1.0rc2.dist-info → csspin_python-6.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
|
@@ -81,6 +81,8 @@ from functools import cache
|
|
|
81
81
|
from subprocess import CalledProcessError, check_output
|
|
82
82
|
from textwrap import dedent, indent
|
|
83
83
|
from typing import Generator, Iterable, Type, Union
|
|
84
|
+
from urllib.error import HTTPError
|
|
85
|
+
from urllib.parse import urljoin, urlsplit
|
|
84
86
|
|
|
85
87
|
try:
|
|
86
88
|
from typing import Self # type: ignore[attr-defined]
|
|
@@ -162,11 +164,6 @@ defaults = config(
|
|
|
162
164
|
enabled=False,
|
|
163
165
|
memo="{spin.spin_dir}/aws_auth.memo",
|
|
164
166
|
key_duration=3600 * 10, # 10 hours
|
|
165
|
-
static_oidc=False,
|
|
166
|
-
index="16.0/simple",
|
|
167
|
-
# Need to set client secret to empty string, otherwise the string "None"
|
|
168
|
-
# would be handled as secret and obfucsated in logs.
|
|
169
|
-
client_secret="", # nosec: B106
|
|
170
167
|
),
|
|
171
168
|
index_url="https://pypi.org/simple",
|
|
172
169
|
skip_js_build=None,
|
|
@@ -187,11 +184,13 @@ def wheel(
|
|
|
187
184
|
) -> None:
|
|
188
185
|
"""Build a wheel of the current project and any additional wheels."""
|
|
189
186
|
setenv(PIP_INDEX_URL=cfg.python.index_url)
|
|
187
|
+
if cfg.python.extra_index_urls:
|
|
188
|
+
setenv(PIP_EXTRA_INDEX_URL=" ".join(cfg.python.extra_index_urls))
|
|
190
189
|
search_paths = paths or cfg.python.build_wheels
|
|
191
190
|
for build_path in {Path(path).absolute() for path in search_paths}:
|
|
192
191
|
try:
|
|
193
192
|
echo("Building PEP 517-like wheel")
|
|
194
|
-
|
|
193
|
+
cmd = [
|
|
195
194
|
"python",
|
|
196
195
|
"-m",
|
|
197
196
|
"build",
|
|
@@ -199,20 +198,28 @@ def wheel(
|
|
|
199
198
|
build_path,
|
|
200
199
|
"-o",
|
|
201
200
|
"{spin.project_root}/dist",
|
|
202
|
-
|
|
201
|
+
]
|
|
202
|
+
if cfg.verbosity <= Verbosity.NORMAL:
|
|
203
|
+
cmd.append("--config-setting=quiet=true")
|
|
204
|
+
sh(*cmd)
|
|
203
205
|
except Abort:
|
|
204
206
|
echo("Building does not seem to work, use legacy setup.py style")
|
|
205
207
|
with cd(build_path):
|
|
206
|
-
|
|
208
|
+
cmd = [
|
|
207
209
|
"python",
|
|
208
210
|
"setup.py",
|
|
209
|
-
|
|
211
|
+
]
|
|
212
|
+
if cfg.verbosity <= Verbosity.NORMAL:
|
|
213
|
+
cmd.append("-q")
|
|
214
|
+
cmd += [
|
|
215
|
+
"build",
|
|
210
216
|
"-b",
|
|
211
217
|
"{spin.project_root}/build",
|
|
212
218
|
"bdist_wheel",
|
|
213
219
|
"-d",
|
|
214
220
|
"{spin.project_root}/dist",
|
|
215
|
-
|
|
221
|
+
]
|
|
222
|
+
sh(*cmd)
|
|
216
223
|
|
|
217
224
|
|
|
218
225
|
@task()
|
|
@@ -297,8 +304,13 @@ def _check_venv( # pylint: disable=too-many-return-statements
|
|
|
297
304
|
cfg: ConfigTree,
|
|
298
305
|
) -> bool:
|
|
299
306
|
"""
|
|
300
|
-
Checks whether the venv is actually a venv compatible
|
|
301
|
-
|
|
307
|
+
Checks whether the venv is actually a venv compatible with our configuration
|
|
308
|
+
and not just some dir.
|
|
309
|
+
|
|
310
|
+
Uses absolute interpreter paths and runs *outside* the subprocess environment
|
|
311
|
+
on purpose: it must work while inspecting an existing venv to decide whether a
|
|
312
|
+
``python.version`` change requires rebuilding it -- i.e. before the new
|
|
313
|
+
interpreter is provisioned.
|
|
302
314
|
"""
|
|
303
315
|
try:
|
|
304
316
|
python_version = (
|
|
@@ -402,6 +414,11 @@ def configure(cfg: ConfigTree) -> None:
|
|
|
402
414
|
if cfg.python.aws_auth.enabled:
|
|
403
415
|
_check_aws_token_validity(cfg)
|
|
404
416
|
|
|
417
|
+
# Provide spin's subprocess environment (see python_env). Registered here
|
|
418
|
+
# rather than in init so it is also available to the provision and
|
|
419
|
+
# finalize_provision hooks of plugins depending on this one.
|
|
420
|
+
cfg.spin.subprocess_environment = lambda: python_env(cfg)
|
|
421
|
+
|
|
405
422
|
|
|
406
423
|
def init(cfg: ConfigTree) -> None:
|
|
407
424
|
"""Initialize the python plugin"""
|
|
@@ -413,32 +430,85 @@ def init(cfg: ConfigTree) -> None:
|
|
|
413
430
|
" project. You might want to run spin with the 'provision'"
|
|
414
431
|
" task."
|
|
415
432
|
)
|
|
416
|
-
venv_init(cfg)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
# We won't activate more than once.
|
|
420
|
-
ACTIVATED = False
|
|
421
433
|
|
|
422
434
|
|
|
423
435
|
def venv_init(cfg: ConfigTree) -> None:
|
|
424
436
|
"""Activate the virtual environment"""
|
|
425
|
-
|
|
426
|
-
if
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
437
|
+
activate_this = cfg.python.scriptdir / "activate_this.py"
|
|
438
|
+
if not exists(activate_this):
|
|
439
|
+
die(
|
|
440
|
+
f"{cfg.python.venv} does not exist. You may want to provision"
|
|
441
|
+
" it using 'spin provision'"
|
|
442
|
+
)
|
|
443
|
+
if sys.platform == "win32":
|
|
444
|
+
echo(f"{cfg.python.scriptdir}\\activate.ps1")
|
|
445
|
+
else:
|
|
446
|
+
echo(f". {cfg.python.scriptdir}/activate")
|
|
447
|
+
with open(activate_this, encoding="utf-8") as file:
|
|
448
|
+
exec( # pylint: disable=exec-used # nosec
|
|
449
|
+
file.read(), {"__file__": activate_this}
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
@contextmanager
|
|
454
|
+
def python_env(cfg: ConfigTree) -> Generator:
|
|
455
|
+
"""
|
|
456
|
+
Context manager to activate the subprocess/python virtual environment and
|
|
457
|
+
deactivate it once done.
|
|
458
|
+
|
|
459
|
+
Registered as ``spin.subprocess_environment`` by the plugin's
|
|
460
|
+
:py:func:`configure` hook, so ``csspin.sh`` activates the virtual environment
|
|
461
|
+
around the commands it spawns. Activation is conditional: while the virtual
|
|
462
|
+
environment has not been created yet (e.g. early during ``spin provision``)
|
|
463
|
+
this is a no-op and the command runs in spin's in-process environment; once
|
|
464
|
+
the venv exists it is activated. Saving and restoring
|
|
465
|
+
``os.environ``, ``sys.path`` and the ``sys`` prefix attributes makes nesting
|
|
466
|
+
safe -- an explicit ``with python_env(cfg):`` block whose inner ``csspin.sh``
|
|
467
|
+
re-enters this context.
|
|
468
|
+
|
|
469
|
+
See the "The subprocess environment" section of csspin's plugin development
|
|
470
|
+
guide for the general concept.
|
|
471
|
+
"""
|
|
472
|
+
if not exists(cfg.python.scriptdir / "activate_this.py"):
|
|
473
|
+
# The virtual environment has not been provisioned (yet); run the command
|
|
474
|
+
# in spin's in-process environment instead of activating.
|
|
475
|
+
yield
|
|
476
|
+
else:
|
|
477
|
+
|
|
478
|
+
def interpolate_environ_value(value: str) -> str:
|
|
479
|
+
if not value:
|
|
480
|
+
return ""
|
|
481
|
+
value = str(value)
|
|
482
|
+
keys = re.findall(r"{(?P<key>\w+?)}", value)
|
|
483
|
+
for key in keys:
|
|
484
|
+
if key in os.environ:
|
|
485
|
+
value = value.replace(f"{{{key}}}", os.environ[key])
|
|
486
|
+
return value
|
|
487
|
+
|
|
488
|
+
missing = object()
|
|
489
|
+
old_env = os.environ.copy()
|
|
490
|
+
old_sys_path = sys.path.copy()
|
|
491
|
+
old_sys_prefix = sys.prefix
|
|
492
|
+
old_real_prefix = getattr(sys, "real_prefix", missing)
|
|
493
|
+
try:
|
|
494
|
+
for name, value in EXPORTS:
|
|
495
|
+
os.environ[name] = interpolate_environ_value(value)
|
|
496
|
+
|
|
497
|
+
venv_init(cfg)
|
|
498
|
+
yield
|
|
499
|
+
finally:
|
|
500
|
+
echo(f"deactivate {cfg.python.venv}")
|
|
501
|
+
os.environ.clear()
|
|
502
|
+
os.environ.update(old_env)
|
|
503
|
+
sys.path = old_sys_path
|
|
504
|
+
sys.prefix = old_sys_prefix
|
|
505
|
+
# activate_this.py sets sys.real_prefix on activation; restore it (or
|
|
506
|
+
# remove it) so the attribute does not leak past this context.
|
|
507
|
+
if old_real_prefix is missing:
|
|
508
|
+
if hasattr(sys, "real_prefix"):
|
|
509
|
+
delattr(sys, "real_prefix")
|
|
510
|
+
else:
|
|
511
|
+
setattr(sys, "real_prefix", old_real_prefix)
|
|
442
512
|
|
|
443
513
|
|
|
444
514
|
class ActivateScriptPatcher(abc.ABC):
|
|
@@ -721,7 +791,9 @@ class PythonActivate(ActivateScriptPatcher):
|
|
|
721
791
|
|
|
722
792
|
|
|
723
793
|
@cache
|
|
724
|
-
def get_project_metadata(
|
|
794
|
+
def get_project_metadata( # type: ignore[return] # pylint: disable=inconsistent-return-statements # noqa: E501
|
|
795
|
+
project_path: str, index_url: str, extra_index_urls: tuple[str, ...] = ()
|
|
796
|
+
) -> dict:
|
|
725
797
|
"""
|
|
726
798
|
Retrieve project metadata of ``project_path`` via ``python -m build
|
|
727
799
|
--metadata``.
|
|
@@ -730,12 +802,16 @@ def get_project_metadata(project_path: str, index_url: str) -> dict: # type: ig
|
|
|
730
802
|
every caller within the same process.
|
|
731
803
|
"""
|
|
732
804
|
setenv(PIP_INDEX_URL=index_url)
|
|
805
|
+
if extra_index_urls:
|
|
806
|
+
setenv(PIP_EXTRA_INDEX_URL=" ".join(extra_index_urls))
|
|
733
807
|
kwargs = {}
|
|
734
808
|
if CONFIG.verbosity < Verbosity.INFO:
|
|
735
809
|
kwargs["stderr"] = subprocess.DEVNULL
|
|
736
810
|
raw_metadata = backtick(
|
|
737
811
|
"python", "-m", "build", "--metadata", project_path, **kwargs
|
|
738
812
|
)
|
|
813
|
+
if extra_index_urls:
|
|
814
|
+
setenv(PIP_EXTRA_INDEX_URL=None)
|
|
739
815
|
setenv(PIP_INDEX_URL=None)
|
|
740
816
|
|
|
741
817
|
if raw_metadata:
|
|
@@ -975,7 +1051,6 @@ def venv_provision( # pylint: disable=too-many-branches,missing-function-docstr
|
|
|
975
1051
|
info("Provisioning venv '{python.venv}'")
|
|
976
1052
|
cfg.python.provisioner.provision_venv(cfg)
|
|
977
1053
|
|
|
978
|
-
# This sets PATH to the venv
|
|
979
1054
|
init(cfg)
|
|
980
1055
|
|
|
981
1056
|
_configure_pipconf(cfg)
|
|
@@ -1055,21 +1130,78 @@ def _configure_pipconf(cfg: ConfigTree, update: bool = False) -> None:
|
|
|
1055
1130
|
"index_url" in config_parser["global"] or "index-url" in config_parser["global"]
|
|
1056
1131
|
):
|
|
1057
1132
|
config_parser["global"]["index_url"] = interpolate1(cfg.python.index_url)
|
|
1133
|
+
|
|
1134
|
+
# Extra index urls are only configured via 'python.extra_index_urls', not
|
|
1135
|
+
# 'python.pipconf', so drop whatever the pipconf template declares rather
|
|
1136
|
+
# than merging it in.
|
|
1137
|
+
for key in ("extra_index_url", "extra-index-url"):
|
|
1138
|
+
config_parser["global"].pop(key, None)
|
|
1139
|
+
|
|
1140
|
+
if cfg.python.extra_index_urls:
|
|
1141
|
+
config_parser["global"]["extra_index_url"] = "\n".join(
|
|
1142
|
+
interpolate1(url) for url in cfg.python.extra_index_urls
|
|
1143
|
+
)
|
|
1058
1144
|
with open(_get_pipconf(cfg), mode="w", encoding="utf-8") as fd:
|
|
1059
1145
|
config_parser.write(fd)
|
|
1060
1146
|
|
|
1061
1147
|
|
|
1062
1148
|
def _obfuscate_index_url(index_url: str) -> None:
|
|
1063
|
-
"""Add the
|
|
1149
|
+
"""Add the credentials of an index URL to the secrets."""
|
|
1064
1150
|
|
|
1065
1151
|
from csspin import secrets
|
|
1066
1152
|
|
|
1067
|
-
|
|
1153
|
+
if password := urlsplit(index_url).password:
|
|
1154
|
+
secrets.add(password)
|
|
1068
1155
|
|
|
1069
1156
|
|
|
1070
|
-
def
|
|
1071
|
-
|
|
1072
|
-
|
|
1157
|
+
def _merge_extra_index_urls(existing: Iterable[str], new: list[str]) -> list[str]:
|
|
1158
|
+
"""
|
|
1159
|
+
Merge `new` extra index URLs into `existing`, preserving order and dropping
|
|
1160
|
+
duplicates. Used so that a user-configured ``python.extra_index_urls``
|
|
1161
|
+
survives aws_auth resolving its own CodeArtifact-backed extra indexes.
|
|
1162
|
+
"""
|
|
1163
|
+
merged = list(existing)
|
|
1164
|
+
for url in new:
|
|
1165
|
+
if url not in merged:
|
|
1166
|
+
merged.append(url)
|
|
1167
|
+
return merged
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def _resolve_extra_index_urls(cfg: ConfigTree, index_base_url: str) -> list[str]:
|
|
1171
|
+
"""
|
|
1172
|
+
Resolve ``python.aws_auth.extra_indexes`` into full, authenticated
|
|
1173
|
+
CodeArtifact index URLs, obfuscating each token along the way.
|
|
1174
|
+
"""
|
|
1175
|
+
|
|
1176
|
+
extra_index_urls = []
|
|
1177
|
+
for extra_index in cfg.python.aws_auth.extra_indexes:
|
|
1178
|
+
extra_index_url = urljoin(index_base_url + "/", interpolate1(extra_index))
|
|
1179
|
+
# Kinda redundant since index_url carries the same secret and is already
|
|
1180
|
+
# obfuscated. So this noop here just exists to make the obfucation
|
|
1181
|
+
# explicit.
|
|
1182
|
+
_obfuscate_index_url(extra_index_url)
|
|
1183
|
+
extra_index_urls.append(extra_index_url)
|
|
1184
|
+
return extra_index_urls
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
def _apply_resolved_index_urls(cfg: ConfigTree, index_base_url: str) -> None:
|
|
1188
|
+
"""
|
|
1189
|
+
Set ``python.index_url`` and merge-resolve ``python.extra_index_urls`` from
|
|
1190
|
+
`index_base_url`, the CodeArtifact domain/repository base shared by the
|
|
1191
|
+
primary index and any ``aws_auth.extra_indexes``. Shared by both the
|
|
1192
|
+
fresh-token and cached-token paths of ``_check_aws_token_validity``.
|
|
1193
|
+
"""
|
|
1194
|
+
|
|
1195
|
+
index_url = urljoin(index_base_url + "/", interpolate1(cfg.python.aws_auth.index))
|
|
1196
|
+
cfg.python.index_url = index_url
|
|
1197
|
+
_obfuscate_index_url(index_url)
|
|
1198
|
+
cfg.python.extra_index_urls = _merge_extra_index_urls(
|
|
1199
|
+
cfg.python.extra_index_urls,
|
|
1200
|
+
_resolve_extra_index_urls(cfg, index_base_url),
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def _check_aws_token_validity(cfg: ConfigTree) -> None:
|
|
1073
1205
|
"""
|
|
1074
1206
|
If csspin-python[aws_auth] is installed, we can use csaccess to get the
|
|
1075
1207
|
CodeArtifact authentication token.
|
|
@@ -1085,66 +1217,39 @@ def _check_aws_token_validity( # pylint: disable=too-many-locals
|
|
|
1085
1217
|
|
|
1086
1218
|
import time
|
|
1087
1219
|
|
|
1088
|
-
if not (
|
|
1089
|
-
client_secret := (
|
|
1090
|
-
interpolate1(cfg.python.aws_auth.client_secret)
|
|
1091
|
-
or os.getenv("CS_AWS_OIDC_CLIENT_SECRET")
|
|
1092
|
-
)
|
|
1093
|
-
):
|
|
1094
|
-
die(
|
|
1095
|
-
"Please provide a client secret for CodeArtifact access via"
|
|
1096
|
-
" 'python.aws_auth.client_secret'."
|
|
1097
|
-
)
|
|
1098
|
-
|
|
1099
1220
|
current_time = int(time.time())
|
|
1100
1221
|
timestamp_key = "aws_auth_timestamp"
|
|
1101
1222
|
|
|
1102
1223
|
with memoizer(cfg.python.aws_auth.memo) as memo:
|
|
1103
|
-
|
|
1104
|
-
if isinstance(item,
|
|
1105
|
-
|
|
1224
|
+
if memo.items() and (item := memo.items()[0]):
|
|
1225
|
+
if not isinstance(item, tuple):
|
|
1226
|
+
memo.clear()
|
|
1227
|
+
else:
|
|
1228
|
+
_, last_time, index_base_url = item
|
|
1106
1229
|
if current_time - last_time < int(
|
|
1107
1230
|
interpolate1(cfg.python.aws_auth.key_duration)
|
|
1108
1231
|
):
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if index_url := (
|
|
1115
|
-
config_parser["global"].get("index_url")
|
|
1116
|
-
or config_parser["global"].get("index-url")
|
|
1117
|
-
):
|
|
1118
|
-
cfg.python.index_url = index_url
|
|
1119
|
-
_obfuscate_index_url(index_url)
|
|
1120
|
-
break
|
|
1121
|
-
memo.items().remove(item)
|
|
1122
|
-
else:
|
|
1123
|
-
info("Updating Codeartifact token.")
|
|
1124
|
-
from urllib.error import HTTPError
|
|
1125
|
-
from urllib.parse import urljoin
|
|
1126
|
-
|
|
1127
|
-
opts = {
|
|
1128
|
-
"client_secret": client_secret,
|
|
1129
|
-
"static_oidc": interpolate1(cfg.python.aws_auth.static_oidc).lower()
|
|
1130
|
-
== "true",
|
|
1131
|
-
}
|
|
1132
|
-
if cfg.python.aws_auth.client_id:
|
|
1133
|
-
opts["client_id"] = interpolate1(cfg.python.aws_auth.client_id)
|
|
1134
|
-
if cfg.python.aws_auth.role_arn:
|
|
1135
|
-
opts["aws_role_arn"] = interpolate1(cfg.python.aws_auth.role_arn)
|
|
1232
|
+
info("Using cached CodeArtifact token.")
|
|
1233
|
+
_apply_resolved_index_urls(cfg, index_base_url)
|
|
1234
|
+
return
|
|
1235
|
+
memo.clear()
|
|
1136
1236
|
|
|
1137
|
-
|
|
1138
|
-
index_base_url = get_ca_pypi_url_programmatic(**opts)
|
|
1139
|
-
except HTTPError as e:
|
|
1140
|
-
die(f"Failed to establish CodeArtifact connection: {e}")
|
|
1237
|
+
info("Updating Codeartifact token.")
|
|
1141
1238
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
cfg.python.
|
|
1146
|
-
|
|
1239
|
+
opts = {}
|
|
1240
|
+
|
|
1241
|
+
if cfg.python.aws_auth.client_id:
|
|
1242
|
+
opts["client_id"] = interpolate1(cfg.python.aws_auth.client_id)
|
|
1243
|
+
if cfg.python.aws_auth.role_arn:
|
|
1244
|
+
opts["aws_role_arn"] = interpolate1(cfg.python.aws_auth.role_arn)
|
|
1245
|
+
|
|
1246
|
+
try:
|
|
1247
|
+
index_base_url = get_ca_pypi_url_programmatic(**opts)
|
|
1248
|
+
except HTTPError as e:
|
|
1249
|
+
die(f"Failed to establish CodeArtifact connection: {e}")
|
|
1250
|
+
|
|
1251
|
+
_apply_resolved_index_urls(cfg, index_base_url)
|
|
1147
1252
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1253
|
+
if exists(cfg.python.venv):
|
|
1254
|
+
_configure_pipconf(cfg, update=True)
|
|
1255
|
+
memo.add((timestamp_key, current_time, index_base_url))
|
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
|
|
|
@@ -68,13 +71,26 @@ def sbom(cfg: ConfigTree) -> None:
|
|
|
68
71
|
die(f"Project path '{project_path}' does not exist.")
|
|
69
72
|
|
|
70
73
|
project_path = Path(project_path).absolute()
|
|
71
|
-
metadata = get_project_metadata(
|
|
74
|
+
metadata = get_project_metadata(
|
|
75
|
+
project_path, cfg.python.index_url, tuple(cfg.python.extra_index_urls)
|
|
76
|
+
)
|
|
77
|
+
project_name, project_version = metadata.get("name"), metadata.get("version")
|
|
78
|
+
|
|
79
|
+
file_name = _predict_wheel_filename(
|
|
80
|
+
project_path, project_name, project_version, cfg.python.python
|
|
81
|
+
)
|
|
72
82
|
third_party_deps = _collect_thirdparty_deps(
|
|
73
83
|
metadata.get("requires_dist", set()), python_version=cfg.python.version
|
|
74
84
|
)
|
|
75
85
|
sbom_json = json.loads(_run_cyclonedx(cfg, third_party_deps, stderr))
|
|
76
|
-
_enrich_sbom(
|
|
77
|
-
|
|
86
|
+
_enrich_sbom(
|
|
87
|
+
sbom_json,
|
|
88
|
+
metadata,
|
|
89
|
+
third_party_deps,
|
|
90
|
+
cfg.python_sbom.repository_url,
|
|
91
|
+
file_name,
|
|
92
|
+
)
|
|
93
|
+
_write_sbom(cfg, sbom_json, project_name)
|
|
78
94
|
|
|
79
95
|
|
|
80
96
|
def cleanup(cfg: ConfigTree) -> None:
|
|
@@ -114,7 +130,7 @@ def _ensure_cyclonedx_venv(cfg: ConfigTree, binary_dir: str, quiet: str | None)
|
|
|
114
130
|
)
|
|
115
131
|
rmtree(venv_cdx)
|
|
116
132
|
|
|
117
|
-
sh(cfg.python.interpreter, "-m", "venv", venv_cdx)
|
|
133
|
+
sh(cfg.python.interpreter, "-m", "venv", venv_cdx, use_subprocess_environment=False)
|
|
118
134
|
sh(
|
|
119
135
|
interpreter_cdx,
|
|
120
136
|
"-m",
|
|
@@ -124,7 +140,9 @@ def _ensure_cyclonedx_venv(cfg: ConfigTree, binary_dir: str, quiet: str | None)
|
|
|
124
140
|
"install",
|
|
125
141
|
"--index-url",
|
|
126
142
|
cfg.python.index_url,
|
|
143
|
+
*[f"--extra-index-url={url}" for url in cfg.python.extra_index_urls],
|
|
127
144
|
"cyclonedx-bom==" + requested_version,
|
|
145
|
+
use_subprocess_environment=False,
|
|
128
146
|
)
|
|
129
147
|
with memoizer(memo_file) as memo:
|
|
130
148
|
memo.add(memo_key)
|
|
@@ -143,7 +161,7 @@ def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) ->
|
|
|
143
161
|
with TemporaryDirectory() as tmp_dir:
|
|
144
162
|
venv = Path(tmp_dir) / "venv"
|
|
145
163
|
interpreter = venv / binary_dir / "python" + cfg.platform.exe
|
|
146
|
-
sh(cfg.python.interpreter, "-m", "venv", venv)
|
|
164
|
+
sh(cfg.python.interpreter, "-m", "venv", venv, use_subprocess_environment=False)
|
|
147
165
|
if third_party_deps:
|
|
148
166
|
sh(
|
|
149
167
|
interpreter,
|
|
@@ -153,15 +171,34 @@ def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) ->
|
|
|
153
171
|
"install",
|
|
154
172
|
"--index-url",
|
|
155
173
|
cfg.python.index_url,
|
|
174
|
+
*[f"--extra-index-url={url}" for url in cfg.python.extra_index_urls],
|
|
156
175
|
*[
|
|
157
176
|
f"--constraint={constraint}"
|
|
158
177
|
for constraint in cfg.python.constraints
|
|
159
178
|
],
|
|
160
179
|
*third_party_deps,
|
|
180
|
+
use_subprocess_environment=False,
|
|
161
181
|
stderr=stderr,
|
|
162
182
|
)
|
|
163
|
-
sh(
|
|
164
|
-
|
|
183
|
+
sh(
|
|
184
|
+
interpreter,
|
|
185
|
+
"-m",
|
|
186
|
+
"pip",
|
|
187
|
+
quiet,
|
|
188
|
+
"uninstall",
|
|
189
|
+
"-y",
|
|
190
|
+
"pip",
|
|
191
|
+
use_subprocess_environment=False,
|
|
192
|
+
)
|
|
193
|
+
return backtick( # type: ignore[no-any-return]
|
|
194
|
+
interpreter_cdx,
|
|
195
|
+
"-m",
|
|
196
|
+
"cyclonedx_py",
|
|
197
|
+
"environment",
|
|
198
|
+
venv,
|
|
199
|
+
use_subprocess_environment=False,
|
|
200
|
+
stderr=stderr,
|
|
201
|
+
)
|
|
165
202
|
|
|
166
203
|
|
|
167
204
|
def _write_sbom(cfg: ConfigTree, sbom_json: dict, project_name: str) -> None:
|
|
@@ -197,7 +234,52 @@ def _parse_authors(author_name: str, author_email: str) -> str:
|
|
|
197
234
|
return ", ".join(f"{name} ({addr})" for name, addr in entries)
|
|
198
235
|
|
|
199
236
|
|
|
200
|
-
def
|
|
237
|
+
def _repository_base_url(index_url: str) -> str:
|
|
238
|
+
"""Reduce a pip index URL to the repository base URL a purl expects."""
|
|
239
|
+
parsed = urlparse(index_url)
|
|
240
|
+
segments = [segment for segment in parsed.path.split("/") if segment]
|
|
241
|
+
if segments and segments[-1] in ("simple", "+simple"):
|
|
242
|
+
segments.pop()
|
|
243
|
+
return parsed._replace(
|
|
244
|
+
netloc=parsed.netloc.rsplit("@", 1)[-1], # drop basic-auth credentials
|
|
245
|
+
path="/".join([""] + segments),
|
|
246
|
+
).geturl()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _build_purl(name: str, version: str, repository_url: str, file_name: str) -> str:
|
|
250
|
+
"""Build the pypi Package URL for a project, per tn-0058.
|
|
251
|
+
|
|
252
|
+
``repository_url`` is only added when the package isn't published to
|
|
253
|
+
public pypi.org, since type, name and version are unique there already.
|
|
254
|
+
"""
|
|
255
|
+
purl = f"pkg:pypi/{canonicalize_name(name)}@{quote(version, safe=':')}"
|
|
256
|
+
|
|
257
|
+
qualifiers = {"file_name": file_name}
|
|
258
|
+
if urlparse(repository_url).hostname != "pypi.org":
|
|
259
|
+
qualifiers["repository_url"] = _repository_base_url(repository_url)
|
|
260
|
+
|
|
261
|
+
return f"{purl}?{urlencode(sorted(qualifiers.items()), safe=':')}"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _predict_wheel_filename(
|
|
265
|
+
project_path: str, name: str, version: str, interpreter: str
|
|
266
|
+
) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Predict the wheel filename ``python:wheel`` will produce for
|
|
269
|
+
``project_path``, without building it.
|
|
270
|
+
|
|
271
|
+
``name`` and ``version`` come from :func:`get_project_metadata`.
|
|
272
|
+
``interpreter`` must be ``project_path``'s own, since it owns the tags.
|
|
273
|
+
Cached since the prediction is identical for every caller within the same
|
|
274
|
+
process.
|
|
275
|
+
"""
|
|
276
|
+
script = Path(__file__).parent / "_predict_wheel_filename.py"
|
|
277
|
+
return backtick(interpreter, script, project_path, name, version).strip() # type: ignore[no-any-return] # noqa: E501
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _build_primary_component(
|
|
281
|
+
metadata: dict, repository_url: str, file_name: str
|
|
282
|
+
) -> tuple[dict, str]:
|
|
201
283
|
"""Build the CycloneDX primary component dict and its bom-ref from project metadata."""
|
|
202
284
|
if not (name := metadata.get("name")):
|
|
203
285
|
die("Project metadata is missing 'name'.")
|
|
@@ -206,6 +288,7 @@ def _build_primary_component(metadata: dict) -> tuple[dict, str]:
|
|
|
206
288
|
if not (license_id := metadata.get("license")):
|
|
207
289
|
die("Project metadata is missing 'license'.")
|
|
208
290
|
|
|
291
|
+
purl = _build_purl(str(name), str(version), repository_url, file_name)
|
|
209
292
|
authors = _parse_authors(
|
|
210
293
|
author_name=metadata.get("author", "").strip(),
|
|
211
294
|
author_email=metadata.get("author_email", "").strip(),
|
|
@@ -216,6 +299,7 @@ def _build_primary_component(metadata: dict) -> tuple[dict, str]:
|
|
|
216
299
|
"bom-ref": primary_ref,
|
|
217
300
|
"licenses": [{"expression": license_id}],
|
|
218
301
|
"name": name,
|
|
302
|
+
"purl": purl,
|
|
219
303
|
"type": "application",
|
|
220
304
|
"version": version,
|
|
221
305
|
}
|
|
@@ -226,9 +310,13 @@ def _enrich_sbom(
|
|
|
226
310
|
sbom_json: dict,
|
|
227
311
|
metadata: dict,
|
|
228
312
|
third_party_deps: set[str],
|
|
313
|
+
repository_url: str,
|
|
314
|
+
file_name: str,
|
|
229
315
|
) -> None:
|
|
230
316
|
"""Add the primary component and its dependency entry to the CycloneDX document."""
|
|
231
|
-
component, primary_ref = _build_primary_component(
|
|
317
|
+
component, primary_ref = _build_primary_component(
|
|
318
|
+
metadata, repository_url, file_name
|
|
319
|
+
)
|
|
232
320
|
existing_metadata = sbom_json.get("metadata", {})
|
|
233
321
|
existing_tools = existing_metadata.get("tools", {})
|
|
234
322
|
existing_tool_components = (
|
csspin_python/python_schema.yaml
CHANGED
|
@@ -116,6 +116,13 @@ python:
|
|
|
116
116
|
The index url from where packages should get installed into the
|
|
117
117
|
venv. This value will also be put into cfg.python.pipconf, if
|
|
118
118
|
not already mentioned there.
|
|
119
|
+
extra_index_urls:
|
|
120
|
+
type: list
|
|
121
|
+
help: |
|
|
122
|
+
Additional package indexes to install from, alongside
|
|
123
|
+
'index_url'. If 'aws_auth.extra_indexes' is set, its
|
|
124
|
+
resolved, authenticated CodeArtifact URLs are merged into
|
|
125
|
+
this list.
|
|
119
126
|
build_wheels:
|
|
120
127
|
type: list
|
|
121
128
|
help: |
|
|
@@ -141,22 +148,17 @@ python:
|
|
|
141
148
|
Time in seconds defining how long the plugin should
|
|
142
149
|
consider the authentication token as valid before
|
|
143
150
|
issuing a new one.
|
|
144
|
-
static_oidc:
|
|
145
|
-
type: bool
|
|
146
|
-
help: |
|
|
147
|
-
Whether to static OIDC when authenticating with AWS
|
|
148
|
-
CodeArtifact.
|
|
149
151
|
index:
|
|
150
152
|
type: str
|
|
151
|
-
help: The CodeArtifact repository index (e.g. "
|
|
153
|
+
help: The CodeArtifact repository index (e.g. "2026.2/simple").
|
|
154
|
+
extra_indexes:
|
|
155
|
+
type: list
|
|
156
|
+
help: |
|
|
157
|
+
Additional CodeArtifact repository indexes to
|
|
158
|
+
authenticate against, alongside 'index'.
|
|
152
159
|
client_id:
|
|
153
160
|
type: str
|
|
154
161
|
help: The OIDC client ID to use.
|
|
155
|
-
client_secret:
|
|
156
|
-
type: secret
|
|
157
|
-
help: |
|
|
158
|
-
The OIDC client secret to use, defaults to the
|
|
159
|
-
environment variable 'CS_AWS_OIDC_CLIENT_SECRET'.
|
|
160
162
|
role_arn:
|
|
161
163
|
type: str
|
|
162
164
|
help: The role ARN to assume when authenticating.
|
csspin_python/uv_provisioner.py
CHANGED
|
@@ -161,16 +161,26 @@ class SimpleUvProvisioner(SimpleProvisioner):
|
|
|
161
161
|
self._uv_cmd("pip", "install", "pip")
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def _set_extra_index_url(toml_content: dict, extra_index_urls: list[str]) -> None:
|
|
165
|
+
"""Set or drop the 'extra-index-url' key in `toml_content` in place."""
|
|
166
|
+
if extra_index_urls:
|
|
167
|
+
toml_content["extra-index-url"] = extra_index_urls
|
|
168
|
+
else:
|
|
169
|
+
toml_content.pop("extra-index-url", None)
|
|
170
|
+
|
|
171
|
+
|
|
164
172
|
def _configure_uv_toml(cfg: ConfigTree) -> None:
|
|
165
173
|
"""
|
|
166
174
|
Create a config file for uv, similar to the pip.conf of
|
|
167
175
|
csspin_python.python, since `uv` pip won't respect the pip.conf.
|
|
176
|
+
|
|
177
|
+
'index-url'/'extra-index-url' declared in the 'uv_provisioner.uv_toml'
|
|
178
|
+
template are ignored; 'python.index_url'/'python.extra_index_urls' are
|
|
179
|
+
the only supported way to configure these.
|
|
168
180
|
"""
|
|
169
181
|
toml_content = tomllib.loads(cfg.uv_provisioner.uv_toml or "")
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
else:
|
|
173
|
-
toml_content["index-url"] = toml_content.get("index-url", cfg.python.index_url)
|
|
182
|
+
toml_content["index-url"] = cfg.python.index_url
|
|
183
|
+
_set_extra_index_url(toml_content, cfg.python.extra_index_urls)
|
|
174
184
|
|
|
175
185
|
with open(cfg.uv_provisioner.uv_toml_path, mode="wb") as fd:
|
|
176
186
|
tomli_w.dump(toml_content, fd)
|
|
@@ -178,12 +188,20 @@ def _configure_uv_toml(cfg: ConfigTree) -> None:
|
|
|
178
188
|
|
|
179
189
|
def _update_index_url_in_toml(cfg: ConfigTree) -> None:
|
|
180
190
|
"""
|
|
181
|
-
Update the index-url in the uv.toml in case
|
|
191
|
+
Update the index-url and extra-index-url in the uv.toml in case they
|
|
192
|
+
changed.
|
|
182
193
|
"""
|
|
183
194
|
if (uv_toml_path := interpolate1(Path(cfg.uv_provisioner.uv_toml_path))).exists():
|
|
184
195
|
with open(uv_toml_path, mode="rb") as fd:
|
|
185
196
|
toml_content = tomllib.load(fd)
|
|
197
|
+
changed = False
|
|
186
198
|
if toml_content.get("index-url") != cfg.python.index_url:
|
|
187
199
|
toml_content["index-url"] = cfg.python.index_url
|
|
200
|
+
changed = True
|
|
201
|
+
|
|
202
|
+
if toml_content.get("extra-index-url", []) != cfg.python.extra_index_urls:
|
|
203
|
+
_set_extra_index_url(toml_content, cfg.python.extra_index_urls)
|
|
204
|
+
changed = True
|
|
205
|
+
if changed:
|
|
188
206
|
with open(uv_toml_path, mode="wb") as fd:
|
|
189
207
|
tomli_w.dump(toml_content, fd)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csspin-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.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>
|
|
@@ -27,7 +27,7 @@ License-File: LICENSE
|
|
|
27
27
|
Requires-Dist: platformdirs~=4.3.8
|
|
28
28
|
Requires-Dist: virtualenv
|
|
29
29
|
Provides-Extra: aws-auth
|
|
30
|
-
Requires-Dist: csaccess>=0.
|
|
30
|
+
Requires-Dist: csaccess>=0.3.0; extra == "aws-auth"
|
|
31
31
|
Provides-Extra: uv
|
|
32
32
|
Requires-Dist: tomli; python_version < "3.11" and extra == "uv"
|
|
33
33
|
Requires-Dist: tomli-w; extra == "uv"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
csspin_python/_predict_wheel_filename.py,sha256=sz09QpDOPQesG-QAANjH607QSkS3PFo0qxPifG1y85o,2399
|
|
2
|
+
csspin_python/behave.py,sha256=iJZeyIqB7V_NzTdLTZldNY9W_GGwCWkXe6WY69wpDqs,4997
|
|
3
|
+
csspin_python/behave_schema.yaml,sha256=8qoOCK-uTmwgRRW29urgK0X_kgn0zO0X34v89bvii2w,1241
|
|
4
|
+
csspin_python/debugpy.py,sha256=v0ZZopv5TNoSaFf2kiePsw9OmhBpjfOBFh0u71jTcnQ,962
|
|
5
|
+
csspin_python/debugpy_schema.yaml,sha256=BeH30nSirDYctkdhS9xMXUG5htj3PED_ZjmxPG5WRUc,364
|
|
6
|
+
csspin_python/devpi.py,sha256=o7O06Dw-xt47y-M74TShcl9MUMaW78aFmw6i6ISYwoU,2351
|
|
7
|
+
csspin_python/devpi_schema.yaml,sha256=2gPATWjVcfvCTrGZX2FK6wH8hh9KS0XzZ35JvZeJGEU,487
|
|
8
|
+
csspin_python/playwright.py,sha256=oFfphLqa4AB6K9vasCUFHN0kFXu63n3ocrsqVuRp4-0,5102
|
|
9
|
+
csspin_python/playwright_schema.yaml,sha256=TSeR16YHa7m7bfO59F2eMV-jXcglluTJdEpUeL16saY,1178
|
|
10
|
+
csspin_python/pytest.py,sha256=vSLtmS-kNO0NPo_tszfEm5zGwJb6Yb5IGS-hA7CNguo,4544
|
|
11
|
+
csspin_python/pytest_schema.yaml,sha256=tzXtdF6MvGC9v59EVRJFfLeMMHqPsXcFXy2zJtRECBI,1535
|
|
12
|
+
csspin_python/python.py,sha256=uoWCm_X2_yGaOdnxAb_h2Mr8RjLd5MjuVL4cPF15E2E,41973
|
|
13
|
+
csspin_python/python_sbom.py,sha256=AT_dFEKoghZTVkkTECz0cOo-w3AZAYAgROx4prhAHKw,13603
|
|
14
|
+
csspin_python/python_sbom_schema.yaml,sha256=QysysjGJ6n1WEoLBqYHcdbojuEomUvL8QG4ZSWQW2_8,739
|
|
15
|
+
csspin_python/python_schema.yaml,sha256=RwJgbyIRenj18-s60HmqkwWCGtd6G1acpJ9TDI9QjBQ,6448
|
|
16
|
+
csspin_python/radon.py,sha256=uFqm6FEi5oWj-_XVaAm3s9cam0cUmr1_FwRf40K6xWs,1876
|
|
17
|
+
csspin_python/radon_schema.yaml,sha256=rlRzXw5z4XbjOVznRiUxWGP4E9hx1Jm-gGw1iQiYzE0,548
|
|
18
|
+
csspin_python/uv_provisioner.py,sha256=2M1I8PqWTCUxqaOeJI5rchmly6t-MnOPQfIx-kKzdQU,6757
|
|
19
|
+
csspin_python/uv_provisioner_schema.yaml,sha256=Y8ZNC2OMnhR8Us3WUXAXK9hMjqGWAKFJB2puX4X5XNQ,727
|
|
20
|
+
csspin_python-6.0.0.dist-info/licenses/LICENSE,sha256=4MAecetnRTQw5DlHtiikDSzKWO1xVLwzM5_DsPMYlnE,10172
|
|
21
|
+
csspin_python-6.0.0.dist-info/METADATA,sha256=a-NIDfoER1W6_3YNyaxS_RKPKLXmWp6tk-uApcS6Lr4,5254
|
|
22
|
+
csspin_python-6.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
23
|
+
csspin_python-6.0.0.dist-info/top_level.txt,sha256=QSeglMEGbFu1z4L6MCQYwo01NgL0KojWvC4rzgMQ8gU,14
|
|
24
|
+
csspin_python-6.0.0.dist-info/RECORD,,
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
csspin_python/behave.py,sha256=iJZeyIqB7V_NzTdLTZldNY9W_GGwCWkXe6WY69wpDqs,4997
|
|
2
|
-
csspin_python/behave_schema.yaml,sha256=8qoOCK-uTmwgRRW29urgK0X_kgn0zO0X34v89bvii2w,1241
|
|
3
|
-
csspin_python/debugpy.py,sha256=v0ZZopv5TNoSaFf2kiePsw9OmhBpjfOBFh0u71jTcnQ,962
|
|
4
|
-
csspin_python/debugpy_schema.yaml,sha256=BeH30nSirDYctkdhS9xMXUG5htj3PED_ZjmxPG5WRUc,364
|
|
5
|
-
csspin_python/devpi.py,sha256=o7O06Dw-xt47y-M74TShcl9MUMaW78aFmw6i6ISYwoU,2351
|
|
6
|
-
csspin_python/devpi_schema.yaml,sha256=2gPATWjVcfvCTrGZX2FK6wH8hh9KS0XzZ35JvZeJGEU,487
|
|
7
|
-
csspin_python/playwright.py,sha256=oFfphLqa4AB6K9vasCUFHN0kFXu63n3ocrsqVuRp4-0,5102
|
|
8
|
-
csspin_python/playwright_schema.yaml,sha256=TSeR16YHa7m7bfO59F2eMV-jXcglluTJdEpUeL16saY,1178
|
|
9
|
-
csspin_python/pytest.py,sha256=N9YaU_ouQab0PFPf46HLE7Vg4JeoZW4dzVD7EevqJ1U,4573
|
|
10
|
-
csspin_python/pytest_schema.yaml,sha256=tzXtdF6MvGC9v59EVRJFfLeMMHqPsXcFXy2zJtRECBI,1535
|
|
11
|
-
csspin_python/python.py,sha256=yqH1eG6mqRJomu-F99cB74PFaUzbSoG_dhpUjYwr1xE,37725
|
|
12
|
-
csspin_python/python_sbom.py,sha256=8NVZ6uVUz7rU3VhEoXJ_wHLCdv4BIV6Zjz1ZRa9nQhc,10488
|
|
13
|
-
csspin_python/python_sbom_schema.yaml,sha256=VCxY9E2AG_fS00dvIYe6Fbvz1maPjpY0zyZK1Z0wJu4,538
|
|
14
|
-
csspin_python/python_schema.yaml,sha256=pgVVjByUYjxQWek7aFmjQzRwmq2ROLvHYgwGPMrT9sM,6351
|
|
15
|
-
csspin_python/radon.py,sha256=uFqm6FEi5oWj-_XVaAm3s9cam0cUmr1_FwRf40K6xWs,1876
|
|
16
|
-
csspin_python/radon_schema.yaml,sha256=rlRzXw5z4XbjOVznRiUxWGP4E9hx1Jm-gGw1iQiYzE0,548
|
|
17
|
-
csspin_python/uv_provisioner.py,sha256=1e-_Sb39JrqNWyaUNeBX59R5tutXLJ1ZsT7urCN1U0I,6044
|
|
18
|
-
csspin_python/uv_provisioner_schema.yaml,sha256=Y8ZNC2OMnhR8Us3WUXAXK9hMjqGWAKFJB2puX4X5XNQ,727
|
|
19
|
-
csspin_python-4.1.0rc2.dist-info/licenses/LICENSE,sha256=4MAecetnRTQw5DlHtiikDSzKWO1xVLwzM5_DsPMYlnE,10172
|
|
20
|
-
csspin_python-4.1.0rc2.dist-info/METADATA,sha256=v7TeI32ffR2KghQaNg2Yi1nXefTReG2Pr6iI3D8LKM0,5257
|
|
21
|
-
csspin_python-4.1.0rc2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
22
|
-
csspin_python-4.1.0rc2.dist-info/scm_file_list.json,sha256=Q47_SEZYR492yYYWq_DlPPp-64AFlvHaOz7APY0i8j0,2902
|
|
23
|
-
csspin_python-4.1.0rc2.dist-info/scm_version.json,sha256=r-bmiPdloFp64jPWB2hS3VjSJvF_ljCIL5y985ukLKY,163
|
|
24
|
-
csspin_python-4.1.0rc2.dist-info/top_level.txt,sha256=QSeglMEGbFu1z4L6MCQYwo01NgL0KojWvC4rzgMQ8gU,14
|
|
25
|
-
csspin_python-4.1.0rc2.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
|