shipyard/src/bin/shipyard_airflow/shipyard_airflow/common/notes/storage_impl_db.py

168 lines
5.7 KiB
Python

# Copyright 2018 AT&T Intellectual Property. All other 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.
#
"""ShipyardSQLNotesStorage
Implementation of NotesStorage that is based on the structure of the notes
table as defined in Shipyard. Accepts a SQLAlchemy engine as input, and
generates a model class from the notes table.
Mapping to/from Note objects is encapsulated here for a consistent interface
"""
from contextlib import contextmanager
import logging
from sqlalchemy import and_
from sqlalchemy import Column
from sqlalchemy import func
from sqlalchemy import Text
from sqlalchemy import types
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .notes import Note
from .notes import NotesStorage
from .errors import NotesError
from .errors import NotesInitializationError
LOG = logging.getLogger(__name__)
Base = declarative_base()
class TNote(Base):
"""Notes ORM class"""
__tablename__ = 'notes'
# These must align with the table defined using Alembic
note_id = Column('note_id', types.String(26), primary_key=True)
assoc_id = Column('assoc_id', types.String(128), nullable=False)
subject = Column('subject', types.String(128), nullable=False)
sub_type = Column('sub_type', types.String(128), nullable=False)
note_val = Column('note_val', Text, nullable=False)
verbosity = Column('verbosity', types.Integer, nullable=False)
link_url = Column('link_url', Text, nullable=True)
is_auth_link = Column('is_auth_link', types.Boolean, nullable=False)
note_timestamp = Column('note_timestamp',
types.TIMESTAMP(timezone=True),
server_default=func.now())
class ShipyardSQLNotesStorage(NotesStorage):
"""SQL Alchemy implementation of a notes storage; Shipayrd table structure
Accepts a SQL Alchemy Engine to serve as the connection to the persistence
layer for Notes.
:param engine_getter: A method that can be used to get SQLAlchemy engine
to use
"""
def __init__(self, engine_getter):
try:
self._engine_getter = engine_getter
self._session = None
except Exception as ex:
LOG.exception(ex)
raise NotesInitializationError(
"Misconfiguration has casuse a failure to setup the desired "
"database connection for Notes."
)
def _get_session(self):
"""Lazy initilize the sessionmaker, invoke the engine getter, and
use it to return a session
"""
if not self._session:
self._session = sessionmaker(bind=self._engine_getter())
return self._session()
@contextmanager
def session_scope(self):
"""Context manager for a SQLAlchemy session"""
session = self._get_session()
try:
yield session
session.commit()
except Exception as ex:
session.rollback()
if isinstance(ex, NotesError):
raise
else:
LOG.exception(ex)
raise NotesError(
"An unexpected error has occurred while attempting to "
"interact with the database for note storage"
)
finally:
session.close()
def store(self, note):
"""Store a note in the database"""
r_note = None
with self.session_scope() as session:
tnote = self._map(note, TNote)
session.add(tnote)
r_note = self._map(tnote, Note)
return r_note
def retrieve(self, query):
a_id_pat = query.assoc_id_pattern
m_verb = query.max_verbosity
r_notes = []
with self.session_scope() as session:
notes_res = []
if (query.exact_match):
n_qry = session.query(TNote).filter(
and_(
TNote.assoc_id == a_id_pat,
TNote.verbosity <= m_verb
)
)
else:
n_qry = session.query(TNote).filter(
and_(
TNote.assoc_id.like(a_id_pat + '%'),
TNote.verbosity <= m_verb
)
)
db_notes = n_qry.all()
for tn in db_notes:
r_notes.append(self._map(tn, Note))
return r_notes
def _map(self, src, target_type):
"""Maps a Note object to/from a TNote object.
:param src: the object to use as a source
:param target_type: the type of object to create and map to
"""
try:
tgt = target_type(
assoc_id=src.assoc_id,
subject=src.subject,
sub_type=src.sub_type,
note_val=src.note_val,
verbosity=src.verbosity,
link_url=src.link_url,
is_auth_link=src.is_auth_link,
note_id=src.note_id,
note_timestamp=src.note_timestamp
)
except AttributeError as ae:
LOG.exception(ae)
raise NotesError(
"Note could not be translated from/to SQL form; mapping error"
)
return tgt