flagsmith-common 1.5.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 (41) hide show
  1. flagsmith_common-1.5.0/LICENSE +28 -0
  2. flagsmith_common-1.5.0/PKG-INFO +128 -0
  3. flagsmith_common-1.5.0/README.md +95 -0
  4. flagsmith_common-1.5.0/pyproject.toml +119 -0
  5. flagsmith_common-1.5.0/src/common/__init__.py +0 -0
  6. flagsmith_common-1.5.0/src/common/core/__init__.py +0 -0
  7. flagsmith_common-1.5.0/src/common/core/app.py +6 -0
  8. flagsmith_common-1.5.0/src/common/core/logging.py +24 -0
  9. flagsmith_common-1.5.0/src/common/core/main.py +40 -0
  10. flagsmith_common-1.5.0/src/common/core/management/__init__.py +0 -0
  11. flagsmith_common-1.5.0/src/common/core/management/commands/__init__.py +0 -0
  12. flagsmith_common-1.5.0/src/common/core/management/commands/start.py +41 -0
  13. flagsmith_common-1.5.0/src/common/core/metrics.py +23 -0
  14. flagsmith_common-1.5.0/src/common/core/urls.py +17 -0
  15. flagsmith_common-1.5.0/src/common/core/utils.py +90 -0
  16. flagsmith_common-1.5.0/src/common/core/views.py +23 -0
  17. flagsmith_common-1.5.0/src/common/environments/permissions.py +15 -0
  18. flagsmith_common-1.5.0/src/common/features/__init__.py +0 -0
  19. flagsmith_common-1.5.0/src/common/features/multivariate/__init__.py +0 -0
  20. flagsmith_common-1.5.0/src/common/features/multivariate/serializers.py +19 -0
  21. flagsmith_common-1.5.0/src/common/features/serializers.py +68 -0
  22. flagsmith_common-1.5.0/src/common/features/versioning/__init__.py +0 -0
  23. flagsmith_common-1.5.0/src/common/features/versioning/serializers.py +13 -0
  24. flagsmith_common-1.5.0/src/common/gunicorn/__init__.py +0 -0
  25. flagsmith_common-1.5.0/src/common/gunicorn/conf.py +18 -0
  26. flagsmith_common-1.5.0/src/common/gunicorn/constants.py +1 -0
  27. flagsmith_common-1.5.0/src/common/gunicorn/logging.py +94 -0
  28. flagsmith_common-1.5.0/src/common/gunicorn/metrics.py +14 -0
  29. flagsmith_common-1.5.0/src/common/gunicorn/middleware.py +28 -0
  30. flagsmith_common-1.5.0/src/common/gunicorn/utils.py +61 -0
  31. flagsmith_common-1.5.0/src/common/metadata/serializers.py +100 -0
  32. flagsmith_common-1.5.0/src/common/organisations/permissions.py +10 -0
  33. flagsmith_common-1.5.0/src/common/projects/permissions.py +40 -0
  34. flagsmith_common-1.5.0/src/common/prometheus/__init__.py +3 -0
  35. flagsmith_common-1.5.0/src/common/prometheus/utils.py +18 -0
  36. flagsmith_common-1.5.0/src/common/py.typed +0 -0
  37. flagsmith_common-1.5.0/src/common/segments/serializers.py +338 -0
  38. flagsmith_common-1.5.0/src/common/test_tools/__init__.py +3 -0
  39. flagsmith_common-1.5.0/src/common/test_tools/plugin.py +34 -0
  40. flagsmith_common-1.5.0/src/common/test_tools/types.py +11 -0
  41. flagsmith_common-1.5.0/src/common/types.py +45 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Flagsmith
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.3
2
+ Name: flagsmith-common
3
+ Version: 1.5.0
4
+ Summary: Flagsmith's common library
5
+ License: BSD-3-Clause
6
+ Author: Matthew Elwell
7
+ Maintainer: Flagsmith Team
8
+ Maintainer-email: support@flagsmith.com
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Dist: django (<5)
19
+ Requires-Dist: django-health-check
20
+ Requires-Dist: djangorestframework
21
+ Requires-Dist: djangorestframework-recursive
22
+ Requires-Dist: drf-writable-nested
23
+ Requires-Dist: environs (<15)
24
+ Requires-Dist: flagsmith-flag-engine
25
+ Requires-Dist: gunicorn (>=19.1)
26
+ Requires-Dist: prometheus-client (>=0.0.16)
27
+ Project-URL: Download, https://github.com/flagsmith/flagsmith-common/releases
28
+ Project-URL: Homepage, https://flagsmith.com
29
+ Project-URL: Issues, https://github.com/flagsmith/flagsmith-common/issues
30
+ Project-URL: Repository, https://github.com/flagsmith/flagsmith-common
31
+ Description-Content-Type: text/markdown
32
+
33
+ # flagsmith-common
34
+ Flagsmith's common library
35
+
36
+ ### Development Setup
37
+
38
+ This project uses [Poetry](https://python-poetry.org/) for dependency management and includes a Makefile to simplify common development tasks.
39
+
40
+ #### Prerequisites
41
+
42
+ - Python >= 3.11
43
+ - Make
44
+
45
+ #### Installation
46
+
47
+ You can set up your development environment using the provided Makefile:
48
+
49
+ ```bash
50
+ # Install everything (pip, poetry, and project dependencies)
51
+ make install
52
+
53
+ # Individual installation steps are also available
54
+ make install-pip # Upgrade pip
55
+ make install-poetry # Install Poetry
56
+ make install-packages # Install project dependencies
57
+ ```
58
+
59
+ By default, Poetry version 2.0.1 will be installed. You can specify a different version:
60
+
61
+ ```bash
62
+ make install-poetry POETRY_VERSION=2.1.0
63
+ ```
64
+
65
+ #### Development
66
+
67
+ Run linting checks using pre-commit:
68
+
69
+ ```bash
70
+ make lint
71
+ ```
72
+
73
+ Additional options can be passed to the `install-packages` target:
74
+
75
+ ```bash
76
+ # Install with development dependencies
77
+ make install-packages opts="--with dev"
78
+
79
+ # Install with specific extras
80
+ make install-packages opts="--extras 'feature1 feature2'"
81
+ ```
82
+
83
+ ### Usage
84
+
85
+ #### Installation
86
+
87
+ 1. Make sure `"common.core"` is in the `INSTALLED_APPS` of your settings module.
88
+ This enables the `manage.py flagsmith` commands.
89
+
90
+ 2. Add `"common.gunicorn.middleware.RouteLoggerMiddleware"` to `MIDDLEWARE` in your settings module.
91
+ This enables the `route` label for Prometheus HTTP metrics.
92
+
93
+ 3. To enable the `/metrics` endpoint, set the `PROMETHEUS_ENABLED` setting to `True`.
94
+
95
+ #### Metrics
96
+
97
+ Flagsmith uses Prometheus to track performance metrics.
98
+
99
+ The following default metrics are exposed:
100
+
101
+ - `flagsmith_build_info`: Has the labels `version` and `ci_commit_sha`.
102
+ - `http_server_request_duration_seconds`: Histogram labeled with `method`, `path`, and `response_status`.
103
+ - `http_server_requests_total`: Counter labeled with `method`, `path`, and `response_status`.
104
+
105
+ ##### Guidelines
106
+
107
+ Try to come up with meaningful metrics to cover your feature with when developing it. Refer to [Prometheus best practices][1] when naming your metric and labels.
108
+
109
+ Define your metrics in a `metrics.py` module of your Django application — see [example][2]. Contrary to Prometheus Python client examples and documentation, please name a metric variable exactly as your metric name.
110
+
111
+ It's generally a good idea to allow users to define histogram buckets of their own. Flagsmith accepts a `PROMETHEUS_HISTOGRAM_BUCKETS` setting so users can customise their buckets. To honour the setting, use the `common.prometheus.Histogram` class when defining your histograms. When using `prometheus_client.Histogram` directly, please expose a dedicated setting like so:
112
+
113
+ ```python
114
+ import prometheus_client
115
+ from django.conf import settings
116
+
117
+ distance_from_earth_au = prometheus.Histogram(
118
+ "distance_from_earth_au",
119
+ "Distance from Earth in astronomical units",
120
+ buckets=settings.DISTANCE_FROM_EARTH_AU_HISTOGRAM_BUCKETS,
121
+ )
122
+ ```
123
+
124
+ [1]: https://prometheus.io/docs/practices/naming/
125
+ [2]: https://github.com/Flagsmith/flagsmith-common/blob/main/src/common/gunicorn/metrics.py
126
+ [3]: https://docs.gunicorn.org/en/stable/design.html#server-model
127
+ [4]: https://prometheus.github.io/client_python/multiprocess
128
+
@@ -0,0 +1,95 @@
1
+ # flagsmith-common
2
+ Flagsmith's common library
3
+
4
+ ### Development Setup
5
+
6
+ This project uses [Poetry](https://python-poetry.org/) for dependency management and includes a Makefile to simplify common development tasks.
7
+
8
+ #### Prerequisites
9
+
10
+ - Python >= 3.11
11
+ - Make
12
+
13
+ #### Installation
14
+
15
+ You can set up your development environment using the provided Makefile:
16
+
17
+ ```bash
18
+ # Install everything (pip, poetry, and project dependencies)
19
+ make install
20
+
21
+ # Individual installation steps are also available
22
+ make install-pip # Upgrade pip
23
+ make install-poetry # Install Poetry
24
+ make install-packages # Install project dependencies
25
+ ```
26
+
27
+ By default, Poetry version 2.0.1 will be installed. You can specify a different version:
28
+
29
+ ```bash
30
+ make install-poetry POETRY_VERSION=2.1.0
31
+ ```
32
+
33
+ #### Development
34
+
35
+ Run linting checks using pre-commit:
36
+
37
+ ```bash
38
+ make lint
39
+ ```
40
+
41
+ Additional options can be passed to the `install-packages` target:
42
+
43
+ ```bash
44
+ # Install with development dependencies
45
+ make install-packages opts="--with dev"
46
+
47
+ # Install with specific extras
48
+ make install-packages opts="--extras 'feature1 feature2'"
49
+ ```
50
+
51
+ ### Usage
52
+
53
+ #### Installation
54
+
55
+ 1. Make sure `"common.core"` is in the `INSTALLED_APPS` of your settings module.
56
+ This enables the `manage.py flagsmith` commands.
57
+
58
+ 2. Add `"common.gunicorn.middleware.RouteLoggerMiddleware"` to `MIDDLEWARE` in your settings module.
59
+ This enables the `route` label for Prometheus HTTP metrics.
60
+
61
+ 3. To enable the `/metrics` endpoint, set the `PROMETHEUS_ENABLED` setting to `True`.
62
+
63
+ #### Metrics
64
+
65
+ Flagsmith uses Prometheus to track performance metrics.
66
+
67
+ The following default metrics are exposed:
68
+
69
+ - `flagsmith_build_info`: Has the labels `version` and `ci_commit_sha`.
70
+ - `http_server_request_duration_seconds`: Histogram labeled with `method`, `path`, and `response_status`.
71
+ - `http_server_requests_total`: Counter labeled with `method`, `path`, and `response_status`.
72
+
73
+ ##### Guidelines
74
+
75
+ Try to come up with meaningful metrics to cover your feature with when developing it. Refer to [Prometheus best practices][1] when naming your metric and labels.
76
+
77
+ Define your metrics in a `metrics.py` module of your Django application — see [example][2]. Contrary to Prometheus Python client examples and documentation, please name a metric variable exactly as your metric name.
78
+
79
+ It's generally a good idea to allow users to define histogram buckets of their own. Flagsmith accepts a `PROMETHEUS_HISTOGRAM_BUCKETS` setting so users can customise their buckets. To honour the setting, use the `common.prometheus.Histogram` class when defining your histograms. When using `prometheus_client.Histogram` directly, please expose a dedicated setting like so:
80
+
81
+ ```python
82
+ import prometheus_client
83
+ from django.conf import settings
84
+
85
+ distance_from_earth_au = prometheus.Histogram(
86
+ "distance_from_earth_au",
87
+ "Distance from Earth in astronomical units",
88
+ buckets=settings.DISTANCE_FROM_EARTH_AU_HISTOGRAM_BUCKETS,
89
+ )
90
+ ```
91
+
92
+ [1]: https://prometheus.io/docs/practices/naming/
93
+ [2]: https://github.com/Flagsmith/flagsmith-common/blob/main/src/common/gunicorn/metrics.py
94
+ [3]: https://docs.gunicorn.org/en/stable/design.html#server-model
95
+ [4]: https://prometheus.github.io/client_python/multiprocess
@@ -0,0 +1,119 @@
1
+ [project]
2
+ name = "flagsmith-common"
3
+ version = "1.5.0"
4
+ description = "Flagsmith's common library"
5
+ requires-python = ">=3.11,<4.0"
6
+ dependencies = [
7
+ "django (<5)",
8
+ "django-health-check",
9
+ "djangorestframework-recursive",
10
+ "djangorestframework",
11
+ "drf-writable-nested",
12
+ "flagsmith-flag-engine",
13
+ "gunicorn (>=19.1)",
14
+ "prometheus-client (>=0.0.16)",
15
+ "environs (<15)",
16
+ ]
17
+ authors = [
18
+ { name = "Matthew Elwell" },
19
+ { name = "Gagan Trivedi" },
20
+ { name = "Kim Gustyr" },
21
+ { name = "Zach Aysan" },
22
+ { name = "Francesco Lo Franco" },
23
+ { name = "Rodrigo López Dato" },
24
+ ]
25
+ maintainers = [{ name = "Flagsmith Team", email = "support@flagsmith.com" }]
26
+ license = "BSD-3-Clause"
27
+ license-files = ["LICENSE"]
28
+ readme = "README.md"
29
+ dynamic = ["classifiers"]
30
+
31
+ [project.urls]
32
+ Download = "https://github.com/flagsmith/flagsmith-common/releases"
33
+ Homepage = "https://flagsmith.com"
34
+ Issues = "https://github.com/flagsmith/flagsmith-common/issues"
35
+ Repository = "https://github.com/flagsmith/flagsmith-common"
36
+
37
+ [project.scripts]
38
+ flagsmith = "common.core.main:main"
39
+
40
+ [project.entry-points.pytest11]
41
+ flagsmith-test-tools = "common.test_tools.plugin"
42
+
43
+ [tool.poetry]
44
+ requires-poetry = ">=2.0"
45
+ classifiers = [
46
+ "Framework :: Django",
47
+ "Framework :: Pytest",
48
+ "Intended Audience :: Developers",
49
+ "License :: OSI Approved :: BSD License",
50
+ ]
51
+ packages = [{ include = "common", from = "src" }]
52
+
53
+ [tool.poetry.group.dev.dependencies]
54
+ django-stubs = "^5.1.3"
55
+ djangorestframework-stubs = "^3.15.3"
56
+ mypy = "^1.15.0"
57
+ pre-commit = "*"
58
+ pyfakefs = "^5.7.4"
59
+ pytest = "^8.3.4"
60
+ pytest-asyncio = "^0.25.3"
61
+ pytest-cov = "^6.0.0"
62
+ pytest-django = "^4.10.0"
63
+ pytest-freezegun = "^0.4.2"
64
+ pytest-mock = "^3.14.0"
65
+ requests = "^2.32.3"
66
+ ruff = "*"
67
+ setuptools = "^77.0.3"
68
+
69
+ [build-system]
70
+ requires = ["poetry-core"]
71
+ build-backend = "poetry.core.masonry.api"
72
+
73
+ [tool.pytest.ini_options]
74
+ addopts = ['--ds=settings.dev', '-vvvv', '-p', 'no:warnings']
75
+ console_output_style = 'count'
76
+
77
+ [tool.ruff]
78
+ line-length = 88
79
+ indent-width = 4
80
+ target-version = "py311"
81
+ extend-exclude = ["migrations"]
82
+
83
+ [tool.ruff.format]
84
+ # Like Black, use double quotes for strings.
85
+ quote-style = "double"
86
+
87
+ # Like Black, indent with spaces, rather than tabs.
88
+ indent-style = "space"
89
+
90
+ # Like Black, respect magic trailing commas.
91
+ skip-magic-trailing-comma = false
92
+
93
+ # Like Black, automatically detect the appropriate line ending.
94
+ line-ending = "auto"
95
+
96
+ # Enable auto-formatting of code examples in docstrings. Markdown,
97
+ # reStructuredText code/literal blocks and doctests are all supported.
98
+ docstring-code-format = true
99
+
100
+ # Set the line length limit used when formatting code snippets in
101
+ # docstrings.
102
+ docstring-code-line-length = "dynamic"
103
+
104
+ [tool.ruff.lint]
105
+ # Establish parity with flake8 + isort
106
+ select = ["C901", "E4", "E7", "E9", "F", "I", "W"]
107
+ ignore = []
108
+ fixable = ["ALL"]
109
+ unfixable = []
110
+
111
+ [tool.mypy]
112
+ plugins = ["mypy_django_plugin.main"]
113
+ strict = true
114
+
115
+ [tool.django-stubs]
116
+ django_settings_module = "settings.dev"
117
+
118
+ [tool.drf-stubs]
119
+ enabled = true
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CoreConfig(AppConfig):
5
+ name = "common.core"
6
+ label = "common_core"
@@ -0,0 +1,24 @@
1
+ import json
2
+ import logging
3
+ from typing import Any
4
+
5
+
6
+ class JsonFormatter(logging.Formatter):
7
+ """Custom formatter for json logs."""
8
+
9
+ def get_json_record(self, record: logging.LogRecord) -> dict[str, Any]:
10
+ formatted_message = record.getMessage()
11
+ json_record = {
12
+ "levelname": record.levelname,
13
+ "message": formatted_message,
14
+ "timestamp": self.formatTime(record, self.datefmt),
15
+ "logger_name": record.name,
16
+ "pid": record.process,
17
+ "thread_name": record.threadName,
18
+ }
19
+ if record.exc_info:
20
+ json_record["exc_info"] = self.formatException(record.exc_info)
21
+ return json_record
22
+
23
+ def format(self, record: logging.LogRecord) -> str:
24
+ return json.dumps(self.get_json_record(record))
@@ -0,0 +1,40 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import tempfile
5
+
6
+ from django.core.management import execute_from_command_line
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def main() -> None:
12
+ """
13
+ The main entry point to the Flagsmith application.
14
+
15
+ An equivalent to Django's `manage.py` script, this module is used to run management commands.
16
+
17
+ It's installed as the `flagsmith` command.
18
+
19
+ Everything that needs to be run before Django is started should be done here.
20
+
21
+ The end goal is to eventually replace Core API's `run-docker.sh` with this.
22
+
23
+ Usage:
24
+ `flagsmith <command> [options]`
25
+ """
26
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.dev")
27
+
28
+ # Set up Prometheus' multiprocess mode
29
+ if "PROMETHEUS_MULTIPROC_DIR" not in os.environ:
30
+ prometheus_multiproc_dir = tempfile.TemporaryDirectory(
31
+ prefix="prometheus_multiproc",
32
+ )
33
+ logger.info(
34
+ "Created %s for Prometheus multi-process mode",
35
+ prometheus_multiproc_dir.name,
36
+ )
37
+ os.environ["PROMETHEUS_MULTIPROC_DIR"] = prometheus_multiproc_dir.name
38
+
39
+ # Run Django
40
+ execute_from_command_line(sys.argv)
@@ -0,0 +1,41 @@
1
+ from typing import Any, Callable
2
+
3
+ from django.core.management import BaseCommand, CommandParser
4
+ from django.utils.module_loading import autodiscover_modules
5
+
6
+ from common.gunicorn.utils import add_arguments, run_server
7
+
8
+
9
+ class Command(BaseCommand):
10
+ help = "Start the Flagsmith application."
11
+
12
+ def create_parser(self, *args: Any, **kwargs: Any) -> CommandParser:
13
+ return super().create_parser(*args, conflict_handler="resolve", **kwargs)
14
+
15
+ def add_arguments(self, parser: CommandParser) -> None:
16
+ add_arguments(parser)
17
+
18
+ subparsers = parser.add_subparsers(
19
+ title="sub-commands",
20
+ required=True,
21
+ )
22
+ api_parser = subparsers.add_parser(
23
+ "api",
24
+ help="Start the Core API.",
25
+ )
26
+ api_parser.set_defaults(handle_method=self.handle_api)
27
+
28
+ def initialise(self) -> None:
29
+ autodiscover_modules("metrics")
30
+
31
+ def handle(
32
+ self,
33
+ *args: Any,
34
+ handle_method: Callable[..., None],
35
+ **options: Any,
36
+ ) -> None:
37
+ self.initialise()
38
+ handle_method(*args, **options)
39
+
40
+ def handle_api(self, *args: Any, **options: Any) -> None:
41
+ run_server(options)
@@ -0,0 +1,23 @@
1
+ import prometheus_client
2
+
3
+ from common.core.utils import get_version_info
4
+
5
+ flagsmith_build_info = prometheus_client.Gauge(
6
+ "flagsmith_build_info",
7
+ "Flagsmith version and build information",
8
+ ["ci_commit_sha", "version"],
9
+ multiprocess_mode="livemax",
10
+ )
11
+
12
+
13
+ def advertise() -> None:
14
+ # Advertise Flagsmith build info.
15
+ version_info = get_version_info()
16
+
17
+ flagsmith_build_info.labels(
18
+ ci_commit_sha=version_info["ci_commit_sha"],
19
+ version=version_info.get("package_versions", {}).get(".") or "unknown",
20
+ ).set(1)
21
+
22
+
23
+ advertise()
@@ -0,0 +1,17 @@
1
+ from django.conf import settings
2
+ from django.urls import include, re_path
3
+
4
+ from common.core import views
5
+
6
+ urlpatterns = [
7
+ re_path(r"^version/?", views.version_info),
8
+ re_path(r"^health/liveness/?", views.version_info),
9
+ re_path(r"^health/readiness/?", include("health_check.urls", namespace="health")),
10
+ re_path(r"^health", include("health_check.urls", namespace="health-deprecated")),
11
+ # Aptible health checks must be on /healthcheck and cannot redirect
12
+ # see https://www.aptible.com/docs/core-concepts/apps/connecting-to-apps/app-endpoints/https-endpoints/health-checks
13
+ re_path(r"^healthcheck", include("health_check.urls", namespace="health-aptible")),
14
+ ]
15
+
16
+ if settings.PROMETHEUS_ENABLED:
17
+ urlpatterns += [re_path(r"^metrics/?", views.metrics)]
@@ -0,0 +1,90 @@
1
+ import json
2
+ import pathlib
3
+ from functools import lru_cache
4
+ from typing import NotRequired, TypedDict
5
+
6
+ from django.conf import settings
7
+ from django.contrib.auth import get_user_model
8
+ from django.contrib.auth.models import AbstractBaseUser
9
+ from django.db.models import Manager
10
+
11
+ UNKNOWN = "unknown"
12
+ VERSIONS_INFO_FILE_LOCATION = ".versions.json"
13
+
14
+
15
+ class SelfHostedData(TypedDict):
16
+ has_users: bool
17
+ has_logins: bool
18
+
19
+
20
+ class VersionInfo(TypedDict):
21
+ ci_commit_sha: str
22
+ image_tag: str
23
+ has_email_provider: bool
24
+ is_enterprise: bool
25
+ is_saas: bool
26
+ self_hosted_data: SelfHostedData | None
27
+ package_versions: NotRequired[dict[str, str]]
28
+
29
+
30
+ @lru_cache()
31
+ def is_enterprise() -> bool:
32
+ return pathlib.Path("./ENTERPRISE_VERSION").exists()
33
+
34
+
35
+ @lru_cache()
36
+ def is_saas() -> bool:
37
+ return pathlib.Path("./SAAS_DEPLOYMENT").exists()
38
+
39
+
40
+ @lru_cache()
41
+ def has_email_provider() -> bool:
42
+ match settings.EMAIL_BACKEND:
43
+ case "django.core.mail.backends.smtp.EmailBackend":
44
+ return settings.EMAIL_HOST_USER is not None
45
+ case "sgbackend.SendGridBackend":
46
+ return settings.SENDGRID_API_KEY is not None
47
+ case "django_ses.SESBackend":
48
+ return settings.AWS_SES_REGION_ENDPOINT is not None
49
+ case _:
50
+ return False
51
+
52
+
53
+ def get_version_info() -> VersionInfo:
54
+ """Reads the version info baked into src folder of the docker container"""
55
+ _is_saas = is_saas()
56
+ version_json: VersionInfo = {
57
+ "ci_commit_sha": get_file_contents("./CI_COMMIT_SHA") or UNKNOWN,
58
+ "image_tag": UNKNOWN,
59
+ "has_email_provider": has_email_provider(),
60
+ "is_enterprise": is_enterprise(),
61
+ "is_saas": _is_saas,
62
+ "self_hosted_data": None,
63
+ }
64
+
65
+ manifest_versions_content = get_file_contents(VERSIONS_INFO_FILE_LOCATION)
66
+
67
+ if manifest_versions_content:
68
+ manifest_versions = json.loads(manifest_versions_content)
69
+ version_json["package_versions"] = manifest_versions
70
+ version_json["image_tag"] = manifest_versions["."]
71
+
72
+ if not _is_saas:
73
+ user_objects: Manager[AbstractBaseUser] = getattr(get_user_model(), "objects")
74
+
75
+ version_json["self_hosted_data"] = {
76
+ "has_users": user_objects.exists(),
77
+ "has_logins": user_objects.filter(last_login__isnull=False).exists(),
78
+ }
79
+
80
+ return version_json
81
+
82
+
83
+ @lru_cache()
84
+ def get_file_contents(file_path: str) -> str | None:
85
+ """Attempts to read a file from the filesystem and return the contents"""
86
+ try:
87
+ with open(file_path) as f:
88
+ return f.read().replace("\n", "")
89
+ except FileNotFoundError:
90
+ return None
@@ -0,0 +1,23 @@
1
+ import logging
2
+
3
+ import prometheus_client
4
+ from django.http import HttpResponse, JsonResponse
5
+ from rest_framework.request import Request
6
+
7
+ from common.core import utils
8
+ from common.prometheus.utils import get_registry
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def version_info(request: Request) -> JsonResponse:
14
+ return JsonResponse(utils.get_version_info())
15
+
16
+
17
+ def metrics(request: Request) -> HttpResponse:
18
+ registry = get_registry()
19
+ metrics_page = prometheus_client.generate_latest(registry)
20
+ return HttpResponse(
21
+ metrics_page,
22
+ content_type=prometheus_client.CONTENT_TYPE_LATEST,
23
+ )
@@ -0,0 +1,15 @@
1
+ # Maintain a list of permissions here
2
+ VIEW_ENVIRONMENT = "VIEW_ENVIRONMENT"
3
+ UPDATE_FEATURE_STATE = "UPDATE_FEATURE_STATE"
4
+ MANAGE_IDENTITIES = "MANAGE_IDENTITIES"
5
+ VIEW_IDENTITIES = "VIEW_IDENTITIES"
6
+ CREATE_CHANGE_REQUEST = "CREATE_CHANGE_REQUEST"
7
+ APPROVE_CHANGE_REQUEST = "APPROVE_CHANGE_REQUEST"
8
+ MANAGE_SEGMENT_OVERRIDES = "MANAGE_SEGMENT_OVERRIDES"
9
+
10
+ TAG_SUPPORTED_PERMISSIONS = [
11
+ UPDATE_FEATURE_STATE,
12
+ CREATE_CHANGE_REQUEST,
13
+ APPROVE_CHANGE_REQUEST,
14
+ MANAGE_SEGMENT_OVERRIDES,
15
+ ]
File without changes