sovereign 0.14.2__py3-none-any.whl → 1.0.0a4__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.

Files changed (99) hide show
  1. sovereign/__init__.py +17 -78
  2. sovereign/app.py +74 -59
  3. sovereign/cache/__init__.py +245 -0
  4. sovereign/cache/backends/__init__.py +110 -0
  5. sovereign/cache/backends/s3.py +161 -0
  6. sovereign/cache/filesystem.py +74 -0
  7. sovereign/cache/types.py +17 -0
  8. sovereign/configuration.py +607 -0
  9. sovereign/constants.py +1 -0
  10. sovereign/context.py +271 -100
  11. sovereign/dynamic_config/__init__.py +112 -0
  12. sovereign/dynamic_config/deser.py +78 -0
  13. sovereign/dynamic_config/loaders.py +120 -0
  14. sovereign/error_info.py +61 -0
  15. sovereign/events.py +49 -0
  16. sovereign/logging/access_logger.py +85 -0
  17. sovereign/logging/application_logger.py +54 -0
  18. sovereign/logging/base_logger.py +41 -0
  19. sovereign/logging/bootstrapper.py +36 -0
  20. sovereign/logging/types.py +10 -0
  21. sovereign/middlewares.py +8 -7
  22. sovereign/modifiers/lib.py +2 -1
  23. sovereign/rendering.py +124 -0
  24. sovereign/rendering_common.py +91 -0
  25. sovereign/response_class.py +18 -0
  26. sovereign/server.py +123 -28
  27. sovereign/statistics.py +19 -21
  28. sovereign/templates/base.html +59 -46
  29. sovereign/templates/resources.html +203 -102
  30. sovereign/testing/loaders.py +9 -0
  31. sovereign/{modifiers/test.py → testing/modifiers.py} +0 -2
  32. sovereign/tracing.py +103 -0
  33. sovereign/types.py +304 -0
  34. sovereign/utils/auth.py +27 -13
  35. sovereign/utils/crypto/__init__.py +0 -0
  36. sovereign/utils/crypto/crypto.py +135 -0
  37. sovereign/utils/crypto/suites/__init__.py +21 -0
  38. sovereign/utils/crypto/suites/aes_gcm_cipher.py +42 -0
  39. sovereign/utils/crypto/suites/base_cipher.py +21 -0
  40. sovereign/utils/crypto/suites/disabled_cipher.py +25 -0
  41. sovereign/utils/crypto/suites/fernet_cipher.py +29 -0
  42. sovereign/utils/dictupdate.py +3 -2
  43. sovereign/utils/eds.py +40 -22
  44. sovereign/utils/entry_point_loader.py +18 -0
  45. sovereign/utils/mock.py +60 -17
  46. sovereign/utils/resources.py +17 -0
  47. sovereign/utils/templates.py +4 -2
  48. sovereign/utils/timer.py +5 -3
  49. sovereign/utils/version_info.py +8 -0
  50. sovereign/utils/weighted_clusters.py +2 -1
  51. sovereign/v2/__init__.py +0 -0
  52. sovereign/v2/data/data_store.py +621 -0
  53. sovereign/v2/data/render_discovery_response.py +24 -0
  54. sovereign/v2/data/repositories.py +90 -0
  55. sovereign/v2/data/utils.py +33 -0
  56. sovereign/v2/data/worker_queue.py +273 -0
  57. sovereign/v2/jobs/refresh_context.py +117 -0
  58. sovereign/v2/jobs/render_discovery_job.py +145 -0
  59. sovereign/v2/logging.py +81 -0
  60. sovereign/v2/types.py +41 -0
  61. sovereign/v2/web.py +101 -0
  62. sovereign/v2/worker.py +199 -0
  63. sovereign/views/__init__.py +7 -0
  64. sovereign/views/api.py +82 -0
  65. sovereign/views/crypto.py +46 -15
  66. sovereign/views/discovery.py +52 -67
  67. sovereign/views/healthchecks.py +107 -20
  68. sovereign/views/interface.py +173 -117
  69. sovereign/worker.py +193 -0
  70. {sovereign-0.14.2.dist-info → sovereign-1.0.0a4.dist-info}/METADATA +81 -73
  71. sovereign-1.0.0a4.dist-info/RECORD +85 -0
  72. {sovereign-0.14.2.dist-info → sovereign-1.0.0a4.dist-info}/WHEEL +1 -1
  73. sovereign-1.0.0a4.dist-info/entry_points.txt +46 -0
  74. sovereign_files/__init__.py +0 -0
  75. sovereign_files/static/darkmode.js +51 -0
  76. sovereign_files/static/node_expression.js +42 -0
  77. sovereign_files/static/panel.js +76 -0
  78. sovereign_files/static/resources.css +246 -0
  79. sovereign_files/static/resources.js +642 -0
  80. sovereign_files/static/sass/style.scss +33 -0
  81. sovereign_files/static/style.css +16143 -0
  82. sovereign_files/static/style.css.map +1 -0
  83. sovereign/config_loader.py +0 -225
  84. sovereign/discovery.py +0 -175
  85. sovereign/logs.py +0 -131
  86. sovereign/schemas.py +0 -715
  87. sovereign/sources/__init__.py +0 -3
  88. sovereign/sources/file.py +0 -21
  89. sovereign/sources/inline.py +0 -38
  90. sovereign/sources/lib.py +0 -40
  91. sovereign/sources/poller.py +0 -298
  92. sovereign/static/sass/style.scss +0 -27
  93. sovereign/static/style.css +0 -13553
  94. sovereign/templates/ul_filter.html +0 -22
  95. sovereign/utils/crypto.py +0 -64
  96. sovereign/views/admin.py +0 -120
  97. sovereign-0.14.2.dist-info/LICENSE.txt +0 -13
  98. sovereign-0.14.2.dist-info/RECORD +0 -45
  99. sovereign-0.14.2.dist-info/entry_points.txt +0 -10
sovereign/worker.py ADDED
@@ -0,0 +1,193 @@
1
+ import asyncio
2
+ from contextlib import asynccontextmanager
3
+ from typing import final
4
+
5
+ from fastapi import Body, FastAPI
6
+
7
+ from sovereign import (
8
+ application_logger as log,
9
+ )
10
+ from sovereign import (
11
+ cache,
12
+ disabled_ciphersuite,
13
+ rendering,
14
+ server_cipher_container,
15
+ stats,
16
+ )
17
+ from sovereign.configuration import config
18
+ from sovereign.context import TemplateContext
19
+ from sovereign.events import Topic, bus
20
+ from sovereign.types import DiscoveryRequest, RegisterClientRequest
21
+
22
+
23
+ # noinspection PyUnusedLocal
24
+ def hidden_field(*args, **kwargs):
25
+ return "(value hidden)"
26
+
27
+
28
+ def inject_builtin_items(request, output):
29
+ output["__hide_from_ui"] = lambda v: v
30
+ output["crypto"] = server_cipher_container
31
+ if request.is_internal_request:
32
+ output["__hide_from_ui"] = hidden_field
33
+ output["crypto"] = disabled_ciphersuite
34
+
35
+
36
+ template_context = TemplateContext.from_config()
37
+ context_middleware = [inject_builtin_items]
38
+ template_context.middleware = context_middleware
39
+ writer = cache.CacheWriter()
40
+
41
+ ClientId = str
42
+ OnDemandJob = tuple[ClientId, DiscoveryRequest]
43
+
44
+
45
+ @final
46
+ class RenderQueue:
47
+ def __init__(self, maxsize: int = 0):
48
+ self._queue: asyncio.Queue[OnDemandJob] = asyncio.Queue(maxsize)
49
+ self._set: set[ClientId] = set()
50
+ self._lock = asyncio.Lock()
51
+
52
+ async def put(self, item: OnDemandJob):
53
+ cid = item[0]
54
+ async with self._lock:
55
+ if cid not in self._set:
56
+ await self._queue.put(item)
57
+ self._set.add(cid)
58
+
59
+ def put_nowait(self, item: OnDemandJob):
60
+ cid = item[0]
61
+ if cid in self._set:
62
+ return
63
+ if self._queue.full():
64
+ raise asyncio.QueueFull
65
+ self._queue.put_nowait(item)
66
+ self._set.add(cid)
67
+
68
+ async def get(self):
69
+ return await self._queue.get()
70
+
71
+ def full(self):
72
+ return self._queue.full()
73
+
74
+ async def task_done(self, cid):
75
+ async with self._lock:
76
+ self._set.remove(cid)
77
+ self._queue.task_done()
78
+
79
+
80
+ ONDEMAND = RenderQueue()
81
+
82
+
83
+ poller = None
84
+ if config.sources is not None:
85
+ if config.matching is not None:
86
+ matching_enabled = config.matching.enabled
87
+ node_key: str | None = config.matching.node_key
88
+ source_key: str | None = config.matching.source_key
89
+ else:
90
+ matching_enabled = False
91
+ node_key = None
92
+ source_key = None
93
+
94
+
95
+ async def render_on_event(ctx):
96
+ subscription = bus.subscribe(Topic.CONTEXT)
97
+ while True:
98
+ # block forever until new context arrives
99
+ event = await subscription.get()
100
+ context_name = event.metadata.get("name")
101
+
102
+ log.debug(event.message)
103
+ try:
104
+ if registered := writer.get_registered_clients():
105
+ size = len(registered)
106
+ stats.increment("template.render_on_event", tags=[f"batch_size:{size}"])
107
+
108
+ for client, request in registered:
109
+ if context_name in request.template.depends_on:
110
+ log.info(
111
+ f"Rendering template on-event for {request} because {context_name} was updated"
112
+ )
113
+ job = rendering.RenderJob(
114
+ id=client,
115
+ request=request,
116
+ context=ctx.get_context(request),
117
+ )
118
+ job.submit()
119
+
120
+ finally:
121
+ await asyncio.sleep(config.template_context.cooldown)
122
+
123
+
124
+ async def render_on_demand(ctx):
125
+ while True:
126
+ cid, request = await ONDEMAND.get()
127
+ stats.increment("template.render_on_demand")
128
+ log.debug(
129
+ f"Received on-demand request to render templates for {cid} ({request})"
130
+ )
131
+ job = rendering.RenderJob(
132
+ id=cid, request=request, context=ctx.get_context(request)
133
+ )
134
+ _ = job.submit()
135
+ await ONDEMAND.task_done(cid)
136
+
137
+
138
+ # noinspection PyProtectedMember
139
+ async def monitor_render_queue():
140
+ """Periodically report render queue size metrics"""
141
+ while True:
142
+ await asyncio.sleep(10)
143
+ stats.gauge("template.on_demand_queue_size", ONDEMAND._queue.qsize())
144
+
145
+
146
+ @asynccontextmanager
147
+ async def lifespan(_: FastAPI):
148
+ # Template Rendering
149
+ log.debug("Starting rendering loops")
150
+ asyncio.create_task(render_on_event(template_context))
151
+ asyncio.create_task(render_on_demand(template_context))
152
+ asyncio.create_task(monitor_render_queue())
153
+
154
+ # Template context
155
+ subscription = bus.subscribe(Topic.CONTEXT)
156
+ log.debug("Starting context loop")
157
+ asyncio.create_task(template_context.start())
158
+ event = await subscription.get()
159
+ log.debug(event.message)
160
+
161
+ log.debug("Worker lifespan initialized")
162
+ yield
163
+
164
+
165
+ worker = FastAPI(lifespan=lifespan)
166
+ if dsn := config.sentry_dsn.get_secret_value():
167
+ try:
168
+ # noinspection PyUnusedImports
169
+ import sentry_sdk
170
+
171
+ # noinspection PyUnusedImports
172
+ from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
173
+
174
+ sentry_sdk.init(dsn)
175
+ worker.add_middleware(SentryAsgiMiddleware) # type: ignore
176
+ except ImportError: # pragma: no cover
177
+ log.error("Sentry DSN configured but failed to attach to worker")
178
+
179
+
180
+ @worker.get("/health")
181
+ def health():
182
+ return "OK"
183
+
184
+
185
+ @worker.put("/client")
186
+ async def client_add(
187
+ registration: RegisterClientRequest = Body(...),
188
+ ):
189
+ log.info(f"Received registration: {registration.request}")
190
+ xds = registration.request
191
+ client_id, req = writer.register(xds)
192
+ ONDEMAND.put_nowait((client_id, req))
193
+ return "Registered", 200
@@ -1,13 +1,13 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: sovereign
3
- Version: 0.14.2
3
+ Version: 1.0.0a4
4
4
  Summary: Envoy Proxy control-plane written in Python
5
- Home-page: https://pypi.org/project/sovereign/
6
- License: Apache-2.0
7
- Keywords: envoy,envoyproxy,control-plane,management,server
8
- Author: Vasili Syrakis
9
- Author-email: vsyrakis@atlassian.com
10
- Requires-Python: >=3.8,<4.0
5
+ Project-URL: Homepage, https://pypi.org/project/sovereign/
6
+ Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
7
+ Project-URL: Documentation, https://developer.atlassian.com/platform/sovereign/
8
+ Author-email: Vasili Syrakis <vsyrakis@atlassian.com>
9
+ License-Expression: Apache-2.0
10
+ Keywords: control-plane,envoy,envoyproxy,management,server
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Environment :: No Input/Output (Daemon)
13
13
  Classifier: Intended Audience :: Developers
@@ -16,83 +16,103 @@ Classifier: Intended Audience :: System Administrators
16
16
  Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Natural Language :: English
18
18
  Classifier: Operating System :: POSIX :: Linux
19
- Classifier: Programming Language :: Python :: 3
20
19
  Classifier: Programming Language :: Python :: 3.8
21
20
  Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
22
23
  Classifier: Topic :: Internet :: Proxy Servers
24
+ Requires-Python: <4.0,>=3.11.0
25
+ Requires-Dist: aiofiles<24,>=23.2.1
26
+ Requires-Dist: cachelib<0.11,>=0.10.2
27
+ Requires-Dist: cachetools<6,>=5.3.2
28
+ Requires-Dist: croniter<2,>=1.4.1
29
+ Requires-Dist: cryptography>=45.0.2
30
+ Requires-Dist: fastapi<0.129,>=0.128.0
31
+ Requires-Dist: glom<24,>=23.3.0
32
+ Requires-Dist: h11<0.17,>=0.16.0
33
+ Requires-Dist: jinja2<4,>=3.1.2
34
+ Requires-Dist: jmespath<2,>=1.0.1
35
+ Requires-Dist: pydantic-settings<2.6.0
36
+ Requires-Dist: pydantic<3,>=2.7.2
37
+ Requires-Dist: pyyaml<7,>=6.0.1
38
+ Requires-Dist: requests<3,>=2.32.4
39
+ Requires-Dist: rich>=14.2.0
40
+ Requires-Dist: starlette-context<0.4,>=0.3.6
41
+ Requires-Dist: starlette<0.50,>=0.49.1
42
+ Requires-Dist: structlog<24,>=23.1.0
43
+ Requires-Dist: supervisor<5,>=4.2.5
44
+ Requires-Dist: uvicorn<0.24,>=0.23.2
45
+ Requires-Dist: uvloop<1.0,>0.19.0
23
46
  Provides-Extra: boto
47
+ Requires-Dist: boto3<2,>=1.28.62; extra == 'boto'
48
+ Provides-Extra: caching
49
+ Provides-Extra: httptools
50
+ Requires-Dist: httptools<0.7,>=0.6.0; extra == 'httptools'
24
51
  Provides-Extra: orjson
52
+ Requires-Dist: orjson<4,>=3.9.15; extra == 'orjson'
25
53
  Provides-Extra: sentry
54
+ Requires-Dist: sentry-sdk<3,>=2.14.0; extra == 'sentry'
26
55
  Provides-Extra: statsd
56
+ Requires-Dist: datadog>=0.50.1; extra == 'statsd'
27
57
  Provides-Extra: ujson
28
- Requires-Dist: Jinja2 (>=3.1.2,<4.0.0)
29
- Requires-Dist: PyYAML (>=5.4,<6.0)
30
- Requires-Dist: aiofiles (>=0.8.0,<0.9.0)
31
- Requires-Dist: boto3 (>=1.17.0,<2.0.0); extra == "boto"
32
- Requires-Dist: cachelib (>=0.1.1,<0.2.0)
33
- Requires-Dist: croniter (>=1.3.5,<2.0.0)
34
- Requires-Dist: cryptography (>=38.0.3,<39.0.0)
35
- Requires-Dist: datadog (>=0.39.0,<0.40.0); extra == "statsd"
36
- Requires-Dist: fastapi (>=0.78.0,<0.79.0)
37
- Requires-Dist: glom (>=20.11.0,<21.0.0)
38
- Requires-Dist: gunicorn (>=20.1.0,<21.0.0)
39
- Requires-Dist: orjson (>=3.4.7,<4.0.0); extra == "orjson"
40
- Requires-Dist: requests (>=2.28.1,<3.0.0)
41
- Requires-Dist: sentry-sdk (>=0.19.5,<0.20.0); extra == "sentry"
42
- Requires-Dist: structlog (==21.5.0)
43
- Requires-Dist: ujson (>=5.4.0,<6.0.0); extra == "ujson"
44
- Requires-Dist: uvicorn (>=0.18.2,<0.19.0)
45
- Requires-Dist: uvloop (>=0.16.0,<0.17.0)
46
- Project-URL: Documentation, https://vsyrakis.bitbucket.io/sovereign/docs/
47
- Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
58
+ Requires-Dist: ujson<6,>=5.8.0; extra == 'ujson'
48
59
  Description-Content-Type: text/markdown
49
60
 
50
61
  sovereign
51
62
  =========
52
63
 
64
+
53
65
  Mission statement
54
66
  -----------------
55
67
  This project implements a JSON control-plane based on the [envoy](https://envoyproxy.io) [data-plane-api](https://github.com/envoyproxy/data-plane-api)
56
68
 
57
- The purpose of `sovereign` is to supply downstream envoy proxies with
58
- configuration in near-realtime by responding to discovery requests.
69
+ The purpose of `sovereign` is to supply downstream envoy proxies with dynamic configuration.
70
+
59
71
 
60
72
  Mechanism of Operation
61
73
  ----------------------
62
- tl;dr version:
63
- ```
64
- * Polls HTTP/File/Other for data
65
- * (optional) Applies transforms to the data
66
- * Uses the data to generate Envoy configuration from templates
67
- ```
74
+ Sovereign allows you to define templates that represent each resource type
75
+ provided by Envoy. For example, clusters, routes, listeners, secrets,
76
+ extension_configs, etc.
68
77
 
69
- In a nutshell, Sovereign
70
- gathers contextual data (*"sources"* and *"template context"*),
71
- optionally applies transforms to that data (using *"modifiers"*) and finally
72
- uses the data to generate envoy configuration from either python code, or jinja2 templates.
78
+ In order to enrich the templates with data, Sovereign has ways of polling data
79
+ out-of-band which it then includes as variables that can be accessed within the
80
+ templates.
73
81
 
74
- This is performed in a semi-stateless way, where the only state is data cached in memory.
82
+ This allows Sovereign to provide configuration to Envoy that changes over time
83
+ depending on the data sources, without needing to redeploy the control-plane.
75
84
 
76
- Template context is intended to be statically configured, whereas *Sources*
77
- are meant to be dynamic - for example, fetching from an API, an S3 bucket,
78
- or a file that receives updates.
85
+ Sovereign provides some built-in ways of polling data (such as over HTTP, or
86
+ on-disk) but also exposes extension points, allowing you to write your own
87
+ plugins in Python.
79
88
 
80
- *Modifiers* can mutate the data retrieved from sources, just in case the data
81
- is in a less than favorable structure.
82
89
 
83
- Both modifiers and sources are pluggable, i.e. it's easy to write your own and
84
- plug them into Sovereign for your use-case.
90
+ Support
91
+ ------------
92
+ [Submit new issues here](https://bitbucket.org/atlassian/sovereign/issues/new)
85
93
 
86
- Currently, Sovereign supports only providing configuration to Envoy as JSON.
87
- That is to say, gRPC is not supported yet. Contributions in this area are highly
88
- appreciated!
94
+ If you're unable to submit an issue on Bitbucket, send an email to [vsyrakis@atlassian.com](mailto:vsyrakis@atlassian.com)
95
+
96
+
97
+ Release
98
+ ------------
99
+ See [RELEASE.md]
100
+
101
+
102
+ Roadmap
103
+ ------------
104
+ * Performance improvements
105
+ * Data persistence
106
+ * Push API (versus polling)
107
+ * Client for Sovereign
108
+ * gRPC
89
109
 
90
- The JSON configuration can be viewed in real-time with Sovereign's read-only web interface.
91
110
 
92
111
  Requirements
93
112
  ------------
94
113
  * Python 3.8+
95
114
 
115
+
96
116
  Installation
97
117
  ------------
98
118
  ```
@@ -101,62 +121,53 @@ pip install sovereign
101
121
 
102
122
  Documentation
103
123
  -------------
104
- [Read the docs here!](https://vsyrakis.bitbucket.io/sovereign/docs/)
105
-
106
- :new: Read-only user interface
107
- ------------------------
108
- Added in `v0.5.3`!
124
+ [Read the docs here!](https://developer.atlassian.com/platform/sovereign/)
109
125
 
110
- This interface allows you to browse the resources currently returned by Sovereign.
111
126
 
112
- ![Sovereign User Interface Screenshot](https://bitbucket.org/atlassian/sovereign/src/master/assets/sovereign_ui.png)
113
127
 
114
128
  Local development
115
129
  =================
116
130
 
131
+
117
132
  Requirements
118
133
  ------------
134
+ * uv
119
135
  * Docker
120
136
  * Docker-compose
121
137
 
138
+
122
139
  Installing dependencies for dev
123
140
  -------------------------------
124
- I recommend creating a virtualenv before doing any dev work
125
-
141
+ Dependencies and creation of virtualenv is handled by uv
126
142
  ```
127
- python3 -m venv venv
128
- source venv/bin/activate
129
- pip install -r requirements-dev.txt
143
+ uv sync
144
+ uv venv activate
145
+ uv run <command>
130
146
  ```
131
147
 
132
148
  Running locally
133
149
  ---------------
134
150
  Running the test env
135
-
136
151
  ```
137
152
  make run
138
153
  ```
139
154
 
140
155
  Running the test env daemonized
141
-
142
156
  ```
143
157
  make run-daemon
144
158
  ```
145
159
 
146
160
  Pylint
147
-
148
161
  ```
149
162
  make lint
150
163
  ```
151
164
 
152
165
  Unit tests
153
-
154
166
  ```
155
167
  make unit
156
168
  ```
157
169
 
158
170
  Acceptance tests
159
-
160
171
  ```
161
172
  make run-daemon acceptance
162
173
  ```
@@ -164,7 +175,6 @@ make run-daemon acceptance
164
175
 
165
176
  Contributors
166
177
  ============
167
-
168
178
  Pull requests, issues and comments welcome. For pull requests:
169
179
 
170
180
  * Add tests for new features and bug fixes
@@ -193,9 +203,7 @@ those contributing as an individual.
193
203
 
194
204
  License
195
205
  ========
196
-
197
206
  Copyright (c) 2018 Atlassian and others.
198
207
  Apache 2.0 licensed, see [LICENSE.txt](LICENSE.txt) file.
199
208
 
200
209
 
201
-
@@ -0,0 +1,85 @@
1
+ sovereign/__init__.py,sha256=D6-BdAO7EyeQ7KU3sl9oiBYsGXDBkc5UM7I1kkas1Eg,1069
2
+ sovereign/app.py,sha256=l7AO8PtHsiVIN6ybWKj0qgzPJ-cNX8q9_syLfIG9XHY,5117
3
+ sovereign/configuration.py,sha256=AqDOVc7eOgEsP8BOvW5UsMhbC2j2FR72Uios76gK6Ao,21177
4
+ sovereign/constants.py,sha256=qdWD1lTvkaW5JGF7TmZhfksQHlRAJFVqbG7v6JQA9k8,46
5
+ sovereign/context.py,sha256=nwLJ_gvXFoKZJMCt4NX3KRkM381XMq_DJfTedceToJQ,9076
6
+ sovereign/error_info.py,sha256=_xfwYCV10zpAa0XP_zz0FoGtjj-1S_f4IgOf78-6wcw,1487
7
+ sovereign/events.py,sha256=gSWxaEqRdYfA_y7iFEzQPPrg3N5jy9svpfHzn6kBdRk,1203
8
+ sovereign/middlewares.py,sha256=6w4JpvtNGvQA4rocQsYQjuu-ckhpKT6gKYA16T-kiqA,3082
9
+ sovereign/rendering.py,sha256=qKtO0BUoqRs_FqDGsw2o0_756Uj59aBhfeKRpNjFlQM,4103
10
+ sovereign/rendering_common.py,sha256=WvZ_vCHD3ODiAX3Hw6Q6GwTklH4a2BFY3-zwqDNnmjg,3395
11
+ sovereign/response_class.py,sha256=S-8xziWdWVk8jbde_2OdxV1XWdrw6EwInLLhFmaYgkA,427
12
+ sovereign/server.py,sha256=5JQJ72yyOA4rjVs4rFznwYvh7zFEsg48cTDBqCvmsQo,3650
13
+ sovereign/statistics.py,sha256=0bbL7eLGOC-_hOY_ilx2uV_VpSJTIYjBGvEztDlgQDs,2045
14
+ sovereign/tracing.py,sha256=xL1blm4u2gmahpkFKhwY-pA-hE976rDXi9QxaOdRnoI,2686
15
+ sovereign/types.py,sha256=j8w3tXfkm0FHcMP8WXcXd99D1YhJ-cIuc-oVV0LuBSc,10245
16
+ sovereign/worker.py,sha256=ecLm_LDnbJPVzQPLmBhF3J-cWpzPKo6H2HAncDNbneE,5615
17
+ sovereign/cache/__init__.py,sha256=bq5BPiO-oCpSmiQ7GoXDk3PckVqYNS2NADguWBcRzeQ,9536
18
+ sovereign/cache/filesystem.py,sha256=GQP34jhJP2JsaEQgnI4O80EuN6sdK7lnKbP37jrI4d8,2416
19
+ sovereign/cache/types.py,sha256=JOLrPI8X209Fndt5xtLtEWMB6Fh7cvnzwsEBAoBV06c,243
20
+ sovereign/cache/backends/__init__.py,sha256=3oKlNWuHd5KyETor1napzq6nIJv0KGbOEPUrIZf35So,3160
21
+ sovereign/cache/backends/s3.py,sha256=92YPCBW9t4wLNQuCiES29GJ36lUP1IWIJDMHWFGNJDA,5865
22
+ sovereign/dynamic_config/__init__.py,sha256=VU71zUxc6zwioX3zm9VTzj_XQl8ye6FJGPuna3BaieY,3546
23
+ sovereign/dynamic_config/deser.py,sha256=uJsv6vWEPb-dJ9PIDZ51J-KC8J-IikbTvUM8hmvKAyY,1834
24
+ sovereign/dynamic_config/loaders.py,sha256=hp8drF4jXaotO-7CGudgs_enkg63vrXW34nTzP4MHlY,2915
25
+ sovereign/logging/access_logger.py,sha256=W5GE0fOjkYTwdA8KVsbPzof_FcPfJ8kCOpnL_tEi9gw,2989
26
+ sovereign/logging/application_logger.py,sha256=DKrLVX8sl7ckGYCUlSJLwJ9K9nomNoLBitzhEj2s_K8,1806
27
+ sovereign/logging/base_logger.py,sha256=ScOzHs8Rt1RZaUZGvaJSAlDEjD0BxkD5sLKSm2GgM0I,1243
28
+ sovereign/logging/bootstrapper.py,sha256=9a13x8gDEH0fiDkpEkwxmQYzWmb8jqPJKx4QCgdT7II,1302
29
+ sovereign/logging/types.py,sha256=rGqJAEVvgvzHy4aPfvEH6yQ-yblXNkEcWG7G8l9ALEA,282
30
+ sovereign/modifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ sovereign/modifiers/lib.py,sha256=tck5rw6PNqLofBtm3GTRMmrfrV9eSj_Rwhn5tM3Q2UA,2885
32
+ sovereign/templates/base.html,sha256=MMhhvvClTixKibYfhXm8Ezx6ttu6Sqki44niciCPMO4,2990
33
+ sovereign/templates/err.html,sha256=a3cEzOqyqWOIe3YxfTEjkxbTfxBxq1knD6GwzEFljfs,603
34
+ sovereign/templates/resources.html,sha256=5MfXHW8s3tAWda66Q48zVgDhZNLwHGsdCKkKHLZohIs,10420
35
+ sovereign/testing/loaders.py,sha256=JEjoxPSy0O-SUowW1U7P_BX6ABuqrmUrCuAxsUZaMos,200
36
+ sovereign/testing/modifiers.py,sha256=YI0aJOEbb1C_1T5VDrZKyLopk8JiIlKtUtxXMFfsCtk,290
37
+ sovereign/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ sovereign/utils/auth.py,sha256=e9lMNo8KqScTBITz4upQ0qL8A-4DTl-ybCy9DPwpfoM,2307
39
+ sovereign/utils/dictupdate.py,sha256=R60mHb3qf4r-KT-SyxQJv7KQktmtzp-bRtVOjebo0To,2558
40
+ sovereign/utils/eds.py,sha256=eykFVKaLXoMeieJ2TnArx3q5KIJeQYiB-OYQrTWtslw,4391
41
+ sovereign/utils/entry_point_loader.py,sha256=OPvaLfwUH0XGYw9WM3hdi_-uBAwmibBoq2NEjLFtnKs,526
42
+ sovereign/utils/mock.py,sha256=Oc5p8conSku2t6b75m7nMHsESepLmnv_itHcAE8cIXw,2396
43
+ sovereign/utils/resources.py,sha256=otKaJZIDQa4So_cKkFN9gBsLZaG6rH0hg9hjELn1Qic,472
44
+ sovereign/utils/templates.py,sha256=yML5rU-BUY-2oQx6gOK7fOkLjtD5VSmaH8vzPKnetSc,1059
45
+ sovereign/utils/timer.py,sha256=DvbjDOSXZsGXigWpuRxE1NyVrpq4qktIRQhJIOt2Dcs,919
46
+ sovereign/utils/version_info.py,sha256=adBfu0z6jsg8E5-BIUjZyBwZvfLASj7fpCpYeIvBeMY,576
47
+ sovereign/utils/weighted_clusters.py,sha256=RTRLX5TFvL426_B-M8XhC7FeGelHzpgGpHwQaVFEHRQ,1167
48
+ sovereign/utils/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
+ sovereign/utils/crypto/crypto.py,sha256=FMo4TikqWcPJ5fyc6j2WEHkkwZU3WY3qJKr7VLfckv0,4532
50
+ sovereign/utils/crypto/suites/__init__.py,sha256=smMvNa1VsQ0PvsNj6lnRNh4ktB7dMnas1CqeTOFqgGA,526
51
+ sovereign/utils/crypto/suites/aes_gcm_cipher.py,sha256=Yjfj1LCQDGTzHBjrZR3-koh29L_N34v65kPoIfta0aw,1239
52
+ sovereign/utils/crypto/suites/base_cipher.py,sha256=kUOZh_ZIILyo5zv99-qzbJZDpeMmt76vhkBDEPvAt4A,454
53
+ sovereign/utils/crypto/suites/disabled_cipher.py,sha256=3uagd2IkE-IHLkfOgX1rofatHIBVLryV_hGvdJPmuTM,579
54
+ sovereign/utils/crypto/suites/fernet_cipher.py,sha256=rP6M5ys1vctyadOxDGNFoyerWPUOunLQdZ2jjS1pxzc,701
55
+ sovereign/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ sovereign/v2/logging.py,sha256=ia8ntWngTKXlMzFhkVNScIDaFTrL0HkIkUT-afQGEJE,2639
57
+ sovereign/v2/types.py,sha256=0qclM0aylR1-FjCVCzlYlZShNICWt6wI-52B3SkSC1w,800
58
+ sovereign/v2/web.py,sha256=dzjLJ0lXdGZqkGc237QHVe930Hmls9m_e-clas9mOps,3443
59
+ sovereign/v2/worker.py,sha256=rGqDsinDCtGNbCmBDF1mHIik4KJH0rKpH4WLzL-VDAo,7467
60
+ sovereign/v2/data/data_store.py,sha256=IDjV3ouYjVxWdcvcAUIPrfnXVwAbbyMmkbXjdfKfnw8,20310
61
+ sovereign/v2/data/render_discovery_response.py,sha256=3AFMXBu1aBRaSLjUTxVUI9Lt-to0B5eXY5m1SMO7dB8,949
62
+ sovereign/v2/data/repositories.py,sha256=KsdFvOIDZmkZDe-SmatX4GialhRk4LWD_2pOBeOTAQs,3292
63
+ sovereign/v2/data/utils.py,sha256=cJ93ioHXPHquMhqvYY4SulbWVgTPxY6t_gvYhOt6T1s,1075
64
+ sovereign/v2/data/worker_queue.py,sha256=aDTLqeXM-qx-CJ0VpOaNVKkGmlEllVQxERXIaS0Kv9w,10407
65
+ sovereign/v2/jobs/refresh_context.py,sha256=lhzP9zl8U4fA0ND-SFJs4HefnEAtv8d7no4u5cFI-L4,4277
66
+ sovereign/v2/jobs/render_discovery_job.py,sha256=Z5L4Qj54tg8FVlaMaBBCZiJgAqJIS-Rl28ekxoJ1o58,5451
67
+ sovereign/views/__init__.py,sha256=0niB6LnhAZQzI4nk_xXsWk95Idm0YFINLoL8WhNdA70,183
68
+ sovereign/views/api.py,sha256=dcNy9HKEB9n6DAQI_gP5cyVLn_4YFOODMx1zIu2-J6Y,2954
69
+ sovereign/views/crypto.py,sha256=0UX0mEGQmiwRcR8gMeIkNYY2ykEGpPC09xmfFRsh-bY,3366
70
+ sovereign/views/discovery.py,sha256=1yIKSIuVnPT7wtPd0fJ7KdEp5ZZf8PdWP67XoI_4R6s,3111
71
+ sovereign/views/healthchecks.py,sha256=56exKqRz4Qdz3KJlbwHWZNIQ-yJUOgpDnzROrzQn3rg,4108
72
+ sovereign/views/interface.py,sha256=vHTdboqhBMGyX4COnExKbal51hOXFX4txaK0nmz8bs8,9294
73
+ sovereign_files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
+ sovereign_files/static/darkmode.js,sha256=3ip-eKGctDvNhN7UgmaHhzls7r5qIY-Jvh2EpefHbQ0,1449
75
+ sovereign_files/static/node_expression.js,sha256=GKxKTSRc_96IbL3H4L_31ueJFXq4N7scm5R1RNqxP24,1489
76
+ sovereign_files/static/panel.js,sha256=i5mGExjv-I4Gtt9dQiTyFwPZa8pg5rXeuTeidXNUiTE,2695
77
+ sovereign_files/static/resources.css,sha256=Rt_ir_FkoI-VIAOqPhk0vILy8kB2egAYbQU26SOs1io,4500
78
+ sovereign_files/static/resources.js,sha256=-TaXZ6tohyKA1SkX5YwrTcV5M8mOZ68cvEXpvZWznTo,24506
79
+ sovereign_files/static/style.css,sha256=kmvkJ2820RKehWxhddkucbgFkvnpUgBMteOtpEuXjvQ,601347
80
+ sovereign_files/static/style.css.map,sha256=h1ufjfDVX-8z-FuJqFG2-U9AVdi66U-e8uyiGdUZjDw,66576
81
+ sovereign_files/static/sass/style.scss,sha256=LdGXXuHi_tyMc7XhijIOrlIxyfLt827AAs2Z7DYpFpg,990
82
+ sovereign-1.0.0a4.dist-info/METADATA,sha256=i7YfqN62JPqLC2-55IdBfaXkhZaajFwufE6XG6glcG0,5943
83
+ sovereign-1.0.0a4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
84
+ sovereign-1.0.0a4.dist-info/entry_points.txt,sha256=Mj1IvDg3Y61lasbqkGtEWpPn_xgYWCyeuMnOl8hTLVQ,1787
85
+ sovereign-1.0.0a4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry 1.0.3
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,46 @@
1
+ [console_scripts]
2
+ sovereign = sovereign.server:main
3
+ sovereign-web = sovereign.server:web
4
+ sovereign-worker = sovereign.server:worker
5
+
6
+ [sovereign.cache.backends]
7
+ s3 = sovereign.cache.backends.s3:S3Backend
8
+
9
+ [sovereign.data_stores]
10
+ memory = sovereign.v2.data.data_store:InMemoryDataStore
11
+ sqlite = sovereign.v2.data.data_store:SqliteDataStore
12
+
13
+ [sovereign.deserializers]
14
+ jinja = sovereign.dynamic_config.deser:JinjaDeserializer
15
+ jinja2 = sovereign.dynamic_config.deser:JinjaDeserializer
16
+ json = sovereign.dynamic_config.deser:JsonDeserializer
17
+ none = sovereign.dynamic_config.deser:PassthroughDeserializer
18
+ orjson = sovereign.dynamic_config.deser:OrjsonDeserializer
19
+ passthrough = sovereign.dynamic_config.deser:PassthroughDeserializer
20
+ raw = sovereign.dynamic_config.deser:PassthroughDeserializer
21
+ string = sovereign.dynamic_config.deser:StringDeserializer
22
+ ujson = sovereign.dynamic_config.deser:UjsonDeserializer
23
+ yaml = sovereign.dynamic_config.deser:YamlDeserializer
24
+
25
+ [sovereign.loaders]
26
+ env = sovereign.dynamic_config.loaders:EnvironmentVariable
27
+ example = sovereign.testing.loaders:Multiply
28
+ file = sovereign.dynamic_config.loaders:File
29
+ http = sovereign.dynamic_config.loaders:Web
30
+ https = sovereign.dynamic_config.loaders:Web
31
+ inline = sovereign.dynamic_config.loaders:Inline
32
+ module = sovereign.dynamic_config.loaders:PythonModule
33
+ pkgdata = sovereign.dynamic_config.loaders:PackageData
34
+ python = sovereign.dynamic_config.loaders:PythonInlineCode
35
+ s3 = sovereign.dynamic_config.loaders:S3Bucket
36
+
37
+ [sovereign.modifiers]
38
+ sovereign_3rd_party_test = sovereign.testing.modifiers:Test
39
+
40
+ [sovereign.queues]
41
+ memory = sovereign.v2.data.worker_queue:InMemoryQueue
42
+ sqlite = sovereign.v2.data.worker_queue:SqliteQueue
43
+
44
+ [sovereign.sources]
45
+ file = sovereign.sources.file:File
46
+ inline = sovereign.sources.inline:Inline
File without changes
@@ -0,0 +1,51 @@
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const darkmode = "theme-dark";
3
+ const lightmode = "theme-light";
4
+ const toggle = document.getElementById('dark-mode-toggle');
5
+ const htmlTag = document.documentElement;
6
+
7
+ function preferredTheme() {
8
+ const preference = localStorage.getItem("theme");
9
+ if (preference) {
10
+ return preference;
11
+ }
12
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
13
+ return "dark";
14
+ } else {
15
+ return "light";
16
+ };
17
+ }
18
+
19
+ function currentTheme() {
20
+ if (htmlTag.classList.contains(darkmode)) {
21
+ return "dark"
22
+ } else {
23
+ return "light"
24
+ }
25
+ }
26
+
27
+ function setTheme(theme) {
28
+ localStorage.setItem("theme", theme);
29
+ if (theme === "dark") {
30
+ htmlTag.classList.remove(lightmode);
31
+ htmlTag.classList.add(darkmode);
32
+ toggle.textContent = '🌘';
33
+ } else {
34
+ htmlTag.classList.remove(darkmode);
35
+ htmlTag.classList.add(lightmode);
36
+ toggle.textContent = '🌞';
37
+ }
38
+ }
39
+
40
+ setTheme(preferredTheme());
41
+
42
+ toggle.addEventListener("click", function() {
43
+ let current = currentTheme();
44
+ console.log("Current theme: " + current);
45
+ if (current === "dark") {
46
+ setTheme("light");
47
+ } else {
48
+ setTheme("dark");
49
+ }
50
+ });
51
+ });