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.
- flagsmith_common-1.5.0/LICENSE +28 -0
- flagsmith_common-1.5.0/PKG-INFO +128 -0
- flagsmith_common-1.5.0/README.md +95 -0
- flagsmith_common-1.5.0/pyproject.toml +119 -0
- flagsmith_common-1.5.0/src/common/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/core/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/core/app.py +6 -0
- flagsmith_common-1.5.0/src/common/core/logging.py +24 -0
- flagsmith_common-1.5.0/src/common/core/main.py +40 -0
- flagsmith_common-1.5.0/src/common/core/management/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/core/management/commands/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/core/management/commands/start.py +41 -0
- flagsmith_common-1.5.0/src/common/core/metrics.py +23 -0
- flagsmith_common-1.5.0/src/common/core/urls.py +17 -0
- flagsmith_common-1.5.0/src/common/core/utils.py +90 -0
- flagsmith_common-1.5.0/src/common/core/views.py +23 -0
- flagsmith_common-1.5.0/src/common/environments/permissions.py +15 -0
- flagsmith_common-1.5.0/src/common/features/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/features/multivariate/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/features/multivariate/serializers.py +19 -0
- flagsmith_common-1.5.0/src/common/features/serializers.py +68 -0
- flagsmith_common-1.5.0/src/common/features/versioning/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/features/versioning/serializers.py +13 -0
- flagsmith_common-1.5.0/src/common/gunicorn/__init__.py +0 -0
- flagsmith_common-1.5.0/src/common/gunicorn/conf.py +18 -0
- flagsmith_common-1.5.0/src/common/gunicorn/constants.py +1 -0
- flagsmith_common-1.5.0/src/common/gunicorn/logging.py +94 -0
- flagsmith_common-1.5.0/src/common/gunicorn/metrics.py +14 -0
- flagsmith_common-1.5.0/src/common/gunicorn/middleware.py +28 -0
- flagsmith_common-1.5.0/src/common/gunicorn/utils.py +61 -0
- flagsmith_common-1.5.0/src/common/metadata/serializers.py +100 -0
- flagsmith_common-1.5.0/src/common/organisations/permissions.py +10 -0
- flagsmith_common-1.5.0/src/common/projects/permissions.py +40 -0
- flagsmith_common-1.5.0/src/common/prometheus/__init__.py +3 -0
- flagsmith_common-1.5.0/src/common/prometheus/utils.py +18 -0
- flagsmith_common-1.5.0/src/common/py.typed +0 -0
- flagsmith_common-1.5.0/src/common/segments/serializers.py +338 -0
- flagsmith_common-1.5.0/src/common/test_tools/__init__.py +3 -0
- flagsmith_common-1.5.0/src/common/test_tools/plugin.py +34 -0
- flagsmith_common-1.5.0/src/common/test_tools/types.py +11 -0
- 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,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)
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
|
File without changes
|