feat: populate component.authors from pyproject.toml, Poetry manifests and packaging metadata - #1096
feat: populate component.authors from pyproject.toml, Poetry manifests and packaging metadata#1096SemyonAndreyev wants to merge 2 commits into
Conversation
…s and packaging metadata Fixes CycloneDX#648. Adds the structured, repeatable `components[].authors` (CycloneDX 1.6+) and derives the legacy singular `components[].author` string for the single-author case, from three independent sources: - PEP 621 `project.authors` (pep621.py) -- tables of name/email, with lenient handling of the non-compliant-but-real-world "Name <email>" string form some pyproject.toml files use here instead. - Poetry's `tool.poetry.authors` (utils/poetry.py) -- "Name <email>" strings. - Packaging core-metadata's `Author`/`Author-email` (utils/packaging.py, wired into the environment scan) -- the two are independent free-text fields with no guaranteed correlation when either holds more than one person; see the docstring of `metadata2authors()` for the rules used to combine or keep them separate. New shared helper module `utils/contact.py`: - `person_string2contact()` parses the "Name <email>" convention used by both Poetry and packaging metadata. - `contacts2author()` derives the legacy `author` string only when there is exactly one author -- CycloneDX has no agreed-upon way to fold multiple authors into one string (see CycloneDX/specification#335), so this deliberately does not guess a join convention. Found and worked around a real footgun in `email.utils.getaddresses()`: fed a bare name with no `@`, it silently mis-splits on whitespace and keeps only the last word (`getaddresses(['Jane Doe']) == [('', 'Jane')]`). `metadata2authors()` filters out any parsed address that doesn't actually contain `@` before trusting it. Tests: 56 new cases across a new `test_utils_contact.py`, new `test_utils_poetry.py`, new `test_utils_packaging.py`, and an extended `test_utils_pep621.py` -- covering both happy paths and the edge cases above. Full existing suite passes unchanged aside from the expected snapshot updates (root components in fixtures that declare authors now show them). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xJ8pA9rPpAjVATkZCzCS2 Signed-off-by: Semyon Andreev <q2quantum.app@gmail.com>
Not up to standards ⛔🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…ting Additional test coverage for the component.authors feature (CycloneDX#1096/CycloneDX#648), found by systematically probing edge cases of the three author sources: - contact.py: `_PERSON_STRING_MATCHER.match()` returning None was marked `# pragma: nocover -- the pattern matches any string, including the empty one`, but that comment is incorrect -- multiple `<...>` fragments, an unbalanced bracket, or trailing text after a closing `>` all make the whole match fail (verified live). Removed the stale pragma, documented why the branch is reachable, and added 5 new test cases exercising it (test_none_for_unparseable_shape). - pep621.py: `project2authors()` already tolerates a not-per-spec *string* entry in `authors` (see test_project2authors_string_entry_not_per_spec_ but_tolerated), but a non-dict, non-string entry -- e.g. `authors = [123]`, valid TOML, invalid PEP 621 -- crashed with AttributeError on `author.get(...)`. Verified live before fixing. Now skipped, consistent with the function's own existing "be lenient, don't crash" intent for the string case; 3 new test cases. - poetry.py: same class of bug in `poetry2authors()` -- a non-string entry crashed with TypeError from `re.match()` inside `person_string2contact()`. Same fix, same reasoning; 3 new test cases. metadata2authors() in packaging.py was also checked for a similar gap but already has thorough dedicated coverage matching its own documented rules (9 existing tests); no gap found there. 68/68 tests pass across the 4 affected test modules (11 new cases added). flake8 and mypy clean on all changed files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xJ8pA9rPpAjVATkZCzCS2 Signed-off-by: Semyon Andreev <q2quantum.app@gmail.com>
|
Pushed a follow-up commit while stress-testing this feature's edge cases further: found and fixed two real crashes on not-per-spec (but valid TOML)
Both are now skipped, consistent with this same code's existing "be lenient, don't crash" handling of a not-per-spec string entry in the PEP 621 case. Also removed a stale 11 new test cases, 68/68 passing across the 4 affected test modules. |
Description
Populates
component.authors(CycloneDX 1.6+, structured, repeatable) and derives the legacy singularcomponent.authorfor the single-author case, from three independent sources: PEP 621project.authors, Poetry'stool.poetry.authors, and packaging core-metadataAuthor/Author-email(wired into the environment scan).New shared helper module
cyclonedx_py/_internal/utils/contact.py:person_string2contact()parses the"Name <email>"convention used by both Poetry and packaging metadata.contacts2author()derives the legacyauthorstring only when there is exactly one author — CycloneDX has no agreed-upon way to fold multiple authors into one string (see CycloneDX/specification#335), so this deliberately does not guess a join convention and leaves it unset otherwise.Along the way, found and handled two real edge cases:
email.utils.getaddresses()silently mis-splits a bare name with no@on whitespace, keeping only the last word (getaddresses(['Jane Doe']) == [('', 'Jane')]).metadata2authors()filters out any parsed address that doesn't actually contain@before trusting it.authorsto be tables, buttests/_data/infiles/pipenv/no-deps/pyproject.tomlalready exercises the non-compliantauthors = ["Name <email>", ...]string form some real-world files use anyway —project2authors()now falls back to the same string parser instead of raising.56 new test cases across a new
test_utils_contact.py, newtest_utils_poetry.py, newtest_utils_packaging.py, and an extendedtest_utils_pep621.py. Full existing suite passes with only the expected snapshot updates (root components in fixtures that declare authors now show them).flake8/isort/mypyclean.Resolves or fixes issue: #648
AI Tool Disclosure
component.authors#648 as specified by the maintainer's own comments on the issue; add thorough test coverage; verify against the full existing test suite with no regressions.Affirmation
🤖 Generated with Claude Code