77from flag_engine import engine
88from flag_engine .context .types import SegmentContext , SegmentRule
99
10+ from flagsmith .api .types import EnvironmentModel
1011from flagsmith .mappers import (
11- get_identity_overrides_identifiers ,
1212 map_context_and_identity_data_to_context ,
13- map_identifiers_to_identity_overrides_rule ,
13+ map_environment_document_to_context ,
14+ map_environment_document_to_identity_overrides_segments ,
1415)
1516from flagsmith .models import (
1617 Flag ,
@@ -61,40 +62,49 @@ class LocalEvaluation:
6162 Built once each time the environment document is refreshed, so the
6263 work here is amortised across every evaluation until the next refresh.
6364
64- Identity overrides are mapped to segments matching the identifiers
65- they apply to. Left in the context, every one of them would be
66- matched on every identity evaluation. Instead, they're indexed by
67- identifier, and each identity is evaluated with only its own.
65+ Identity overrides are kept out of the context, by identifier. Each
66+ identity is evaluated with its own identity overrides segment only.
6867 """
6968
70- def __init__ (self , context : SDKEvaluationContext ) -> None :
71- segments : SegmentContexts = {}
69+ def __init__ (
70+ self ,
71+ context : SDKEvaluationContext ,
72+ identity_overrides_segments : typing .Optional [
73+ typing .Mapping [str , SegmentContext [SegmentMetadata , FeatureMetadata ]]
74+ ] = None ,
75+ ) -> None :
76+ self .context = context
7277 self ._identity_free_segments : SegmentContexts = {}
73- self ._identity_overrides_segments : SegmentContexts = {}
7478 reads_flags = reads_identity = False
75- self ._identity_overrides_segment_keys : typing .Dict [str , typing .List [str ]] = {}
7679 for segment_key , segment_context in (context .get ("segments" ) or {}).items ():
77- identifiers = get_identity_overrides_identifiers (segment_context )
78- if identifiers is None :
79- segments [segment_key ] = segment_context
80- reads = _get_context_reads (segment_context ["rules" ])
81- reads_flags |= reads .flags
82- reads_identity |= reads .identity
83- if not reads .identity :
84- self ._identity_free_segments [segment_key ] = segment_context
85- continue
86- self ._identity_overrides_segments [segment_key ] = segment_context
87- for identifier in identifiers :
88- self ._identity_overrides_segment_keys .setdefault (identifier , []).append (
89- segment_key
90- )
91-
80+ reads = _get_context_reads (segment_context ["rules" ])
81+ reads_flags |= reads .flags
82+ reads_identity |= reads .identity
83+ if not reads .identity :
84+ self ._identity_free_segments [segment_key ] = segment_context
9285 self .reads = _ContextReads (flags = reads_flags , identity = reads_identity )
93-
94- # The context without identity overrides segments.
95- self .context : SDKEvaluationContext = {** context , "segments" : segments }
9686 self .overrides_index : SegmentOverridesIndex = build_segment_overrides_index (
97- self .context
87+ context
88+ )
89+ self ._identity_overrides = {
90+ identifier : (segment_context , self ._index_overrides (segment_context ))
91+ for identifier , segment_context in (
92+ identity_overrides_segments or {}
93+ ).items ()
94+ }
95+
96+ @classmethod
97+ def from_environment_document (
98+ cls ,
99+ environment_document : EnvironmentModel ,
100+ ) -> LocalEvaluation :
101+ return cls (
102+ context = map_environment_document_to_context (environment_document ),
103+ identity_overrides_segments = (
104+ map_environment_document_to_identity_overrides_segments (
105+ environment_document
106+ )
107+ ),
98108 )
99109
100110 @cached_property
@@ -113,35 +123,33 @@ def get_identity_context(
113123 identifier : str ,
114124 traits : typing .Optional [TraitMapping ] = None ,
115125 ) -> typing .Tuple [SDKEvaluationContext , SegmentOverridesIndex ]:
116- """Evaluation context and overrides index for an identity.
117-
118- The context carries the identity's own identity overrides segments,
119- narrowed to its identifier, so that their size doesn't depend on how
120- many identities share the same overrides.
121- """
126+ """Evaluation context and overrides index for an identity, including
127+ its own identity overrides segment, if any."""
122128 context = map_context_and_identity_data_to_context (
123129 context = self .context ,
124130 identifier = identifier ,
125131 traits = traits ,
126132 )
127- if not ( segment_keys := self ._identity_overrides_segment_keys .get (identifier )):
133+ if ( identity_overrides := self ._identity_overrides .get (identifier )) is None :
128134 return context , self .overrides_index
129135
130- identity_segments : SegmentContexts = {}
131- identity_overrides_index : SegmentOverridesIndex = {}
132- for segment_key in segment_keys :
133- segment_context : SegmentContext [SegmentMetadata , FeatureMetadata ] = {
134- ** self ._identity_overrides_segments [segment_key ],
135- "rules" : [map_identifiers_to_identity_overrides_rule ([identifier ])],
136- }
137- identity_segments [segment_key ] = segment_context
138- for override in segment_context .get ("overrides" ) or ():
139- feature_name = override ["name" ]
140- if feature_name not in identity_overrides_index :
141- identity_overrides_index [feature_name ] = dict (
142- self .overrides_index .get (feature_name ) or {}
143- )
144- identity_overrides_index [feature_name ][segment_key ] = segment_context
145-
146- context ["segments" ] = {** (context .get ("segments" ) or {}), ** identity_segments }
136+ segment_context , identity_overrides_index = identity_overrides
137+ context ["segments" ] = {
138+ ** (context .get ("segments" ) or {}),
139+ segment_context ["key" ]: segment_context ,
140+ }
147141 return context , ChainMap (identity_overrides_index , self .overrides_index )
142+
143+ def _index_overrides (
144+ self ,
145+ segment_context : SegmentContext [SegmentMetadata , FeatureMetadata ],
146+ ) -> SegmentOverridesIndex :
147+ """The overrides index, for the features `segment_context` overrides."""
148+ return {
149+ feature_name : {
150+ ** (self .overrides_index .get (feature_name ) or {}),
151+ segment_context ["key" ]: segment_context ,
152+ }
153+ for override in segment_context .get ("overrides" ) or ()
154+ if (feature_name := override ["name" ])
155+ }
0 commit comments