sovereign 0.29.1__py3-none-any.whl → 0.29.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.

Potentially problematic release.


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

sovereign/discovery.py CHANGED
@@ -99,11 +99,12 @@ def response(request: DiscoveryRequest, xds_type: str) -> ProcessedTemplate:
99
99
  :return: An envoy Discovery Response
100
100
  """
101
101
  template: XdsTemplate = select_template(request, xds_type)
102
+ node_context = template_context.get_context(request, template)
102
103
  context = dict(
103
104
  discovery_request=request,
104
105
  host_header=request.desired_controlplane,
105
106
  resource_names=request.resources,
106
- **template_context.get_context(request, template),
107
+ **node_context,
107
108
  )
108
109
  content = template(**context)
109
110
 
@@ -118,12 +119,28 @@ def response(request: DiscoveryRequest, xds_type: str) -> ProcessedTemplate:
118
119
  # Early return if the template is identical
119
120
  config_version = compute_hash(content)
120
121
  if config_version == request.version_info and not config.discovery_cache.enabled:
121
- return ProcessedTemplate(version_info=config_version, resources=[])
122
+ return ProcessedTemplate(
123
+ version_info=config_version,
124
+ resources=[],
125
+ metadata=[
126
+ "Detail: no changes",
127
+ f"Template used: {template}",
128
+ f"Template memory address: {hex(id(template))}",
129
+ ],
130
+ )
122
131
 
123
132
  if not isinstance(content, dict):
124
133
  raise RuntimeError(f"Attempting to filter unstructured data: {content}")
125
134
  resources = filter_resources(content["resources"], request.resources)
126
- return ProcessedTemplate(resources=resources, version_info=config_version)
135
+ return ProcessedTemplate(
136
+ resources=resources,
137
+ version_info=config_version,
138
+ metadata=[
139
+ "Detail: new resources generated",
140
+ f"Template used: {template}",
141
+ f"Template memory address: {hex(id(template))}",
142
+ ],
143
+ )
127
144
 
128
145
 
129
146
  def deserialize_config(content: str) -> Dict[str, Any]:
sovereign/schemas.py CHANGED
@@ -142,6 +142,7 @@ class XdsTemplate:
142
142
  self.source = self.load_source()
143
143
  template_ast = jinja_env.parse(self.source)
144
144
  self.jinja_variables = meta.find_undeclared_variables(template_ast)
145
+ self._repr = f"XdsTemplate({self.loadable=})"
145
146
 
146
147
  def __call__(
147
148
  self, *args: Any, **kwargs: Any
@@ -192,7 +193,7 @@ class XdsTemplate:
192
193
  return str(ret)
193
194
 
194
195
  def __repr__(self) -> str:
195
- return f"XdsTemplate({self.loadable=}, {self.is_python_source=}, {self.source=}, {self.jinja_variables=})"
196
+ return self._repr
196
197
 
197
198
 
198
199
  class ProcessedTemplate:
@@ -200,10 +201,14 @@ class ProcessedTemplate:
200
201
  self,
201
202
  resources: List[Dict[str, Any]],
202
203
  version_info: Optional[str],
204
+ metadata: Optional[List[str]] = None,
203
205
  ) -> None:
204
206
  self.resources = resources
205
207
  self.version_info = version_info
206
208
  self._rendered: Optional[bytes] = None
209
+ if metadata == None:
210
+ metadata = []
211
+ self.metadata = metadata
207
212
 
208
213
  @property
209
214
  def version(self) -> str:
@@ -295,9 +300,7 @@ class Resources(List[str]):
295
300
  """
296
301
 
297
302
  def __contains__(self, item: object) -> bool:
298
- if (
299
- len(self) == 0
300
- ): # TODO: refactor to remove overriding __contains__; its being used in multiple places
303
+ if len(self) == 0:
301
304
  return True
302
305
  return super().__contains__(item)
303
306
 
@@ -308,17 +311,13 @@ class Status(BaseModel):
308
311
  details: List[Any]
309
312
 
310
313
 
311
- def resources_factory() -> Resources:
312
- return Resources()
313
-
314
-
315
314
  class DiscoveryRequest(BaseModel):
316
315
  node: Node = Field(..., title="Node information about the envoy proxy")
317
316
  version_info: str = Field(
318
317
  "0", title="The version of the envoy clients current configuration"
319
318
  )
320
- resource_names: Resources = Field(
321
- default_factory=resources_factory, title="List of requested resource names"
319
+ resource_names: List[str] = Field(
320
+ default_factory=list, title="List of requested resource names"
322
321
  )
323
322
  hide_private_keys: bool = False
324
323
  type_url: Optional[str] = Field(
@@ -360,11 +359,6 @@ class DiscoveryRequest(BaseModel):
360
359
  self.desired_controlplane,
361
360
  )
362
361
 
363
- @field_validator("resource_names", mode="before")
364
- @classmethod
365
- def validate_resources(cls, v: Union[Resources, List[str]]) -> Resources:
366
- return Resources(v)
367
-
368
362
 
369
363
  class DiscoveryResponse(BaseModel):
370
364
  version_info: str = Field(
@@ -60,6 +60,8 @@
60
60
  </p>
61
61
  </div>
62
62
 
63
+ {% set count = resources|length %}
64
+ {% if count > 0 %}
63
65
  <nav class="panel is-dark" id="resources">
64
66
  <p class="panel-heading">
65
67
  {{ resource_type|capitalize }}
@@ -82,7 +84,6 @@
82
84
  </div>
83
85
 
84
86
  {% set res = resources|selectattr('get')|list %}
85
- {% set count = res|length %}
86
87
  {% set plural = {
87
88
  0: 'resources',
88
89
  1: 'resource'
@@ -95,7 +96,9 @@
95
96
  <div class="notification is-danger">
96
97
  {{ resource["sovereign_error"] }}
97
98
  </div>
99
+ <pre>
98
100
  {{ resource["sovereign_error"] }}
101
+ </pre>
99
102
  {% else %}
100
103
  {% set name = resource.get('name') or resource['cluster_name'] %}
101
104
  <a class="panel-block has-text-weight-medium"
@@ -107,59 +110,84 @@
107
110
  </a>
108
111
  {% endif %}
109
112
  {% endfor %}
110
- <div class="panel-block">
111
- <p class="content is-small">
112
- {{ count }} {{ plural.get(count, 'resources') }}
113
- </p>
114
- </div>
115
- </nav>
116
-
117
- {% if resource_type == 'routes' %}
118
- <nav class="panel" id="virtual_hosts">
119
- <p class="panel-heading">
120
- Virtual Hosts
121
- </p>
122
113
  <div class="panel-block">
123
- <p class="control has-icons-left">
124
- <label for="search_filter_virtual_hosts">
125
- <input
126
- class="input"
127
- type="text"
128
- id="search_filter_virtual_hosts"
129
- onkeyup="filter_results('search_filter_virtual_hosts', 'virtual_hosts')"
130
- placeholder="Filter resources by any string"
131
- >
132
- </label>
133
- <span class="icon is-left">
134
- <i class="fas fa-search" aria-hidden="true"></i>
135
- </span>
114
+ <p class="content is-small">
115
+ {{ count }} {{ plural.get(count, 'resources') }}
136
116
  </p>
137
117
  </div>
118
+ </nav>
138
119
 
139
- {% set vs_count = 0 %}
120
+ {% if resource_type == 'routes' %}
140
121
  {% for resource in res %}
141
- {% set vs_len = resource['virtual_hosts']|length %}
142
- {% set vs_count = vs_count + vs_len %}
143
- {% for virtualhost in resource['virtual_hosts'] %}
144
- {% if loop.first and loop.last %}
145
- {# A single virtualhost makes no sense to render. It will be in one of the routes above. #}
146
- {% else %}
122
+ <nav class="panel" id="virtual_hosts">
123
+ <p class="panel-heading">
124
+ Virtual Hosts (Route Configuration: {{ resource["name"] }})
125
+ </p>
126
+ <div class="panel-block">
127
+ <p class="control has-icons-left">
128
+ <label for="search_filter_virtual_hosts">
129
+ <input
130
+ class="input"
131
+ type="text"
132
+ id="search_filter_virtual_hosts"
133
+ onkeyup="filter_results('search_filter_virtual_hosts', 'virtual_hosts')"
134
+ placeholder="Filter resources by any string"
135
+ >
136
+ </label>
137
+ <span class="icon is-left">
138
+ <i class="fas fa-search" aria-hidden="true"></i>
139
+ </span>
140
+ </p>
141
+ </div>
142
+
143
+ {% for virtualhost in resource['virtual_hosts'] %}
147
144
  <a class="panel-block"
148
145
  href="/ui/resources/routes/{{ resource['name'] }}/{{ virtualhost['name'] }}">
149
- <span class="panel-icon">
150
- <i class="fas fa-arrow-right" aria-hidden="true"></i>
151
- </span>
146
+ <span class="panel-icon">
147
+ <i class="fas fa-arrow-right" aria-hidden="true"></i>
148
+ </span>
152
149
  {{ virtualhost['name'] }}
153
150
  </a>
154
- {% endif %}
155
- {% endfor %}
151
+ {% endfor %}
152
+ </nav>
156
153
  {% endfor %}
157
- <div class="panel-block">
158
- <p class="content is-small">
159
- {{ vs_count }} {{ plural.get(vs_count, 'resources') }}
160
- </p>
161
- </div>
162
- </nav>
154
+ {% endif %}
155
+ {% else %}
156
+ <span class="panel-icon">
157
+ <i class="fas fa-arrow-right" aria-hidden="true"></i>
158
+ </span>
159
+ <div class="notification is-danger">
160
+ No resources found
161
+ </div>
162
+ {% endif %}
163
+ {% if show_debuginfo %}
164
+ <pre>
165
+ {%- if discovery_request != None %}
166
+ ---
167
+ DiscoveryRequest:
168
+ Node:
169
+ Id: {{ discovery_request.node.id }}
170
+ Cluster: {{ discovery_request.node.cluster }}
171
+ Metadata: {{ discovery_request.node.metadata }}
172
+ Locality: {{ discovery_request.node.locality }}
173
+ Build Version: {{ discovery_request.node.build_version }}
174
+ Version Info: {{ discovery_request.version_info }}
175
+ Resource Names: {{ discovery_request.resources or "*" }}
176
+ # Sovereign-generated fields
177
+ Envoy Version: {{ discovery_request.envoy_version }}
178
+ Client Uid: {{ discovery_request.uid }}
179
+ Host Header: {{ discovery_request.desired_controlplane }}
180
+ {%- endif %}
181
+ {%- if discovery_response != None %}
182
+ ---
183
+ DiscoveryResponse:
184
+ Metadata:
185
+ {%- for line in discovery_response.metadata %}
186
+ {{ line }}
187
+ {%- endfor %}
188
+ Config version: {{ discovery_response.version }}
189
+ {%- endif %}
190
+ </pre>
163
191
  {% endif %}
164
192
  {% endblock -%}
165
193
  {% block footer %}
sovereign/utils/mock.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from typing import Optional, Dict, List
2
2
  from random import randint
3
- from sovereign.schemas import DiscoveryRequest, Node, Locality, Resources, Status
3
+ from sovereign.schemas import DiscoveryRequest, Node, Locality, Status
4
4
 
5
5
 
6
6
  def mock_discovery_request(
@@ -12,9 +12,7 @@ def mock_discovery_request(
12
12
  error_message: Optional[str] = None,
13
13
  ) -> DiscoveryRequest:
14
14
  if resource_names is None:
15
- resource_names = Resources()
16
- else:
17
- resource_names = Resources(resource_names)
15
+ resource_names = []
18
16
  request = DiscoveryRequest(
19
17
  type_url=None,
20
18
  node=Node(
sovereign/views/admin.py CHANGED
@@ -6,7 +6,6 @@ from fastapi.responses import JSONResponse
6
6
  from fastapi.encoders import jsonable_encoder
7
7
  from sovereign import config, stats, poller, template_context
8
8
  from sovereign.discovery import select_template
9
- from sovereign.schemas import Resources
10
9
  from sovereign.utils.mock import mock_discovery_request
11
10
  from sovereign.views.discovery import perform_discovery
12
11
 
@@ -32,7 +31,7 @@ async def display_config(
32
31
  ret: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
33
32
  mock_request = mock_discovery_request(
34
33
  service_cluster=service_cluster,
35
- resource_names=Resources(resource_names),
34
+ resource_names=resource_names,
36
35
  version=version,
37
36
  region=region,
38
37
  )
@@ -123,18 +123,18 @@ async def perform_discovery(
123
123
  api_version,
124
124
  resource_type,
125
125
  req.envoy_version,
126
- req.resource_names,
126
+ req.resources,
127
127
  req.desired_controlplane,
128
128
  req.hide_private_keys,
129
129
  req.type_url,
130
130
  req.node.cluster,
131
131
  req.node.locality,
132
+ # TODO: this is very bad and everyone should feel bad. Remove this in the next breaking release
132
133
  req.node.metadata.get("auth", None),
133
134
  req.node.metadata.get("num_cpus", None),
134
- ] + extra_metadata
135
-
135
+ ]
136
+ hash_keys += extra_metadata
136
137
  cache_key = compute_hash(hash_keys)
137
-
138
138
  if template := await cache.get(key=cache_key, default=None):
139
139
  logs.access_logger.queue_log_fields(CACHE_XDS_HIT=True)
140
140
  return template # type: ignore[no-any-return]
@@ -89,16 +89,18 @@ async def resources(
89
89
  envoy_version: str = Cookie(
90
90
  "__any__", title="The clients envoy version to emulate in this XDS request"
91
91
  ),
92
+ debug: str = Query(0, title="Show debug information on errors"),
92
93
  ) -> HTMLResponse:
93
94
  ret: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
95
+ response = None
96
+ mock_request = mock_discovery_request(
97
+ service_cluster=service_cluster,
98
+ version=envoy_version,
99
+ region=region,
100
+ )
94
101
  try:
95
102
  response = await perform_discovery(
96
- req=mock_discovery_request(
97
- service_cluster=service_cluster,
98
- resource_names=[],
99
- version=envoy_version,
100
- region=region,
101
- ),
103
+ req=mock_request,
102
104
  api_version=api_version,
103
105
  resource_type=xds_type,
104
106
  skip_auth=True,
@@ -106,12 +108,15 @@ async def resources(
106
108
  except KeyError as e:
107
109
  ret["resources"] = [{"sovereign_error": str(e)}]
108
110
  else:
109
- ret["resources"] += response.deserialize_resources()
111
+ ret["resources"] = response.deserialize_resources()
110
112
  return html_templates.TemplateResponse(
111
113
  request=request,
112
114
  name="resources.html",
113
115
  media_type="text/html",
114
116
  context={
117
+ "show_debuginfo": True if debug else False,
118
+ "discovery_response": response,
119
+ "discovery_request": mock_request,
115
120
  "resources": ret["resources"],
116
121
  "resource_type": xds_type,
117
122
  "all_types": all_types,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sovereign
3
- Version: 0.29.1
3
+ Version: 0.29.3
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
@@ -38,7 +38,7 @@ Requires-Dist: cachelib (>=0.10.2,<0.11.0)
38
38
  Requires-Dist: cachetools (>=5.3.2,<6.0.0)
39
39
  Requires-Dist: cashews[redis] (>=6.3.0,<7.0.0) ; extra == "caching"
40
40
  Requires-Dist: croniter (>=1.4.1,<2.0.0)
41
- Requires-Dist: cryptography (>=43.0.1,<44.0.0)
41
+ Requires-Dist: cryptography (>=42.0.0,<43.0.0)
42
42
  Requires-Dist: datadog (>=0.47.0,<0.48.0) ; extra == "statsd"
43
43
  Requires-Dist: fastapi (>=0.110.0,<0.111.0)
44
44
  Requires-Dist: glom (>=23.3.0,<24.0.0)
@@ -3,7 +3,7 @@ sovereign/app.py,sha256=udDhuprAcJdYNgXufl94-oh-G74H9hXVzr8cKhgdQYI,4087
3
3
  sovereign/configuration.py,sha256=BCezlWYIpTsFRZwQIBwU-XrfBk1MdjTKMLA8huN-VPg,2484
4
4
  sovereign/constants.py,sha256=qdWD1lTvkaW5JGF7TmZhfksQHlRAJFVqbG7v6JQA9k8,46
5
5
  sovereign/context.py,sha256=Vwr-Jmo1I_05rFraxv6GoYEC83oFl87LG16px4R7IfA,6461
6
- sovereign/discovery.py,sha256=KHnWXUlceUglbR9eV5Q6LjPOpGv2dcrGOSus9-QLQJM,5952
6
+ sovereign/discovery.py,sha256=LrgrojgehyZIObZl4wICi_s5PHwIl73wSym3tSNBkfI,6409
7
7
  sovereign/dynamic_config/__init__.py,sha256=QoRNcuCAqV26zeyHm0iavsR55K3TwMohabWpPGIq_rM,2838
8
8
  sovereign/dynamic_config/deser.py,sha256=CYTP9UNx8falCXU_bEaWGNatyQlYrV4T57NPXNhTn0o,1842
9
9
  sovereign/dynamic_config/loaders.py,sha256=HxDT-6hlqg_ewPjrFu2RaWi6O1mmJ_Mpnu8AQk_enNg,2923
@@ -17,7 +17,7 @@ sovereign/middlewares.py,sha256=UoLdfhqMj_E6jXgtr-n0maQIBYe9n95s3BwaQZfebHo,3097
17
17
  sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  sovereign/modifiers/lib.py,sha256=DbXsxrrjnFE4Y7rbwpeiM5tS5w5NBwSdYH58AtDTP0I,2884
19
19
  sovereign/response_class.py,sha256=beMAFV-4L6DwyWzJzy71GkEW4gb7fzH1jd8-Tul13cU,427
20
- sovereign/schemas.py,sha256=nkpMVLwdtODmyfbwu5PEExxFKFLY3H0Oj4mqB9HJnfI,31552
20
+ sovereign/schemas.py,sha256=NfbBub6Fnozrb-cQNwchwIH0FRlVozycOhmm6Vyqpb4,31286
21
21
  sovereign/server.py,sha256=z8Uz1UYIZix0S40Srk774WIMDN2jl2SozO8irib0wc4,1402
22
22
  sovereign/sources/__init__.py,sha256=g9hEpFk8j5i1ApHQpbc9giTyJW41Ppgsqv5P9zGxOJk,78
23
23
  sovereign/sources/file.py,sha256=prUThsDCSPNwZaZpkKXhAm-GVRZWbBoGKGU0It4HHXs,690
@@ -29,7 +29,7 @@ sovereign/static/style.css,sha256=vG8HPsbCbPIZfHgy7gSeof97Pnp0okkyaXyJzIEEW-8,44
29
29
  sovereign/statistics.py,sha256=Xfj4oWMfCkbYc2ibF7rDUpbw6Zw6dI4N5BpCLDQc4j4,2336
30
30
  sovereign/templates/base.html,sha256=5vw3-NmN291pXRdArpCwhSce9bAYBWCJVRhvO5EmE9g,2296
31
31
  sovereign/templates/err.html,sha256=a3cEzOqyqWOIe3YxfTEjkxbTfxBxq1knD6GwzEFljfs,603
32
- sovereign/templates/resources.html,sha256=r67x1FeUgczh-0JlwFFu-qG_b2u9t-_P_If9jlKg2ZI,6832
32
+ sovereign/templates/resources.html,sha256=_mcqw3POMEI0uWR4xF1QlCsdmzgWheYT5zklJhHdPqE,7857
33
33
  sovereign/templates/ul_filter.html,sha256=LrzZv5408Qq5UP4lcHVRwY2G6lXd3IiSNiJn1aH7Yqo,666
34
34
  sovereign/testing/loaders.py,sha256=mcmErhI9ZkJUBZl8jv2qP-PCBRFeAIgyBFlfCgU4Vvk,199
35
35
  sovereign/testing/modifiers.py,sha256=7_c2hWXn_sYJ6997N1_uSWtClOikcOzu1yRCY56-l-4,361
@@ -46,20 +46,20 @@ sovereign/utils/crypto/suites/fernet_cipher.py,sha256=rP6M5ys1vctyadOxDGNFoyerWP
46
46
  sovereign/utils/dictupdate.py,sha256=JkDjg16u7sW6A_4Q2oX1PY_MtJU7m1VivZWn9VLZ9V8,2559
47
47
  sovereign/utils/eds.py,sha256=sCEDj1y-0Crs40cHZLiPGVb7ed1f8vFqgHLY5R2LMbw,4377
48
48
  sovereign/utils/entry_point_loader.py,sha256=BEVodk-um70RvT1nSOu_IB-hr1K4ppthXod0VZEiZJ8,526
49
- sovereign/utils/mock.py,sha256=s2LS8RzGjwIqsgDPQnpQs6W39hehq88II0dOefvoq0w,1342
49
+ sovereign/utils/mock.py,sha256=M4hvNKSS3c4wYwHgVaHtwMspoufzZR4rCx_3TB-HD4U,1261
50
50
  sovereign/utils/resources.py,sha256=rPrWgcIt4YhV-Dz88_kr5WrQNiSKt-jTlOZ8EIJxJx8,472
51
51
  sovereign/utils/templates.py,sha256=FE_H_oE7VrS3X_VN1z_g10b9-rpmi1_gL-cMxi5XtXU,1057
52
52
  sovereign/utils/timer.py,sha256=_dUtEasj0BKbWYuQ_T3HFIyjurXXj-La-dNSMAwKMSo,795
53
53
  sovereign/utils/version_info.py,sha256=vbAiUyz6v3-zSOoS-7HwrvJie729RgIKy0Bt091Z6RE,349
54
54
  sovereign/utils/weighted_clusters.py,sha256=bPzuRE7Qgvv04HcR2AhMDvBrFlZ8AfteweLKhY9SvWg,1166
55
55
  sovereign/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- sovereign/views/admin.py,sha256=9jUI3YqaU42AtzCCOCKDcfj_2JXoaMU6eOAD6WYPjoI,4312
56
+ sovereign/views/admin.py,sha256=DlazEqI8P9M3h1llVUgRoAnUlmjYH3f1UZnrQmzLXI8,4261
57
57
  sovereign/views/crypto.py,sha256=o8NSyiUBy7v1pMOXt_1UBi68FNcGkXSlEVg9C18y8kY,3324
58
- sovereign/views/discovery.py,sha256=9TyXfS3nW7I7Bkeg-KdsTeCm1N9WSCvDj_pTAi1V4bo,6067
58
+ sovereign/views/discovery.py,sha256=BmsHsBe_N33_VahkLQda3CfuIqJZAHSA0Bndg9KagQM,6187
59
59
  sovereign/views/healthchecks.py,sha256=_WkMunlrFpqGTLgtNtRr7gCsDCv5kiuYxCyTi-dMEKM,1357
60
- sovereign/views/interface.py,sha256=TFXbYp5oXZPRkVnAo-NWQFBb8XMtB519FaV78ludCcI,7055
61
- sovereign-0.29.1.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
62
- sovereign-0.29.1.dist-info/METADATA,sha256=L-Jn-sqfELFxS3Pu-VuG7tM6ZHvt1-j3uyXhtH4MpC0,6607
63
- sovereign-0.29.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
64
- sovereign-0.29.1.dist-info/entry_points.txt,sha256=2mUHQjqeXEokMF6ZjDmvqQ9Fxk-Or2S4eC0h70ZxKmk,1201
65
- sovereign-0.29.1.dist-info/RECORD,,
60
+ sovereign/views/interface.py,sha256=MRn4HN7Ngo4H2Ha6LCPCimUqI9Q95XTtan0pzmvISus,7255
61
+ sovereign-0.29.3.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
62
+ sovereign-0.29.3.dist-info/METADATA,sha256=AivJbWTs_OkESWdv3x0BLvuGiIQDIm4AqMX3U1QadJ0,6607
63
+ sovereign-0.29.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
64
+ sovereign-0.29.3.dist-info/entry_points.txt,sha256=2mUHQjqeXEokMF6ZjDmvqQ9Fxk-Or2S4eC0h70ZxKmk,1201
65
+ sovereign-0.29.3.dist-info/RECORD,,