docstr: Define the SQLAlchemy ORM class for each table in the ModelHub database.
  
  These must be defined after the Database class is initialized so that
  the database metadata is available (at runtime).
  If the database does not already exist, it will be created. If it does
  exist, it will not be updated with new schema -- after schema changes,
  the database must be destroyed and reinialized.
docstr_tok: ["Define","the","SQLAlchemy","ORM","class","for","each","table","in","the","ModelHub","database",".","These","must","be","defined","after","the","Database","class","is","initialized","so","that","the","database","metadata","is","available","(","at","runtime",")",".","If","the","database","does","not","already","exist",",","it","will","be","created",".","If","it","does","exist",",","it","will","not","be","updated","with","new","schema","--","after","schema","changes",",","the","database","must","be","destroyed","and","reinialized","."]
code: def _define_tables(self):
      """
      Define the SQLAlchemy ORM class for each table in the ModelHub database.
  
      These must be defined after the Database class is initialized so that
      the database metadata is available (at runtime).
      If the database does not already exist, it will be created. If it does
      exist, it will not be updated with new schema -- after schema changes,
      the database must be destroyed and reinialized.
      """
      metadata = MetaData(bind=self.engine)
      Base = declarative_base(metadata=metadata)
      db = self
  
      class Dataset(Base):
          __tablename__ = "datasets"
  
          id = Column(Integer, primary_key=True, autoincrement=True)
          name = Column(String(100), nullable=False)
  
          # columns necessary for loading/processing data
          class_column = Column(String(100), nullable=False)
          train_path = Column(String(200), nullable=False)
          test_path = Column(String(200))
          description = Column(String(1000))
  
          # metadata columns, for convenience
          n_examples = Column(Integer, nullable=False)
          k_classes = Column(Integer, nullable=False)
          d_features = Column(Integer, nullable=False)
          majority = Column(
              Numeric(precision=10, scale=9), nullable=False
          )
          size_kb = Column(Integer, nullable=False)
  
          def load(
              self,
              test_size=0.3,
              random_state=0,
              aws_access_key=None,
              aws_secret_key=None,
          ):
              data = load_data(
                  self.name,
                  self.train_path,
                  aws_access_key,
                  aws_secret_key,
              )
  
              if self.test_path:
                  if self.name.endswith(".csv"):
                      test_name = self.name.replace(".csv", "_test.csv")
                  else:
                      test_name = self.name + "_test"
  
                  test_data = load_data(
                      test_name,
                      self.test_path,
                      aws_access_key,
                      aws_secret_key,
                  )
                  return data, test_data
  
              else:
                  return train_test_split(
                      data,
                      test_size=test_size,
                      random_state=random_state,
                  )
  
          def _add_extra_fields(
              self, aws_access_key=None, aws_secret_key=None
          ):
              data = load_data(
                  self.name,
                  self.train_path,
                  aws_access_key,
                  aws_secret_key,
              )
  
              if self.n_examples is None:
                  self.n_examples = len(data)
  
              if self.k_classes is None:
                  self.k_classes = len(
                      np.unique(data[self.class_column])
                  )
  
              if self.d_features is None:
                  total_features = data.shape[1] - 1
                  for column in data.columns:
                      if data[column].dtype == "object":
                          total_features += (
                              len(np.unique(data[column])) - 1
                          )
  
                  self.d_features = total_features
  
              if self.majority is None:
                  counts = data[self.class_column].value_counts()
                  self.majority = float(max(counts)) / float(
                      sum(counts)
                  )
  
              if self.size_kb is None:
                  self.size_kb = int(np.array(data).nbytes / 1000)
  
          @staticmethod
          def _make_name(path):
              md5 = hashlib.md5(path.encode("utf-8"))
              return md5.hexdigest()
  
          def __init__(
              self,
              train_path,
              test_path=None,
              name=None,
              description=None,
              class_column=None,
              n_examples=None,
              majority=None,
              k_classes=None,
              size_kb=None,
              d_features=None,
              id=None,
              aws_access_key=None,
              aws_secret_key=None,
          ):
              self.train_path = train_path
              self.test_path = test_path
              self.name = name or self._make_name(train_path)
              self.description = description or self.name
              self.class_column = class_column
              self.id = id
  
              self.n_examples = n_examples
              self.d_features = d_features
              self.majority = majority
              self.k_classes = k_classes
              self.size_kb = size_kb
  
              self._add_extra_fields(aws_access_key, aws_secret_key)
  
          def __repr__(self):
              base = "<%s: %s, %d classes, %d features, %d rows>"
              return base % (
                  self.name,
                  self.description,
                  self.k_classes,
                  self.d_features,
                  self.n_examples,
              )
  
      class Datarun(Base):
          __tablename__ = "dataruns"
  
          # relational columns
          id = Column(Integer, primary_key=True, autoincrement=True)
          dataset_id = Column(Integer, ForeignKey("datasets.id"))
          dataset = relationship("Dataset", back_populates="dataruns")
  
          description = Column(String(200), nullable=False)
          priority = Column(Integer)
  
          # hyperparameter selection and tuning settings
          selector = Column(String(200), nullable=False)
          k_window = Column(Integer)
          tuner = Column(String(200), nullable=False)
          gridding = Column(Integer, nullable=False)
          r_minimum = Column(Integer)
  
          # budget settings
          budget_type = Column(Enum(*BUDGET_TYPES))
          budget = Column(Integer)
          deadline = Column(DateTime)
  
          # which metric to use for judgment, and how to compute it
          metric = Column(Enum(*METRICS))
          score_target = Column(
              Enum(*[s + "_judgment_metric" for s in SCORE_TARGETS])
          )
  
          # variables that store the status of the datarun
          start_time = Column(DateTime)
          end_time = Column(DateTime)
          status = Column(
              Enum(*DATARUN_STATUS), default=RunStatus.PENDING
          )
  
          def __repr__(self):
              base = "<ID = %d, dataset ID = %s, strategy = %s, budget = %s (%s), status: %s>"
              return base % (
                  self.id,
                  self.dataset_id,
                  self.description,
                  self.budget_type,
                  self.budget,
                  self.status,
              )
  
          @property
          def completed_classifiers(self):
              return len(self.get_complete_classifiers())
  
          def get_scores(self):
              columns = [
                  "id",
                  "cv_judgment_metric",
                  "cv_judgment_metric_stdev",
                  "test_judgment_metric",
              ]
  
              classifiers = db.get_classifiers(datarun_id=self.id)
              scores = [
                  {
                      key: value
                      for key, value in vars(classifier).items()
                      if key in columns
                  }
                  for classifier in classifiers
              ]
  
              scores = pd.DataFrame(scores)
              scores.sort_values(
                  by="cv_judgment_metric", ascending=False, inplace=True
              )
              scores["rank"] = scores["cv_judgment_metric"].rank(
                  ascending=0
              )
  
              return scores.reset_index(drop=True)
  
          def get_best_classifier(self):
              return db.get_best_classifier(
                  self.score_target, datarun_id=self.id
              )
  
          def get_complete_classifiers(self):
              return db.get_classifiers(
                  datarun_id=self.id, status=ClassifierStatus.COMPLETE
              )
  
          def export_best_classifier(self, path, force=False):
              if os.path.exists(path) and not force:
                  print(
                      "The indicated path already exists. Use `force=True` to overwrite."
                  )
  
              base_path = os.path.dirname(path)
              if base_path and not os.path.exists(base_path):
                  os.makedirs(base_path)
  
              classifier = self.get_best_classifier()
              model = classifier.load_model()
              with open(path, "wb") as pickle_file:
                  pickle.dump(model, pickle_file)
  
              print(
                  "Classifier {} saved as {}".format(
                      classifier.id, path
                  )
              )
  
          def describe(self):
              dataset = db.get_dataset(self.dataset_id)
  
              elapsed = (
                  self.end_time - self.start_time
                  if self.end_time
                  else "Not finished yet."
              )
  
              to_print = [
                  "Datarun {} summary:".format(self.id),
                  "\tDataset: '{}'".format(dataset.train_path),
                  "\tColumn Name: '{}'".format(dataset.class_column),
                  "\tJudgment Metric: '{}'".format(self.metric),
                  "\tClassifiers Tested: {}".format(
                      len(db.get_classifiers(datarun_id=self.id))
                  ),
                  "\tElapsed Time: {}".format(elapsed),
              ]
  
              print("\n".join(to_print))
  
      Dataset.dataruns = relationship(
          "Datarun", order_by="Datarun.id", back_populates="dataset"
      )
  
      class Hyperpartition(Base):
          __tablename__ = "hyperpartitions"
  
          # relational columns
          id = Column(Integer, primary_key=True, autoincrement=True)
          datarun_id = Column(Integer, ForeignKey("dataruns.id"))
          datarun = relationship(
              "Datarun", back_populates="hyperpartitions"
          )
  
          # name of or path to a configured classification method
          method = Column(String(255))
  
          # list of categorical parameters whose values are fixed to define
          # this hyperpartition
          categorical_hyperparameters_64 = Column(Text)
  
          # list of continuous parameters which are not fixed; their values
          # must be selected by a Tuner
          tunable_hyperparameters_64 = Column(Text)
  
          # list of categorical or continuous parameters whose values are
          # always fixed. These do not define the hyperpartition, but their
          # values must be passed on to the method. Here for convenience.
          constant_hyperparameters_64 = Column(Text)
  
          # has the partition had too many errors, or is gridding done?
          status = Column(
              Enum(*PARTITION_STATUS),
              default=PartitionStatus.INCOMPLETE,
          )
  
          @property
          def categoricals(self):
              """
              A list of categorical variables along with the fixed values
              which define this hyperpartition.
              Each element is a ('name', HyperParameter) tuple.
              """
              return base_64_to_object(
                  self.categorical_hyperparameters_64
              )
  
          @categoricals.setter
          def categoricals(self, value):
              self.categorical_hyperparameters_64 = object_to_base_64(
                  value
              )
  
          @property
          def tunables(self):
              """
              A list of parameters which are unspecified and must be selected
              with a Tuner. Each element is a ('name', HyperParameter) tuple.
              """
              return base_64_to_object(self.tunable_hyperparameters_64)
  
          @tunables.setter
          def tunables(self, value):
              self.tunable_hyperparameters_64 = object_to_base_64(value)
  
          @property
          def constants(self):
              return base_64_to_object(self.constant_hyperparameters_64)
  
          @constants.setter
          def constants(self, value):
              self.constant_hyperparameters_64 = object_to_base_64(
                  value
              )
  
          def __repr__(self):
              return "<%s: %s>" % (self.method, self.categoricals)
  
      Datarun.hyperpartitions = relationship(
          "Hyperpartition",
          order_by="Hyperpartition.id",
          back_populates="datarun",
      )
  
      class Classifier(Base):
          __tablename__ = "classifiers"
  
          # relational columns
          id = Column(Integer, primary_key=True, autoincrement=True)
          datarun_id = Column(Integer, ForeignKey("dataruns.id"))
          datarun = relationship(
              "Datarun", back_populates="classifiers"
          )
          hyperpartition_id = Column(
              Integer, ForeignKey("hyperpartitions.id")
          )
          hyperpartition = relationship(
              "Hyperpartition", back_populates="classifiers"
          )
  
          # name of the host where the model was trained
          host = Column(String(50))
  
          # these columns point to where the output is stored
          model_location = Column(String(300))
          metrics_location = Column(String(300))
  
          # base 64 encoding of the hyperparameter names and values
          hyperparameter_values_64 = Column(Text, nullable=False)
  
          # performance metrics
          cv_judgment_metric = Column(Numeric(precision=20, scale=10))
          cv_judgment_metric_stdev = Column(
              Numeric(precision=20, scale=10)
          )
          test_judgment_metric = Column(Numeric(precision=20, scale=10))
  
          start_time = Column(DateTime)
          end_time = Column(DateTime)
          status = Column(Enum(*CLASSIFIER_STATUS), nullable=False)
          error_message = Column(Text)
  
          @property
          def hyperparameter_values(self):
              return base_64_to_object(self.hyperparameter_values_64)
  
          @hyperparameter_values.setter
          def hyperparameter_values(self, value):
              self.hyperparameter_values_64 = object_to_base_64(value)
  
          @property
          def mu_sigma_judgment_metric(self):
              # compute the lower confidence bound on the cross-validated
              # judgment metric
              if self.cv_judgment_metric is None:
                  return None
              return (
                  self.cv_judgment_metric
                  - 2 * self.cv_judgment_metric_stdev
              )
  
          def __repr__(self):
              params = "\n".join(
                  [
                      "\t{}: {}".format(name, value)
                      for name, value in self.hyperparameter_values.items()
                  ]
              )
  
              to_print = [
                  "Classifier id: {}".format(self.id),
                  "Classifier type: {}".format(
                      db.get_hyperpartition(
                          self.hyperpartition_id
                      ).method
                  ),
                  "Params chosen: \n{}".format(params),
                  "Cross Validation Score: {:.3f} +- {:.3f}".format(
                      self.cv_judgment_metric,
                      self.cv_judgment_metric_stdev,
                  ),
                  "Test Score: {:.3f}".format(
                      self.test_judgment_metric
                  ),
              ]
  
              return "\n".join(to_print)
  
          def load_s3_data(
              self, s3_url, aws_access_key=None, aws_secret_key=None
          ):
              """Returns raw data from S3"""
  
              client = boto3.client(
                  "s3",
                  aws_access_key_id=aws_access_key,
                  aws_secret_access_key=aws_secret_key,
              )
  
              bucket = s3_url.split("/")[2]
              path_to_read = s3_url.replace(
                  "s3://{}/".format(bucket), ""
              )
  
              with BytesIO() as data:
                  client.download_fileobj(bucket, path_to_read, data)
                  data.seek(0)
                  return data
  
          def load_model(self):
              """Return the model's insntance."""
              if self.model_location.startswith("s3"):
                  pickled = self.load_s3_data(
                      self.model_location,
                      self.aws_access_key,
                      self.aws_secret_key,
                  )
                  return pickle.load(pickled)
  
              else:
                  with open(self.model_location, "rb") as f:
                      return pickle.load(f)
  
          def load_metrics(self):
              """Return the metrics"""
              if self.metrics_location.startswith("s3"):
                  pickled = self.load_s3_data(
                      self.metrics_location,
                      self.aws_access_key,
                      self.aws_secret_key,
                  )
                  return json.load(pickled)
  
              else:
                  with open(self.metrics_location, "rb") as f:
                      return json.load(f)
  
      Datarun.classifiers = relationship(
          "Classifier",
          order_by="Classifier.id",
          back_populates="datarun",
      )
  
      Hyperpartition.classifiers = relationship(
          "Classifier",
          order_by="Classifier.id",
          back_populates="hyperpartition",
      )
  
      self.Dataset = Dataset
      self.Datarun = Datarun
      self.Hyperpartition = Hyperpartition
      self.Classifier = Classifier
  
      Base.metadata.create_all(bind=self.engine)
  
imports: from __future__ import absolute_import, unicode_literals
  import hashlib
  import json
  import os
  import pickle
  from builtins import object
  from datetime import datetime
  from io import BytesIO
  from operator import attrgetter
  import boto3
  import numpy
  import pandas
  import pymysql
  from sklearn.model_selection import train_test_split
  from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, MetaData, Numeric, String, Text, and_, create_engine, func, inspect
  from sqlalchemy.engine.url import URL
  from sqlalchemy.ext.declarative import declarative_base
  from sqlalchemy.orm import relationship, sessionmaker
  from sqlalchemy.orm.properties import ColumnProperty
  from atm.constants import BUDGET_TYPES, CLASSIFIER_STATUS, DATARUN_STATUS, METRICS, PARTITION_STATUS, SCORE_TARGETS, ClassifierStatus, PartitionStatus, RunStatus
  from atm.data import load_data
  from atm.utilities import base_64_to_object, object_to_base_64
label_desc: Use image node_id 2 for calling the Database obj's underlying member method code with example usage: obj._define_tables() and returns: md5, unknown, unknown, len, scores, db, db, base_64_to_object, base_64_to_object, base_64_to_object, unknown, base_64_to_object, unknown, str, data,  test_data, train_test_split, None, data, pickle, json, pickle, json