csspin-python 4.0.0__py3-none-any.whl → 4.1.0rc2__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/python.py CHANGED
@@ -69,12 +69,15 @@ 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
80
+ from functools import cache
78
81
  from subprocess import CalledProcessError, check_output
79
82
  from textwrap import dedent, indent
80
83
  from typing import Generator, Iterable, Type, Union
@@ -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,
@@ -716,6 +720,30 @@ class PythonActivate(ActivateScriptPatcher):
716
720
  return value
717
721
 
718
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
+
719
747
  def get_site_packages(interpreter: Path) -> Path:
720
748
  """Return the path to the virtual environments site-packages."""
721
749
  return Path(
@@ -0,0 +1,297 @@
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 email.utils
21
+ import json
22
+ import sys
23
+ import sysconfig
24
+ from importlib.metadata import metadata as _pkg_metadata
25
+ from importlib.metadata import version as _pkg_version
26
+ from subprocess import DEVNULL, PIPE
27
+ from tempfile import TemporaryDirectory
28
+
29
+ from csspin import (
30
+ Verbosity,
31
+ backtick,
32
+ config,
33
+ die,
34
+ exists,
35
+ info,
36
+ memoizer,
37
+ rmtree,
38
+ sh,
39
+ task,
40
+ )
41
+ from csspin.tree import ConfigTree
42
+ from packaging.requirements import Requirement
43
+ from path import Path
44
+
45
+ defaults = config(
46
+ cyclonedx_bom_version="7.3.0",
47
+ project_paths=["{spin.project_root}"],
48
+ requires=config(spin=["csspin_python.python"]),
49
+ )
50
+
51
+
52
+ @task("python-sbom", when="sbom:build")
53
+ def sbom(cfg: ConfigTree) -> None:
54
+ """
55
+ Create the SBOMs for Python projects defined in via
56
+ 'python_sbom.project_paths'.
57
+
58
+ This task assumes that the current package defines its dependencies that
59
+ are to be included in the SBOM via the "thirdparty" extra.
60
+
61
+ If there is no such extra, the SBOM generation is skipped.
62
+ """
63
+ from csspin_python.python import get_project_metadata
64
+
65
+ stderr = PIPE if cfg.verbosity > Verbosity.NORMAL else DEVNULL
66
+ for project_path in cfg.python_sbom.project_paths:
67
+ if not exists(project_path):
68
+ die(f"Project path '{project_path}' does not exist.")
69
+
70
+ project_path = Path(project_path).absolute()
71
+ metadata = get_project_metadata(project_path, cfg.python.index_url)
72
+ third_party_deps = _collect_thirdparty_deps(
73
+ metadata.get("requires_dist", set()), python_version=cfg.python.version
74
+ )
75
+ sbom_json = json.loads(_run_cyclonedx(cfg, third_party_deps, stderr))
76
+ _enrich_sbom(sbom_json, metadata, third_party_deps)
77
+ _write_sbom(cfg, sbom_json, metadata.get("name"))
78
+
79
+
80
+ def cleanup(cfg: ConfigTree) -> None:
81
+ """Get rid of all generated .cdx.json files and the cyclonedx-bom venv."""
82
+ for cdx_file in cfg.spin.project_root.glob("*.python_sbom.cdx.json"):
83
+ rmtree(cdx_file)
84
+ rmtree(cfg.spin.project_root / ".spin" / "venv_csspin_python__python_sbom")
85
+
86
+
87
+ # ---- Internals ---------------------------------------------------------------
88
+
89
+
90
+ def _ensure_cyclonedx_venv(cfg: ConfigTree, binary_dir: str, quiet: str | None) -> Path:
91
+ """Return the cyclonedx-bom interpreter path, (re)creating the venv if needed.
92
+
93
+ We install cyclonedx-bom into a persistent venv since defining it as a
94
+ dependency of csspin-python itself doesn't work at this moment.
95
+ See https://github.com/CycloneDX/cyclonedx-python/issues/1045
96
+ """
97
+ venv_cdx = cfg.spin.project_root / ".spin" / "venv_csspin_python__python_sbom"
98
+ interpreter_cdx = venv_cdx / binary_dir / "python" + cfg.platform.exe
99
+
100
+ requested_version = cfg.python_sbom.cyclonedx_bom_version
101
+ memo_key = f"cyclonedx-bom=={requested_version}"
102
+ memo_file = venv_cdx / "csspin_python_sbom.memo"
103
+
104
+ if venv_cdx.exists():
105
+ with memoizer(memo_file) as memo:
106
+ if memo.check(memo_key):
107
+ info(
108
+ f"Reusing existing cyclonedx-bom {requested_version} from {venv_cdx}"
109
+ )
110
+ return interpreter_cdx
111
+ info(
112
+ f"cyclonedx-bom version mismatch (wanted={requested_version}), "
113
+ f"recreating {venv_cdx}"
114
+ )
115
+ rmtree(venv_cdx)
116
+
117
+ sh(cfg.python.interpreter, "-m", "venv", venv_cdx)
118
+ sh(
119
+ interpreter_cdx,
120
+ "-m",
121
+ "pip",
122
+ quiet,
123
+ "--disable-pip-version-check",
124
+ "install",
125
+ "--index-url",
126
+ cfg.python.index_url,
127
+ "cyclonedx-bom==" + requested_version,
128
+ )
129
+ with memoizer(memo_file) as memo:
130
+ memo.add(memo_key)
131
+ return interpreter_cdx
132
+
133
+
134
+ def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) -> str:
135
+ """
136
+ Install third-party deps into a temp venv and return the CycloneDX JSON.
137
+ """
138
+
139
+ binary_dir = "Scripts" if sys.platform == "win32" else "bin"
140
+ quiet = None if cfg.verbosity > Verbosity.NORMAL else "-q"
141
+ interpreter_cdx = _ensure_cyclonedx_venv(cfg, binary_dir, quiet)
142
+
143
+ with TemporaryDirectory() as tmp_dir:
144
+ venv = Path(tmp_dir) / "venv"
145
+ interpreter = venv / binary_dir / "python" + cfg.platform.exe
146
+ sh(cfg.python.interpreter, "-m", "venv", venv)
147
+ if third_party_deps:
148
+ sh(
149
+ interpreter,
150
+ "-m",
151
+ "pip",
152
+ quiet,
153
+ "install",
154
+ "--index-url",
155
+ cfg.python.index_url,
156
+ *[
157
+ f"--constraint={constraint}"
158
+ for constraint in cfg.python.constraints
159
+ ],
160
+ *third_party_deps,
161
+ stderr=stderr,
162
+ )
163
+ sh(interpreter, "-m", "pip", quiet, "uninstall", "-y", "pip")
164
+ return backtick(interpreter_cdx, "-m", "cyclonedx_py", "environment", venv, stderr=stderr) # type: ignore[no-any-return] # noqa: E501
165
+
166
+
167
+ def _write_sbom(cfg: ConfigTree, sbom_json: dict, project_name: str) -> None:
168
+ """Write the CycloneDX JSON document to the output file."""
169
+ platform_tag = sysconfig.get_platform().replace("-", "_")
170
+ output_file = cfg.spin.project_root / (
171
+ f"{project_name}.{platform_tag}.python_sbom.cdx.json"
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 _parse_authors(author_name: str, author_email: str) -> str:
179
+ """Parse RFC 2822 Author-email metadata into 'name (email)' format."""
180
+ if not author_email:
181
+ die("Project metadata has no Author-email field.")
182
+ return ""
183
+
184
+ entries = email.utils.getaddresses([author_email])
185
+
186
+ if len(entries) == 1 and not entries[0][0] and author_name:
187
+ entries = [(author_name, entries[0][1])]
188
+
189
+ for name, addr in entries:
190
+ if not name:
191
+ die(f"Author entry '{addr}' has no name; all authors require a name.")
192
+ return ""
193
+ if not addr:
194
+ die(f"Author entry '{name}' has no email; all authors require an email.")
195
+ return ""
196
+
197
+ return ", ".join(f"{name} ({addr})" for name, addr in entries)
198
+
199
+
200
+ def _build_primary_component(metadata: dict) -> tuple[dict, str]:
201
+ """Build the CycloneDX primary component dict and its bom-ref from project metadata."""
202
+ if not (name := metadata.get("name")):
203
+ die("Project metadata is missing 'name'.")
204
+ if not (version := metadata.get("version")):
205
+ die("Project metadata is missing 'version'.")
206
+ if not (license_id := metadata.get("license")):
207
+ die("Project metadata is missing 'license'.")
208
+
209
+ authors = _parse_authors(
210
+ author_name=metadata.get("author", "").strip(),
211
+ author_email=metadata.get("author_email", "").strip(),
212
+ )
213
+ primary_ref = f"{name}=={version}"
214
+ component = {
215
+ "author": authors,
216
+ "bom-ref": primary_ref,
217
+ "licenses": [{"expression": license_id}],
218
+ "name": name,
219
+ "type": "application",
220
+ "version": version,
221
+ }
222
+ return component, primary_ref
223
+
224
+
225
+ def _enrich_sbom(
226
+ sbom_json: dict,
227
+ metadata: dict,
228
+ third_party_deps: set[str],
229
+ ) -> None:
230
+ """Add the primary component and its dependency entry to the CycloneDX document."""
231
+ component, primary_ref = _build_primary_component(metadata)
232
+ existing_metadata = sbom_json.get("metadata", {})
233
+ existing_tools = existing_metadata.get("tools", {})
234
+ existing_tool_components = (
235
+ existing_tools
236
+ if isinstance(existing_tools, list)
237
+ else existing_tools.get("components", [])
238
+ )
239
+ sbom_json["metadata"] = {
240
+ **existing_metadata,
241
+ "component": component,
242
+ "tools": {
243
+ "components": existing_tool_components
244
+ + [
245
+ {
246
+ "description": "Python SBOM plugin for csspin",
247
+ "licenses": [
248
+ {
249
+ "expression": _pkg_metadata("csspin-python")[
250
+ "License-Expression"
251
+ ]
252
+ }
253
+ ],
254
+ "name": "csspin-python",
255
+ "supplier": {
256
+ "name": "CONTACT Software GmbH",
257
+ "url": [
258
+ "https://www.contact-software.com/",
259
+ "https://pypi.org/project/csspin-python/",
260
+ ],
261
+ },
262
+ "type": "application",
263
+ "version": _pkg_version("csspin-python"),
264
+ }
265
+ ]
266
+ },
267
+ }
268
+ direct_dep_names = {Requirement(dep).name.lower() for dep in third_party_deps}
269
+ direct_dep_refs = sorted(
270
+ comp["bom-ref"]
271
+ for comp in sbom_json.get("components", [])
272
+ if comp.get("name", "").lower() in direct_dep_names and "bom-ref" in comp
273
+ )
274
+ dependencies = sbom_json.get("dependencies", [])
275
+ dependencies.append({"dependsOn": direct_dep_refs, "ref": primary_ref})
276
+ sbom_json["dependencies"] = dependencies
277
+
278
+
279
+ def _collect_thirdparty_deps(requires_dist: list, python_version: str) -> set[str]:
280
+ """Extract 'thirdparty' dependency specifiers from project metadata."""
281
+
282
+ import platform
283
+
284
+ env = {
285
+ "sys_platform": sys.platform,
286
+ "extra": "thirdparty",
287
+ "platform_system": platform.system(),
288
+ "python_version": python_version,
289
+ }
290
+
291
+ dependencies = set()
292
+
293
+ for require in requires_dist:
294
+ req = Requirement(require)
295
+ if req.marker and req.marker.evaluate(environment=env):
296
+ dependencies.add(req.name + str(req.specifier))
297
+ 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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csspin-python
3
- Version: 4.0.0
3
+ Version: 4.1.0rc2
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>
@@ -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 and
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 Python environment.
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 Codeartifact. It also provides an ``uv``
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
@@ -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/
@@ -8,14 +8,18 @@ csspin_python/playwright.py,sha256=oFfphLqa4AB6K9vasCUFHN0kFXu63n3ocrsqVuRp4-0,5
8
8
  csspin_python/playwright_schema.yaml,sha256=TSeR16YHa7m7bfO59F2eMV-jXcglluTJdEpUeL16saY,1178
9
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=ogmaOdaXXZHaCYaPa0b2y11Rn6ZgMgzRftUG6-seBWM,36824
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
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
17
  csspin_python/uv_provisioner.py,sha256=1e-_Sb39JrqNWyaUNeBX59R5tutXLJ1ZsT7urCN1U0I,6044
16
18
  csspin_python/uv_provisioner_schema.yaml,sha256=Y8ZNC2OMnhR8Us3WUXAXK9hMjqGWAKFJB2puX4X5XNQ,727
17
- csspin_python-4.0.0.dist-info/licenses/LICENSE,sha256=4MAecetnRTQw5DlHtiikDSzKWO1xVLwzM5_DsPMYlnE,10172
18
- csspin_python-4.0.0.dist-info/METADATA,sha256=yC8s-jxM6RQjQWi6UsukDCZlGdoV_NOFubcZ0Y-QERM,5035
19
- csspin_python-4.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
20
- csspin_python-4.0.0.dist-info/top_level.txt,sha256=QSeglMEGbFu1z4L6MCQYwo01NgL0KojWvC4rzgMQ8gU,14
21
- csspin_python-4.0.0.dist-info/RECORD,,
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,,
@@ -0,0 +1,78 @@
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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "4.1.0rc2",
3
+ "distance": 0,
4
+ "node": "gcaf72582381496c091ef82854c9703c0c9583259",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-06-23"
8
+ }