Phase 1 of the flag-engine v10 migration (#5991, PR #6653/#6741) upgraded
flagsmith-flag-engine to ^10.0.3 and vendored Pydantic models from the
fix/missing-export branch to maintain backwards compatibility.
This issue tracks Phase 2: fully migrating to flag-engine v10's native APIs
and removing the vendored code.
Goals
util.mappers.engine — Django models → EvaluationContext
Engine mappers should map from Django ORM models to flag-engine's
EvaluationContext. Core API should define its own SegmentMetadata and
FeatureMetadata TypedDicts (e.g. in a types.py module) and use them as type
arguments for the generic EvaluationContext[SegmentMetadata, FeatureMetadata].
util.mappers.dynamodb — Django models → flagsmith_schemas.dynamodb TypedDicts
DynamoDB mappers should map from Django ORM models directly to the TypedDicts
defined in flagsmith_schemas.dynamodb (e.g. Environment,
EnvironmentV2IdentityOverride, Identity), eliminating the intermediate
Pydantic model layer.
util.mappers.sdk — Django models → flagsmith_schemas.api TypedDicts
SDK mappers should map from Django ORM models directly to the TypedDicts defined
in flagsmith_schemas.api (e.g. V1EnvironmentDocumentResponse), eliminating
the intermediate Pydantic model layer.
Typing discipline
No new # type: ignore comments should be introduced. typing.cast calls
should be avoided — types should be correct by construction.
Tasks
Consumers of identity-based feature state resolution
Code that resolves feature state values via the identity hash key /
multivariate resolution path. These need migrating to get_evaluation_result.
| File |
Method / usage |
What it does |
features/models.py |
FeatureState.get_feature_state_value(identity=) |
Public entry point — extracts hash key, delegates to get_feature_state_value_by_hash_key |
features/models.py |
FeatureState.get_feature_state_value_by_hash_key() |
Calls get_multivariate_feature_state_value() for MV features |
features/models.py |
FeatureState.get_multivariate_feature_state_value() |
Hashes [feature_state.id, identity_hash_key] to pick MV variant |
features/serializers.py |
FeatureStateSerializerFull.get_feature_state_value() |
Calls obj.get_feature_state_value(identity=...) |
features/serializers.py |
SDKFeatureStateSerializer.get_feature_state_value() |
Calls obj.get_feature_state_value(identity=...) |
features/serializers.py |
SDKFeatureStatesQuerySerializer context |
Calls obj.get_feature_state_value(identity=...) |
environments/identities/serializers.py |
IdentityAllFeatureStatesSerializer.get_feature_state_value() |
Calls instance.get_feature_state_value_by_hash_key(hash_key) or instance.get_value(hash_key) |
edge_api/identities/serializers.py |
FeatureStateValueEdgeIdentityField.to_representation() |
Calls obj.get_value(identity_id=...) on Pydantic FeatureStateModel |
integrations/amplitude/amplitude.py |
AmplitudeWrapper.generate_user_data() |
Calls feature_state.get_feature_state_value(identity=identity) |
integrations/heap/heap.py |
HeapWrapper.generate_user_data() |
Calls feature_state.get_feature_state_value(identity=identity) |
integrations/mixpanel/mixpanel.py |
MixpanelWrapper.generate_user_data() |
Calls feature_state.get_feature_state_value(identity=identity) |
integrations/rudderstack/rudderstack.py |
RudderstackWrapper.generate_user_data() |
Calls feature_state.get_feature_state_value(identity=identity) |
integrations/segment/segment.py |
SegmentWrapper.generate_user_data() |
Calls feature_state.get_feature_state_value(identity=identity) |
Consumers of vendored segment evaluation (solved in #6669)
Code that still uses the vendored is_context_in_segment / get_context_segments wrappers.
| File |
Import / usage |
Proposed change |
util/engine_models/context/mappers.py |
is_context_in_segment(), get_context_segments() |
Remove — callers should use get_evaluation_result directly |
environments/dynamodb/wrappers/identity_wrapper.py |
get_context_segments(), map_environment_identity_to_context() |
Use EvaluationContext + get_evaluation_result for segment matching |
Consumers of util.engine_models (non-test files)
| File |
Imports |
Proposed change |
edge_api/identities/models.py |
IdentityModel, IdentityFeaturesList, FeatureStateModel |
Use flagsmith_schemas.dynamodb.Identity / FeatureState TypedDicts as storage representation |
edge_api/identities/serializers.py |
FeatureModel, FeatureStateModel, MultivariateFeatureOptionModel, MultivariateFeatureStateValueModel, IdentityModel, DuplicateFeatureState |
Construct flagsmith_schemas.dynamodb TypedDicts directly from request data |
edge_api/identities/views.py |
IdentityModel, IdentityFeaturesList, TraitModel |
Use flagsmith_schemas.dynamodb.Identity / Trait TypedDicts |
edge_api/identities/utils.py |
FeatureStateModel |
Use flagsmith_schemas.dynamodb.FeatureState for change tracking |
edge_api/identities/export.py |
map_any_value_to_trait_value |
Use flag_engine.ContextValue or a standalone coercion function |
environments/dynamodb/wrappers/identity_wrapper.py |
EnvironmentModel, IdentityModel, map_environment_identity_to_context, get_context_segments |
Use EvaluationContext + get_evaluation_result for segment matching |
environments/dynamodb/services.py |
IdentityModel |
Use TypeAdapter[flagsmith_schemas.dynamodb.Identity] for validation |
environments/dynamodb/types.py |
FeatureStateModel |
Replace IdentityOverrideV2 with flagsmith_schemas.dynamodb.EnvironmentV2IdentityOverride |
environments/identities/serializers.py |
FeatureStateModel |
Use flagsmith_schemas.api.FeatureState in get_feature_state_value() |
organisations/models.py |
TraitValue |
Use flag_engine.ContextValue directly |
app/pagination.py |
IdentityModel |
Use TypeAdapter[flagsmith_schemas.dynamodb.Identity] for validation |
util/mappers/engine.py |
All models (15+ types) |
Produce EvaluationContext[SegmentMetadata, FeatureMetadata] directly |
util/mappers/dynamodb.py |
FeatureStateModel |
Produce flagsmith_schemas.dynamodb TypedDicts directly; drop parse_obj() |
Related
Phase 1 of the flag-engine v10 migration (#5991, PR #6653/#6741) upgraded
flagsmith-flag-engineto^10.0.3and vendored Pydantic models from thefix/missing-exportbranch to maintain backwards compatibility.This issue tracks Phase 2: fully migrating to flag-engine v10's native APIs
and removing the vendored code.
Goals
util.mappers.engine— Django models →EvaluationContextEngine mappers should map from Django ORM models to flag-engine's
EvaluationContext. Core API should define its ownSegmentMetadataandFeatureMetadataTypedDicts (e.g. in atypes.pymodule) and use them as typearguments for the generic
EvaluationContext[SegmentMetadata, FeatureMetadata].util.mappers.dynamodb— Django models →flagsmith_schemas.dynamodbTypedDictsDynamoDB mappers should map from Django ORM models directly to the TypedDicts
defined in
flagsmith_schemas.dynamodb(e.g.Environment,EnvironmentV2IdentityOverride,Identity), eliminating the intermediatePydantic model layer.
util.mappers.sdk— Django models →flagsmith_schemas.apiTypedDictsSDK mappers should map from Django ORM models directly to the TypedDicts defined
in
flagsmith_schemas.api(e.g.V1EnvironmentDocumentResponse), eliminatingthe intermediate Pydantic model layer.
Typing discipline
No new
# type: ignorecomments should be introduced.typing.castcallsshould be avoided — types should be correct by construction.
Tasks
SegmentMetadataandFeatureMetadataTypedDicts for useas
EvaluationContexttype arguments.util.mappers.engineto map Django models directly toEvaluationContext[SegmentMetadata, FeatureMetadata], removing allvendored Pydantic model usage.
is_context_in_segmenttoflag-engine's
get_evaluation_result(Migrate fromis_context_in_segmenttoget_evaluation_result#6669, PR refactor: Migrate fromis_context_in_segmenttoget_evaluation_result#6896).get_evaluation_resultfor full identity flag evaluation, replacingIdentity.get_all_feature_states()+ serialiser-time multivariateresolution (see consumers table below).
map_feature_state_to_feature_contextmapper inutil/mappers/engine.pyto convert DjangoFeatureState(withprefetched MV values) into a
FeatureContextTypedDict withvariants.EvaluationContext.featuresalongside segments inIdentity.get_all_feature_states()(or a replacement method), soget_evaluation_resultresolves segments, applies segment overrides,and resolves multivariate values in a single pass.
FlagResultdicts from the engineinstead of calling
get_feature_state_value_by_hash_key()/get_feature_state_value(identity=...).segment) to receive already-resolved values from the evaluation result
rather than calling
get_feature_state_value(identity=identity).FeatureState.get_multivariate_feature_state_value()andFeatureState.get_feature_state_value_by_hash_key()(or simplify fornon-identity cases).
get_hashed_percentage_for_object_idsfromutil/engine_models/utils/hashing.pyandutil/engine_models/features/models.py.util.mappers.dynamodbto map Django models directly toflagsmith_schemas.dynamodbTypedDicts, removing PydanticBaseModelserialisation and
FeatureStateModel.parse_obj()calls.util.mappers.sdkto map Django models directly toflagsmith_schemas.apiTypedDicts, removing vendored model usage.IdentityOverrideV2inenvironments/dynamodb/types.pywithflagsmith_schemas.dynamodb.EnvironmentV2IdentityOverrideor anequivalent TypedDict that does not depend on vendored models.
util.engine_models(see table below).api/util/engine_models/entirely.pydantic-collectionsfrompyproject.toml.# type: ignorecomments ortyping.castcalls areintroduced; all types should be correct by construction.
Consumers of identity-based feature state resolution
Code that resolves feature state values via the identity hash key /
multivariate resolution path. These need migrating to
get_evaluation_result.features/models.pyFeatureState.get_feature_state_value(identity=)get_feature_state_value_by_hash_keyfeatures/models.pyFeatureState.get_feature_state_value_by_hash_key()get_multivariate_feature_state_value()for MV featuresfeatures/models.pyFeatureState.get_multivariate_feature_state_value()[feature_state.id, identity_hash_key]to pick MV variantfeatures/serializers.pyFeatureStateSerializerFull.get_feature_state_value()obj.get_feature_state_value(identity=...)features/serializers.pySDKFeatureStateSerializer.get_feature_state_value()obj.get_feature_state_value(identity=...)features/serializers.pySDKFeatureStatesQuerySerializercontextobj.get_feature_state_value(identity=...)environments/identities/serializers.pyIdentityAllFeatureStatesSerializer.get_feature_state_value()instance.get_feature_state_value_by_hash_key(hash_key)orinstance.get_value(hash_key)edge_api/identities/serializers.pyFeatureStateValueEdgeIdentityField.to_representation()obj.get_value(identity_id=...)on Pydantic FeatureStateModelintegrations/amplitude/amplitude.pyAmplitudeWrapper.generate_user_data()feature_state.get_feature_state_value(identity=identity)integrations/heap/heap.pyHeapWrapper.generate_user_data()feature_state.get_feature_state_value(identity=identity)integrations/mixpanel/mixpanel.pyMixpanelWrapper.generate_user_data()feature_state.get_feature_state_value(identity=identity)integrations/rudderstack/rudderstack.pyRudderstackWrapper.generate_user_data()feature_state.get_feature_state_value(identity=identity)integrations/segment/segment.pySegmentWrapper.generate_user_data()feature_state.get_feature_state_value(identity=identity)Consumers of vendored segment evaluation (solved in #6669)
Code that still uses the vendored
is_context_in_segment/get_context_segmentswrappers.util/engine_models/context/mappers.pyis_context_in_segment(),get_context_segments()get_evaluation_resultdirectlyenvironments/dynamodb/wrappers/identity_wrapper.pyget_context_segments(),map_environment_identity_to_context()EvaluationContext+get_evaluation_resultfor segment matchingConsumers of
util.engine_models(non-test files)edge_api/identities/models.pyIdentityModel,IdentityFeaturesList,FeatureStateModelflagsmith_schemas.dynamodb.Identity/FeatureStateTypedDicts as storage representationedge_api/identities/serializers.pyFeatureModel,FeatureStateModel,MultivariateFeatureOptionModel,MultivariateFeatureStateValueModel,IdentityModel,DuplicateFeatureStateflagsmith_schemas.dynamodbTypedDicts directly from request dataedge_api/identities/views.pyIdentityModel,IdentityFeaturesList,TraitModelflagsmith_schemas.dynamodb.Identity/TraitTypedDictsedge_api/identities/utils.pyFeatureStateModelflagsmith_schemas.dynamodb.FeatureStatefor change trackingedge_api/identities/export.pymap_any_value_to_trait_valueflag_engine.ContextValueor a standalone coercion functionenvironments/dynamodb/wrappers/identity_wrapper.pyEnvironmentModel,IdentityModel,map_environment_identity_to_context,get_context_segmentsEvaluationContext+get_evaluation_resultfor segment matchingenvironments/dynamodb/services.pyIdentityModelTypeAdapter[flagsmith_schemas.dynamodb.Identity]for validationenvironments/dynamodb/types.pyFeatureStateModelIdentityOverrideV2withflagsmith_schemas.dynamodb.EnvironmentV2IdentityOverrideenvironments/identities/serializers.pyFeatureStateModelflagsmith_schemas.api.FeatureStateinget_feature_state_value()organisations/models.pyTraitValueflag_engine.ContextValuedirectlyapp/pagination.pyIdentityModelTypeAdapter[flagsmith_schemas.dynamodb.Identity]for validationutil/mappers/engine.pyEvaluationContext[SegmentMetadata, FeatureMetadata]directlyutil/mappers/dynamodb.pyFeatureStateModelflagsmith_schemas.dynamodbTypedDicts directly; dropparse_obj()Related
is_context_in_segmenttoget_evaluation_result#6669