fix(bundler): treat an explicitly null catalog field as empty, not "None" - #4532
jawwad-ali wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Catalog entry IDs remain incorrectly normalized, and the nested version-field change lacks regression coverage.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Normalizes explicit null catalog values to empty strings using _text.
Changes:
- Fixes null source and catalog-entry parsing.
- Adds regression tests for affected fields and required source values.
- Reported verification was reviewed but not rerun.
File summaries
| File | Description |
|---|---|
src/specify_cli/bundler/models/catalog.py |
Applies null-safe text normalization. |
tests/contract/test_catalog_schema.py |
Adds null-value regression coverage. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback and resolve conflicts
|
Please resolve conflicts |
…one"
`CatalogSource.from_dict` and `CatalogEntry.from_dict` coerced fields with
`str(data.get(key, ""))`. That default only covers a *missing* key. A key that
is present but null -- how YAML spells an empty field (`author:` with nothing
after it), and what a generator emits for an absent value in JSON -- yields
`None`, and `str(None)` is the literal `"None"`.
Reproduced on main:
name = 'None'
version = 'None'
role = 'None'
description = 'None'
author = 'None'
license = 'None'
download_url = 'None'
sha256 = None (already guarded)
repository = None (already guarded)
The same constructor already guards `sha256` and `repository` against exactly
this, so the treatment was internally inconsistent.
For `CatalogSource` it is more than cosmetic: `"None"` is truthy, so it
defeats the required-field guards and a malformed source was ACCEPTED --
CatalogSource.from_dict({"id": None, "url": None, ...})
-> id = 'None', url = 'None'
registering a catalog source named "None" pointing at a URL "None".
Routed through the existing `manifest._text` helper, whose docstring already
describes this failure mode for bundle manifests.
Rebased onto current main (files moved in the workflow/bundler restructure).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
db47ee5 to
f8f6682
Compare
|
@mnriem Conflicts resolved (f8f6682). The fix moved to Verified: 11 new cases fail with the source reverted to Rebased as a single commit on current |


Problem
CatalogSource.from_dictandCatalogEntry.from_dictcoerce fields withstr(data.get(key, "")).That default only covers a missing key. A key that is present but null — how YAML spells an empty field (
author:with nothing after it), and what a generator emits for an absent value in JSON — yieldsNone, andstr(None)is the literal"None".Reproduction on current
main(c173bf1)The same constructor already guards
sha256andrepositoryagainst exactly this, so the treatment was internally inconsistent.For
CatalogSourceit is more than cosmetic"None"is truthy, so it defeats the required-field guards and the malformed source is accepted:A user whose config has an empty
id:silently gets a catalog source registered under the name"None", pointing at a URL"None".Fix
Route both constructors through the existing
manifest._texthelper, whose docstring already describes this precise failure mode for bundle manifests:After the fix:
Verification
upstream/main→ 54 passed with the fix.CatalogEntryfields, plus dedicated tests for the nullidand nullurlsources.tests/contract: 170 passed, up from a clean-mainbaseline of 161 passed, with zero pre-existing or new failures.models.manifestimport (verified by importingmodels.catalogdirectly).uvx ruff@0.15.0 check src tests→ cleanBehaviour change, disclosed: only inputs that previously produced the literal
"None"change. A source with a nullid/urlnow raises the accurate "missing its 'id'/'url'" error instead of being accepted;download_url: nullnow reads as empty, so it reports "has no download_url" rather than failing later as a non-HTTP(S) URL.Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current
main.🤖 Generated with Claude Code