text: from __future__ import print_function, unicode_literals
  
  from os import getcwd, path, makedirs
  
  import yaml
  import click
  
  from algen.compilers import ModelCompiler
  
  __author__ = "danishabdullah"
  
  
  class DirectoryCreationException(Exception):
      pass
  
  
  class FileNotFound(Exception):
      pass
  
  
  class InvalidModelDefinition(Exception):
      pass
  
  
  def check_or_create_models_dir(destination=None):
      dir = destination or "{}/models".format(getcwd())
      if not path.exists(dir):
          click.echo("Didn't find {}. Creating now.".format(dir))
          try:
              makedirs(dir)
          except Exception as e:
              raise DirectoryCreationException
      return dir
  
  
  def create_col_def_dict_from_cli_input(col_input):
      col_name, col_type = col_input.split(':')
      return {'name': col_name, 'type': col_type}
  
  
  def parse_cli_columns(name, columns):
      column_defs = [create_col_def_dict_from_cli_input(n) for n in columns]
      res = {name: {'columns': column_defs}}
      ensure_names(res)
      ensure_types(res)
      return res
  
  
  def ensure_names(model_defs):
      for model_def in model_defs.values():
          for column in model_def['columns']:
              if not column.get('name', None):
                  raise InvalidModelDefinition("Missing 'name")
  
  
  def ensure_types(model_defs):
      for model_def in model_defs.values():
          for column in model_def['columns']:
              if not column.get('type', None):
                  raise InvalidModelDefinition("Missing 'type")
  
  
  def ensure_foreign_keys(model_defs):
      for model_def in model_defs.values():
          for column in model_def.get('foreign_keys', []):
              if not column.get('name', None):
                  raise InvalidModelDefinition("Missing 'name")
              if not column.get('type', None):
                  raise InvalidModelDefinition("Missing 'type")
              reference = column.get('reference', None)
              if not reference:
                  raise InvalidModelDefinition("Missing 'reference' from foreign key")
              assert isinstance(reference, dict)
              if not reference.get('table', None):
                  raise InvalidModelDefinition("Missing 'reference.name' from foreign key")
              if not reference.get('column', None):
                  raise InvalidModelDefinition("Missing 'reference.column' from foreign key")
  
  
  def ensure_relationships(model_defs):
      for model_def in model_defs.values():
          for column in model_def.get('relationships', []):
              if not column.get('name', None):
                  raise InvalidModelDefinition("Missing 'name")
              if not column.get('class', None):
                  raise InvalidModelDefinition("Missing 'class' from relationship")
  
  
  def parse_yaml(pth):
      if not path.exists(pth):
          raise FileNotFound
  
      with open(pth, 'r') as fyle:
          model_defs = yaml.load(fyle)
      assert isinstance(model_defs, dict)
      ensure_names(model_defs)
      ensure_types(model_defs)
      ensure_foreign_keys(model_defs)
      ensure_relationships(model_defs)
      return model_defs
  
  
  def get_model_defs(name=None, columns=None, yaml=None):
      if yaml:
          model_defs = parse_yaml(yaml)
      else:
          model_defs = parse_cli_columns(name, columns)
      return model_defs
  
  
  @click.command()
  @click.option('--name', '-n', help='Name of model', type=str)
  @click.option('--columns', '-c',
                help=("Column definition. e.g. col_name:col_type"
                      " Can be used multiple times hence named "
                      "columns. e.g. -c foo:Int -c bar:Unicode(20)"),
                type=str, multiple=True)
  @click.option('--destination', '-d', help=("Destination directory. Default will "
                                             "assume 'models' directory inside the"
                                             " current working directory"),
                type=click.Path(exists=True))
  @click.option('--yaml', '-y', help=("Yaml file describing the Model. This "
                                      "supersedes the column definition provided "
                                      "through --columns option."),
                type=click.Path(exists=True))
  def cli(name, columns, destination, yaml):
      click.echo('Creating Models with the following options:\n'
                 '  --name:{}\n  --columns:{}\n  --destination:{}\n'
                 '  --yaml:{}'.format(name, columns, destination, yaml))
      if destination:
          if destination.endswith('/'):
              destination = destination[:len(destination) - 1]
              click.echo('Trimmed destination to {}'.format(destination))
      try:
          # destination cannot be created or accessed
          destination = check_or_create_models_dir(destination=destination)
      except DirectoryCreationException as e:
          click.echo("An error happened while trying to access/create the 'models' "
                     "directory. Please make sure that you have the "
                     "appropriate ownership and right provided by the os.")
          return
      if not (columns or yaml):
          click.echo("You must provide at least one of --columns or --yaml")
          return
      try:
          # invalid definitions
          if yaml:
              if columns:
                  click.echo("Ignoring columns provided through cli since a yaml"
                             " file was also provided")
              try:
                  # not found errors
                  model_defs = get_model_defs(yaml=yaml)
              except FileNotFound:
                  click.echo("The yaml file does not exist. Exiting!")
                  return
          else:
              if not name:
                  click.echo("Model must have a name!")
                  return
              model_defs = get_model_defs(columns=columns, name=name)
      except InvalidModelDefinition:
          click.echo('All columns must have a name!')
          return
      for name, column_defs in model_defs.items():
          model = ModelCompiler(name, column_defs).compiled_model
          filename = "{}/{}.py".format(destination, ModelCompiler.convert_case(name))
          try:
              # file is not writeable
              with open(filename, 'w') as fyle:
                  click.echo("Writing {} to {}".format(name, filename))
                  fyle.write(model)
          except IOError:
              click.echo("The following python model was generated:\n\n{}"
                         "Cannot write model  to {}. Make sure the "
                         "destination is writable.\n"
                         .format(name, filename))
  
  
  if __name__ == '__main__':
      cli()
  