Skip to content
This repository has been archived by the owner on Mar 17, 2023. It is now read-only.

Improve profile merging for nested dicts #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 46 additions & 12 deletions jupyterhub_singleuser_profiles/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,9 @@ def write_config_map(self, config_map_name, key_name, data):
def update_user_profile_cm(self, username, data={}, key=None, value=None):
cm_name = _USER_CONFIG_MAP_TEMPLATE % escape(username)
cm_key_name = "profile"
cm_data = data
if len(data) > 0 and 'env' not in data:
cm_data = {'env': data}
if key and value:
cm_data[key] = value
cm_data = self.get_user_profile_cm(username)
cm_data = self.merge_profiles(cm_data, data)

self.write_config_map(cm_name, cm_key_name, cm_data)

def get_user_profile_cm(self, username):
Expand Down Expand Up @@ -171,13 +169,49 @@ def empty_profile(self):

@classmethod
def merge_profiles(self, profile1, profile2):
profile1["name"] = ", ".join(filter(None, [profile1.get("name", ""), profile2.get("name", "")]))
profile1["images"] = list(set(profile1.get("images", []) + profile2.get("images", [])))
profile1["users"] = list(set(profile1.get("users", []) + profile2.get("users", [])))
profile1["env"] = {**profile1.get('env', {}), **profile2.get('env', {})}
profile1["resources"] = {**profile1.get('resources', {}), **profile2.get('resources', {})}
profile1["services"] = {**profile1.get('services', {}), **profile2.get('services', {})}
return profile1
name = ", ".join(filter(None, [profile1.get("name", ""), profile2.get("name", "")]))
result = {}
for k, v in profile1.items():
result[k] = self._profile_data_merge(v, profile2.get(k, type(v)()))

result["name"] = name
return result

@classmethod
def _profile_data_merge(self, a, b):
"""merges b into a and return merged result

NOTE: tuples and arbitrary objects are not handled as it is totally ambiguous what should happen"""
key = None
# ## debug output
# sys.stderr.write("DEBUG: %s to %s\n" %(b,a))
try:
if a is None or isinstance(a, str) or isinstance(a, int) or isinstance(a, float):
# border case for first run or if a is a primitive
a = b
elif isinstance(a, list):
# lists can be only appended
if isinstance(b, list):
# merge lists
a = list(set(a + b))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will prevent you from implementing list of dicts in the user profiles since default dictionaries aren't hashable

else:
# append to list
a.append(b)
elif isinstance(a, dict):
# dicts must be merged
if isinstance(b, dict):
for key in b:
if key in a:
a[key] = self._profile_data_merge(a[key], b[key])
else:
a[key] = b[key]
else:
raise Exception('Cannot merge non-dict "%s" into dict "%s"' % (b, a))
else:
raise Exception('NOT IMPLEMENTED "%s" into "%s"' % (b, a))
except TypeError as e:
raise Exception('TypeError "%s" in key "%s" when merging "%s" into "%s"' % (e, key, b, a))
return a

@classmethod
def apply_pod_profile(self, spawner, pod, profile):
Expand Down