77#
88
99import json
10+ import logging
11+ import time
1012from collections import defaultdict
13+ from contextlib import contextmanager
1114from contextlib import suppress
1215
1316from django import forms
4851from product_portfolio .models import ProductRelationStatus
4952from product_portfolio .models import ScanCodeProject
5053
54+ logger = logging .getLogger (__name__ )
55+
56+
57+ @contextmanager
58+ def log_elapsed (label ):
59+ """Context manager that logs the elapsed time of the wrapped block."""
60+ start = time .perf_counter ()
61+ yield
62+ logger .info (f"{ label } : { time .perf_counter () - start :.1f} s" )
63+
5164
5265class CleanProductMixin (ComponentRelatedFieldImportMixin ):
5366 def clean_product (self ):
@@ -683,33 +696,57 @@ def __init__(
683696 self .package_uid_mapping = {}
684697
685698 self .user = user
699+ self .dataspace = product .dataspace
686700 self .project_uuid = project_uuid
687701 self .product = product
688702 self .update_existing = update_existing
689703 self .scan_all_packages = scan_all_packages
690704 self .infer_download_urls = infer_download_urls
691705 self .create_dependencies = create_dependencies
692706
693- scancodeio = ScanCodeIO (user .dataspace )
694- self .packages = scancodeio .fetch_project_packages (self .project_uuid )
695- self .dependencies = scancodeio .fetch_project_dependencies (self .project_uuid )
696- if not self .packages and not self .dependencies :
707+ def save (self ):
708+ save_start = time .perf_counter ()
709+
710+ scancodeio = ScanCodeIO (self .dataspace )
711+
712+ with log_elapsed ("fetch_project_packages" ):
713+ self .packages = scancodeio .fetch_project_packages (self .project_uuid )
714+
715+ if not self .packages :
697716 raise Exception ("Packages could not be fetched from ScanCode.io" )
698717
699- def save (self ):
700- self .import_packages ()
701718 if self .create_dependencies :
702- self .import_dependencies ()
719+ with log_elapsed ("fetch_project_dependencies" ):
720+ self .dependencies = scancodeio .fetch_project_dependencies (self .project_uuid )
721+
722+ with log_elapsed ("import_packages" ):
723+ self .import_packages ()
724+
725+ if self .create_dependencies :
726+ with log_elapsed ("import_dependencies" ):
727+ self .import_dependencies ()
703728
704729 if self .scan_all_packages :
705730 transaction .on_commit (lambda : self .product .scan_all_packages_task (self .user ))
731+ logger .info ("scan_all_packages: scheduled" )
706732
707733 if self .user .dataspace .enable_vulnerablecodedb_access :
708- self .product .fetch_vulnerabilities ()
734+ with log_elapsed ("fetch_vulnerabilities" ):
735+ self .product .fetch_vulnerabilities ()
736+
737+ logger .info (f"save total: { time .perf_counter () - save_start :.1f} s" )
709738
710739 return dict (self .created ), dict (self .existing ), dict (self .errors )
711740
712741 def import_packages (self ):
742+ # Pre-fetch once to avoid repeated DB lookups in DefaultOnAdditionMixin.save().
743+ self .default_review_status = ProductRelationStatus .objects .get_default_on_addition_qs (
744+ self .dataspace
745+ ).first ()
746+ self .default_purpose = ProductItemPurpose .objects .get_default_on_addition_qs (
747+ self .dataspace
748+ ).first ()
749+
713750 for package_data in self .packages :
714751 self .import_package (package_data )
715752
@@ -767,8 +804,9 @@ def import_package(self, package_data):
767804 if not package :
768805 try :
769806 package = Package .create_from_data (self .user , package_data , validate = True )
770- except ValidationError as errors :
771- self .errors ["package" ].append (str (errors ))
807+ except ValidationError as error :
808+ logger .error (f"Failed to create package: { error } \n { package_data } " )
809+ self .errors ["package" ].append (str (error ))
772810 return
773811 self .created ["package" ].append (str (package ))
774812
@@ -780,8 +818,11 @@ def import_package(self, package_data):
780818 "license_expression" : package .license_expression ,
781819 "notes" : "Imported from ScanCode.io" ,
782820 "created_by" : self .user ,
821+ "review_status" : self .default_review_status ,
822+ "purpose" : self .default_purpose ,
783823 },
784824 )
825+
785826 package_uid = package_data .get ("package_uid" ) or package .uuid
786827 self .package_uid_mapping [package_uid ] = package
787828
@@ -846,7 +887,7 @@ def look_for_existing_package(self, package_data):
846887 return package
847888
848889 # 2. If the package data does not include a download_url value:
849- # Attemp to find an existing package using purl-only match.
890+ # Attempt to find an existing package using purl-only match.
850891 if not package_data .get ("download_url" ):
851892 purl_lookups = {field : package_data .get (field , "" ) for field in PACKAGE_URL_FIELDS }
852893 same_purl_packages = package_qs .filter (** purl_lookups )
0 commit comments