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.
Files changed (33) hide show
  1. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/CHANGELOG.rst +29 -0
  2. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/MANIFEST.in +1 -0
  3. {open_api_framework-0.6.0/open_api_framework.egg-info → open_api_framework-0.7.0}/PKG-INFO +15 -15
  4. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/README.rst +1 -1
  5. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/conf/base.py +291 -44
  6. open_api_framework-0.7.0/open_api_framework/conf/utils.py +121 -0
  7. open_api_framework-0.7.0/open_api_framework/management/commands/generate_envvar_docs.py +58 -0
  8. open_api_framework-0.7.0/open_api_framework/py.typed +0 -0
  9. open_api_framework-0.7.0/open_api_framework/templates/open_api_framework/env_config.rst +50 -0
  10. open_api_framework-0.7.0/open_api_framework/templatetags/__init__.py +0 -0
  11. open_api_framework-0.7.0/open_api_framework/templatetags/doc_tags.py +35 -0
  12. {open_api_framework-0.6.0 → open_api_framework-0.7.0/open_api_framework.egg-info}/PKG-INFO +15 -15
  13. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/SOURCES.txt +7 -1
  14. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/requires.txt +13 -13
  15. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/pyproject.toml +15 -15
  16. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/tests/test_config_helpers.py +2 -2
  17. open_api_framework-0.7.0/tests/test_generate_envvar_docs.py +85 -0
  18. open_api_framework-0.6.0/open_api_framework/conf/utils.py +0 -63
  19. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/LICENSE +0 -0
  20. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/__init__.py +0 -0
  21. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/apps.py +0 -0
  22. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/conf/__init__.py +0 -0
  23. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/context_processors.py +0 -0
  24. {open_api_framework-0.6.0/open_api_framework/templatetags → open_api_framework-0.7.0/open_api_framework/management}/__init__.py +0 -0
  25. /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
  26. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/static/open_api_framework/css/admin.css +0 -0
  27. {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
  28. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templates/open_api_framework/components/environment.html +0 -0
  29. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templates/open_api_framework/components/version.html +0 -0
  30. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework/templatetags/open_api_framework.py +0 -0
  31. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/dependency_links.txt +0 -0
  32. {open_api_framework-0.6.0 → open_api_framework-0.7.0}/open_api_framework.egg-info/top_level.txt +0 -0
  33. {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.6.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>=4.2.11
34
- Requires-Dist: django-axes>=6.3.0
35
- Requires-Dist: django-cors-headers>=4.3.1
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.21.4
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.12.4
41
+ Requires-Dist: djangorestframework>=3.15.2
42
42
  Requires-Dist: djangorestframework-gis>=1.0
43
- Requires-Dist: django-filter>=23.2
44
- Requires-Dist: drf-spectacular>=0.27.0
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.14.1
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.31.0
53
+ Requires-Dist: requests>=2.32.3
54
54
  Requires-Dist: uwsgi>=2.0.23
55
- Requires-Dist: sentry-sdk>=1.39.2
56
- Requires-Dist: elastic-apm>=6.20.0
57
- Requires-Dist: celery>=5.2.7
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.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.6.0
80
+ :Version: 0.7.0
81
81
  :Source: https://github.com/maykinmedia/open-api-framework
82
82
  :Keywords: metapackage, dependencies
83
83
 
@@ -1,7 +1,7 @@
1
1
  Open API Framework
2
2
  ==================
3
3
 
4
- :Version: 0.6.0
4
+ :Version: 0.7.0
5
5
  :Source: https://github.com/maykinmedia/open-api-framework
6
6
  :Keywords: metapackage, dependencies
7
7
 
@@ -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("SITE_ID", default=1)
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("SECRET_KEY")
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("DEBUG", default=False)
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("ALLOWED_HOSTS", default="", split=True)
43
- USE_X_FORWARDED_HOST = config("USE_X_FORWARDED_HOST", default=False)
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("IS_HTTPS", default=not DEBUG)
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("DB_NAME", PROJECT_DIRNAME),
70
- "USER": config("DB_USER", PROJECT_DIRNAME),
71
- "PASSWORD": config("DB_PASSWORD", PROJECT_DIRNAME),
72
- "HOST": config("DB_HOST", "localhost"),
73
- "PORT": config("DB_PORT", 5432),
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://{config('CACHE_DEFAULT', 'localhost:6379/0')}",
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://{config('CACHE_AXES', 'localhost:6379/0')}",
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://{config('CACHE_DEFAULT', 'localhost:6379/0')}",
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("EMAIL_HOST", default="localhost")
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", default=25
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("EMAIL_HOST_USER", default="")
231
- EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD", default="")
232
- EMAIL_USE_TLS = config("EMAIL_USE_TLS", default=False)
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("DEFAULT_FROM_EMAIL", f"{PROJECT_DIRNAME}@example.com")
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("LOG_STDOUT", default=False)
241
- LOG_LEVEL = config("LOG_LEVEL", default="WARNING")
242
- LOG_QUERIES = config("LOG_QUERIES", default=False)
243
- LOG_REQUESTS = config("LOG_REQUESTS", default=False)
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("ENVIRONMENT", "")
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("SUBPATH", None)
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("RELEASE", GIT_SHA)
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("CORS_ALLOW_ALL_ORIGINS", default=False)
520
- CORS_ALLOWED_ORIGINS = config("CORS_ALLOWED_ORIGINS", split=True, default=[])
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", split=True, default=[]
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("CORS_EXTRA_ALLOW_HEADERS", split=True, default=[])
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("NOTIFICATIONS_DISABLED", default=False)
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("SENTRY_DSN", None)
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("CELERY_RESULT_BACKEND", "redis://localhost:6379/1")
591
- CELERY_RESULT_BACKEND = config("CELERY_RESULT_BACKEND", "redis://localhost:6379/1")
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("ELASTIC_APM_SERVER_URL", None)
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", f"{PROJECT_DIRNAME} - {ENVIRONMENT}"
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("ELASTIC_APM_TRANSACTION_SAMPLE_RATE", 0.1),
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("DISABLE_2FA", default=False): # pragma: no cover
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", default=True
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", default=True
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", default=7
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.
@@ -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.6.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>=4.2.11
34
- Requires-Dist: django-axes>=6.3.0
35
- Requires-Dist: django-cors-headers>=4.3.1
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.21.4
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.12.4
41
+ Requires-Dist: djangorestframework>=3.15.2
42
42
  Requires-Dist: djangorestframework-gis>=1.0
43
- Requires-Dist: django-filter>=23.2
44
- Requires-Dist: drf-spectacular>=0.27.0
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.14.1
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.31.0
53
+ Requires-Dist: requests>=2.32.3
54
54
  Requires-Dist: uwsgi>=2.0.23
55
- Requires-Dist: sentry-sdk>=1.39.2
56
- Requires-Dist: elastic-apm>=6.20.0
57
- Requires-Dist: celery>=5.2.7
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.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.6.0
80
+ :Version: 0.7.0
81
81
  :Source: https://github.com/maykinmedia/open-api-framework
82
82
  :Keywords: metapackage, dependencies
83
83
 
@@ -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
@@ -1,30 +1,30 @@
1
- django>=4.2.11
2
- django-axes>=6.3.0
3
- django-cors-headers>=4.3.1
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.21.4
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.12.4
9
+ djangorestframework>=3.15.2
10
10
  djangorestframework-gis>=1.0
11
- django-filter>=23.2
12
- drf-spectacular>=0.27.0
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.14.1
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.31.0
21
+ requests>=2.32.3
22
22
  uwsgi>=2.0.23
23
- sentry-sdk>=1.39.2
24
- elastic-apm>=6.20.0
25
- celery>=5.2.7
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.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.6.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.11",
31
- "django-axes>=6.3.0",
32
- "django-cors-headers>=4.3.1",
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.21.4",
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.12.4",
38
+ "djangorestframework>=3.15.2",
39
39
  "djangorestframework-gis>=1.0",
40
- "django-filter>=23.2",
41
- "drf-spectacular>=0.27.0",
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.14.1",
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.31.0",
50
+ "requests>=2.32.3",
51
51
  "uwsgi>=2.0.23",
52
- "sentry-sdk>=1.39.2",
53
- "elastic-apm>=6.20.0",
54
- "celery>=5.2.7",
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.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.6.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