open-api-framework 0.6.0__tar.gz → 0.7.0__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.
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/CHANGELOG.rst +29 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/MANIFEST.in +1 -0
- {open_api_framework-0.6.0/open_api_framework.egg-info → open_api_framework-0.7.0}/PKG-INFO +15 -15
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/README.rst +1 -1
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/conf/base.py +291 -44
- open_api_framework-0.7.0/open_api_framework/conf/utils.py +121 -0
- open_api_framework-0.7.0/open_api_framework/management/commands/generate_envvar_docs.py +58 -0
- open_api_framework-0.7.0/open_api_framework/py.typed +0 -0
- open_api_framework-0.7.0/open_api_framework/templates/open_api_framework/env_config.rst +50 -0
- open_api_framework-0.7.0/open_api_framework/templatetags/__init__.py +0 -0
- open_api_framework-0.7.0/open_api_framework/templatetags/doc_tags.py +35 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0/open_api_framework.egg-info}/PKG-INFO +15 -15
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/SOURCES.txt +7 -1
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/requires.txt +13 -13
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/pyproject.toml +15 -15
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/tests/test_config_helpers.py +2 -2
- open_api_framework-0.7.0/tests/test_generate_envvar_docs.py +85 -0
- open_api_framework-0.6.0/open_api_framework/conf/utils.py +0 -63
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/LICENSE +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/__init__.py +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/apps.py +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/conf/__init__.py +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/context_processors.py +0 -0
- {open_api_framework-0.6.0/open_api_framework/templatetags → open_api_framework-0.7.0/open_api_framework/management}/__init__.py +0 -0
- /open_api_framework-0.6.0/open_api_framework/py.typed → /open_api_framework-0.7.0/open_api_framework/management/commands/__init__.py +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/static/open_api_framework/css/admin.css +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templates/open_api_framework/admin/base_site.html +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templates/open_api_framework/components/environment.html +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templates/open_api_framework/components/version.html +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templatetags/open_api_framework.py +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/dependency_links.txt +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/top_level.txt +0 -0
- {open_api_framework-0.6.0 → open_api_framework-0.7.0}/setup.cfg +0 -0
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
0.7.0 (2024-08-15)
|
|
5
|
+
------------------
|
|
6
|
+
|
|
7
|
+
**New features**
|
|
8
|
+
|
|
9
|
+
* Management command to generate documentation for environment variables
|
|
10
|
+
* Made SESSION_COOKIE_SAMESITE and CSRF_COOKIE_SAMESITE configurable via environment variables (default ``Strict``)
|
|
11
|
+
|
|
12
|
+
0.6.1 (2024-07-31)
|
|
13
|
+
------------------
|
|
14
|
+
|
|
15
|
+
**Project maintenance and QOL**
|
|
16
|
+
|
|
17
|
+
* Updated dependencies:
|
|
18
|
+
- django ``4.2.11+`` to ``4.2.14+``
|
|
19
|
+
- django-axes ``6.3.0+`` to ``6.5.1+``
|
|
20
|
+
- django-cors-headers ``4.3.1+`` to ``4.4.0+``
|
|
21
|
+
- django-jsonform ``2.21.4+`` to ``2.22.0+``
|
|
22
|
+
- djangorestframework ``3.12.4+`` to ``3.15.2+``
|
|
23
|
+
- django-filter ``23.2+`` to ``24.2+``
|
|
24
|
+
- drf-spectacular ``0.27.0+`` to ``0.27.2+``
|
|
25
|
+
- mozilla-django-oidc-db ``0.14.1+`` to ``0.19.0+``
|
|
26
|
+
- requests ``2.31.0+`` to ``2.32.3+``
|
|
27
|
+
- sentry-sdk ``1.39.2+`` to ``2.11.0+``
|
|
28
|
+
- elastic-apm ``6.20.0+`` to ``6.22.0+``
|
|
29
|
+
- celery ``5.2.7+`` to ``5.4.0+``
|
|
30
|
+
- maykin-2fa ``1.0.0+`` to ``1.0.1+``
|
|
31
|
+
|
|
32
|
+
|
|
4
33
|
0.6.0 (2024-07-04)
|
|
5
34
|
------------------
|
|
6
35
|
|
|
@@ -2,6 +2,7 @@ include *.rst
|
|
|
2
2
|
include LICENSE
|
|
3
3
|
include open_api_framework/py.typed
|
|
4
4
|
recursive-include open_api_framework *.html
|
|
5
|
+
recursive-include open_api_framework *.rst
|
|
5
6
|
recursive-include open_api_framework *.txt
|
|
6
7
|
recursive-include open_api_framework *.po
|
|
7
8
|
recursive-include open_api_framework/static *.css
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: open_api_framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A metapackage for registration components, that bundles the dependencies shared between these components and provides generic settings
|
|
5
5
|
Author-email: Maykin Media <support@maykinmedia.nl>
|
|
6
6
|
License: Copyright © Maykin 2024
|
|
@@ -30,33 +30,33 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
30
30
|
Requires-Python: >=3.9
|
|
31
31
|
Description-Content-Type: text/x-rst
|
|
32
32
|
License-File: LICENSE
|
|
33
|
-
Requires-Dist: django
|
|
34
|
-
Requires-Dist: django-axes>=6.
|
|
35
|
-
Requires-Dist: django-cors-headers>=4.
|
|
33
|
+
Requires-Dist: django<5.0.0,>=4.2.14
|
|
34
|
+
Requires-Dist: django-axes>=6.5.1
|
|
35
|
+
Requires-Dist: django-cors-headers>=4.4.0
|
|
36
36
|
Requires-Dist: django-markup>=1.8.1
|
|
37
|
-
Requires-Dist: django-jsonform>=2.
|
|
37
|
+
Requires-Dist: django-jsonform>=2.22.0
|
|
38
38
|
Requires-Dist: django-log-outgoing-requests>=0.6.1
|
|
39
39
|
Requires-Dist: django-admin-index>=3.1.0
|
|
40
40
|
Requires-Dist: django-redis>=5.4.0
|
|
41
|
-
Requires-Dist: djangorestframework>=3.
|
|
41
|
+
Requires-Dist: djangorestframework>=3.15.2
|
|
42
42
|
Requires-Dist: djangorestframework-gis>=1.0
|
|
43
|
-
Requires-Dist: django-filter>=
|
|
44
|
-
Requires-Dist: drf-spectacular>=0.27.
|
|
43
|
+
Requires-Dist: django-filter>=24.2
|
|
44
|
+
Requires-Dist: drf-spectacular>=0.27.2
|
|
45
45
|
Requires-Dist: djangorestframework-inclusions>=1.2.0
|
|
46
46
|
Requires-Dist: commonground-api-common>=1.12.1
|
|
47
|
-
Requires-Dist: mozilla-django-oidc-db>=0.
|
|
47
|
+
Requires-Dist: mozilla-django-oidc-db>=0.19.0
|
|
48
48
|
Requires-Dist: zgw-consumers>=0.27.0
|
|
49
49
|
Requires-Dist: psycopg2>=2.9.9
|
|
50
50
|
Requires-Dist: bleach>=6.1.0
|
|
51
51
|
Requires-Dist: python-dotenv>=1.0.0
|
|
52
52
|
Requires-Dist: python-decouple>=3.8
|
|
53
|
-
Requires-Dist: requests>=2.
|
|
53
|
+
Requires-Dist: requests>=2.32.3
|
|
54
54
|
Requires-Dist: uwsgi>=2.0.23
|
|
55
|
-
Requires-Dist: sentry-sdk>=
|
|
56
|
-
Requires-Dist: elastic-apm>=6.
|
|
57
|
-
Requires-Dist: celery>=5.
|
|
55
|
+
Requires-Dist: sentry-sdk>=2.11.0
|
|
56
|
+
Requires-Dist: elastic-apm>=6.22.0
|
|
57
|
+
Requires-Dist: celery>=5.4.0
|
|
58
58
|
Requires-Dist: flower>=2.0.1
|
|
59
|
-
Requires-Dist: maykin-2fa>=1.0.
|
|
59
|
+
Requires-Dist: maykin-2fa>=1.0.1
|
|
60
60
|
Requires-Dist: django-setup-configuration>=0.1.0
|
|
61
61
|
Provides-Extra: tests
|
|
62
62
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -77,7 +77,7 @@ Requires-Dist: twine; extra == "release"
|
|
|
77
77
|
Open API Framework
|
|
78
78
|
==================
|
|
79
79
|
|
|
80
|
-
:Version: 0.
|
|
80
|
+
:Version: 0.7.0
|
|
81
81
|
:Source: https://github.com/maykinmedia/open-api-framework
|
|
82
82
|
:Keywords: metapackage, dependencies
|
|
83
83
|
|
|
@@ -30,19 +30,61 @@ BASE_DIR = Path(DJANGO_PROJECT_DIR).resolve().parents[1]
|
|
|
30
30
|
#
|
|
31
31
|
# Core Django settings
|
|
32
32
|
#
|
|
33
|
-
SITE_ID = config(
|
|
33
|
+
SITE_ID = config(
|
|
34
|
+
"SITE_ID",
|
|
35
|
+
default=1,
|
|
36
|
+
help_text="The database ID of the site object. You usually won't have to touch this.",
|
|
37
|
+
)
|
|
34
38
|
|
|
35
39
|
# SECURITY WARNING: keep the secret key used in production secret!
|
|
36
|
-
SECRET_KEY = config(
|
|
40
|
+
SECRET_KEY = config(
|
|
41
|
+
"SECRET_KEY",
|
|
42
|
+
help_text=(
|
|
43
|
+
"Secret key that's used for certain cryptographic utilities. "
|
|
44
|
+
"You should generate one via `miniwebtool <https://www.miniwebtool.com/django-secret-key-generator>`_"
|
|
45
|
+
),
|
|
46
|
+
)
|
|
37
47
|
|
|
38
48
|
# NEVER run with DEBUG=True in production-like environments
|
|
39
|
-
DEBUG = config(
|
|
49
|
+
DEBUG = config(
|
|
50
|
+
"DEBUG",
|
|
51
|
+
default=False,
|
|
52
|
+
help_text=(
|
|
53
|
+
"Only set this to ``True`` on a local development environment. "
|
|
54
|
+
"Various other security settings are derived from this setting!"
|
|
55
|
+
),
|
|
56
|
+
)
|
|
40
57
|
|
|
41
58
|
# = domains we're running on
|
|
42
|
-
ALLOWED_HOSTS = config(
|
|
43
|
-
|
|
59
|
+
ALLOWED_HOSTS = config(
|
|
60
|
+
"ALLOWED_HOSTS",
|
|
61
|
+
default="",
|
|
62
|
+
split=True,
|
|
63
|
+
help_text=(
|
|
64
|
+
"a comma separated (without spaces!) list of domains that serve "
|
|
65
|
+
"the installation. Used to protect against Host header attacks."
|
|
66
|
+
),
|
|
67
|
+
group="Required",
|
|
68
|
+
)
|
|
69
|
+
USE_X_FORWARDED_HOST = config(
|
|
70
|
+
"USE_X_FORWARDED_HOST",
|
|
71
|
+
default=False,
|
|
72
|
+
help_text=(
|
|
73
|
+
"whether to grab the domain/host from the X-Forwarded-Host header or not. "
|
|
74
|
+
"This header is typically set by reverse proxies (such as nginx, traefik, Apache...). "
|
|
75
|
+
"Note: this is a header that can be spoofed and you need to ensure you control it before enabling this."
|
|
76
|
+
),
|
|
77
|
+
)
|
|
44
78
|
|
|
45
|
-
IS_HTTPS = config(
|
|
79
|
+
IS_HTTPS = config(
|
|
80
|
+
"IS_HTTPS",
|
|
81
|
+
default=not DEBUG,
|
|
82
|
+
help_text=(
|
|
83
|
+
"Used to construct absolute URLs and controls a variety of security settings. "
|
|
84
|
+
"Defaults to the inverse of ``DEBUG``."
|
|
85
|
+
),
|
|
86
|
+
auto_display_default=False,
|
|
87
|
+
)
|
|
46
88
|
|
|
47
89
|
# Internationalization
|
|
48
90
|
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
|
@@ -66,11 +108,37 @@ USE_THOUSAND_SEPARATOR = True
|
|
|
66
108
|
DATABASES = {
|
|
67
109
|
"default": {
|
|
68
110
|
"ENGINE": "django.db.backends.postgresql",
|
|
69
|
-
"NAME": config(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
"NAME": config(
|
|
112
|
+
"DB_NAME",
|
|
113
|
+
PROJECT_DIRNAME,
|
|
114
|
+
group="Database",
|
|
115
|
+
help_text="name of the PostgreSQL database.",
|
|
116
|
+
),
|
|
117
|
+
"USER": config(
|
|
118
|
+
"DB_USER",
|
|
119
|
+
PROJECT_DIRNAME,
|
|
120
|
+
group="Database",
|
|
121
|
+
help_text="username of the database user.",
|
|
122
|
+
),
|
|
123
|
+
"PASSWORD": config(
|
|
124
|
+
"DB_PASSWORD",
|
|
125
|
+
PROJECT_DIRNAME,
|
|
126
|
+
group="Database",
|
|
127
|
+
help_text="password of the database user.",
|
|
128
|
+
),
|
|
129
|
+
"HOST": config(
|
|
130
|
+
"DB_HOST",
|
|
131
|
+
"localhost",
|
|
132
|
+
group="Database",
|
|
133
|
+
help_text=(
|
|
134
|
+
"hostname of the PostgreSQL database. Defaults to ``db`` for the docker environment, "
|
|
135
|
+
"otherwise defaults to ``localhost``."
|
|
136
|
+
),
|
|
137
|
+
auto_display_default=False,
|
|
138
|
+
),
|
|
139
|
+
"PORT": config(
|
|
140
|
+
"DB_PORT", 5432, group="Database", help_text="port number of the database"
|
|
141
|
+
),
|
|
74
142
|
}
|
|
75
143
|
}
|
|
76
144
|
|
|
@@ -78,10 +146,21 @@ DATABASES = {
|
|
|
78
146
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-DEFAULT_AUTO_FIELD
|
|
79
147
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|
80
148
|
|
|
149
|
+
CACHE_DEFAULT = config(
|
|
150
|
+
"CACHE_DEFAULT",
|
|
151
|
+
"localhost:6379/0",
|
|
152
|
+
help_text="redis cache address for the default cache",
|
|
153
|
+
)
|
|
154
|
+
CACHE_AXES = config(
|
|
155
|
+
"CACHE_AXES",
|
|
156
|
+
"localhost:6379/0",
|
|
157
|
+
help_text="redis cache address for the brute force login protection cache",
|
|
158
|
+
)
|
|
159
|
+
|
|
81
160
|
CACHES = {
|
|
82
161
|
"default": {
|
|
83
162
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
84
|
-
"LOCATION": f"redis://{
|
|
163
|
+
"LOCATION": f"redis://{CACHE_DEFAULT}",
|
|
85
164
|
"OPTIONS": {
|
|
86
165
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
87
166
|
"IGNORE_EXCEPTIONS": True,
|
|
@@ -89,7 +168,7 @@ CACHES = {
|
|
|
89
168
|
},
|
|
90
169
|
"axes": {
|
|
91
170
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
92
|
-
"LOCATION": f"redis://{
|
|
171
|
+
"LOCATION": f"redis://{CACHE_AXES}",
|
|
93
172
|
"OPTIONS": {
|
|
94
173
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
95
174
|
"IGNORE_EXCEPTIONS": True,
|
|
@@ -97,7 +176,7 @@ CACHES = {
|
|
|
97
176
|
},
|
|
98
177
|
"oidc": {
|
|
99
178
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
100
|
-
"LOCATION": f"redis://{
|
|
179
|
+
"LOCATION": f"redis://{CACHE_DEFAULT}",
|
|
101
180
|
"OPTIONS": {
|
|
102
181
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
103
182
|
"IGNORE_EXCEPTIONS": True,
|
|
@@ -223,24 +302,68 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
|
|
223
302
|
#
|
|
224
303
|
# Sending EMAIL
|
|
225
304
|
#
|
|
226
|
-
EMAIL_HOST = config(
|
|
305
|
+
EMAIL_HOST = config(
|
|
306
|
+
"EMAIL_HOST",
|
|
307
|
+
default="localhost",
|
|
308
|
+
help_text="hostname for the outgoing e-mail server",
|
|
309
|
+
)
|
|
227
310
|
EMAIL_PORT = config(
|
|
228
|
-
"EMAIL_PORT",
|
|
311
|
+
"EMAIL_PORT",
|
|
312
|
+
default=25,
|
|
313
|
+
help_text=(
|
|
314
|
+
"port number of the outgoing e-mail server. Note that if you're on Google Cloud, "
|
|
315
|
+
"sending e-mail via port 25 is completely blocked and you should use 487 for TLS."
|
|
316
|
+
),
|
|
229
317
|
) # disabled on Google Cloud, use 487 instead
|
|
230
|
-
EMAIL_HOST_USER = config(
|
|
231
|
-
|
|
232
|
-
|
|
318
|
+
EMAIL_HOST_USER = config(
|
|
319
|
+
"EMAIL_HOST_USER", default="", help_text="username to connect to the mail server"
|
|
320
|
+
)
|
|
321
|
+
EMAIL_HOST_PASSWORD = config(
|
|
322
|
+
"EMAIL_HOST_PASSWORD",
|
|
323
|
+
default="",
|
|
324
|
+
help_text="password to connect to the mail server",
|
|
325
|
+
)
|
|
326
|
+
EMAIL_USE_TLS = config(
|
|
327
|
+
"EMAIL_USE_TLS",
|
|
328
|
+
default=False,
|
|
329
|
+
help_text=(
|
|
330
|
+
"whether to use TLS or not to connect to the mail server. "
|
|
331
|
+
"Should be True if you're changing the ``EMAIL_PORT`` to 487."
|
|
332
|
+
),
|
|
333
|
+
)
|
|
233
334
|
EMAIL_TIMEOUT = 10
|
|
234
335
|
|
|
235
|
-
DEFAULT_FROM_EMAIL = config(
|
|
336
|
+
DEFAULT_FROM_EMAIL = config(
|
|
337
|
+
"DEFAULT_FROM_EMAIL",
|
|
338
|
+
f"{PROJECT_DIRNAME}@example.com",
|
|
339
|
+
help_text="The default email address from which emails are sent",
|
|
340
|
+
)
|
|
236
341
|
|
|
237
342
|
#
|
|
238
343
|
# LOGGING
|
|
239
344
|
#
|
|
240
|
-
LOG_STDOUT = config(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
345
|
+
LOG_STDOUT = config(
|
|
346
|
+
"LOG_STDOUT", default=False, help_text="whether to log to stdout or not"
|
|
347
|
+
)
|
|
348
|
+
LOG_LEVEL = config(
|
|
349
|
+
"LOG_LEVEL",
|
|
350
|
+
default="WARNING",
|
|
351
|
+
help_text=(
|
|
352
|
+
"control the verbosity of logging output. "
|
|
353
|
+
"Available values are ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO`` and ``DEBUG``"
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
LOG_QUERIES = config(
|
|
357
|
+
"LOG_QUERIES",
|
|
358
|
+
default=False,
|
|
359
|
+
help_text=(
|
|
360
|
+
"enable (query) logging at the database backend level. Note that you "
|
|
361
|
+
"must also set ``DEBUG=1``, which should be done very sparingly!"
|
|
362
|
+
),
|
|
363
|
+
)
|
|
364
|
+
LOG_REQUESTS = config(
|
|
365
|
+
"LOG_REQUESTS", default=False, help_text="enable logging of the outgoing requests"
|
|
366
|
+
)
|
|
244
367
|
if LOG_QUERIES and not DEBUG:
|
|
245
368
|
warnings.warn(
|
|
246
369
|
"Requested LOG_QUERIES=1 but DEBUG is false, no query logs will be emited.",
|
|
@@ -416,8 +539,10 @@ LOGOUT_REDIRECT_URL = reverse_lazy("admin:index")
|
|
|
416
539
|
#
|
|
417
540
|
SESSION_COOKIE_SECURE = IS_HTTPS
|
|
418
541
|
SESSION_COOKIE_HTTPONLY = True
|
|
542
|
+
SESSION_COOKIE_SAMESITE = config("SESSION_COOKIE_SAMESITE", "Strict")
|
|
419
543
|
|
|
420
544
|
CSRF_COOKIE_SECURE = IS_HTTPS
|
|
545
|
+
CSRF_COOKIE_SAMESITE = config("CSRF_COOKIE_SAMESITE", "Strict")
|
|
421
546
|
|
|
422
547
|
X_FRAME_OPTIONS = "DENY"
|
|
423
548
|
|
|
@@ -438,11 +563,28 @@ DATA_UPLOAD_MAX_NUMBER_FIELDS = 10000
|
|
|
438
563
|
#
|
|
439
564
|
# Custom settings
|
|
440
565
|
#
|
|
441
|
-
ENVIRONMENT = config(
|
|
566
|
+
ENVIRONMENT = config(
|
|
567
|
+
"ENVIRONMENT",
|
|
568
|
+
"",
|
|
569
|
+
help_text=(
|
|
570
|
+
"An identifier for the environment, displayed in the admin depending on "
|
|
571
|
+
"the settings module used and included in the error monitoring (see ``SENTRY_DSN``). "
|
|
572
|
+
"The default is set according to ``DJANGO_SETTINGS_MODULE``."
|
|
573
|
+
),
|
|
574
|
+
auto_display_default=False,
|
|
575
|
+
)
|
|
442
576
|
ENVIRONMENT_SHOWN_IN_ADMIN = True
|
|
443
577
|
|
|
444
578
|
# Generating the schema, depending on the component
|
|
445
|
-
subpath = config(
|
|
579
|
+
subpath = config(
|
|
580
|
+
"SUBPATH",
|
|
581
|
+
None,
|
|
582
|
+
help_text=(
|
|
583
|
+
"If hosted on a subpath, provide the value here. If you provide ``/gateway``, "
|
|
584
|
+
"the component assumes its running at the base URL: ``https://somedomain/gateway/``. "
|
|
585
|
+
"Defaults to an empty string."
|
|
586
|
+
),
|
|
587
|
+
)
|
|
446
588
|
if subpath:
|
|
447
589
|
if not subpath.startswith("/"):
|
|
448
590
|
subpath = f"/{subpath}"
|
|
@@ -462,12 +604,22 @@ elif (Path(BASE_DIR) / ".git").exists():
|
|
|
462
604
|
else:
|
|
463
605
|
GIT_SHA = None
|
|
464
606
|
|
|
465
|
-
RELEASE = config(
|
|
607
|
+
RELEASE = config(
|
|
608
|
+
"RELEASE",
|
|
609
|
+
GIT_SHA,
|
|
610
|
+
help_text="The version number or commit hash of the application (this is also sent to Sentry).",
|
|
611
|
+
auto_display_default=False,
|
|
612
|
+
)
|
|
466
613
|
|
|
467
614
|
NUM_PROXIES = config( # TODO: this also is relevant for DRF settings if/when we have rate-limited endpoints
|
|
468
615
|
"NUM_PROXIES",
|
|
469
616
|
default=1,
|
|
470
617
|
cast=lambda val: int(val) if val is not None else None,
|
|
618
|
+
help_text=(
|
|
619
|
+
"the number of reverse proxies in front of the application, as an integer. "
|
|
620
|
+
"This is used to determine the actual client IP adres. "
|
|
621
|
+
"On Kubernetes with an ingress you typically want to set this to 2."
|
|
622
|
+
),
|
|
471
623
|
)
|
|
472
624
|
|
|
473
625
|
##############################
|
|
@@ -516,10 +668,28 @@ IPWARE_META_PRECEDENCE_ORDER = (
|
|
|
516
668
|
#
|
|
517
669
|
# DJANGO-CORS-MIDDLEWARE
|
|
518
670
|
#
|
|
519
|
-
CORS_ALLOW_ALL_ORIGINS = config(
|
|
520
|
-
|
|
671
|
+
CORS_ALLOW_ALL_ORIGINS = config(
|
|
672
|
+
"CORS_ALLOW_ALL_ORIGINS",
|
|
673
|
+
default=False,
|
|
674
|
+
group="Cross-Origin-Resource-Sharing",
|
|
675
|
+
help_text="allow cross-domain access from any client",
|
|
676
|
+
)
|
|
677
|
+
CORS_ALLOWED_ORIGINS = config(
|
|
678
|
+
"CORS_ALLOWED_ORIGINS",
|
|
679
|
+
split=True,
|
|
680
|
+
default=[],
|
|
681
|
+
group="Cross-Origin-Resource-Sharing",
|
|
682
|
+
help_text=(
|
|
683
|
+
"explicitly list the allowed origins for cross-domain requests. "
|
|
684
|
+
"Example: http://localhost:3000,https://some-app.gemeente.nl"
|
|
685
|
+
),
|
|
686
|
+
)
|
|
521
687
|
CORS_ALLOWED_ORIGIN_REGEXES = config(
|
|
522
|
-
"CORS_ALLOWED_ORIGIN_REGEXES",
|
|
688
|
+
"CORS_ALLOWED_ORIGIN_REGEXES",
|
|
689
|
+
split=True,
|
|
690
|
+
default=[],
|
|
691
|
+
group="Cross-Origin-Resource-Sharing",
|
|
692
|
+
help_text="same as ``CORS_ALLOWED_ORIGINS``, but supports regular expressions",
|
|
523
693
|
)
|
|
524
694
|
# Authorization is included in default_cors_headers
|
|
525
695
|
CORS_ALLOW_HEADERS = (
|
|
@@ -528,7 +698,17 @@ CORS_ALLOW_HEADERS = (
|
|
|
528
698
|
"accept-crs",
|
|
529
699
|
"content-crs",
|
|
530
700
|
]
|
|
531
|
-
+ config(
|
|
701
|
+
+ config(
|
|
702
|
+
"CORS_EXTRA_ALLOW_HEADERS",
|
|
703
|
+
split=True,
|
|
704
|
+
default=[],
|
|
705
|
+
group="Cross-Origin-Resource-Sharing",
|
|
706
|
+
help_text=(
|
|
707
|
+
"headers that are allowed to be sent as part of the cross-domain request. "
|
|
708
|
+
"By default, Authorization, Accept-Crs and Content-Crs are already included. "
|
|
709
|
+
"The value of this variable is added to these already included headers."
|
|
710
|
+
),
|
|
711
|
+
)
|
|
532
712
|
)
|
|
533
713
|
CORS_EXPOSE_HEADERS = [
|
|
534
714
|
"content-crs",
|
|
@@ -545,6 +725,7 @@ CSRF_TRUSTED_ORIGINS = config(
|
|
|
545
725
|
"CSRF_TRUSTED_ORIGINS",
|
|
546
726
|
split=True,
|
|
547
727
|
default=[strip_protocol_from_origin(origin) for origin in CORS_ALLOWED_ORIGINS],
|
|
728
|
+
help_text="A list of trusted origins for unsafe requests (e.g. POST)",
|
|
548
729
|
)
|
|
549
730
|
#
|
|
550
731
|
# DJANGO-PRIVATES -- safely serve files after authorization
|
|
@@ -556,7 +737,16 @@ PRIVATE_MEDIA_URL = "/private-media/"
|
|
|
556
737
|
#
|
|
557
738
|
# NOTIFICATIONS-API-COMMON
|
|
558
739
|
#
|
|
559
|
-
NOTIFICATIONS_DISABLED = config(
|
|
740
|
+
NOTIFICATIONS_DISABLED = config(
|
|
741
|
+
"NOTIFICATIONS_DISABLED",
|
|
742
|
+
default=False,
|
|
743
|
+
help_text=(
|
|
744
|
+
"indicates whether or not notifications should be sent to the Notificaties API "
|
|
745
|
+
"for operations on the API endpoints. "
|
|
746
|
+
"Defaults to ``True`` for the ``dev`` environment, otherwise defaults to ``False``"
|
|
747
|
+
),
|
|
748
|
+
auto_display_default=False,
|
|
749
|
+
)
|
|
560
750
|
|
|
561
751
|
#
|
|
562
752
|
# SENTRY - error monitoring
|
|
@@ -564,7 +754,15 @@ NOTIFICATIONS_DISABLED = config("NOTIFICATIONS_DISABLED", default=False)
|
|
|
564
754
|
|
|
565
755
|
|
|
566
756
|
def init_sentry(before_send: Callable | None = None):
|
|
567
|
-
SENTRY_DSN = config(
|
|
757
|
+
SENTRY_DSN = config(
|
|
758
|
+
"SENTRY_DSN",
|
|
759
|
+
None,
|
|
760
|
+
help_text=(
|
|
761
|
+
"URL of the sentry project to send error reports to. Default empty, "
|
|
762
|
+
"i.e. -> no monitoring set up. Highly recommended to configure this."
|
|
763
|
+
),
|
|
764
|
+
auto_display_default=False,
|
|
765
|
+
)
|
|
568
766
|
|
|
569
767
|
if SENTRY_DSN:
|
|
570
768
|
SENTRY_CONFIG = {
|
|
@@ -587,8 +785,18 @@ def init_sentry(before_send: Callable | None = None):
|
|
|
587
785
|
#
|
|
588
786
|
# CELERY
|
|
589
787
|
#
|
|
590
|
-
CELERY_BROKER_URL = config(
|
|
591
|
-
|
|
788
|
+
CELERY_BROKER_URL = config(
|
|
789
|
+
"CELERY_RESULT_BACKEND",
|
|
790
|
+
"redis://localhost:6379/1",
|
|
791
|
+
group="Celery",
|
|
792
|
+
help_text="the URL of the backend/broker that will be used by Celery to send the notifications",
|
|
793
|
+
)
|
|
794
|
+
CELERY_RESULT_BACKEND = config(
|
|
795
|
+
"CELERY_RESULT_BACKEND",
|
|
796
|
+
"redis://localhost:6379/1",
|
|
797
|
+
group="Celery",
|
|
798
|
+
help_text="the URL of the backend/broker that will be used by Celery to send the notifications",
|
|
799
|
+
)
|
|
592
800
|
|
|
593
801
|
|
|
594
802
|
#
|
|
@@ -611,15 +819,40 @@ MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT = 5 * 60
|
|
|
611
819
|
#
|
|
612
820
|
# Elastic APM
|
|
613
821
|
#
|
|
614
|
-
ELASTIC_APM_SERVER_URL = config(
|
|
822
|
+
ELASTIC_APM_SERVER_URL = config(
|
|
823
|
+
"ELASTIC_APM_SERVER_URL",
|
|
824
|
+
None,
|
|
825
|
+
"URL where Elastic APM is hosted",
|
|
826
|
+
group="Elastic APM",
|
|
827
|
+
)
|
|
615
828
|
ELASTIC_APM = {
|
|
616
829
|
# FIXME this does change the default service name, because PROJECT_DIRNAME != PROJECT_NAME
|
|
617
830
|
"SERVICE_NAME": config(
|
|
618
|
-
"ELASTIC_APM_SERVICE_NAME",
|
|
831
|
+
"ELASTIC_APM_SERVICE_NAME",
|
|
832
|
+
f"{PROJECT_DIRNAME} - {ENVIRONMENT}",
|
|
833
|
+
help_text=(
|
|
834
|
+
f"Name of the service for this application in Elastic APM. "
|
|
835
|
+
f"Defaults to ``{PROJECT_DIRNAME} - <environment>``"
|
|
836
|
+
),
|
|
837
|
+
group="Elastic APM",
|
|
838
|
+
auto_display_default=False,
|
|
839
|
+
),
|
|
840
|
+
"SECRET_TOKEN": config(
|
|
841
|
+
"ELASTIC_APM_SECRET_TOKEN",
|
|
842
|
+
"default",
|
|
843
|
+
"Token used to communicate with Elastic APM",
|
|
844
|
+
group="Elastic APM",
|
|
619
845
|
),
|
|
620
|
-
"SECRET_TOKEN": config("ELASTIC_APM_SECRET_TOKEN", "default"),
|
|
621
846
|
"SERVER_URL": ELASTIC_APM_SERVER_URL,
|
|
622
|
-
"TRANSACTION_SAMPLE_RATE": config(
|
|
847
|
+
"TRANSACTION_SAMPLE_RATE": config(
|
|
848
|
+
"ELASTIC_APM_TRANSACTION_SAMPLE_RATE",
|
|
849
|
+
0.1,
|
|
850
|
+
help_text=(
|
|
851
|
+
"By default, the agent will sample every transaction (e.g. request to your service). "
|
|
852
|
+
"To reduce overhead and storage requirements, set the sample rate to a value between 0.0 and 1.0"
|
|
853
|
+
),
|
|
854
|
+
group="Elastic APM",
|
|
855
|
+
),
|
|
623
856
|
}
|
|
624
857
|
if not ELASTIC_APM_SERVER_URL:
|
|
625
858
|
ELASTIC_APM["ENABLED"] = False
|
|
@@ -652,7 +885,11 @@ MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = [
|
|
|
652
885
|
|
|
653
886
|
# if DISABLE_2FA is true, fill the MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS with all
|
|
654
887
|
# configured AUTHENTICATION_BACKENDS and thus disabeling the entire 2FA chain.
|
|
655
|
-
if config(
|
|
888
|
+
if config(
|
|
889
|
+
"DISABLE_2FA",
|
|
890
|
+
default=False,
|
|
891
|
+
help_text="Whether or not two factor authentication should be disabled",
|
|
892
|
+
): # pragma: no cover
|
|
656
893
|
MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = AUTHENTICATION_BACKENDS
|
|
657
894
|
|
|
658
895
|
|
|
@@ -660,13 +897,23 @@ if config("DISABLE_2FA", default=False): # pragma: no cover
|
|
|
660
897
|
# LOG OUTGOING REQUESTS
|
|
661
898
|
#
|
|
662
899
|
LOG_OUTGOING_REQUESTS_EMIT_BODY = config(
|
|
663
|
-
"LOG_OUTGOING_REQUESTS_EMIT_BODY",
|
|
900
|
+
"LOG_OUTGOING_REQUESTS_EMIT_BODY",
|
|
901
|
+
default=True,
|
|
902
|
+
help_text="Whether or not outgoing request bodies should be logged",
|
|
903
|
+
)
|
|
904
|
+
LOG_OUTGOING_REQUESTS_DB_SAVE = config(
|
|
905
|
+
"LOG_OUTGOING_REQUESTS_DB_SAVE",
|
|
906
|
+
default=False,
|
|
907
|
+
help_text="Whether or not outgoing request logs should be saved to the database",
|
|
664
908
|
)
|
|
665
909
|
LOG_OUTGOING_REQUESTS_DB_SAVE_BODY = config(
|
|
666
|
-
"LOG_OUTGOING_REQUESTS_DB_SAVE_BODY",
|
|
910
|
+
"LOG_OUTGOING_REQUESTS_DB_SAVE_BODY",
|
|
911
|
+
default=True,
|
|
912
|
+
help_text="Whether or not outgoing request bodies should be saved to the database",
|
|
667
913
|
)
|
|
668
|
-
LOG_OUTGOING_REQUESTS_DB_SAVE = config("LOG_OUTGOING_REQUESTS_DB_SAVE", default=False)
|
|
669
914
|
LOG_OUTGOING_REQUESTS_RESET_DB_SAVE_AFTER = None
|
|
670
915
|
LOG_OUTGOING_REQUESTS_MAX_AGE = config(
|
|
671
|
-
"LOG_OUTGOING_REQUESTS_MAX_AGE",
|
|
916
|
+
"LOG_OUTGOING_REQUESTS_MAX_AGE",
|
|
917
|
+
default=7,
|
|
918
|
+
help_text="The amount of time after which request logs should be deleted from the database",
|
|
672
919
|
) # number of days
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
from decouple import Csv, Undefined, config as _config, undefined
|
|
8
|
+
from sentry_sdk.integrations import DidNotEnable, django, redis
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class EnvironmentVariable:
|
|
13
|
+
name: str
|
|
14
|
+
default: Any
|
|
15
|
+
help_text: str
|
|
16
|
+
group: Optional[str] = None
|
|
17
|
+
auto_display_default: bool = True
|
|
18
|
+
|
|
19
|
+
def __post_init__(self):
|
|
20
|
+
if not self.group:
|
|
21
|
+
self.group = (
|
|
22
|
+
"Required" if isinstance(self.default, Undefined) else "Optional"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def __eq__(self, other):
|
|
26
|
+
return isinstance(other, EnvironmentVariable) and self.name == other.name
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
ENVVAR_REGISTRY = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def config(
|
|
33
|
+
option: str,
|
|
34
|
+
default: Any = undefined,
|
|
35
|
+
help_text="",
|
|
36
|
+
group=None,
|
|
37
|
+
add_to_docs=True,
|
|
38
|
+
auto_display_default=True,
|
|
39
|
+
*args,
|
|
40
|
+
**kwargs,
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
An override of ``decouple.config``, with custom options to construct documentation
|
|
44
|
+
for environment variables.
|
|
45
|
+
|
|
46
|
+
Pull a config parameter from the environment.
|
|
47
|
+
|
|
48
|
+
Read the config variable ``option``. If it's optional, use the ``default`` value.
|
|
49
|
+
Input is automatically cast to the correct type, where the type is derived from the
|
|
50
|
+
default value if possible.
|
|
51
|
+
|
|
52
|
+
Pass ``split=True`` to split the comma-separated input into a list.
|
|
53
|
+
|
|
54
|
+
Additionally, the variable is added to a registry that is used to construct documentation
|
|
55
|
+
via the ``generate_envvar_docs`` management command. The following arguments are added for this:
|
|
56
|
+
|
|
57
|
+
:param help_text: The help text to be displayed for this variable in the documentation. Default `""`
|
|
58
|
+
:param group: The name of the section under which this variable will be grouped. Default ``None``
|
|
59
|
+
:param add_to_docs: Whether or not this variable will be displayed in the documentation. Default ``True``
|
|
60
|
+
:param auto_display_default: Whether or not the passed ``default`` value is displayed in the docs, this can be
|
|
61
|
+
set to ``False`` in case a default needs more explanation that can be added to the ``help_text``
|
|
62
|
+
(e.g. if it is computed or based on another variable). Default ``True``
|
|
63
|
+
"""
|
|
64
|
+
if add_to_docs:
|
|
65
|
+
variable = EnvironmentVariable(
|
|
66
|
+
name=option,
|
|
67
|
+
default=default,
|
|
68
|
+
help_text=help_text,
|
|
69
|
+
group=group,
|
|
70
|
+
auto_display_default=auto_display_default,
|
|
71
|
+
)
|
|
72
|
+
if variable not in ENVVAR_REGISTRY:
|
|
73
|
+
ENVVAR_REGISTRY.append(variable)
|
|
74
|
+
else:
|
|
75
|
+
# If the same variable is defined again (i.e. because a project defines a custom default), override it
|
|
76
|
+
ENVVAR_REGISTRY[ENVVAR_REGISTRY.index(variable)] = variable
|
|
77
|
+
|
|
78
|
+
if "split" in kwargs:
|
|
79
|
+
kwargs.pop("split")
|
|
80
|
+
kwargs["cast"] = Csv()
|
|
81
|
+
if isinstance(default, list):
|
|
82
|
+
default = ",".join(default)
|
|
83
|
+
|
|
84
|
+
if default is not undefined and default is not None:
|
|
85
|
+
kwargs.setdefault("cast", type(default))
|
|
86
|
+
return _config(option, default=default, *args, **kwargs)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_sentry_integrations() -> list:
|
|
90
|
+
"""
|
|
91
|
+
Determine which Sentry SDK integrations to enable.
|
|
92
|
+
"""
|
|
93
|
+
default = [
|
|
94
|
+
django.DjangoIntegration(),
|
|
95
|
+
redis.RedisIntegration(),
|
|
96
|
+
]
|
|
97
|
+
extra = []
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
from sentry_sdk.integrations import celery
|
|
101
|
+
except DidNotEnable: # happens if the celery import fails by the integration
|
|
102
|
+
pass
|
|
103
|
+
else:
|
|
104
|
+
extra.append(celery.CeleryIntegration())
|
|
105
|
+
|
|
106
|
+
return [*default, *extra]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def strip_protocol_from_origin(origin: str) -> str:
|
|
110
|
+
parsed = urlparse(origin)
|
|
111
|
+
return parsed.netloc
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_project_dirname() -> str:
|
|
115
|
+
return config("DJANGO_SETTINGS_MODULE", add_to_docs=False).split(".")[0]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_django_project_dir() -> str:
|
|
119
|
+
# Get the path of the importing module
|
|
120
|
+
base_dirname = get_project_dirname()
|
|
121
|
+
return Path(sys.modules[base_dirname].__file__).parent
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
|
|
4
|
+
from django.core.management.base import BaseCommand
|
|
5
|
+
from django.template import loader
|
|
6
|
+
|
|
7
|
+
from open_api_framework.conf.utils import EnvironmentVariable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def convert_variables_to_rst(variables: list[EnvironmentVariable]) -> str:
|
|
11
|
+
template = loader.get_template("open_api_framework/env_config.rst")
|
|
12
|
+
grouped_vars = defaultdict(list)
|
|
13
|
+
for var in variables:
|
|
14
|
+
if not var.help_text:
|
|
15
|
+
warnings.warn(f"missing help_text for environment variable {var}")
|
|
16
|
+
grouped_vars[var.group].append(var)
|
|
17
|
+
return template.render({"vars": grouped_vars.items()})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Command(BaseCommand):
|
|
21
|
+
help = "Generate documentation for all used envvars"
|
|
22
|
+
|
|
23
|
+
def add_arguments(self, parser):
|
|
24
|
+
super().add_arguments(parser)
|
|
25
|
+
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--file",
|
|
28
|
+
help="Name and path of the file to which the documentation will be written.",
|
|
29
|
+
nargs="?",
|
|
30
|
+
default="docs/env_config.rst",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--exclude-group",
|
|
34
|
+
help="Names of groups that should not be excluded in the generated docs.",
|
|
35
|
+
action="append",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def handle(self, *args, **options):
|
|
39
|
+
from open_api_framework.conf.utils import ENVVAR_REGISTRY
|
|
40
|
+
|
|
41
|
+
file_path = options["file"]
|
|
42
|
+
exclude_groups = options["exclude_group"] or []
|
|
43
|
+
|
|
44
|
+
def _sort(envvar):
|
|
45
|
+
match envvar.group:
|
|
46
|
+
case "Required":
|
|
47
|
+
return 0
|
|
48
|
+
case "Optional":
|
|
49
|
+
return 2
|
|
50
|
+
case _:
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
sorted_registry = sorted(
|
|
54
|
+
[var for var in ENVVAR_REGISTRY if var.group not in exclude_groups],
|
|
55
|
+
key=_sort,
|
|
56
|
+
)
|
|
57
|
+
with open(file_path, "w") as f:
|
|
58
|
+
f.write(convert_variables_to_rst(sorted_registry))
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{% load doc_tags %}.. _installation_env_config:
|
|
2
|
+
|
|
3
|
+
===================================
|
|
4
|
+
Environment configuration reference
|
|
5
|
+
===================================
|
|
6
|
+
|
|
7
|
+
{% block intro %}{% endblock %}
|
|
8
|
+
|
|
9
|
+
Available environment variables
|
|
10
|
+
===============================
|
|
11
|
+
|
|
12
|
+
{% for group_name, vars in vars %}
|
|
13
|
+
{{group_name}}
|
|
14
|
+
{{group_name|repeat_char:"-"}}
|
|
15
|
+
|
|
16
|
+
{% for var in vars %}* ``{{var.name}}``: {% if var.help_text %}{{var.help_text|safe|ensure_endswith:"."}}{% endif %}{% if var.auto_display_default and not var.default|is_undefined %} Defaults to: ``{{var.default|to_str}}``.{% endif %}
|
|
17
|
+
{% endfor %}
|
|
18
|
+
{% endfor %}
|
|
19
|
+
|
|
20
|
+
{% block extra %}{% endblock %}
|
|
21
|
+
|
|
22
|
+
Specifying the environment variables
|
|
23
|
+
=====================================
|
|
24
|
+
|
|
25
|
+
There are two strategies to specify the environment variables:
|
|
26
|
+
|
|
27
|
+
* provide them in a ``.env`` file
|
|
28
|
+
* start the component processes (with uwsgi/gunicorn/celery) in a process
|
|
29
|
+
manager that defines the environment variables
|
|
30
|
+
|
|
31
|
+
Providing a .env file
|
|
32
|
+
---------------------
|
|
33
|
+
|
|
34
|
+
This is the most simple setup and easiest to debug. The ``.env`` file must be
|
|
35
|
+
at the root of the project - i.e. on the same level as the ``src`` directory (
|
|
36
|
+
NOT *in* the ``src`` directory).
|
|
37
|
+
|
|
38
|
+
The syntax is key-value:
|
|
39
|
+
|
|
40
|
+
.. code::
|
|
41
|
+
|
|
42
|
+
SOME_VAR=some_value
|
|
43
|
+
OTHER_VAR="quoted_value"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Provide the envvars via the process manager
|
|
47
|
+
-------------------------------------------
|
|
48
|
+
|
|
49
|
+
If you use a process manager (such as supervisor/systemd), use their techniques
|
|
50
|
+
to define the envvars. The component will pick them up out of the box.
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from django import template
|
|
2
|
+
|
|
3
|
+
from decouple import Undefined
|
|
4
|
+
|
|
5
|
+
register = template.Library()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@register.filter(name="repeat_char")
|
|
9
|
+
def repeat_char(value, char="-"):
|
|
10
|
+
try:
|
|
11
|
+
length = len(value)
|
|
12
|
+
return char * length
|
|
13
|
+
except TypeError:
|
|
14
|
+
return ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register.filter(name="is_undefined")
|
|
18
|
+
def is_undefined(value):
|
|
19
|
+
return isinstance(value, Undefined)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register.filter(name="to_str")
|
|
23
|
+
def to_str(value):
|
|
24
|
+
if value == "":
|
|
25
|
+
return "(empty string)"
|
|
26
|
+
return str(value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@register.filter(name="ensure_endswith")
|
|
30
|
+
def ensure_endswith(value, char):
|
|
31
|
+
if not isinstance(value, str):
|
|
32
|
+
value = str(value)
|
|
33
|
+
if not value.endswith(char):
|
|
34
|
+
value += char
|
|
35
|
+
return value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: open_api_framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A metapackage for registration components, that bundles the dependencies shared between these components and provides generic settings
|
|
5
5
|
Author-email: Maykin Media <support@maykinmedia.nl>
|
|
6
6
|
License: Copyright © Maykin 2024
|
|
@@ -30,33 +30,33 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
30
30
|
Requires-Python: >=3.9
|
|
31
31
|
Description-Content-Type: text/x-rst
|
|
32
32
|
License-File: LICENSE
|
|
33
|
-
Requires-Dist: django
|
|
34
|
-
Requires-Dist: django-axes>=6.
|
|
35
|
-
Requires-Dist: django-cors-headers>=4.
|
|
33
|
+
Requires-Dist: django<5.0.0,>=4.2.14
|
|
34
|
+
Requires-Dist: django-axes>=6.5.1
|
|
35
|
+
Requires-Dist: django-cors-headers>=4.4.0
|
|
36
36
|
Requires-Dist: django-markup>=1.8.1
|
|
37
|
-
Requires-Dist: django-jsonform>=2.
|
|
37
|
+
Requires-Dist: django-jsonform>=2.22.0
|
|
38
38
|
Requires-Dist: django-log-outgoing-requests>=0.6.1
|
|
39
39
|
Requires-Dist: django-admin-index>=3.1.0
|
|
40
40
|
Requires-Dist: django-redis>=5.4.0
|
|
41
|
-
Requires-Dist: djangorestframework>=3.
|
|
41
|
+
Requires-Dist: djangorestframework>=3.15.2
|
|
42
42
|
Requires-Dist: djangorestframework-gis>=1.0
|
|
43
|
-
Requires-Dist: django-filter>=
|
|
44
|
-
Requires-Dist: drf-spectacular>=0.27.
|
|
43
|
+
Requires-Dist: django-filter>=24.2
|
|
44
|
+
Requires-Dist: drf-spectacular>=0.27.2
|
|
45
45
|
Requires-Dist: djangorestframework-inclusions>=1.2.0
|
|
46
46
|
Requires-Dist: commonground-api-common>=1.12.1
|
|
47
|
-
Requires-Dist: mozilla-django-oidc-db>=0.
|
|
47
|
+
Requires-Dist: mozilla-django-oidc-db>=0.19.0
|
|
48
48
|
Requires-Dist: zgw-consumers>=0.27.0
|
|
49
49
|
Requires-Dist: psycopg2>=2.9.9
|
|
50
50
|
Requires-Dist: bleach>=6.1.0
|
|
51
51
|
Requires-Dist: python-dotenv>=1.0.0
|
|
52
52
|
Requires-Dist: python-decouple>=3.8
|
|
53
|
-
Requires-Dist: requests>=2.
|
|
53
|
+
Requires-Dist: requests>=2.32.3
|
|
54
54
|
Requires-Dist: uwsgi>=2.0.23
|
|
55
|
-
Requires-Dist: sentry-sdk>=
|
|
56
|
-
Requires-Dist: elastic-apm>=6.
|
|
57
|
-
Requires-Dist: celery>=5.
|
|
55
|
+
Requires-Dist: sentry-sdk>=2.11.0
|
|
56
|
+
Requires-Dist: elastic-apm>=6.22.0
|
|
57
|
+
Requires-Dist: celery>=5.4.0
|
|
58
58
|
Requires-Dist: flower>=2.0.1
|
|
59
|
-
Requires-Dist: maykin-2fa>=1.0.
|
|
59
|
+
Requires-Dist: maykin-2fa>=1.0.1
|
|
60
60
|
Requires-Dist: django-setup-configuration>=0.1.0
|
|
61
61
|
Provides-Extra: tests
|
|
62
62
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -77,7 +77,7 @@ Requires-Dist: twine; extra == "release"
|
|
|
77
77
|
Open API Framework
|
|
78
78
|
==================
|
|
79
79
|
|
|
80
|
-
:Version: 0.
|
|
80
|
+
:Version: 0.7.0
|
|
81
81
|
:Source: https://github.com/maykinmedia/open-api-framework
|
|
82
82
|
:Keywords: metapackage, dependencies
|
|
83
83
|
|
{open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/SOURCES.txt
RENAMED
|
@@ -16,10 +16,16 @@ open_api_framework.egg-info/top_level.txt
|
|
|
16
16
|
open_api_framework/conf/__init__.py
|
|
17
17
|
open_api_framework/conf/base.py
|
|
18
18
|
open_api_framework/conf/utils.py
|
|
19
|
+
open_api_framework/management/__init__.py
|
|
20
|
+
open_api_framework/management/commands/__init__.py
|
|
21
|
+
open_api_framework/management/commands/generate_envvar_docs.py
|
|
19
22
|
open_api_framework/static/open_api_framework/css/admin.css
|
|
23
|
+
open_api_framework/templates/open_api_framework/env_config.rst
|
|
20
24
|
open_api_framework/templates/open_api_framework/admin/base_site.html
|
|
21
25
|
open_api_framework/templates/open_api_framework/components/environment.html
|
|
22
26
|
open_api_framework/templates/open_api_framework/components/version.html
|
|
23
27
|
open_api_framework/templatetags/__init__.py
|
|
28
|
+
open_api_framework/templatetags/doc_tags.py
|
|
24
29
|
open_api_framework/templatetags/open_api_framework.py
|
|
25
|
-
tests/test_config_helpers.py
|
|
30
|
+
tests/test_config_helpers.py
|
|
31
|
+
tests/test_generate_envvar_docs.py
|
{open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/requires.txt
RENAMED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
django
|
|
2
|
-
django-axes>=6.
|
|
3
|
-
django-cors-headers>=4.
|
|
1
|
+
django<5.0.0,>=4.2.14
|
|
2
|
+
django-axes>=6.5.1
|
|
3
|
+
django-cors-headers>=4.4.0
|
|
4
4
|
django-markup>=1.8.1
|
|
5
|
-
django-jsonform>=2.
|
|
5
|
+
django-jsonform>=2.22.0
|
|
6
6
|
django-log-outgoing-requests>=0.6.1
|
|
7
7
|
django-admin-index>=3.1.0
|
|
8
8
|
django-redis>=5.4.0
|
|
9
|
-
djangorestframework>=3.
|
|
9
|
+
djangorestframework>=3.15.2
|
|
10
10
|
djangorestframework-gis>=1.0
|
|
11
|
-
django-filter>=
|
|
12
|
-
drf-spectacular>=0.27.
|
|
11
|
+
django-filter>=24.2
|
|
12
|
+
drf-spectacular>=0.27.2
|
|
13
13
|
djangorestframework-inclusions>=1.2.0
|
|
14
14
|
commonground-api-common>=1.12.1
|
|
15
|
-
mozilla-django-oidc-db>=0.
|
|
15
|
+
mozilla-django-oidc-db>=0.19.0
|
|
16
16
|
zgw-consumers>=0.27.0
|
|
17
17
|
psycopg2>=2.9.9
|
|
18
18
|
bleach>=6.1.0
|
|
19
19
|
python-dotenv>=1.0.0
|
|
20
20
|
python-decouple>=3.8
|
|
21
|
-
requests>=2.
|
|
21
|
+
requests>=2.32.3
|
|
22
22
|
uwsgi>=2.0.23
|
|
23
|
-
sentry-sdk>=
|
|
24
|
-
elastic-apm>=6.
|
|
25
|
-
celery>=5.
|
|
23
|
+
sentry-sdk>=2.11.0
|
|
24
|
+
elastic-apm>=6.22.0
|
|
25
|
+
celery>=5.4.0
|
|
26
26
|
flower>=2.0.1
|
|
27
|
-
maykin-2fa>=1.0.
|
|
27
|
+
maykin-2fa>=1.0.1
|
|
28
28
|
django-setup-configuration>=0.1.0
|
|
29
29
|
|
|
30
30
|
[coverage]
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "open_api_framework"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "A metapackage for registration components, that bundles the dependencies shared between these components and provides generic settings"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Maykin Media", email = "support@maykinmedia.nl"}
|
|
@@ -27,33 +27,33 @@ classifiers = [
|
|
|
27
27
|
]
|
|
28
28
|
requires-python = ">=3.9"
|
|
29
29
|
dependencies = [
|
|
30
|
-
"django>=4.2.
|
|
31
|
-
"django-axes>=6.
|
|
32
|
-
"django-cors-headers>=4.
|
|
30
|
+
"django>=4.2.14,<5.0.0",
|
|
31
|
+
"django-axes>=6.5.1",
|
|
32
|
+
"django-cors-headers>=4.4.0",
|
|
33
33
|
"django-markup>=1.8.1",
|
|
34
|
-
"django-jsonform>=2.
|
|
34
|
+
"django-jsonform>=2.22.0",
|
|
35
35
|
"django-log-outgoing-requests>=0.6.1",
|
|
36
36
|
"django-admin-index>=3.1.0",
|
|
37
37
|
"django-redis>=5.4.0",
|
|
38
|
-
"djangorestframework>=3.
|
|
38
|
+
"djangorestframework>=3.15.2",
|
|
39
39
|
"djangorestframework-gis>=1.0",
|
|
40
|
-
"django-filter>=
|
|
41
|
-
"drf-spectacular>=0.27.
|
|
40
|
+
"django-filter>=24.2",
|
|
41
|
+
"drf-spectacular>=0.27.2",
|
|
42
42
|
"djangorestframework-inclusions>=1.2.0",
|
|
43
43
|
"commonground-api-common>=1.12.1",
|
|
44
|
-
"mozilla-django-oidc-db>=0.
|
|
44
|
+
"mozilla-django-oidc-db>=0.19.0",
|
|
45
45
|
"zgw-consumers>=0.27.0",
|
|
46
46
|
"psycopg2>=2.9.9",
|
|
47
47
|
"bleach>=6.1.0",
|
|
48
48
|
"python-dotenv>=1.0.0",
|
|
49
49
|
"python-decouple>=3.8",
|
|
50
|
-
"requests>=2.
|
|
50
|
+
"requests>=2.32.3",
|
|
51
51
|
"uwsgi>=2.0.23",
|
|
52
|
-
"sentry-sdk>=
|
|
53
|
-
"elastic-apm>=6.
|
|
54
|
-
"celery>=5.
|
|
52
|
+
"sentry-sdk>=2.11.0",
|
|
53
|
+
"elastic-apm>=6.22.0",
|
|
54
|
+
"celery>=5.4.0",
|
|
55
55
|
"flower>=2.0.1",
|
|
56
|
-
"maykin-2fa>=1.0.
|
|
56
|
+
"maykin-2fa>=1.0.1",
|
|
57
57
|
"django-setup-configuration>=0.1.0",
|
|
58
58
|
]
|
|
59
59
|
|
|
@@ -100,7 +100,7 @@ testpaths = ["tests"]
|
|
|
100
100
|
DJANGO_SETTINGS_MODULE = "testapp.settings"
|
|
101
101
|
|
|
102
102
|
[tool.bumpversion]
|
|
103
|
-
current_version = "0.
|
|
103
|
+
current_version = "0.7.0"
|
|
104
104
|
files = [
|
|
105
105
|
{filename = "pyproject.toml"},
|
|
106
106
|
{filename = "README.rst"},
|
|
@@ -2,12 +2,12 @@ from open_api_framework.conf.utils import config
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def test_empty_list_as_default():
|
|
5
|
-
value = config("SOME_TEST_ENVVAR", split=True, default=[])
|
|
5
|
+
value = config("SOME_TEST_ENVVAR", split=True, default=[], add_to_docs=False)
|
|
6
6
|
|
|
7
7
|
assert value == []
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def test_non_empty_list_as_default():
|
|
11
|
-
value = config("SOME_TEST_ENVVAR", split=True, default=["foo"])
|
|
11
|
+
value = config("SOME_TEST_ENVVAR", split=True, default=["foo"], add_to_docs=False)
|
|
12
12
|
|
|
13
13
|
assert value == ["foo"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from unittest.mock import mock_open, patch
|
|
2
|
+
|
|
3
|
+
from django.core.management import call_command
|
|
4
|
+
|
|
5
|
+
EXPECTED_OUTPUT = """\
|
|
6
|
+
.. _installation_env_config:
|
|
7
|
+
|
|
8
|
+
===================================
|
|
9
|
+
Environment configuration reference
|
|
10
|
+
===================================
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Available environment variables
|
|
15
|
+
===============================
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Required
|
|
19
|
+
--------
|
|
20
|
+
|
|
21
|
+
* ``SECRET_KEY``: Secret key that's used for certain cryptographic utilities. Defaults to: \
|
|
22
|
+
``so-secret-i-cant-believe-you-are-looking-at-this``.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Optional
|
|
26
|
+
--------
|
|
27
|
+
|
|
28
|
+
* ``DEBUG``: Only set this to ``True`` on a local development environment. Various other \
|
|
29
|
+
security settings are derived from this setting!. Defaults to: ``False``.
|
|
30
|
+
* ``IS_HTTPS``: Used to construct absolute URLs and controls a variety of security settings. \
|
|
31
|
+
Defaults to the inverse of ``DEBUG``.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Specifying the environment variables
|
|
38
|
+
=====================================
|
|
39
|
+
|
|
40
|
+
There are two strategies to specify the environment variables:
|
|
41
|
+
|
|
42
|
+
* provide them in a ``.env`` file
|
|
43
|
+
* start the component processes (with uwsgi/gunicorn/celery) in a process
|
|
44
|
+
manager that defines the environment variables
|
|
45
|
+
|
|
46
|
+
Providing a .env file
|
|
47
|
+
---------------------
|
|
48
|
+
|
|
49
|
+
This is the most simple setup and easiest to debug. The ``.env`` file must be
|
|
50
|
+
at the root of the project - i.e. on the same level as the ``src`` directory (
|
|
51
|
+
NOT *in* the ``src`` directory).
|
|
52
|
+
|
|
53
|
+
The syntax is key-value:
|
|
54
|
+
|
|
55
|
+
.. code::
|
|
56
|
+
|
|
57
|
+
SOME_VAR=some_value
|
|
58
|
+
OTHER_VAR="quoted_value"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Provide the envvars via the process manager
|
|
62
|
+
-------------------------------------------
|
|
63
|
+
|
|
64
|
+
If you use a process manager (such as supervisor/systemd), use their techniques
|
|
65
|
+
to define the envvars. The component will pick them up out of the box.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_generate_envvar_docs():
|
|
70
|
+
mock_file = mock_open()
|
|
71
|
+
with patch(
|
|
72
|
+
"open_api_framework.management.commands.generate_envvar_docs.open", mock_file
|
|
73
|
+
):
|
|
74
|
+
call_command(
|
|
75
|
+
"generate_envvar_docs", file="some/file/path.txt", exclude_group="Excluded"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
mock_file.assert_called_once_with("some/file/path.txt", "w")
|
|
79
|
+
|
|
80
|
+
handle = mock_file()
|
|
81
|
+
|
|
82
|
+
# Check the entire content written to the mock file
|
|
83
|
+
written_content = "".join(call.args[0] for call in handle.write.call_args_list)
|
|
84
|
+
|
|
85
|
+
assert written_content == EXPECTED_OUTPUT
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Any
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
|
-
|
|
6
|
-
from decouple import Csv, config as _config, undefined
|
|
7
|
-
from sentry_sdk.integrations import DidNotEnable, django, redis
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def config(option: str, default: Any = undefined, *args, **kwargs):
|
|
11
|
-
"""
|
|
12
|
-
Pull a config parameter from the environment.
|
|
13
|
-
|
|
14
|
-
Read the config variable ``option``. If it's optional, use the ``default`` value.
|
|
15
|
-
Input is automatically cast to the correct type, where the type is derived from the
|
|
16
|
-
default value if possible.
|
|
17
|
-
|
|
18
|
-
Pass ``split=True`` to split the comma-separated input into a list.
|
|
19
|
-
"""
|
|
20
|
-
if "split" in kwargs:
|
|
21
|
-
kwargs.pop("split")
|
|
22
|
-
kwargs["cast"] = Csv()
|
|
23
|
-
if isinstance(default, list):
|
|
24
|
-
default = ",".join(default)
|
|
25
|
-
|
|
26
|
-
if default is not undefined and default is not None:
|
|
27
|
-
kwargs.setdefault("cast", type(default))
|
|
28
|
-
return _config(option, default=default, *args, **kwargs)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_sentry_integrations() -> list:
|
|
32
|
-
"""
|
|
33
|
-
Determine which Sentry SDK integrations to enable.
|
|
34
|
-
"""
|
|
35
|
-
default = [
|
|
36
|
-
django.DjangoIntegration(),
|
|
37
|
-
redis.RedisIntegration(),
|
|
38
|
-
]
|
|
39
|
-
extra = []
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
from sentry_sdk.integrations import celery
|
|
43
|
-
except DidNotEnable: # happens if the celery import fails by the integration
|
|
44
|
-
pass
|
|
45
|
-
else:
|
|
46
|
-
extra.append(celery.CeleryIntegration())
|
|
47
|
-
|
|
48
|
-
return [*default, *extra]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def strip_protocol_from_origin(origin: str) -> str:
|
|
52
|
-
parsed = urlparse(origin)
|
|
53
|
-
return parsed.netloc
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def get_project_dirname() -> str:
|
|
57
|
-
return config("DJANGO_SETTINGS_MODULE").split(".")[0]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def get_django_project_dir() -> str:
|
|
61
|
-
# Get the path of the importing module
|
|
62
|
-
base_dirname = get_project_dirname()
|
|
63
|
-
return Path(sys.modules[base_dirname].__file__).parent
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/context_processors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|