In progress: Jeeves communication is now based on SQLAlchemy

This commit is contained in:
Marcus Lindvall 2019-04-05 16:50:38 +02:00
parent 0fdc029153
commit 28726fee01
21 changed files with 637 additions and 78 deletions

142
pyjeeves/connector.py Normal file
View file

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
pyjeeves
~~~~~~~~~~~~~~~
Global objects
"""
from pyjeeves import logging, config
from weakref import WeakValueDictionary
from sqlalchemy import create_engine, orm
from sqlalchemy.orm import sessionmaker, scoped_session, Query, aliased
from sqlalchemy.orm.exc import UnmappedClassError
from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta
logger = logging.getLogger("PyJeeves." + __name__)
class BaseFilterQuery(Query):
def get(self, ident):
# Override get() so that the flag is always checked in the
# DB as opposed to pulling from the identity map. - this is optional.
return Query.get(self.populate_existing(), ident)
def __iter__(self):
return Query.__iter__(self.private())
def from_self(self, *ent):
# Override from_self() to automatically apply
# the criterion to. this works with count() and
# others.
return Query.from_self(self.private(), *ent)
def private(self):
# Fetch the model name and column list and apply model-specific base filters
mzero = self._mapper_zero()
if mzero:
# Sometimes a plain model class will be fetched instead of mzero
try:
model = mzero.class_
obj = mzero.class_
except Exception:
model = mzero.__class__
obj = mzero
if hasattr(model, '_base_filters'):
return self.enable_assertions(False).filter(model._base_filters(obj))
return self
class Model(object):
"""Baseclass for custom user models."""
#: the query class used. The :attr:`query` attribute is an instance
#: of this class. By default a :class:`BaseQuery` is used.
query_class = BaseFilterQuery
#: an instance of :attr:`query_class`. Can be used to query the
#: database for instances of this model.
query = None
class MetaBaseModel(DeclarativeMeta):
""" Define a metaclass for the BaseModel
Implement `__getitem__` for managing aliases """
def __init__(cls, *args):
super().__init__(*args)
cls.aliases = WeakValueDictionary()
def __getitem__(cls, key):
try:
alias = cls.aliases[key]
except KeyError:
alias = aliased(cls)
cls.aliases[key] = alias
return alias
class _QueryProperty(object):
def __init__(self, sa):
self.sa = sa
def __get__(self, obj, type):
try:
mapper = orm.class_mapper(type)
if mapper:
if type.__module__ == 'pyjeeves.models.raw':
return type.query_class(mapper, session=self.sa.raw_session())
else:
return type.query_class(mapper, session=self.sa.meta_session())
except UnmappedClassError:
return None
class DBConnector(object):
"""This class is used to control the SQLAlchemy integration"""
def __init__(self, enabled_sessions=['raw'], metadata=None):
logger.info("Creating engines and sessionmakers")
self.raw_session, self.meta_session = self.create_scoped_session(enabled_sessions)
self.Model = self.make_declarative_base(metadata)
# self.Query = Query
@property
def metadata(self):
"""Returns the metadata"""
return self.Model.metadata
# @property
# def _config(self):
# """Returns the configuration"""
# return config()
def make_declarative_base(self, metadata=None):
"""Creates the declarative base."""
base = declarative_base(cls=Model, name='Model',
metadata=metadata,
metaclass=MetaBaseModel)
base.query = _QueryProperty(self)
return base
def create_scoped_session(self, sessions=[]):
RawSession, MetaSession = None, None
if 'raw' in sessions:
raw_engine = create_engine(
'mssql+pymssql://{user}:{pw}@{host}:{port}/{db}?charset=utf8'.format(
**config.config['databases']['raw']),
implicit_returning=False)
RawSession = scoped_session(sessionmaker(bind=raw_engine))
if 'meta' in sessions:
meta_engine = create_engine(
'mysql+pymysql://{user}:{pw}@{host}:{port}/{db}?charset=utf8mb4'.format(
**config.config['databases']['meta']))
MetaSession = scoped_session(sessionmaker(bind=meta_engine))
return RawSession, MetaSession