Metadata-Version: 2.4
Name: flagsmith-common
Version: 3.4.0
Summary: Flagsmith's common library
Author: Matthew Elwell, Gagan Trivedi, Kim Gustyr, Zach Aysan, Francesco Lo Franco, Rodrigo López Dato, Evandro Myller, Wadii Zaim
License-Expression: BSD-3-Clause
License-File: LICENSE
Classifier: Framework :: Django
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Requires-Dist: django>4,<6 ; extra == 'common-core'
Requires-Dist: django-health-check ; extra == 'common-core'
Requires-Dist: djangorestframework-recursive ; extra == 'common-core'
Requires-Dist: djangorestframework ; extra == 'common-core'
Requires-Dist: drf-spectacular>=0.28.0,<1 ; extra == 'common-core'
Requires-Dist: drf-writable-nested ; extra == 'common-core'
Requires-Dist: environs<15 ; extra == 'common-core'
Requires-Dist: gunicorn>=19.1 ; extra == 'common-core'
Requires-Dist: prometheus-client>=0.0.16 ; extra == 'common-core'
Requires-Dist: psycopg2-binary>=2.9,<3 ; extra == 'common-core'
Requires-Dist: requests ; extra == 'common-core'
Requires-Dist: simplejson>=3,<4 ; extra == 'common-core'
Requires-Dist: simplejson ; extra == 'flagsmith-schemas'
Requires-Dist: typing-extensions ; extra == 'flagsmith-schemas'
Requires-Dist: flagsmith-flag-engine>6 ; extra == 'flagsmith-schemas'
Requires-Dist: backoff>=2.2.1,<3.0.0 ; extra == 'task-processor'
Requires-Dist: django>4,<6 ; extra == 'task-processor'
Requires-Dist: django-health-check ; extra == 'task-processor'
Requires-Dist: prometheus-client>=0.0.16 ; extra == 'task-processor'
Requires-Dist: pyfakefs>=5,<6 ; extra == 'test-tools'
Requires-Dist: pytest-django>=4,<5 ; extra == 'test-tools'
Maintainer: Flagsmith Team
Maintainer-email: Flagsmith Team <support@flagsmith.com>
Requires-Python: >=3.11, <4.0
Project-URL: Changelog, https://github.com/flagsmith/flagsmith-common/blob/main/CHANGELOG.md
Project-URL: Download, https://github.com/flagsmith/flagsmith-common/releases
Project-URL: Homepage, https://flagsmith.com
Project-URL: Issues, https://github.com/flagsmith/flagsmith-common/issues
Project-URL: Repository, https://github.com/flagsmith/flagsmith-common
Provides-Extra: common-core
Provides-Extra: flagsmith-schemas
Provides-Extra: task-processor
Provides-Extra: test-tools
Description-Content-Type: text/markdown

# flagsmith-common

[![Coverage](https://codecov.io/gh/Flagsmith/flagsmith-common/graph/badge.svg?token=L3OGOXH86K)](https://codecov.io/gh/Flagsmith/flagsmith-common)

Flagsmith's common library

## Local development

The project assumes the following tools installed:

- [uv](https://github.com/astral-sh/uv)
- [GNU Make](https://www.gnu.org/software/make/)

To list available Makefile targets, run `make help`.

To set up local development environment, run `make install`.

To run linters, run `make lint`.

To run tests, run `make test`.

## Usage

### Installation

1. Install all runtime packages: `uv add flagsmith-common[common-core,task-processor]`

2. To enable the Pytest fixtures, run `uv add --G dev flagsmith-common[test-tools]`. Skipping this step will make Pytest collection fail due to missing dependencies.

3. Make sure `"common.core"` is in the `INSTALLED_APPS` of your settings module.
This enables the `manage.py flagsmith` commands.

4. Add `"common.gunicorn.middleware.RouteLoggerMiddleware"` to `MIDDLEWARE` in your settings module.
This enables the `route` label for Prometheus HTTP metrics.

5. To enable the `/metrics` endpoint, set the `PROMETHEUS_ENABLED` setting to `True`.

### Pre-commit hooks

This repo provides a [`flagsmith-lint-tests`](.pre-commit-hooks.yaml) hook that enforces test conventions:

- **FT001**: No module-level `Test*` classes — use function-based tests
- **FT002**: No `import unittest` / `from unittest import TestCase` — use pytest (`unittest.mock` is fine)
- **FT003**: Test names must follow `test_{subject}__{condition}__{expected}`
- **FT004**: Test bodies must contain `# Given`, `# When`, and `# Then` comments

To use in your repo, add to `.pre-commit-config.yaml`:

```yaml
- repo: https://github.com/Flagsmith/flagsmith-common
  rev: main
  hooks:
    - id: flagsmith-lint-tests
```

Use `# noqa: FT003` (or any code) inline to suppress individual violations.

### Test tools

#### Fixtures

##### `assert_metric`

To test your metrics using the `assert_metric` fixture:

```python
from common.test_tools import AssertMetricFixture

def test_my_code__expected_metrics(assert_metric: AssertMetricFixture) -> None:
    # When
    my_code()

    # Then
    assert_metric(
        name="flagsmith_distance_from_earth_au_sum",
        labels={"engine_type": "solar_sail"},
        value=1.0,
    )
```

##### `saas_mode`

The `saas_mode` fixture makes all `common.core.utils.is_saas` calls return `True`.

##### `enterprise_mode`

The `enterprise_mode` fixture makes all `common.core.utils.is_enterprise` calls return `True`.

#### Markers

##### `pytest.mark.saas_mode`

Use this mark to auto-use the `saas_mode` fixture.

##### `pytest.mark.enterprise_mode`

Use this mark to auto-use the `enterprise_mode` fixture.

### Metrics

Flagsmith uses Prometheus to track performance metrics.

The following default metrics are exposed:

#### Common metrics

- `flagsmith_build_info`: Has the labels `version` and `ci_commit_sha`.
- `flagsmith_http_server_request_duration_seconds`: Histogram labeled with `method`, `route`, and `response_status`.
- `flagsmith_http_server_requests_total`: Counter labeled with `method`, `route`, and `response_status`.
- `flagsmith_http_server_response_size_bytes`: Histogram labeled with `method`, `route`, and `response_status`.
- `flagsmith_task_processor_enqueued_tasks_total`: Counter labeled with `task_identifier`.

#### Task Processor metrics

- `flagsmith_task_processor_finished_tasks_total`: Counter labeled with `task_identifier`, `task_type` (`"recurring"`, `"standard"`) and `result` (`"success"`, `"failure"`).
- `flagsmith_task_processor_task_duration_seconds`: Histogram labeled with `task_identifier`, `task_type` (`"recurring"`, `"standard"`) and `result` (`"success"`, `"failure"`).

#### Guidelines

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.

As a reasonable default, Flagsmith metrics are expected to be namespaced with the `"flagsmith_"` prefix.

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.

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:

```python
import prometheus_client
from django.conf import settings

flagsmith_distance_from_earth_au = prometheus_client.Histogram(
    "flagsmith_distance_from_earth_au",
    "Distance from Earth in astronomical units",
    labels=["engine_type"],
    buckets=settings.DISTANCE_FROM_EARTH_AU_HISTOGRAM_BUCKETS,
)
```

For testing your metrics, refer to [`assert_metric` documentation][5].

[1]: https://prometheus.io/docs/practices/naming/
[2]: https://github.com/Flagsmith/flagsmith-common/blob/main/src/common/gunicorn/metrics.py
[3]: https://docs.gunicorn.org/en/stable/design.html#server-model
[4]: https://prometheus.github.io/client_python/multiprocess
[5]: #assert_metric
