sovereign 1.0.0b121__tar.gz → 1.0.0b148__tar.gz

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 (89) hide show
  1. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/PKG-INFO +42 -48
  2. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/README.md +5 -4
  3. sovereign-1.0.0b148/pyproject.toml +146 -0
  4. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/__init__.py +2 -20
  5. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/app.py +20 -23
  6. sovereign-1.0.0b148/src/sovereign/cache/__init__.py +172 -0
  7. sovereign-1.0.0b148/src/sovereign/cache/backends/__init__.py +110 -0
  8. sovereign-1.0.0b148/src/sovereign/cache/backends/s3.py +143 -0
  9. sovereign-1.0.0b148/src/sovereign/cache/filesystem.py +73 -0
  10. sovereign-1.0.0b148/src/sovereign/cache/types.py +15 -0
  11. sovereign-1.0.0b148/src/sovereign/configuration.py +573 -0
  12. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/context.py +31 -22
  13. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/dynamic_config/__init__.py +48 -30
  14. sovereign-1.0.0b148/src/sovereign/events.py +49 -0
  15. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/logging/access_logger.py +1 -1
  16. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/logging/application_logger.py +1 -1
  17. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/logging/bootstrapper.py +1 -1
  18. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/middlewares.py +1 -1
  19. sovereign-1.0.0b148/src/sovereign/rendering.py +192 -0
  20. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/server.py +7 -6
  21. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/sources/poller.py +46 -19
  22. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/statistics.py +3 -3
  23. sovereign-1.0.0b148/src/sovereign/templates/base.html +77 -0
  24. sovereign-1.0.0b148/src/sovereign/templates/resources.html +260 -0
  25. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/testing/modifiers.py +0 -2
  26. sovereign-1.0.0b148/src/sovereign/tracing.py +102 -0
  27. sovereign-1.0.0b148/src/sovereign/types.py +299 -0
  28. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/auth.py +3 -2
  29. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/crypto.py +1 -1
  30. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/eds.py +2 -2
  31. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/mock.py +8 -4
  32. sovereign-1.0.0b148/src/sovereign/views/__init__.py +4 -0
  33. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/views/api.py +13 -13
  34. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/views/crypto.py +1 -1
  35. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/views/discovery.py +10 -6
  36. sovereign-1.0.0b148/src/sovereign/views/healthchecks.py +109 -0
  37. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/views/interface.py +46 -20
  38. sovereign-1.0.0b148/src/sovereign/worker.py +204 -0
  39. sovereign-1.0.0b148/src/sovereign_files/static/darkmode.js +51 -0
  40. sovereign-1.0.0b148/src/sovereign_files/static/node_expression.js +42 -0
  41. sovereign-1.0.0b148/src/sovereign_files/static/resources.css +246 -0
  42. sovereign-1.0.0b148/src/sovereign_files/static/resources.js +642 -0
  43. sovereign-1.0.0b148/src/sovereign_files/static/sass/style.scss +33 -0
  44. sovereign-1.0.0b148/src/sovereign_files/static/style.css +16143 -0
  45. sovereign-1.0.0b148/src/sovereign_files/static/style.css.map +1 -0
  46. sovereign-1.0.0b121/LICENSE.txt +0 -13
  47. sovereign-1.0.0b121/pyproject.toml +0 -143
  48. sovereign-1.0.0b121/src/sovereign/cache.py +0 -133
  49. sovereign-1.0.0b121/src/sovereign/rendering.py +0 -148
  50. sovereign-1.0.0b121/src/sovereign/schemas.py +0 -1052
  51. sovereign-1.0.0b121/src/sovereign/static/node_expression.js +0 -16
  52. sovereign-1.0.0b121/src/sovereign/static/sass/style.scss +0 -27
  53. sovereign-1.0.0b121/src/sovereign/static/style.css +0 -13553
  54. sovereign-1.0.0b121/src/sovereign/templates/base.html +0 -64
  55. sovereign-1.0.0b121/src/sovereign/templates/resources.html +0 -1055
  56. sovereign-1.0.0b121/src/sovereign/tracing.py +0 -85
  57. sovereign-1.0.0b121/src/sovereign/views/healthchecks.py +0 -55
  58. sovereign-1.0.0b121/src/sovereign/worker.py +0 -167
  59. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/constants.py +0 -0
  60. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/dynamic_config/deser.py +0 -0
  61. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/dynamic_config/loaders.py +0 -0
  62. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/error_info.py +0 -0
  63. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/logging/base_logger.py +0 -0
  64. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/logging/types.py +0 -0
  65. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/modifiers/__init__.py +0 -0
  66. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/modifiers/lib.py +0 -0
  67. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/response_class.py +0 -0
  68. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/sources/__init__.py +0 -0
  69. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/sources/file.py +0 -0
  70. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/sources/inline.py +0 -0
  71. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/sources/lib.py +0 -0
  72. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/templates/err.html +0 -0
  73. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/testing/loaders.py +0 -0
  74. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/__init__.py +0 -0
  75. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/__init__.py +0 -0
  76. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/__init__.py +0 -0
  77. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py +0 -0
  78. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/base_cipher.py +0 -0
  79. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/disabled_cipher.py +0 -0
  80. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/fernet_cipher.py +0 -0
  81. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/dictupdate.py +0 -0
  82. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/entry_point_loader.py +0 -0
  83. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/resources.py +0 -0
  84. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/templates.py +0 -0
  85. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/timer.py +0 -0
  86. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/version_info.py +0 -0
  87. {sovereign-1.0.0b121 → sovereign-1.0.0b148}/src/sovereign/utils/weighted_clusters.py +0 -0
  88. {sovereign-1.0.0b121/src/sovereign/views → sovereign-1.0.0b148/src/sovereign_files}/__init__.py +0 -0
  89. {sovereign-1.0.0b121/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/panel.js +0 -0
@@ -1,13 +1,13 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: sovereign
3
- Version: 1.0.0b121
3
+ Version: 1.0.0b148
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.11,<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,51 +16,45 @@ 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
- Classifier: Programming Language :: Python :: 3.11
21
- Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3.13
23
- Classifier: Programming Language :: Python :: 3.10
24
19
  Classifier: Programming Language :: Python :: 3.8
25
20
  Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
26
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.117,>=0.116.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: starlette-context<0.4,>=0.3.6
40
+ Requires-Dist: starlette<0.48,>=0.47.2
41
+ Requires-Dist: structlog<24,>=23.1.0
42
+ Requires-Dist: supervisor<5,>=4.2.5
43
+ Requires-Dist: uvicorn<0.24,>=0.23.2
44
+ Requires-Dist: uvloop<1.0,>0.19.0
27
45
  Provides-Extra: boto
46
+ Requires-Dist: boto3<2,>=1.28.62; extra == 'boto'
28
47
  Provides-Extra: caching
29
48
  Provides-Extra: httptools
49
+ Requires-Dist: httptools<0.7,>=0.6.0; extra == 'httptools'
30
50
  Provides-Extra: orjson
51
+ Requires-Dist: orjson<4,>=3.9.15; extra == 'orjson'
31
52
  Provides-Extra: sentry
53
+ Requires-Dist: sentry-sdk<3,>=2.14.0; extra == 'sentry'
32
54
  Provides-Extra: statsd
55
+ Requires-Dist: datadog>=0.50.1; extra == 'statsd'
33
56
  Provides-Extra: ujson
34
- Requires-Dist: Jinja2 (>=3.1.2,<4.0.0)
35
- Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
36
- Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
37
- Requires-Dist: boto3 (>=1.28.62,<2.0.0) ; extra == "boto"
38
- Requires-Dist: cachelib (>=0.10.2,<0.11.0)
39
- Requires-Dist: cachetools (>=5.3.2,<6.0.0)
40
- Requires-Dist: cashews[redis] (>=6.3.0,<7.0.0) ; extra == "caching"
41
- Requires-Dist: croniter (>=1.4.1,<2.0.0)
42
- Requires-Dist: cryptography (>=45.0.2)
43
- Requires-Dist: datadog (>=0.50.1) ; extra == "statsd"
44
- Requires-Dist: fastapi (>=0.116.0,<0.117.0)
45
- Requires-Dist: glom (>=23.3.0,<24.0.0)
46
- Requires-Dist: h11 (>=0.16.0,<0.17.0)
47
- Requires-Dist: httptools (>=0.6.0,<0.7.0) ; extra == "httptools"
48
- Requires-Dist: jmespath (>=1.0.1,<2.0.0)
49
- Requires-Dist: orjson (>=3.9.15,<4.0.0) ; extra == "orjson"
50
- Requires-Dist: pydantic (>=2.7.2,<3.0.0)
51
- Requires-Dist: pydantic-settings (<2.6.0)
52
- Requires-Dist: redis (<=5.0.0)
53
- Requires-Dist: requests (>=2.32.4,<3.0.0)
54
- Requires-Dist: sentry-sdk (>=2.14.0,<3.0.0) ; extra == "sentry"
55
- Requires-Dist: starlette (>=0.47.2,<0.48.0)
56
- Requires-Dist: starlette-context (>=0.3.6,<0.4.0)
57
- Requires-Dist: structlog (>=23.1.0,<24.0.0)
58
- Requires-Dist: supervisor (>=4.2.5,<5.0.0)
59
- Requires-Dist: ujson (>=5.8.0,<6.0.0) ; extra == "ujson"
60
- Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
61
- Requires-Dist: uvloop (>=0.19.0,<0.20.0)
62
- Project-URL: Documentation, https://developer.atlassian.com/platform/sovereign/
63
- Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
57
+ Requires-Dist: ujson<6,>=5.8.0; extra == 'ujson'
64
58
  Description-Content-Type: text/markdown
65
59
 
66
60
  sovereign
@@ -136,17 +130,18 @@ Local development
136
130
 
137
131
  Requirements
138
132
  ------------
139
- * Poetry
133
+ * uv
140
134
  * Docker
141
135
  * Docker-compose
142
136
 
143
137
 
144
138
  Installing dependencies for dev
145
139
  -------------------------------
146
- Dependencies and creation of virtualenv is handled by poetry
140
+ Dependencies and creation of virtualenv is handled by uv
147
141
  ```
148
- poetry install
149
- poetry shell
142
+ uv sync
143
+ uv venv activate
144
+ uv run <command>
150
145
  ```
151
146
 
152
147
  Running locally
@@ -211,4 +206,3 @@ Copyright (c) 2018 Atlassian and others.
211
206
  Apache 2.0 licensed, see [LICENSE.txt](LICENSE.txt) file.
212
207
 
213
208
 
214
-
@@ -71,17 +71,18 @@ Local development
71
71
 
72
72
  Requirements
73
73
  ------------
74
- * Poetry
74
+ * uv
75
75
  * Docker
76
76
  * Docker-compose
77
77
 
78
78
 
79
79
  Installing dependencies for dev
80
80
  -------------------------------
81
- Dependencies and creation of virtualenv is handled by poetry
81
+ Dependencies and creation of virtualenv is handled by uv
82
82
  ```
83
- poetry install
84
- poetry shell
83
+ uv sync
84
+ uv venv activate
85
+ uv run <command>
85
86
  ```
86
87
 
87
88
  Running locally
@@ -0,0 +1,146 @@
1
+ [project]
2
+ name = "sovereign"
3
+ version = "1.0.0b148"
4
+ description = "Envoy Proxy control-plane written in Python"
5
+ authors = [{ name = "Vasili Syrakis", email = "vsyrakis@atlassian.com" }]
6
+ requires-python = ">=3.11.0, <4.0"
7
+ readme = "README.md"
8
+ license = "Apache-2.0"
9
+ keywords = [
10
+ "envoy",
11
+ "envoyproxy",
12
+ "control-plane",
13
+ "management",
14
+ "server",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Environment :: No Input/Output (Daemon)",
19
+ "Intended Audience :: Developers",
20
+ "Intended Audience :: Information Technology",
21
+ "Intended Audience :: System Administrators",
22
+ "License :: OSI Approved :: Apache Software License",
23
+ "Natural Language :: English",
24
+ "Operating System :: POSIX :: Linux",
25
+ "Programming Language :: Python :: 3.8",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Topic :: Internet :: Proxy Servers",
30
+ ]
31
+ dependencies = [
32
+ "uvicorn>=0.23.2,<0.24",
33
+ "aiofiles>=23.2.1,<24",
34
+ "requests>=2.32.4,<3",
35
+ "PyYAML>=6.0.1,<7",
36
+ "Jinja2>=3.1.2,<4",
37
+ "structlog>=23.1.0,<24",
38
+ "cachelib>=0.10.2,<0.11",
39
+ "glom>=23.3.0,<24",
40
+ "cryptography>=45.0.2",
41
+ "fastapi>=0.116.0,<0.117",
42
+ "uvloop>0.19.0,<1.0",
43
+ "croniter>=1.4.1,<2",
44
+ "cachetools>=5.3.2,<6",
45
+ "pydantic>=2.7.2,<3",
46
+ "pydantic-settings<2.6.0",
47
+ "starlette-context>=0.3.6,<0.4",
48
+ "starlette>=0.47.2,<0.48",
49
+ "h11>=0.16.0,<0.17",
50
+ "supervisor>=4.2.5,<5",
51
+ "jmespath>=1.0.1,<2",
52
+ ]
53
+
54
+ [project.optional-dependencies]
55
+ sentry = ["sentry-sdk>=2.14.0,<3"]
56
+ boto = ["boto3>=1.28.62,<2"]
57
+ statsd = ["datadog>=0.50.1"]
58
+ ujson = ["ujson>=5.8.0,<6"]
59
+ orjson = ["orjson>=3.9.15,<4"]
60
+ caching = []
61
+ httptools = ["httptools>=0.6.0,<0.7"]
62
+
63
+ [project.urls]
64
+ Homepage = "https://pypi.org/project/sovereign/"
65
+ Repository = "https://bitbucket.org/atlassian/sovereign/src/master/"
66
+ Documentation = "https://developer.atlassian.com/platform/sovereign/"
67
+
68
+ [project.scripts]
69
+ sovereign = "sovereign.server:main"
70
+ sovereign-web = "sovereign.server:web"
71
+ sovereign-worker = "sovereign.server:worker"
72
+
73
+ [project.entry-points."sovereign.sources"]
74
+ file = "sovereign.sources.file:File"
75
+ inline = "sovereign.sources.inline:Inline"
76
+
77
+ [project.entry-points."sovereign.modifiers"]
78
+ sovereign_3rd_party_test = "sovereign.testing.modifiers:Test"
79
+
80
+ [project.entry-points."sovereign.loaders"]
81
+ example = "sovereign.testing.loaders:Multiply"
82
+ file = "sovereign.dynamic_config.loaders:File"
83
+ pkgdata = "sovereign.dynamic_config.loaders:PackageData"
84
+ http = "sovereign.dynamic_config.loaders:Web"
85
+ https = "sovereign.dynamic_config.loaders:Web"
86
+ env = "sovereign.dynamic_config.loaders:EnvironmentVariable"
87
+ module = "sovereign.dynamic_config.loaders:PythonModule"
88
+ s3 = "sovereign.dynamic_config.loaders:S3Bucket"
89
+ python = "sovereign.dynamic_config.loaders:PythonInlineCode"
90
+ inline = "sovereign.dynamic_config.loaders:Inline"
91
+
92
+ [project.entry-points."sovereign.deserializers"]
93
+ yaml = "sovereign.dynamic_config.deser:YamlDeserializer"
94
+ json = "sovereign.dynamic_config.deser:JsonDeserializer"
95
+ jinja = "sovereign.dynamic_config.deser:JinjaDeserializer"
96
+ jinja2 = "sovereign.dynamic_config.deser:JinjaDeserializer"
97
+ string = "sovereign.dynamic_config.deser:StringDeserializer"
98
+ raw = "sovereign.dynamic_config.deser:PassthroughDeserializer"
99
+ none = "sovereign.dynamic_config.deser:PassthroughDeserializer"
100
+ passthrough = "sovereign.dynamic_config.deser:PassthroughDeserializer"
101
+ ujson = "sovereign.dynamic_config.deser:UjsonDeserializer"
102
+ orjson = "sovereign.dynamic_config.deser:OrjsonDeserializer"
103
+
104
+ [project.entry-points."sovereign.cache.backends"]
105
+ s3 = "sovereign.cache.backends.s3:S3Backend"
106
+
107
+ [dependency-groups]
108
+ dev = [
109
+ "tavern",
110
+ "moto",
111
+ "freezegun",
112
+ "pytest-mock",
113
+ "pytest-asyncio",
114
+ "ruff",
115
+ "toml",
116
+ "httpx",
117
+ "types-croniter",
118
+ "types-requests",
119
+ "types-setuptools",
120
+ "types-ujson",
121
+ "types-PyYAML",
122
+ "types-cachetools",
123
+ "ty",
124
+ ]
125
+
126
+ [tool.hatch.build.targets.sdist]
127
+ include = [
128
+ "src/sovereign",
129
+ "src/sovereign_files",
130
+ "src/sovereign_files/**/*",
131
+ ]
132
+
133
+ [tool.hatch.build.targets.wheel]
134
+ include = [
135
+ "src/sovereign",
136
+ "src/sovereign_files",
137
+ "src/sovereign_files/**/*",
138
+ ]
139
+
140
+ [tool.hatch.build.targets.wheel.sources]
141
+ "src/sovereign" = "sovereign"
142
+ "src/sovereign_files" = "sovereign_files"
143
+
144
+ [build-system]
145
+ requires = ["hatchling"]
146
+ build-backend = "hatchling.build"
@@ -1,20 +1,11 @@
1
- import sys
2
1
  from contextvars import ContextVar
3
2
  from importlib.metadata import version
4
- from sovereign.context import TemplateContext
5
3
 
6
4
  from sovereign.utils.crypto.suites import EncryptionType
7
- from starlette.templating import Jinja2Templates
8
-
9
5
  from sovereign.logging.bootstrapper import LoggerBootstrapper
10
- from sovereign.schemas import (
11
- config,
12
- EncryptionConfig,
13
- migrate_configs,
14
- )
6
+ from sovereign.configuration import config, EncryptionConfig
15
7
  from sovereign.statistics import configure_statsd
16
8
  from sovereign.utils.crypto.crypto import CipherContainer
17
- from sovereign.utils.resources import get_package_file
18
9
 
19
10
  _request_id_ctx_var: ContextVar[str] = ContextVar("request_id", default="")
20
11
 
@@ -23,23 +14,14 @@ def get_request_id() -> str:
23
14
  return _request_id_ctx_var.get()
24
15
 
25
16
 
17
+ WORKER_URL = "http://localhost:9080"
26
18
  DIST_NAME = "sovereign"
27
-
28
19
  __version__ = version(DIST_NAME)
29
20
 
30
- html_templates = Jinja2Templates(
31
- directory=str(get_package_file(DIST_NAME, "templates"))
32
- )
33
-
34
- if sys.argv[0].endswith("sovereign"):
35
- migrate_configs()
36
-
37
21
  stats = configure_statsd()
38
22
  logs = LoggerBootstrapper(config)
39
23
  application_logger = logs.application_logger.logger
40
24
 
41
- template_context = TemplateContext.from_config()
42
-
43
25
  encryption_configs = config.authentication.encryption_configs
44
26
  server_cipher_container = CipherContainer.from_encryption_configs(
45
27
  encryption_configs, logger=application_logger
@@ -6,12 +6,8 @@ from fastapi import FastAPI, Request
6
6
  from fastapi.responses import FileResponse, JSONResponse, RedirectResponse, Response
7
7
  from starlette_context.middleware import RawContextMiddleware
8
8
 
9
- from sovereign import (
10
- __version__,
11
- config,
12
- logs,
13
- )
14
- from sovereign.schemas import DiscoveryTypes
9
+ from sovereign import __version__, logs
10
+ from sovereign.configuration import config, ConfiguredResourceTypes
15
11
  from sovereign.response_class import json_response_class
16
12
  from sovereign.error_info import ErrorInfo
17
13
  from sovereign.middlewares import LoggingMiddleware, RequestContextLogMiddleware
@@ -21,15 +17,6 @@ from sovereign.views import crypto, discovery, healthchecks, interface, api
21
17
  Router = namedtuple("Router", "module tags prefix")
22
18
 
23
19
  DEBUG = config.debug
24
- SENTRY_DSN = config.sentry_dsn.get_secret_value()
25
-
26
- try:
27
- import sentry_sdk
28
- from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
29
-
30
- SENTRY_INSTALLED = True
31
- except ImportError: # pragma: no cover
32
- SENTRY_INSTALLED = False
33
20
 
34
21
 
35
22
  def generic_error_response(e: Exception) -> JSONResponse:
@@ -76,14 +63,22 @@ def init_app() -> FastAPI:
76
63
  router.module, tags=router.tags, prefix=router.prefix
77
64
  )
78
65
 
79
- application.add_middleware(RequestContextLogMiddleware)
80
- application.add_middleware(LoggingMiddleware)
66
+ application.add_middleware(RequestContextLogMiddleware) # type: ignore
67
+ application.add_middleware(LoggingMiddleware) # type: ignore
81
68
 
82
- if SENTRY_INSTALLED and SENTRY_DSN:
83
- sentry_sdk.init(SENTRY_DSN)
84
- application.add_middleware(SentryAsgiMiddleware)
69
+ if dsn := config.sentry_dsn.get_secret_value():
70
+ try:
71
+ import sentry_sdk
72
+ from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
85
73
 
86
- application.add_middleware(RawContextMiddleware)
74
+ sentry_sdk.init(dsn)
75
+ application.add_middleware(SentryAsgiMiddleware) # type: ignore
76
+ except ImportError:
77
+ logs.application_logger.logger.error(
78
+ "Sentry DSN configured but failed to attach to webserver"
79
+ )
80
+
81
+ application.add_middleware(RawContextMiddleware) # type: ignore
87
82
 
88
83
  @application.exception_handler(500)
89
84
  async def exception_handler(_: Request, exc: Exception) -> JSONResponse:
@@ -101,14 +96,16 @@ def init_app() -> FastAPI:
101
96
 
102
97
  @application.get("/static/{filename}", summary="Return a static asset")
103
98
  async def static(filename: str) -> Response:
104
- return FileResponse(get_package_file("sovereign", f"static/{filename}")) # type: ignore[arg-type]
99
+ return FileResponse(get_package_file("sovereign_files", f"static/{filename}")) # type: ignore[arg-type]
105
100
 
106
101
  @application.get(
107
102
  "/admin/xds_dump",
108
103
  summary="Deprecated API, please use /api/resources/{resource_type}",
109
104
  )
110
105
  async def dump_resources(request: Request) -> Response:
111
- resource_type = DiscoveryTypes(request.query_params.get("xds_type", "cluster"))
106
+ resource_type = ConfiguredResourceTypes(
107
+ request.query_params.get("xds_type", "cluster")
108
+ )
112
109
  resource_name = request.query_params.get("name")
113
110
  api_version = request.query_params.get("api_version", "v3")
114
111
  service_cluster = request.query_params.get("service_cluster", "*")
@@ -0,0 +1,172 @@
1
+ """
2
+ Sovereign Cache Module
3
+
4
+ This module provides an extensible cache backend system that allows clients
5
+ to configure their own remote cache backends through entry points.
6
+ """
7
+
8
+ import asyncio
9
+ from typing import Any
10
+ from typing_extensions import final
11
+
12
+ import requests
13
+
14
+ from sovereign import WORKER_URL, stats, application_logger as log
15
+ from sovereign.types import DiscoveryRequest, RegisterClientRequest
16
+ from sovereign.configuration import config
17
+ from sovereign.cache.types import Entry, CacheResult
18
+ from sovereign.cache.backends import CacheBackend, get_backend
19
+ from sovereign.cache.filesystem import FilesystemCache
20
+
21
+
22
+ CACHE_READ_TIMEOUT = config.cache.read_timeout
23
+
24
+
25
+ class CacheManagerBase:
26
+ def __init__(self) -> None:
27
+ self.local: FilesystemCache = FilesystemCache()
28
+ self.remote: CacheBackend | None = get_backend()
29
+ if self.remote is None:
30
+ log.info("Cache initialized with filesystem backend only")
31
+ else:
32
+ log.info("Cache initialized with filesystem and remote backends")
33
+
34
+ # Client Id registration
35
+
36
+ def register(self, req: DiscoveryRequest):
37
+ id = client_id(req)
38
+ log.debug(f"Registering client {id}")
39
+ self.local.register(id, req)
40
+ stats.increment("client.registration", tags=["status:registered"])
41
+ return id, req
42
+
43
+ def registered(self, req: DiscoveryRequest) -> bool:
44
+ ret = False
45
+ id = client_id(req)
46
+ if value := self.local.registered(id):
47
+ ret = value
48
+ log.debug(f"Client {id} registered={ret}")
49
+ return ret
50
+
51
+ def get_registered_clients(self) -> list[tuple[str, DiscoveryRequest]]:
52
+ if value := self.local.get_registered_clients():
53
+ return value
54
+ return []
55
+
56
+
57
+ @final
58
+ class CacheReader(CacheManagerBase):
59
+ def try_read(self, key: str) -> CacheResult | None:
60
+ # Try filesystem first
61
+ if value := self.local.get(key):
62
+ stats.increment("cache.fs.hit")
63
+ return CacheResult(value=value, from_remote=False)
64
+ stats.increment("cache.fs.miss")
65
+
66
+ # Fallback to remote cache if available
67
+ if self.remote:
68
+ try:
69
+ if value := self.remote.get(key):
70
+ ret = CacheResult(value=value, from_remote=True)
71
+ stats.increment("cache.remote.hit")
72
+ return ret
73
+ except Exception as e:
74
+ log.warning(f"Failed to read from remote cache: {e}")
75
+ stats.increment("cache.remote.error")
76
+ stats.increment("cache.remote.miss")
77
+ log.warning(f"Failed to read from either cache for {key}")
78
+ return None
79
+
80
+ def get(self, req: DiscoveryRequest) -> Entry | None:
81
+ id = client_id(req)
82
+ if result := self.try_read(id):
83
+ if result.from_remote:
84
+ self.register(req)
85
+ # Write back to filesystem
86
+ self.local.set(id, result.value)
87
+ return result.value
88
+ return None
89
+
90
+ @stats.timed("cache.read_ms")
91
+ async def blocking_read(
92
+ self, req: DiscoveryRequest, timeout_s=CACHE_READ_TIMEOUT, poll_interval_s=0.5
93
+ ) -> Entry | None:
94
+ cid = client_id(req)
95
+ metric = "client.registration"
96
+ if entry := self.get(req):
97
+ return entry
98
+
99
+ log.info(f"Cache entry not found for {cid}, registering and waiting")
100
+ registered = False
101
+ start = asyncio.get_event_loop().time()
102
+ attempt = 1
103
+ while (asyncio.get_event_loop().time() - start) < timeout_s:
104
+ if not registered:
105
+ try:
106
+ if self.register_over_http(req):
107
+ stats.increment(metric, tags=["status:registered"])
108
+ registered = True
109
+ log.info(f"Client {cid} registered")
110
+ else:
111
+ stats.increment(metric, tags=["status:ratelimited"])
112
+ await asyncio.sleep(min(attempt, CACHE_READ_TIMEOUT))
113
+ attempt *= 2
114
+ except Exception as e:
115
+ stats.increment(metric, tags=["status:failed"])
116
+ log.exception(f"Tried to register client but failed: {e}")
117
+ if entry := self.get(req):
118
+ log.info(f"Entry has been populated for {cid}")
119
+ return entry
120
+ await asyncio.sleep(poll_interval_s)
121
+
122
+ return None
123
+
124
+ def register_over_http(self, req: DiscoveryRequest) -> bool:
125
+ registration = RegisterClientRequest(request=req)
126
+ log.debug(f"Sending registration to worker for {req}")
127
+ try:
128
+ response = requests.put(
129
+ f"{WORKER_URL}/client",
130
+ json=registration.model_dump(),
131
+ timeout=3,
132
+ )
133
+ match response.status_code:
134
+ case 200 | 202:
135
+ log.debug("Worker responded OK to registration")
136
+ return True
137
+ case code:
138
+ log.debug(f"Worker responded with {code} to registration")
139
+ except Exception as e:
140
+ log.exception(f"Error while registering client: {e}")
141
+ return False
142
+
143
+
144
+ @final
145
+ class CacheWriter(CacheManagerBase):
146
+ def set(
147
+ self, key: str, value: Entry, timeout: int | None = None
148
+ ) -> tuple[bool, list[tuple[str, str]]]:
149
+ msg = []
150
+ cached = False
151
+ try:
152
+ self.local.set(key, value, timeout)
153
+ stats.increment("cache.fs.write.success")
154
+ cached = True
155
+ except Exception as e:
156
+ log.warning(f"Failed to write to filesystem cache: {e}")
157
+ msg.append(("warning", f"Failed to write to filesystem cache: {e}"))
158
+ stats.increment("cache.fs.write.error")
159
+ if self.remote:
160
+ try:
161
+ self.remote.set(key, value, timeout)
162
+ stats.increment("cache.remote.write.success")
163
+ cached = True
164
+ except Exception as e:
165
+ log.warning(f"Failed to write to remote cache: {e}")
166
+ msg.append(("warning", f"Failed to write to remote cache: {e}"))
167
+ stats.increment("cache.remote.write.error")
168
+ return cached, msg
169
+
170
+
171
+ def client_id(req: DiscoveryRequest) -> str:
172
+ return req.cache_key(config.cache.hash_rules)