sovereign 1.0.0b125__py3-none-any.whl → 1.0.0b126__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/rendering.py +46 -29
- sovereign/static/node_expression.js +23 -0
- sovereign/templates/resources.html +10 -0
- sovereign/utils/mock.py +6 -2
- sovereign/views/interface.py +26 -9
- sovereign/worker.py +15 -28
- {sovereign-1.0.0b125.dist-info → sovereign-1.0.0b126.dist-info}/METADATA +1 -1
- {sovereign-1.0.0b125.dist-info → sovereign-1.0.0b126.dist-info}/RECORD +11 -11
- {sovereign-1.0.0b125.dist-info → sovereign-1.0.0b126.dist-info}/LICENSE.txt +0 -0
- {sovereign-1.0.0b125.dist-info → sovereign-1.0.0b126.dist-info}/WHEEL +0 -0
- {sovereign-1.0.0b125.dist-info → sovereign-1.0.0b126.dist-info}/entry_points.txt +0 -0
sovereign/rendering.py
CHANGED
|
@@ -7,6 +7,8 @@ Functions used to render and return discovery responses to Envoy proxies.
|
|
|
7
7
|
The templates are configurable. `todo See ref:Configuration#Templates`
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import threading
|
|
11
|
+
from multiprocessing import Process, Semaphore, cpu_count
|
|
10
12
|
from typing import Any, Dict, List
|
|
11
13
|
|
|
12
14
|
import yaml
|
|
@@ -27,6 +29,8 @@ from sovereign.schemas import (
|
|
|
27
29
|
ProcessedTemplate,
|
|
28
30
|
)
|
|
29
31
|
|
|
32
|
+
# limit render jobs to number of cores
|
|
33
|
+
RENDER_SEMAPHORE = Semaphore(cpu_count())
|
|
30
34
|
|
|
31
35
|
type_urls = {
|
|
32
36
|
"v2": {
|
|
@@ -54,39 +58,52 @@ class RenderJob(pydantic.BaseModel):
|
|
|
54
58
|
request: DiscoveryRequest
|
|
55
59
|
context: dict[str, Any]
|
|
56
60
|
|
|
61
|
+
def spawn(self):
|
|
62
|
+
t = threading.Thread(target=self._run)
|
|
63
|
+
t.start()
|
|
64
|
+
|
|
65
|
+
def _run(self):
|
|
66
|
+
with RENDER_SEMAPHORE:
|
|
67
|
+
proc = Process(target=generate, args=[self])
|
|
68
|
+
proc.start()
|
|
69
|
+
proc.join()
|
|
70
|
+
|
|
57
71
|
|
|
58
72
|
def generate(job: RenderJob) -> None:
|
|
59
73
|
request = job.request
|
|
60
74
|
tags = [f"type:{request.resource_type}"]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
try:
|
|
76
|
+
with stats.timed("template.render_ms", tags=tags):
|
|
77
|
+
content = request.template(
|
|
78
|
+
discovery_request=request,
|
|
79
|
+
host_header=request.desired_controlplane,
|
|
80
|
+
resource_names=request.resources,
|
|
81
|
+
**job.context,
|
|
82
|
+
)
|
|
83
|
+
if not request.template.is_python_source:
|
|
84
|
+
assert isinstance(content, str)
|
|
85
|
+
content = deserialize_config(content)
|
|
86
|
+
assert isinstance(content, dict)
|
|
87
|
+
resources = filter_resources(content["resources"], request.resources)
|
|
88
|
+
add_type_urls(request.api_version, request.resource_type, resources)
|
|
89
|
+
response = ProcessedTemplate(resources=resources)
|
|
90
|
+
cache.write(
|
|
91
|
+
job.id,
|
|
92
|
+
cache.Entry(
|
|
93
|
+
text=response.model_dump_json(indent=None),
|
|
94
|
+
len=len(response.resources),
|
|
95
|
+
version=response.version_info,
|
|
96
|
+
node=request.node,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
tags.append("result:ok")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
tags.append("result:err")
|
|
102
|
+
tags.append(f"error:{e.__class__.__name__.lower()}")
|
|
103
|
+
if SENTRY_INSTALLED and config.sentry_dsn:
|
|
104
|
+
sentry_sdk.capture_exception(e)
|
|
105
|
+
finally:
|
|
106
|
+
stats.increment("template.render", tags=tags)
|
|
90
107
|
|
|
91
108
|
|
|
92
109
|
def deserialize_config(content: str) -> Dict[str, Any]:
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
const input = document.getElementById('filterInput');
|
|
2
|
+
const inputMessage = document.getElementById('filterMessage');
|
|
2
3
|
const form = document.getElementById('filterForm');
|
|
3
4
|
|
|
5
|
+
function validateInput(inputString) {
|
|
6
|
+
if (!inputString || inputString.trim() === '') {
|
|
7
|
+
return "empty";
|
|
8
|
+
}
|
|
9
|
+
const validationRegex = /^(?:(?:id|cluster|metadata\.[\w\.\=\-]+|locality\.?(?:zone|sub_zone|region))=[a-zA-Z0-9_-]+ ?)*$/;
|
|
10
|
+
return validationRegex.test(inputString);
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
window.addEventListener('DOMContentLoaded', () => {
|
|
5
14
|
const match = document.cookie.match(/(?:^|; )node_expression=([^;]*)/);
|
|
6
15
|
if (match) {
|
|
@@ -8,6 +17,20 @@ window.addEventListener('DOMContentLoaded', () => {
|
|
|
8
17
|
}
|
|
9
18
|
});
|
|
10
19
|
|
|
20
|
+
input.addEventListener('input', (event) => {
|
|
21
|
+
const result = validateInput(event.target.value);
|
|
22
|
+
if (result === "empty") {
|
|
23
|
+
input.className = "input is-dark";
|
|
24
|
+
inputMessage.innerHTML = "";
|
|
25
|
+
} else if (result === true) {
|
|
26
|
+
input.className = "input is-success";
|
|
27
|
+
inputMessage.innerHTML = "";
|
|
28
|
+
} else {
|
|
29
|
+
input.className = "input is-danger";
|
|
30
|
+
inputMessage.innerHTML = "The node filter expression may have no effect, or be invalid";
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
11
34
|
form.addEventListener('submit', (event) => {
|
|
12
35
|
event.preventDefault();
|
|
13
36
|
const value = input.value.trim();
|
|
@@ -271,6 +271,14 @@
|
|
|
271
271
|
|
|
272
272
|
{%- block body %}
|
|
273
273
|
<div class="content">
|
|
274
|
+
{% if error %}
|
|
275
|
+
<span class="panel-icon">
|
|
276
|
+
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
|
277
|
+
</span>
|
|
278
|
+
<div class="notification is-danger">
|
|
279
|
+
{{ error }}
|
|
280
|
+
</div>
|
|
281
|
+
{% endif %}
|
|
274
282
|
<p class="content">
|
|
275
283
|
<div class="columns">
|
|
276
284
|
<div class="column is-narrow">
|
|
@@ -303,6 +311,7 @@
|
|
|
303
311
|
<form id="filterForm">
|
|
304
312
|
<input id="filterInput" class="input is-dark" type="text" placeholder="Node filter expression"/>
|
|
305
313
|
</form>
|
|
314
|
+
<p id="filterMessage" class="help is-danger"></p>
|
|
306
315
|
</div>
|
|
307
316
|
<div class="column is-narrow">
|
|
308
317
|
<div class="tooltip">
|
|
@@ -322,6 +331,7 @@
|
|
|
322
331
|
</div>
|
|
323
332
|
</div>
|
|
324
333
|
|
|
334
|
+
|
|
325
335
|
{% set count = resources|length %}
|
|
326
336
|
{% if count > 0 %}
|
|
327
337
|
<nav class="panel is-dark" id="resources">
|
sovereign/utils/mock.py
CHANGED
|
@@ -7,6 +7,10 @@ from sovereign.schemas import DiscoveryRequest, Node, Locality, Status
|
|
|
7
7
|
scrub = re.compile(r"[^a-zA-Z_\.]")
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
class NodeExpressionError(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
def mock_discovery_request(
|
|
11
15
|
api_version: Optional[str] = "V3",
|
|
12
16
|
resource_type: Optional[str] = None,
|
|
@@ -54,7 +58,7 @@ def set_node_expressions(node, expressions):
|
|
|
54
58
|
field, value = re.split(r"\s*=\s*", expr, maxsplit=1)
|
|
55
59
|
value = f'"{value}"'
|
|
56
60
|
except ValueError:
|
|
57
|
-
raise
|
|
61
|
+
raise NodeExpressionError(f"Invalid node filter format: {expr}")
|
|
58
62
|
|
|
59
63
|
field = scrub.sub("", field)
|
|
60
64
|
parts = field.split(".")
|
|
@@ -62,7 +66,7 @@ def set_node_expressions(node, expressions):
|
|
|
62
66
|
try:
|
|
63
67
|
value = ast.literal_eval(value)
|
|
64
68
|
except Exception as e:
|
|
65
|
-
raise
|
|
69
|
+
raise NodeExpressionError(f"Invalid node filter value: {value}") from e
|
|
66
70
|
|
|
67
71
|
current = node
|
|
68
72
|
for part in parts[:-1]:
|
sovereign/views/interface.py
CHANGED
|
@@ -10,7 +10,7 @@ from fastapi.responses import HTMLResponse, JSONResponse, Response
|
|
|
10
10
|
from sovereign import html_templates, cache
|
|
11
11
|
from sovereign.schemas import DiscoveryTypes, XDS_TEMPLATES
|
|
12
12
|
from sovereign.response_class import json_response_class
|
|
13
|
-
from sovereign.utils.mock import mock_discovery_request
|
|
13
|
+
from sovereign.utils.mock import NodeExpressionError, mock_discovery_request
|
|
14
14
|
|
|
15
15
|
router = APIRouter()
|
|
16
16
|
|
|
@@ -63,18 +63,31 @@ async def resources(
|
|
|
63
63
|
) -> HTMLResponse:
|
|
64
64
|
ret: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
65
65
|
response = None
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
try:
|
|
67
|
+
mock_request = mock_discovery_request(
|
|
68
|
+
api_version,
|
|
69
|
+
xds_type,
|
|
70
|
+
version=envoy_version,
|
|
71
|
+
region=region,
|
|
72
|
+
expressions=node_expression.split(),
|
|
73
|
+
)
|
|
74
|
+
clear_cookie = False
|
|
75
|
+
error = None
|
|
76
|
+
except NodeExpressionError as e:
|
|
77
|
+
mock_request = mock_discovery_request(
|
|
78
|
+
api_version,
|
|
79
|
+
xds_type,
|
|
80
|
+
version=envoy_version,
|
|
81
|
+
region=region,
|
|
82
|
+
)
|
|
83
|
+
clear_cookie = True
|
|
84
|
+
error = str(e)
|
|
85
|
+
|
|
73
86
|
response = await cache.blocking_read(mock_request)
|
|
74
87
|
if response:
|
|
75
88
|
ret["resources"] = json.loads(response.text).get("resources", [])
|
|
76
89
|
|
|
77
|
-
|
|
90
|
+
resp = html_templates.TemplateResponse(
|
|
78
91
|
request=request,
|
|
79
92
|
name="resources.html",
|
|
80
93
|
media_type="text/html",
|
|
@@ -87,8 +100,12 @@ async def resources(
|
|
|
87
100
|
"all_types": all_types,
|
|
88
101
|
"version": envoy_version,
|
|
89
102
|
"available_versions": list(XDS_TEMPLATES.keys()),
|
|
103
|
+
"error": error,
|
|
90
104
|
},
|
|
91
105
|
)
|
|
106
|
+
if clear_cookie:
|
|
107
|
+
resp.delete_cookie("node_expression", path="/ui/resources/")
|
|
108
|
+
return resp
|
|
92
109
|
|
|
93
110
|
|
|
94
111
|
@router.get(
|
sovereign/worker.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Optional, final
|
|
3
|
-
from multiprocessing import Process, cpu_count
|
|
4
3
|
from contextlib import asynccontextmanager
|
|
5
4
|
|
|
6
5
|
from fastapi import FastAPI, Body
|
|
@@ -61,7 +60,6 @@ class RenderQueue:
|
|
|
61
60
|
|
|
62
61
|
|
|
63
62
|
ONDEMAND = RenderQueue()
|
|
64
|
-
RENDER_SEMAPHORE = asyncio.Semaphore(cpu_count())
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
def hidden_field(*args, **kwargs):
|
|
@@ -99,42 +97,23 @@ if config.sources is not None:
|
|
|
99
97
|
context_middleware.append(poller.add_to_context)
|
|
100
98
|
|
|
101
99
|
|
|
102
|
-
def render(job: rendering.RenderJob):
|
|
103
|
-
log.debug(f"Spawning render process for {job.id}")
|
|
104
|
-
process = Process(target=rendering.generate, args=[job])
|
|
105
|
-
process.start()
|
|
106
|
-
return process
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
async def submit_render(job: rendering.RenderJob):
|
|
110
|
-
async with RENDER_SEMAPHORE:
|
|
111
|
-
process = render(job)
|
|
112
|
-
# Wait for the process to complete to ensure semaphore is held
|
|
113
|
-
# until the actual rendering work is done
|
|
114
|
-
await asyncio.get_event_loop().run_in_executor(None, process.join)
|
|
115
|
-
|
|
116
|
-
|
|
117
100
|
async def render_on_event():
|
|
118
101
|
while True:
|
|
119
102
|
# block forever until new context arrives
|
|
120
|
-
await NEW_CONTEXT.wait()
|
|
103
|
+
_ = await NEW_CONTEXT.wait()
|
|
121
104
|
log.debug("New context detected, re-rendering templates")
|
|
122
105
|
try:
|
|
123
106
|
if registered := cache.clients():
|
|
124
107
|
log.debug("New context detected, re-rendering templates")
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
size = len(registered)
|
|
109
|
+
stats.increment("template.render_on_event", tags=[f"batch_size:{size}"])
|
|
110
|
+
for client, request in registered:
|
|
111
|
+
job = rendering.RenderJob(
|
|
127
112
|
id=client,
|
|
128
113
|
request=request,
|
|
129
114
|
context=template_context.get_context(request),
|
|
130
115
|
)
|
|
131
|
-
|
|
132
|
-
]
|
|
133
|
-
tasks = [submit_render(job) for job in jobs]
|
|
134
|
-
size = len(tasks)
|
|
135
|
-
stats.increment("template.render_on_event", tags=[f"batch_size:{size}"])
|
|
136
|
-
await asyncio.gather(*tasks)
|
|
137
|
-
log.debug(f"Completed rendering {size} jobs")
|
|
116
|
+
job.spawn()
|
|
138
117
|
finally:
|
|
139
118
|
NEW_CONTEXT.clear()
|
|
140
119
|
|
|
@@ -147,16 +126,24 @@ async def render_on_demand():
|
|
|
147
126
|
job = rendering.RenderJob(
|
|
148
127
|
id=id, request=request, context=template_context.get_context(request)
|
|
149
128
|
)
|
|
150
|
-
|
|
129
|
+
job.spawn()
|
|
151
130
|
ONDEMAND.task_done()
|
|
152
131
|
|
|
153
132
|
|
|
133
|
+
async def monitor_render_queue():
|
|
134
|
+
"""Periodically report render queue size metrics"""
|
|
135
|
+
while True:
|
|
136
|
+
await asyncio.sleep(10)
|
|
137
|
+
stats.gauge("template.on_demand_queue_size", ONDEMAND._queue.qsize())
|
|
138
|
+
|
|
139
|
+
|
|
154
140
|
@asynccontextmanager
|
|
155
141
|
async def lifespan(_: FastAPI):
|
|
156
142
|
# Template Rendering
|
|
157
143
|
log.debug("Starting rendering loops")
|
|
158
144
|
asyncio.create_task(render_on_event())
|
|
159
145
|
asyncio.create_task(render_on_demand())
|
|
146
|
+
asyncio.create_task(monitor_render_queue())
|
|
160
147
|
|
|
161
148
|
# Template context
|
|
162
149
|
log.debug("Starting context loop")
|
|
@@ -15,7 +15,7 @@ sovereign/logging/types.py,sha256=rGqJAEVvgvzHy4aPfvEH6yQ-yblXNkEcWG7G8l9ALEA,28
|
|
|
15
15
|
sovereign/middlewares.py,sha256=6w4JpvtNGvQA4rocQsYQjuu-ckhpKT6gKYA16T-kiqA,3082
|
|
16
16
|
sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
sovereign/modifiers/lib.py,sha256=Cx0VrpTKbSjb3YmHyG4Jy6YEaPlrwpeqNaom3zu1_hw,2885
|
|
18
|
-
sovereign/rendering.py,sha256=
|
|
18
|
+
sovereign/rendering.py,sha256=hxlu_K7h1fVNYRLVA4-YM_KY4rhaEOzHq-4dCVwgdWE,5688
|
|
19
19
|
sovereign/response_class.py,sha256=beMAFV-4L6DwyWzJzy71GkEW4gb7fzH1jd8-Tul13cU,427
|
|
20
20
|
sovereign/schemas.py,sha256=Jf_w2mAX1b31eCAtJuxSZgy9KMVRKntmnEbbCZGZCxQ,38067
|
|
21
21
|
sovereign/server.py,sha256=uviXN0mX-Wbhv36xjWlHiybzIlZrJsCwRf4xyRnibXQ,2970
|
|
@@ -24,14 +24,14 @@ 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
26
|
sovereign/sources/poller.py,sha256=zpNUhQft-NoJbbxO1kCFp6jJSRSkBmf181xodnF_TiI,18469
|
|
27
|
-
sovereign/static/node_expression.js,sha256=
|
|
27
|
+
sovereign/static/node_expression.js,sha256=TXN9TAyutktR8IUrLbtaqupigKQ-_AhBoulS4KKrEjI,1311
|
|
28
28
|
sovereign/static/panel.js,sha256=i5mGExjv-I4Gtt9dQiTyFwPZa8pg5rXeuTeidXNUiTE,2695
|
|
29
29
|
sovereign/static/sass/style.scss,sha256=tPHPEm3sZeBFGDyyn3pHcA-nbaKT-h-UsSTsf6dHNDU,1158
|
|
30
30
|
sovereign/static/style.css,sha256=vG8HPsbCbPIZfHgy7gSeof97Pnp0okkyaXyJzIEEW-8,447517
|
|
31
31
|
sovereign/statistics.py,sha256=QhDB0bs5kZDGjy248AOIv_bzNbz_c2U7xmJ0hoUNOmw,2033
|
|
32
32
|
sovereign/templates/base.html,sha256=5vw3-NmN291pXRdArpCwhSce9bAYBWCJVRhvO5EmE9g,2296
|
|
33
33
|
sovereign/templates/err.html,sha256=a3cEzOqyqWOIe3YxfTEjkxbTfxBxq1knD6GwzEFljfs,603
|
|
34
|
-
sovereign/templates/resources.html,sha256=
|
|
34
|
+
sovereign/templates/resources.html,sha256=dDjpUHgZ13Su8sCG2Jxz0EJ_hrc3ei8SGqMVx7MvXX0,40527
|
|
35
35
|
sovereign/testing/loaders.py,sha256=mcmErhI9ZkJUBZl8jv2qP-PCBRFeAIgyBFlfCgU4Vvk,199
|
|
36
36
|
sovereign/testing/modifiers.py,sha256=7_c2hWXn_sYJ6997N1_uSWtClOikcOzu1yRCY56-l-4,361
|
|
37
37
|
sovereign/tracing.py,sha256=Xo3npgh6yesACSlynv9j6qnXxvYEBzXv5LL4Zkc1QDw,2446
|
|
@@ -47,7 +47,7 @@ sovereign/utils/crypto/suites/fernet_cipher.py,sha256=rP6M5ys1vctyadOxDGNFoyerWP
|
|
|
47
47
|
sovereign/utils/dictupdate.py,sha256=Bi7QaC7en-k3EOepwNJqpOKRNBgp6ZsBZVOvH_0nMtc,2558
|
|
48
48
|
sovereign/utils/eds.py,sha256=sCEDj1y-0Crs40cHZLiPGVb7ed1f8vFqgHLY5R2LMbw,4377
|
|
49
49
|
sovereign/utils/entry_point_loader.py,sha256=BEVodk-um70RvT1nSOu_IB-hr1K4ppthXod0VZEiZJ8,526
|
|
50
|
-
sovereign/utils/mock.py,sha256=
|
|
50
|
+
sovereign/utils/mock.py,sha256=j9zaLT39MSuF7vGKvhGRJtrXSTU7WFB1rirUWWBGYm4,2388
|
|
51
51
|
sovereign/utils/resources.py,sha256=rPrWgcIt4YhV-Dz88_kr5WrQNiSKt-jTlOZ8EIJxJx8,472
|
|
52
52
|
sovereign/utils/templates.py,sha256=FE_H_oE7VrS3X_VN1z_g10b9-rpmi1_gL-cMxi5XtXU,1057
|
|
53
53
|
sovereign/utils/timer.py,sha256=_dUtEasj0BKbWYuQ_T3HFIyjurXXj-La-dNSMAwKMSo,795
|
|
@@ -58,10 +58,10 @@ sovereign/views/api.py,sha256=jKVjSvi0Tr1HHix3hc0H8yGZoyDind2sJ4w7a4pJvy0,2168
|
|
|
58
58
|
sovereign/views/crypto.py,sha256=7y0eHWtt-bbr2CwHEkH7odPaJ1IEviU-71U-MYJD0Kc,3360
|
|
59
59
|
sovereign/views/discovery.py,sha256=B_D1ckfbN1dSKBvuFCTyfB79GUUriCADTB53OwZ8D4Q,2409
|
|
60
60
|
sovereign/views/healthchecks.py,sha256=TaXbxkX679jyQ8v5FxtBa2Qa0Z7KuqQ10WgAqfuVGUc,1743
|
|
61
|
-
sovereign/views/interface.py,sha256=
|
|
62
|
-
sovereign/worker.py,sha256=
|
|
63
|
-
sovereign-1.0.
|
|
64
|
-
sovereign-1.0.
|
|
65
|
-
sovereign-1.0.
|
|
66
|
-
sovereign-1.0.
|
|
67
|
-
sovereign-1.0.
|
|
61
|
+
sovereign/views/interface.py,sha256=KQKf9qOaVc_zWOFQ1WXEJKlTEhdQO4fQhIu5c_vCdPw,6843
|
|
62
|
+
sovereign/worker.py,sha256=Y4le54cZU1Enj8scu1G1sM_KPVPUmZ7vmTuxpd59X2Q,5855
|
|
63
|
+
sovereign-1.0.0b126.dist-info/LICENSE.txt,sha256=2X125zvAb9AYLjCgdMDQZuufhm0kwcg31A8pGKj_-VY,560
|
|
64
|
+
sovereign-1.0.0b126.dist-info/METADATA,sha256=wxIU-o096f2VZKm70ZrYp24K7Yr_RrD0evUr7aBcKbY,6304
|
|
65
|
+
sovereign-1.0.0b126.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
66
|
+
sovereign-1.0.0b126.dist-info/entry_points.txt,sha256=VKJdnnN_HNL8xYQMXsFXfFmN6QkdXMEk5S964avxQJI,1404
|
|
67
|
+
sovereign-1.0.0b126.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|