66
77import datetime
88import logging
9+ import time
910from typing import List , Dict
1011
1112from pydantic import BaseModel
1819from src .v2 .models import (
1920 RecommenderRequest ,
2021 StackRecommendationResult ,
21- PackageDetails ,
2222 Ecosystem ,
2323 RecommendedPackageData ,
24- Package ,
2524)
2625
2726logger = logging .getLogger (__name__ )
@@ -52,7 +51,7 @@ def _prepare_insights_url(host: str) -> str:
5251 return endpoint
5352
5453
55- ECOSYSTEM_TO_SERVICE_HOST = {
54+ ECOSYSTEM_TO_INSIGHTS_URL = {
5655 "pypi" : _prepare_insights_url (Settings ().pypi_service_host ),
5756 "npm" : _prepare_insights_url (Settings ().chester_service_host ),
5857 "maven" : _prepare_insights_url (Settings ().maven_service_host ),
@@ -69,15 +68,22 @@ def _call(payload: InsightsRequest):
6968 Calls the PGM model with the normalized manifest information to get
7069 the relevant packages.
7170 """
72- insights_url = ECOSYSTEM_TO_SERVICE_HOST .get (payload .ecosystem , None )
71+ insights_url = ECOSYSTEM_TO_INSIGHTS_URL .get (payload .ecosystem , None )
7372 assert insights_url
7473 try :
74+ started_at = time .time ()
7575 response = get_session_retry ().post (insights_url , json = [payload .dict ()])
7676 response .raise_for_status ()
7777 except Exception as e :
7878 raise InsightsCallException () from e
7979 else :
8080 json_response = response .json ()
81+ logger .info (
82+ "Recommendation [%s] req.pkgs [%d] elapsed time [%0.2f] sec" ,
83+ payload .ecosystem .value ,
84+ len (payload .package_list ),
85+ time .time () - started_at ,
86+ )
8187 return json_response
8288
8389 def _get_insights_response (self , normalized_packages : NormalizedPackages ):
@@ -97,67 +103,64 @@ def _get_insights_response(self, normalized_packages: NormalizedPackages):
97103 # Call PGM and get the response
98104 return self ._call (insights_payload )
99105
100- def _get_package_details (self , insights_response ) -> Dict [str , PackageDetails ]:
101- packages = list (
106+ def _get_recommended_package_details (
107+ self , insights_response
108+ ) -> List [RecommendedPackageData ]:
109+ companion_packages = insights_response .get ("companion_packages" , [])
110+ package_to_stats_map = dict (
102111 map (
103- lambda x : x .get ("package_name" ),
104- insights_response . get ( " companion_packages" , []) ,
112+ lambda x : ( x .get ("package_name" ), x ),
113+ companion_packages ,
105114 )
106115 )
116+ packages = list (package_to_stats_map .keys ())
107117 ecosystem = insights_response ["ecosystem" ]
108118 query = (
109119 """g.V().has('ecosystem', ecosystem).has('name', within(name)).valueMap()"""
110120 )
121+ started_at = time .time ()
111122 result = post_gremlin (
112123 query = query , bindings = {"ecosystem" : ecosystem , "name" : packages }
113124 )
125+ logger .info (
126+ "graph req.pkgs [%d] elapsed time [%0.2f] sec" ,
127+ len (packages ),
128+ time .time () - started_at ,
129+ )
114130
115- def map_package (data ):
116- name = data .get ("name" , ["" ])[0 ]
131+ def extract_version (data ):
117132 # all versions are not vulnerable if latest_non_cve_version doesn't exist.
118133 # all versions are vulnerable if latest_non_cve_version is empty.
119134 recommended_version = data .get (
120135 "latest_non_cve_version" , data .get ("latest_version" , ["" ])
121136 )
122137 version = recommended_version [0 ] if len (recommended_version ) else ""
123- pkg = Package (name = name , version = version )
124- return name , PackageDetails (
125- ** pkg .dict (),
126- github = get_github_details (data ),
127- licenses = data .get ("declared_licenses" , []),
128- ecosystem = ecosystem ,
129- url = get_snyk_package_link (ecosystem , name ),
130- latest_version = data .get ("latest_version" , ["" ])[0 ]
131- )
138+ return version
132139
133- return dict (map (map_package , result ["result" ]["data" ]))
140+ def has_valid_version (data ):
141+ return extract_version (data ) != ""
134142
135- def _get_recommended_package_details (
136- self , insights_response
137- ) -> List [RecommendedPackageData ]:
138- package_details = self ._get_package_details (insights_response )
143+ def get_recommendation_statistics (package_name : str ) -> Dict [str , str ]:
144+ # below dict has cooccurrence_probability, cooccurrence_count, topic_list
145+ return package_to_stats_map [package_name ]
139146
140- def map_response (r ):
141- package_detail = package_details [r ["package_name" ]]
147+ def map_to_recommendation_package_data (data ):
148+ name = data .get ("name" , ["" ])[0 ]
149+ version = extract_version (data )
142150 return RecommendedPackageData (
143- ** package_detail .dict (),
144- cooccurrence_probability = r .get ("cooccurrence_probability" , 0 ),
145- cooccurrence_count = r .get ("cooccurrence_count" , 0 ),
146- topic_list = r .get ("topic_list" , [])
151+ name = name ,
152+ version = version ,
153+ github = get_github_details (data ),
154+ licenses = data .get ("declared_licenses" , []),
155+ ecosystem = ecosystem ,
156+ url = get_snyk_package_link (ecosystem , name ),
157+ latest_version = data .get ("latest_version" , ["" ])[0 ],
158+ # join stats from insight
159+ ** get_recommendation_statistics (name ),
147160 )
148161
149- # remove recommended packages which are not in database.
150- recommended_packages = filter (
151- lambda r : package_details .get (r ["package_name" ]),
152- insights_response ["companion_packages" ],
153- )
154- # map to RecommendedPackageData
155- recommended_packages = map (map_response , recommended_packages )
156- # remove packages which has empty version string.
157- recommended_packages = filter (
158- lambda pkg : pkg .version != "" , recommended_packages
159- )
160- return list (recommended_packages )
162+ valid_packages = filter (has_valid_version , result ["result" ]["data" ])
163+ return list (map (map_to_recommendation_package_data , valid_packages ))
161164
162165 def execute (self , arguments = None , persist = True , check_license = False ): # noqa: F841
163166 """Execute task."""
@@ -167,15 +170,12 @@ def execute(self, arguments=None, persist=True, check_license=False): # noqa: F
167170 logging .warning ("Recommendation is yet to be implemented for golang" )
168171 return {}
169172
170- external_request_id = request .external_request_id
171-
172173 normalized_packages = NormalizedPackages (request .packages , request .ecosystem )
173174 insights_response = self ._get_insights_response (normalized_packages )
174175
175176 result = StackRecommendationResult (
176177 ** arguments ,
177178 companion = self ._get_recommended_package_details (insights_response [0 ]),
178- usage_outliers = []
179179 )
180180
181181 recommendation = result .dict ()
@@ -186,9 +186,10 @@ def execute(self, arguments=None, persist=True, check_license=False): # noqa: F
186186 "version" : "v2" ,
187187 }
188188
189+ external_request_id = request .external_request_id
189190 if persist :
190191 persist_data_in_db (
191- external_request_id = request . external_request_id ,
192+ external_request_id = external_request_id ,
192193 task_result = recommendation ,
193194 worker = "recommendation_v2" ,
194195 started_at = started_at ,
0 commit comments