@@ -97,22 +97,72 @@ def get_data_from_manifests(project, package_registry, manifest_resources, model
9797 )
9898 return []
9999
100+ # Group manifest resources by package type for batch processing
101+ manifests_by_type = {}
100102 for resource in manifest_resources :
101- packages = resolve_manifest_resources (resource , package_registry )
102- if packages :
103- resolved_packages .extend (packages )
104- if headers := get_manifest_headers (resource ):
105- sboms_headers [resource .name ] = headers
106- else :
107- project .add_error (
108- description = "No packages could be resolved" ,
109- model = model ,
110- object_instance = resource ,
111- )
103+ package_type = get_default_package_type (resource .location )
104+ if package_type :
105+ if package_type not in manifests_by_type :
106+ manifests_by_type [package_type ] = []
107+ manifests_by_type [package_type ].append (resource )
108+
109+ # Process PyPI manifests together in a single batch
110+ if "pypi" in manifests_by_type :
111+ pypi_resources = manifests_by_type ["pypi" ]
112+ pypi_locations = [resource .location for resource in pypi_resources ]
113+
114+ resolver = package_registry .get ("pypi" )
115+ if resolver :
116+ try :
117+ packages = resolver (input_locations = pypi_locations )
118+ if packages :
119+ # Associate packages with their source resources
120+ # Since we're processing multiple files together, we need to
121+ # associate each package with all the manifest resources
122+ for package_data in packages :
123+ package_data ["codebase_resources" ] = pypi_resources
124+ resolved_packages .extend (packages )
125+
126+ # Collect headers for each manifest
127+ for resource in pypi_resources :
128+ if headers := get_manifest_headers (resource ):
129+ sboms_headers [resource .name ] = headers
130+ else :
131+ for resource in pypi_resources :
132+ project .add_error (
133+ description = "No packages could be resolved" ,
134+ model = model ,
135+ object_instance = resource ,
136+ )
137+ except Exception as e :
138+ for resource in pypi_resources :
139+ project .add_error (
140+ description = f"Error resolving packages: { e } " ,
141+ model = model ,
142+ object_instance = resource ,
143+ )
144+
145+ # Remove pypi from the dict so we don't process it again below
146+ del manifests_by_type ["pypi" ]
147+
148+ # Process other manifest types individually (SPDX, CycloneDX, About files)
149+ for package_type , resources in manifests_by_type .items ():
150+ for resource in resources :
151+ packages = resolve_manifest_resources (resource , package_registry )
152+ if packages :
153+ resolved_packages .extend (packages )
154+ if headers := get_manifest_headers (resource ):
155+ sboms_headers [resource .name ] = headers
156+ else :
157+ project .add_error (
158+ description = "No packages could be resolved" ,
159+ model = model ,
160+ object_instance = resource ,
161+ )
112162
113- dependencies = get_dependencies_from_manifest (resource )
114- if dependencies :
115- resolved_dependencies .extend (dependencies )
163+ dependencies = get_dependencies_from_manifest (resource )
164+ if dependencies :
165+ resolved_dependencies .extend (dependencies )
116166
117167 if sboms_headers :
118168 project .update_extra_data ({"sboms_headers" : sboms_headers })
@@ -222,13 +272,30 @@ def get_manifest_resources(project):
222272 return project .codebaseresources .filter (status = flag .APPLICATION_PACKAGE )
223273
224274
225- def resolve_pypi_packages (input_location ):
226- """Resolve the PyPI packages from the ``input_location`` requirements file."""
275+ def resolve_pypi_packages (input_location = None , input_locations = None ):
276+ """
277+ Resolve the PyPI packages from requirement file(s).
278+
279+ Args:
280+ input_location: Single requirement file path (for backward compatibility)
281+ input_locations: List of requirement file paths (for batch processing)
282+
283+ Returns:
284+ List of resolved package data dictionaries
285+ """
286+ # Handle both single file and multiple files
287+ if input_locations :
288+ requirement_files = input_locations
289+ elif input_location :
290+ requirement_files = [input_location ]
291+ else :
292+ raise ValueError ("Either input_location or input_locations must be provided" )
293+
227294 python_version = f"{ sys .version_info .major } { sys .version_info .minor } "
228295 operating_system = "linux"
229296
230297 resolution_output = python_inspector .resolve_dependencies (
231- requirement_files = [ input_location ] ,
298+ requirement_files = requirement_files ,
232299 python_version = python_version ,
233300 operating_system = operating_system ,
234301 # Prefer source distributions over binary distributions,
0 commit comments