open-api-framework 0.6.1__tar.gz → 0.7.1__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.1 → open_api_framework-0.7.1}/CHANGELOG.rst +14 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/MANIFEST.in +1 -0
- {open_api_framework-0.6.1/open_api_framework.egg-info → open_api_framework-0.7.1}/PKG-INFO +2 -2
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/README.rst +1 -1
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/conf/base.py +306 -44
- open_api_framework-0.7.1/open_api_framework/conf/utils.py +121 -0
- open_api_framework-0.7.1/open_api_framework/management/commands/generate_envvar_docs.py +58 -0
- open_api_framework-0.7.1/open_api_framework/py.typed +0 -0
- open_api_framework-0.7.1/open_api_framework/templates/open_api_framework/env_config.rst +50 -0
- open_api_framework-0.7.1/open_api_framework/templatetags/__init__.py +0 -0
- open_api_framework-0.7.1/open_api_framework/templatetags/doc_tags.py +35 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1/open_api_framework.egg-info}/PKG-INFO +2 -2
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework.egg-info/SOURCES.txt +7 -1
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/pyproject.toml +2 -2
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/tests/test_config_helpers.py +2 -2
- open_api_framework-0.7.1/tests/test_generate_envvar_docs.py +85 -0
- open_api_framework-0.6.1/open_api_framework/conf/utils.py +0 -63
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/LICENSE +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/__init__.py +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/apps.py +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/conf/__init__.py +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/context_processors.py +0 -0
- {open_api_framework-0.6.1/open_api_framework/templatetags → open_api_framework-0.7.1/open_api_framework/management}/__init__.py +0 -0
- /open_api_framework-0.6.1/open_api_framework/py.typed → /open_api_framework-0.7.1/open_api_framework/management/commands/__init__.py +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/static/open_api_framework/css/admin.css +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/templates/open_api_framework/admin/base_site.html +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/templates/open_api_framework/components/environment.html +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/templates/open_api_framework/components/version.html +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework/templatetags/open_api_framework.py +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework.egg-info/dependency_links.txt +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework.egg-info/requires.txt +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework.egg-info/top_level.txt +0 -0
- {open_api_framework-0.6.1 → open_api_framework-0.7.1}/setup.cfg +0 -0
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
0.7.1 (2024-08-16)
|
|
5
|
+
------------------
|
|
6
|
+
|
|
7
|
+
**Bugfixes**
|
|
8
|
+
|
|
9
|
+
* Add missing help_text for SESSION_COOKIE_SAMESITE and CSRF_COOKIE_SAMESITE envvars
|
|
10
|
+
|
|
11
|
+
0.7.0 (2024-08-15)
|
|
12
|
+
------------------
|
|
13
|
+
|
|
14
|
+
**New features**
|
|
15
|
+
|
|
16
|
+
* Management command to generate documentation for environment variables
|
|
17
|
+
* Made SESSION_COOKIE_SAMESITE and CSRF_COOKIE_SAMESITE configurable via environment variables (default ``Strict``)
|
|
4
18
|
|
|
5
19
|
0.6.1 (2024-07-31)
|
|
6
20
|
------------------
|
|
@@ -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.1
|
|
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
|
|
@@ -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.1
|
|
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,25 @@ 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(
|
|
543
|
+
"SESSION_COOKIE_SAMESITE",
|
|
544
|
+
"Strict",
|
|
545
|
+
help_text=(
|
|
546
|
+
"The value of the SameSite flag on the session cookie. This flag prevents the "
|
|
547
|
+
"cookie from being sent in cross-site requests thus preventing CSRF attacks and "
|
|
548
|
+
"making some methods of stealing session cookie impossible."
|
|
549
|
+
),
|
|
550
|
+
)
|
|
419
551
|
|
|
420
552
|
CSRF_COOKIE_SECURE = IS_HTTPS
|
|
553
|
+
CSRF_COOKIE_SAMESITE = config(
|
|
554
|
+
"CSRF_COOKIE_SAMESITE",
|
|
555
|
+
"Strict",
|
|
556
|
+
help_text=(
|
|
557
|
+
"The value of the SameSite flag on the CSRF cookie. This flag prevents the cookie "
|
|
558
|
+
"from being sent in cross-site requests."
|
|
559
|
+
),
|
|
560
|
+
)
|
|
421
561
|
|
|
422
562
|
X_FRAME_OPTIONS = "DENY"
|
|
423
563
|
|
|
@@ -438,11 +578,28 @@ DATA_UPLOAD_MAX_NUMBER_FIELDS = 10000
|
|
|
438
578
|
#
|
|
439
579
|
# Custom settings
|
|
440
580
|
#
|
|
441
|
-
ENVIRONMENT = config(
|
|
581
|
+
ENVIRONMENT = config(
|
|
582
|
+
"ENVIRONMENT",
|
|
583
|
+
"",
|
|
584
|
+
help_text=(
|
|
585
|
+
"An identifier for the environment, displayed in the admin depending on "
|
|
586
|
+
"the settings module used and included in the error monitoring (see ``SENTRY_DSN``). "
|
|
587
|
+
"The default is set according to ``DJANGO_SETTINGS_MODULE``."
|
|
588
|
+
),
|
|
589
|
+
auto_display_default=False,
|
|
590
|
+
)
|
|
442
591
|
ENVIRONMENT_SHOWN_IN_ADMIN = True
|
|
443
592
|
|
|
444
593
|
# Generating the schema, depending on the component
|
|
445
|
-
subpath = config(
|
|
594
|
+
subpath = config(
|
|
595
|
+
"SUBPATH",
|
|
596
|
+
None,
|
|
597
|
+
help_text=(
|
|
598
|
+
"If hosted on a subpath, provide the value here. If you provide ``/gateway``, "
|
|
599
|
+
"the component assumes its running at the base URL: ``https://somedomain/gateway/``. "
|
|
600
|
+
"Defaults to an empty string."
|
|
601
|
+
),
|
|
602
|
+
)
|
|
446
603
|
if subpath:
|
|
447
604
|
if not subpath.startswith("/"):
|
|
448
605
|
subpath = f"/{subpath}"
|
|
@@ -462,12 +619,22 @@ elif (Path(BASE_DIR) / ".git").exists():
|
|
|
462
619
|
else:
|
|
463
620
|
GIT_SHA = None
|
|
464
621
|
|
|
465
|
-
RELEASE = config(
|
|
622
|
+
RELEASE = config(
|
|
623
|
+
"RELEASE",
|
|
624
|
+
GIT_SHA,
|
|
625
|
+
help_text="The version number or commit hash of the application (this is also sent to Sentry).",
|
|
626
|
+
auto_display_default=False,
|
|
627
|
+
)
|
|
466
628
|
|
|
467
629
|
NUM_PROXIES = config( # TODO: this also is relevant for DRF settings if/when we have rate-limited endpoints
|
|
468
630
|
"NUM_PROXIES",
|
|
469
631
|
default=1,
|
|
470
632
|
cast=lambda val: int(val) if val is not None else None,
|
|
633
|
+
help_text=(
|
|
634
|
+
"the number of reverse proxies in front of the application, as an integer. "
|
|
635
|
+
"This is used to determine the actual client IP adres. "
|
|
636
|
+
"On Kubernetes with an ingress you typically want to set this to 2."
|
|
637
|
+
),
|
|
471
638
|
)
|
|
472
639
|
|
|
473
640
|
##############################
|
|
@@ -516,10 +683,28 @@ IPWARE_META_PRECEDENCE_ORDER = (
|
|
|
516
683
|
#
|
|
517
684
|
# DJANGO-CORS-MIDDLEWARE
|
|
518
685
|
#
|
|
519
|
-
CORS_ALLOW_ALL_ORIGINS = config(
|
|
520
|
-
|
|
686
|
+
CORS_ALLOW_ALL_ORIGINS = config(
|
|
687
|
+
"CORS_ALLOW_ALL_ORIGINS",
|
|
688
|
+
default=False,
|
|
689
|
+
group="Cross-Origin-Resource-Sharing",
|
|
690
|
+
help_text="allow cross-domain access from any client",
|
|
691
|
+
)
|
|
692
|
+
CORS_ALLOWED_ORIGINS = config(
|
|
693
|
+
"CORS_ALLOWED_ORIGINS",
|
|
694
|
+
split=True,
|
|
695
|
+
default=[],
|
|
696
|
+
group="Cross-Origin-Resource-Sharing",
|
|
697
|
+
help_text=(
|
|
698
|
+
"explicitly list the allowed origins for cross-domain requests. "
|
|
699
|
+
"Example: http://localhost:3000,https://some-app.gemeente.nl"
|
|
700
|
+
),
|
|
701
|
+
)
|
|
521
702
|
CORS_ALLOWED_ORIGIN_REGEXES = config(
|
|
522
|
-
"CORS_ALLOWED_ORIGIN_REGEXES",
|
|
703
|
+
"CORS_ALLOWED_ORIGIN_REGEXES",
|
|
704
|
+
split=True,
|
|
705
|
+
default=[],
|
|
706
|
+
group="Cross-Origin-Resource-Sharing",
|
|
707
|
+
help_text="same as ``CORS_ALLOWED_ORIGINS``, but supports regular expressions",
|
|
523
708
|
)
|
|
524
709
|
# Authorization is included in default_cors_headers
|
|
525
710
|
CORS_ALLOW_HEADERS = (
|
|
@@ -528,7 +713,17 @@ CORS_ALLOW_HEADERS = (
|
|
|
528
713
|
"accept-crs",
|
|
529
714
|
"content-crs",
|
|
530
715
|
]
|
|
531
|
-
+ config(
|
|
716
|
+
+ config(
|
|
717
|
+
"CORS_EXTRA_ALLOW_HEADERS",
|
|
718
|
+
split=True,
|
|
719
|
+
default=[],
|
|
720
|
+
group="Cross-Origin-Resource-Sharing",
|
|
721
|
+
help_text=(
|
|
722
|
+
"headers that are allowed to be sent as part of the cross-domain request. "
|
|
723
|
+
"By default, Authorization, Accept-Crs and Content-Crs are already included. "
|
|
724
|
+
"The value of this variable is added to these already included headers."
|
|
725
|
+
),
|
|
726
|
+
)
|
|
532
727
|
)
|
|
533
728
|
CORS_EXPOSE_HEADERS = [
|
|
534
729
|
"content-crs",
|
|
@@ -545,6 +740,7 @@ CSRF_TRUSTED_ORIGINS = config(
|
|
|
545
740
|
"CSRF_TRUSTED_ORIGINS",
|
|
546
741
|
split=True,
|
|
547
742
|
default=[strip_protocol_from_origin(origin) for origin in CORS_ALLOWED_ORIGINS],
|
|
743
|
+
help_text="A list of trusted origins for unsafe requests (e.g. POST)",
|
|
548
744
|
)
|
|
549
745
|
#
|
|
550
746
|
# DJANGO-PRIVATES -- safely serve files after authorization
|
|
@@ -556,7 +752,16 @@ PRIVATE_MEDIA_URL = "/private-media/"
|
|
|
556
752
|
#
|
|
557
753
|
# NOTIFICATIONS-API-COMMON
|
|
558
754
|
#
|
|
559
|
-
NOTIFICATIONS_DISABLED = config(
|
|
755
|
+
NOTIFICATIONS_DISABLED = config(
|
|
756
|
+
"NOTIFICATIONS_DISABLED",
|
|
757
|
+
default=False,
|
|
758
|
+
help_text=(
|
|
759
|
+
"indicates whether or not notifications should be sent to the Notificaties API "
|
|
760
|
+
"for operations on the API endpoints. "
|
|
761
|
+
"Defaults to ``True`` for the ``dev`` environment, otherwise defaults to ``False``"
|
|
762
|
+
),
|
|
763
|
+
auto_display_default=False,
|
|
764
|
+
)
|
|
560
765
|
|
|
561
766
|
#
|
|
562
767
|
# SENTRY - error monitoring
|
|
@@ -564,7 +769,15 @@ NOTIFICATIONS_DISABLED = config("NOTIFICATIONS_DISABLED", default=False)
|
|
|
564
769
|
|
|
565
770
|
|
|
566
771
|
def init_sentry(before_send: Callable | None = None):
|
|
567
|
-
SENTRY_DSN = config(
|
|
772
|
+
SENTRY_DSN = config(
|
|
773
|
+
"SENTRY_DSN",
|
|
774
|
+
None,
|
|
775
|
+
help_text=(
|
|
776
|
+
"URL of the sentry project to send error reports to. Default empty, "
|
|
777
|
+
"i.e. -> no monitoring set up. Highly recommended to configure this."
|
|
778
|
+
),
|
|
779
|
+
auto_display_default=False,
|
|
780
|
+
)
|
|
568
781
|
|
|
569
782
|
if SENTRY_DSN:
|
|
570
783
|
SENTRY_CONFIG = {
|
|
@@ -587,8 +800,18 @@ def init_sentry(before_send: Callable | None = None):
|
|
|
587
800
|
#
|
|
588
801
|
# CELERY
|
|
589
802
|
#
|
|
590
|
-
CELERY_BROKER_URL = config(
|
|
591
|
-
|
|
803
|
+
CELERY_BROKER_URL = config(
|
|
804
|
+
"CELERY_RESULT_BACKEND",
|
|
805
|
+
"redis://localhost:6379/1",
|
|
806
|
+
group="Celery",
|
|
807
|
+
help_text="the URL of the backend/broker that will be used by Celery to send the notifications",
|
|
808
|
+
)
|
|
809
|
+
CELERY_RESULT_BACKEND = config(
|
|
810
|
+
"CELERY_RESULT_BACKEND",
|
|
811
|
+
"redis://localhost:6379/1",
|
|
812
|
+
group="Celery",
|
|
813
|
+
help_text="the URL of the backend/broker that will be used by Celery to send the notifications",
|
|
814
|
+
)
|
|
592
815
|
|
|
593
816
|
|
|
594
817
|
#
|
|
@@ -611,15 +834,40 @@ MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT = 5 * 60
|
|
|
611
834
|
#
|
|
612
835
|
# Elastic APM
|
|
613
836
|
#
|
|
614
|
-
ELASTIC_APM_SERVER_URL = config(
|
|
837
|
+
ELASTIC_APM_SERVER_URL = config(
|
|
838
|
+
"ELASTIC_APM_SERVER_URL",
|
|
839
|
+
None,
|
|
840
|
+
"URL where Elastic APM is hosted",
|
|
841
|
+
group="Elastic APM",
|
|
842
|
+
)
|
|
615
843
|
ELASTIC_APM = {
|
|
616
844
|
# FIXME this does change the default service name, because PROJECT_DIRNAME != PROJECT_NAME
|
|
617
845
|
"SERVICE_NAME": config(
|
|
618
|
-
"ELASTIC_APM_SERVICE_NAME",
|
|
846
|
+
"ELASTIC_APM_SERVICE_NAME",
|
|
847
|
+
f"{PROJECT_DIRNAME} - {ENVIRONMENT}",
|
|
848
|
+
help_text=(
|
|
849
|
+
f"Name of the service for this application in Elastic APM. "
|
|
850
|
+
f"Defaults to ``{PROJECT_DIRNAME} - <environment>``"
|
|
851
|
+
),
|
|
852
|
+
group="Elastic APM",
|
|
853
|
+
auto_display_default=False,
|
|
854
|
+
),
|
|
855
|
+
"SECRET_TOKEN": config(
|
|
856
|
+
"ELASTIC_APM_SECRET_TOKEN",
|
|
857
|
+
"default",
|
|
858
|
+
"Token used to communicate with Elastic APM",
|
|
859
|
+
group="Elastic APM",
|
|
619
860
|
),
|
|
620
|
-
"SECRET_TOKEN": config("ELASTIC_APM_SECRET_TOKEN", "default"),
|
|
621
861
|
"SERVER_URL": ELASTIC_APM_SERVER_URL,
|
|
622
|
-
"TRANSACTION_SAMPLE_RATE": config(
|
|
862
|
+
"TRANSACTION_SAMPLE_RATE": config(
|
|
863
|
+
"ELASTIC_APM_TRANSACTION_SAMPLE_RATE",
|
|
864
|
+
0.1,
|
|
865
|
+
help_text=(
|
|
866
|
+
"By default, the agent will sample every transaction (e.g. request to your service). "
|
|
867
|
+
"To reduce overhead and storage requirements, set the sample rate to a value between 0.0 and 1.0"
|
|
868
|
+
),
|
|
869
|
+
group="Elastic APM",
|
|
870
|
+
),
|
|
623
871
|
}
|
|
624
872
|
if not ELASTIC_APM_SERVER_URL:
|
|
625
873
|
ELASTIC_APM["ENABLED"] = False
|
|
@@ -652,7 +900,11 @@ MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = [
|
|
|
652
900
|
|
|
653
901
|
# if DISABLE_2FA is true, fill the MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS with all
|
|
654
902
|
# configured AUTHENTICATION_BACKENDS and thus disabeling the entire 2FA chain.
|
|
655
|
-
if config(
|
|
903
|
+
if config(
|
|
904
|
+
"DISABLE_2FA",
|
|
905
|
+
default=False,
|
|
906
|
+
help_text="Whether or not two factor authentication should be disabled",
|
|
907
|
+
): # pragma: no cover
|
|
656
908
|
MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = AUTHENTICATION_BACKENDS
|
|
657
909
|
|
|
658
910
|
|
|
@@ -660,13 +912,23 @@ if config("DISABLE_2FA", default=False): # pragma: no cover
|
|
|
660
912
|
# LOG OUTGOING REQUESTS
|
|
661
913
|
#
|
|
662
914
|
LOG_OUTGOING_REQUESTS_EMIT_BODY = config(
|
|
663
|
-
"LOG_OUTGOING_REQUESTS_EMIT_BODY",
|
|
915
|
+
"LOG_OUTGOING_REQUESTS_EMIT_BODY",
|
|
916
|
+
default=True,
|
|
917
|
+
help_text="Whether or not outgoing request bodies should be logged",
|
|
918
|
+
)
|
|
919
|
+
LOG_OUTGOING_REQUESTS_DB_SAVE = config(
|
|
920
|
+
"LOG_OUTGOING_REQUESTS_DB_SAVE",
|
|
921
|
+
default=False,
|
|
922
|
+
help_text="Whether or not outgoing request logs should be saved to the database",
|
|
664
923
|
)
|
|
665
924
|
LOG_OUTGOING_REQUESTS_DB_SAVE_BODY = config(
|
|
666
|
-
"LOG_OUTGOING_REQUESTS_DB_SAVE_BODY",
|
|
925
|
+
"LOG_OUTGOING_REQUESTS_DB_SAVE_BODY",
|
|
926
|
+
default=True,
|
|
927
|
+
help_text="Whether or not outgoing request bodies should be saved to the database",
|
|
667
928
|
)
|
|
668
|
-
LOG_OUTGOING_REQUESTS_DB_SAVE = config("LOG_OUTGOING_REQUESTS_DB_SAVE", default=False)
|
|
669
929
|
LOG_OUTGOING_REQUESTS_RESET_DB_SAVE_AFTER = None
|
|
670
930
|
LOG_OUTGOING_REQUESTS_MAX_AGE = config(
|
|
671
|
-
"LOG_OUTGOING_REQUESTS_MAX_AGE",
|
|
931
|
+
"LOG_OUTGOING_REQUESTS_MAX_AGE",
|
|
932
|
+
default=7,
|
|
933
|
+
help_text="The amount of time after which request logs should be deleted from the database",
|
|
672
934
|
) # 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.1
|
|
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
|
|
@@ -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.1
|
|
81
81
|
:Source: https://github.com/maykinmedia/open-api-framework
|
|
82
82
|
:Keywords: metapackage, dependencies
|
|
83
83
|
|
{open_api_framework-0.6.1 → open_api_framework-0.7.1}/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
|
|
@@ -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.1"
|
|
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"}
|
|
@@ -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.1"
|
|
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.1 → open_api_framework-0.7.1}/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.1 → open_api_framework-0.7.1}/open_api_framework.egg-info/requires.txt
RENAMED
|
File without changes
|
{open_api_framework-0.6.1 → open_api_framework-0.7.1}/open_api_framework.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|