Skip to content

feat: add pypi ecosystem to blast (CM-1358) - #4449

Draft
ulemons wants to merge 1 commit into
mainfrom
feat/add-pypi-ecosystem-to-blast
Draft

feat: add pypi ecosystem to blast (CM-1358)#4449
ulemons wants to merge 1 commit into
mainfrom
feat/add-pypi-ecosystem-to-blast

Conversation

@ulemons

@ulemons ulemons commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds PyPI as a supported blast-radius ecosystem, mirroring the existing npm/go/maven/cargo/nuget/rubygems pipeline (intel → dependents → reachability → report).

Changes

  • Add comparePep440 to osv/versionCompare.ts — hand-written PEP 440 comparator (epoch, release tuple, pre/post/dev segments, local version ordering); PyPI is not semver so this is new logic, not a clone of an existing comparator.
  • Add toBarePypiName / toPypiNormalizedName to packageIdentifier.ts (PEP 503 name normalization).
  • Add findPackageIdByPurl to services/libs/data-access-layer/src/packages/osv.ts (protected file, needs code-owner approval). Needed because packages.name for pypi rows can drift from the canonical PEP 503 spelling (writer disagreement between the deps.dev and pypi ingestion paths), while packages.purl is always normalized — so the intel stage looks packages up by purl instead of by name for this ecosystem only.
  • Add stages/pypi/pypiConstraint.ts — PEP 440 specifier-set matching (~=, ===, ==/!= with .* wildcard, <=/>=/</>), over-inclusive by design on unparseable input, matching the contract of every other ecosystem's constraint matcher.
  • Add clients/pypiSource.ts — downloads and extracts a package's source (sdist preferred, wheel fallback) by reading the URL from pypi.org's per-version JSON API rather than constructing it (the real filename/hash can't be derived).
  • Add stages/pypi/{intelPyPi,dependentsPyPi,dependentsScanPyPi,reachabilityConfig}.ts and agent/pypiPrompts.ts, and wire pypi into stages/ecosystems.ts's ECOSYSTEMS registry, blast-radius/ecosystemSupport.ts, and the public API's SUPPORTED_BLAST_RADIUS_ECOSYSTEMS zod enum.
  • Range resolution for PyPI follows the Maven template rather than Cargo's: PyPI's OSV entries are ECOSYSTEM-typed ranges (not SEMVER-typed), even though PyPI — like Cargo — is a deps.dev EDGE ecosystem with a resolved dependent version available, which dependents-scan prefers as ground truth over the declared specifier.
  • Scope: blast-radius only. PyPI advisories are not yet added to osv/schedule.ts's ingestion allowlist, so pypi analyses currently resolve OSV data live and carry a null advisory_id until a follow-up PR.
  • Extend dispatch.test.ts and ecosystemSupport.test.ts with pypi routing/registration cases.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Performance improvement
  • Chore / dependency update
  • Documentation

JIRA ticket

1358

Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
@ulemons ulemons self-assigned this Aug 6, 2026
Copilot AI balanced review requested due to automatic review settings August 6, 2026 13:57
@ulemons ulemons added the Feature Created by Linear-GitHub Sync label Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds PyPI support to the blast-radius analysis pipeline.

Changes:

  • Implements PEP 440 comparison and dependency constraints.
  • Adds PyPI source extraction, analysis stages, and prompts.
  • Registers PyPI in worker and public API routing.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
services/libs/data-access-layer/src/packages/osv.ts Adds package lookup by purl.
services/apps/packages_worker/src/pypi/types.ts Extends PyPI response types.
services/apps/packages_worker/src/osv/versionCompare.ts Adds PEP 440 comparison.
services/apps/packages_worker/src/osv/__tests__/versionCompare.test.ts Tests PyPI version ordering.
services/apps/packages_worker/src/blast-radius/stages/pypi/reachabilityConfig.ts Configures PyPI reachability.
services/apps/packages_worker/src/blast-radius/stages/pypi/pypiConstraint.ts Matches PyPI dependency constraints.
services/apps/packages_worker/src/blast-radius/stages/pypi/intelPyPi.ts Implements PyPI intelligence stage.
services/apps/packages_worker/src/blast-radius/stages/pypi/dependentsScanPyPi.ts Scans reverse dependencies.
services/apps/packages_worker/src/blast-radius/stages/pypi/dependentsPyPi.ts Persists dependent candidates.
services/apps/packages_worker/src/blast-radius/stages/pypi/__tests__/pypiConstraint.test.ts Tests constraint matching.
services/apps/packages_worker/src/blast-radius/stages/ecosystems.ts Registers PyPI stages.
services/apps/packages_worker/src/blast-radius/stages/__tests__/dispatch.test.ts Tests PyPI dispatch.
services/apps/packages_worker/src/blast-radius/packageIdentifier.ts Adds PyPI name normalization.
services/apps/packages_worker/src/blast-radius/ecosystemSupport.ts Marks PyPI as supported.
services/apps/packages_worker/src/blast-radius/clients/pypiSource.ts Downloads and extracts distributions.
services/apps/packages_worker/src/blast-radius/clients/__tests__/pypiSource.test.ts Tests source extraction.
services/apps/packages_worker/src/blast-radius/agent/pypiPrompts.ts Adds Python analysis prompts.
services/apps/packages_worker/src/blast-radius/__tests__/ecosystemSupport.test.ts Tests ecosystem registration.
backend/src/api/public/v1/packages/blastRadius.ts Enables PyPI in API validation.
Suppressed comments (1)

services/apps/packages_worker/src/blast-radius/agent/pypiPrompts.ts:50

  • Remove this section-header comment; the project convention explicitly forbids section-header comments (CLAUDE.md:82).
// ---------- STAGE 3: REACHABILITY ----------

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +125
export async function findPackageIdByPurl(qx: QueryExecutor, purl: string): Promise<number | null> {
const row = await qx.selectOneOrNone(`SELECT id FROM packages WHERE purl = $(purl)`, { purl })
return (row?.id as number | undefined) ?? null
Comment on lines +84 to +87
if (clause.wildcard) {
const matches = normalizedForWildcard(version).startsWith(clause.version.toLowerCase())
return clause.op === '==' ? matches : !matches
}
Comment on lines +133 to +135
if (resolvedVersion) {
return vulnerableVersions.includes(resolvedVersion) ? 'matched' : 'excluded'
}
Comment on lines +203 to +206
if (dist.packagetype === 'sdist') {
await downloadSdist(dist.url, destDir, packageName, version)
} else {
await downloadWheel(dist.url, destDir, packageName, version)
'cargo',
'nuget',
'rubygems',
'pypi',
Comment on lines +121 to +125
export function toBarePypiName(input: string): string {
let name = input.trim()

name = stripQueryAndFragment(name)

} from './promptKit'
import { SymbolSpec } from './prompts'

// ---------- STAGE 1: INTEL ----------
Comment on lines +123 to +125
export async function findPackageIdByPurl(qx: QueryExecutor, purl: string): Promise<number | null> {
const row = await qx.selectOneOrNone(`SELECT id FROM packages WHERE purl = $(purl)`, { purl })
return (row?.id as number | undefined) ?? null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Created by Linear-GitHub Sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants