licomp-toolkit 0.6.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ # SPDX-FileCopyrightText: 2025 Henrik Sandklef
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import logging
6
+ import re
7
+ import traceback
8
+
9
+ from licomp_toolkit.toolkit import LicompToolkit
10
+ from licomp_toolkit.toolkit import ExpressionExpressionChecker
11
+ from licomp.interface import Provisioning
12
+ from licomp.interface import UseCase
13
+
14
+ from flame.license_db import FossLicenses
15
+
16
+ class OutboundSuggester:
17
+ """
18
+ OutboundSuggester suggests outbound candidate licenses from a
19
+ license expression
20
+
21
+ If you have a license expression like this "BSD-3-Clause AND MIT
22
+ AND GPL-2.0-only", typically the result of your dependencies'
23
+ licenses, and want to know which outbound license you can chose.
24
+
25
+ """
26
+ def __init__(self):
27
+ self._compatibility_rankings = {}
28
+ self.lt = LicompToolkit()
29
+ self.le = ExpressionExpressionChecker()
30
+ self.flame = FossLicenses()
31
+
32
+ def compatibility_rankings(self, usecase, provisioning):
33
+ """ Returns a dict with ranked licenses based on your usecase/provisioning, e.g.
34
+
35
+ {
36
+ 'GPL3': {'count': 2, 'valids': '1', 'index': 69},
37
+ 'MIT': {'count': 108, 'valids': '1', 'index': 169}
38
+ }
39
+
40
+ count - how many other licenses the license is compatible with (primary sort)
41
+ valids - how many valid resources provided a 'yes' answer (secondary sort)
42
+ index - index for the licenses, the higher index the more compatible (with other licenses)
43
+ """
44
+ if usecase in self._compatibility_rankings:
45
+ if provisioning in self._compatibility_rankings[usecase]:
46
+ return self._compatibility_rankings[usecase][provisioning]
47
+ else:
48
+ self._compatibility_rankings[usecase] = {}
49
+
50
+ #
51
+ # identify compatiblities
52
+ lic_map = {}
53
+ # loop through every license (against themselves)
54
+ for in_lic in self.lt.supported_licenses():
55
+ for out_lic in self.lt.supported_licenses():
56
+ # get compat
57
+ ret = self.lt.outbound_inbound_compatibility(out_lic,
58
+ in_lic,
59
+ UseCase.string_to_usecase(usecase),
60
+ Provisioning.string_to_provisioning(provisioning))
61
+ results = ret['summary']['results']
62
+ valid_results = results['nr_valid']
63
+ # only increase the 'count' if the answers from the resources are only 'yes'
64
+ yes = results.get('yes', {'count': 0, 'percent': 0.0})
65
+ inc = 0
66
+ if yes['percent'] == 100:
67
+ inc = 1
68
+
69
+ lic_map[in_lic] = {
70
+ 'count': lic_map.get(in_lic, {'count': 0})['count'] + inc,
71
+ 'valids': valid_results,
72
+ }
73
+
74
+ # decorate (to sort list)
75
+ decorated = [(lic_map[lic]['count'], lic_map[lic]['valids'], i, lic) for i, lic in enumerate(lic_map)]
76
+ # sort after count, valids (in tuple)
77
+ decorated.sort()
78
+ # undecorate
79
+ ordered_lic = [lic for count, valids, i, lic in decorated]
80
+
81
+ # create new dict - use license name as key
82
+ ordered_lic_map = {}
83
+ for i, lic in enumerate(ordered_lic):
84
+ ordered_lic_map[lic] = lic_map[lic]
85
+ ordered_lic_map[lic]['index'] = i
86
+
87
+ # store foar later (re)use
88
+ self._compatibility_rankings[usecase][provisioning] = ordered_lic_map
89
+
90
+ return self._compatibility_rankings[usecase][provisioning]
91
+
92
+ def licenses(self, license_expr):
93
+ """Returns a list of licenses in a license expression.
94
+
95
+ If you have a license expression like this:
96
+ "(MIT OR Apache-2.0) AND MIT AND GPL-2.0-only WITH Classpath-exception-2.0"
97
+
98
+ you get a list like this:
99
+ ["MIT", "Apache-2.0", "GPL-2.0-only WITH Classpath-exception-2.0"]
100
+
101
+ """
102
+ normalized = self.flame.expression_license(license_expr, update_dual=False)['identified_license']
103
+
104
+ with_fixed = normalized.replace(' WITH ', '-WITH-')
105
+
106
+ splits = [x.replace('-WITH-', ' WITH ') for x in re.split(r' AND | OR |\(|\)', with_fixed) if len(x) > 1]
107
+
108
+ return splits
109
+
110
+ def compat_licenses(self, license_expr, usecase, provisioning, licenses_to_check=None, resources=None):
111
+ """Returns a list of licenses that are compatible with a license expression
112
+
113
+ If you have a license expression like this:
114
+ "(MIT OR Apache-2.0) AND MIT AND GPL-2.0-only"
115
+
116
+ you get a list like this of licenses that are compatible with
117
+ the expression listed in order of ranked compatibility. If you
118
+ don't provide any licenses to check the functions defaults to
119
+ looking at the licenses in the expressions itself. This you will get a list like this:
120
+
121
+ ["GPL-2.0-only"]
122
+
123
+ """
124
+ compats = []
125
+ if not licenses_to_check:
126
+ licenses_to_check = self.licenses(license_expr)
127
+
128
+ license_expression_parsed = self.flame.expression_license(license_expr, update_dual=False)['identified_license']
129
+ for lic in licenses_to_check:
130
+ logging.debug(f' check licenses: {lic}')
131
+ try:
132
+ ret = self.le.check_compatibility(lic,
133
+ license_expression_parsed,
134
+ usecase,
135
+ provisioning,
136
+ resources)
137
+ if ret['compatibility'] == 'yes':
138
+ compats.append(lic)
139
+ logging.debug(f' appending: {lic}')
140
+ except Exception as e:
141
+ logging.debug(f'Exception caught: {e}')
142
+ logging.debug(traceback.format_exc())
143
+
144
+ # decorate compat list with indices (from rankings)
145
+ decorated = [(self.__compat_index(lic, usecase, provisioning), lic) for lic in compats]
146
+
147
+ # sort and reverse (to get the most compatible first)
148
+ decorated.sort()
149
+ decorated.reverse()
150
+
151
+ # undecorate
152
+ sorted_compats = [lic for index, lic in decorated]
153
+
154
+ return sorted_compats
155
+
156
+ def __compat_index(self, lic, usecase, provisioning):
157
+ # returns the ranking index for a license,
158
+ # given usecase and provisioning
159
+
160
+ # initialize the ranking
161
+ self.compatibility_rankings(usecase, provisioning)
162
+ return self._compatibility_rankings[usecase][provisioning][lic]['index']
@@ -0,0 +1,504 @@
1
+ # SPDX-FileCopyrightText: 2024 Henrik Sandklef
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import logging
6
+ from license_expression import Licensing
7
+
8
+ from licomp.interface import Licomp
9
+ from licomp.interface import UseCase
10
+ from licomp.interface import Provisioning
11
+ from licomp.interface import LicompException
12
+ from licomp.return_codes import ReturnCodes
13
+ from licomp.interface import CompatibilityStatus
14
+
15
+ from licomp_osadl.osadl import LicompOsadl
16
+ from licomp_reclicense.reclicense import LicompReclicense
17
+ from licomp_proprietary.proprietary import LicompProprietary
18
+ from licomp_dwheeler.dwheeler import LicompDw
19
+ from licomp_hermione.hermione import LicompHermione
20
+ from licomp_gnuguide.gnuguide import GnuQuickGuideLicense
21
+ from licomp_oslc_handbook.oslc_handbook import LicompOslcHandbook
22
+ from licomp_doubleopen.doubleopen import LicompDoubleOpen
23
+
24
+ from licomp_toolkit.config import disclaimer
25
+ from licomp_toolkit.config import licomp_toolkit_version
26
+ from licomp_toolkit.config import licomp_toolkit_file_version
27
+ from licomp_toolkit.config import cli_name
28
+ from licomp_toolkit.config import module_name
29
+
30
+ from licomp_toolkit.expr_parser import LicenseExpressionParser
31
+ from licomp_toolkit.expr_parser import COMPATIBILITY_TYPE
32
+ from licomp_toolkit.expr_parser import AND
33
+ from licomp_toolkit.expr_parser import OR
34
+
35
+ from licomp_toolkit.config import my_supported_api_version
36
+
37
+ class LicompToolkit(Licomp):
38
+ """A class implementing Licomp, but for a misc Licomp resources
39
+ and packaging the responses into a new reply
40
+ (licomp_toolkit/reply_schema.json).
41
+
42
+ LicompToolkit can check a single license agaisnt another for
43
+ compatibility, but not license expressions.
44
+ """
45
+
46
+ def __init__(self):
47
+ Licomp.__init__(self)
48
+ self._licomp_resources = {}
49
+ for licomp in [LicompReclicense, LicompOsadl]:
50
+ licomp_instance = licomp()
51
+ self._licomp_resources[licomp_instance.name()] = licomp_instance
52
+
53
+ self._licomp_resources_optional = {}
54
+ for licomp in [LicompHermione, LicompProprietary, LicompDw, GnuQuickGuideLicense, LicompOslcHandbook, LicompDoubleOpen]:
55
+ licomp_instance = licomp()
56
+ self._licomp_resources_optional[licomp_instance.name()] = licomp_instance
57
+
58
+ def supported_api_version(self):
59
+ return my_supported_api_version
60
+
61
+ def __add_to_list(self, store, data, name):
62
+ if not data:
63
+ return
64
+ if data not in store:
65
+ store[data] = []
66
+ store[data].append(name)
67
+
68
+ def __add_meta(self, compatibilities):
69
+ compatibilities["meta"] = {}
70
+ compatibilities["meta"]['disclaimer'] = disclaimer
71
+
72
+ def licomp_resources(self):
73
+ return self._licomp_resources | self._licomp_resources_optional
74
+
75
+ def licomp_standard_resources(self):
76
+ return self._licomp_resources
77
+
78
+ def licomp_optional_resources(self):
79
+ return self._licomp_resources_optional
80
+
81
+ def licomp_resource_long(self, resource):
82
+ return {
83
+ 'name': resource.name(),
84
+ 'version': resource.version(),
85
+ 'usecases': [UseCase.usecase_to_string(x) for x in resource.supported_usecases()],
86
+ 'provisionings': [Provisioning.provisioning_to_string(x) for x in resource.supported_provisionings()],
87
+ 'licenses': resource.supported_licenses(),
88
+ 'type': self._resource_type(resource),
89
+ }
90
+
91
+ def licomp_resources_long(self):
92
+ _resources = []
93
+ for resource in self.licomp_resources().values():
94
+ _resources.append(self.licomp_resource_long(resource))
95
+ return _resources
96
+
97
+ def licomp_resources_short(self):
98
+ _resources = []
99
+ for resource in self.licomp_resources().values():
100
+ _resources.append(f'{resource.name()}:{resource.version()}')
101
+ return _resources
102
+
103
+ def _resource_type(self, resource):
104
+ if self._resource_is_standard(resource):
105
+ return 'standard'
106
+ return 'optional'
107
+
108
+ def _resource_is_optional(self, resource):
109
+ return resource.name() in self._licomp_resources_optional
110
+
111
+ def _resource_is_standard(self, resource):
112
+ return not self._resource_is_optional(resource)
113
+
114
+ def __summarize_compatibility(self, compatibilities, outbound, inbound, usecase, provisioning, resources):
115
+ compatibilities["summary"] = {}
116
+ statuses = {}
117
+ compats = {}
118
+ compatibilities['nr_licomp'] = len(resources)
119
+ # for resource_name in self._licomp_resources():
120
+ for compat in compatibilities["compatibilities"]:
121
+ logging.debug(f': {compat}')
122
+ logging.debug(f': {compat["resource_name"]}')
123
+ self.__add_to_list(statuses, compat['status'], compat)
124
+ self.__add_to_list(compats, compat['compatibility_status'], compat)
125
+ compatibilities["summary"]["resources"] = self.licomp_resources_short()
126
+ compatibilities["summary"]["outbound"] = outbound
127
+ compatibilities["summary"]["inbound"] = inbound
128
+ compatibilities["summary"]["usecase"] = UseCase.usecase_to_string(usecase)
129
+ compatibilities["summary"]["provisioning"] = Provisioning.provisioning_to_string(provisioning)
130
+ compatibilities["summary"]["statuses"] = statuses
131
+ compatibilities["summary"]["compatibility_statuses"] = compats
132
+
133
+ compat_number = len(compatibilities["summary"]["statuses"].get("success", []))
134
+ logging.debug(f': {compatibilities["summary"]["statuses"]}')
135
+ results = {}
136
+ results['nr_valid'] = f'{compat_number}'
137
+ for key, value in compatibilities["summary"]["compatibility_statuses"].items():
138
+ logging.debug(f': {len(value)}/{compat_number}')
139
+ if compat_number == 0:
140
+ continue
141
+ else:
142
+ if key == 'unsupported':
143
+ continue
144
+ else:
145
+ count = len(value)
146
+ perc = len(value) / compat_number * 100
147
+ results[key] = {
148
+ 'count': count,
149
+ 'percent': perc,
150
+ }
151
+ compatibilities['summary']['results'] = results
152
+
153
+ # override top class
154
+ def outbound_inbound_compatibility(self, outbound, inbound, usecase, provisioning, resources=None):
155
+ logging.debug(f'{inbound} {outbound} ')
156
+
157
+ compatibilities = {}
158
+ compatibilities['compatibilities'] = []
159
+
160
+ if not resources:
161
+ resources = self.licomp_resources().keys()
162
+
163
+ for resource_name in resources:
164
+ resource = self.licomp_resources()[resource_name]
165
+ logging.debug(f'-- resource: {resource.name()}')
166
+
167
+ compat = resource.outbound_inbound_compatibility(outbound, inbound, usecase, provisioning=provisioning)
168
+ compatibilities['compatibilities'].append(compat)
169
+
170
+ self.__summarize_compatibility(compatibilities, outbound, inbound, usecase, provisioning, resources)
171
+ self.__add_meta(compatibilities)
172
+
173
+ return compatibilities
174
+
175
+ def simplify(self, lic):
176
+ return str(Licensing([]).parse(lic).simplify())
177
+
178
+ def supported_licenses(self):
179
+ licenses = set()
180
+ for resource in self.licomp_resources().values():
181
+ licenses.update(set(resource.supported_licenses()))
182
+ licenses = list(licenses)
183
+ licenses.sort()
184
+ return licenses
185
+
186
+ def supported_provisionings(self):
187
+ provisionings = set()
188
+ for resource in self.licomp_resources().values():
189
+ provisionings.update(set(resource.supported_provisionings()))
190
+ return list(provisionings)
191
+
192
+ def supported_usecases(self):
193
+ usecases = set()
194
+ for resource in self.licomp_resources().values():
195
+ usecases.update(set(resource.supported_usecases()))
196
+ return list(usecases)
197
+
198
+ def disclaimer(self):
199
+ return disclaimer
200
+
201
+ def version(self, verbose=False):
202
+ return licomp_toolkit_version
203
+
204
+ def versions(self, verbose=False):
205
+ resources = {}
206
+ for resource in self.licomp_resources().values():
207
+ resources[resource.name()] = resource.version()
208
+ return {
209
+ self.name(): self.version(),
210
+ 'licomp-resources': resources,
211
+ }
212
+
213
+ def name(self):
214
+ return cli_name
215
+
216
+ class LicenseExpressionChecker():
217
+ """This class can check compatibility between a single outbound
218
+ license (e.g GPL-2.0-only) against an inbound license expression
219
+ (e.g. MIT OR X11)
220
+ """
221
+
222
+ def __init__(self):
223
+ self.le_parser = LicenseExpressionParser()
224
+ self.licomp_toolkit = LicompToolkit()
225
+
226
+ def __compatibility_status(self, compatibility):
227
+ status = compatibility['summary']['results']
228
+ rets = []
229
+ for ret in status:
230
+ if ret == 'nr_valid':
231
+ continue
232
+ elif not ret:
233
+ pass
234
+ elif ret == CompatibilityStatus.compat_status_to_string(CompatibilityStatus.UNSUPPORTED):
235
+ pass
236
+ else:
237
+ rets.append(ret)
238
+
239
+ if len(rets) == 0:
240
+ return CompatibilityStatus.compat_status_to_string(CompatibilityStatus.UNSUPPORTED)
241
+
242
+ if len(rets) == 1:
243
+ return rets[0]
244
+
245
+ return CompatibilityStatus.compat_status_to_string(CompatibilityStatus.MIXED)
246
+
247
+ def check_compatibility(self,
248
+ outbound,
249
+ parsed_expression,
250
+ usecase,
251
+ provisioning,
252
+ resources,
253
+ detailed_report=True):
254
+
255
+ compat_object = {
256
+ 'compatibility_check': f'outbound-{self.le_parser.parse_license_expression(outbound)["compatibility_type"]} -> inbound-{parsed_expression["compatibility_type"]}',
257
+ 'check_class': __class__.__name__,
258
+ }
259
+
260
+ if parsed_expression[COMPATIBILITY_TYPE] == 'license':
261
+ compat_object['compatibility_check'] = 'outbound-license -> inbound-license'
262
+ lic = parsed_expression['license']
263
+ compat = self.licomp_toolkit.outbound_inbound_compatibility(outbound,
264
+ lic,
265
+ usecase,
266
+ provisioning,
267
+ resources)
268
+ compat_object['compatibility'] = self.__compatibility_status(compat)
269
+ if detailed_report:
270
+ compat_object['compatibility_details'] = compat
271
+ else:
272
+ compat_object['compatibility_details'] = None
273
+ compat_object['inbound_license'] = lic
274
+ compat_object['outbound_license'] = outbound
275
+ compat_object['compatibility_object'] = {}
276
+
277
+ else:
278
+ operator = parsed_expression['operator']
279
+ operands = parsed_expression['operands']
280
+ compat_object['operator'] = operator
281
+
282
+ compat_object['inbound_license'] = self.le_parser.to_string(parsed_expression)
283
+ compat_object['outbound_license'] = outbound
284
+ compat_object['compatibility_details'] = None
285
+ operands_object = []
286
+ for operand in operands:
287
+ operand_compat = self.check_compatibility(outbound, operand, usecase, provisioning, resources, detailed_report=detailed_report)
288
+ operand_object = {
289
+ 'compatibility_object': operand_compat,
290
+ 'compatibility': operand_compat['compatibility'],
291
+ }
292
+ operands_object.append(operand_object)
293
+
294
+ compat_object['compatibility'] = self.summarise_compatibilities(operator, operands_object)
295
+ compat_object['operands'] = operands_object
296
+
297
+ return compat_object
298
+
299
+ def __init_summary(self, operands):
300
+ summary = {
301
+ "yes": 0,
302
+ "no": 0,
303
+ "depends": 0,
304
+ "unknown": 0,
305
+ "unsupported": 0,
306
+ "mixed": 0,
307
+ }
308
+ for operand in operands:
309
+ compat = operand['compatibility']
310
+ summary[compat] = summary[compat] + 1
311
+ return summary
312
+
313
+ def __summarise_compatibilities_and(self, operands):
314
+ nr_operands = len(operands)
315
+ summary = self.__init_summary(operands)
316
+
317
+ if summary['no'] != 0:
318
+ return 'no'
319
+
320
+ if summary['yes'] == nr_operands:
321
+ return "yes"
322
+
323
+ return "no"
324
+
325
+ def __summarise_compatibilities_or(self, operands):
326
+ summary = self.__init_summary(operands)
327
+
328
+ if summary['yes'] != 0:
329
+ return 'yes'
330
+
331
+ return "no"
332
+
333
+ def summarise_compatibilities(self, operator, operands):
334
+ return {
335
+ AND: self.__summarise_compatibilities_and,
336
+ OR: self.__summarise_compatibilities_or,
337
+ }[operator](operands)
338
+
339
+
340
+ class ExpressionExpressionChecker():
341
+ """
342
+ This class can check, for compatibility;
343
+ * inbound license expression (e.g. MIT OR Apache-2.0)
344
+ * against outbound license expression (e.g. GPL-2.0-only OR BSD-2-Clause)
345
+ """
346
+
347
+ def __init__(self):
348
+ self.le_checker = LicenseExpressionChecker()
349
+ self.le_parser = LicenseExpressionParser()
350
+ self.licomp_toolkit = LicompToolkit()
351
+
352
+ def __parsed_expression_to_name(self, parsed_expression):
353
+ return parsed_expression[parsed_expression[COMPATIBILITY_TYPE]]
354
+
355
+ def meta_information(self):
356
+ return {
357
+ 'tool': module_name,
358
+ 'file': 'verification',
359
+ 'file_version': licomp_toolkit_file_version,
360
+ }
361
+
362
+ def check_compatibility(self, outbound, inbound, usecase, provisioning, resources=None, detailed_report=True):
363
+
364
+ # Check usecase
365
+ try:
366
+ usecase_obj = UseCase.string_to_usecase(usecase)
367
+ except KeyError:
368
+ raise LicompException(f'Usecase {usecase} not supported.', ReturnCodes.LICOMP_UNSUPPORTED_USECASE)
369
+
370
+ # Check provisioning
371
+ try:
372
+ provisioning_obj = Provisioning.string_to_provisioning(provisioning)
373
+ except KeyError:
374
+ raise LicompException(f'Provisioning {provisioning} not supported.', ReturnCodes.LICOMP_UNSUPPORTED_PROVISIONING)
375
+
376
+ licomp_resources = list(self.licomp_toolkit.licomp_standard_resources().keys())
377
+ if not resources:
378
+ resources = licomp_resources
379
+ else:
380
+ resources = resources
381
+
382
+ unavailable_resources = []
383
+
384
+ for resource in resources:
385
+ resource_object = self.licomp_toolkit.licomp_resources()[resource]
386
+ unavailable_reasons = []
387
+
388
+ # is usecase supported by resource
389
+ if not resource_object.usecase_supported(UseCase.string_to_usecase(usecase)):
390
+ unavailable_reasons.append(f'Usecase "{usecase}" not supported')
391
+
392
+ # is prov case supported by resource
393
+ if not resource_object.provisioning_supported(Provisioning.string_to_provisioning(provisioning)):
394
+ unavailable_reasons.append(f'Provisioning case "{provisioning}" not supported')
395
+
396
+ if unavailable_reasons:
397
+ unavailable_resources.append({
398
+ "resource": resource,
399
+ 'reasons': ", ".join(unavailable_reasons),
400
+ })
401
+
402
+ unavailable_resource_keys = [resource['resource'] for resource in unavailable_resources]
403
+ available_resources = [resource for resource in resources if resource not in unavailable_resource_keys]
404
+
405
+ inbound_parsed = self.le_parser.parse_license_expression(inbound)
406
+ outbound_parsed = self.le_parser.parse_license_expression(outbound)
407
+ compatibility_object = self.__check_compatibility(outbound_parsed,
408
+ inbound_parsed,
409
+ usecase_obj,
410
+ provisioning_obj,
411
+ resources,
412
+ detailed_report)
413
+ return {
414
+ 'meta': self.meta_information(),
415
+ 'inbound': str(inbound),
416
+ 'outbound': str(outbound),
417
+ 'usecase': usecase,
418
+ 'resources': resources,
419
+ 'provisioning': provisioning,
420
+ 'compatibility': compatibility_object['compatibility'],
421
+ 'compatibility_report': compatibility_object,
422
+ 'unavailable_resources': unavailable_resources,
423
+ 'available_resources': available_resources,
424
+ }
425
+
426
+ def __check_compatibility(self,
427
+ outbound_parsed,
428
+ inbound_parsed,
429
+ usecase,
430
+ provisioning,
431
+ resources,
432
+ detailed_report=True):
433
+
434
+ outbound_type = outbound_parsed[COMPATIBILITY_TYPE]
435
+ compat_object = {
436
+ 'inbound_license': self.le_parser.to_string(inbound_parsed),
437
+ 'outbound_license': self.le_parser.to_string(outbound_parsed),
438
+ 'check_class': __class__.__name__,
439
+ }
440
+
441
+ if outbound_type == 'license':
442
+ if False:
443
+ compat_object['compatibility_check'] = f'outbound-license -> inbound-{inbound_parsed["compatibility_type"]} HERE?'
444
+ outbound_parsed_license = outbound_parsed['license']
445
+
446
+ compat = self.le_checker.check_compatibility(outbound_parsed_license,
447
+ inbound_parsed,
448
+ usecase,
449
+ provisioning,
450
+ resources,
451
+ detailed_report)
452
+ compat_object['compatibility'] = compat['compatibility']
453
+ compat_object['compatibility_object'] = compat
454
+ compat_object['compatibility_details'] = None
455
+
456
+ outbound_parsed_license = outbound_parsed['license']
457
+ compat = self.le_checker.check_compatibility(outbound_parsed_license,
458
+ inbound_parsed,
459
+ usecase,
460
+ provisioning,
461
+ resources,
462
+ detailed_report)
463
+ compat_object = compat
464
+
465
+ elif outbound_type == 'expression':
466
+ compat_object['compatibility_details'] = None
467
+ compat_object['compatibility_check'] = f'outbound-expression -> inbound-{inbound_parsed["compatibility_type"]}'
468
+ operator = outbound_parsed['operator']
469
+ operands = outbound_parsed['operands']
470
+
471
+ compat_object['operator'] = operator
472
+
473
+ operands_object = []
474
+ for operand in operands:
475
+ # Check if:
476
+ # operand from outbound license
477
+ # is compatible with
478
+ # inbound license
479
+ operand_compat = self.__check_compatibility(operand,
480
+ inbound_parsed,
481
+ usecase,
482
+ provisioning,
483
+ resources,
484
+ detailed_report)
485
+ operand_object = {
486
+ 'compatibility_object': operand_compat,
487
+ 'compatibility': operand_compat['compatibility'],
488
+ }
489
+ operands_object.append(operand_object)
490
+
491
+ compat_object['compatibility'] = self.le_checker.summarise_compatibilities(operator, operands_object)
492
+ compat_object['operands'] = operands_object
493
+
494
+ return compat_object
495
+
496
+ def simplify(self, compat):
497
+ return {
498
+ 'outbound': compat['outbound'],
499
+ 'inbound': compat['inbound'],
500
+ 'usecase': compat['usecase'],
501
+ 'provisioning': compat['provisioning'],
502
+ 'compatibility': compat['compatibility'],
503
+ 'resources': compat['available_resources'],
504
+ }