flagsmith-common 2.0.0__tar.gz → 2.1.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 (92) hide show
  1. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/PKG-INFO +1 -1
  2. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/pyproject.toml +1 -1
  3. flagsmith_common-2.1.0/src/common/core/__init__.py +6 -0
  4. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/utils.py +66 -2
  5. flagsmith_common-2.0.0/src/task_processor/migrations/sql/__init__.py +0 -0
  6. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/LICENSE +0 -0
  7. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/README.md +0 -0
  8. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/__init__.py +0 -0
  9. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/app.py +0 -0
  10. {flagsmith_common-2.0.0/src/common/core → flagsmith_common-2.1.0/src/common/core/cli}/__init__.py +0 -0
  11. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/cli/healthcheck.py +0 -0
  12. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/logging.py +0 -0
  13. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/main.py +0 -0
  14. {flagsmith_common-2.0.0/src/common/core/cli → flagsmith_common-2.1.0/src/common/core/management}/__init__.py +0 -0
  15. {flagsmith_common-2.0.0/src/common/core/management → flagsmith_common-2.1.0/src/common/core/management/commands}/__init__.py +0 -0
  16. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/management/commands/docgen.py +0 -0
  17. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/management/commands/start.py +0 -0
  18. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/management/commands/waitfordb.py +0 -0
  19. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/metrics.py +0 -0
  20. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/middleware.py +0 -0
  21. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/templates/docgen-metrics.md +0 -0
  22. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/urls.py +0 -0
  23. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/core/views.py +0 -0
  24. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/environments/permissions.py +0 -0
  25. {flagsmith_common-2.0.0/src/common/core/management/commands → flagsmith_common-2.1.0/src/common/features}/__init__.py +0 -0
  26. {flagsmith_common-2.0.0/src/common/features → flagsmith_common-2.1.0/src/common/features/multivariate}/__init__.py +0 -0
  27. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/features/multivariate/serializers.py +0 -0
  28. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/features/serializers.py +0 -0
  29. {flagsmith_common-2.0.0/src/common/features/multivariate → flagsmith_common-2.1.0/src/common/features/versioning}/__init__.py +0 -0
  30. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/features/versioning/serializers.py +0 -0
  31. {flagsmith_common-2.0.0/src/common/features/versioning → flagsmith_common-2.1.0/src/common/gunicorn}/__init__.py +0 -0
  32. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/gunicorn/conf.py +0 -0
  33. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/gunicorn/constants.py +0 -0
  34. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/gunicorn/logging.py +0 -0
  35. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/gunicorn/metrics.py +0 -0
  36. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/gunicorn/middleware.py +0 -0
  37. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/gunicorn/utils.py +0 -0
  38. {flagsmith_common-2.0.0/src/common/gunicorn → flagsmith_common-2.1.0/src/common/migrations}/__init__.py +0 -0
  39. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/migrations/helpers/__init__.py +0 -0
  40. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/migrations/helpers/postgres_helpers.py +0 -0
  41. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/organisations/permissions.py +0 -0
  42. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/projects/permissions.py +0 -0
  43. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/prometheus/__init__.py +0 -0
  44. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/prometheus/utils.py +0 -0
  45. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/py.typed +0 -0
  46. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/test_tools/__init__.py +0 -0
  47. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/test_tools/plugin.py +0 -0
  48. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/test_tools/types.py +0 -0
  49. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/test_tools/utils.py +0 -0
  50. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/common/types.py +0 -0
  51. {flagsmith_common-2.0.0/src/common/migrations → flagsmith_common-2.1.0/src/task_processor}/__init__.py +0 -0
  52. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/admin.py +0 -0
  53. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/apps.py +0 -0
  54. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/decorators.py +0 -0
  55. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/exceptions.py +0 -0
  56. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/health.py +0 -0
  57. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/managers.py +0 -0
  58. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/metrics.py +0 -0
  59. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0001_initial.py +0 -0
  60. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0002_healthcheckmodel.py +0 -0
  61. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0003_add_completed_to_task.py +0 -0
  62. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0004_recreate_task_indexes.py +0 -0
  63. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0005_update_conditional_index_conditions.py +0 -0
  64. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0006_auto_20230221_0802.py +0 -0
  65. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0007_add_is_locked.py +0 -0
  66. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0008_add_get_task_to_process_function.py +0 -0
  67. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0009_add_recurring_task_run_first_run_at.py +0 -0
  68. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0010_task_priority.py +0 -0
  69. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0011_add_priority_to_get_tasks_to_process.py +0 -0
  70. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0012_add_locked_at_and_timeout.py +0 -0
  71. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/0013_add_last_picked_at.py +0 -0
  72. {flagsmith_common-2.0.0/src/task_processor → flagsmith_common-2.1.0/src/task_processor/migrations}/__init__.py +0 -0
  73. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/sql/0008_get_recurring_tasks_to_process.sql +0 -0
  74. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/sql/0008_get_tasks_to_process.sql +0 -0
  75. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/sql/0011_get_tasks_to_process.sql +0 -0
  76. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/sql/0012_get_recurringtasks_to_process.sql +0 -0
  77. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/migrations/sql/0013_get_recurringtasks_to_process.sql +0 -0
  78. {flagsmith_common-2.0.0/src/task_processor/migrations → flagsmith_common-2.1.0/src/task_processor/migrations/sql}/__init__.py +0 -0
  79. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/models.py +0 -0
  80. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/monitoring.py +0 -0
  81. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/processor.py +0 -0
  82. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/py.typed +0 -0
  83. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/routers.py +0 -0
  84. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/serializers.py +0 -0
  85. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/task_registry.py +0 -0
  86. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/task_run_method.py +0 -0
  87. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/tasks.py +0 -0
  88. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/threads.py +0 -0
  89. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/types.py +0 -0
  90. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/urls.py +0 -0
  91. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/utils.py +0 -0
  92. {flagsmith_common-2.0.0 → flagsmith_common-2.1.0}/src/task_processor/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flagsmith-common
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Flagsmith's common library
5
5
  License: BSD-3-Clause
6
6
  Author: Matthew Elwell
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flagsmith-common"
3
- version = "2.0.0"
3
+ version = "2.1.0"
4
4
  description = "Flagsmith's common library"
5
5
  requires-python = ">=3.11,<4.0"
6
6
  dependencies = [
@@ -0,0 +1,6 @@
1
+ import enum
2
+
3
+
4
+ class ReplicaReadStrategy(enum.StrEnum):
5
+ DISTRIBUTED = enum.auto()
6
+ SEQUENTIAL = enum.auto()
@@ -1,16 +1,30 @@
1
1
  import json
2
+ import logging
2
3
  import pathlib
4
+ import random
3
5
  from functools import lru_cache
4
- from typing import NotRequired, TypedDict
6
+ from itertools import cycle
7
+ from typing import Iterator, Literal, NotRequired, TypedDict, TypeVar
5
8
 
6
9
  from django.conf import settings
7
10
  from django.contrib.auth import get_user_model
8
11
  from django.contrib.auth.models import AbstractBaseUser
9
- from django.db.models import Manager
12
+ from django.db import connections
13
+ from django.db.models import Manager, Model
14
+ from django.db.utils import OperationalError
15
+
16
+ from common.core import ReplicaReadStrategy
17
+
18
+ logger = logging.getLogger(__name__)
10
19
 
11
20
  UNKNOWN = "unknown"
12
21
  VERSIONS_INFO_FILE_LOCATION = ".versions.json"
13
22
 
23
+ ManagerType = TypeVar("ManagerType", bound=Manager[Model])
24
+
25
+ ReplicaNamePrefix = Literal["replica_", "cross_region_replica_"]
26
+ _replica_sequential_names_by_prefix: dict[ReplicaNamePrefix, Iterator[str]] = {}
27
+
14
28
 
15
29
  class SelfHostedData(TypedDict):
16
30
  has_users: bool
@@ -114,3 +128,53 @@ def get_file_contents(file_path: str) -> str | None:
114
128
  return f.read().replace("\n", "")
115
129
  except FileNotFoundError:
116
130
  return None
131
+
132
+
133
+ def using_database_replica(
134
+ manager: ManagerType,
135
+ replica_prefix: ReplicaNamePrefix = "replica_",
136
+ ) -> ManagerType:
137
+ """Attempts to bind a manager to a healthy database replica"""
138
+ local_replicas = [name for name in connections if name.startswith(replica_prefix)]
139
+
140
+ if not local_replicas:
141
+ logger.info("No replicas set up.")
142
+ return manager
143
+
144
+ chosen_replica = None
145
+
146
+ if settings.REPLICA_READ_STRATEGY == ReplicaReadStrategy.SEQUENTIAL:
147
+ sequence = _replica_sequential_names_by_prefix.setdefault(
148
+ replica_prefix, cycle(local_replicas)
149
+ )
150
+ for _ in range(len(local_replicas)):
151
+ attempted_replica = next(sequence)
152
+ try:
153
+ connections[attempted_replica].ensure_connection()
154
+ chosen_replica = attempted_replica
155
+ break
156
+ except OperationalError:
157
+ logger.exception(f"Replica '{attempted_replica}' is not available.")
158
+ continue
159
+
160
+ if settings.REPLICA_READ_STRATEGY == ReplicaReadStrategy.DISTRIBUTED:
161
+ for _ in range(len(local_replicas)):
162
+ attempted_replica = random.choice(local_replicas)
163
+ try:
164
+ connections[attempted_replica].ensure_connection()
165
+ chosen_replica = attempted_replica
166
+ break
167
+ except OperationalError:
168
+ logger.exception(f"Replica '{attempted_replica}' is not available.")
169
+ local_replicas.remove(attempted_replica)
170
+ continue
171
+
172
+ if not chosen_replica:
173
+ if replica_prefix == "replica_":
174
+ logger.warning("Falling back to cross-region replicas, if any.")
175
+ return using_database_replica(manager, "cross_region_replica_")
176
+
177
+ logger.warning("No replicas available.")
178
+ return manager
179
+
180
+ return manager.db_manager(chosen_replica)