in metaflow/plugins/cards/component_serializer.py [0:0]
def _finalize(self):
"""
The `_finalize` function is called once the last @card decorator calls `step_init`. Calling this function makes `current.card` ready for usage inside `@step` code.
This function's works two parts :
1. Resolving `self._default_editable_card`.
- The `self._default_editable_card` holds the uuid of the card that will have access to the `append`/`extend` methods.
2. Resolving edge cases where @card `id` argument may be `None` or have a duplicate `id` when there are more than one editable cards.
3. Resolving the `self._default_editable_card` to the card with the`customize=True` argument.
"""
all_card_meta = list(self._cards_meta.values())
for c in all_card_meta:
ct = get_card_class(c["type"])
c["exists"] = False
if ct is not None:
c["exists"] = True
# If a card has `customize=True` and is not editable then it will not be considered default editable.
editable_cards_meta = [c for c in all_card_meta if c["editable"]]
if len(editable_cards_meta) == 0:
return
# Create the `self._card_id_map` lookup table which maps card `id` to `uuid`.
# This table has access to all cards with `id`s set to them.
card_ids = []
for card_meta in all_card_meta:
if card_meta["card_id"] is not None:
self._card_id_map[card_meta["card_id"]] = card_meta["uuid"]
card_ids.append(card_meta["card_id"])
# If there is only one editable card then this card becomes `self._default_editable_card`
if len(editable_cards_meta) == 1:
self._default_editable_card = editable_cards_meta[0]["uuid"]
return
# Segregate cards which have id as none and those which don't.
not_none_id_cards = [c for c in editable_cards_meta if c["card_id"] is not None]
none_id_cards = [c for c in editable_cards_meta if c["card_id"] is None]
# If there is only 1 card with id set to None then we can use that as the default card.
if len(none_id_cards) == 1:
self._default_editable_card = none_id_cards[0]["uuid"]
# If the size of the set of ids is not equal to total number of cards with ids then warn the user that we cannot disambiguate
# so `current.card['my_card_id']` won't work.
id_set = set(card_ids)
if len(card_ids) != len(id_set):
non_unique_ids = [
idx
for idx in id_set
if len(list(filter(lambda x: x["card_id"] == idx, not_none_id_cards)))
> 1
]
nui = ", ".join(non_unique_ids)
# throw a warning that decorators have non-unique Ids
self._warning(
(
"Multiple `@card` decorator have been annotated with duplicate ids : %s. "
"`current.card['%s']` will not work"
)
% (nui, non_unique_ids[0])
)
# remove the non unique ids from the `self._card_id_map`
for idx in non_unique_ids:
del self._card_id_map[idx]
# if a @card has `customize=True` in the arguments then there should only be one @card with `customize=True`. This @card will be the _default_editable_card
customize_cards = [c for c in editable_cards_meta if c["customize"]]
if len(customize_cards) > 1:
self._warning(
(
"Multiple @card decorators have `customize=True`. "
"Only one @card per @step can have `customize=True`. "
"`current.card.append` will ignore all decorators marked `customize=True`."
)
)
elif len(customize_cards) == 1:
# since `editable_cards_meta` hold only `editable=True` by default we can just set this card here.
self._default_editable_card = customize_cards[0]["uuid"]