Exemple #1
0
    def get_many_one(self, id: int, parent: str, child: str):
        """Retrieve the many to many relationship data of a parent and child.

        Args:
            id (int): Given ID of type parent
            parent (str): Type of parent
            child (str): Type of child
        """
        join_table = JuncHolder.lookup_table(parent, child)

        # This should not be reachable
        # if join_table is None:
        # return {'error': f"relationship '{child}' of '{parent}' not found."}
        try:
            session = Session()
            parent_col_str = f"{parent}_id"
            child_col_str = f"{child}_id"

            cols = {parent_col_str: id}
            query = session.query(join_table).filter_by(**cols).all()

            children = []
            for row in query:
                # example - {'programs_id': 2, 'credentials_id': 3}
                row_dict = row._asdict()
                children.append(row_dict[child_col_str])

        except Exception:
            raise InternalServerError()

        finally:
            session.close()

        return {f"{child}": children}, 200
Exemple #2
0
    def get_stored_descriptors(self) -> list:
        """Gets stored json models from database.

        Returns:
            list: List of JSON dictionaries
        """
        session = Session()
        descriptor_list = []  # list of json dict
        try:
            query = session.query(Checksum)
            for _row in query.all():
                try:
                    if not _row.descriptor_json:
                        continue
                except Exception:
                    logger.exception(
                        "Checksum table or row does not have a value for the descriptor_json column."
                    )

                descriptor_list.append(_row.descriptor_json)

        except Exception:
            logger.info("Error retrieving stored models")
        finally:
            session.close()

        return descriptor_list
Exemple #3
0
    def patch_many_one(self, id: int, parent: str, child: str, values):
        """put data for a many to many relationship of a parent and child.

        Args:
            id (int): Given ID of type parent
            parent (str): Type of parent
            child (str): Type of child
            values (list or int): list of values to patch
        """
        try:
            session = Session()

            many_query = []
            junc_table = JuncHolder.lookup_table(parent, child)

            if junc_table is not None:
                if not isinstance(values, list):
                    values = [values]
                many_query.append([child, values, junc_table])

            for field, values, table in many_query:
                # TODO this should only insert when it doesnt already exist
                self.process_many_query(session, table, id, field, parent,
                                        values)

        except Exception:
            raise InternalServerError()

        finally:
            session.close()

        return self.get_many_one(id, parent, child)
Exemple #4
0
    def delete_many_one(self, id: int, parent: str, child: str, values):
        try:
            session = Session()
            junc_table = JuncHolder.lookup_table(parent, child)

            if not isinstance(values, list):
                values = [values]

            for value in values:
                parent_col = getattr(junc_table.c, f"{parent}_id")
                child_col = getattr(junc_table.c, f"{child}_id")
                del_st = junc_table.delete().where(
                    and_(parent_col == id, child_col == value))

                res = session.execute(del_st)
                print(res)
                session.commit()

        except Exception:
            session.rollback()
            raise InternalServerError()

        finally:
            session.close()

        return self.get_many_one(id, parent, child)
Exemple #5
0
 def get_migrations_from_db_and_save_locally(self) -> None:
     logger.info("Restoring migration files from DB...")
     session = Session()
     try:
         query = session.query(Migrations)
         for _row in query.all():
             self.save_migrations_to_local_file(_row.file_name, _row.file_blob)
     except Exception:
         logger.info("Failed to restore migration files from DB.")
     finally:
         session.close()
Exemple #6
0
    def query(
        self,
        data_model,
        data_resource_name,
        restricted_fields,
        table_schema,
        request_obj,
    ):
        """Query the data resource."""

        try:
            request_obj = request_obj.json
        except Exception:
            raise ApiError("No request body found.", 400)

        errors = []
        _ = Schema(table_schema)
        accepted_fields = []
        response = OrderedDict()
        response["results"] = []
        if validate(table_schema):
            for field in table_schema["fields"]:
                if field["name"] not in restricted_fields:
                    accepted_fields.append(field["name"])
            for field in request_obj.keys():
                if field not in accepted_fields:
                    errors.append(
                        "Unknown or restricted field '{}' found.".format(
                            field))
            if len(errors) > 0:
                raise ApiUnhandledError("Invalid request body.", 400, errors)
            else:
                try:
                    session = Session()
                    results = session.query(data_model).filter_by(
                        **request_obj)
                    for row in results:
                        response["results"].append(
                            self.build_json_from_object(
                                row, restricted_fields))

                    if len(response["results"]) == 0:
                        return {"message": "No matches found"}, 404
                    else:
                        return response, 200
                except Exception:
                    raise ApiUnhandledError("Failed to create new resource.",
                                            400)
                finally:
                    session.close()
        else:
            raise SchemaValidationFailure()

        return {"message": "querying data resource"}, 200
Exemple #7
0
    def get_stored_checksums(self) -> list:
        session = Session()
        checksums = []  # list of json dict
        try:
            query = session.query(Checksum)
            for _row in query.all():
                checksums.append((_row.data_resource, _row.model_checksum))
        except Exception:
            logger.exception("Error retrieving stored models")
        finally:
            session.close()

        return checksums
    def initalize_base_models(self):
        self.logger.info("Initalizing base models...")

        db_active = False
        max_retries = 10
        retries = 0

        exponential_time = exponential_backoff(1, 1.5)

        while not db_active and retries <= max_retries:
            if retries != 0:
                sleep_time = exponential_time()
                self.logger.info(
                    f"Sleeping for {sleep_time} with exponential backoff...")
                sleep(sleep_time)

            retries += 1

            self.logger.info("Checking database availability...")
            try:
                self.logger.info("Looking for checksum...")
                session = Session()
                _ = session.query(Checksum).all()
                db_active = True
                self.logger.info("Successfully found checksum.")
            except Exception as e:
                self.logger.info("Hit exception looking for checksum...")
                # UndefinedTable
                if e.code == "f405":
                    self.logger.info(
                        "Checksum table was not found; Running inital migration..."
                    )
                    # The migration file that describes checksums, logs, and migrations
                    # is present in the migrations folder.
                    self.db.upgrade()

                    db_active = True
                    self.logger.info("Successfully ran upgrade.")

                elif e.code == "e3q8":
                    self.logger.info(
                        "Database is not available yet exception.")
                    self.logger.info(
                        "Waiting on database to become available.... {}/{}".
                        format(retries, max_retries))
                else:
                    self.logger.exception(f"Error occured upgrading database.")
            finally:
                session.close()

        self.logger.info("Base models initalized.")
Exemple #9
0
    def update_model_checksum(
        self, table_name: str, model_checksum: str, descriptor_json: dict = {}
    ):
        """Updates a checksum for a data model.

        Args:
            table_name (str): Name of the table to add the checksum.
            checksum (str): Checksum value.

        Returns:
            bool: True if checksum was updated. False otherwise.
        """
        session = Session()
        updated = False
        try:
            checksum = (
                session.query(Checksum)
                .filter(Checksum.data_resource == table_name)
                .first()
            )
            checksum.model_checksum = model_checksum
            checksum.descriptor_json = descriptor_json
            session.commit()
            updated = True
        except Exception:
            logger.exception("Error updating checksum")
        finally:
            session.close()
        return updated
    def wait_for_db(self):
        db_active = False
        max_retries = 10
        retries = 0

        exponential_time = exponential_backoff(1, 1.5)

        while not db_active and retries <= max_retries:
            if retries != 0:
                sleep_time = exponential_time()
                self.logger.info(
                    f"Sleeping for {sleep_time} with exponential backoff...")
                sleep(sleep_time)

            retries += 1

            self.logger.info("Checking database availability...")
            try:
                self.logger.info("Looking for DB...")
                session = Session()
                _ = session.query(Checksum).all()
                db_active = True
                self.logger.info("Successfully connected to DB.")
            except Exception as e:
                self.logger.info("Hit exception looking for checksum...")
                # UndefinedTable
                if e.code == "f405":
                    # TODO this is a race condition with DMM?
                    db_active = True
                    self.logger.info("Successfully connected to DB.")

                elif e.code == "e3q8":
                    self.logger.info(
                        "Database is not available yet exception.")
                    self.logger.info(
                        "Waiting on database to become available.... {}/{}".
                        format(retries, max_retries))
                else:
                    self.logger.exception(f"Error occured upgrading database.")
            finally:
                session.close()

        self.logger.info("Connected to DB.")
def check_for_checksum_column():
    session = Session()
    query = """
    SELECT 1
    FROM information_schema.columns
    WHERE table_name='checksums' and column_name='descriptor_json'
    """
    rs = session.execute(query)
    session.close()

    count = 0
    for _row in rs:
        count += 1

    if count == 1:
        print("Found the descriptor_json column -- skipping import")
        return True

    return False
def check_for_migrations_table():
    session = Session()
    query = """
    SELECT *
    FROM information_schema.tables
    WHERE table_name='migrations';
    """
    rs = session.execute(query)
    session.close()

    count = 0
    for _row in rs:
        count += 1

    if count == 1:
        print("Found the migrations table -- skipping import")
        return True

    return False
def upgrade_checksum():
    session = Session()
    query = """
    ALTER TABLE checksums ADD COLUMN descriptor_json jsonb;
    """
    try:
        session.execute(query)
        session.commit()
    except BaseException:
        logger.exception("Failed to upgrade checksum")
    finally:
        session.close()
def is_migrations_loaded():
    session = Session()
    result = session.query(Migrations).count()
    logger.info(f"Found {result} rows in migrations table")
    if result == 0:
        session.close()
        return False
    session.close()
    return True
def push_descriptors():
    session = Session()

    descriptors = DescriptorsLoader([SCHEMA_DIR], [])
    for desc in descriptors.iter_descriptors():
        try:
            row = (session.query(Checksum).filter(
                Checksum.data_resource == desc.table_name).first())

            row.descriptor_json = desc.descriptor
            session.add(row)
            session.commit()
        except Exception:
            logger.exception("Error pushing descriptors")
            continue
        finally:
            session.close()
Exemple #16
0
    def get_model_checksum(self, table_name: str):
        """Retrieves a checksum by table name.

        Args:
            table_name (str): Name of the table to add the checksum.

        Returns:
            object: The checksum object if it exists, None otherwise.
        """
        session = Session()
        checksum = None
        try:
            checksum = (
                session.query(Checksum)
                .filter(Checksum.data_resource == table_name)
                .first()
            )
        except Exception:
            logger.exception("Error retrieving checksum")
        finally:
            session.close()
        return checksum
def create_migrations():
    session = Session()
    query = """
    CREATE TABLE migrations (
        file_name TEXT PRIMARY KEY,
        file_blob BYTEA
    );
    """
    try:
        session.execute(query)
        session.commit()
    except BaseException:
        logger.exception("Failed to create table migrations")
    finally:
        session.close()
Exemple #18
0
 def emit(self, record):
     session = Session()
     trace = None
     exc = record.__dict__["exc_info"]
     if exc:
         trace = traceback.format_exc()
     log = Log(
         logger=record.__dict__["name"],
         level=record.__dict__["levelname"],
         trace=trace,
         msg=record.__dict__["msg"],
     )
     session.add(log)
     session.commit()
     session.close()
Exemple #19
0
    def put_many_one(self, id: int, parent: str, child: str, values):
        """put data for a many to many relationship of a parent and child.

        Args:
            id (int): Given ID of type parent
            parent (str): Type of parent
            child (str): Type of child
        """
        try:
            session = Session()
            junc_table = JuncHolder.lookup_table(parent, child)

            # delete all relations
            parent_col = getattr(junc_table.c, f"{parent}_id")
            del_st = junc_table.delete().where(parent_col == id)

            _ = session.execute(del_st)

            # put the items
            many_query = []

            if junc_table is not None:
                if not isinstance(values, list):
                    values = [values]
                many_query.append([child, values, junc_table])

            for field, values, table in many_query:
                self.process_many_query(session, table, id, field, parent,
                                        values)

        except Exception:
            raise InternalServerError()

        finally:
            session.close()

        return self.get_many_one(id, parent, child)
Exemple #20
0
    def get_one(self, id, data_model, data_resource_name, table_schema):
        """Retrieve a single object from the data model based on it's primary
        key.

        Args:
            id (any): The primary key for the specific object.
            data_model (object): SQLAlchemy ORM model.
            data_resource_name (str): Name of the data resource.
            table_schema (dict): The Table Schema object to use for validation.

        Return:
            dict, int: The response object and the HTTP status code.
        """
        try:
            primary_key = table_schema["primaryKey"]
            session = Session()
            result = (session.query(data_model).filter(
                getattr(data_model, primary_key) == id).first())
            response = self.build_json_from_object(result)
            return response, 200
        except Exception:
            raise ApiUnhandledError(f"Resource with id '{id}' not found.", 404)
        finally:
            session.close()
Exemple #21
0
    def save_migration(file_name: str, file_blob) -> None:
        """This function is called by alembic as a post write hook.

        It will take a migration file and save it to the database.
        """
        logger.info("Trying to save migration files to DB...")
        session = Session()
        try:
            new_migration = Migrations()
            new_migration.file_name = file_name
            new_migration.file_blob = file_blob
            result = (
                session.query(Migrations)
                .filter(Migrations.file_name == file_name)
                .count()
            )
            if result == 0:
                session.add(new_migration)
                session.commit()
        except Exception:
            logger.exception("Failed to save migration files to DB.")
        finally:
            session.close()
Exemple #22
0
    def add_model_checksum(
        self, table_name: str, model_checksum: str = "0", descriptor_json: dict = {}
    ):
        """Adds a new checksum for a data model.

        Args:
            table_name (str): Name of the table to add the checksum.
            checksum (str): Checksum value.
        """
        session = Session()
        try:
            checksum = Checksum()
            checksum.data_resource = table_name
            checksum.model_checksum = model_checksum
            checksum.descriptor_json = descriptor_json
            session.add(checksum)
            session.commit()
        except Exception:
            logger.exception("Error adding checksum")
        finally:
            session.close()
Exemple #23
0
    def get_all(self,
                data_model,
                data_resource_name,
                restricted_fields,
                offset=0,
                limit=1):
        """Retrieve a paginated list of items.

        Args:
            data_model (object): SQLAlchemy ORM model.
            data_resource_name (str): Name of the data resource.
            offset (int): Pagination offset.
            limit (int): Result limit.

        Return:
            dict, int: The response object and associated HTTP status code.
        """
        session = Session()
        response = OrderedDict()
        response[data_resource_name] = []
        response["links"] = []
        links = []

        try:
            results = session.query(data_model).limit(limit).offset(
                offset).all()
            for row in results:
                response[data_resource_name].append(
                    self.build_json_from_object(row, restricted_fields))
            row_count = session.query(data_model).count()
            if row_count > 0:
                links = self.build_links(data_resource_name, offset, limit,
                                         row_count)
            response["links"] = links
        except Exception:
            raise InternalServerError()

        session.close()
        return response, 200
Exemple #24
0
    def update_one(
        self,
        id,
        data_model,
        data_resource_name,
        table_schema,
        restricted_fields,
        request_obj,
        mode="PATCH",
    ):
        """Update a single object from the data model based on it's primary
        key.

        Args:
            id (any): The primary key for the specific object.
            data_model (object): SQLAlchemy ORM model.
            data_resource_name (str): Name of the data resource.
            table_schema (dict): The Table Schema object to use for validation.

        Return:
            dict, int: The response object and the HTTP status code.
        """
        try:
            request_obj = request_obj.json
        except Exception:
            raise ApiError("No request body found.", 400)

        try:
            primary_key = table_schema["primaryKey"]
            session = Session()
            data_obj = (session.query(data_model).filter(
                getattr(data_model, primary_key) == id).first())
            if data_obj is None:
                session.close()
                raise ApiUnhandledError(f"Resource with id '{id}' not found.",
                                        404)
        except Exception:
            raise ApiUnhandledError(f"Resource with id '{id}' not found.", 404)

        _ = Schema(table_schema)
        errors = []
        accepted_fields = []
        if validate(table_schema):
            for field in table_schema["fields"]:
                accepted_fields.append(field["name"])
            for field in request_obj.keys():
                if field not in accepted_fields:
                    errors.append(f"Unknown field '{field}' found.")
                elif field in restricted_fields:
                    errors.append(f"Cannot update restricted field '{field}'.")
        else:
            session.close()
            raise ApiError("Data schema validation error.", 400)

        if len(errors) > 0:
            session.close()
            raise ApiError("Invalid request body.", 400, errors)

        if mode == "PATCH":
            for key, value in request_obj.items():
                setattr(data_obj, key, value)
            session.commit()
        elif mode == "PUT":
            for field in table_schema["fields"]:
                if field["required"] and field["name"] not in request_obj.keys(
                ):
                    errors.append(
                        f"Required field '{field['name']}' is missing.")

            if len(errors) > 0:
                session.close()
                raise ApiError("Invalid request body.", 400, errors)

            for key, value in request_obj.items():
                setattr(data_obj, key, value)
            session.commit()

        session.close()
        return {"message": f"Successfully updated resource '{id}'."}, 201
Exemple #25
0
    def insert_one(self, data_model, data_resource_name, table_schema,
                   request_obj):
        """Insert a new object.

        Args:
            data_model (object): SQLAlchemy ORM model.
            data_resource_name (str): Name of the data resource.
            table_schema (dict): The Table Schema object to use for validation.
            request_obj (dict): HTTP request object.

        Return:
            dict, int: The response object and associated HTTP status code.
        """
        try:
            request_obj = request_obj.json
        except Exception:
            raise ApiError("No request body found.", 400)

        _ = Schema(table_schema)
        errors = []
        accepted_fields = []

        if not validate(table_schema):
            raise SchemaValidationFailure()

        # Check for required fields
        for field in table_schema["fields"]:
            accepted_fields.append(field["name"])

            if field["required"] and not field["name"] in request_obj.keys():
                errors.append(f"Required field '{field['name']}' is missing.")

        valid_fields = []
        many_query = []

        for field in request_obj.keys():
            if field in accepted_fields:
                valid_fields.append(field)
            else:
                junc_table = JuncHolder.lookup_table(field, data_resource_name)

                if junc_table is not None:
                    values = request_obj[field]
                    if not isinstance(values, list):
                        values = [values]
                    many_query.append([field, values, junc_table])
                else:
                    errors.append(f"Unknown field '{field}' found.")

        if len(errors) > 0:
            raise ApiError("Invalid request body.", 400, errors)

        try:
            session = Session()
            new_object = data_model()
            for field in valid_fields:
                value = request_obj[field]
                setattr(new_object, field, value)
            session.add(new_object)
            session.commit()
            id_value = getattr(new_object, table_schema["primaryKey"])

            # process the many_query
            for field, values, table in many_query:
                self.process_many_query(session, table, id_value, field,
                                        data_resource_name, values)

            return {
                "message": "Successfully added new resource.",
                "id": id_value
            }, 201
        except Exception:
            raise ApiUnhandledError("Failed to create new resource.", 400)
        finally:
            session.close()