File: //snap/google-cloud-cli/current/lib/googlecloudsdk/command_lib/concepts/concept_managers.py
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Classes that manage concepts and dependencies.
For usage examples, see
googlecloudsdk/command_lib/concepts/all_concepts/base.py.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.command_lib.concepts import base
from googlecloudsdk.command_lib.concepts import dependency_managers
from googlecloudsdk.command_lib.concepts import names
import six
class ConceptManager(object):
"""A manager that contains all concepts (v2) for a given command.
This object is responsible for registering all concepts, creating arguments
for the concepts, and creating a RuntimeParser which will be responsible
for parsing the concepts.
Attributes:
concepts: [base.Concept], a list of concepts.
runtime_parser: RuntimeParser, the runtime parser manager for all concepts.
"""
def __init__(self):
self.concepts = []
self.runtime_parser = None
self._command_level_fallthroughs = {}
def AddConcept(self, concept):
"""Add a single concept.
This method adds a concept to the ConceptManager. It does not immediately
have any effect on the command's argparse parser.
Args:
concept: base.Concept, an instantiated concept.
"""
self.concepts.append(concept)
def AddToParser(self, parser):
"""Adds concept arguments and concept RuntimeParser to argparse parser.
For each concept, the Attribute() method is called, and all resulting
attributes and attribute groups are translated into arguments for the
argparse parser.
Additionally, a concept-specific RuntimeParser is created with all of the
resulting attributes from the first step. (This will be responsible for
parsing the concepts.) It is registered to the argparse parser, and will
appear in the final parsed namespace under CONCEPT_ARGS.
Args:
parser: googlecloudsdk.calliope.parser_arguments.ArgumentInterceptor, the
argparse parser to which to add argparse arguments.
"""
attributes = [concept.Attribute() for concept in self.concepts]
self._AddToArgparse(attributes, parser)
self.runtime_parser = RuntimeParser(attributes)
parser.add_concepts(self.runtime_parser)
def _AddToArgparse(self, attributes, parser):
"""Recursively add an arg definition to the parser."""
for attribute in attributes:
if isinstance(attribute, base.Attribute):
parser.add_argument(attribute.arg_name, **attribute.kwargs)
continue
group = parser.add_argument_group(attribute.kwargs.pop('help'),
**attribute.kwargs)
self._AddToArgparse(attribute.attributes, group)
class RuntimeParser(object):
"""An object to manage parsing all concepts via their attributes.
Once argument parsing is complete and ParseConcepts is called, each parsed
concept is stored on this runtime parser as an attribute, named after the
name of that concept.
Attributes:
parsed_args: the argparse namespace after arguments have been parsed.
<CONCEPT_NAME> (the namespace format of each top level concept, such as
"foo_bar"): the parsed concept corresponding to that name.
"""
def __init__(self, attributes):
self.parsed_args = None
self._attributes = {}
for attribute in attributes:
attr_name = names.ConvertToNamespaceName(
attribute.concept.GetPresentationName())
if attr_name in self._attributes:
raise ValueError('Attempting to add two concepts with the same '
'presentation name: [{}]'.format(attr_name))
self._attributes[attr_name] = attribute
def ParseConcepts(self):
"""Parse all concepts.
Stores the result of parsing concepts, keyed to the namespace format of
their presentation name. Afterward, will be accessible as
args.<LOWER_SNAKE_CASE_NAME>.
Raises:
googlecloudsdk.command_lib.concepts.exceptions.Error: if parsing fails.
"""
# Collect all final parse values in final before assigning back to args
# because multiple FinalParse calls may use an attr_name multiple times.
final = {}
for attr_name, attribute in six.iteritems(self._attributes):
dependencies = dependency_managers.DependencyNode.FromAttribute(attribute)
final[attr_name] = FinalParse(dependencies, self.ParsedArgs)
# Set the final parsed name=value in the args namespace. add_argument(),
# either called explicitly, or via the concept manager, detects duplicate
# names and raises an exception before this method is called.
for name, value in six.iteritems(final):
setattr(self.parsed_args, name, value)
def ParsedArgs(self):
"""A lazy property to use during concept parsing.
Returns:
googlecloudsdk.calliope.parser_extensions.Namespace: the parsed argparse
namespace | None, if the parser hasn't been registered to the namespace
yet.
"""
return self.parsed_args
def FinalParse(dependencies, arg_getter):
"""Lazy parser stored under args.CONCEPT_ARGS.
Args:
dependencies: dependency_managers.DependencyNode, the root of the tree of
the concept's dependencies.
arg_getter: Callable, a function that returns the parsed args namespace.
Raises:
googlecloudsdk.command_lib.concepts.exceptions.Error: if parsing fails.
Returns:
the result of parsing the root concept.
"""
dependency_manager = dependency_managers.DependencyManager(dependencies)
parsed_args = arg_getter()
return dependency_manager.ParseConcept(parsed_args)