def _query_to_dict(self, query: MultiDict): """ Return a dict with list as value from the MultiDict. The value will be wrapped in a list if the args spec is define as a list or if the multiple values are sent (i.e ?foo=1&foo=2) """ return { key: values if len(values := query.getall(key)) > 1 or key in self._is_multiple else value for key, value in query.items() }
def parse(self, post_data: MultiDict): self.clear() if isinstance(post_data, dict): post_data = MultiDict(post_data) self.incr_fields = set() for k, v in post_data.items(): # 提交多个相同值,等价于提交一个数组(用于formdata和urlencode形式) v_all = post_data.getall(k) if len(v_all) > 1: v = v_all if k.startswith('$'): continue elif k == 'returning': self.returning = True continue elif '.' in k: # TODO: 不允许 incr 和普通赋值同时出现 k, op = k.rsplit('.', 1) if op == 'incr': self.incr_fields.add(k) self[k] = v
def _multi_matchup_bullets(self, section, points, matchups): def multi_matchup_string(primary_team, opponents): opponent_string = opponent_matchup_string( self._team_points(primary_team), opponents) return (f"{self._team_string(primary_team)}" f"{';' if len(opponents) > 1 else ''} {opponent_string}") def opponent_matchup_string(primary_points, opponents): opponent_strings = [ f"over {self._team_string(opponent)}" if self._team_points(opponent) < primary_points else f"to {self._team_string(opponent)}" if self._team_points(opponent) > primary_points else f"tied with {self._team_points(opponent)}" for opponent in opponents ] if len(opponent_strings) == 1: return opponent_strings[0] elif len(opponent_strings) > 2: first_string = "; ".join(opponent_strings[:-2]) last_string = "; and ".join(opponent_strings[-2:]) return f"{first_string}; {last_string}" return ", and ".join(opponent_strings) matchup_dict = MultiDict(matchups) return [ multi_matchup_string(team, matchup_dict.getall(team)) for team in list(dict.fromkeys(matchup_dict.keys())) ]
def multidict_to_dict(x: MultiDict) -> dict: """ Funzione che converte un MultiDict in un dict :param x: MultiDict da convertire :type: MultiDict :return: dict corrispondente al MultiDict :rtype: dict """ return { k: v if hasattr(v, "__len__") and len(v) <= 1 else x.getall(k) for k, v in x.items() }
async def add_relations(self, rel_attr: InstrumentedAttribute, data: MultiDict, conn): # rel_attr - атрибут связи many to many (например User.groups, User.permissions) relations_table = rel_attr.property.secondary # класс таблицы связей many-to-many ident_col, rel_col = tuple( (item[1] for item in rel_attr.prop.local_remote_pairs)) # rel_attr.prop.local_remote_pairs - список кортежей (по 2 элемента) связей таблиц relations = [{ 'id': None, ident_col.name: self.id, rel_col.name: rel_id } for rel_id in data.getall(rel_col.name, [])] await conn.execute(relations_table.delete(ident_col == self.id)) if len(relations) == 0: return await conn.execute(relations_table.insert(), relations)
class DivisionTableFormatter: def __init__(self, teams): self._divisions = MultiDict( (team.division, team) for team in teams.values()) def markdown(self): markdown_string = "### Division Stats\n" all_stats = self._all_division_stats() for index, entries in enumerate(list(zip(*all_stats))): columns = [ f"**{entry}**" if index == 0 else f"{entry}" for entry in entries ] markdown_string = f"{markdown_string}\n| {' | '.join(columns)} |" if index == 0: alignment = " | ".join(len(columns) * [":---:"]) markdown_string = f"{markdown_string}\n| {alignment} |" return [markdown_string] def _all_division_stats(self): stats = [ DivisionStats("Division", "Points", "Points/Team", "Std. Dev.", "Record") ] stats.extend( self._division_stats(division) for division in sorted( list(dict.fromkeys(self._divisions.keys())))) stats.append(self._division_stats("League")) return stats def _division_stats(self, division_name): teams = (self._divisions.values() if division_name == "League" else self._divisions.getall(division_name)) use_record = division_name != "League" scores = list(map(lambda team: team.total_points, teams)) wins = sum(map(lambda team: len(team.wins), teams), 0) losses = sum(map(lambda team: len(team.losses), teams), 0) ties = sum(map(lambda team: len(team.ties), teams), 0) record_string = ("" if not use_record else f"{wins}-{losses}" if ties == 0 else f"{wins}-{losses}-{ties}") return DivisionStats(division_name, sum(scores, 0), round(statistics.mean(scores), 1), round(statistics.stdev(scores), 1), record_string)
def _dict_convert(self, data) -> Dict: if isinstance(data, dict): return data elif isinstance(data, MultiDict): data = MultiDict(data) tmp = {} for k, v in data.items(): # 提交多个相同值,等价于提交一个数组(用于formdata和urlencode形式) v_all = data.getall(k) if len(v_all) > 1: v = v_all tmp[k] = v return tmp else: return dict(data)
def parse(self, post_data: MultiDict): self.clear() if isinstance(post_data, dict): post_data = MultiDict(post_data) for k, v in post_data.items(): v_all = post_data.getall(k) if len(v_all) > 1: v = v_all if k.startswith('$'): continue elif k == '_inner_data': continue elif k == 'returning': self.returning = True continue elif '.' in k: k, op = k.rsplit('.', 1) v = UpdateInfo(k, op, v) self[k] = v
def main(): with open(cClean_no_meir_list_name, 'r', errors='replace') as cClean_no_meir_list_file: csvfile = csv.reader(cClean_no_meir_list_file) cClean_no_meir_list = [] site_names = set() site_names_and_participants = set() for row in csvfile: if len(row) == 0: continue cClean_no_meir_list.append(row[0]) site_participant, = row site_name, partipant_name = extract_site_name_and_participant( site_participant) site_names.add(site_name) site_names_and_participants.add((site_name, partipant_name)) cclean_site_names_list = list(site_names) cclean_site_names_list.sort() image_descriptions = [] #print ("cClean site names: \n", '\n'.join(cclean_site_names_list)) with open(zip_list_name, 'r', errors='replace') as zip_list: csvfile = csv.reader(zip_list) site_names = dict() with open(zip_and_cClean_no_meir_list, 'wt') as zip_ccclean_noMeir_file: csvwriter = csv.writer(zip_ccclean_noMeir_file) csvwriter.writerow(ImageDesc._fields) for row in csvfile: site_participant, filename = row separator = site_participant.rfind('_') site = site_participant[:separator] filename = filename.replace('\\', '/') filename_base = filename[:-4] # cut off '.jpg' parts = filename_base.split('/') alt_site_name, participant, event, img_index = parts[-4:] if site == "SingTel": alt_site_name = "SingTel" if site == "KippsBay": alt_site_name = "KBCBMSVR1" if site == "SeaView": alt_site_name = "SVS1-HZ-ISR" match = extension_regex.search(filename) if match is None: raise Exception('Not an archive') zip_file = filename[:match.end() - 1] path_in_zip = filename[match.end():] desc = ImageDesc(site, alt_site_name, participant, event, img_index, zip_file, path_in_zip) site_names[site] = alt_site_name site_names[alt_site_name] = site if (site, participant) in site_names_and_participants or ( alt_site_name, participant) in site_names_and_participants: image_descriptions.append(desc) csvwriter.writerow(desc) participant_to_images = MultiDict() for desc in image_descriptions: participant_to_images.add(str((desc.site, desc.participant)), desc) chosen_site_to_participant = MultiDict() selected_image_descriptions = [] for participant in set(participant_to_images.keys()): images = participant_to_images.getall(participant) if len(images) > MIN_NUM_IMAGES and len(images) <= MAX_NUM_IMAGES: # selected_image_descriptions += images chosen_site_to_participant.add(images[0].site, images[0].participant) num_chosen_participants_per_site = dict() for site in set(chosen_site_to_participant.keys()): num_chosen_participants_per_site[site] = len( chosen_site_to_participant.getall(site)) print(site, num_chosen_participants_per_site[site]) total_num_chosen_participants = sum( num_chosen_participants_per_site.values()) print(total_num_chosen_participants, 'total') for site, num_part in num_chosen_participants_per_site.items(): ratio = num_part / total_num_chosen_participants num_participants_to_select = round(ratio * NUM_CHOSEN_PARTICIPANTS) print(site, num_part, ratio, num_participants_to_select) if num_participants_to_select == 0: continue participants = chosen_site_to_participant.getall(site) participants = participants[:num_participants_to_select] for participant in participants: images = participant_to_images.getall(str((site, participant))) per_event = MultiDict() for img in images: per_event.add(img.event, img) num_events = len(set(per_event.keys())) images_per_event = ceil(MIN_NUM_IMAGES / num_events) selected_images_for_this_participant = [] for event in set(per_event.keys()): images_for_this_event = per_event.getall(event) selected_images_for_this_participant += images_for_this_event[: images_per_event] if len(selected_images_for_this_participant) >= MIN_NUM_IMAGES: selected_images_for_this_participant = selected_images_for_this_participant[: MIN_NUM_IMAGES] else: selected_images_set = set(selected_images_for_this_participant) for img in images: selected_images_set.add(img) if len(selected_images_set) == MIN_NUM_IMAGES: break selected_images_for_this_participant = list( selected_images_set) selected_image_descriptions += selected_images_for_this_participant outfile = open( r'C:\Users\leahb\Documents\Leah\MyDataSets\FileSelection\selected_images_for_unknown.csv', 'wt', encoding='ascii') csv_out = csv.writer(outfile) csv_out.writerow(ImageDesc._fields) for rec in selected_image_descriptions: csv_out.writerow(rec)
class EventManager(object): """ A manager for events. This deals with firing of events and temporary listeners. """ def __init__(self): #: The task manager used to spawn events. self.task_manager = None #: A list of event hooks. self.event_hooks = set() #: A MultiDict of event listeners. self.event_listeners = MultiDict() #: A MultiDict of temporary listeners. self.temporary_listeners = MultiDict() # add or removal functions # Events def add_event(self, func, name: str = None): """ Add an event to the internal registry of events. :param name: The event name to register under. :param func: The function to add. """ if not inspect.iscoroutinefunction(func): raise TypeError("Event must be an async function") if name is None: evs = func.events else: evs = [name] for ev_name in evs: logger.debug("Registered event `{}` handling `{}`".format( func, ev_name)) self.event_listeners.add(ev_name, func) def remove_event(self, name: str, func): """ Removes a function event. :param name: The name the event is registered under. :param func: The function to remove. """ self.event_listeners = remove_from_multidict(self.event_listeners, key=name, item=func) # listeners def add_temporary_listener(self, name: str, listener): """ Adds a new temporary listener. To remove the listener, you can raise ListenerExit which will exit it and remove the listener from the list. :param name: The name of the event to listen to. :param listener: The listener function. """ self.temporary_listeners.add(name, listener) def remove_listener_early(self, name: str, listener): """ Removes a temporary listener early. :param name: The name of the event the listener is registered under. :param listener: The listener function. """ self.event_listeners = remove_from_multidict(self.event_listeners, key=name, item=listener) def add_event_hook(self, listener): """ Adds an event hook. :param listener: The event hook callable to use. """ logger.warning("Adding event hook '%s'", listener) self.event_hooks.add(listener) def remove_event_hook(self, listener): """ Removes an event hook. """ self.event_hooks.remove(listener) # wrapper functions async def _safety_wrapper(self, func, *args, **kwargs): """ Ensures a coro's error is caught and doesn't balloon out. """ try: await func(*args, **kwargs) except Exception as e: logger.exception("Unhandled exception in {}!".format( func.__name__), exc_info=True) async def _listener_wrapper(self, key: str, func, *args, **kwargs): """ Wraps a listener, ensuring ListenerExit is handled properly. """ try: await func(*args, **kwargs) except ListenerExit: # remove the function self.temporary_listeners = remove_from_multidict( self.temporary_listeners, key, func) except Exception: logger.exception("Unhandled exception in listener {}!".format( func.__name__), exc_info=True) self.temporary_listeners = remove_from_multidict( self.temporary_listeners, key, func) async def wait_for(self, event_name: str, predicate=None): """ Waits for an event. Returning a truthy value from the predicate will cause it to exit and return. :param event_name: The name of the event. :param predicate: The predicate to use to check for the event. """ p = multio.Promise() errored = False async def listener(ctx, *args): # exit immediately if the predicate is none if predicate is None: await p.set(args) raise ListenerExit try: res = predicate(*args) if inspect.isawaitable(res): res = await res except ListenerExit: # ??? await p.set(args) raise except Exception as e: # something bad happened, set exception and exit logger.exception("Exception in wait_for predicate!") # signal that an error happened nonlocal errored errored = True await p.set(e) raise ListenerExit else: # exit now if result is true if res is True: await p.set(args) raise ListenerExit self.add_temporary_listener(name=event_name, listener=listener) output = await p.wait() if errored: raise output # unwrap tuples, if applicable if len(output) == 1: return output[0] return output def wait_for_manager(self, event_name: str, predicate) -> 'typing.AsyncContextManager[None]': """ Returns a context manager that can be used to run some steps whilst waiting for a temporary listener. .. code-block:: python async with client.events.wait_for_manager("member_update", predicate=...): await member.nickname.set("Test") This probably won't be needed outside of internal library functions. """ return _wait_for_manager(self, event_name, predicate) async def spawn(self, cofunc, *args) -> typing.Any: """ Spawns a new async function using our task manager. Usage:: async def myfn(a, b): await do_some_operation(a + b) await events.spawn(myfn, 1, 2) :param cofunc: The async function to spawn. :param args: Args to provide to the async function. """ return await multio.asynclib.spawn(self.task_manager, cofunc, *args) async def fire_event(self, event_name: str, *args, **kwargs): """ Fires an event. :param event_name: The name of the event to fire. """ if "ctx" not in kwargs: gateway = kwargs.pop("gateway") client = kwargs.pop("client") ctx = EventContext(client, gateway.gw_state.shard_id, event_name) else: ctx = kwargs.pop("ctx") # clobber event name ctx.event_name = event_name # always ensure hooks are ran first for hook in self.event_hooks: cofunc = functools.partial(hook, ctx, *args, **kwargs) await self.spawn(cofunc) for handler in self.event_listeners.getall(event_name, []): coro = functools.partial(handler, ctx, *args, **kwargs) coro.__name__ = handler.__name__ await self.spawn(self._safety_wrapper, coro) for listener in self.temporary_listeners.getall(event_name, []): coro = functools.partial(self._listener_wrapper, event_name, listener, ctx, *args, **kwargs) await self.spawn(coro)
class MatchupSectionFormatter: MATCHUP_HEADERS = [ "Blowout of the Week", "Closest Matchup of the Week", "Strongest Loss", "No Wins for the Effort", "Weakest Win", "Dirty Cheater", ] def __init__(self, teams): self._teams = teams self._winners = MultiDict() self._losers = MultiDict() self._ties = MultiDict() for team in teams.values(): [self._winners.add(team.name, loser) for loser in team.wins] [self._losers.add(team.name, winner) for winner in team.losses] [self._ties.add(team.name, other_team) for other_team in team.ties] def markdown(self): def section_markdown(section): sorted_matchups = self._sort_matchups(section)[0] bullets = (self._comparison_matchup_bullets( section, *sorted_matchups) if section in [ "Blowout of the Week", "Closest Matchup of the Week", ] else self._multi_matchup_bullets(section, *sorted_matchups)) return markdown_for_section(section, bullets) return list(map(section_markdown, self.MATCHUP_HEADERS)) def _sort_matchups(self, section): def filter_key(matchup_entry): primary_team, opponent = matchup_entry if section == "Strongest Loss": return opponent in self._losers.getall(primary_team, []) elif section == "No Wins for the Effort": return primary_team not in self._winners elif section == "Dirty Cheater": return primary_team not in self._losers else: return opponent in self._winners.getall(primary_team, []) def groupby_key(matchup_entry): primary_team, opponent = matchup_entry primary_points = self._team_points(primary_team) opponent_points = self._team_points(opponent) if section in [ "Blowout of the Week", "Closest Matchup of the Week" ]: return primary_points - opponent_points else: return primary_points def sort_key(matchup_entry): primary_team, opponent = matchup_entry return ( groupby_key(matchup_entry), self._team_points(primary_team), self._team_points(opponent), ) if section == "Strongest Loss": entries = self._losers elif section == "No Wins for the Effort": entries = MultiDict(self._losers, **self._ties) elif section in ["Closest Matchup", "Dirty Cheater"]: entries = MultiDict(self._winners, **self._ties) else: entries = self._winners sorted_matchups = sorted(filter(filter_key, entries.items()), key=sort_key, reverse=self._reverse_sort(section)) return [(points, list(group)) for points, group in groupby(sorted_matchups, key=groupby_key)] def _team_points(self, team): return self._teams[team].total_points @staticmethod def _reverse_sort(section): return section in [ "Blowout of the Week", "Strongest Loss", "No Wins for the Effort", ] def _comparison_matchup_bullets(self, section, points, matchups): def comparison_matchup_string(matchup_entry): primary_team, opponent = matchup_entry primary_points = self._team_points(primary_team) opponent_points = self._team_points(opponent) point_diff = primary_points - opponent_points if point_diff == 0: return (f"Tied at {points_string(primary_points)}:" f" {primary_team} and {opponent}") return (f"{self._team_string(primary_team)}" f" over {self._team_string(opponent)}" f" by {points_string(point_diff)}.") deduplicated = [] [ deduplicated.append((primary_team, opponent)) for primary_team, opponent in matchups if (opponent, primary_team) not in deduplicated ] return list(map(comparison_matchup_string, deduplicated)) def _team_string(self, team): return team_string(team, self._team_points(team)) def _multi_matchup_bullets(self, section, points, matchups): def multi_matchup_string(primary_team, opponents): opponent_string = opponent_matchup_string( self._team_points(primary_team), opponents) return (f"{self._team_string(primary_team)}" f"{';' if len(opponents) > 1 else ''} {opponent_string}") def opponent_matchup_string(primary_points, opponents): opponent_strings = [ f"over {self._team_string(opponent)}" if self._team_points(opponent) < primary_points else f"to {self._team_string(opponent)}" if self._team_points(opponent) > primary_points else f"tied with {self._team_points(opponent)}" for opponent in opponents ] if len(opponent_strings) == 1: return opponent_strings[0] elif len(opponent_strings) > 2: first_string = "; ".join(opponent_strings[:-2]) last_string = "; and ".join(opponent_strings[-2:]) return f"{first_string}; {last_string}" return ", and ".join(opponent_strings) matchup_dict = MultiDict(matchups) return [ multi_matchup_string(team, matchup_dict.getall(team)) for team in list(dict.fromkeys(matchup_dict.keys())) ]
def collection(self, user_name, extra_params): collection_data = [] plays_data = [] if isinstance(extra_params, list): for params in extra_params: collection_data += self.client.collection( user_name=user_name, **params, ) else: collection_data = self.client.collection( user_name=user_name, **extra_params, ) # Dummy game for linking extra promos and accessories collection_data.append( _create_blank_collection(EXTRA_EXPANSIONS_GAME_ID, "ZZZ: Expansions without Game (A-I)")) params = {"subtype": "boardgameaccessory", "own": 1} accessory_collection = self.client.collection(user_name=user_name, **params) accessory_list_data = self.client.game_list([ game_in_collection["id"] for game_in_collection in accessory_collection ]) accessory_collection_by_id = MultiDict() for acc in accessory_collection: accessory_collection_by_id.add(str(acc["id"]), acc) plays_data = self.client.plays(user_name=user_name, ) game_list_data = self.client.game_list([ game_in_collection["id"] for game_in_collection in collection_data ]) collection_by_id = MultiDict() for item in collection_data: item["players"] = [] collection_by_id.add(str(item["id"]), item) for play in plays_data: play_id = str(play["game"]["gameid"]) if play_id in collection_by_id: collection_by_id[play_id]["players"].extend(play["players"]) games_data = list( filter(lambda x: x["type"] == "boardgame", game_list_data)) expansions_data = list( filter(lambda x: x["type"] == "boardgameexpansion", game_list_data)) game_data_by_id = {} expansion_data_by_id = {} for game in games_data: game["accessories_collection"] = [] game["expansions_collection"] = [] game_data_by_id[game["id"]] = game for expansion in expansions_data: expansion["accessories_collection"] = [] expansion["expansions_collection"] = [] expansion_data_by_id[expansion["id"]] = expansion expansion_data_by_id = custom_expansion_mappings(expansion_data_by_id) for expansion_data in expansion_data_by_id.values(): if is_promo_box(expansion_data): game_data_by_id[expansion_data["id"]] = expansion_data for expansion in expansion_data["expansions"]: id = expansion["id"] if expansion["inbound"] and id in expansion_data_by_id: expansion_data_by_id[id]["expansions_collection"].append( expansion_data) for accessory_data in accessory_list_data: own_game = False for accessory in accessory_data["accessories"]: id = accessory["id"] if accessory["inbound"]: if id in game_data_by_id: game_data_by_id[id]["accessories_collection"].append( accessory_data) own_game = True elif id in expansion_data_by_id: expansion_data_by_id[id][ "accessories_collection"].append(accessory_data) own_game = True if not own_game: game_data_by_id[EXTRA_EXPANSIONS_GAME_ID][ "accessories_collection"].append(accessory_data) for expansion_data in expansion_data_by_id.values(): own_base_game = False for expansion in expansion_data["expansions"]: id = expansion["id"] if expansion["inbound"]: if id in game_data_by_id: own_base_game = True if not is_promo_box(expansion_data): game_data_by_id[id][ "expansions_collection"].append(expansion_data) game_data_by_id[id][ "expansions_collection"].extend( expansion_data_by_id[expansion_data["id"]] ["expansions_collection"]) game_data_by_id[id][ "accessories_collection"].extend( expansion_data_by_id[expansion_data["id"]] ["accessories_collection"]) elif id in expansion_data_by_id: own_base_game = True if not own_base_game: id = EXTRA_EXPANSIONS_GAME_ID expansion_data["suggested_numplayers"] = [] game_data_by_id[id]["expansions_collection"].append( expansion_data) game_data_by_id[id]["expansions_collection"].extend( expansion_data_by_id[ expansion_data["id"]]["expansions_collection"]) game_data_by_id[id]["accessories_collection"].extend( expansion_data_by_id[ expansion_data["id"]]["accessories_collection"]) games_collection = list( filter(lambda x: x["id"] in game_data_by_id, collection_by_id.values())) games = [ BoardGame(game_data_by_id[collection["id"]], collection, expansions=[ BoardGame(expansion_data, collection) for expansion_data in _uniq(game_data_by_id[ collection["id"]]["expansions_collection"]) for collection in collection_by_id.getall( str(expansion_data["id"])) ], accessories=[ BoardGame(accessory_data, collection) for accessory_data in _uniq(game_data_by_id[ collection["id"]]["accessories_collection"]) for collection in accessory_collection_by_id.getall( str(accessory_data["id"])) ]) for collection in games_collection ] newGames = [] # Cleanup the game for game in games: for exp in game.expansions: exp.name = remove_prefix(exp.name, game) for acc in game.accessories: acc.name = remove_prefix(acc.name, game) contained_list = [] for con in game.contained: if con["inbound"]: con["name"] = remove_prefix(con["name"], game) contained_list.append(con) game.contained = sorted(contained_list, key=lambda x: x["name"]) integrates_list = [] for integrate in game.integrates: # Filter integrates to owned games if str(integrate["id"]) in collection_by_id: integrate["name"] = name_scrubber(integrate["name"]) integrates_list.append(integrate) game.integrates = sorted(integrates_list, key=lambda x: x["name"]) for reimps in game.reimplements: reimps["name"] = name_scrubber(reimps["name"]) for reimpby in game.reimplementedby: reimpby["name"] = name_scrubber(reimpby["name"]) family_list = [] for fam in game.families: newFam = family_filter(fam) if newFam: family_list.append(newFam) game.families = family_list game.publishers = publisher_filter(game.publishers, collection_by_id[str(game.id)]) # TODO This is terrible, but split the extra expansions by letter if game.id == EXTRA_EXPANSIONS_GAME_ID: game.description = "" game.players = [] for exp in game.expansions: exp.players.clear() newGame = copy.deepcopy(game) newGame.name = "ZZZ: Expansions without Game (J-Q)" newGame.collection_id = str(game.collection_id) + "jq" newGame.expansions = list( filter(lambda x: re.search(r"^[j-qJ-Q]", x.name), game.expansions)) newGame.accessories = list( filter(lambda x: re.search(r"^[j-qJ-Q]", x.name), game.accessories)) newGame.expansions = sorted(newGame.expansions, key=lambda x: x.name) newGame.accessories = sorted(newGame.accessories, key=lambda x: x.name) game.expansions = list( set(game.expansions) - set(newGame.expansions)) game.accessories = list( set(game.accessories) - set(newGame.accessories)) newGames.append(newGame) newGame = copy.deepcopy(game) newGame.name = "ZZZ: Expansions without Game (R-Z)" newGame.collection_id = str(game.collection_id) + "rz" newGame.expansions = list( filter(lambda x: re.search(r"^[r-zR-Z]", x.name), game.expansions)) newGame.accessories = list( filter(lambda x: re.search(r"^[r-zR-Z]", x.name), game.accessories)) newGame.expansions = sorted(newGame.expansions, key=lambda x: x.name) newGame.accessories = sorted(newGame.accessories, key=lambda x: x.name) game.expansions = list( set(game.expansions) - set(newGame.expansions)) game.accessories = list( set(game.accessories) - set(newGame.accessories)) newGames.append(newGame) # Resort the list after updating the names game.expansions = sorted(game.expansions, key=lambda x: x.name) game.accessories = sorted(game.accessories, key=lambda x: x.name) game.contained = sorted(game.contained, key=lambda x: x["name"]) game.families = sorted(game.families, key=lambda x: x["name"]) game.reimplements = sorted(game.reimplements, key=lambda x: x["name"]) game.reimplementedby = sorted(game.reimplementedby, key=lambda x: x["name"]) games.extend(newGames) return games