266 lines
8.8 KiB
Python
266 lines
8.8 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2023 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Utilities for generating and updating fallthrough maps."""
|
|
|
|
import copy
|
|
|
|
from googlecloudsdk.calliope.concepts import deps as deps_lib
|
|
|
|
|
|
def AddFlagFallthroughs(
|
|
base_fallthroughs_map, attributes, attribute_to_args_map):
|
|
"""Adds flag fallthroughs to fallthrough map.
|
|
|
|
Iterates through each attribute and prepends a flag fallthrough.
|
|
This allows resource attributes to be resolved to flag first. For example:
|
|
|
|
{'book': [deps.ValueFallthrough('foo')]}
|
|
|
|
will update to something like...
|
|
|
|
{
|
|
'book': [
|
|
deps.ArgFallthrough('--foo'),
|
|
deps.ValueFallthrough('foo')
|
|
]
|
|
}
|
|
|
|
Args:
|
|
base_fallthroughs_map: {str: [deps._FallthroughBase]}, A map of attribute
|
|
names to fallthroughs
|
|
attributes: list[concepts.Attribute], list of attributes associated
|
|
with the resource
|
|
attribute_to_args_map: {str: str}, A map of attribute names to the names
|
|
of their associated flags.
|
|
"""
|
|
for attribute in attributes:
|
|
current_fallthroughs = base_fallthroughs_map.get(attribute.name, [])
|
|
|
|
if arg_name := attribute_to_args_map.get(attribute.name):
|
|
arg_fallthrough = deps_lib.ArgFallthrough(arg_name)
|
|
else:
|
|
arg_fallthrough = None
|
|
|
|
if arg_fallthrough:
|
|
filtered_fallthroughs = [
|
|
f for f in current_fallthroughs if f != arg_fallthrough]
|
|
fallthroughs = [arg_fallthrough] + filtered_fallthroughs
|
|
else:
|
|
fallthroughs = current_fallthroughs
|
|
base_fallthroughs_map[attribute.name] = fallthroughs
|
|
|
|
|
|
def AddAnchorFallthroughs(
|
|
base_fallthroughs_map, attributes, anchor, collection_info,
|
|
anchor_fallthroughs):
|
|
"""Adds fully specified fallthroughs to fallthrough map.
|
|
|
|
Iterates through each attribute and prepends a fully specified fallthrough.
|
|
This allows resource attributes to resolve to the fully specified anchor
|
|
value first. For example:
|
|
|
|
{'book': [deps.ValueFallthrough('foo')]}
|
|
|
|
will udpate to something like...
|
|
|
|
{
|
|
'book': [
|
|
deps.FullySpecifiedAnchorFallthrough(anchor_fallthroughs),
|
|
deps.ValueFallthrough('foo')
|
|
]
|
|
}
|
|
|
|
Args:
|
|
base_fallthroughs_map: {str: [deps._FallthroughBase]}, A map of attribute
|
|
names to fallthroughs
|
|
attributes: list[concepts.Attribute], list of attributes associated
|
|
with the resource
|
|
anchor: concepts.Attribute, attribute that the other attributes should
|
|
resolve to if fully specified
|
|
collection_info: the info of the collection to parse the anchor as
|
|
anchor_fallthroughs: list[deps._FallthroughBase], fallthroughs used to
|
|
resolve the anchor value
|
|
"""
|
|
for attribute in attributes:
|
|
current_fallthroughs = base_fallthroughs_map.get(attribute.name, [])
|
|
anchor_based_fallthrough = deps_lib.FullySpecifiedAnchorFallthrough(
|
|
anchor_fallthroughs, collection_info, attribute.param_name)
|
|
|
|
if attribute != anchor:
|
|
filtered_fallthroughs = [
|
|
f for f in current_fallthroughs if f != anchor_based_fallthrough]
|
|
fallthroughs = [anchor_based_fallthrough] + filtered_fallthroughs
|
|
else:
|
|
fallthroughs = current_fallthroughs
|
|
base_fallthroughs_map[attribute.name] = fallthroughs
|
|
|
|
|
|
def UpdateWithValueFallthrough(
|
|
base_fallthroughs_map, attribute_name, parsed_args):
|
|
"""Shortens fallthrough list to a single deps.ValueFallthrough.
|
|
|
|
Used to replace the attribute_name entry in a fallthrough map to a
|
|
single ValueFallthrough. For example:
|
|
|
|
{'book': [deps.Fallthrough(lambda: 'foo')]}
|
|
|
|
will update to something like...
|
|
|
|
{'book': [deps.ValueFallthrough('foo')]}
|
|
|
|
Args:
|
|
base_fallthroughs_map: {str: [deps._FallthroughBase]}, A map of attribute
|
|
names to fallthroughs we are updating
|
|
attribute_name: str, entry in fallthrough map we are updating
|
|
parsed_args: Namespace | None, used to derive the value for ValueFallthrough
|
|
"""
|
|
if not parsed_args:
|
|
return
|
|
|
|
attribute_value, attribute_fallthrough = _GetFallthroughAndValue(
|
|
attribute_name, base_fallthroughs_map, parsed_args)
|
|
|
|
if attribute_fallthrough:
|
|
_UpdateMapWithValueFallthrough(
|
|
base_fallthroughs_map, attribute_value, attribute_name,
|
|
attribute_fallthrough)
|
|
|
|
|
|
def CreateValueFallthroughMapList(
|
|
base_fallthroughs_map, attribute_name, parsed_args):
|
|
"""Generates a list of fallthrough maps for each anchor value in a list.
|
|
|
|
For each anchor value, generate a fallthrough map. For example, if user
|
|
provides anchor values ['foo', 'bar'] and a base fallthrough like...
|
|
|
|
{'book': [deps.ArgFallthrough('--book')]}
|
|
|
|
will generate something like...
|
|
|
|
[
|
|
{'book': [deps.ValueFallthrough('foo')]},
|
|
{'book': [deps.ValueFallthrough('bar')]}
|
|
]
|
|
|
|
Args:
|
|
base_fallthroughs_map: {str: [deps._FallthroughBase]}, A map of attribute
|
|
names to fallthroughs we are updating
|
|
attribute_name: str, entry in fallthrough map we are updating
|
|
parsed_args: Namespace | None, used to derive the value for ValueFallthrough
|
|
|
|
Returns:
|
|
list[{str: deps._FallthroughBase}], a list of fallthrough maps for
|
|
each parsed anchor value
|
|
"""
|
|
attribute_values, attribute_fallthrough = _GetFallthroughAndValue(
|
|
attribute_name, base_fallthroughs_map, parsed_args)
|
|
|
|
map_list = []
|
|
if not attribute_fallthrough:
|
|
return map_list
|
|
|
|
for value in attribute_values:
|
|
new_map = {**base_fallthroughs_map}
|
|
_UpdateMapWithValueFallthrough(
|
|
new_map, value, attribute_name, attribute_fallthrough)
|
|
map_list.append(new_map)
|
|
return map_list
|
|
|
|
|
|
def PluralizeFallthroughs(base_fallthroughs_map, attribute_name):
|
|
"""Updates fallthrough map entry to make fallthroughs plural.
|
|
|
|
For example:
|
|
|
|
{'book': [deps.ArgFallthrough('--foo')]}
|
|
|
|
will update to something like...
|
|
|
|
{'book': [deps.ArgFallthrough('--foo'), plural=True]}
|
|
|
|
Args:
|
|
base_fallthroughs_map: {str: [deps.Fallthrough]}, A map of attribute
|
|
names to fallthroughs we are updating
|
|
attribute_name: str, entry in fallthrough map we are updating
|
|
"""
|
|
given_fallthroughs = base_fallthroughs_map.get(attribute_name, [])
|
|
|
|
base_fallthroughs_map[attribute_name] = [
|
|
_PluralizeFallthrough(fallthrough)
|
|
for fallthrough in given_fallthroughs
|
|
]
|
|
|
|
|
|
def _PluralizeFallthrough(fallthrough):
|
|
plural_fallthrough = copy.deepcopy(fallthrough)
|
|
plural_fallthrough.plural = True
|
|
return plural_fallthrough
|
|
|
|
|
|
def _UpdateMapWithValueFallthrough(
|
|
base_fallthroughs_map, value, attribute_name, attribute_fallthrough):
|
|
value_fallthrough = deps_lib.ValueFallthrough(
|
|
value,
|
|
attribute_fallthrough.hint,
|
|
active=attribute_fallthrough.active)
|
|
base_fallthroughs_map[attribute_name] = [value_fallthrough]
|
|
|
|
|
|
def _GetFallthroughAndValue(attribute_name, fallthroughs_map, parsed_args):
|
|
"""Derives value and fallthrough used to derives value from map."""
|
|
for possible_fallthrough in fallthroughs_map.get(attribute_name, []):
|
|
try:
|
|
value = possible_fallthrough.GetValue(parsed_args)
|
|
return (value, possible_fallthrough)
|
|
except deps_lib.FallthroughNotFoundError:
|
|
continue
|
|
else:
|
|
return (None, None)
|
|
|
|
|
|
def ValidateFallthroughMap(fallthroughs_map):
|
|
"""Validates fallthrough map to ensure fallthrough map is not invalid.
|
|
|
|
Fallthrough maps are only invalid if an inactive fallthrough comes before
|
|
an active fallthrough. It could result in an active fallthrough that can
|
|
never be reached.
|
|
|
|
Args:
|
|
fallthroughs_map: {str: [deps._FallthroughBase]}, A map of attribute
|
|
names to fallthroughs we are validating
|
|
|
|
Returns:
|
|
(bool, str), bool for whether fallthrough map is valid and str for
|
|
the error message
|
|
"""
|
|
|
|
for attr, fallthroughs in fallthroughs_map.items():
|
|
inactive_fallthrough = None
|
|
for fallthrough in fallthroughs:
|
|
if inactive_fallthrough and fallthrough.active:
|
|
active_str = fallthrough.__class__.__name__
|
|
inactive_str = inactive_fallthrough.__class__.__name__
|
|
msg = (f'Invalid Fallthrough Map: Fallthrough map at [{attr}] contains '
|
|
f'inactive fallthrough [{inactive_str}] before active '
|
|
f'fallthrough [{active_str}]. Fix the order so that active '
|
|
f'fallthrough [{active_str}] is reachable or remove active '
|
|
f'fallthrough [{active_str}].')
|
|
return False, msg
|
|
|
|
if not fallthrough.active:
|
|
inactive_fallthrough = fallthrough
|
|
else:
|
|
return True, None
|