illumio-pylo 0.2.5__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.
Files changed (73) hide show
  1. illumio_pylo/API/APIConnector.py +1308 -0
  2. illumio_pylo/API/AuditLog.py +42 -0
  3. illumio_pylo/API/ClusterHealth.py +136 -0
  4. illumio_pylo/API/CredentialsManager.py +286 -0
  5. illumio_pylo/API/Explorer.py +1077 -0
  6. illumio_pylo/API/JsonPayloadTypes.py +240 -0
  7. illumio_pylo/API/RuleSearchQuery.py +128 -0
  8. illumio_pylo/API/__init__.py +0 -0
  9. illumio_pylo/AgentStore.py +139 -0
  10. illumio_pylo/Exception.py +44 -0
  11. illumio_pylo/Helpers/__init__.py +3 -0
  12. illumio_pylo/Helpers/exports.py +508 -0
  13. illumio_pylo/Helpers/functions.py +166 -0
  14. illumio_pylo/IPList.py +135 -0
  15. illumio_pylo/IPMap.py +285 -0
  16. illumio_pylo/Label.py +25 -0
  17. illumio_pylo/LabelCommon.py +48 -0
  18. illumio_pylo/LabelGroup.py +68 -0
  19. illumio_pylo/LabelStore.py +403 -0
  20. illumio_pylo/LabeledObject.py +25 -0
  21. illumio_pylo/Organization.py +258 -0
  22. illumio_pylo/Query.py +331 -0
  23. illumio_pylo/ReferenceTracker.py +41 -0
  24. illumio_pylo/Rule.py +671 -0
  25. illumio_pylo/Ruleset.py +306 -0
  26. illumio_pylo/RulesetStore.py +101 -0
  27. illumio_pylo/SecurityPrincipal.py +62 -0
  28. illumio_pylo/Service.py +256 -0
  29. illumio_pylo/SoftwareVersion.py +125 -0
  30. illumio_pylo/VirtualService.py +17 -0
  31. illumio_pylo/VirtualServiceStore.py +75 -0
  32. illumio_pylo/Workload.py +506 -0
  33. illumio_pylo/WorkloadStore.py +289 -0
  34. illumio_pylo/__init__.py +82 -0
  35. illumio_pylo/cli/NativeParsers.py +96 -0
  36. illumio_pylo/cli/__init__.py +134 -0
  37. illumio_pylo/cli/__main__.py +10 -0
  38. illumio_pylo/cli/commands/__init__.py +32 -0
  39. illumio_pylo/cli/commands/credential_manager.py +168 -0
  40. illumio_pylo/cli/commands/iplist_import_from_file.py +185 -0
  41. illumio_pylo/cli/commands/misc.py +7 -0
  42. illumio_pylo/cli/commands/ruleset_export.py +129 -0
  43. illumio_pylo/cli/commands/update_pce_objects_cache.py +44 -0
  44. illumio_pylo/cli/commands/ven_duplicate_remover.py +366 -0
  45. illumio_pylo/cli/commands/ven_idle_to_visibility.py +287 -0
  46. illumio_pylo/cli/commands/ven_upgrader.py +226 -0
  47. illumio_pylo/cli/commands/workload_export.py +251 -0
  48. illumio_pylo/cli/commands/workload_import.py +423 -0
  49. illumio_pylo/cli/commands/workload_relabeler.py +510 -0
  50. illumio_pylo/cli/commands/workload_reset_names_to_null.py +83 -0
  51. illumio_pylo/cli/commands/workload_used_in_rule_finder.py +80 -0
  52. illumio_pylo/docs/Doxygen +1757 -0
  53. illumio_pylo/tmp.py +104 -0
  54. illumio_pylo/utilities/__init__.py +0 -0
  55. illumio_pylo/utilities/cli.py +10 -0
  56. illumio_pylo/utilities/credentials.example.json +20 -0
  57. illumio_pylo/utilities/explorer_report_exporter.py +86 -0
  58. illumio_pylo/utilities/health_monitoring.py +102 -0
  59. illumio_pylo/utilities/iplist_analyzer.py +148 -0
  60. illumio_pylo/utilities/iplists_stats_duplicates_unused_finder.py +75 -0
  61. illumio_pylo/utilities/resources/iplists-import-example.csv +3 -0
  62. illumio_pylo/utilities/resources/iplists-import-example.xlsx +0 -0
  63. illumio_pylo/utilities/resources/workload-exporter-filter-example.csv +3 -0
  64. illumio_pylo/utilities/resources/workloads-import-example.csv +2 -0
  65. illumio_pylo/utilities/resources/workloads-import-example.xlsx +0 -0
  66. illumio_pylo/utilities/ven_compatibility_report_export.py +240 -0
  67. illumio_pylo/utilities/ven_idle_to_illumination.py +344 -0
  68. illumio_pylo/utilities/ven_reassign_pce.py +183 -0
  69. illumio_pylo-0.2.5.dist-info/LICENSE +176 -0
  70. illumio_pylo-0.2.5.dist-info/METADATA +197 -0
  71. illumio_pylo-0.2.5.dist-info/RECORD +73 -0
  72. illumio_pylo-0.2.5.dist-info/WHEEL +5 -0
  73. illumio_pylo-0.2.5.dist-info/top_level.txt +1 -0
illumio_pylo/IPList.py ADDED
@@ -0,0 +1,135 @@
1
+ from typing import Dict, Optional
2
+
3
+ import illumio_pylo as pylo
4
+ from .API.JsonPayloadTypes import IPListObjectJsonStructure
5
+ from illumio_pylo import log
6
+ from .Helpers import *
7
+
8
+
9
+ class IPList(pylo.ReferenceTracker):
10
+
11
+ """
12
+ :type owner: IPListStore
13
+ :type description: str|None
14
+ :type raw_entries: dict[str,str]
15
+ """
16
+ name: str
17
+ href: str
18
+
19
+ def __init__(self, name: str, href: str, owner: 'pylo.IPListStore', description=None):
20
+ """
21
+ :type name: str
22
+ :type href: str
23
+ :type owner: IPListStore
24
+ """
25
+ pylo.ReferenceTracker.__init__(self)
26
+ self.owner = owner
27
+ self.name = name
28
+ self.href = href
29
+ self.description = description
30
+ self.raw_json = None
31
+ self.raw_entries = {}
32
+
33
+ def count_entries(self) -> int:
34
+ return len(self.raw_entries)
35
+
36
+ def load_from_json(self, json_input: IPListObjectJsonStructure):
37
+ self.raw_json = json_input
38
+
39
+ ip_ranges_array = json_input.get("ip_ranges")
40
+ if ip_ranges_array is None:
41
+ raise pylo.PyloEx("cannot find 'ip_ranges' in IPList JSON:\n" + nice_json(json_input))
42
+
43
+ for ip_range in ip_ranges_array:
44
+ from_ip = ip_range.get("from_ip")
45
+ if from_ip is None:
46
+ raise pylo.PyloEx("cannot find 'from_ip' in IPList JSON:\n" + nice_json(ip_range))
47
+
48
+ slash_pos = from_ip.find('/')
49
+ if slash_pos < 0:
50
+ to_ip = ip_range.get("to_ip")
51
+ if to_ip is None:
52
+ entry = from_ip
53
+ else:
54
+ if len(to_ip) < 4:
55
+ entry = from_ip + "/" + to_ip
56
+ else:
57
+ entry = from_ip + '-' + to_ip
58
+ else:
59
+ entry = from_ip
60
+
61
+ exclusion = ip_range.get('exclusion')
62
+ if exclusion is not None and exclusion:
63
+ entry = '!' + entry
64
+
65
+ self.raw_entries[entry] = entry
66
+
67
+ def get_ip4map(self) -> pylo.IP4Map:
68
+ map = pylo.IP4Map()
69
+
70
+ for entry in self.raw_entries:
71
+ if entry[0] == '!':
72
+ map.subtract_from_text(entry[1:], ignore_ipv6=True)
73
+ else:
74
+ map.add_from_text(entry, ignore_ipv6=True)
75
+
76
+ return map
77
+
78
+ def get_raw_entries_as_string_list(self, separator=',') -> str:
79
+ return pylo.string_list_to_text(self.raw_entries.values(), separator=separator)
80
+
81
+ def get_api_reference_json(self):
82
+ return {'ip_list': {'href': self.href}}
83
+
84
+
85
+ class IPListStore:
86
+ items_by_href: Dict[str, 'pylo.IPList']
87
+
88
+ def __init__(self, owner: 'pylo.Organization'):
89
+ self.owner = owner
90
+ self.items_by_href = {}
91
+
92
+ def count(self) -> int:
93
+ return len(self.items_by_href)
94
+
95
+ @property
96
+ def iplists(self) -> list['pylo.IPList']:
97
+ return list(self.items_by_href.values())
98
+
99
+ @property
100
+ def iplists_by_href(self) -> Dict[str, 'pylo.IPList']:
101
+ return self.items_by_href.copy()
102
+
103
+ def load_iplists_from_json(self, json_list: list[IPListObjectJsonStructure]):
104
+ for json_item in json_list:
105
+ if 'name' not in json_item or 'href' not in json_item:
106
+ raise Exception("Cannot find 'value'/name or href for iplist in JSON:\n" + nice_json(json_item))
107
+ new_iplist_name = json_item['name']
108
+ new_iplist_href = json_item['href']
109
+ new_iplist_desc = json_item.get('description')
110
+
111
+ new_iplist = pylo.IPList(new_iplist_name, new_iplist_href, self, new_iplist_desc)
112
+ new_iplist.load_from_json(json_item)
113
+
114
+ if new_iplist_href in self.items_by_href:
115
+ raise Exception("A iplist with href '%s' already exists in the table", new_iplist_href)
116
+
117
+ self.items_by_href[new_iplist_href] = new_iplist
118
+
119
+ log.debug("Found iplist '%s' with href '%s'", new_iplist_name, new_iplist_href)
120
+
121
+ def find_by_href(self, href: str) -> Optional['pylo.IPList']:
122
+ return self.items_by_href.get(href)
123
+
124
+ def find_by_name(self, name: str, case_sensitive: bool = True ) -> Optional['pylo.IPList']:
125
+ if case_sensitive:
126
+ for iplist in self.items_by_href.values():
127
+ if iplist.name == name:
128
+ return iplist
129
+ else:
130
+ lower_name = name.lower()
131
+ for iplist in self.items_by_href.values():
132
+ if iplist.name.lower() == lower_name:
133
+ return iplist
134
+ return None
135
+
illumio_pylo/IPMap.py ADDED
@@ -0,0 +1,285 @@
1
+ from .Exception import PyloEx
2
+ from .Helpers.functions import is_valid_ipv6, string_list_to_text
3
+ import ipaddress
4
+ import copy
5
+ from typing import Optional, List, Dict
6
+
7
+
8
+ def sort_first(val):
9
+ return val[0]
10
+
11
+
12
+ start = 0
13
+ end = 1
14
+
15
+ masks = []
16
+ mask = 0
17
+ for i in range(32):
18
+ mask |= 1 << i
19
+ masks.append(mask)
20
+
21
+
22
+ class IP4Map:
23
+ def __init__(self):
24
+ self._entries = []
25
+
26
+ @staticmethod
27
+ def ip_entry_from_text(entry: str, ignore_ipv6=True) -> Optional[List[int]]:
28
+ new_entry = None
29
+
30
+ dash_find = entry.find('-')
31
+
32
+ if dash_find > 0:
33
+ # this is a range entry
34
+ start_txt = entry[0:dash_find]
35
+ if ignore_ipv6 and is_valid_ipv6(start_txt):
36
+ return None
37
+ end_txt = entry[dash_find+1:]
38
+ if ignore_ipv6 and is_valid_ipv6(end_txt):
39
+ return None
40
+ start_ip_object = ipaddress.IPv4Address(start_txt)
41
+ end_ip_object = ipaddress.IPv4Address(end_txt)
42
+ new_entry = [int(start_ip_object), int(end_ip_object)]
43
+ if new_entry[start] > new_entry[end]:
44
+ raise PyloEx("Invalid IP Ranged entered with start address > end address: {}".format(entry))
45
+
46
+ elif entry.find('/') > 0:
47
+ # This is a network entry
48
+ network_str = entry[0:(entry.find('/'))]
49
+ if ignore_ipv6 and (is_valid_ipv6(network_str) or network_str == '::'):
50
+ return None
51
+ ip_object = ipaddress.IPv4Network(entry)
52
+ new_entry = [int(ip_object.network_address), int(ip_object.broadcast_address)]
53
+ if ignore_ipv6 and is_valid_ipv6(ip_object.network_address.__str__()):
54
+ return None
55
+ else:
56
+ if ignore_ipv6 and is_valid_ipv6(entry):
57
+ return None
58
+ ip_object = ipaddress.IPv4Address(entry)
59
+ new_entry = [int(ip_object), int(ip_object)]
60
+
61
+ return new_entry
62
+
63
+ def add_from_text(self, entry: str, skip_recalculation=False, ignore_ipv6=True):
64
+
65
+ new_entry = self.ip_entry_from_text(entry, ignore_ipv6=ignore_ipv6)
66
+
67
+ if ignore_ipv6 and new_entry is None:
68
+ return
69
+
70
+ if not skip_recalculation:
71
+ self._entries.append(new_entry)
72
+ self.sort_and_recalculate()
73
+
74
+ def intersection(self, another_map: 'IP4Map'):
75
+
76
+ inverted_map = IP4Map()
77
+ inverted_map.add_from_text('0.0.0.0-255.255.255.255')
78
+
79
+ inverted_map.substract(another_map)
80
+
81
+ result = copy.deepcopy(self)
82
+ result.substract(inverted_map)
83
+
84
+ return result
85
+
86
+ def contains(self, another_map: 'IP4Map') -> bool:
87
+
88
+ if len(self._entries) < 1:
89
+ return False
90
+
91
+ copy_of_map = copy.deepcopy(another_map)
92
+ copy_of_map.substract(self)
93
+
94
+ if len(copy_of_map._entries) < 1:
95
+ return True
96
+ return False
97
+
98
+ def substract(self, another_map: 'IP4Map'):
99
+ affected_rows = 0
100
+ for entry in another_map._entries:
101
+ affected_rows += self.substract_single_entry(entry)
102
+ return affected_rows
103
+
104
+ def substract_single_entry(self, sub_entry: []) -> int:
105
+ affected_rows = 0
106
+ updated_entries = []
107
+
108
+ for entry in self._entries:
109
+ if sub_entry[end] < entry[start] or sub_entry[start] > entry[end]:
110
+ # no overlap at all, entry is left untouched
111
+ updated_entries.append(entry)
112
+ continue
113
+
114
+ affected_rows += 1
115
+ if sub_entry[start] <= entry[start]:
116
+ if sub_entry[end] >= entry[end]:
117
+ # complete overlap, remove entry
118
+ continue
119
+ else:
120
+ entry[start] = sub_entry[end] + 1
121
+ updated_entries.append(entry)
122
+ else:
123
+ updated_entries.append([entry[start], sub_entry[start] - 1])
124
+ if sub_entry[end] < entry[end]:
125
+ updated_entries.append([sub_entry[end] + 1, entry[end]])
126
+
127
+ self._entries = updated_entries
128
+
129
+ return affected_rows
130
+
131
+ def subtract_from_text(self, entry: str, ignore_ipv6=False):
132
+
133
+ new_entry = self.ip_entry_from_text(entry, ignore_ipv6=ignore_ipv6)
134
+
135
+ if new_entry is None:
136
+ return 0
137
+
138
+ return self.substract_single_entry(new_entry)
139
+
140
+ def sort_and_recalculate(self):
141
+ new_entries = []
142
+
143
+ self._entries.sort(key=sort_first)
144
+
145
+ cursor = None # current entry being processed
146
+ for entry in self._entries:
147
+ if cursor is None: # usually the first entry
148
+ cursor = entry
149
+ continue
150
+
151
+ # if current entry has no overlap with cursor entry then cursor entry is added to result array and cursor
152
+ # is set to current entry and loop starts again
153
+ if entry[start] > cursor[end]:
154
+ new_entries.append(cursor)
155
+ cursor = entry
156
+ continue
157
+
158
+ # if current entry's end is greater than cursor entry's end then cursor entry's end is set to current
159
+ # entry's end and loop starts again
160
+ if entry[end] > cursor[end]:
161
+ cursor[end] = entry[end]
162
+ continue
163
+
164
+ # if current entry's end is less or equal to cursor entry's end then there is nothing to do and loop
165
+ # starts again
166
+ if entry[end] <= cursor[end]:
167
+ continue
168
+
169
+ raise PyloEx("Error while sorting IP4Map, unexpected value found: entry({}-{}) cursor({}-{})".format(
170
+ ipaddress.IPv4Address(entry[start]),
171
+ ipaddress.IPv4Address(entry[end]),
172
+ ipaddress.IPv4Address(cursor[start]),
173
+ ipaddress.IPv4Address(cursor[end])
174
+ ))
175
+
176
+ # in case there is still a cursor entry left, add it to result array
177
+ if cursor is not None:
178
+ new_entries.append(cursor)
179
+
180
+ self._entries = new_entries
181
+
182
+ def to_string_list(self, separator=','):
183
+ ranges = []
184
+
185
+ for entry in self._entries:
186
+ ranges.append('{}-{}'.format(ipaddress.IPv4Address(entry[start]), ipaddress.IPv4Address(entry[end])))
187
+
188
+ return string_list_to_text(ranges, separator=separator)
189
+
190
+ def to_list_of_string(self):
191
+ ranges = []
192
+
193
+ for entry in self._entries:
194
+ if entry[start] == entry[end]:
195
+ ranges.append('{}'.format(ipaddress.IPv4Address(entry[start])))
196
+ else:
197
+ ranges.append('{}-{}'.format(ipaddress.IPv4Address(entry[start]), ipaddress.IPv4Address(entry[end])))
198
+
199
+ return ranges
200
+
201
+ def to_list_of_cidr_string(self, skip_netmask_for_32=False):
202
+
203
+ result = []
204
+
205
+ for entry in self._entries:
206
+
207
+ net_start = entry[start]
208
+ net_end = entry[end]
209
+
210
+ previous_loop_end = net_start
211
+
212
+ while net_start <= net_end:
213
+
214
+ if net_start == net_end:
215
+ if skip_netmask_for_32:
216
+ result.append('{}'.format(ipaddress.IPv4Address(net_start)))
217
+ else:
218
+ result.append('{}'.format(ipaddress.IPv4Address(net_start), 32))
219
+ break
220
+
221
+ for netmask in range(1, 32, 1):
222
+ new_end = net_start | masks[netmask]
223
+ #print("{}/{}/{}/{}".format(ipaddress.IPv4Address(net_start), ipaddress.IPv4Address(net_end), ipaddress.IPv4Address(new_end), 32 - netmask))
224
+
225
+ if new_end > net_end:
226
+ result.append('{}/{}'.format(ipaddress.IPv4Address(net_start), 33 - netmask))
227
+ net_start = previous_loop_end + 1
228
+ previous_loop_end = net_start
229
+ #print("breaking loop with {}/{}".format(ipaddress.IPv4Address(net_start), ipaddress.IPv4Address(previous_loop_end)))
230
+ break
231
+
232
+ if new_end == net_end:
233
+ if skip_netmask_for_32 and netmask == 0:
234
+ result.append('{}'.format(ipaddress.IPv4Address(net_start)))
235
+ else:
236
+ result.append('{}/{}'.format(ipaddress.IPv4Address(net_start), 32 - netmask))
237
+ net_start = net_end+1
238
+ break
239
+ else:
240
+ previous_loop_end = new_end
241
+
242
+ return result
243
+
244
+ def count_ips(self) -> int:
245
+ count = 0
246
+ for entry in self._entries:
247
+ count += entry[1] - entry[0] + 1
248
+
249
+ return count
250
+
251
+ def count_entries(self) -> int:
252
+ return len(self._entries)
253
+
254
+ def print_to_std(self, header=None, padding='', list_marker=' - '):
255
+ if header is not None:
256
+ print('{}{}({} entries)'.format(
257
+ padding,
258
+ header,
259
+ len(self._entries)))
260
+
261
+ for entry in self._entries:
262
+ if entry[0] == entry[1]:
263
+ print('{}{}{}'.format(padding, list_marker, ipaddress.IPv4Address(entry[0])))
264
+ else:
265
+ print('{}{}{}-{}'.format(padding, list_marker, ipaddress.IPv4Address(entry[0]), ipaddress.IPv4Address(entry[1])))
266
+
267
+
268
+ # test = IP4Map()
269
+ # test.add_from_text('10.0.0.0/16')
270
+ # test.add_from_text('10.0.0.0-10.2.50.50')
271
+ # test.add_from_text('192.168.1.2')
272
+ # test.add_from_text('1.0.0.0/8')
273
+ # test.add_from_text('192.168.1.0-192.168.2.0')
274
+ # test.subtract_from_text('192.168.0.0-192.168.1.255')
275
+ #
276
+ # test.add_from_text('200.0.0.0-200.1.255.255')
277
+ # test.subtract_from_text('199.255.255.255-200.1.0.0') # should produce 200.1.0.1-200.1.255.255
278
+ #
279
+ # test.add_from_text('200.10.0.0-200.11.255.255')
280
+ # test.subtract_from_text('200.10.10.10-200.11.0.0') # should produce 200.10.0.0-200.10.10.9 and 200.11.0.1-200.11.255.255
281
+ #
282
+ # test.add_from_text('200.20.0.0-200.21.255.255')
283
+ # test.subtract_from_text('200.20.10.10-200.22.0.0') # should produce 200.20.0.0-200.20.10.9
284
+ #
285
+ # test.print_to_std(header="Show IP4Map test:", padding='')
illumio_pylo/Label.py ADDED
@@ -0,0 +1,25 @@
1
+ from .API.JsonPayloadTypes import LabelHrefRef
2
+ from .ReferenceTracker import ReferenceTracker
3
+ from .LabelCommon import LabelCommon
4
+ from .LabelStore import LabelStore
5
+
6
+
7
+ class Label(ReferenceTracker, LabelCommon):
8
+ def __init__(self, name, href, label_type: str, owner: 'LabelStore'):
9
+ ReferenceTracker.__init__(self)
10
+ LabelCommon.__init__(self, name, href, label_type, owner)
11
+
12
+ def is_group(self) -> bool:
13
+ return False
14
+
15
+ def is_label(self) -> bool:
16
+ return True
17
+
18
+ def reference_obj(self):
19
+ return {"href": self.href,
20
+ "value": self.name,
21
+ "key": self.type}
22
+
23
+ def get_api_reference_json(self) -> LabelHrefRef:
24
+ return {'label': {'href': self.href}}
25
+
@@ -0,0 +1,48 @@
1
+ from typing import Union
2
+ from .Exception import PyloEx
3
+ from .LabelStore import label_type_role, label_type_env, label_type_loc, label_type_app, LabelStore
4
+
5
+
6
+ class LabelCommon:
7
+
8
+ def __init__(self, name: str, href: str, label_type: str, owner: LabelStore):
9
+ self.owner: LabelStore = owner
10
+ self.name: str = name
11
+ self.href: str = href
12
+ self.type = label_type
13
+
14
+ def is_label(self) -> bool:
15
+ raise PyloEx("not implemented")
16
+
17
+ def is_group(self) -> bool:
18
+ raise PyloEx("not implemented")
19
+
20
+ def type_to_short_string(self) -> str:
21
+ return self.type
22
+
23
+ def type_is_location(self) -> bool:
24
+ return self.type == 'loc'
25
+
26
+ def type_is_environment(self) -> bool:
27
+ return self.type == 'env'
28
+
29
+ def type_is_application(self) -> bool:
30
+ return self.type == 'app'
31
+
32
+ def type_is_role(self) -> bool:
33
+ return self.type == 'role'
34
+
35
+ def type_string(self) -> str:
36
+ return self.type
37
+
38
+ def api_set_name(self, new_name: str):
39
+ find_collision = self.owner.find_label_by_name_and_type(new_name, self.type)
40
+ if find_collision is not self:
41
+ raise PyloEx("A Label/LabelGroup with name '{}' already exists".format(new_name))
42
+
43
+ if self.is_group():
44
+ self.owner.owner.connector.objects_labelgroup_update(self.href, data={'name': new_name})
45
+ else:
46
+ self.owner.owner.connector.objects_label_update(self.href, data={'value': new_name})
47
+
48
+ self.name = new_name
@@ -0,0 +1,68 @@
1
+ from typing import Dict, Union
2
+ import illumio_pylo as pylo
3
+ from typing import *
4
+
5
+ from illumio_pylo import Label
6
+ from .API.JsonPayloadTypes import LabelGroupObjectJsonStructure
7
+
8
+
9
+ class LabelGroup(pylo.ReferenceTracker, pylo.LabelCommon):
10
+
11
+ def __init__(self, name: str, href: str, label_type: str, owner):
12
+ pylo.ReferenceTracker.__init__(self)
13
+ pylo.LabelCommon.__init__(self, name, href, label_type, owner)
14
+ self._members_by_href: Dict[str, Union['pylo.Label', 'pylo.LabelGroup']] = {}
15
+ self.raw_json: Optional[LabelGroupObjectJsonStructure] = None
16
+
17
+ def load_from_json(self):
18
+ # print(self.raw_json)
19
+ if 'labels' in self.raw_json:
20
+ for href_record in self.raw_json['labels']:
21
+ if 'href' in href_record:
22
+ find_label = self.owner.find_by_href(href_record['href'])
23
+ if find_label is None:
24
+ raise pylo.PyloEx(f"LabelGroup named '{self.name}' has member with href '{href_record['href']}' not found")
25
+ find_label.add_reference(self)
26
+ self._members_by_href[find_label.href] = find_label
27
+ else:
28
+ raise pylo.PyloEx('LabelGroup member has no HREF')
29
+
30
+ def expand_nested_to_dict_by_href(self) -> Dict[str, 'pylo.Label']:
31
+ results = {}
32
+ for label in self._members_by_href.values():
33
+ if isinstance(label, pylo.Label):
34
+ results[label.href] = label
35
+ elif isinstance(label, pylo.LabelGroup):
36
+ for nested_label in label.expand_nested_to_dict_by_href().values():
37
+ results[nested_label.href] = nested_label
38
+ else:
39
+ raise pylo.PyloEx("Unsupported object type {}".format(type(label)))
40
+ return results
41
+
42
+ def expand_nested_to_array(self) -> List['pylo.Label']:
43
+ return list(self.expand_nested_to_dict_by_href().values())
44
+
45
+ def get_api_reference_json(self) -> Dict:
46
+ return {'label_group': {'href': self.href}}
47
+
48
+ def get_members(self) -> Dict[str, 'pylo.Label']:
49
+ data = {}
50
+ for label in self._members_by_href.values():
51
+ data[label.href] = label
52
+ return data
53
+
54
+ def get_members_count(self) -> int:
55
+ return len(self._members_by_href)
56
+
57
+ def has_member_with_href(self, href: str) -> bool:
58
+ return href in self._members_by_href
59
+
60
+ def has_member_object(self, label: 'pylo.Label') -> bool:
61
+ return label.href in self._members_by_href
62
+
63
+ def is_group(self) -> bool:
64
+ return True
65
+
66
+ def is_label(self) -> bool:
67
+ return False
68
+