flagsmith-common 3.2.1__tar.gz → 3.3.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 (102) hide show
  1. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/PKG-INFO +2 -1
  2. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/pyproject.toml +2 -1
  3. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/templates/docgen-metrics.md +3 -1
  4. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/conf.py +11 -1
  5. flagsmith_common-3.3.0/src/common/gunicorn/metrics_server.py +65 -0
  6. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/dynamodb.py +79 -21
  7. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/types.py +51 -2
  8. flagsmith_common-3.3.0/src/flagsmith_schemas/utils.py +15 -0
  9. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/LICENSE +0 -0
  10. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/README.md +0 -0
  11. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/__init__.py +0 -0
  12. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/__init__.py +0 -0
  13. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/app.py +0 -0
  14. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/cli/__init__.py +0 -0
  15. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/cli/healthcheck.py +0 -0
  16. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/constants.py +0 -0
  17. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/logging.py +0 -0
  18. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/main.py +0 -0
  19. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/management/__init__.py +0 -0
  20. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/management/commands/__init__.py +0 -0
  21. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/management/commands/docgen.py +0 -0
  22. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/management/commands/start.py +0 -0
  23. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/management/commands/waitfordb.py +0 -0
  24. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/metrics.py +0 -0
  25. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/middleware.py +0 -0
  26. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/urls.py +0 -0
  27. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/utils.py +0 -0
  28. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/core/views.py +0 -0
  29. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/environments/permissions.py +0 -0
  30. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/features/__init__.py +0 -0
  31. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/features/multivariate/__init__.py +0 -0
  32. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/features/multivariate/serializers.py +0 -0
  33. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/features/serializers.py +0 -0
  34. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/features/versioning/__init__.py +0 -0
  35. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/features/versioning/serializers.py +0 -0
  36. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/__init__.py +0 -0
  37. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/constants.py +0 -0
  38. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/logging.py +0 -0
  39. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/metrics.py +0 -0
  40. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/middleware.py +0 -0
  41. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/gunicorn/utils.py +0 -0
  42. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/migrations/__init__.py +0 -0
  43. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/migrations/helpers/__init__.py +0 -0
  44. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/migrations/helpers/postgres_helpers.py +0 -0
  45. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/organisations/permissions.py +0 -0
  46. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/projects/permissions.py +0 -0
  47. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/prometheus/__init__.py +0 -0
  48. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/prometheus/utils.py +0 -0
  49. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/py.typed +0 -0
  50. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/test_tools/__init__.py +0 -0
  51. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/test_tools/plugin.py +0 -0
  52. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/test_tools/types.py +0 -0
  53. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/test_tools/utils.py +0 -0
  54. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/common/types.py +0 -0
  55. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/__init__.py +0 -0
  56. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/api.py +0 -0
  57. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/constants.py +0 -0
  58. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/py.typed +0 -0
  59. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/pydantic_types.py +0 -0
  60. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/flagsmith_schemas/validators.py +0 -0
  61. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/__init__.py +0 -0
  62. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/admin.py +0 -0
  63. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/apps.py +0 -0
  64. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/decorators.py +0 -0
  65. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/exceptions.py +0 -0
  66. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/health.py +0 -0
  67. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/managers.py +0 -0
  68. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/metrics.py +0 -0
  69. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0001_initial.py +0 -0
  70. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0002_healthcheckmodel.py +0 -0
  71. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0003_add_completed_to_task.py +0 -0
  72. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0004_recreate_task_indexes.py +0 -0
  73. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0005_update_conditional_index_conditions.py +0 -0
  74. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0006_auto_20230221_0802.py +0 -0
  75. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0007_add_is_locked.py +0 -0
  76. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0008_add_get_task_to_process_function.py +0 -0
  77. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0009_add_recurring_task_run_first_run_at.py +0 -0
  78. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0010_task_priority.py +0 -0
  79. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0011_add_priority_to_get_tasks_to_process.py +0 -0
  80. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0012_add_locked_at_and_timeout.py +0 -0
  81. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/0013_add_last_picked_at.py +0 -0
  82. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/__init__.py +0 -0
  83. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/sql/0008_get_recurring_tasks_to_process.sql +0 -0
  84. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/sql/0008_get_tasks_to_process.sql +0 -0
  85. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/sql/0011_get_tasks_to_process.sql +0 -0
  86. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/sql/0012_get_recurringtasks_to_process.sql +0 -0
  87. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/sql/0013_get_recurringtasks_to_process.sql +0 -0
  88. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/migrations/sql/__init__.py +0 -0
  89. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/models.py +0 -0
  90. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/monitoring.py +0 -0
  91. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/processor.py +0 -0
  92. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/py.typed +0 -0
  93. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/routers.py +0 -0
  94. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/serializers.py +0 -0
  95. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/task_registry.py +0 -0
  96. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/task_run_method.py +0 -0
  97. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/tasks.py +0 -0
  98. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/threads.py +0 -0
  99. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/types.py +0 -0
  100. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/urls.py +0 -0
  101. {flagsmith_common-3.2.1 → flagsmith_common-3.3.0}/src/task_processor/utils.py +0 -0
  102. {flagsmith_common-3.2.1 → flagsmith_common-3.3.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.2.1
3
+ Version: 3.3.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
@@ -21,6 +21,7 @@ Requires-Dist: prometheus-client>=0.0.16 ; extra == 'common-core'
21
21
  Requires-Dist: psycopg2-binary>=2.9,<3 ; extra == 'common-core'
22
22
  Requires-Dist: requests ; extra == 'common-core'
23
23
  Requires-Dist: simplejson>=3,<4 ; extra == 'common-core'
24
+ Requires-Dist: simplejson ; extra == 'flagsmith-schemas'
24
25
  Requires-Dist: typing-extensions ; extra == 'flagsmith-schemas'
25
26
  Requires-Dist: flagsmith-flag-engine>6 ; extra == 'flagsmith-schemas'
26
27
  Requires-Dist: backoff>=2.2.1,<3.0.0 ; extra == 'task-processor'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flagsmith-common"
3
- version = "3.2.1"
3
+ version = "3.3.0"
4
4
  description = "Flagsmith's common library"
5
5
  requires-python = ">=3.11,<4.0"
6
6
  dependencies = []
@@ -26,6 +26,7 @@ optional-dependencies = { test-tools = [
26
26
  "django-health-check",
27
27
  "prometheus-client (>=0.0.16)",
28
28
  ], flagsmith-schemas = [
29
+ "simplejson",
29
30
  "typing_extensions",
30
31
  "flagsmith-flag-engine>6",
31
32
  ] }
@@ -6,7 +6,9 @@ sidebar_position: 20
6
6
 
7
7
  ## Prometheus
8
8
 
9
- To enable the Prometheus `/metrics` endpoint, set the `PROMETHEUS_ENABLED` environment variable to `true`.
9
+ To enable the Prometheus `/metrics` endpoint, set the `PROMETHEUS_ENABLED` environment variable to `true`.
10
+
11
+ When enabled, Flagsmith serves the `/metrics` endpoint on port 9100.
10
12
 
11
13
  The metrics provided by Flagsmith are described below.
12
14
 
@@ -4,6 +4,7 @@ This module is used as a default configuration file for Gunicorn.
4
4
  It is used to correctly support Prometheus metrics in a multi-process environment.
5
5
  """
6
6
 
7
+ import os
7
8
  import typing
8
9
 
9
10
  from prometheus_client.multiprocess import mark_process_dead
@@ -13,6 +14,15 @@ if typing.TYPE_CHECKING: # pragma: no cover
13
14
  from gunicorn.workers.base import Worker # type: ignore[import-untyped]
14
15
 
15
16
 
16
- def worker_exit(server: "Arbiter", worker: "Worker") -> None:
17
+ def when_ready(server: "Arbiter") -> None:
18
+ """Start the standalone Prometheus metrics server after Gunicorn is ready."""
19
+ prometheus_enabled = os.getenv("PROMETHEUS_ENABLED", "")
20
+ if prometheus_enabled.lower() == "true": # Django settings are not available
21
+ from common.gunicorn.metrics_server import start_metrics_server
22
+
23
+ start_metrics_server()
24
+
25
+
26
+ def child_exit(server: "Arbiter", worker: "Worker") -> None:
17
27
  """Detach the process Prometheus metrics collector when a worker exits."""
18
28
  mark_process_dead(worker.pid) # type: ignore[no-untyped-call]
@@ -0,0 +1,65 @@
1
+ """
2
+ Standalone Prometheus metrics HTTP server.
3
+
4
+ This module provides a separate HTTP server for Prometheus metrics,
5
+ independent of the main Gunicorn application server. This improves
6
+ metrics reliability under high API load.
7
+
8
+ The server runs in a daemon thread and serves metrics from the shared
9
+ PROMETHEUS_MULTIPROC_DIR directory.
10
+ """
11
+
12
+ import logging
13
+ import os
14
+ import threading
15
+
16
+ from prometheus_client import CollectorRegistry, start_http_server
17
+ from prometheus_client.multiprocess import MultiProcessCollector
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ METRICS_SERVER_PORT = 9100
22
+
23
+ _server_started = False
24
+ _server_lock = threading.Lock()
25
+
26
+
27
+ def get_multiprocess_registry() -> CollectorRegistry:
28
+ """Create a registry configured for multiprocess metric collection."""
29
+ registry = CollectorRegistry()
30
+ MultiProcessCollector(registry) # type: ignore[no-untyped-call]
31
+ return registry
32
+
33
+
34
+ def start_metrics_server(
35
+ port: int = METRICS_SERVER_PORT,
36
+ ) -> None:
37
+ """
38
+ Start the standalone Prometheus metrics HTTP server.
39
+
40
+ This function is idempotent - calling it multiple times will only
41
+ start one server. The server runs in a daemon thread.
42
+
43
+ Args:
44
+ port: The port to serve metrics on. Defaults to 9100.
45
+ """
46
+ global _server_started
47
+
48
+ with _server_lock:
49
+ if _server_started:
50
+ logger.debug("Metrics server already started")
51
+ return
52
+
53
+ prometheus_multiproc_dir = os.environ.get("PROMETHEUS_MULTIPROC_DIR")
54
+ if not prometheus_multiproc_dir:
55
+ logger.warning("PROMETHEUS_MULTIPROC_DIR not set, skipping metrics server")
56
+ return
57
+
58
+ registry = get_multiprocess_registry()
59
+
60
+ try:
61
+ start_http_server(port=port, registry=registry)
62
+ _server_started = True
63
+ logger.info("Prometheus metrics server started on port %d", port)
64
+ except OSError as e:
65
+ logger.error("Failed to start metrics server on port %d: %s", port, e)
@@ -20,6 +20,7 @@ from flagsmith_schemas.types import (
20
20
  DynamoFloat,
21
21
  DynamoInt,
22
22
  FeatureType,
23
+ JsonGzipped,
23
24
  UUIDStr,
24
25
  )
25
26
 
@@ -200,7 +201,7 @@ class Webhook(TypedDict):
200
201
  """Secret used to sign webhook payloads."""
201
202
 
202
203
 
203
- class _EnvironmentFields(TypedDict):
204
+ class _EnvironmentBaseFields(TypedDict):
204
205
  """Common fields for Environment documents."""
205
206
 
206
207
  name: NotRequired[str]
@@ -208,11 +209,6 @@ class _EnvironmentFields(TypedDict):
208
209
  updated_at: NotRequired[DateTimeStr | None]
209
210
  """Last updated timestamp. If not set, current timestamp should be assumed."""
210
211
 
211
- project: Project
212
- """Project-specific data for this environment."""
213
- feature_states: list[FeatureState]
214
- """List of feature states representing the environment defaults."""
215
-
216
212
  allow_client_traits: NotRequired[bool]
217
213
  """Whether the SDK API should allow clients to set traits for this environment. Identical to project-level's `persist_trait_data` setting. Defaults to `True`."""
218
214
  hide_sensitive_data: NotRequired[bool]
@@ -240,7 +236,52 @@ class _EnvironmentFields(TypedDict):
240
236
  """Webhook configuration."""
241
237
 
242
238
 
243
- ### Root document schemas below. Indexed fields are marked as **INDEXED** in the docstrings. ###
239
+ class _EnvironmentV1Fields(TypedDict):
240
+ """Common fields for environment documents in `flagsmith_environments`."""
241
+
242
+ api_key: str
243
+ """Public client-side API key for the environment. **INDEXED**."""
244
+ id: DynamoInt
245
+ """Unique identifier for the environment in Core."""
246
+
247
+
248
+ class _EnvironmentV2MetaFields(TypedDict):
249
+ """Common fields for environment documents in `flagsmith_environments_v2`."""
250
+
251
+ environment_id: str
252
+ """Unique identifier for the environment in Core. Same as `Environment.id`, but string-typed to reduce coupling with Core's type definitions **INDEXED**."""
253
+ environment_api_key: str
254
+ """Public client-side API key for the environment. **INDEXED**."""
255
+ document_key: Literal["_META"]
256
+ """The fixed document key for the environment v2 document. Always `"_META"`. **INDEXED**."""
257
+
258
+ id: DynamoInt
259
+ """Unique identifier for the environment in Core. Exists for compatibility with the API environment document schema."""
260
+
261
+
262
+ class _EnvironmentBaseFieldsUncompressed(TypedDict):
263
+ """Common fields for uncompressed environment documents."""
264
+
265
+ project: Project
266
+ """Project-specific data for this environment."""
267
+ feature_states: list[FeatureState]
268
+ """List of feature states representing the environment defaults."""
269
+ compressed: NotRequired[Literal[False]]
270
+ """Either `False` or absent to indicate the data is uncompressed."""
271
+
272
+
273
+ class _EnvironmentBaseFieldsCompressed(TypedDict):
274
+ """Common fields for compressed environment documents."""
275
+
276
+ project: JsonGzipped[Project]
277
+ """Project-specific data for this environment. **COMPRESSED**."""
278
+ feature_states: JsonGzipped[list[FeatureState]]
279
+ """List of feature states representing the environment defaults. **COMPRESSED**."""
280
+ compressed: Literal[True]
281
+ """Always `True` to indicate the data is compressed."""
282
+
283
+
284
+ ### Root document schemas below. Indexed fields are marked as **INDEXED** in the docstrings. Compressed fields are marked as **COMPRESSED**. ###
244
285
 
245
286
 
246
287
  class EnvironmentAPIKey(TypedDict):
@@ -295,33 +336,50 @@ class Identity(TypedDict):
295
336
  """Unique identifier for the identity in Core. If identity created via Core's `edge-identities` API, this can be missing or `None`."""
296
337
 
297
338
 
298
- class Environment(_EnvironmentFields):
339
+ class Environment(
340
+ _EnvironmentBaseFieldsUncompressed,
341
+ _EnvironmentV1Fields,
342
+ _EnvironmentBaseFields,
343
+ ):
299
344
  """Represents a Flagsmith environment. Carries all necessary data for flag evaluation within the environment.
300
345
 
301
346
  **DynamoDB table**: `flagsmith_environments`
302
347
  """
303
348
 
304
- api_key: str
305
- """Public client-side API key for the environment. **INDEXED**."""
306
- id: DynamoInt
307
- """Unique identifier for the environment in Core."""
308
349
 
350
+ class EnvironmentCompressed(
351
+ _EnvironmentBaseFieldsCompressed,
352
+ _EnvironmentV1Fields,
353
+ _EnvironmentBaseFields,
354
+ ):
355
+ """Represents a Flagsmith environment. Carries all necessary data for flag evaluation within the environment.
356
+ Has compressed fields.
357
+
358
+ **DynamoDB table**: `flagsmith_environments`
359
+ """
309
360
 
310
- class EnvironmentV2Meta(_EnvironmentFields):
361
+
362
+ class EnvironmentV2Meta(
363
+ _EnvironmentBaseFieldsUncompressed,
364
+ _EnvironmentV2MetaFields,
365
+ _EnvironmentBaseFields,
366
+ ):
311
367
  """Represents a Flagsmith environment. Carries all necessary data for flag evaluation within the environment.
312
368
 
313
369
  **DynamoDB table**: `flagsmith_environments_v2`
314
370
  """
315
371
 
316
- environment_id: str
317
- """Unique identifier for the environment in Core. Same as `Environment.id`, but string-typed to reduce coupling with Core's type definitions **INDEXED**."""
318
- environment_api_key: str
319
- """Public client-side API key for the environment. **INDEXED**."""
320
- document_key: Literal["_META"]
321
- """The fixed document key for the environment v2 document. Always `"_META"`. **INDEXED**."""
322
372
 
323
- id: DynamoInt
324
- """Unique identifier for the environment in Core. Exists for compatibility with the API environment document schema."""
373
+ class EnvironmentV2MetaCompressed(
374
+ _EnvironmentBaseFieldsCompressed,
375
+ _EnvironmentV2MetaFields,
376
+ _EnvironmentBaseFields,
377
+ ):
378
+ """Represents a Flagsmith environment. Carries all necessary data for flag evaluation within the environment.
379
+ Has compressed fields.
380
+
381
+ **DynamoDB table**: `flagsmith_environments_v2`
382
+ """
325
383
 
326
384
 
327
385
  class EnvironmentV2IdentityOverride(TypedDict):
@@ -1,10 +1,25 @@
1
1
  from decimal import Decimal
2
- from typing import TYPE_CHECKING, Annotated, Literal, TypeAlias
2
+ from typing import (
3
+ TYPE_CHECKING,
4
+ Annotated,
5
+ Any,
6
+ Generic,
7
+ Literal,
8
+ SupportsBytes,
9
+ TypeAlias,
10
+ TypeVar,
11
+ get_args,
12
+ )
3
13
 
4
14
  from flagsmith_schemas.constants import PYDANTIC_INSTALLED
5
15
 
6
16
  if PYDANTIC_INSTALLED:
7
- from pydantic import WithJsonSchema
17
+ from pydantic import (
18
+ GetCoreSchemaHandler,
19
+ TypeAdapter,
20
+ WithJsonSchema,
21
+ )
22
+ from pydantic_core import core_schema
8
23
 
9
24
  from flagsmith_schemas.pydantic_types import (
10
25
  ValidateDecimalAsFloat,
@@ -13,6 +28,7 @@ if PYDANTIC_INSTALLED:
13
28
  ValidateStrAsISODateTime,
14
29
  ValidateStrAsUUID,
15
30
  )
31
+ from flagsmith_schemas.utils import json_gzip
16
32
  elif not TYPE_CHECKING:
17
33
  # This code runs at runtime when Pydantic is not installed.
18
34
  # We could use PEP 649 strings with `Annotated`, but Pydantic is inconsistent in how it parses them.
@@ -26,6 +42,39 @@ elif not TYPE_CHECKING:
26
42
  ValidateStrAsISODateTime = ...
27
43
  ValidateStrAsUUID = ...
28
44
 
45
+ T = TypeVar("T")
46
+
47
+
48
+ class DynamoBinary(SupportsBytes):
49
+ """boto3's wrapper type for bytes stored in DynamoDB."""
50
+
51
+ value: bytes | bytearray
52
+
53
+
54
+ class JsonGzipped(DynamoBinary, Generic[T]):
55
+ """A gzipped JSON blob representing a value of type `T`."""
56
+
57
+ if PYDANTIC_INSTALLED:
58
+
59
+ @classmethod
60
+ def __get_pydantic_core_schema__(
61
+ cls,
62
+ source_type: "type[JsonGzipped[T]]",
63
+ handler: GetCoreSchemaHandler,
64
+ ) -> core_schema.CoreSchema:
65
+ _adapter: TypeAdapter[T] = TypeAdapter(get_args(source_type)[0])
66
+
67
+ def _validate_json_gzipped(data: Any) -> bytes:
68
+ return json_gzip(_adapter.validate_python(data))
69
+
70
+ # We're returning bytes here for two reasons:
71
+ # 1. boto3.dynamodb seems to expect bytes as input for Binary columns.
72
+ # 2. We want to avoid having boto3 as a dependency.
73
+ return core_schema.no_info_before_validator_function(
74
+ _validate_json_gzipped,
75
+ core_schema.bytes_schema(strict=False),
76
+ )
77
+
29
78
 
30
79
  DynamoInt: TypeAlias = Annotated[Decimal, ValidateDecimalAsInt]
31
80
  """An integer value stored in DynamoDB.
@@ -0,0 +1,15 @@
1
+ import gzip
2
+ import typing
3
+
4
+ import simplejson as json
5
+
6
+
7
+ def json_gzip(value: typing.Any) -> bytes:
8
+ return gzip.compress(
9
+ json.dumps(
10
+ value,
11
+ separators=(",", ":"),
12
+ sort_keys=True,
13
+ ).encode("utf-8"),
14
+ mtime=0,
15
+ )