sovereign 0.19.3__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.
- sovereign/__init__.py +13 -81
- sovereign/app.py +62 -48
- sovereign/cache/__init__.py +245 -0
- sovereign/cache/backends/__init__.py +110 -0
- sovereign/cache/backends/s3.py +161 -0
- sovereign/cache/filesystem.py +74 -0
- sovereign/cache/types.py +17 -0
- sovereign/configuration.py +607 -0
- sovereign/constants.py +1 -0
- sovereign/context.py +270 -104
- sovereign/dynamic_config/__init__.py +112 -0
- sovereign/dynamic_config/deser.py +78 -0
- sovereign/dynamic_config/loaders.py +120 -0
- sovereign/error_info.py +2 -3
- sovereign/events.py +49 -0
- sovereign/logging/access_logger.py +85 -0
- sovereign/logging/application_logger.py +54 -0
- sovereign/logging/base_logger.py +41 -0
- sovereign/logging/bootstrapper.py +36 -0
- sovereign/logging/types.py +10 -0
- sovereign/middlewares.py +8 -7
- sovereign/modifiers/lib.py +2 -1
- sovereign/rendering.py +124 -0
- sovereign/rendering_common.py +91 -0
- sovereign/response_class.py +18 -0
- sovereign/server.py +112 -35
- sovereign/statistics.py +19 -21
- sovereign/templates/base.html +59 -46
- sovereign/templates/resources.html +203 -102
- sovereign/testing/loaders.py +9 -0
- sovereign/{modifiers/test.py → testing/modifiers.py} +0 -2
- sovereign/tracing.py +103 -0
- sovereign/types.py +304 -0
- sovereign/utils/auth.py +27 -13
- sovereign/utils/crypto/__init__.py +0 -0
- sovereign/utils/crypto/crypto.py +135 -0
- sovereign/utils/crypto/suites/__init__.py +21 -0
- sovereign/utils/crypto/suites/aes_gcm_cipher.py +42 -0
- sovereign/utils/crypto/suites/base_cipher.py +21 -0
- sovereign/utils/crypto/suites/disabled_cipher.py +25 -0
- sovereign/utils/crypto/suites/fernet_cipher.py +29 -0
- sovereign/utils/dictupdate.py +3 -2
- sovereign/utils/eds.py +40 -22
- sovereign/utils/entry_point_loader.py +2 -2
- sovereign/utils/mock.py +56 -17
- sovereign/utils/resources.py +17 -0
- sovereign/utils/templates.py +4 -2
- sovereign/utils/timer.py +5 -3
- sovereign/utils/version_info.py +8 -0
- sovereign/utils/weighted_clusters.py +2 -1
- sovereign/v2/__init__.py +0 -0
- sovereign/v2/data/data_store.py +621 -0
- sovereign/v2/data/render_discovery_response.py +24 -0
- sovereign/v2/data/repositories.py +90 -0
- sovereign/v2/data/utils.py +33 -0
- sovereign/v2/data/worker_queue.py +273 -0
- sovereign/v2/jobs/refresh_context.py +117 -0
- sovereign/v2/jobs/render_discovery_job.py +145 -0
- sovereign/v2/logging.py +81 -0
- sovereign/v2/types.py +41 -0
- sovereign/v2/web.py +101 -0
- sovereign/v2/worker.py +199 -0
- sovereign/views/__init__.py +7 -0
- sovereign/views/api.py +82 -0
- sovereign/views/crypto.py +46 -15
- sovereign/views/discovery.py +55 -119
- sovereign/views/healthchecks.py +107 -20
- sovereign/views/interface.py +171 -111
- sovereign/worker.py +193 -0
- {sovereign-0.19.3.dist-info → sovereign-1.0.0a4.dist-info}/METADATA +80 -76
- sovereign-1.0.0a4.dist-info/RECORD +85 -0
- {sovereign-0.19.3.dist-info → sovereign-1.0.0a4.dist-info}/WHEEL +1 -1
- sovereign-1.0.0a4.dist-info/entry_points.txt +46 -0
- sovereign_files/__init__.py +0 -0
- sovereign_files/static/darkmode.js +51 -0
- sovereign_files/static/node_expression.js +42 -0
- sovereign_files/static/panel.js +76 -0
- sovereign_files/static/resources.css +246 -0
- sovereign_files/static/resources.js +642 -0
- sovereign_files/static/sass/style.scss +33 -0
- sovereign_files/static/style.css +16143 -0
- sovereign_files/static/style.css.map +1 -0
- sovereign/config_loader.py +0 -225
- sovereign/discovery.py +0 -175
- sovereign/logs.py +0 -131
- sovereign/schemas.py +0 -780
- sovereign/sources/__init__.py +0 -3
- sovereign/sources/file.py +0 -21
- sovereign/sources/inline.py +0 -38
- sovereign/sources/lib.py +0 -40
- sovereign/sources/poller.py +0 -294
- sovereign/static/sass/style.scss +0 -27
- sovereign/static/style.css +0 -13553
- sovereign/templates/ul_filter.html +0 -22
- sovereign/utils/crypto.py +0 -103
- sovereign/views/admin.py +0 -120
- sovereign-0.19.3.dist-info/LICENSE.txt +0 -13
- sovereign-0.19.3.dist-info/RECORD +0 -47
- sovereign-0.19.3.dist-info/entry_points.txt +0 -10
sovereign/sources/__init__.py
DELETED
sovereign/sources/file.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict
|
|
2
|
-
from sovereign.sources.lib import Source
|
|
3
|
-
from sovereign.config_loader import Loadable
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class File(Source):
|
|
7
|
-
def __init__(self, config: Dict[str, Any], scope: str = "default"):
|
|
8
|
-
super(File, self).__init__(config, scope)
|
|
9
|
-
try:
|
|
10
|
-
self.path = Loadable.from_legacy_fmt(config["path"])
|
|
11
|
-
except KeyError:
|
|
12
|
-
try:
|
|
13
|
-
self.path = Loadable(**config["spec"])
|
|
14
|
-
except KeyError:
|
|
15
|
-
raise KeyError('File source needs to specify "spec" within config')
|
|
16
|
-
|
|
17
|
-
def get(self) -> Any:
|
|
18
|
-
"""
|
|
19
|
-
Uses the file config loader to load the given path
|
|
20
|
-
"""
|
|
21
|
-
return self.path.load()
|
sovereign/sources/inline.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Inline Source
|
|
3
|
-
-------------
|
|
4
|
-
|
|
5
|
-
Example configuration (YAML):
|
|
6
|
-
|
|
7
|
-
.. code-block:: yaml
|
|
8
|
-
|
|
9
|
-
sources:
|
|
10
|
-
- type: inline
|
|
11
|
-
config:
|
|
12
|
-
instances:
|
|
13
|
-
- instance_id: my_service
|
|
14
|
-
service_clusters:
|
|
15
|
-
- P2
|
|
16
|
-
parameters:
|
|
17
|
-
upstream_address:
|
|
18
|
-
- address: service.domain.com
|
|
19
|
-
region: us-east-1
|
|
20
|
-
plan_id: 7d57270a-0348-58d3-829d-447a98fe98d5
|
|
21
|
-
"""
|
|
22
|
-
from typing import Dict, Any, List
|
|
23
|
-
from sovereign.sources.lib import Source
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Inline(Source):
|
|
27
|
-
def __init__(
|
|
28
|
-
self, config: Dict[str, List[Dict[str, Any]]], scope: str = "default"
|
|
29
|
-
) -> None:
|
|
30
|
-
super(Inline, self).__init__(config, scope)
|
|
31
|
-
try:
|
|
32
|
-
self.instances = config["instances"]
|
|
33
|
-
except KeyError:
|
|
34
|
-
raise KeyError('Inline source config must contain "instances"')
|
|
35
|
-
|
|
36
|
-
def get(self) -> List[Dict[str, Any]]:
|
|
37
|
-
"""Returns the data passed via configuration"""
|
|
38
|
-
return self.instances
|
sovereign/sources/lib.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Source abstract class
|
|
3
|
-
---------------------
|
|
4
|
-
This class can be subclassed, installed as an entry point, and then
|
|
5
|
-
used via configuration.
|
|
6
|
-
|
|
7
|
-
`todo entry point install guide`
|
|
8
|
-
"""
|
|
9
|
-
import abc
|
|
10
|
-
from typing import Any, Dict, List
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Source(abc.ABC):
|
|
14
|
-
def __init__(self, config: Dict[str, Any], scope: str) -> None:
|
|
15
|
-
"""
|
|
16
|
-
:param config: arbitrary configuration which can be used by the subclass
|
|
17
|
-
"""
|
|
18
|
-
self.config = config
|
|
19
|
-
self.scope = scope
|
|
20
|
-
|
|
21
|
-
def setup(self) -> None:
|
|
22
|
-
"""
|
|
23
|
-
Optional method which is invoked prior to the Source running self.get()
|
|
24
|
-
"""
|
|
25
|
-
return None
|
|
26
|
-
|
|
27
|
-
@abc.abstractmethod
|
|
28
|
-
def get(self) -> List[Dict[str, Any]]:
|
|
29
|
-
"""
|
|
30
|
-
Required method to retrieve data from an arbitrary source
|
|
31
|
-
"""
|
|
32
|
-
raise NotImplementedError
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class SourceImplementation(Source):
|
|
36
|
-
def __call__(self, config: Dict[str, Any], scope: str) -> "SourceImplementation":
|
|
37
|
-
return self
|
|
38
|
-
|
|
39
|
-
def get(self) -> List[Dict[str, Any]]:
|
|
40
|
-
return []
|
sovereign/sources/poller.py
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import traceback
|
|
3
|
-
from copy import deepcopy
|
|
4
|
-
from importlib.metadata import EntryPoint
|
|
5
|
-
from datetime import timedelta, datetime
|
|
6
|
-
from typing import Iterable, Any, Dict, List, Union, Type, Optional
|
|
7
|
-
|
|
8
|
-
from glom import glom, PathAccessError
|
|
9
|
-
|
|
10
|
-
from sovereign.utils.entry_point_loader import EntryPointLoader
|
|
11
|
-
from sovereign.sources.lib import Source
|
|
12
|
-
from sovereign.modifiers.lib import Modifier, GlobalModifier
|
|
13
|
-
from sovereign.schemas import (
|
|
14
|
-
ConfiguredSource,
|
|
15
|
-
SourceData,
|
|
16
|
-
Node,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def is_debug_request(v: str, debug: bool = False) -> bool:
|
|
21
|
-
return v == "" and debug
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def is_wildcard(v: List[str]) -> bool:
|
|
25
|
-
return v in [["*"], "*", ("*",)]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def contains(container: Iterable[Any], item: Any) -> bool:
|
|
29
|
-
return item in container
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Mods = Dict[str, Type[Modifier]]
|
|
33
|
-
GMods = Dict[str, Type[GlobalModifier]]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class SourcePoller:
|
|
37
|
-
def __init__(
|
|
38
|
-
self,
|
|
39
|
-
sources: List[ConfiguredSource],
|
|
40
|
-
matching_enabled: bool,
|
|
41
|
-
node_match_key: str,
|
|
42
|
-
source_match_key: str,
|
|
43
|
-
source_refresh_rate: int,
|
|
44
|
-
logger: Any,
|
|
45
|
-
stats: Any,
|
|
46
|
-
):
|
|
47
|
-
self.matching_enabled = matching_enabled
|
|
48
|
-
self.node_match_key = node_match_key
|
|
49
|
-
self.source_match_key = source_match_key
|
|
50
|
-
self.source_refresh_rate = source_refresh_rate
|
|
51
|
-
self.logger = logger
|
|
52
|
-
self.stats = stats
|
|
53
|
-
|
|
54
|
-
self.entry_points = EntryPointLoader("sources", "modifiers", "global_modifiers")
|
|
55
|
-
|
|
56
|
-
self.source_classes: Dict[str, Type[Source]] = {
|
|
57
|
-
e.name: e.load() for e in self.entry_points.groups["sources"]
|
|
58
|
-
}
|
|
59
|
-
self.sources = [self.setup_source(s) for s in sources]
|
|
60
|
-
if not self.sources:
|
|
61
|
-
raise RuntimeError("No data sources available!")
|
|
62
|
-
|
|
63
|
-
# These have to be loaded later to avoid circular imports
|
|
64
|
-
self.modifiers: Mods = dict()
|
|
65
|
-
self.global_modifiers: GMods = dict()
|
|
66
|
-
|
|
67
|
-
# initially set data and modify
|
|
68
|
-
self.source_data = self.refresh()
|
|
69
|
-
self.last_updated = datetime.now()
|
|
70
|
-
self.instance_count = 0
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def data_is_stale(self) -> bool:
|
|
74
|
-
return self.last_updated < datetime.now() - timedelta(minutes=2)
|
|
75
|
-
|
|
76
|
-
def setup_source(self, configured_source: ConfiguredSource) -> Source:
|
|
77
|
-
source_class = self.source_classes[configured_source.type]
|
|
78
|
-
source = source_class(
|
|
79
|
-
config=configured_source.config,
|
|
80
|
-
scope=configured_source.scope,
|
|
81
|
-
)
|
|
82
|
-
source.setup()
|
|
83
|
-
return source
|
|
84
|
-
|
|
85
|
-
def lazy_load_modifiers(self, modifiers: List[str]) -> None:
|
|
86
|
-
if len(self.modifiers) == len(modifiers):
|
|
87
|
-
return
|
|
88
|
-
self.modifiers = self.load_modifier_entrypoints(
|
|
89
|
-
self.entry_points.groups["modifiers"], modifiers
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
def lazy_load_global_modifiers(self, global_modifiers: List[str]) -> None:
|
|
93
|
-
if len(self.global_modifiers) == len(global_modifiers):
|
|
94
|
-
return
|
|
95
|
-
self.global_modifiers = self.load_global_modifier_entrypoints(
|
|
96
|
-
self.entry_points.groups["global_modifiers"], global_modifiers
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
def load_modifier_entrypoints(
|
|
100
|
-
self, entry_points: Iterable[EntryPoint], configured_modifiers: List[str]
|
|
101
|
-
) -> Dict[str, Type[Modifier]]:
|
|
102
|
-
ret = dict()
|
|
103
|
-
for entry_point in entry_points:
|
|
104
|
-
if entry_point.name in configured_modifiers:
|
|
105
|
-
self.logger(event=f"Loading modifier {entry_point.name}")
|
|
106
|
-
ret[entry_point.name] = entry_point.load()
|
|
107
|
-
loaded = len(ret)
|
|
108
|
-
configured = len(configured_modifiers)
|
|
109
|
-
assert loaded == configured, (
|
|
110
|
-
f"Number of modifiers loaded ({loaded})"
|
|
111
|
-
f"differ from configured: {configured_modifiers}"
|
|
112
|
-
)
|
|
113
|
-
return ret
|
|
114
|
-
|
|
115
|
-
def load_global_modifier_entrypoints(
|
|
116
|
-
self, entry_points: Iterable[EntryPoint], configured_modifiers: List[str]
|
|
117
|
-
) -> Dict[str, Type[GlobalModifier]]:
|
|
118
|
-
ret = dict()
|
|
119
|
-
for entry_point in entry_points:
|
|
120
|
-
if entry_point.name in configured_modifiers:
|
|
121
|
-
self.logger(event=f"Loading global modifier {entry_point.name}")
|
|
122
|
-
ret[entry_point.name] = entry_point.load()
|
|
123
|
-
|
|
124
|
-
loaded = len(ret)
|
|
125
|
-
configured = len(configured_modifiers)
|
|
126
|
-
assert loaded == configured, (
|
|
127
|
-
f"Number of global modifiers loaded ({loaded})"
|
|
128
|
-
f"differ from configured: {configured_modifiers}"
|
|
129
|
-
)
|
|
130
|
-
return ret
|
|
131
|
-
|
|
132
|
-
def apply_modifications(self, data: Optional[SourceData]) -> SourceData:
|
|
133
|
-
with self.stats.timed("modifiers.apply_ms"):
|
|
134
|
-
if data is None:
|
|
135
|
-
data = self.source_data
|
|
136
|
-
|
|
137
|
-
source_data = deepcopy(data)
|
|
138
|
-
for scope, instances in source_data.scopes.items():
|
|
139
|
-
for g in self.global_modifiers.values():
|
|
140
|
-
global_modifier = g(instances)
|
|
141
|
-
global_modifier.apply()
|
|
142
|
-
source_data.scopes[scope] = global_modifier.join()
|
|
143
|
-
|
|
144
|
-
for instance in source_data.scopes[scope]:
|
|
145
|
-
for m in self.modifiers.values():
|
|
146
|
-
modifier = m(instance)
|
|
147
|
-
if modifier.match():
|
|
148
|
-
# Modifies the instance in-place
|
|
149
|
-
modifier.apply()
|
|
150
|
-
return source_data
|
|
151
|
-
|
|
152
|
-
def refresh(self) -> SourceData:
|
|
153
|
-
self.stats.increment("sources.attempt")
|
|
154
|
-
try:
|
|
155
|
-
new = SourceData()
|
|
156
|
-
for source in self.sources:
|
|
157
|
-
new.scopes[source.scope].extend(source.get())
|
|
158
|
-
except Exception as e:
|
|
159
|
-
self.logger(
|
|
160
|
-
event="Error while refreshing sources",
|
|
161
|
-
traceback=[line for line in traceback.format_exc().split("\n")],
|
|
162
|
-
error=e.__class__.__name__,
|
|
163
|
-
detail=getattr(e, "detail", "-"),
|
|
164
|
-
)
|
|
165
|
-
self.stats.increment("sources.error")
|
|
166
|
-
raise
|
|
167
|
-
|
|
168
|
-
# Is the new data the same as what we currently have
|
|
169
|
-
if new == getattr(self, "source_data", None):
|
|
170
|
-
self.stats.increment("sources.unchanged")
|
|
171
|
-
self.last_updated = datetime.now()
|
|
172
|
-
return self.source_data
|
|
173
|
-
else:
|
|
174
|
-
self.stats.increment("sources.refreshed")
|
|
175
|
-
self.last_updated = datetime.now()
|
|
176
|
-
self.instance_count = len(
|
|
177
|
-
[instance for scope in new.scopes.values() for instance in scope]
|
|
178
|
-
)
|
|
179
|
-
return new
|
|
180
|
-
|
|
181
|
-
def extract_node_key(self, node: Union[Node, Dict[Any, Any]]) -> Any:
|
|
182
|
-
if "." not in self.node_match_key:
|
|
183
|
-
# key is not nested, don't need glom
|
|
184
|
-
node_value = getattr(node, self.node_match_key)
|
|
185
|
-
else:
|
|
186
|
-
try:
|
|
187
|
-
node_value = glom(node, self.node_match_key)
|
|
188
|
-
except PathAccessError:
|
|
189
|
-
raise RuntimeError(
|
|
190
|
-
f'Failed to find key "{self.node_match_key}" in discoveryRequest({node}).\n'
|
|
191
|
-
f"See the docs for more info: "
|
|
192
|
-
f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
|
|
193
|
-
)
|
|
194
|
-
return node_value
|
|
195
|
-
|
|
196
|
-
def extract_source_key(self, source: Dict[Any, Any]) -> Any:
|
|
197
|
-
if "." not in self.source_match_key:
|
|
198
|
-
# key is not nested, don't need glom
|
|
199
|
-
source_value = source[self.source_match_key]
|
|
200
|
-
else:
|
|
201
|
-
try:
|
|
202
|
-
source_value = glom(source, self.source_match_key)
|
|
203
|
-
except PathAccessError:
|
|
204
|
-
raise RuntimeError(
|
|
205
|
-
f'Failed to find key "{self.source_match_key}" in instance({source}).\n'
|
|
206
|
-
f"See the docs for more info: "
|
|
207
|
-
f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
|
|
208
|
-
)
|
|
209
|
-
return source_value
|
|
210
|
-
|
|
211
|
-
def match_node(
|
|
212
|
-
self,
|
|
213
|
-
node_value: Any,
|
|
214
|
-
modify: bool = True,
|
|
215
|
-
) -> SourceData:
|
|
216
|
-
"""
|
|
217
|
-
Checks a node against all sources, using the node_match_key and source_match_key
|
|
218
|
-
to determine if the node should receive the source in its configuration.
|
|
219
|
-
"""
|
|
220
|
-
|
|
221
|
-
if self.data_is_stale:
|
|
222
|
-
# Log/emit metric and manually refresh sources.
|
|
223
|
-
self.stats.increment("sources.stale")
|
|
224
|
-
self.logger(
|
|
225
|
-
event="Sources have not been refreshed in 2 minutes",
|
|
226
|
-
last_update=self.last_updated,
|
|
227
|
-
instance_count=self.instance_count,
|
|
228
|
-
)
|
|
229
|
-
self.poll()
|
|
230
|
-
|
|
231
|
-
ret = SourceData()
|
|
232
|
-
|
|
233
|
-
if modify:
|
|
234
|
-
if not hasattr(self, "source_data_modified"):
|
|
235
|
-
self.poll()
|
|
236
|
-
data = self.source_data_modified
|
|
237
|
-
else:
|
|
238
|
-
data = self.source_data
|
|
239
|
-
|
|
240
|
-
for scope, instances in data.scopes.items():
|
|
241
|
-
if self.matching_enabled is False:
|
|
242
|
-
ret.scopes[scope] = instances
|
|
243
|
-
continue
|
|
244
|
-
|
|
245
|
-
for instance in instances:
|
|
246
|
-
source_value = self.extract_source_key(instance)
|
|
247
|
-
|
|
248
|
-
# If a single expression evaluates true, the remaining are not evaluated/executed.
|
|
249
|
-
# This saves (a small amount of) computation, which helps when the server starts
|
|
250
|
-
# to receive thousands of requests. The list has been ordered descending by what
|
|
251
|
-
# we think will more commonly be true.
|
|
252
|
-
match = (
|
|
253
|
-
contains(source_value, node_value)
|
|
254
|
-
or node_value == source_value
|
|
255
|
-
or is_wildcard(node_value)
|
|
256
|
-
or is_wildcard(source_value)
|
|
257
|
-
or is_debug_request(node_value)
|
|
258
|
-
)
|
|
259
|
-
if match:
|
|
260
|
-
ret.scopes[scope].append(instance)
|
|
261
|
-
return ret
|
|
262
|
-
|
|
263
|
-
@property
|
|
264
|
-
def match_keys(self) -> List[str]:
|
|
265
|
-
"""
|
|
266
|
-
Checks for all match keys present in existing sources and adds them to a list
|
|
267
|
-
|
|
268
|
-
A dict is used instead of a set because dicts cannot have duplicate keys, and
|
|
269
|
-
have ordering since python 3.6
|
|
270
|
-
"""
|
|
271
|
-
ret: Dict[str, None] = dict()
|
|
272
|
-
ret["*"] = None
|
|
273
|
-
for _, instances in self.source_data.scopes.items():
|
|
274
|
-
if self.matching_enabled is False:
|
|
275
|
-
break
|
|
276
|
-
for instance in instances:
|
|
277
|
-
source_value = glom(instance, self.source_match_key)
|
|
278
|
-
if isinstance(source_value, str):
|
|
279
|
-
ret[source_value] = None
|
|
280
|
-
elif isinstance(source_value, Iterable):
|
|
281
|
-
for item in source_value:
|
|
282
|
-
ret[item] = None
|
|
283
|
-
continue
|
|
284
|
-
ret[source_value] = None
|
|
285
|
-
return list(ret.keys())
|
|
286
|
-
|
|
287
|
-
def poll(self) -> None:
|
|
288
|
-
self.source_data = self.refresh()
|
|
289
|
-
self.source_data_modified = self.apply_modifications(self.source_data)
|
|
290
|
-
|
|
291
|
-
async def poll_forever(self) -> None:
|
|
292
|
-
while True:
|
|
293
|
-
self.poll()
|
|
294
|
-
await asyncio.sleep(self.source_refresh_rate)
|
sovereign/static/sass/style.scss
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
@charset "utf-8";
|
|
2
|
-
|
|
3
|
-
// Import a Google Font
|
|
4
|
-
@import url('https://fonts.googleapis.com/css?family=Nunito:400,700');
|
|
5
|
-
|
|
6
|
-
// Update Bulma's global variables
|
|
7
|
-
$family-sans-serif: "Nunito", sans-serif;
|
|
8
|
-
$primary: #433fca;
|
|
9
|
-
$link: #6c90ec;
|
|
10
|
-
|
|
11
|
-
// Force the navbar menu to not collapse
|
|
12
|
-
$desktop: 1px;
|
|
13
|
-
$body-background-color: rgb(250, 250, 250);
|
|
14
|
-
|
|
15
|
-
// Import only what you need from Bulma
|
|
16
|
-
@import "../../../../node_modules/bulma/sass/base/_all.sass";
|
|
17
|
-
@import "../../../../node_modules/bulma/sass/form/_all.sass";
|
|
18
|
-
@import "../../../../node_modules/bulma/sass/utilities/_all.sass";
|
|
19
|
-
@import "../../../../node_modules/bulma/sass/helpers/_all.sass";
|
|
20
|
-
@import "../../../../node_modules/bulma/sass/elements/button.sass";
|
|
21
|
-
@import "../../../../node_modules/bulma/sass/elements/title.sass";
|
|
22
|
-
@import "../../../../node_modules/bulma/sass/elements/content.sass";
|
|
23
|
-
@import "../../../../node_modules/bulma/sass/components/navbar.sass";
|
|
24
|
-
@import "../../../../node_modules/bulma/sass/components/dropdown.sass";
|
|
25
|
-
@import "../../../../node_modules/bulma/sass/components/panel.sass";
|
|
26
|
-
@import "../../../../node_modules/bulma/sass/grid/columns.sass";
|
|
27
|
-
@import "../../../../node_modules/bulma/bulma.sass";
|