{"id":20556423,"url":"https://github.com/flagsmith/flagsmith-common","last_synced_at":"2026-04-20T12:10:20.570Z","repository":{"id":248718412,"uuid":"829471300","full_name":"Flagsmith/flagsmith-common","owner":"Flagsmith","description":"Flagsmith's common library","archived":false,"fork":false,"pushed_at":"2026-04-16T09:46:06.000Z","size":1200,"stargazers_count":2,"open_issues_count":23,"forks_count":2,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-04-16T11:37:26.764Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://flagsmith.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Flagsmith.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-16T13:48:11.000Z","updated_at":"2026-04-13T15:33:57.000Z","dependencies_parsed_at":"2024-07-16T18:39:35.802Z","dependency_job_id":"b433da8b-d893-41ea-8c64-78685a98411c","html_url":"https://github.com/Flagsmith/flagsmith-common","commit_stats":null,"previous_names":["flagsmith/flagsmith-common"],"tags_count":45,"template":false,"template_full_name":null,"purl":"pkg:github/Flagsmith/flagsmith-common","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flagsmith%2Fflagsmith-common","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flagsmith%2Fflagsmith-common/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flagsmith%2Fflagsmith-common/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flagsmith%2Fflagsmith-common/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flagsmith","download_url":"https://codeload.github.com/Flagsmith/flagsmith-common/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flagsmith%2Fflagsmith-common/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32046471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T11:35:06.609Z","status":"ssl_error","status_checked_at":"2026-04-20T11:34:48.899Z","response_time":94,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T03:27:39.448Z","updated_at":"2026-04-20T12:10:20.564Z","avatar_url":"https://github.com/Flagsmith.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# flagsmith-common\n\n[![Coverage](https://codecov.io/gh/Flagsmith/flagsmith-common/graph/badge.svg?token=L3OGOXH86K)](https://codecov.io/gh/Flagsmith/flagsmith-common)\n\nFlagsmith's common library\n\n## Local development\n\nThe project assumes the following tools installed:\n\n- [uv](https://github.com/astral-sh/uv)\n- [GNU Make](https://www.gnu.org/software/make/)\n\nTo list available Makefile targets, run `make help`.\n\nTo set up local development environment, run `make install`.\n\nTo run linters, run `make lint`.\n\nTo run tests, run `make test`.\n\n## Usage\n\n### Installation\n\n1. Install all runtime packages: `uv add flagsmith-common[common-core,task-processor]`\n\n2. 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.\n\n3. Make sure `\"common.core\"` is in the `INSTALLED_APPS` of your settings module.\nThis enables the `manage.py flagsmith` commands.\n\n4. Add `\"common.gunicorn.middleware.RouteLoggerMiddleware\"` to `MIDDLEWARE` in your settings module.\nThis enables the `route` label for Prometheus HTTP metrics.\n\n5. To enable the `/metrics` endpoint, set the `PROMETHEUS_ENABLED` setting to `True`.\n\n### Pre-commit hooks\n\nThis repo provides a [`flagsmith-lint-tests`](.pre-commit-hooks.yaml) hook that enforces test conventions:\n\n- **FT001**: No module-level `Test*` classes — use function-based tests\n- **FT002**: No `import unittest` / `from unittest import TestCase` — use pytest (`unittest.mock` is fine)\n- **FT003**: Test names must follow `test_{subject}__{condition}__{expected}`\n- **FT004**: Test bodies must contain `# Given`, `# When`, and `# Then` comments\n\nTo use in your repo, add to `.pre-commit-config.yaml`:\n\n```yaml\n- repo: https://github.com/Flagsmith/flagsmith-common\n  rev: main\n  hooks:\n    - id: flagsmith-lint-tests\n```\n\nUse `# noqa: FT003` (or any code) inline to suppress individual violations.\n\n### Test tools\n\n#### Fixtures\n\n##### `assert_metric`\n\nTo test your metrics using the `assert_metric` fixture:\n\n```python\nfrom common.test_tools import AssertMetricFixture\n\ndef test_my_code__expected_metrics(assert_metric: AssertMetricFixture) -\u003e None:\n    # When\n    my_code()\n\n    # Then\n    assert_metric(\n        name=\"flagsmith_distance_from_earth_au_sum\",\n        labels={\"engine_type\": \"solar_sail\"},\n        value=1.0,\n    )\n```\n\n##### `saas_mode`\n\nThe `saas_mode` fixture makes all `common.core.utils.is_saas` calls return `True`.\n\n##### `enterprise_mode`\n\nThe `enterprise_mode` fixture makes all `common.core.utils.is_enterprise` calls return `True`.\n\n#### Markers\n\n##### `pytest.mark.saas_mode`\n\nUse this mark to auto-use the `saas_mode` fixture.\n\n##### `pytest.mark.enterprise_mode`\n\nUse this mark to auto-use the `enterprise_mode` fixture.\n\n### OpenTelemetry\n\nFlagsmith supports exporting traces and structured logs over OTLP.\n\n#### Configuration\n\nOTel instrumentation is opt-in, controlled by environment variables:\n\n| Variable                          | Description                                                                                                           | Default         |\n| --------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------- |\n| `OTEL_EXPORTER_OTLP_ENDPOINT`     | Base OTLP endpoint (e.g. `http://collector:4318`). If unset, no OTel setup occurs.                                    | _(disabled)_    |\n| `OTEL_SERVICE_NAME`               | The `service.name` resource attribute. Defaults to `flagsmith-task-processor` when running the task processor.         | `flagsmith-api` |\n| `OTEL_TRACING_EXCLUDED_URL_PATHS` | Comma-separated URL paths to exclude from tracing (e.g. `health/liveness,health/readiness`).                          | _(none)_        |\n\nStandard `OTEL_*` env vars (e.g. `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_EXPORTER_OTLP_HEADERS`) are also respected by the OTel SDK.\n\n#### What gets configured\n\nWhen `OTEL_EXPORTER_OTLP_ENDPOINT` is set, `ensure_cli_env()` sets up:\n\n- **Tracing**: `TracerProvider` with OTLP/HTTP span export, W3C `TraceContext` + `Baggage` propagation, and auto-instrumentation for:\n  - **Django** (`DjangoInstrumentor`): creates a root span per HTTP request with span names formatted as `{METHOD} {route_template}` (e.g. `GET /api/v1/projects/{pk}/`).\n  - **psycopg2** (`Psycopg2Instrumentor`): creates child spans for each SQL query with `db.system`, `db.statement`, and `db.name` attributes. SQL commenter is enabled, adding trace context as SQL comments for database-side correlation.\n  - **Redis** (`RedisInstrumentor`): creates child spans for each Redis command with `db.system` and `db.statement` attributes.\n- **Structured log export**: A structlog processor that emits each log event as both an OTLP log record and a span event (when an active span exists).\n- **Task processor trace propagation**: When a task is enqueued via `TaskHandler.delay()`, the current W3C trace context (including baggage) is serialized into the task's `trace_context` field. When the task processor executes the task, the context is extracted and a child span is created, linking the task execution back to the originating request trace. Span attributes (`task_identifier`, `task_type`, `result`) match the Prometheus metric labels for cross-signal correlation.\n\n#### Emitting OTel log events via structlog\n\nUse structlog as usual. The OTel processor captures events and maps them to OTLP log records:\n\n```python\nimport structlog\n\nlog = structlog.get_logger(\"code_references\")\nlog.info(\"scan-created\", code_references__count=3, feature__count=2)\n```\n\nThis produces:\n\n1. An **OTLP log record** with:\n   - `Body: scan-created`\n   - `EventName: code_references.scan_created` (logger name + `inflection.underscore` of the event)\n   - `Severity: INFO`\n   - `Attributes: code_references.count=3, feature.count=2` (double underscores are converted to dots)\n   - W3C Baggage entries from the current OTel context are copied into log attributes (e.g. `amplitude.device_id`, `amplitude.session_id`).\n\n2. A **span event** on the active span (if one exists) with the same name and attributes. This makes structlog events visible in trace backends (e.g. SigNoz's \"Events\" tab) without requiring separate log correlation. When no span is active (e.g. during startup or management commands), only the OTLP log record is emitted.\n\n### Metrics\n\nFlagsmith uses Prometheus to track performance metrics.\n\nThe following default metrics are exposed:\n\n#### Common metrics\n\n- `flagsmith_build_info`: Has the labels `version` and `ci_commit_sha`.\n- `flagsmith_http_server_request_duration_seconds`: Histogram labeled with `method`, `route`, and `response_status`.\n- `flagsmith_http_server_requests_total`: Counter labeled with `method`, `route`, and `response_status`.\n- `flagsmith_http_server_response_size_bytes`: Histogram labeled with `method`, `route`, and `response_status`.\n- `flagsmith_task_processor_enqueued_tasks_total`: Counter labeled with `task_identifier`.\n\n#### Task Processor metrics\n\n- `flagsmith_task_processor_finished_tasks_total`: Counter labeled with `task_identifier`, `task_type` (`\"recurring\"`, `\"standard\"`) and `result` (`\"success\"`, `\"failure\"`).\n- `flagsmith_task_processor_task_duration_seconds`: Histogram labeled with `task_identifier`, `task_type` (`\"recurring\"`, `\"standard\"`) and `result` (`\"success\"`, `\"failure\"`).\n\n#### Guidelines\n\nTry 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.\n\nAs a reasonable default, Flagsmith metrics are expected to be namespaced with the `\"flagsmith_\"` prefix.\n\nDefine 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.\n\nIt'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:\n\n```python\nimport prometheus_client\nfrom django.conf import settings\n\nflagsmith_distance_from_earth_au = prometheus_client.Histogram(\n    \"flagsmith_distance_from_earth_au\",\n    \"Distance from Earth in astronomical units\",\n    labels=[\"engine_type\"],\n    buckets=settings.DISTANCE_FROM_EARTH_AU_HISTOGRAM_BUCKETS,\n)\n```\n\nFor testing your metrics, refer to [`assert_metric` documentation][5].\n\n[1]: https://prometheus.io/docs/practices/naming/\n[2]: https://github.com/Flagsmith/flagsmith-common/blob/main/src/common/gunicorn/metrics.py\n[3]: https://docs.gunicorn.org/en/stable/design.html#server-model\n[4]: https://prometheus.github.io/client_python/multiprocess\n[5]: #assert_metric\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflagsmith%2Fflagsmith-common","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflagsmith%2Fflagsmith-common","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflagsmith%2Fflagsmith-common/lists"}