csspin-python 4.1.0rc1__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.
@@ -17,8 +17,12 @@
17
17
 
18
18
  """Module implementing the python_sbom plugin for csspin"""
19
19
 
20
+ import email.utils
20
21
  import json
21
22
  import sys
23
+ import sysconfig
24
+ from importlib.metadata import metadata as _pkg_metadata
25
+ from importlib.metadata import version as _pkg_version
22
26
  from subprocess import DEVNULL, PIPE
23
27
  from tempfile import TemporaryDirectory
24
28
 
@@ -68,8 +72,9 @@ def sbom(cfg: ConfigTree) -> None:
68
72
  third_party_deps = _collect_thirdparty_deps(
69
73
  metadata.get("requires_dist", set()), python_version=cfg.python.version
70
74
  )
71
- sbom_content = _run_cyclonedx(cfg, third_party_deps, stderr)
72
- _write_sbom(cfg, sbom_content, metadata.get("name"), metadata.get("version"))
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"))
73
78
 
74
79
 
75
80
  def cleanup(cfg: ConfigTree) -> None:
@@ -159,22 +164,118 @@ def _run_cyclonedx(cfg: ConfigTree, third_party_deps: set[str], stderr: int) ->
159
164
  return backtick(interpreter_cdx, "-m", "cyclonedx_py", "environment", venv, stderr=stderr) # type: ignore[no-any-return] # noqa: E501
160
165
 
161
166
 
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
- }
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
173
  with open(output_file, "w", encoding="utf-8") as f:
174
174
  json.dump(sbom_json, f, indent=2, sort_keys=True)
175
175
  info(f"Generated Python SBOM successfully ({output_file})")
176
176
 
177
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
+
178
279
  def _collect_thirdparty_deps(requires_dist: list, python_version: str) -> set[str]:
179
280
  """Extract 'thirdparty' dependency specifiers from project metadata."""
180
281
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csspin-python
3
- Version: 4.1.0rc1
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>
@@ -9,15 +9,17 @@ csspin_python/playwright_schema.yaml,sha256=TSeR16YHa7m7bfO59F2eMV-jXcglluTJdEpU
9
9
  csspin_python/pytest.py,sha256=N9YaU_ouQab0PFPf46HLE7Vg4JeoZW4dzVD7EevqJ1U,4573
10
10
  csspin_python/pytest_schema.yaml,sha256=tzXtdF6MvGC9v59EVRJFfLeMMHqPsXcFXy2zJtRECBI,1535
11
11
  csspin_python/python.py,sha256=yqH1eG6mqRJomu-F99cB74PFaUzbSoG_dhpUjYwr1xE,37725
12
- csspin_python/python_sbom.py,sha256=RpqobiJ768Q6no7uwYAqvykp1-Bj0bkvED3a3pZbEBI,6817
12
+ csspin_python/python_sbom.py,sha256=8NVZ6uVUz7rU3VhEoXJ_wHLCdv4BIV6Zjz1ZRa9nQhc,10488
13
13
  csspin_python/python_sbom_schema.yaml,sha256=VCxY9E2AG_fS00dvIYe6Fbvz1maPjpY0zyZK1Z0wJu4,538
14
14
  csspin_python/python_schema.yaml,sha256=pgVVjByUYjxQWek7aFmjQzRwmq2ROLvHYgwGPMrT9sM,6351
15
15
  csspin_python/radon.py,sha256=uFqm6FEi5oWj-_XVaAm3s9cam0cUmr1_FwRf40K6xWs,1876
16
16
  csspin_python/radon_schema.yaml,sha256=rlRzXw5z4XbjOVznRiUxWGP4E9hx1Jm-gGw1iQiYzE0,548
17
17
  csspin_python/uv_provisioner.py,sha256=1e-_Sb39JrqNWyaUNeBX59R5tutXLJ1ZsT7urCN1U0I,6044
18
18
  csspin_python/uv_provisioner_schema.yaml,sha256=Y8ZNC2OMnhR8Us3WUXAXK9hMjqGWAKFJB2puX4X5XNQ,727
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,,
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
+ }