@@ -41,9 +41,15 @@ def __init__(
4141 ):
4242 """
4343 A `context` dict should have:
44- - a `dimension_type` such as "depmap_model" (which is not used in this evaluator)
4544 - an `expr` such as { "==": [ { "var": "var1" }, "Breast" ] }
46- - a set of `vars`, each of which assigns a name to a slice query
45+ - a dictionary of `vars` which assigns names to slice queries, such as
46+ {
47+ "var1": {
48+ "dataset_id": "depmap_model_metadata",
49+ "identifier": "OncotreeLineage",
50+ "identifier_type": "column"
51+ }
52+ }
4753 """
4854 self .expr = _encode_dots_in_vars (context ["expr" ])
4955 self .slice_query_vars = _escape_dots (context .get ("vars" , {}))
@@ -52,8 +58,8 @@ def __init__(
5258 self .get_slice_data = get_slice_data
5359
5460 # The cache is used so that slice values only need to be looked up once per context.
55- # - The keys in this dictionary are slice queries encoded as a tuple
56- # (ex. `("Chronos_combined", "SOX10", "feature_label")` ).
61+ # - The keys in this dictionary match the values of an any { "var": "<var_name>" }
62+ # expressions (and therefore should also match the keys of context["vars"] ).
5763 # - The values are each an entire dictionary of slice values (indexed by given ID)
5864 self .cache = {}
5965
@@ -135,104 +141,13 @@ def __bool__(self):
135141 return True
136142
137143
138- class LegacyContextEvaluator :
139- """
140- DEPRECATED: Use `ContextEvaluator` for future development.
141- This older version has a few differences from the new one:
142- - Slices are specified using string slice IDs instead of slice queries
143- - Matching on index is done with a field called "entity_label". For features,
144- this is expected to match the feature label. For samples, this matches on sample ID (not label).
145- This confusing behavior is part of why we're deprecating the old version.
146- - the field "context_type" is used to specify the dimension type
147- """
148-
149- def __init__ (self , context : dict , get_slice_data : Callable [[str ], dict [str , Any ]]):
150- """
151- A `context` dict should have:
152- - a `context_type` such as "depmap_model"
153- - an `expr` such as { "==": [ { "var": "slice/lineage/1/label" }, "Breast" ] }
154- """
155- self .context_type = context ["context_type" ]
156- self .expr = _encode_dots_in_vars (context ["expr" ])
157- # Cache is keyed by Slice ID. Each value is an entire dictionary of slice values.
158- self .cache = {}
159- self .get_slice_data = get_slice_data
160-
161- def is_match (self , dimension_label : str ):
162- """
163- This evaluates `expr` against a given `dimension_label`. It returns
164- True/False depending on if `dimension_label` satifies the conditions of
165- the expression, including any variables ("var" subexpressions) which
166- are bound by using a magic dict that does lookups lazily.
167- """
168- dictionary_override = _LegacyLazyContextDict (
169- self .context_type , dimension_label , self .cache , self .get_slice_data
170- )
171-
172- try :
173- return jsonLogic (self .expr , dictionary_override )
174- except (TypeError , ValueError ) as e :
175- print ("Exception evaluating" , self .expr , "against" , dimension_label )
176- print (e )
177- return False
178-
179-
180- class _LegacyLazyContextDict (dict ):
181- """
182- The JsonLogic library wants to be passed a dictionary of values. However, we need to
183- inject our own special cases and caching, so we override the dictionary class with
184- special functionality. Interesting thread on overriding the Dict class:
185- https://stackoverflow.com/questions/3387691/how-to-perfectly-override-a-dict
186- But we don't need to "perfectly" override it; just well enough to trick the JsonLogic library.
187-
188- This implementation uses and updates the cache that's been passed in to the constructor.
189- """
190-
191- def __init__ (
192- self ,
193- context_type : str ,
194- dimension_label : str ,
195- cache : dict ,
196- get_slice_data : Callable [[str ], dict [str , Any ]],
197- ):
198- self .context_type = context_type
199- self .dimension_label = dimension_label
200- self .cache = cache
201- self .get_slice_data = get_slice_data
202-
203- def __getitem__ (self , prop ):
204- """
205- Given a slice ID, get the slice value which corresponds to the
206- "dimension_label" that's already been passed into the constructor of this class.
207- """
208- # Handle trivial case where we're just looking up a dimension's own
209- # label. Note that this is called "entity_label" for historical
210- # reasons.
211- if prop == "entity_label" :
212- return self .dimension_label
213-
214- if prop .startswith ("slice/" ):
215- if prop not in self .cache :
216- self .cache [prop ] = self .get_slice_data (prop )
217-
218- return self .cache [prop ][self .dimension_label ]
219-
220- raise LookupError (
221- f"Unable to find context property '{ prop } '. Are you sure a corresponding "
222- f"dataset exists and can be looked up by { self .context_type } ?"
223- )
224-
225- # We don't want our virtual dictionary to appear empty.
226- # Otherwise, the JsonLogic library will stomp it out with an empty default dict:
227- # https://github.com/nadirizr/json-logic-py/blob/master/json_logic/__init__.py#L180
228- def __bool__ (self ):
229- return True
230-
231144
232145def _encode_dots_in_vars (expr : dict ):
233146 """
234147 URL-encode any dots in variables. Otherwise, JsonLogic thinks they are property lookups.
235- Example expression: { "==": [ { "var": "slice/lineage/1/label" }, "Breast" ] }
148+ This was important back when we would use slice IDs as var names. Example:
149+ { "var": "slice/msi-0584.6%2Fmsi/CCLE (NGS)/label" }
150+ This is no longer much of an issue because the UI now generates simplified var names.
236151 """
237152
238153 def walk (node , key ):
@@ -249,7 +164,11 @@ def walk(node, key):
249164
250165
251166def _escape_dots (d : dict ) -> dict :
252- """Return a new dict with all dots in keys replaced by %2E."""
167+ """
168+ Return a new dict with all dots in keys replaced by %2E. This is the complement to
169+ _encode_dots_in_vars, ensuring entries in the `slice_query_vars` dict will match
170+ those found in { "var": "..." } expressions.
171+ """
253172 return {
254173 (k .replace ("." , "%2E" ) if isinstance (k , str ) else k ): v for k , v in d .items ()
255174 }
0 commit comments