|
| 1 | +""" |
| 2 | +Types describing Flagsmith Edge API's data model. |
| 3 | +The schemas are written to DynamoDB documents by Core, and read by Edge. |
| 4 | +""" |
| 5 | + |
| 6 | +from datetime import datetime |
| 7 | +from typing import Literal, NotRequired, TypeAlias, TypedDict |
| 8 | +from uuid import UUID |
| 9 | + |
| 10 | +FeatureType = Literal["STANDARD", "MULTIVARIATE"] |
| 11 | +"""Represents the type of a Flagsmith feature. Multivariate features include multiple weighted values.""" |
| 12 | + |
| 13 | +FeatureValue: TypeAlias = object |
| 14 | +"""Represents the value of a Flagsmith feature. Can be stored a boolean, an integer, or a string. |
| 15 | +
|
| 16 | +The default (SaaS) maximum length for strings is 20000 characters. |
| 17 | +""" |
| 18 | + |
| 19 | +ContextValue: TypeAlias = int | float | bool | str |
| 20 | +"""Represents a scalar value in the Flagsmith context, e.g., of an identity trait. |
| 21 | +Here's how we store different types: |
| 22 | +- Numeric string values (int, float) are stored as numbers. |
| 23 | +- Boolean values are stored as booleans. |
| 24 | +- All other values are stored as strings. |
| 25 | +- Maximum length for strings is 2000 characters. |
| 26 | +
|
| 27 | +This type does not include complex structures like lists or dictionaries. |
| 28 | +""" |
| 29 | + |
| 30 | +ConditionOperator = Literal[ |
| 31 | + "EQUAL", |
| 32 | + "GREATER_THAN", |
| 33 | + "LESS_THAN", |
| 34 | + "LESS_THAN_INCLUSIVE", |
| 35 | + "CONTAINS", |
| 36 | + "GREATER_THAN_INCLUSIVE", |
| 37 | + "NOT_CONTAINS", |
| 38 | + "NOT_EQUAL", |
| 39 | + "REGEX", |
| 40 | + "PERCENTAGE_SPLIT", |
| 41 | + "MODULO", |
| 42 | + "IS_SET", |
| 43 | + "IS_NOT_SET", |
| 44 | + "IN", |
| 45 | +] |
| 46 | +"""Represents segment condition operators used by Flagsmith engine.""" |
| 47 | + |
| 48 | +RuleType = Literal[ |
| 49 | + "ALL", |
| 50 | + "ANY", |
| 51 | + "NONE", |
| 52 | +] |
| 53 | +"""Represents segment rule types used by Flagsmith engine.""" |
| 54 | + |
| 55 | + |
| 56 | +class Feature(TypedDict): |
| 57 | + """Represents a Flagsmith feature defined at project level.""" |
| 58 | + |
| 59 | + id: int |
| 60 | + """Unique identifier for the feature in Core.""" |
| 61 | + name: str |
| 62 | + """Name of the feature. Must be unique within a project.""" |
| 63 | + type: FeatureType |
| 64 | + |
| 65 | + |
| 66 | +class MultivariateFeatureOption(TypedDict): |
| 67 | + """A container for a feature state value of a multivariate feature state.""" |
| 68 | + |
| 69 | + id: NotRequired[int | None] |
| 70 | + """Unique identifier for the multivariate feature option in Core. **DEPRECATED**: MultivariateFeatureValue.id should be used instead.""" |
| 71 | + value: FeatureValue |
| 72 | + """The feature state value that should be served when this option's parent multivariate feature state is selected by the engine.""" |
| 73 | + |
| 74 | + |
| 75 | +class MultivariateFeatureStateValue(TypedDict): |
| 76 | + """Represents a multivariate feature state value assigned to an identity or environment.""" |
| 77 | + |
| 78 | + id: NotRequired[int | None] |
| 79 | + """Unique identifier for the multivariate feature state value in Core. TODO: document why and when this can be `None`.""" |
| 80 | + mv_fs_value_uuid: NotRequired[UUID] |
| 81 | + """The UUID for this multivariate feature state value. Should be used if `id` is `None`.""" |
| 82 | + percentage_allocation: float |
| 83 | + """The percentage allocation for this multivariate feature state value. Should be between or equal to 0 and 100.""" |
| 84 | + multivariate_feature_option: MultivariateFeatureOption |
| 85 | + """The multivariate feature option that this value corresponds to.""" |
| 86 | + |
| 87 | + |
| 88 | +class FeatureSegment(TypedDict): |
| 89 | + """Represents data specific to a segment feature override.""" |
| 90 | + |
| 91 | + priority: NotRequired[int | None] |
| 92 | + """The priority of this segment feature override. Lower numbers indicate stronger priority. If `None` or not set, the weakest priority is assumed.""" |
| 93 | + |
| 94 | + |
| 95 | +class FeatureState(TypedDict): |
| 96 | + """Represents a Flagsmith feature state. Used to define the state of a feature for an environment, segment overrides, and identity overrides.""" |
| 97 | + |
| 98 | + feature: Feature |
| 99 | + """The feature that this feature state is for.""" |
| 100 | + enabled: bool |
| 101 | + """Whether the feature is enabled or disabled.""" |
| 102 | + feature_state_value: FeatureValue |
| 103 | + """The value for this feature state.""" |
| 104 | + django_id: NotRequired[int | None] |
| 105 | + """Unique identifier for the feature state in Core. TODO: document why and when this can be `None`.""" |
| 106 | + featurestate_uuid: NotRequired[UUID] |
| 107 | + """The UUID for this feature state. Should be used if `django_id` is `None`. If not set, should be generated.""" |
| 108 | + feature_segment: NotRequired[FeatureSegment | None] |
| 109 | + """Segment override data, if this feature state is for a segment override.""" |
| 110 | + multivariate_feature_state_values: NotRequired[list[MultivariateFeatureStateValue]] |
| 111 | + """List of multivariate feature state values, if this feature state is for a multivariate feature. |
| 112 | +
|
| 113 | + Total `percentage_allocation` sum must be less or equal to 100. |
| 114 | + """ |
| 115 | + |
| 116 | + |
| 117 | +class Trait(TypedDict): |
| 118 | + """Represents a key-value pair associated with an identity.""" |
| 119 | + |
| 120 | + trait_key: str |
| 121 | + """Key of the trait.""" |
| 122 | + trait_value: ContextValue |
| 123 | + """Value of the trait.""" |
| 124 | + |
| 125 | + |
| 126 | +class SegmentCondition(TypedDict): |
| 127 | + """Represents a condition within a segment rule used by Flagsmith engine.""" |
| 128 | + |
| 129 | + operator: ConditionOperator |
| 130 | + """Operator to be applied for this condition.""" |
| 131 | + value: NotRequired[str | None] |
| 132 | + """Value to be compared against in this condition. May be `None` for `IS_SET` and `IS_NOT_SET` operators.""" |
| 133 | + property_: NotRequired[str | None] |
| 134 | + """The property (context key) this condition applies to. May be `None` for the `PERCENTAGE_SPLIT` operator. |
| 135 | +
|
| 136 | + Named `property_` to avoid conflict with Python's `property` built-in. |
| 137 | + """ |
| 138 | + |
| 139 | + |
| 140 | +class SegmentRule(TypedDict): |
| 141 | + """Represents a rule within a segment used by Flagsmith engine.""" |
| 142 | + |
| 143 | + type: RuleType |
| 144 | + """Type of the rule, defining how conditions are evaluated.""" |
| 145 | + rules: "list[SegmentRule]" |
| 146 | + """Nested rules within this rule.""" |
| 147 | + conditions: list[SegmentCondition] |
| 148 | + """Conditions that must be met for this rule, evaluated based on the rule type.""" |
| 149 | + |
| 150 | + |
| 151 | +class Segment(TypedDict): |
| 152 | + """Represents a Flagsmith segment. Carries rules, feature overrides, and segment rules.""" |
| 153 | + |
| 154 | + id: int |
| 155 | + """Unique identifier for the segment in Core.""" |
| 156 | + name: str |
| 157 | + """Name of the segment.""" |
| 158 | + rules: list[SegmentRule] |
| 159 | + """List of rules within the segment.""" |
| 160 | + feature_states: list[FeatureState] |
| 161 | + """List of segment overrides.""" |
| 162 | + |
| 163 | + |
| 164 | +class Organisation(TypedDict): |
| 165 | + """Represents data about a Flagsmith organisation. Carries settings necessary for an SDK API operation.""" |
| 166 | + |
| 167 | + id: int |
| 168 | + """Unique identifier for the organisation in Core.""" |
| 169 | + name: str |
| 170 | + """Organisation name as set via Core.""" |
| 171 | + feature_analytics: NotRequired[bool] |
| 172 | + """Whether the SDK API should log feature analytics events for this organisation. Defaults to `False`.""" |
| 173 | + stop_serving_flags: NotRequired[bool] |
| 174 | + """Whether flag serving is disabled for this organisation. Defaults to `False`.""" |
| 175 | + persist_trait_data: NotRequired[bool] |
| 176 | + """If set to `False`, trait data will never be persisted for this organisation. Defaults to `True`.""" |
| 177 | + |
| 178 | + |
| 179 | +class Project(TypedDict): |
| 180 | + """Represents data about a Flagsmith project. Carries settings necessary for an SDK API operation.""" |
| 181 | + |
| 182 | + id: int |
| 183 | + """Unique identifier for the project in Core.""" |
| 184 | + name: str |
| 185 | + """Project name as set via Core.""" |
| 186 | + organisation: Organisation |
| 187 | + """The organisation that this project belongs to.""" |
| 188 | + segments: list[Segment] |
| 189 | + """List of segments.""" |
| 190 | + server_key_only_feature_ids: NotRequired[list[int]] |
| 191 | + """List of feature IDs that are skipped when the SDK API serves flags for a public client-side key.""" |
| 192 | + enable_realtime_updates: NotRequired[bool] |
| 193 | + """Whether the SDK API should use real-time updates. Defaults to `False`. Not currently used neither by SDK APIs nor by SDKs themselves.""" |
| 194 | + hide_disabled_flags: NotRequired[bool] |
| 195 | + """Whether the SDK API should hide disabled flags for this project. Defaults to `False`.""" |
| 196 | + |
| 197 | + |
| 198 | +class Integration(TypedDict): |
| 199 | + """Represents evaluation integration data.""" |
| 200 | + |
| 201 | + api_key: NotRequired[str | None] |
| 202 | + """API key for the integration.""" |
| 203 | + base_url: NotRequired[str | None] |
| 204 | + """Base URL for the integration.""" |
| 205 | + |
| 206 | + |
| 207 | +class DynatraceIntegration(Integration): |
| 208 | + """Represents Dynatrace evaluation integration data.""" |
| 209 | + |
| 210 | + entity_selector: str |
| 211 | + """A Dynatrace entity selector string.""" |
| 212 | + |
| 213 | + |
| 214 | +class Webhook(TypedDict): |
| 215 | + """Represents a webhook configuration.""" |
| 216 | + |
| 217 | + url: str |
| 218 | + """Webhook target URL.""" |
| 219 | + secret: str |
| 220 | + """Secret used to sign webhook payloads.""" |
| 221 | + |
| 222 | + |
| 223 | +### Root document schemas below. Indexed fields are marked as **INDEXED** in the docstrings. ### |
| 224 | + |
| 225 | + |
| 226 | +class EnvironmentAPIKey(TypedDict): |
| 227 | + """Represents a server-side API key for a Flagsmith environment.""" |
| 228 | + |
| 229 | + id: int |
| 230 | + """Unique identifier for the environment API key in Core. **INDEXED**.""" |
| 231 | + key: str |
| 232 | + """The server-side API key string, e.g. `"ser.xxxxxxxxxxxxx"`. **INDEXED**.""" |
| 233 | + created_at: datetime |
| 234 | + """Creation timestamp.""" |
| 235 | + name: str |
| 236 | + """Name of the API key.""" |
| 237 | + client_api_key: str |
| 238 | + """The corresponding public client-side API key.""" |
| 239 | + expires_at: NotRequired[datetime | None] |
| 240 | + """Expiration timestamp. If `None`, the key does not expire.""" |
| 241 | + active: bool |
| 242 | + """Whether the key is active. Defaults to `True`.""" |
| 243 | + |
| 244 | + |
| 245 | +class Identity(TypedDict): |
| 246 | + """Represents a Flagsmith identity within an environment. Carries traits and feature overrides.""" |
| 247 | + |
| 248 | + identifier: str |
| 249 | + """Unique identifier for the identity. **INDEXED**.""" |
| 250 | + environment_api_key: str |
| 251 | + """API key of the environment this identity belongs to. Used to scope the identity within a specific environment. **INDEXED**.""" |
| 252 | + identity_uuid: UUID |
| 253 | + """The UUID for this identity. **INDEXED**.""" |
| 254 | + composite_key: str |
| 255 | + """A composite key combining the environment and identifier. **INDEXED**. |
| 256 | +
|
| 257 | + Generated as: `{environment_api_key}_{identifier}`. |
| 258 | + """ |
| 259 | + created_date: datetime |
| 260 | + """Creation timestamp.""" |
| 261 | + identity_features: NotRequired[list[FeatureState]] |
| 262 | + """List of identity overrides for this identity.""" |
| 263 | + identity_traits: list[Trait] |
| 264 | + """List of traits associated with this identity.""" |
| 265 | + django_id: NotRequired[int | None] |
| 266 | + """Unique identifier for the identity in Core. TODO: document why and when this can be `None`.""" |
| 267 | + |
| 268 | + |
| 269 | +class Environment(TypedDict): |
| 270 | + """Represents a Flagsmith environment. Carries all necessary data for flag evaluation within the environment.""" |
| 271 | + |
| 272 | + id: int |
| 273 | + """Unique identifier for the environment in Core. **INDEXED**.""" |
| 274 | + api_key: str |
| 275 | + """Public client-side API key for the environment. **INDEXED**.""" |
| 276 | + name: NotRequired[str] |
| 277 | + """Environment name.""" |
| 278 | + updated_at: NotRequired[datetime | None] |
| 279 | + """Last updated timestamp. If not set, current timestamp should be assumed.""" |
| 280 | + |
| 281 | + project: Project |
| 282 | + """Project-specific data for this environment.""" |
| 283 | + feature_states: list[FeatureState] |
| 284 | + """List of feature states representing the environment defaults.""" |
| 285 | + |
| 286 | + allow_client_traits: NotRequired[bool] |
| 287 | + """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`.""" |
| 288 | + hide_sensitive_data: NotRequired[bool] |
| 289 | + """Whether the SDK API should hide sensitive data for this environment. Defaults to `False`.""" |
| 290 | + hide_disabled_flags: NotRequired[bool] |
| 291 | + """Whether the SDK API should hide disabled flags for this environment. If `None`, the SDK API should fall back to project-level setting.""" |
| 292 | + use_identity_composite_key_for_hashing: NotRequired[bool] |
| 293 | + """Whether the SDK API should set `$.identity.key` in engine evaluation context to identity's composite key. Defaults to `False`.""" |
| 294 | + use_identity_overrides_in_local_eval: NotRequired[bool] |
| 295 | + """Whether the SDK API should return identity overrides as part of the environment document. Defaults to `False`.""" |
| 296 | + |
| 297 | + amplitude_config: NotRequired[Integration] |
| 298 | + """Amplitude integration configuration.""" |
| 299 | + dynatrace_config: NotRequired[DynatraceIntegration] |
| 300 | + """Dynatrace integration configuration.""" |
| 301 | + heap_config: NotRequired[Integration] |
| 302 | + """Heap integration configuration.""" |
| 303 | + mixpanel_config: NotRequired[Integration] |
| 304 | + """Mixpanel integration configuration.""" |
| 305 | + rudderstack_config: NotRequired[Integration] |
| 306 | + """RudderStack integration configuration.""" |
| 307 | + segment_config: NotRequired[Integration] |
| 308 | + """Segment integration configuration.""" |
| 309 | + webhook_config: NotRequired[Webhook] |
| 310 | + """Webhook configuration.""" |
0 commit comments