csspin-python 3.2.0__py3-none-any.whl → 4.1.0rc1__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/devpi.py +0 -1
- csspin_python/pytest.py +0 -1
- csspin_python/python.py +106 -60
- csspin_python/python_sbom.py +196 -0
- csspin_python/python_sbom_schema.yaml +18 -0
- csspin_python/uv_provisioner.py +4 -3
- {csspin_python-3.2.0.dist-info → csspin_python-4.1.0rc1.dist-info}/METADATA +12 -8
- {csspin_python-3.2.0.dist-info → csspin_python-4.1.0rc1.dist-info}/RECORD +11 -9
- {csspin_python-3.2.0.dist-info → csspin_python-4.1.0rc1.dist-info}/WHEEL +1 -1
- {csspin_python-3.2.0.dist-info → csspin_python-4.1.0rc1.dist-info}/licenses/LICENSE +0 -0
- {csspin_python-3.2.0.dist-info → csspin_python-4.1.0rc1.dist-info}/top_level.txt +0 -0
csspin_python/devpi.py
CHANGED
csspin_python/pytest.py
CHANGED
csspin_python/python.py
CHANGED
|
@@ -69,13 +69,16 @@ point to the base installation.
|
|
|
69
69
|
import abc
|
|
70
70
|
import configparser
|
|
71
71
|
import hashlib
|
|
72
|
+
import json
|
|
72
73
|
import logging
|
|
73
74
|
import os
|
|
74
75
|
import re
|
|
75
76
|
import shutil
|
|
77
|
+
import subprocess
|
|
76
78
|
import sys
|
|
77
79
|
from contextlib import contextmanager
|
|
78
|
-
from
|
|
80
|
+
from functools import cache
|
|
81
|
+
from subprocess import CalledProcessError, check_output
|
|
79
82
|
from textwrap import dedent, indent
|
|
80
83
|
from typing import Generator, Iterable, Type, Union
|
|
81
84
|
|
|
@@ -88,6 +91,7 @@ except ImportError:
|
|
|
88
91
|
|
|
89
92
|
from click.exceptions import Abort
|
|
90
93
|
from csspin import (
|
|
94
|
+
CONFIG,
|
|
91
95
|
EXPORTS,
|
|
92
96
|
Command,
|
|
93
97
|
Memoizer,
|
|
@@ -289,20 +293,65 @@ def nuget_install(cfg: ConfigTree) -> None:
|
|
|
289
293
|
)
|
|
290
294
|
|
|
291
295
|
|
|
292
|
-
def
|
|
293
|
-
|
|
294
|
-
|
|
296
|
+
def _check_venv( # pylint: disable=too-many-return-statements
|
|
297
|
+
cfg: ConfigTree,
|
|
298
|
+
) -> bool:
|
|
299
|
+
"""
|
|
300
|
+
Checks whether the venv is actually a venv compatible
|
|
301
|
+
with our configuration and not just some dir.
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
295
304
|
python_version = (
|
|
296
305
|
check_output([cfg.python.python, "--version"])
|
|
297
306
|
.decode()
|
|
298
307
|
.strip()
|
|
299
308
|
.replace("Python ", "")
|
|
300
309
|
)
|
|
301
|
-
|
|
310
|
+
except CalledProcessError:
|
|
311
|
+
return False
|
|
312
|
+
if not cfg.python.version and not cfg.python.use:
|
|
313
|
+
return False
|
|
314
|
+
if cfg.python.use:
|
|
315
|
+
try:
|
|
316
|
+
use_python_version = (
|
|
317
|
+
check_output([cfg.python.use, "--version"])
|
|
318
|
+
.decode()
|
|
319
|
+
.strip()
|
|
320
|
+
.replace("Python ", "")
|
|
321
|
+
)
|
|
322
|
+
except CalledProcessError:
|
|
323
|
+
return False
|
|
324
|
+
if use_python_version == python_version:
|
|
325
|
+
return True
|
|
326
|
+
else:
|
|
327
|
+
warn(
|
|
328
|
+
"The cfg.python.use version does not match the cfg.python.python "
|
|
329
|
+
"version set in the venv. If you want to update the python version "
|
|
330
|
+
"used in the venv, you have to manually remove it."
|
|
331
|
+
)
|
|
332
|
+
return True
|
|
333
|
+
if python_version.startswith(cfg.python.version):
|
|
334
|
+
return True
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def provision(cfg: ConfigTree) -> None:
|
|
339
|
+
"""Provision the python plugin"""
|
|
340
|
+
|
|
341
|
+
info("Checking venv '{python.venv}'")
|
|
342
|
+
|
|
343
|
+
fresh_venv = False
|
|
344
|
+
|
|
345
|
+
if exists("{python.venv}"):
|
|
346
|
+
if not _check_venv(cfg):
|
|
302
347
|
_cleanup_memoed_provisioners(cfg)
|
|
303
348
|
rmtree(cfg.python.provisioner_memo)
|
|
304
349
|
rmtree(cfg.python.aws_auth.memo)
|
|
305
350
|
rmtree(cfg.python.venv)
|
|
351
|
+
fresh_venv = True
|
|
352
|
+
else:
|
|
353
|
+
fresh_venv = True
|
|
354
|
+
|
|
306
355
|
with memoizer(cfg.python.provisioner_memo) as memo:
|
|
307
356
|
if cfg.python.provisioner is None:
|
|
308
357
|
cfg.python.provisioner = SimpleProvisioner(cfg)
|
|
@@ -311,7 +360,7 @@ def provision(cfg: ConfigTree) -> None:
|
|
|
311
360
|
if not shutil.which(cfg.python.interpreter):
|
|
312
361
|
cfg.python.provisioner.provision_python(cfg)
|
|
313
362
|
|
|
314
|
-
venv_provision(cfg)
|
|
363
|
+
venv_provision(cfg, fresh_venv)
|
|
315
364
|
|
|
316
365
|
cfg.python.site_packages = get_site_packages(interpreter=cfg.python.python)
|
|
317
366
|
|
|
@@ -468,24 +517,19 @@ class BashActivate(ActivateScriptPatcher):
|
|
|
468
517
|
replacements = [
|
|
469
518
|
("deactivate", "origdeactivate"),
|
|
470
519
|
]
|
|
471
|
-
old_env_pattern = dedent(
|
|
472
|
-
"""
|
|
520
|
+
old_env_pattern = dedent("""
|
|
473
521
|
if [ -z ${{{name}+x}} ]; then
|
|
474
522
|
export _OLD_SPIN_UNSET{name}=""
|
|
475
523
|
else
|
|
476
524
|
export _OLD_SPIN_VALUE{name}="${name}"
|
|
477
525
|
fi
|
|
478
|
-
"""
|
|
479
|
-
|
|
480
|
-
setpattern = dedent(
|
|
481
|
-
"""
|
|
526
|
+
""")
|
|
527
|
+
setpattern = dedent("""
|
|
482
528
|
{name}="{value}"
|
|
483
529
|
export {name}
|
|
484
|
-
"""
|
|
485
|
-
)
|
|
530
|
+
""")
|
|
486
531
|
resetpattern = indent(
|
|
487
|
-
dedent(
|
|
488
|
-
"""
|
|
532
|
+
dedent("""
|
|
489
533
|
if ! [ -z "${{_OLD_SPIN_VALUE{name}+_}}" ] ; then
|
|
490
534
|
{name}="$_OLD_SPIN_VALUE{name}"
|
|
491
535
|
export {name}
|
|
@@ -495,12 +539,10 @@ class BashActivate(ActivateScriptPatcher):
|
|
|
495
539
|
unset {name}
|
|
496
540
|
unset _OLD_SPIN_UNSET{name}
|
|
497
541
|
fi
|
|
498
|
-
"""
|
|
499
|
-
),
|
|
542
|
+
"""),
|
|
500
543
|
prefix=" ",
|
|
501
544
|
)
|
|
502
|
-
script = dedent(
|
|
503
|
-
"""
|
|
545
|
+
script = dedent("""
|
|
504
546
|
{patchmarker}
|
|
505
547
|
{original}
|
|
506
548
|
deactivate () {{
|
|
@@ -520,8 +562,7 @@ class BashActivate(ActivateScriptPatcher):
|
|
|
520
562
|
# commands. Without forgetting past commands the $PATH changes
|
|
521
563
|
# we made may not be respected
|
|
522
564
|
hash -r 2>/dev/null
|
|
523
|
-
"""
|
|
524
|
-
)
|
|
565
|
+
""")
|
|
525
566
|
|
|
526
567
|
@staticmethod
|
|
527
568
|
def interpolate_environ_value(value: str) -> str:
|
|
@@ -543,24 +584,19 @@ class PowershellActivate(ActivateScriptPatcher):
|
|
|
543
584
|
old_env_pattern = (
|
|
544
585
|
"New-Variable -Scope global -Name _OLD_SPIN_{name} -Value $env:{name}"
|
|
545
586
|
)
|
|
546
|
-
setpattern = dedent(
|
|
547
|
-
"""
|
|
587
|
+
setpattern = dedent("""
|
|
548
588
|
$env:{name} = "{value}"
|
|
549
|
-
"""
|
|
550
|
-
)
|
|
589
|
+
""")
|
|
551
590
|
resetpattern = indent(
|
|
552
|
-
dedent(
|
|
553
|
-
"""
|
|
591
|
+
dedent("""
|
|
554
592
|
if (Test-Path variable:_OLD_SPIN_{name}) {{
|
|
555
593
|
$env:{name} = $variable:_OLD_SPIN_{name}
|
|
556
594
|
Remove-Variable "_OLD_SPIN_{name}" -Scope global
|
|
557
595
|
}}
|
|
558
|
-
"""
|
|
559
|
-
),
|
|
596
|
+
"""),
|
|
560
597
|
prefix=" ",
|
|
561
598
|
)
|
|
562
|
-
script = dedent(
|
|
563
|
-
"""
|
|
599
|
+
script = dedent("""
|
|
564
600
|
{patchmarker}
|
|
565
601
|
{original}
|
|
566
602
|
function global:deactivate([switch] $NonDestructive) {{
|
|
@@ -574,8 +610,7 @@ class PowershellActivate(ActivateScriptPatcher):
|
|
|
574
610
|
deactivate -nondestructive
|
|
575
611
|
{old_value_setters}
|
|
576
612
|
{setters}
|
|
577
|
-
"""
|
|
578
|
-
)
|
|
613
|
+
""")
|
|
579
614
|
|
|
580
615
|
@staticmethod
|
|
581
616
|
def interpolate_environ_value(value: str) -> str:
|
|
@@ -592,8 +627,7 @@ class BatchActivate(ActivateScriptPatcher):
|
|
|
592
627
|
patchmarker = "\nREM Patched by csspin_python.python\n"
|
|
593
628
|
activatescript = Path("{python.scriptdir}") / "activate.bat"
|
|
594
629
|
replacements = []
|
|
595
|
-
old_env_pattern = dedent(
|
|
596
|
-
"""
|
|
630
|
+
old_env_pattern = dedent("""
|
|
597
631
|
if defined _OLD_SPIN_VALUE_{name} goto ENDIFSPIN{name}1
|
|
598
632
|
if defined _OLD_SPIN_UNSET_{name} goto ENDIFSPIN{name}2
|
|
599
633
|
if defined {name} goto ENDIFSPIN{name}3
|
|
@@ -613,19 +647,16 @@ class BatchActivate(ActivateScriptPatcher):
|
|
|
613
647
|
set "_OLD_SPIN_UNSET_{name}= "
|
|
614
648
|
goto ENDIFSPIN{name}5
|
|
615
649
|
:ENDIFSPIN{name}5
|
|
616
|
-
"""
|
|
617
|
-
)
|
|
650
|
+
""")
|
|
618
651
|
setpattern = 'set "{name}={value}"'
|
|
619
652
|
resetpattern = ""
|
|
620
|
-
script = dedent(
|
|
621
|
-
"""
|
|
653
|
+
script = dedent("""
|
|
622
654
|
@echo off
|
|
623
655
|
{patchmarker}
|
|
624
656
|
{original}
|
|
625
657
|
{old_value_setters}
|
|
626
658
|
{setters}
|
|
627
|
-
"""
|
|
628
|
-
)
|
|
659
|
+
""")
|
|
629
660
|
|
|
630
661
|
@staticmethod
|
|
631
662
|
def interpolate_environ_value(value: str) -> str:
|
|
@@ -644,8 +675,7 @@ class BatchDeactivate(ActivateScriptPatcher):
|
|
|
644
675
|
replacements = []
|
|
645
676
|
old_env_pattern = ""
|
|
646
677
|
setpattern = ""
|
|
647
|
-
resetpattern = dedent(
|
|
648
|
-
"""
|
|
678
|
+
resetpattern = dedent("""
|
|
649
679
|
if defined _OLD_SPIN_VALUE_{name} goto ENDIFVSPIN{name}1
|
|
650
680
|
if defined _OLD_SPIN_UNSET_{name} goto ENDIFVSPIN{name}2
|
|
651
681
|
:ENDIFVSPIN{name}1
|
|
@@ -657,16 +687,13 @@ class BatchDeactivate(ActivateScriptPatcher):
|
|
|
657
687
|
set _OLD_SPIN_UNSET_{name}=
|
|
658
688
|
goto ENDIFVSPIN{name}0
|
|
659
689
|
:ENDIFVSPIN{name}0
|
|
660
|
-
"""
|
|
661
|
-
|
|
662
|
-
script = dedent(
|
|
663
|
-
"""
|
|
690
|
+
""")
|
|
691
|
+
script = dedent("""
|
|
664
692
|
@echo off
|
|
665
693
|
{patchmarker}
|
|
666
694
|
{original}
|
|
667
695
|
{resetters}
|
|
668
|
-
"""
|
|
669
|
-
)
|
|
696
|
+
""")
|
|
670
697
|
|
|
671
698
|
|
|
672
699
|
class PythonActivate(ActivateScriptPatcher):
|
|
@@ -676,13 +703,11 @@ class PythonActivate(ActivateScriptPatcher):
|
|
|
676
703
|
old_env_pattern = ""
|
|
677
704
|
setpattern = 'os.environ["{name}"] = fr"{value}"'
|
|
678
705
|
resetpattern = ""
|
|
679
|
-
script = dedent(
|
|
680
|
-
"""
|
|
706
|
+
script = dedent("""
|
|
681
707
|
{patchmarker}
|
|
682
708
|
{original}
|
|
683
709
|
{setters}
|
|
684
|
-
"""
|
|
685
|
-
)
|
|
710
|
+
""")
|
|
686
711
|
|
|
687
712
|
@staticmethod
|
|
688
713
|
def interpolate_environ_value(value: str) -> str:
|
|
@@ -695,6 +720,30 @@ class PythonActivate(ActivateScriptPatcher):
|
|
|
695
720
|
return value
|
|
696
721
|
|
|
697
722
|
|
|
723
|
+
@cache
|
|
724
|
+
def get_project_metadata(project_path: str, index_url: str) -> dict: # type: ignore[return] # pylint: disable=inconsistent-return-statements # noqa: E501
|
|
725
|
+
"""
|
|
726
|
+
Retrieve project metadata of ``project_path`` via ``python -m build
|
|
727
|
+
--metadata``.
|
|
728
|
+
|
|
729
|
+
Cached since ``build --metadata`` is noisy and the output is identical for
|
|
730
|
+
every caller within the same process.
|
|
731
|
+
"""
|
|
732
|
+
setenv(PIP_INDEX_URL=index_url)
|
|
733
|
+
kwargs = {}
|
|
734
|
+
if CONFIG.verbosity < Verbosity.INFO:
|
|
735
|
+
kwargs["stderr"] = subprocess.DEVNULL
|
|
736
|
+
raw_metadata = backtick(
|
|
737
|
+
"python", "-m", "build", "--metadata", project_path, **kwargs
|
|
738
|
+
)
|
|
739
|
+
setenv(PIP_INDEX_URL=None)
|
|
740
|
+
|
|
741
|
+
if raw_metadata:
|
|
742
|
+
return json.loads(raw_metadata) # type: ignore[no-any-return]
|
|
743
|
+
|
|
744
|
+
die(f"Could not retrieve project metadata of '{project_path}'.")
|
|
745
|
+
|
|
746
|
+
|
|
698
747
|
def get_site_packages(interpreter: Path) -> Path:
|
|
699
748
|
"""Return the path to the virtual environments site-packages."""
|
|
700
749
|
return Path(
|
|
@@ -919,15 +968,12 @@ def _req_for_memo(
|
|
|
919
968
|
|
|
920
969
|
|
|
921
970
|
def venv_provision( # pylint: disable=too-many-branches,missing-function-docstring
|
|
922
|
-
cfg: ConfigTree,
|
|
971
|
+
cfg: ConfigTree, fresh_venv: bool = False
|
|
923
972
|
) -> None:
|
|
924
|
-
fresh_env = False
|
|
925
|
-
info("Checking venv '{python.venv}'")
|
|
926
973
|
|
|
927
|
-
if
|
|
974
|
+
if fresh_venv:
|
|
928
975
|
info("Provisioning venv '{python.venv}'")
|
|
929
976
|
cfg.python.provisioner.provision_venv(cfg)
|
|
930
|
-
fresh_env = True
|
|
931
977
|
|
|
932
978
|
# This sets PATH to the venv
|
|
933
979
|
init(cfg)
|
|
@@ -935,7 +981,7 @@ def venv_provision( # pylint: disable=too-many-branches,missing-function-docstr
|
|
|
935
981
|
_configure_pipconf(cfg)
|
|
936
982
|
|
|
937
983
|
# Establish the prerequisites
|
|
938
|
-
if
|
|
984
|
+
if fresh_venv:
|
|
939
985
|
cfg.python.provisioner.prerequisites(cfg)
|
|
940
986
|
|
|
941
987
|
# Plugins can define a 'venv_hook' function, to give them a
|
|
@@ -0,0 +1,196 @@
|
|
|
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
|
+
"""Module implementing the python_sbom plugin for csspin"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
from subprocess import DEVNULL, PIPE
|
|
23
|
+
from tempfile import TemporaryDirectory
|
|
24
|
+
|
|
25
|
+
from csspin import (
|
|
26
|
+
Verbosity,
|
|
27
|
+
backtick,
|
|
28
|
+
config,
|
|
29
|
+
die,
|
|
30
|
+
exists,
|
|
31
|
+
info,
|
|
32
|
+
memoizer,
|
|
33
|
+
rmtree,
|
|
34
|
+
sh,
|
|
35
|
+
task,
|
|
36
|
+
)
|
|
37
|
+
from csspin.tree import ConfigTree
|
|
38
|
+
from packaging.requirements import Requirement
|
|
39
|
+
from path import Path
|
|
40
|
+
|
|
41
|
+
defaults = config(
|
|
42
|
+
cyclonedx_bom_version="7.3.0",
|
|
43
|
+
project_paths=["{spin.project_root}"],
|
|
44
|
+
requires=config(spin=["csspin_python.python"]),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@task("python-sbom", when="sbom:build")
|
|
49
|
+
def sbom(cfg: ConfigTree) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Create the SBOMs for Python projects defined in via
|
|
52
|
+
'python_sbom.project_paths'.
|
|
53
|
+
|
|
54
|
+
This task assumes that the current package defines its dependencies that
|
|
55
|
+
are to be included in the SBOM via the "thirdparty" extra.
|
|
56
|
+
|
|
57
|
+
If there is no such extra, the SBOM generation is skipped.
|
|
58
|
+
"""
|
|
59
|
+
from csspin_python.python import get_project_metadata
|
|
60
|
+
|
|
61
|
+
stderr = PIPE if cfg.verbosity > Verbosity.NORMAL else DEVNULL
|
|
62
|
+
for project_path in cfg.python_sbom.project_paths:
|
|
63
|
+
if not exists(project_path):
|
|
64
|
+
die(f"Project path '{project_path}' does not exist.")
|
|
65
|
+
|
|
66
|
+
project_path = Path(project_path).absolute()
|
|
67
|
+
metadata = get_project_metadata(project_path, cfg.python.index_url)
|
|
68
|
+
third_party_deps = _collect_thirdparty_deps(
|
|
69
|
+
metadata.get("requires_dist", set()), python_version=cfg.python.version
|
|
70
|
+
)
|
|
71
|
+
sbom_content = _run_cyclonedx(cfg, third_party_deps, stderr)
|
|
72
|
+
_write_sbom(cfg, sbom_content, metadata.get("name"), metadata.get("version"))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def cleanup(cfg: ConfigTree) -> None:
|
|
76
|
+
"""Get rid of all generated .cdx.json files and the cyclonedx-bom venv."""
|
|
77
|
+
for cdx_file in cfg.spin.project_root.glob("*.python_sbom.cdx.json"):
|
|
78
|
+
rmtree(cdx_file)
|
|
79
|
+
rmtree(cfg.spin.project_root / ".spin" / "venv_csspin_python__python_sbom")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---- Internals ---------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _ensure_cyclonedx_venv(cfg: ConfigTree, binary_dir: str, quiet: str | None) -> Path:
|
|
86
|
+
"""Return the cyclonedx-bom interpreter path, (re)creating the venv if needed.
|
|
87
|
+
|
|
88
|
+
We install cyclonedx-bom into a persistent venv since defining it as a
|
|
89
|
+
dependency of csspin-python itself doesn't work at this moment.
|
|
90
|
+
See https://github.com/CycloneDX/cyclonedx-python/issues/1045
|
|
91
|
+
"""
|
|
92
|
+
venv_cdx = cfg.spin.project_root / ".spin" / "venv_csspin_python__python_sbom"
|
|
93
|
+
interpreter_cdx = venv_cdx / binary_dir / "python" + cfg.platform.exe
|
|
94
|
+
|
|
95
|
+
requested_version = cfg.python_sbom.cyclonedx_bom_version
|
|
96
|
+
memo_key = f"cyclonedx-bom=={requested_version}"
|
|
97
|
+
memo_file = venv_cdx / "csspin_python_sbom.memo"
|
|
98
|
+
|
|
99
|
+
if venv_cdx.exists():
|
|
100
|
+
with memoizer(memo_file) as memo:
|
|
101
|
+
if memo.check(memo_key):
|
|
102
|
+
info(
|
|
103
|
+
f"Reusing existing cyclonedx-bom {requested_version} from {venv_cdx}"
|
|
104
|
+
)
|
|
105
|
+
return interpreter_cdx
|
|
106
|
+
info(
|
|
107
|
+
f"cyclonedx-bom version mismatch (wanted={requested_version}), "
|
|
108
|
+
f"recreating {venv_cdx}"
|
|
109
|
+
)
|
|
110
|
+
rmtree(venv_cdx)
|
|
111
|
+
|
|
112
|
+
sh(cfg.python.interpreter, "-m", "venv", venv_cdx)
|
|
113
|
+
sh(
|
|
114
|
+
interpreter_cdx,
|
|
115
|
+
"-m",
|
|
116
|
+
"pip",
|
|
117
|
+
quiet,
|
|
118
|
+
"--disable-pip-version-check",
|
|
119
|
+
"install",
|
|
120
|
+
"--index-url",
|
|
121
|
+
cfg.python.index_url,
|
|
122
|
+
"cyclonedx-bom==" + requested_version,
|
|
123
|
+
)
|
|
124
|
+
with memoizer(memo_file) as memo:
|
|
125
|
+
memo.add(memo_key)
|
|
126
|
+
return interpreter_cdx
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Install third-party deps into a temp venv and return the CycloneDX JSON.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
binary_dir = "Scripts" if sys.platform == "win32" else "bin"
|
|
135
|
+
quiet = None if cfg.verbosity > Verbosity.NORMAL else "-q"
|
|
136
|
+
interpreter_cdx = _ensure_cyclonedx_venv(cfg, binary_dir, quiet)
|
|
137
|
+
|
|
138
|
+
with TemporaryDirectory() as tmp_dir:
|
|
139
|
+
venv = Path(tmp_dir) / "venv"
|
|
140
|
+
interpreter = venv / binary_dir / "python" + cfg.platform.exe
|
|
141
|
+
sh(cfg.python.interpreter, "-m", "venv", venv)
|
|
142
|
+
if third_party_deps:
|
|
143
|
+
sh(
|
|
144
|
+
interpreter,
|
|
145
|
+
"-m",
|
|
146
|
+
"pip",
|
|
147
|
+
quiet,
|
|
148
|
+
"install",
|
|
149
|
+
"--index-url",
|
|
150
|
+
cfg.python.index_url,
|
|
151
|
+
*[
|
|
152
|
+
f"--constraint={constraint}"
|
|
153
|
+
for constraint in cfg.python.constraints
|
|
154
|
+
],
|
|
155
|
+
*third_party_deps,
|
|
156
|
+
stderr=stderr,
|
|
157
|
+
)
|
|
158
|
+
sh(interpreter, "-m", "pip", quiet, "uninstall", "-y", "pip")
|
|
159
|
+
return backtick(interpreter_cdx, "-m", "cyclonedx_py", "environment", venv, stderr=stderr) # type: ignore[no-any-return] # noqa: E501
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _write_sbom(
|
|
163
|
+
cfg: ConfigTree, content: str, project_name: str, project_version: str
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Inject project metadata into the cyclonedx JSON and write the output file."""
|
|
166
|
+
output_file = cfg.spin.project_root / f"{project_name}.python_sbom.cdx.json"
|
|
167
|
+
# cyclonedx-bom doesn't add primary component name and version when not
|
|
168
|
+
# using pyproject.toml
|
|
169
|
+
sbom_json = json.loads(content)
|
|
170
|
+
sbom_json |= {
|
|
171
|
+
"metadata": {"component": {"name": project_name, "version": project_version}}
|
|
172
|
+
}
|
|
173
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
174
|
+
json.dump(sbom_json, f, indent=2, sort_keys=True)
|
|
175
|
+
info(f"Generated Python SBOM successfully ({output_file})")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _collect_thirdparty_deps(requires_dist: list, python_version: str) -> set[str]:
|
|
179
|
+
"""Extract 'thirdparty' dependency specifiers from project metadata."""
|
|
180
|
+
|
|
181
|
+
import platform
|
|
182
|
+
|
|
183
|
+
env = {
|
|
184
|
+
"sys_platform": sys.platform,
|
|
185
|
+
"extra": "thirdparty",
|
|
186
|
+
"platform_system": platform.system(),
|
|
187
|
+
"python_version": python_version,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
dependencies = set()
|
|
191
|
+
|
|
192
|
+
for require in requires_dist:
|
|
193
|
+
req = Requirement(require)
|
|
194
|
+
if req.marker and req.marker.evaluate(environment=env):
|
|
195
|
+
dependencies.add(req.name + str(req.specifier))
|
|
196
|
+
return dependencies
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- mode: yaml; coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Schema for the python_sbom plugin for csspin
|
|
4
|
+
|
|
5
|
+
python_sbom:
|
|
6
|
+
type: object
|
|
7
|
+
help: Configuration related to the python_sbom plugin for csspin
|
|
8
|
+
properties:
|
|
9
|
+
project_paths:
|
|
10
|
+
type: list
|
|
11
|
+
help: |
|
|
12
|
+
List of paths to the Python projects for which to generate the
|
|
13
|
+
SBOM.
|
|
14
|
+
cyclonedx_version:
|
|
15
|
+
type: str
|
|
16
|
+
help: |
|
|
17
|
+
Version of the cyclonedx-bom package to use for generating the
|
|
18
|
+
SBOM.
|
csspin_python/uv_provisioner.py
CHANGED
|
@@ -181,8 +181,9 @@ def _update_index_url_in_toml(cfg: ConfigTree) -> None:
|
|
|
181
181
|
Update the index-url in the uv.toml in case it changed.
|
|
182
182
|
"""
|
|
183
183
|
if (uv_toml_path := interpolate1(Path(cfg.uv_provisioner.uv_toml_path))).exists():
|
|
184
|
-
with open(uv_toml_path, mode="
|
|
184
|
+
with open(uv_toml_path, mode="rb") as fd:
|
|
185
185
|
toml_content = tomllib.load(fd)
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
if toml_content.get("index-url") != cfg.python.index_url:
|
|
187
|
+
toml_content["index-url"] = cfg.python.index_url
|
|
188
|
+
with open(uv_toml_path, mode="wb") as fd:
|
|
188
189
|
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: 4.1.0rc1
|
|
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>
|
|
@@ -15,13 +15,13 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
23
|
Classifier: Topic :: Software Development
|
|
24
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
25
|
Description-Content-Type: text/x-rst
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: platformdirs~=4.3.8
|
|
@@ -45,18 +45,21 @@ The following plugins are available:
|
|
|
45
45
|
- `csspin_python.debugpy`: A plugin for debugging Python code using `debugpy`_.
|
|
46
46
|
- `csspin_python.devpi`: A plugin for simplified usage of `devpi`_.
|
|
47
47
|
- `csspin_python.pytest`: A plugin for running tests using pytest.
|
|
48
|
-
- `csspin_python.python`: A plugin for provisioning Python environments
|
|
49
|
-
installing dependencies.
|
|
48
|
+
- `csspin_python.python`: A plugin for provisioning Python environments,
|
|
49
|
+
installing dependencies, and managing the virtual environment.
|
|
50
|
+
- `csspin_python.python_sbom`: A plugin for generating a `CycloneDX`_ Software
|
|
51
|
+
Bill of Materials (SBOM) for Python third-party dependencies.
|
|
50
52
|
- `csspin_python.radon`: A plugin for running `radon`_ to analyze code
|
|
51
53
|
complexity.
|
|
52
54
|
- `csspin_python.sphinx`: A plugin for building Sphinx documentation.
|
|
53
55
|
- `csspin_python.playwright`: A plugin for running tests using `playwright`_.
|
|
54
56
|
This plugin is deprecated, use the pytest plugin with the
|
|
55
57
|
'pytest.playwright.enabled=true' setting instead.
|
|
56
|
-
- `csspin_python.uv_provisioner`: A plugin that uses `uv`_ to provision the
|
|
58
|
+
- `csspin_python.uv_provisioner`: A plugin that uses `uv`_ to provision the
|
|
59
|
+
Python environment.
|
|
57
60
|
|
|
58
61
|
The package provides an ``aws_auth`` extra, that, if enabled, can authenticate
|
|
59
|
-
to `CONTACT Software GmbH`_'s AWS
|
|
62
|
+
to `CONTACT Software GmbH`_'s AWS CodeArtifact. It also provides an ``uv``
|
|
60
63
|
extra, that is necessary for using the ``csspin_python.uv_provisioner`` plugin.
|
|
61
64
|
|
|
62
65
|
Prerequisites
|
|
@@ -90,7 +93,7 @@ within the `spinfile.yaml` configuration file of your project.
|
|
|
90
93
|
- csspin_python.pytest
|
|
91
94
|
|
|
92
95
|
python:
|
|
93
|
-
version: 3.
|
|
96
|
+
version: 3.10.19
|
|
94
97
|
requirements:
|
|
95
98
|
- sphinx-click
|
|
96
99
|
- sphinx-rtd-theme
|
|
@@ -120,4 +123,5 @@ tests using ``spin pytest`` and do other great things.
|
|
|
120
123
|
.. _`devpi`: https://pypi.org/project/devpi
|
|
121
124
|
.. _`playwright`: https://pypi.org/project/pytest-playwright
|
|
122
125
|
.. _`radon`: https://pypi.org/project/radon
|
|
126
|
+
.. _`CycloneDX`: https://cyclonedx.org/
|
|
123
127
|
.. _`uv`: https://docs.astral.sh/uv/
|
|
@@ -2,20 +2,22 @@ csspin_python/behave.py,sha256=iJZeyIqB7V_NzTdLTZldNY9W_GGwCWkXe6WY69wpDqs,4997
|
|
|
2
2
|
csspin_python/behave_schema.yaml,sha256=8qoOCK-uTmwgRRW29urgK0X_kgn0zO0X34v89bvii2w,1241
|
|
3
3
|
csspin_python/debugpy.py,sha256=v0ZZopv5TNoSaFf2kiePsw9OmhBpjfOBFh0u71jTcnQ,962
|
|
4
4
|
csspin_python/debugpy_schema.yaml,sha256=BeH30nSirDYctkdhS9xMXUG5htj3PED_ZjmxPG5WRUc,364
|
|
5
|
-
csspin_python/devpi.py,sha256=
|
|
5
|
+
csspin_python/devpi.py,sha256=o7O06Dw-xt47y-M74TShcl9MUMaW78aFmw6i6ISYwoU,2351
|
|
6
6
|
csspin_python/devpi_schema.yaml,sha256=2gPATWjVcfvCTrGZX2FK6wH8hh9KS0XzZ35JvZeJGEU,487
|
|
7
7
|
csspin_python/playwright.py,sha256=oFfphLqa4AB6K9vasCUFHN0kFXu63n3ocrsqVuRp4-0,5102
|
|
8
8
|
csspin_python/playwright_schema.yaml,sha256=TSeR16YHa7m7bfO59F2eMV-jXcglluTJdEpUeL16saY,1178
|
|
9
|
-
csspin_python/pytest.py,sha256=
|
|
9
|
+
csspin_python/pytest.py,sha256=N9YaU_ouQab0PFPf46HLE7Vg4JeoZW4dzVD7EevqJ1U,4573
|
|
10
10
|
csspin_python/pytest_schema.yaml,sha256=tzXtdF6MvGC9v59EVRJFfLeMMHqPsXcFXy2zJtRECBI,1535
|
|
11
|
-
csspin_python/python.py,sha256=
|
|
11
|
+
csspin_python/python.py,sha256=yqH1eG6mqRJomu-F99cB74PFaUzbSoG_dhpUjYwr1xE,37725
|
|
12
|
+
csspin_python/python_sbom.py,sha256=RpqobiJ768Q6no7uwYAqvykp1-Bj0bkvED3a3pZbEBI,6817
|
|
13
|
+
csspin_python/python_sbom_schema.yaml,sha256=VCxY9E2AG_fS00dvIYe6Fbvz1maPjpY0zyZK1Z0wJu4,538
|
|
12
14
|
csspin_python/python_schema.yaml,sha256=pgVVjByUYjxQWek7aFmjQzRwmq2ROLvHYgwGPMrT9sM,6351
|
|
13
15
|
csspin_python/radon.py,sha256=uFqm6FEi5oWj-_XVaAm3s9cam0cUmr1_FwRf40K6xWs,1876
|
|
14
16
|
csspin_python/radon_schema.yaml,sha256=rlRzXw5z4XbjOVznRiUxWGP4E9hx1Jm-gGw1iQiYzE0,548
|
|
15
|
-
csspin_python/uv_provisioner.py,sha256=
|
|
17
|
+
csspin_python/uv_provisioner.py,sha256=1e-_Sb39JrqNWyaUNeBX59R5tutXLJ1ZsT7urCN1U0I,6044
|
|
16
18
|
csspin_python/uv_provisioner_schema.yaml,sha256=Y8ZNC2OMnhR8Us3WUXAXK9hMjqGWAKFJB2puX4X5XNQ,727
|
|
17
|
-
csspin_python-
|
|
18
|
-
csspin_python-
|
|
19
|
-
csspin_python-
|
|
20
|
-
csspin_python-
|
|
21
|
-
csspin_python-
|
|
19
|
+
csspin_python-4.1.0rc1.dist-info/licenses/LICENSE,sha256=4MAecetnRTQw5DlHtiikDSzKWO1xVLwzM5_DsPMYlnE,10172
|
|
20
|
+
csspin_python-4.1.0rc1.dist-info/METADATA,sha256=kW0dZXu1Wwuhw4EooWroAHOZGMw5wjBso1MFJDzkJX8,5257
|
|
21
|
+
csspin_python-4.1.0rc1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
22
|
+
csspin_python-4.1.0rc1.dist-info/top_level.txt,sha256=QSeglMEGbFu1z4L6MCQYwo01NgL0KojWvC4rzgMQ8gU,14
|
|
23
|
+
csspin_python-4.1.0rc1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|