flagsmith-common 3.10.1__tar.gz → 3.12.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 (115) hide show
  1. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/PKG-INFO +33 -3
  2. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/README.md +30 -2
  3. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/pyproject.toml +4 -2
  4. flagsmith_common-3.12.0/src/common/core/cli/run.py +79 -0
  5. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/constants.py +4 -0
  6. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/main.py +26 -4
  7. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/otel.py +50 -10
  8. flagsmith_common-3.12.0/src/common/core/types.py +3 -0
  9. flagsmith_common-3.12.0/src/task_processor/constants.py +4 -0
  10. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/utils.py +31 -4
  11. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/LICENSE +0 -0
  12. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/__init__.py +0 -0
  13. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/__init__.py +0 -0
  14. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/app.py +0 -0
  15. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/cli/__init__.py +0 -0
  16. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/cli/healthcheck.py +0 -0
  17. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/docgen/__init__.py +0 -0
  18. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/docgen/events.py +0 -0
  19. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/logging.py +0 -0
  20. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/management/__init__.py +0 -0
  21. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/management/commands/__init__.py +0 -0
  22. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/management/commands/docgen.py +0 -0
  23. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/management/commands/start.py +0 -0
  24. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/management/commands/waitfordb.py +0 -0
  25. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/metrics.py +0 -0
  26. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/middleware.py +0 -0
  27. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/sentry.py +0 -0
  28. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/templates/docgen-events.md +0 -0
  29. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/templates/docgen-metrics.md +0 -0
  30. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/urls.py +0 -0
  31. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/utils.py +0 -0
  32. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/core/views.py +0 -0
  33. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/environments/permissions.py +0 -0
  34. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/features/__init__.py +0 -0
  35. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/features/multivariate/__init__.py +0 -0
  36. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/features/multivariate/serializers.py +0 -0
  37. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/features/serializers.py +0 -0
  38. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/features/versioning/__init__.py +0 -0
  39. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/features/versioning/serializers.py +0 -0
  40. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/__init__.py +0 -0
  41. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/conf.py +0 -0
  42. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/constants.py +0 -0
  43. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/logging.py +0 -0
  44. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/metrics.py +0 -0
  45. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/metrics_server.py +0 -0
  46. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/middleware.py +0 -0
  47. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/processors.py +0 -0
  48. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/gunicorn/utils.py +0 -0
  49. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/lint_tests.py +0 -0
  50. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/migrations/__init__.py +0 -0
  51. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/migrations/helpers/__init__.py +0 -0
  52. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/migrations/helpers/postgres_helpers.py +0 -0
  53. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/organisations/permissions.py +0 -0
  54. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/projects/permissions.py +0 -0
  55. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/prometheus/__init__.py +0 -0
  56. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/prometheus/utils.py +0 -0
  57. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/py.typed +0 -0
  58. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/test_tools/__init__.py +0 -0
  59. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/test_tools/plugin.py +0 -0
  60. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/test_tools/types.py +0 -0
  61. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/test_tools/utils.py +0 -0
  62. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/common/types.py +0 -0
  63. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/__init__.py +0 -0
  64. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/api.py +0 -0
  65. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/constants.py +0 -0
  66. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/dynamodb.py +0 -0
  67. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/py.typed +0 -0
  68. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/pydantic_types.py +0 -0
  69. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/types.py +0 -0
  70. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/utils.py +0 -0
  71. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/flagsmith_schemas/validators.py +0 -0
  72. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/__init__.py +0 -0
  73. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/admin.py +0 -0
  74. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/apps.py +0 -0
  75. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/decorators.py +0 -0
  76. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/exceptions.py +0 -0
  77. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/health.py +0 -0
  78. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/managers.py +0 -0
  79. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/metrics.py +0 -0
  80. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0001_initial.py +0 -0
  81. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0002_healthcheckmodel.py +0 -0
  82. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0003_add_completed_to_task.py +0 -0
  83. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0004_recreate_task_indexes.py +0 -0
  84. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0005_update_conditional_index_conditions.py +0 -0
  85. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0006_auto_20230221_0802.py +0 -0
  86. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0007_add_is_locked.py +0 -0
  87. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0008_add_get_task_to_process_function.py +0 -0
  88. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0009_add_recurring_task_run_first_run_at.py +0 -0
  89. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0010_task_priority.py +0 -0
  90. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0011_add_priority_to_get_tasks_to_process.py +0 -0
  91. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0012_add_locked_at_and_timeout.py +0 -0
  92. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0013_add_last_picked_at.py +0 -0
  93. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0014_add_trace_context.py +0 -0
  94. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/0015_add_is_disabled.py +0 -0
  95. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/__init__.py +0 -0
  96. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/0008_get_recurring_tasks_to_process.sql +0 -0
  97. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/0008_get_tasks_to_process.sql +0 -0
  98. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/0011_get_tasks_to_process.sql +0 -0
  99. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/0012_get_recurringtasks_to_process.sql +0 -0
  100. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/0013_get_recurringtasks_to_process.sql +0 -0
  101. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/0015_get_recurringtasks_to_process.sql +0 -0
  102. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/migrations/sql/__init__.py +0 -0
  103. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/models.py +0 -0
  104. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/monitoring.py +0 -0
  105. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/processor.py +0 -0
  106. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/py.typed +0 -0
  107. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/routers.py +0 -0
  108. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/serializers.py +0 -0
  109. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/task_registry.py +0 -0
  110. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/task_run_method.py +0 -0
  111. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/tasks.py +0 -0
  112. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/threads.py +0 -0
  113. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/types.py +0 -0
  114. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/urls.py +0 -0
  115. {flagsmith_common-3.10.1 → flagsmith_common-3.12.0}/src/task_processor/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flagsmith-common
3
- Version: 3.10.1
3
+ Version: 3.12.0
4
4
  Summary: Flagsmith's common library
5
5
  Author: Matthew Elwell, Gagan Trivedi, Kim Gustyr, Zach Aysan, Francesco Lo Franco, Rodrigo López Dato, Evandro Myller, Wadii Zaim
6
6
  License-Expression: BSD-3-Clause
@@ -32,6 +32,7 @@ Requires-Dist: flagsmith-flag-engine>6 ; extra == 'flagsmith-schemas'
32
32
  Requires-Dist: inflection ; extra == 'otel'
33
33
  Requires-Dist: opentelemetry-api>=1.25,<2 ; extra == 'otel'
34
34
  Requires-Dist: opentelemetry-sdk>=1.25,<2 ; extra == 'otel'
35
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.25,<2 ; extra == 'otel'
35
36
  Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.25,<2 ; extra == 'otel'
36
37
  Requires-Dist: sentry-sdk>=2.0.0,<3.0.0 ; extra == 'otel'
37
38
  Requires-Dist: structlog>=24.4,<26 ; extra == 'otel'
@@ -39,6 +40,7 @@ Requires-Dist: typing-extensions ; extra == 'otel'
39
40
  Requires-Dist: backoff>=2.2.1,<3.0.0 ; extra == 'task-processor'
40
41
  Requires-Dist: django>4,<6 ; extra == 'task-processor'
41
42
  Requires-Dist: django-health-check ; extra == 'task-processor'
43
+ Requires-Dist: environs<16 ; extra == 'task-processor'
42
44
  Requires-Dist: opentelemetry-api>=1.25,<2 ; extra == 'task-processor'
43
45
  Requires-Dist: prometheus-client>=0.0.16 ; extra == 'task-processor'
44
46
  Requires-Dist: pyfakefs>=6,<7 ; extra == 'test-tools'
@@ -95,6 +97,33 @@ This enables the `route` label for Prometheus HTTP metrics.
95
97
 
96
98
  5. To enable the `/metrics` endpoint, set the `PROMETHEUS_ENABLED` setting to `True`.
97
99
 
100
+ ### Startup commands
101
+
102
+ `common.core` registers composite startup verbs on the `flagsmith` command, so a container entrypoint can sequence startup itself rather than via a shell script:
103
+
104
+ - `flagsmith serve` — wait for the database, then start the API.
105
+ - `flagsmith migrate` — migrate each configured database, then create the cache table. A bare invocation runs the full sequence; `flagsmith migrate <app> <name>` defers to Django for targeted migrations.
106
+ - `flagsmith run-task-processor` — migrate, wait for migrations to be applied, then start the Task Processor.
107
+ - `flagsmith migrate-and-serve` — migrate, run any configured startup commands, then start the API.
108
+
109
+ The verbs are configured through these settings:
110
+
111
+ | Setting | Default | Purpose |
112
+ | --- | --- | --- |
113
+ | `FLAGSMITH_MIGRATE_DATABASES` | `["default"]` | Database aliases the `migrate` step applies, in order. |
114
+ | `FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES` | `["default"]` | Database aliases `run-task-processor` waits on before starting. |
115
+ | `FLAGSMITH_STARTUP_COMMANDS` | `[]` | Management commands run, in order, between migrate and serve in `migrate-and-serve`. |
116
+
117
+ …and these environment variables:
118
+
119
+ | Environment variable | Default | Purpose |
120
+ | --- | --- | --- |
121
+ | `SKIP_WAIT_FOR_DB` | unset | When set, skip waiting for the database. |
122
+ | `TASK_PROCESSOR_NUM_THREADS` | `5` | Number of worker threads. |
123
+ | `TASK_PROCESSOR_SLEEP_INTERVAL_MS` | `500` | Millis each worker waits before checking for new tasks (falls back to `TASK_PROCESSOR_SLEEP_INTERVAL`). |
124
+ | `TASK_PROCESSOR_GRACE_PERIOD_MS` | `20000` | Millis before a running task is considered stuck. |
125
+ | `TASK_PROCESSOR_QUEUE_POP_SIZE` | `10` | Tasks each worker pops from the queue per cycle. |
126
+
98
127
  ### Pre-commit hooks
99
128
 
100
129
  This repo provides a [`flagsmith-lint-tests`](.pre-commit-hooks.yaml) hook that enforces test conventions:
@@ -166,7 +195,8 @@ OTel instrumentation is opt-in, controlled by environment variables:
166
195
 
167
196
  | Variable | Description | Default |
168
197
  | --------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------- |
169
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP endpoint (e.g. `http://collector:4318`). If unset, no OTel setup occurs. | _(disabled)_ |
198
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP endpoint (e.g. `http://collector:4318` for HTTP, `http://collector:4317` for gRPC). If unset, no OTel setup occurs. | _(disabled)_ |
199
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP transport: `grpc` or `http/protobuf` (default). | `http/protobuf` |
170
200
  | `OTEL_SERVICE_NAME` | The `service.name` resource attribute. Defaults to `flagsmith-task-processor` when running the task processor. | `flagsmith-api` |
171
201
  | `OTEL_TRACING_EXCLUDED_URL_PATHS` | Comma-separated URL paths to exclude from tracing (e.g. `health/liveness,health/readiness`). | _(none)_ |
172
202
 
@@ -176,7 +206,7 @@ Standard `OTEL_*` env vars (e.g. `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_EXPORTER_OTLP
176
206
 
177
207
  When `OTEL_EXPORTER_OTLP_ENDPOINT` is set, `ensure_cli_env()` sets up:
178
208
 
179
- - **Tracing**: `TracerProvider` with OTLP/HTTP span export, W3C `TraceContext` + `Baggage` propagation, and auto-instrumentation for:
209
+ - **Tracing**: `TracerProvider` with OTLP span export (HTTP or gRPC, per `OTEL_EXPORTER_OTLP_PROTOCOL`), W3C `TraceContext` + `Baggage` propagation, and auto-instrumentation for:
180
210
  - **Django** (`DjangoInstrumentor`): creates a root span per HTTP request with span names formatted as `{METHOD} {route_template}` (e.g. `GET /api/v1/projects/{pk}/`).
181
211
  - **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.
182
212
  - **Redis** (`RedisInstrumentor`): creates child spans for each Redis command with `db.system` and `db.statement` attributes.
@@ -35,6 +35,33 @@ This enables the `route` label for Prometheus HTTP metrics.
35
35
 
36
36
  5. To enable the `/metrics` endpoint, set the `PROMETHEUS_ENABLED` setting to `True`.
37
37
 
38
+ ### Startup commands
39
+
40
+ `common.core` registers composite startup verbs on the `flagsmith` command, so a container entrypoint can sequence startup itself rather than via a shell script:
41
+
42
+ - `flagsmith serve` — wait for the database, then start the API.
43
+ - `flagsmith migrate` — migrate each configured database, then create the cache table. A bare invocation runs the full sequence; `flagsmith migrate <app> <name>` defers to Django for targeted migrations.
44
+ - `flagsmith run-task-processor` — migrate, wait for migrations to be applied, then start the Task Processor.
45
+ - `flagsmith migrate-and-serve` — migrate, run any configured startup commands, then start the API.
46
+
47
+ The verbs are configured through these settings:
48
+
49
+ | Setting | Default | Purpose |
50
+ | --- | --- | --- |
51
+ | `FLAGSMITH_MIGRATE_DATABASES` | `["default"]` | Database aliases the `migrate` step applies, in order. |
52
+ | `FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES` | `["default"]` | Database aliases `run-task-processor` waits on before starting. |
53
+ | `FLAGSMITH_STARTUP_COMMANDS` | `[]` | Management commands run, in order, between migrate and serve in `migrate-and-serve`. |
54
+
55
+ …and these environment variables:
56
+
57
+ | Environment variable | Default | Purpose |
58
+ | --- | --- | --- |
59
+ | `SKIP_WAIT_FOR_DB` | unset | When set, skip waiting for the database. |
60
+ | `TASK_PROCESSOR_NUM_THREADS` | `5` | Number of worker threads. |
61
+ | `TASK_PROCESSOR_SLEEP_INTERVAL_MS` | `500` | Millis each worker waits before checking for new tasks (falls back to `TASK_PROCESSOR_SLEEP_INTERVAL`). |
62
+ | `TASK_PROCESSOR_GRACE_PERIOD_MS` | `20000` | Millis before a running task is considered stuck. |
63
+ | `TASK_PROCESSOR_QUEUE_POP_SIZE` | `10` | Tasks each worker pops from the queue per cycle. |
64
+
38
65
  ### Pre-commit hooks
39
66
 
40
67
  This repo provides a [`flagsmith-lint-tests`](.pre-commit-hooks.yaml) hook that enforces test conventions:
@@ -106,7 +133,8 @@ OTel instrumentation is opt-in, controlled by environment variables:
106
133
 
107
134
  | Variable | Description | Default |
108
135
  | --------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------- |
109
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP endpoint (e.g. `http://collector:4318`). If unset, no OTel setup occurs. | _(disabled)_ |
136
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP endpoint (e.g. `http://collector:4318` for HTTP, `http://collector:4317` for gRPC). If unset, no OTel setup occurs. | _(disabled)_ |
137
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP transport: `grpc` or `http/protobuf` (default). | `http/protobuf` |
110
138
  | `OTEL_SERVICE_NAME` | The `service.name` resource attribute. Defaults to `flagsmith-task-processor` when running the task processor. | `flagsmith-api` |
111
139
  | `OTEL_TRACING_EXCLUDED_URL_PATHS` | Comma-separated URL paths to exclude from tracing (e.g. `health/liveness,health/readiness`). | _(none)_ |
112
140
 
@@ -116,7 +144,7 @@ Standard `OTEL_*` env vars (e.g. `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_EXPORTER_OTLP
116
144
 
117
145
  When `OTEL_EXPORTER_OTLP_ENDPOINT` is set, `ensure_cli_env()` sets up:
118
146
 
119
- - **Tracing**: `TracerProvider` with OTLP/HTTP span export, W3C `TraceContext` + `Baggage` propagation, and auto-instrumentation for:
147
+ - **Tracing**: `TracerProvider` with OTLP span export (HTTP or gRPC, per `OTEL_EXPORTER_OTLP_PROTOCOL`), W3C `TraceContext` + `Baggage` propagation, and auto-instrumentation for:
120
148
  - **Django** (`DjangoInstrumentor`): creates a root span per HTTP request with span names formatted as `{METHOD} {route_template}` (e.g. `GET /api/v1/projects/{pk}/`).
121
149
  - **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.
122
150
  - **Redis** (`RedisInstrumentor`): creates child spans for each Redis command with `db.system` and `db.statement` attributes.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flagsmith-common"
3
- version = "3.10.1"
3
+ version = "3.12.0"
4
4
  description = "Flagsmith's common library"
5
5
  requires-python = ">=3.11,<4.0"
6
6
  dependencies = []
@@ -29,6 +29,7 @@ optional-dependencies = { test-tools = [
29
29
  "inflection",
30
30
  "opentelemetry-api (>=1.25,<2)",
31
31
  "opentelemetry-sdk (>=1.25,<2)",
32
+ "opentelemetry-exporter-otlp-proto-grpc (>=1.25,<2)",
32
33
  "opentelemetry-exporter-otlp-proto-http (>=1.25,<2)",
33
34
  "sentry-sdk (>=2.0.0,<3.0.0)",
34
35
  "structlog (>=24.4,<26)",
@@ -37,6 +38,7 @@ optional-dependencies = { test-tools = [
37
38
  "backoff (>=2.2.1,<3.0.0)",
38
39
  "django (>4,<6)",
39
40
  "django-health-check",
41
+ "environs (<16)",
40
42
  "opentelemetry-api (>=1.25,<2)",
41
43
  "prometheus-client (>=0.0.16)",
42
44
  ], flagsmith-schemas = [
@@ -96,7 +98,7 @@ dev = [
96
98
  "pytest-freezegun (>=0.4.2, <1.0.0)",
97
99
  "pytest-httpserver (>=1.1.3, <2.0.0)",
98
100
  "pytest-mock (>=3.14.0, <4.0.0)",
99
- "setuptools (>=78.1.1, <79.0.0)",
101
+ "setuptools (>=83.0.0, <83.1.0)",
100
102
  "types-python-dateutil (>=2.9.0.20250516, <3.0.0)",
101
103
  "types-simplejson (>=3.20.0.20250326, <4.0.0)",
102
104
  ]
@@ -0,0 +1,79 @@
1
+ """Composite startup verbs for the `flagsmith` entrypoint.
2
+
3
+ These mirror the sequencing that Core API's `run-docker.sh` performed in
4
+ shell, but run in a single process so the Django boot cost is paid once.
5
+ """
6
+
7
+ import os
8
+ import shlex
9
+
10
+ from django.conf import settings
11
+ from django.core.management import (
12
+ execute_from_command_line as django_execute_from_command_line,
13
+ )
14
+
15
+ WAIT_FOR_MIGRATIONS_TIMEOUT_SECONDS = 30
16
+
17
+
18
+ def _wait_for_db(
19
+ *,
20
+ wait_for_migrations: bool = False,
21
+ database: str = "default",
22
+ wait_for: int | None = None,
23
+ ) -> None:
24
+ if os.environ.get("SKIP_WAIT_FOR_DB"):
25
+ return
26
+ args = ["waitfordb", "--database", database]
27
+ if wait_for is not None:
28
+ args += ["--waitfor", str(wait_for)]
29
+ if wait_for_migrations:
30
+ args.append("--migrations")
31
+ django_execute_from_command_line(["flagsmith", *args])
32
+
33
+
34
+ def _migrate() -> None:
35
+ _wait_for_db()
36
+ databases: list[str] = getattr(settings, "FLAGSMITH_MIGRATE_DATABASES", ["default"])
37
+ for database in databases:
38
+ django_execute_from_command_line(
39
+ ["flagsmith", "migrate", "--database", database]
40
+ )
41
+ django_execute_from_command_line(["flagsmith", "createcachetable"])
42
+
43
+
44
+ def migrate(argv: list[str], *, prog: str) -> None:
45
+ """Migrate the configured databases, then create the cache table."""
46
+ if argv:
47
+ django_execute_from_command_line(["flagsmith", "migrate", *argv])
48
+ return
49
+ _migrate()
50
+
51
+
52
+ def serve(argv: list[str], *, prog: str) -> None:
53
+ """Wait for the database, then start the API server."""
54
+ _wait_for_db()
55
+ django_execute_from_command_line(["flagsmith", "start", "api", *argv])
56
+
57
+
58
+ def run_task_processor(argv: list[str], *, prog: str) -> None:
59
+ """Migrate, wait for migrations to be applied, then start the task processor."""
60
+ _migrate()
61
+ databases: list[str] = getattr(
62
+ settings, "FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES", ["default"]
63
+ )
64
+ for database in databases:
65
+ _wait_for_db(
66
+ wait_for_migrations=True,
67
+ database=database,
68
+ wait_for=WAIT_FOR_MIGRATIONS_TIMEOUT_SECONDS,
69
+ )
70
+ django_execute_from_command_line(["flagsmith", "start", "task-processor", *argv])
71
+
72
+
73
+ def migrate_and_serve(argv: list[str], *, prog: str) -> None:
74
+ """Migrate, run any configured startup commands, then start the API server."""
75
+ _migrate()
76
+ startup_commands: list[str] = getattr(settings, "FLAGSMITH_STARTUP_COMMANDS", [])
77
+ for command in startup_commands:
78
+ django_execute_from_command_line(["flagsmith", *shlex.split(command)])
79
+ django_execute_from_command_line(["flagsmith", "start", "api", *argv])
@@ -1,3 +1,7 @@
1
+ from common.core.types import OtlpProtocol
2
+
1
3
  DEFAULT_PROMETHEUS_MULTIPROC_DIR_NAME = "flagsmith-prometheus"
2
4
 
3
5
  LOGGING_DEFAULT_ROOT_LOG_LEVEL = "WARNING"
6
+
7
+ DEFAULT_OTLP_PROTOCOL: OtlpProtocol = "http/protobuf"
@@ -8,10 +8,12 @@ from tempfile import mkdtemp
8
8
  from django.core.management import (
9
9
  execute_from_command_line as django_execute_from_command_line,
10
10
  )
11
- from environs import Env
11
+ from environs import Env, validate
12
12
 
13
- from common.core.cli import healthcheck
13
+ from common.core.cli import healthcheck, run
14
+ from common.core.constants import DEFAULT_OTLP_PROTOCOL
14
15
  from common.core.logging import setup_logging
16
+ from common.core.types import OtlpProtocol
15
17
  from common.gunicorn.processors import make_gunicorn_access_processor
16
18
 
17
19
  env = Env()
@@ -63,6 +65,7 @@ def ensure_cli_env() -> typing.Generator[None, None, None]:
63
65
  build_otel_log_provider,
64
66
  build_tracer_provider,
65
67
  make_structlog_otel_processor,
68
+ resolve_otlp_export_endpoints,
66
69
  setup_tracing,
67
70
  )
68
71
 
@@ -72,17 +75,31 @@ def ensure_cli_env() -> typing.Generator[None, None, None]:
72
75
  else "flagsmith-api"
73
76
  )
74
77
  service_name = env.str("OTEL_SERVICE_NAME", default_service_name)
78
+ otel_protocol = typing.cast(
79
+ OtlpProtocol,
80
+ env.str(
81
+ "OTEL_EXPORTER_OTLP_PROTOCOL",
82
+ DEFAULT_OTLP_PROTOCOL,
83
+ validate=validate.OneOf(typing.get_args(OtlpProtocol)),
84
+ ),
85
+ )
86
+ traces_endpoint, logs_endpoint = resolve_otlp_export_endpoints(
87
+ otel_endpoint,
88
+ otel_protocol,
89
+ )
75
90
  log_provider = build_otel_log_provider(
76
- endpoint=f"{otel_endpoint}/v1/logs",
91
+ endpoint=logs_endpoint,
77
92
  service_name=service_name,
93
+ protocol=otel_protocol,
78
94
  )
79
95
  otel_processors = [
80
96
  add_otel_trace_context,
81
97
  make_structlog_otel_processor(log_provider),
82
98
  ]
83
99
  tracer_provider = build_tracer_provider(
84
- endpoint=f"{otel_endpoint}/v1/traces",
100
+ endpoint=traces_endpoint,
85
101
  service_name=service_name,
102
+ protocol=otel_protocol,
86
103
  )
87
104
  excluded_urls = env.str("OTEL_TRACING_EXCLUDED_URL_PATHS", None)
88
105
  ctx.enter_context(setup_tracing(tracer_provider, excluded_urls=excluded_urls))
@@ -118,6 +135,11 @@ def execute_from_command_line(argv: list[str]) -> None:
118
135
  # Backwards compatibility for task-processor health checks
119
136
  # See https://github.com/Flagsmith/flagsmith-task-processor/issues/24
120
137
  "checktaskprocessorthreadhealth": healthcheck.main,
138
+ # Composite startup verbs, formerly run-docker.sh
139
+ "serve": run.serve,
140
+ "migrate": run.migrate,
141
+ "run-task-processor": run.run_task_processor,
142
+ "migrate-and-serve": run.migrate_and_serve,
121
143
  }[subcommand]
122
144
  except (IndexError, KeyError):
123
145
  logger.info("Invoking Django")
@@ -11,26 +11,37 @@ from opentelemetry import baggage, trace
11
11
  from opentelemetry import context as otel_context
12
12
  from opentelemetry._logs import SeverityNumber
13
13
  from opentelemetry.baggage.propagation import W3CBaggagePropagator
14
+ from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
15
+ OTLPLogExporter as GrpcOTLPLogExporter,
16
+ )
17
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
18
+ OTLPSpanExporter as GrpcOTLPSpanExporter,
19
+ )
14
20
  from opentelemetry.exporter.otlp.proto.http._log_exporter import (
15
- OTLPLogExporter,
21
+ OTLPLogExporter as HttpOTLPLogExporter,
16
22
  )
17
23
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
18
- OTLPSpanExporter,
24
+ OTLPSpanExporter as HttpOTLPSpanExporter,
19
25
  )
20
26
  from opentelemetry.propagate import set_global_textmap
21
27
  from opentelemetry.propagators.composite import CompositePropagator
22
28
  from opentelemetry.propagators.textmap import TextMapPropagator
23
29
  from opentelemetry.sdk._logs import LoggerProvider
24
- from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
30
+ from opentelemetry.sdk._logs.export import (
31
+ BatchLogRecordProcessor,
32
+ LogRecordExporter,
33
+ )
25
34
  from opentelemetry.sdk.resources import Resource
26
35
  from opentelemetry.sdk.trace import TracerProvider
27
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
36
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
28
37
  from opentelemetry.trace.propagation.tracecontext import (
29
38
  TraceContextTextMapPropagator,
30
39
  )
31
40
  from opentelemetry.util.types import AnyValue, Attributes
32
41
  from structlog.typing import EventDict, Processor
33
42
 
43
+ from common.core.types import OtlpProtocol
44
+
34
45
  _SEVERITY_MAP: dict[str, SeverityNumber] = {
35
46
  "debug": SeverityNumber.DEBUG,
36
47
  "info": SeverityNumber.INFO,
@@ -51,6 +62,17 @@ _RESERVED_KEYS = frozenset(
51
62
  )
52
63
 
53
64
 
65
+ def resolve_otlp_export_endpoints(
66
+ base_endpoint: str,
67
+ protocol: OtlpProtocol,
68
+ ) -> tuple[str, str]:
69
+ """Resolve trace and log exporter endpoints from a base OTLP endpoint."""
70
+ normalised_base = base_endpoint.rstrip("/")
71
+ if protocol == "grpc":
72
+ return normalised_base, normalised_base
73
+ return f"{normalised_base}/v1/traces", f"{normalised_base}/v1/logs"
74
+
75
+
54
76
  def get_otel_event_name(*, logger_name: str | None, body: str) -> str:
55
77
  """Build the event name that reaches OTel from a structlog `(logger, event)`.
56
78
 
@@ -153,20 +175,38 @@ def map_value_to_otel_value(value: object) -> str | int | float | bool:
153
175
  return json.dumps(value, default=str)
154
176
 
155
177
 
156
- def build_otel_log_provider(*, endpoint: str, service_name: str) -> LoggerProvider:
157
- """Create and configure an OTel LoggerProvider with OTLP/HTTP export."""
178
+ def build_otel_log_provider(
179
+ *,
180
+ endpoint: str,
181
+ service_name: str,
182
+ protocol: OtlpProtocol,
183
+ ) -> LoggerProvider:
184
+ """Create and configure an OTel LoggerProvider with OTLP export."""
158
185
  resource = Resource.create({"service.name": service_name})
159
186
  provider = LoggerProvider(resource=resource)
160
- exporter = OTLPLogExporter(endpoint=endpoint)
187
+ exporter: LogRecordExporter
188
+ if protocol == "grpc":
189
+ exporter = GrpcOTLPLogExporter(endpoint=endpoint)
190
+ else:
191
+ exporter = HttpOTLPLogExporter(endpoint=endpoint)
161
192
  provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
162
193
  return provider
163
194
 
164
195
 
165
- def build_tracer_provider(*, endpoint: str, service_name: str) -> TracerProvider:
166
- """Create a TracerProvider with OTLP/HTTP export."""
196
+ def build_tracer_provider(
197
+ *,
198
+ endpoint: str,
199
+ service_name: str,
200
+ protocol: OtlpProtocol,
201
+ ) -> TracerProvider:
202
+ """Create a TracerProvider with OTLP export."""
167
203
  resource = Resource.create({"service.name": service_name})
168
204
  tracer_provider = TracerProvider(resource=resource)
169
- span_exporter = OTLPSpanExporter(endpoint=endpoint)
205
+ span_exporter: SpanExporter
206
+ if protocol == "grpc":
207
+ span_exporter = GrpcOTLPSpanExporter(endpoint=endpoint)
208
+ else:
209
+ span_exporter = HttpOTLPSpanExporter(endpoint=endpoint)
170
210
  tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
171
211
  return tracer_provider
172
212
 
@@ -0,0 +1,3 @@
1
+ from typing import Literal
2
+
3
+ OtlpProtocol = Literal["grpc", "http/protobuf"]
@@ -0,0 +1,4 @@
1
+ DEFAULT_TASK_PROCESSOR_NUM_THREADS: int = 5
2
+ DEFAULT_TASK_PROCESSOR_SLEEP_INTERVAL_MS: int = 500
3
+ DEFAULT_TASK_PROCESSOR_GRACE_PERIOD_MS: int = 20000
4
+ DEFAULT_TASK_PROCESSOR_QUEUE_POP_SIZE: int = 10
@@ -1,14 +1,25 @@
1
1
  import argparse
2
2
  import inspect
3
3
  import logging
4
+ import os
4
5
  from contextlib import contextmanager
5
6
  from typing import Any, Generator
6
7
 
8
+ from environs import Env
9
+
10
+ from task_processor.constants import (
11
+ DEFAULT_TASK_PROCESSOR_GRACE_PERIOD_MS,
12
+ DEFAULT_TASK_PROCESSOR_NUM_THREADS,
13
+ DEFAULT_TASK_PROCESSOR_QUEUE_POP_SIZE,
14
+ DEFAULT_TASK_PROCESSOR_SLEEP_INTERVAL_MS,
15
+ )
7
16
  from task_processor.threads import TaskRunnerCoordinator
8
17
  from task_processor.types import TaskCallable, TaskProcessorConfig
9
18
 
10
19
  logger = logging.getLogger(__name__)
11
20
 
21
+ env = Env()
22
+
12
23
 
13
24
  def get_task_identifier_from_function(
14
25
  function: TaskCallable[Any],
@@ -24,25 +35,41 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
24
35
  "--numthreads",
25
36
  type=int,
26
37
  help="Number of worker threads to run.",
27
- default=5,
38
+ default=env.int(
39
+ "TASK_PROCESSOR_NUM_THREADS",
40
+ default=DEFAULT_TASK_PROCESSOR_NUM_THREADS,
41
+ ),
28
42
  )
29
43
  parser.add_argument(
30
44
  "--sleepintervalms",
31
45
  type=int,
32
46
  help="Number of millis each worker waits before checking for new tasks",
33
- default=2000,
47
+ default=(
48
+ env.int("TASK_PROCESSOR_SLEEP_INTERVAL_MS")
49
+ if "TASK_PROCESSOR_SLEEP_INTERVAL_MS" in os.environ
50
+ else env.int(
51
+ "TASK_PROCESSOR_SLEEP_INTERVAL",
52
+ default=DEFAULT_TASK_PROCESSOR_SLEEP_INTERVAL_MS,
53
+ )
54
+ ),
34
55
  )
35
56
  parser.add_argument(
36
57
  "--graceperiodms",
37
58
  type=int,
38
59
  help="Number of millis before running task is considered 'stuck'.",
39
- default=20000,
60
+ default=env.int(
61
+ "TASK_PROCESSOR_GRACE_PERIOD_MS",
62
+ default=DEFAULT_TASK_PROCESSOR_GRACE_PERIOD_MS,
63
+ ),
40
64
  )
41
65
  parser.add_argument(
42
66
  "--queuepopsize",
43
67
  type=int,
44
68
  help="Number of tasks each worker will pop from the queue on each cycle.",
45
- default=10,
69
+ default=env.int(
70
+ "TASK_PROCESSOR_QUEUE_POP_SIZE",
71
+ default=DEFAULT_TASK_PROCESSOR_QUEUE_POP_SIZE,
72
+ ),
46
73
  )
47
74
 
48
75