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

View file

@ -3,7 +3,8 @@
pyjeeves.models
~~~~~~~~~~~~~~~
consolodated models module
models for databases
"""
from pyjeeves.connector import DBConnector
from .jvsmodels import * # noqa
db = DBConnector()

112
pyjeeves/models/abc.py Normal file
View file

@ -0,0 +1,112 @@
"""
Define an Abstract Base Class (ABC) for models
"""
from datetime import datetime
from decimal import Decimal
from sqlalchemy import inspect
from sqlalchemy.sql.expression import and_
from sqlalchemy.orm.collections import InstrumentedList
from pyjeeves import logging
from . import db
logger = logging.getLogger("PyJeeves." + __name__)
class RawBaseModel():
""" Generalize __init__, __repr__ and to_json
Based on the models columns , ForetagKod=1"""
print_only = () # First filter
print_filter = () # Second filter
to_json_filter = () # Only json filter
column_map = {}
__table_args__ = {
'extend_existing': True
}
@classmethod
def _base_filters(self, obj, filters=and_()):
# This method provides base filtering, additional filtering can be done in subclasses
# Add this method to your model if you want more filtering, otherwise leave it out
# import and_ from sqlalchemy package
# this is a base filter for ALL queries
return and_(
obj.ForetagKod == 1,
filters
)
def __repr__(self):
""" Define a base way to print models
Columns inside `print_filter` are excluded """
return '%s(%s)' % (self.__class__.__name__, {
column: value
for column, value in self._to_dict().items()
if column not in self.print_filter
})
@staticmethod
def _to_json_types(value):
if isinstance(value, datetime):
return value.strftime('%Y-%m-%d')
if isinstance(value, Decimal):
return "%.2f" % value
try:
if isinstance(value, InstrumentedList):
return [x.json for x in value]
if type(value).__module__ != 'builtins': # Perhaps == builtin?
return value.json
except AttributeError:
logger.debug(str(type(value)) + " was not converted to jsonifyable type")
return None
return value
@property
def json(self):
""" Define a base way to jsonify models
Columns inside `to_json_filter` are excluded
Columns inside `to_json_only_filter` are only included """
return {
column: RawBaseModel._to_json_types(value)
# if not isinstance(value, datetime) else value.strftime('%Y-%m-%d')
# if type(value).__module__ != self.__module__ # Perhaps == builtin?
# else value.json # Convert instances to json if same module
for column, value in self._to_dict().items()
if column not in self.to_json_filter
}
def _to_dict(self):
""" This would more or less be the same as a `to_json`
But putting it in a "private" function
Allows to_json to be overriden without impacting __repr__
Or the other way around
And to add filter lists """
return {
self._map_columns(column.key): getattr(self, column.key)
for column in inspect(self.__class__).attrs
if not self.print_only or column.key in self.print_only
}
def _map_columns(self, key):
if key in self.column_map:
return self.column_map[key]
return key
def merge(self):
db.raw_session.merge(self)
return self
def commit(self):
db.raw_session.commit()
def save(self):
db.raw_session.add(self)
db.raw_session.commit()
return self
def delete(self):
db.raw_session.delete(self)
db.raw_session.commit()

View file

@ -3,7 +3,7 @@
pyjeeves.models
~~~~~~~~~~~~~~~~~~~~~~
Jeeves data models
Jeeves meta data models
"""
from sqlalchemy.ext.declarative import declarative_base

104
pyjeeves/models/raw.py Normal file
View file

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
"""
pyjeeves.models
~~~~~~~~~~~~~~~~~~~~~~
Jeeves raw data models
"""
# from sqlalchemy import Column, String
from sqlalchemy.schema import MetaData, ForeignKey, Column
from sqlalchemy.orm import relationship
from sqlalchemy.types import Integer, String
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.sql.expression import and_
# from pyjeeves.session import raw_engine
from . import db
from pyjeeves import logging
from .abc import RawBaseModel
logger = logging.getLogger("PyJeeves." + __name__)
logger.info("Reading Jeeves DB structure")
meta = MetaData()
meta.reflect(bind=db.raw_session.connection(), only=['ar', 'ars', 'fr', 'kus', 'oh', 'lp', 'vg'])
# Table('fr', meta, implicit_returning=False)
Base = automap_base(cls=db.Model, name='Model', metadata=meta)
class CommodityGroup(Base, RawBaseModel):
__tablename__ = 'vg'
column_map = {'VaruGruppKod': 'CommodityGroupNumber', 'VaruGruppBeskr': 'CommodityGroupName'}
print_only = ('VaruGruppKod', 'VaruGruppBeskr')
print_filter = ('Articles', 'articles_collection')
# to_json_filter = ('Articles', 'articles_collection')
class ArticleBalance(Base, RawBaseModel):
__tablename__ = 'ars'
column_map = {'LagSaldo': 'Balance',
'LagResAnt': 'ReservedBalance',
'LagsaldoAltEnh': 'BalanceAlternative',
'LagResAntAltEnh': 'ReservedAlternativeBalance',
'LagStalle': 'StorageLocationNumber'}
print_only = ('LagSaldo',
'LagResAnt',
'LagsaldoAltEnh',
'LagResAntAltEnh',
'LagStalle')
# print_filter = ('Articles', 'articles_collection')
# to_json_filter = ('Articles', 'articles_collection')
ArtNr = Column(Integer, ForeignKey('ar.ArtNr'), primary_key=True)
class Articles(Base, RawBaseModel):
__tablename__ = 'ar'
column_map = {'ArtNr': 'ArticleNumber',
'ArtBeskr': 'ArticleName',
'LagSaldoArtikel': 'Balance',
'EnhetsKod': 'Unit',
'ArtListPris': 'ListPrice'}
print_only = (
'ArtNr',
'ArtBeskr',
'CommodityGroup',
'ArticleBalance',
'EnhetsKod',
'LagSaldoArtikel',
'RowCreatedDt',
'ArtListPris')
ArtNr = Column(Integer, primary_key=True)
VaruGruppKod = Column(Integer, ForeignKey('vg.VaruGruppKod'), primary_key=True)
CommodityGroup = relationship(CommodityGroup)
ArticleBalance = relationship(ArticleBalance)
@classmethod
def _base_filters(self, obj):
return RawBaseModel._base_filters(
obj,
and_(obj.LagTyp == 0)
)
class Companies(Base, RawBaseModel):
__tablename__ = 'fr'
column_map = {'FtgNr': 'CompanyNumber', 'FtgNamn': 'CompanyName'}
print_only = ('CompanyNumber', 'CompanyName')
FtgNr = Column(String, primary_key=True)
Base.prepare()
# Base companies for cusomters and suppliers
Customers = Base.classes.kus # Customer information
Orders = Base.classes.oh # Orders by customers
DelivLoc = Base.classes.lp # Connections between a delivery company and customer company