Skip to content

Commit b5910dd

Browse files
committed
Simplifying logic
1 parent 190594c commit b5910dd

File tree

2 files changed

+84
-37
lines changed

2 files changed

+84
-37
lines changed

src/fides/api/service/connectors/query_configs/bigquery_query_config.py

+27-37
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
from fides.api.service.connectors.query_configs.query_config import (
1919
QueryStringWithoutTuplesOverrideQueryConfig,
2020
)
21-
from fides.api.util.collection_util import Row, filter_nonempty_values, unflatten_dict
21+
from fides.api.util.collection_util import (
22+
Row,
23+
filter_nonempty_values,
24+
flatten_dict,
25+
merge_dicts,
26+
unflatten_dict,
27+
)
2228

2329

2430
class BigQueryQueryConfig(QueryStringWithoutTuplesOverrideQueryConfig):
@@ -124,44 +130,28 @@ def generate_update(
124130
This implementation handles nested fields by grouping them as JSON objects rather than
125131
individual field updates.
126132
"""
127-
# Get initial update value map
133+
# Get initial update value map (already flattened)
128134
update_value_map: Dict[str, Any] = self.update_value_map(row, policy, request)
129135

130-
# Convert flattened paths to nested structure using unflatten_dict
131-
unflattened_update_map = unflatten_dict(update_value_map)
132-
133-
# Prepare final update map, preserving original nested structures
134-
final_update_map = {}
135-
for field, value in unflattened_update_map.items():
136-
if isinstance(value, dict):
137-
# For nested fields, preserve original structure and update only changed values
138-
original_struct = row.get(field, {})
139-
if isinstance(original_struct, dict):
140-
updated_struct = {**original_struct, **value}
141-
final_update_map[field] = updated_struct
142-
elif isinstance(value, list):
143-
# Handle array fields, preserving unmodified values
144-
original_array = row.get(field, [])
145-
if isinstance(original_array, list):
146-
updated_array = []
147-
148-
# For each item in the original array
149-
for i, original_item in enumerate(original_array):
150-
if i < len(value):
151-
updated_item = value[i]
152-
# If both are dictionaries, merge them to preserve unmodified fields
153-
if isinstance(original_item, dict) and isinstance(
154-
updated_item, dict
155-
):
156-
updated_item = {**original_item, **updated_item}
157-
updated_array.append(updated_item)
158-
else:
159-
updated_array.append(original_item)
160-
161-
final_update_map[field] = updated_array
162-
else:
163-
# Keep regular fields
164-
final_update_map[field] = value
136+
# 1. Take update_value_map as-is (already flattened)
137+
138+
# 2. Flatten the row
139+
flattened_row = flatten_dict(row)
140+
141+
# 3. Merge flattened_row with update_value_map (update_value_map takes precedence)
142+
merged_dict = merge_dicts(flattened_row, update_value_map)
143+
144+
# 4. Unflatten the merged dictionary
145+
nested_result = unflatten_dict(merged_dict)
146+
147+
# 5. Only keep top-level keys that are in the update_value_map
148+
# Get unique top-level keys from update_value_map
149+
top_level_keys = {key.split(".")[0] for key in update_value_map.keys()}
150+
151+
# Filter the nested result to only include those top-level keys
152+
final_update_map = {
153+
k: v for k, v in nested_result.items() if k in top_level_keys
154+
}
165155

166156
# Use existing non-empty reference fields mechanism for WHERE clause
167157
non_empty_reference_field_keys: Dict[str, Field] = filter_nonempty_values(

src/fides/api/util/collection_util.py

+57
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,60 @@ def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str,
176176
f"Error unflattening dictionary, conflicting levels detected: {exc}"
177177
)
178178
return output
179+
180+
181+
def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str, Any]:
182+
"""
183+
Recursively flatten a dictionary or list into a flat dictionary with dot-notation keys.
184+
Handles nested dictionaries and arrays with proper indices.
185+
186+
example:
187+
188+
{
189+
"A": {
190+
"B": "1",
191+
"C": "2"
192+
},
193+
"D": [
194+
{"E": "3"},
195+
{"E": "4"}
196+
]
197+
}
198+
199+
becomes
200+
201+
{
202+
"A.B": "1",
203+
"A.C": "2",
204+
"D.0.E": "3",
205+
"D.1.E": "4"
206+
}
207+
208+
Args:
209+
data: The data to flatten (dict, list, or scalar value)
210+
prefix: The current key prefix (used in recursion)
211+
separator: The separator to use between key segments (default: ".")
212+
213+
Returns:
214+
A flattened dictionary with dot-notation keys
215+
"""
216+
items = {}
217+
218+
if isinstance(data, dict):
219+
for k, v in data.items():
220+
new_key = f"{prefix}{separator}{k}" if prefix else k
221+
if isinstance(v, (dict, list)):
222+
items.update(flatten_dict(v, new_key, separator))
223+
else:
224+
items[new_key] = v
225+
elif isinstance(data, list):
226+
for i, v in enumerate(data):
227+
new_key = f"{prefix}{separator}{i}"
228+
if isinstance(v, (dict, list)):
229+
items.update(flatten_dict(v, new_key, separator))
230+
else:
231+
items[new_key] = v
232+
else:
233+
items[prefix] = data
234+
235+
return items

0 commit comments

Comments
 (0)