13 from typing
import Union, Optional
21 logger = logging.getLogger(
"osadl_convertor")
22 logger.setLevel(logging.INFO)
27 Class to hold information about a single rule.
28 :ivar __first_license: First license of the rule.
29 :ivar __second_license: Second license of the rule.
30 :ivar __first_type: First type of the license.
31 :ivar __second_type: Second type of the license.
32 :ivar __result: Compatibility result of the rule.
33 :ivar __comment: Comment on the rule.
40 self.
__result__result: Optional[Union[bool, osadl_matrix.OSADLCompatibility]] = \
41 osadl_matrix.OSADLCompatibility.NO
45 def first_license(self) -> Optional[str]:
47 Get name of the first license.
52 def first_license(self, first_license: str) ->
None:
54 Set name of the first license.
59 def second_license(self) -> Optional[str]:
61 Get name of the second license.
65 @second_license.setter
66 def second_license(self, second_license: str) ->
None:
68 Set name of the second license.
73 def first_type(self) -> Optional[str]:
75 Get type of the first license.
80 def first_type(self, first_type: Optional[str]) ->
None:
82 Set type of the first license.
87 def second_type(self) -> Optional[str]:
89 Get type of the second license.
94 def second_type(self, second_type: Optional[str]) ->
None:
96 Set type of the second license.
101 def result(self) -> bool:
103 Get result of the rule as boolean.
105 if isinstance(self.
__result__result, bool):
107 if self.
__result__result == osadl_matrix.OSADLCompatibility.YES \
108 or self.
__result__result == osadl_matrix.OSADLCompatibility.CHECKDEP:
114 result: Union[osadl_matrix.OSADLCompatibility, bool]) ->
None:
116 Set result of the rule. It can be boolean or an object of OSADLCompatibility
124 Get comment on the rule.
132 Set comment on the rule.
138 Two rules are equal if:
140 - They talk about the same licenses and have same result.
141 - They talk about the same license types and have same result.
147 other.first_license
is not None and
148 other.second_license
is not None
159 other.first_type
is not None and
160 other.second_type
is not None
172 return f
"{self.__class__.__name__}(firstname={self.first_license}, " \
173 f
"secondname={self.second_license}, firsttype={self.first_type}, " \
174 f
"secondtype={self.second_type}, compatibility={self.result}, " \
175 f
"comment={self.comment})"
178 def compliance_representer(dumper: yaml.Dumper, data: MatrixItem) -> yaml.Node:
180 Represent MatrixItem (rules) in format FOSSology understands as a YAML map.
183 (dumper.represent_data(
"firstname"),
184 dumper.represent_data(data.first_license)),
185 (dumper.represent_data(
"secondname"),
186 dumper.represent_data(data.second_license)),
187 (dumper.represent_data(
"firsttype"),
188 dumper.represent_data(data.first_type)),
189 (dumper.represent_data(
"secondtype"),
190 dumper.represent_data(data.second_type)),
191 (dumper.represent_data(
"compatibility"),
192 dumper.represent_data(data.result)),
193 (dumper.represent_data(
"comment"),
194 dumper.represent_data(data.comment))
197 return yaml.nodes.MappingNode(
u"tag:yaml.org,2002:map", value)
202 Handle license information from FOSSology.
204 def __init__(self, host: str, port: str, user: str, password: str,
207 Create connection to DB.
208 :param host: Host of the database.
209 :param port: Port of the database.
210 :param user: User of the database.
211 :param password: Password of the database.
212 :param database: Name of the database.
214 self.
__conn__conn = psycopg2.connect(dbname=database, user=user,
215 password=password, host=host, port=port)
219 Get the type of the license from DB.
220 :param license_name: Name of the license to get type for.
221 :return: Type of the license if found, None otherwise.
223 cur = self.
__conn__conn.cursor()
224 cur.execute(
"SELECT rf_licensetype FROM license_ref WHERE "
225 "lower(rf_shortname) = lower(%s);", (license_name,))
226 resp = cur.fetchone()
233 Check if different type of licenses exists in DB.
235 Check if threshold of "Permissive" or None license type (default type) is
236 bellow 80% of the total licenses in database.
237 :return: True if threshold is bellow 80%, False otherwise.
239 cur = self.
__conn__conn.cursor()
240 cur.execute(
"SELECT rf_licensetype, count(*) AS count "
241 "FROM license_ref GROUP BY rf_licensetype;")
242 resp: list[tuple[Optional[str], int]] = cur.fetchall()
248 type_name =
"None" if row[0]
is None else row[0]
250 total_count += type_count
251 if type_name ==
"Permissive" or type_name ==
"None":
252 permissive_count += type_count
253 return (permissive_count / total_count) < 0.8
257 Get list of different license types from database.
258 :return: List of license types in DB.
260 cur = self.
__conn__conn.cursor()
261 cur.execute(
"SELECT DISTINCT rf_licensetype FROM license_ref;")
262 resp: list[tuple[Optional[str]]] = cur.fetchall()
263 type_list: list[Optional[str]] = []
266 type_list.append(row[0])
271 Check if there is a license with the given name in database or not.
272 :param license_name: Name to check
273 :return: True if exists, False otherwise
275 cur = self.
__conn__conn.cursor()
276 cur.execute(
"SELECT 1 FROM license_ref WHERE "
277 "lower(rf_shortname) = lower(%s);", (license_name,))
278 resp = cur.fetchone()
284 def remove_items(compatibility_matrix: list[MatrixItem],
285 first_type: Optional[str], second_type: Optional[str],
286 result: bool) -> list[MatrixItem]:
288 Given the type of licenses and result of the rule, remove them from the list.
291 - They are based on only types (already filtered)
292 - Their license types and results do not match.
293 :param compatibility_matrix: List of rules to filter from.
294 :param first_type: First license type for removal.
295 :param second_type: Second license type for removal.
296 :param result: Result of rule for removal.
297 :return: Filtered list of rules.
299 return [item
for item
in compatibility_matrix
301 item.first_license
is None and item.second_license
is None
303 item.first_type != first_type
or item.second_type != second_type
305 item.first_type != second_type
or item.second_type != first_type
306 ))
or item.result != result)]
308 def remove_type_for_license(compatibility_matrix: list[MatrixItem]) \
311 Remove license type information from rules if they contain licenses. It
312 should be called after remove_items().
313 :param compatibility_matrix: List of rules.
314 :return: List of rules with license types removed.
317 for item
in [item
for item
in compatibility_matrix
318 if item.first_license
is not None]:
319 item.first_type =
None
320 item.second_type =
None
321 new_list.append(item)
322 new_list.extend([item
for item
in compatibility_matrix
323 if item.first_license
is None])
327 def reduce_matrix(license_handler: LicenseHandler,
328 compatibility_matrix: list[MatrixItem],
329 type_dict: dict[tuple[str, str, bool], int]):
331 Reduce the original list of rules by combining rules based on license types
332 and other criteria. The function also checks if the license type threshold
333 is passed to reduce the list based on types. If not, it simply removes the
334 license types from the rules.
336 Requires dictionary of license type and result in following format:
339 { ('license_type_1', 'license_type_2', result<bool>): count of occurrences }
341 :param license_handler: Object of LicenseHandler
342 :param compatibility_matrix: List of rules
343 :param type_dict: Dictionary storing information about license type and
345 :return: Reduced list of rules.
347 if not license_handler.different_type_exists():
348 for item
in compatibility_matrix:
349 item.first_type =
None
350 item.second_type =
None
351 return compatibility_matrix
353 license_types = license_handler.get_license_types()
354 reduced_list = compatibility_matrix
355 for license_type_first
in license_types:
356 for license_type_second
in license_types:
359 if (license_type_first, license_type_second,
True)
in type_dict:
360 true_count = type_dict[(license_type_first, license_type_second,
True)]
361 elif (license_type_second, license_type_first,
True)
in type_dict:
362 true_count = type_dict[(license_type_second, license_type_first,
True)]
363 if (license_type_first, license_type_second,
False)
in type_dict:
364 false_count = type_dict[(license_type_first, license_type_second,
366 elif (license_type_second, license_type_first,
False)
in type_dict:
367 false_count = type_dict[(license_type_second, license_type_first,
369 max_type = true_count > false_count
370 reduced_list = remove_items(reduced_list, license_type_first,
371 license_type_second, max_type)
373 type_only_item.first_type = license_type_first
374 type_only_item.second_type = license_type_second
375 type_only_item.result = max_type
376 type_only_item.comment = f
"{type_only_item.first_type} -> " \
377 f
"{type_only_item.second_type} -> " \
378 f
"{type_only_item.result}"
379 reduced_list.append(type_only_item)
380 return remove_type_for_license(reduced_list)
383 def save_yaml(location: str, compliance_matrix: list[MatrixItem]) ->
None:
384 with open(location,
"w")
as save_file:
385 yaml.add_representer(MatrixItem, compliance_representer)
388 "rules": compliance_matrix
390 logger.info(f
"Saved {len(compliance_matrix)} rules in {location}.")
393 def convert_json_to_matrix(license_handler: LicenseHandler, json_loc: str) \
394 -> tuple[list[MatrixItem], dict[tuple[str, str, bool], int]]:
396 Convert the OSADL matrix JSON from library into list of rules. The rules
397 are made sure to be not duplicated. The type of license is also added to
399 :param license_handler: LicenseHandler object
400 :param json_loc: Location of OSADL JSON
401 :return: List of rules and type dictionary for reduce_matrix()
403 matrix: Union[dict[str, dict[str, str]],
None] =
None
404 compatibility_matrix: list[MatrixItem] = []
405 type_dict: dict[tuple[str, str, bool], int] = dict()
406 with open(json_loc,
"r")
as jsoninput:
407 matrix = json.load(jsoninput)
410 for first_license, comp_list
in matrix.items():
411 if first_license
in [
"timestamp",
"timeformat"]
or not \
412 license_handler.license_exists(first_license):
414 for second_license, result
in comp_list.items():
415 if not license_handler.license_exists(second_license):
418 row.first_license = first_license
419 row.second_license = second_license
420 row.result = osadl_matrix.OSADLCompatibility.from_text(result)
421 row.comment = f
"{first_license} -> {second_license} -> {row.result}"
422 row.first_type = license_handler.get_license_type(row.first_license)
423 row.second_type = license_handler.get_license_type(row.second_license)
424 if row
not in compatibility_matrix:
425 logger.debug(row.comment)
426 compatibility_matrix.append(row)
428 if (row.first_type, row.second_type, row.result)
not in type_dict:
429 if (row.second_type, row.first_type, row.result)
in type_dict:
430 type_dict[(row.second_type, row.first_type, row.result)] += 1
433 type_dict[(row.first_type, row.second_type, row.result)] = \
434 type_dict.get((row.first_type, row.second_type, row.result), 0) + 1
435 return compatibility_matrix, type_dict
438 def main(parsed_args):
439 start_time = time.time()
440 license_handler =
LicenseHandler(parsed_args.host, parsed_args.port,
441 parsed_args.user, parsed_args.password,
442 parsed_args.database)
443 compatibility_matrix, type_dict = convert_json_to_matrix(
444 license_handler, osadl_matrix.OSADL_MATRIX_JSON)
445 reduce_start = int(round(time.time() * 1000))
446 reduced_list = reduce_matrix(license_handler, compatibility_matrix, type_dict)
447 reduce_end = int(round(time.time() * 1000))
448 save_yaml(parsed_args.yaml, reduced_list)
449 time_taken = time.time() - start_time
450 logger.info(f
"Took {(reduce_end - reduce_start):.2f} ms for reducing list.")
451 logger.info(f
"Took {time_taken:.2f} seconds for processing.")
454 if __name__ ==
"__main__":
455 parser = argparse.ArgumentParser(
456 description=textwrap.dedent(
"""
457 Convert OSADL matrix to FOSSology's compatibility YAML
461 "--user", type=str, help=
"Database username", default=
"fossy"
464 "--password", type=str, help=
"Database password", required=
True
467 "--database", type=str, help=
"Database name", default=
"fossology"
470 "--host", type=str, help=
"Database host", default=
"localhost"
473 "--port", type=str, help=
"Database port", default=
"5432"
476 "--yaml", type=str, help=
"Location to store result file", required=
True
479 "-d",
"--debug", action=
"store_true", help=
"Increase verbosity",
482 args = parser.parse_args()
484 logger.setLevel(logging.DEBUG)
bool license_exists(self, str license_name)
def __init__(self, str host, str port, str user, str password, str database)
bool different_type_exists(self)
list[Optional[str]] get_license_types(self)
Optional[str] get_license_type(self, str license_name)
Optional[str] first_license(self)
Optional[str] second_type(self)
None first_license(self, str first_license)
None result(self, Union[osadl_matrix.OSADLCompatibility, bool] result)
Optional[str] first_type(self)
Optional[str] second_license(self)
None second_license(self, str second_license)
None second_type(self, Optional[str] second_type)
None first_type(self, Optional[str] first_type)