Skip to content

feat: populate component.authors from pyproject.toml, Poetry manifests and packaging metadata - #1096

Open
SemyonAndreyev wants to merge 1 commit into
CycloneDX:mainfrom
q2quantum:feat/component-authors
Open

feat: populate component.authors from pyproject.toml, Poetry manifests and packaging metadata#1096
SemyonAndreyev wants to merge 1 commit into
CycloneDX:mainfrom
q2quantum:feat/component-authors

Conversation

@SemyonAndreyev

Copy link
Copy Markdown

Description

Populates component.authors (CycloneDX 1.6+, structured, repeatable) and derives the legacy singular component.author for the single-author case, from three independent sources: PEP 621 project.authors, Poetry's tool.poetry.authors, and packaging core-metadata Author/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 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 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.
  • PEP 621 requires authors to be tables, but tests/_data/infiles/pipenv/no-deps/pyproject.toml already exercises the non-compliant authors = ["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, new test_utils_poetry.py, new test_utils_packaging.py, and an extended test_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/mypy clean.

Resolves or fixes issue: #648

AI Tool Disclosure

  • My contribution includes AI-generated content, as disclosed below:
    • AI Tools: Claude Code
    • LLMs and versions: Claude Sonnet 5
    • Prompts: Implement the three data sources for issue feat: populate 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

…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>
@SemyonAndreyev
SemyonAndreyev requested a review from a team as a code owner September 3, 2026 19:03
@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🔴 Metrics 33 complexity · 0 duplication

Metric Results
Complexity ⚠️ 33 (≤ 20 complexity)
Duplication 0

View in Codacy

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant