text: # coding: utf-8
  #
  # Copyright 2014 The Oppia Authors. 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 relating to value generators."""
  
  from __future__ import absolute_import  # pylint: disable=import-only-modules
  from __future__ import unicode_literals  # pylint: disable=import-only-modules
  
  import copy
  import inspect
  import os
  import pkgutil
  
  import feconf
  import python_utils
  import utils
  
  
  class BaseValueGenerator(python_utils.OBJECT):
      """Base value generator class.
  
      A value generator is a class containing a function that takes in
      customization args and uses them to generate a value. The generated values
      are not typed, so if the caller wants strongly-typed values it would need
      to normalize the output of each generator.
  
      Each value generator should define a template file and an AngularJS
      directive. The names of these two files should be [ClassName].html and
      [ClassName].js respectively, where [ClassName] is the name of the value
      generator class.
      """
  
      @property
      def id(self):
          """Returns the Class name as a string, i.e "BaseValueGenerator".
  
          Returns:
              str. Class name i.e "BaseValueGenerator".
          """
          return self.__class__.__name__
  
      @classmethod
      def get_html_template(cls):
          """Returns the HTML template for the class.
  
          Returns:
              str. The HTML template corresponding to the class.
          """
          return utils.get_file_contents(os.path.join(
              os.getcwd(), feconf.VALUE_GENERATORS_DIR, 'templates',
              '%s.html' % cls.__name__))
  
      def generate_value(self, *args, **kwargs):
          """Generates a new value, using the given customization args.
  
          The first arg should be context_params.
          """
          raise NotImplementedError
  
  
  class Registry(python_utils.OBJECT):
      """Maintains a registry of all the value generators.
  
      Attributes:
          value_generators_dict: dict(str : BaseValueGenerator). Dictionary
              mapping value generator class names to their classes.
      """
  
      value_generators_dict = {}
  
      @classmethod
      def _refresh_registry(cls):
          """Refreshes the dictionary mapping between generator_id and the
          corresponding generator classes.
          """
          cls.value_generators_dict.clear()
  
          # Assemble all generators in
          # extensions/value_generators/models/generators.py.
          value_generator_paths = [os.path.join(
              os.getcwd(), feconf.VALUE_GENERATORS_DIR, 'models')]
  
          # Crawl the directories and add new generator instances to the
          # registries.
          for loader, name, _ in pkgutil.iter_modules(
                  path=value_generator_paths):
              if name.endswith('_test'):
                  continue
              module = loader.find_module(name).load_module(name)
              for _, clazz in inspect.getmembers(
                      module, predicate=inspect.isclass):
                  if issubclass(clazz, BaseValueGenerator):
                      cls.value_generators_dict[clazz.__name__] = clazz
  
      @classmethod
      def get_all_generator_classes(cls):
          """Get the dict of all value generator classes."""
          cls._refresh_registry()
          return copy.deepcopy(cls.value_generators_dict)
  
      @classmethod
      def get_generator_class_by_id(cls, generator_id):
          """Gets a generator class by its id.
  
          Refreshes once if the generator is not found; subsequently, throws an
          error.
  
          Args:
              generator_id: str. An id corresponding to a generator class.
  
          Returns:
              class(BaseValueGenerator). A generator class mapping to the
              generator id given.
  
          Raises:
              KeyError: The given generator_id is invalid.
          """
          if generator_id not in cls.value_generators_dict:
              cls._refresh_registry()
          return cls.value_generators_dict[generator_id]
  