sovereign 1.0.0b102__py3-none-any.whl → 1.0.0b103__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.

Potentially problematic release.


This version of sovereign might be problematic. Click here for more details.

sovereign/schemas.py CHANGED
@@ -44,7 +44,7 @@ class CacheStrategy(str, Enum):
44
44
 
45
45
 
46
46
  class SourceData(BaseModel):
47
- scopes: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
47
+ scopes: Dict[str, List[Dict[str, Any]]] = Field(default_factory=dict)
48
48
 
49
49
 
50
50
  class ConfiguredSource(BaseModel):
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import uuid
2
3
  import asyncio
3
4
  import traceback
4
5
  from copy import deepcopy
@@ -33,29 +34,100 @@ Mods = Dict[str, Type[Modifier]]
33
34
  GMods = Dict[str, Type[GlobalModifier]]
34
35
 
35
36
 
36
- def item_comparison(old, new):
37
- if not isinstance(old, dict) or not isinstance(new, dict):
38
- return ["content_changed"]
39
-
37
+ def _deep_diff(old, new, path="") -> list[dict[str, Any]]:
40
38
  changes = []
41
- all_keys = set(old.keys()) | set(new.keys())
42
-
43
- for key in sorted(all_keys):
44
- old_val = old.get(key)
45
- new_val = new.get(key)
46
-
47
- if old_val != new_val:
39
+
40
+ # handle add/remove
41
+ if (old, new) == (None, None):
42
+ return changes
43
+ elif old is None:
44
+ changes.append({
45
+ "op": "add",
46
+ "path": path,
47
+ "value": new
48
+ })
49
+ return changes
50
+ elif new is None:
51
+ changes.append({
52
+ "op": "remove",
53
+ "path": path,
54
+ "old_value": old
55
+ })
56
+ return changes
57
+
58
+ # handle completely different types
59
+ if type(old) is not type(new):
60
+ changes.append({
61
+ "op": "change",
62
+ "path": path,
63
+ "old_value": old,
64
+ "new_value": new
65
+ })
66
+ return changes
67
+
68
+ # handle fields recursively
69
+ if isinstance(old, dict) and isinstance(new, dict):
70
+ all_keys = set(old.keys()) | set(new.keys())
71
+
72
+ for key in sorted(all_keys):
73
+ old_val = old.get(key)
74
+ new_val = new.get(key)
75
+
76
+ current_path = f"{path}.{key}" if path else key
77
+
48
78
  if key not in old:
49
- changes.append(f"+{key}:{new_val}")
79
+ changes.append({
80
+ "op": "add",
81
+ "path": current_path,
82
+ "value": new_val
83
+ })
50
84
  elif key not in new:
51
- changes.append(f"-{key}:{old_val}")
52
- else:
53
- changes.append(f"~{key}:{old_val}→{new_val}")
54
-
85
+ changes.append({
86
+ "op": "remove",
87
+ "path": current_path,
88
+ "old_value": old_val
89
+ })
90
+ elif old_val != new_val:
91
+ nested_changes = _deep_diff(old_val, new_val, current_path)
92
+ changes.extend(nested_changes)
93
+
94
+ # handle items recursively
95
+ elif isinstance(old, list) and isinstance(new, list):
96
+ max_len = max(len(old), len(new))
97
+
98
+ for i in range(max_len):
99
+ current_path = f"{path}[{i}]" if path else f"[{i}]"
100
+
101
+ if i >= len(old):
102
+ changes.append({
103
+ "op": "add",
104
+ "path": current_path,
105
+ "value": new[i]
106
+ })
107
+ elif i >= len(new):
108
+ changes.append({
109
+ "op": "remove",
110
+ "path": current_path,
111
+ "old_value": old[i]
112
+ })
113
+ elif old[i] != new[i]:
114
+ nested_changes = _deep_diff(old[i], new[i], current_path)
115
+ changes.extend(nested_changes)
116
+
117
+ # handle primitives
118
+ else:
119
+ if old != new:
120
+ changes.append({
121
+ "op": "change",
122
+ "path": path,
123
+ "old_value": old,
124
+ "new_value": new
125
+ })
126
+
55
127
  return changes
56
128
 
57
129
 
58
- def per_field_diff(old, new):
130
+ def per_field_diff(old, new) -> list[dict[str, Any]]:
59
131
  changes = []
60
132
  max_len = max(len(old), len(new))
61
133
 
@@ -64,45 +136,75 @@ def per_field_diff(old, new):
64
136
  new_inst = new[i] if i < len(new) else None
65
137
 
66
138
  if old_inst is None:
67
- changes.append(f"added [index:{i}] {new_inst}")
139
+ changes.append({
140
+ "op": "add",
141
+ "path": f"[{i}]",
142
+ "value": new_inst
143
+ })
68
144
  elif new_inst is None:
69
- changes.append(f"removed [index:{i}] {old_inst}")
145
+ changes.append({
146
+ "op": "remove",
147
+ "path": f"[{i}]",
148
+ "old_value": old_inst
149
+ })
70
150
  elif old_inst != new_inst:
71
- field_changes = item_comparison(old_inst, new_inst)
72
- if field_changes:
73
- changes.append(f"modified [index:{i}]: {', '.join(field_changes)}")
151
+ # Use the deep diff with index prefix
152
+ field_changes = _deep_diff(old_inst, new_inst, f"[{i}]")
153
+ changes.extend(field_changes)
74
154
 
75
155
  return changes
76
156
 
77
157
 
78
- def source_diff_summary(prev, curr):
158
+ def _gen_uuid(diff_summary: dict[str, Any]) -> str:
159
+ blob = json.dumps(diff_summary, sort_keys=True, separators=('', ''))
160
+ return str(uuid.uuid5(uuid.NAMESPACE_DNS, blob))
161
+
162
+
163
+ def source_diff_summary(prev, curr) -> dict[str, Any]:
79
164
  if prev is None:
80
- return [
81
- f"scope:{scope} added:{len(instances)} instances"
82
- for scope, instances in curr.scopes.items()
83
- if instances
84
- ]
165
+ summary = {
166
+ "type": "initial_load",
167
+ "scopes": {
168
+ scope: {"added": len(instances)}
169
+ for scope, instances in curr.scopes.items()
170
+ if instances
171
+ }
172
+ }
173
+ else:
174
+ summary = {
175
+ "type": "update",
176
+ "scopes": {}
177
+ }
178
+
179
+ all_scopes = set(prev.scopes.keys()) | set(curr.scopes.keys())
180
+
181
+ for scope in sorted(all_scopes):
182
+ old = prev.scopes.get(scope, [])
183
+ new = curr.scopes.get(scope, [])
85
184
 
86
- summary = []
87
- all_scopes = set(prev.scopes.keys()) | set(curr.scopes.keys())
185
+ n_old = len(old)
186
+ n_new = len(new)
88
187
 
89
- for scope in sorted(all_scopes):
90
- old = prev.scopes.get(scope, [])
91
- new = curr.scopes.get(scope, [])
188
+ scope_changes = {}
92
189
 
93
- n_old = len(old)
94
- n_new = len(new)
190
+ if n_old == 0 and n_new > 0:
191
+ scope_changes["added"] = n_new
192
+ elif n_old > 0 and n_new == 0:
193
+ scope_changes["removed"] = n_old
194
+ elif old != new:
195
+ detailed_changes = per_field_diff(old, new)
196
+ if detailed_changes:
197
+ scope_changes["field_changes"] = detailed_changes
198
+ scope_changes["count_change"] = n_new - n_old
95
199
 
96
- if n_old == 0 and n_new > 0:
97
- summary.append(f"scope:{scope} added:{n_new} instances")
98
- elif n_old > 0 and n_new == 0:
99
- summary.append(f"scope:{scope} removed:{n_old} instances")
100
- elif old != new:
101
- detailed_changes = per_field_diff(old, new)
102
- if detailed_changes:
103
- summary.append(f"scope:{scope} changes: {'; '.join(detailed_changes)}")
200
+ if scope_changes:
201
+ summary["scopes"][scope] = scope_changes
104
202
 
105
- return summary if summary else ["no changes detected"]
203
+ if not summary["scopes"]:
204
+ summary = {"type": "no_changes"}
205
+
206
+ summary["uuid"] = _gen_uuid(summary)
207
+ return summary
106
208
 
107
209
 
108
210
  class SourcePoller:
@@ -229,7 +331,10 @@ class SourcePoller:
229
331
  try:
230
332
  new = SourceData()
231
333
  for source in self.sources:
232
- new.scopes[source.scope].extend(source.get())
334
+ scope = source.scope
335
+ if scope not in new.scopes:
336
+ new.scopes[scope] = []
337
+ new.scopes[scope].extend(source.get())
233
338
  except Exception as e:
234
339
  self.logger.error(
235
340
  event="Error while refreshing sources",
@@ -345,6 +450,8 @@ class SourcePoller:
345
450
  or is_debug_request(node_value)
346
451
  )
347
452
  if match:
453
+ if scope not in ret.scopes:
454
+ ret.scopes[scope] = []
348
455
  ret.scopes[scope].append(instance)
349
456
  return ret
350
457
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sovereign
3
- Version: 1.0.0b102
3
+ Version: 1.0.0b103
4
4
  Summary: Envoy Proxy control-plane written in Python
5
5
  Home-page: https://pypi.org/project/sovereign/
6
6
  License: Apache-2.0
@@ -17,13 +17,13 @@ sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
17
17
  sovereign/modifiers/lib.py,sha256=Cx0VrpTKbSjb3YmHyG4Jy6YEaPlrwpeqNaom3zu1_hw,2885
18
18
  sovereign/rendering.py,sha256=MIA7se7-C4WTWf7xZSgqpf7NvhDT7NkZbR3_G9N1dHI,5015
19
19
  sovereign/response_class.py,sha256=beMAFV-4L6DwyWzJzy71GkEW4gb7fzH1jd8-Tul13cU,427
20
- sovereign/schemas.py,sha256=nuiLKq26Fd5MGDMPcTEzyb0QuVuWKaXZRtIdlQ3BzkA,37261
20
+ sovereign/schemas.py,sha256=RtZm1qVIuXF9M6FXHa0VP6C0lBNU6sDlmM5k28RJXxM,37271
21
21
  sovereign/server.py,sha256=3qfcUGaRrTF2GTfGEJgGnPXBiZGis1qxOlpIKvoCyFA,2596
22
22
  sovereign/sources/__init__.py,sha256=g9hEpFk8j5i1ApHQpbc9giTyJW41Ppgsqv5P9zGxOJk,78
23
23
  sovereign/sources/file.py,sha256=prUThsDCSPNwZaZpkKXhAm-GVRZWbBoGKGU0It4HHXs,690
24
24
  sovereign/sources/inline.py,sha256=pP77m7bHjqE3sSoqZthcuw1ARVMf9gooVwbz4B8OAek,1003
25
25
  sovereign/sources/lib.py,sha256=0hk_G6mKJrB65WokVZnqF5kdJ3vsQZMNPuJqJO0mBsI,1031
26
- sovereign/sources/poller.py,sha256=sJ4bi6pCPX_cw1RGAUTQfM11bsWLeohEkpzSfFYwUeI,14706
26
+ sovereign/sources/poller.py,sha256=ahR3C08BspaoQxT2-IaJckAQf9uZgyY3w5J5SunFtoI,17824
27
27
  sovereign/static/node_expression.js,sha256=dL9QLM49jorqavf3Qtye6E1QTWYDT1rFI0tQR1HsiLQ,504
28
28
  sovereign/static/panel.js,sha256=i5mGExjv-I4Gtt9dQiTyFwPZa8pg5rXeuTeidXNUiTE,2695
29
29
  sovereign/static/sass/style.scss,sha256=tPHPEm3sZeBFGDyyn3pHcA-nbaKT-h-UsSTsf6dHNDU,1158
@@ -60,8 +60,8 @@ sovereign/views/discovery.py,sha256=4sVRPWpH8nYwY26GWK9N8maOYUNbIJTrUckZ3MahqAU,
60
60
  sovereign/views/healthchecks.py,sha256=TaXbxkX679jyQ8v5FxtBa2Qa0Z7KuqQ10WgAqfuVGUc,1743
61
61
  sovereign/views/interface.py,sha256=FmQ7LiUPLSvkEDOKCncrnKMD9g1lJKu-DQNbbyi8mqk,6346
62
62
  sovereign/worker.py,sha256=NqXlfi9QRlXcWeUPqHWvnYx2CPldY44iLb_fBJAiZ-4,4983
63
- sovereign-1.0.0b102.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
64
- sovereign-1.0.0b102.dist-info/METADATA,sha256=3vjnWeVBnS-UJ9buN9wk8n9PEgdeCi2hY5r2RFZ4fhc,6268
65
- sovereign-1.0.0b102.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
66
- sovereign-1.0.0b102.dist-info/entry_points.txt,sha256=VKJdnnN_HNL8xYQMXsFXfFmN6QkdXMEk5S964avxQJI,1404
67
- sovereign-1.0.0b102.dist-info/RECORD,,
63
+ sovereign-1.0.0b103.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
64
+ sovereign-1.0.0b103.dist-info/METADATA,sha256=KGa6EiG_oC1w-LS3IfL1yzDGxCVVwNp-WbrqdHr6pHg,6268
65
+ sovereign-1.0.0b103.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
66
+ sovereign-1.0.0b103.dist-info/entry_points.txt,sha256=VKJdnnN_HNL8xYQMXsFXfFmN6QkdXMEk5S964avxQJI,1404
67
+ sovereign-1.0.0b103.dist-info/RECORD,,