mirror of
https://github.com/yweber/lodel2.git
synced 2025-11-22 13:46:54 +01:00
Updated version of the sqlwrapper
This commit is contained in:
parent
155e089750
commit
a25e97607c
2 changed files with 256 additions and 433 deletions
67
Database/sqlobject.py
Normal file
67
Database/sqlobject.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging as logger
|
||||
|
||||
import sqlalchemy as sql
|
||||
|
||||
from Database.sqlwrapper import SqlWrapper
|
||||
|
||||
class SqlObject(object):
|
||||
""" Object that make aliases with sqlalchemy
|
||||
|
||||
Example usage of object that inherite from SqlObject :
|
||||
|
||||
class foo(SlqObject,...):
|
||||
def __init__(self, ...):
|
||||
self.__class__.tname = 'foo_table'
|
||||
|
||||
f = foo(...)
|
||||
req = f.where(f.col.id == 42)
|
||||
res = f.rexec(req)
|
||||
|
||||
e = bar(...)
|
||||
req = f.join(e.col.id == f.col.id)
|
||||
res = f.rexec(req)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, tname):
|
||||
self.tname = tname
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
return sql.Table(self.tname, sql.MetaData())
|
||||
|
||||
@property
|
||||
def col(self):
|
||||
return self.table.c
|
||||
|
||||
@property
|
||||
def sel(self):
|
||||
return sql.select(self.table)
|
||||
|
||||
@property
|
||||
def where(self):
|
||||
return self.sel.where
|
||||
|
||||
@property
|
||||
def join(self):
|
||||
return self.sel.join
|
||||
|
||||
@property
|
||||
def rconn(self):
|
||||
return SqlWrapper.rconn()
|
||||
|
||||
@property
|
||||
def wconn(self):
|
||||
return SqlWrapper.wconn()
|
||||
|
||||
def sFetchAll(self, sel):
|
||||
return self.rexec(sel).fetchall()
|
||||
|
||||
def rexec(self, o):
|
||||
return self.rconn.execute(o)
|
||||
|
||||
def wexec(self, o):
|
||||
return self.wconn.execute(o)
|
||||
|
|
@ -1,469 +1,225 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import logging as logger
|
||||
|
||||
from sqlalchemy import * # TODO ajuster les classes à importer
|
||||
from Database.sqlsettings import SQLSettings as sqlsettings
|
||||
import sqlalchemy as sqla
|
||||
from django.conf import settings
|
||||
import re
|
||||
|
||||
import logging as logger #TODO hack to replace the standarts use of logging in django
|
||||
#Logger config
|
||||
logger.getLogger().setLevel('DEBUG')
|
||||
#To be able to use dango confs
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
|
||||
|
||||
class SqlWrapper(object):
|
||||
""" A wrapper class to sqlalchemy
|
||||
Usefull to provide a standart API
|
||||
"""
|
||||
|
||||
def __init__(self, log=False):
|
||||
##Read Engine
|
||||
rengine = None
|
||||
##Write Engine
|
||||
wengine = None
|
||||
|
||||
##Read connection
|
||||
rconn = None
|
||||
##Write connection
|
||||
wconn = None
|
||||
|
||||
##Configuration dict alias for class access
|
||||
config=settings.LODEL2SQLWRAPPER
|
||||
##SqlAlchemy logging
|
||||
sqla_logging = False
|
||||
|
||||
def __init__(self, alchemy_logs=None):
|
||||
""" Instanciate a new SqlWrapper
|
||||
@param log bool: If true activate sqlalchemy logger
|
||||
@param alchemy_logs bool: If true activate sqlalchemy logger
|
||||
"""
|
||||
# TODO Passer ces deux éléments dans les settings django (au niveau de l'init de l'appli)
|
||||
self.read_engine = self.get_engine(sqlsettings.DB_READ_CONNECTION_NAME, log)
|
||||
self.write_engine = self.get_engine(sqlsettings.DB_WRITE_CONNECTION_NAME, log)
|
||||
|
||||
if (alchemy_logs != None and \
|
||||
bool(alchemy_logs) != self.__class__.sqla_logging):
|
||||
#logging config changed for sqlalchemy
|
||||
self.__class__.restart()
|
||||
|
||||
def get_engine(self, connection_name, log=False):
|
||||
""" Crée un moteur logique sur une base de données
|
||||
if not self.__class__.started():
|
||||
self.__class__.start()
|
||||
|
||||
pass
|
||||
|
||||
@param connection_name str: Connection name as defined is django settings
|
||||
@param log bool: If true this engine will have logging enabled
|
||||
@return Engine
|
||||
@classmethod
|
||||
def table(c, o):
|
||||
""" Return a SqlAlchemy Table object
|
||||
@param o str: Table name
|
||||
"""
|
||||
if isinstance(o, str):
|
||||
return sqla.Table(o, sqla.MetaData())
|
||||
return None
|
||||
|
||||
connection_params = settings.DATABASES[connection_name]
|
||||
|
||||
if 'ENGINE' not in connection_params:
|
||||
raise NameError('Missing ENGINE in configuration file')
|
||||
|
||||
if connection_params['ENGINE'] not in sqlsettings.dbms_list:
|
||||
raise NameError('Wrong ENGINE "'+connection_params['ENGINE']+'" in configuration files')
|
||||
dialect = connection_params['ENGINE']
|
||||
|
||||
if 'driver' not in sqlsettings.dbms_list[dialect]:
|
||||
raise NameError('Wrong ENGINE "'+dialect+'" in configuration files')
|
||||
driver = sqlsettings.dbms_list[dialect]['driver']
|
||||
|
||||
if 'NAME' not in connection_params:
|
||||
raise NameError('Missing database name in configuration files')
|
||||
database = connection_params['NAME']
|
||||
|
||||
if 'USER' in connection_params:
|
||||
user = connection_params['USER']
|
||||
if 'PASSWORD' in connection_params:
|
||||
user += ':'+connection_params['PASSWORD']
|
||||
@classmethod
|
||||
def connect(c,read = None):
|
||||
|
||||
if read == None:
|
||||
return c.connect(True) and c.coonect(False)
|
||||
elif read:
|
||||
c.rconn = c.rengine.connect()
|
||||
else:
|
||||
user = ''
|
||||
c.wconn = c.wengine.connect()
|
||||
return True #TODO attention c'est pas checké...
|
||||
|
||||
hostname = connection_params['HOST'] if 'HOST' in connection_params else sqlsettings.DEFAULT_HOSTNAME
|
||||
port = connection_params['PORT'] if 'PORT' in connection_params else ''
|
||||
host = hostname if port=='' else '%s:%s' % (hostname, port)
|
||||
|
||||
|
||||
if dialect == 'sqlite':
|
||||
connection_string = '%s+%s:///%s' % (dialect, driver, database)
|
||||
@classmethod
|
||||
def conn(c, read=True):
|
||||
if read:
|
||||
res = c.rconn
|
||||
else:
|
||||
connection_string = '%s+%s://%s@%s/%s' % (dialect, driver, user, host, database)
|
||||
res = c.wconn
|
||||
|
||||
engine = create_engine(connection_string, encoding=sqlsettings.dbms_list[dialect]['encoding'], echo=log)
|
||||
return engine
|
||||
if res == None:
|
||||
if not c.connect(read):
|
||||
raise RuntimeError('Unable to connect to Db')
|
||||
return self.conn(read)
|
||||
|
||||
def get_read_engine(self):
|
||||
return self.read_engine
|
||||
return c.rconn
|
||||
|
||||
def get_write_engine(self):
|
||||
return self.write_engine
|
||||
@classmethod
|
||||
def rc(c): return c.conn(True)
|
||||
@classmethod
|
||||
def wc(c): return c.conn(False)
|
||||
|
||||
def execute(self, queries, action_type):
|
||||
""" Exécute une série de requêtes en base
|
||||
|
||||
@param queries list: une liste de requêtes
|
||||
@param action_type str: le type d'action ( 'read'|'write' )
|
||||
@return List. Tableau de résultats sous forme de tuples (requête, résultat)
|
||||
@classmethod
|
||||
def start(c, sqlalogging = None):
|
||||
""" Load engines
|
||||
Open connections to databases
|
||||
@param sqlalogging bool: overwrite class parameter about sqlalchemy logging
|
||||
@return False if already started
|
||||
"""
|
||||
|
||||
db = self.get_write_engine() if action_type==sqlsettings.ACTION_TYPE_WRITE else self.get_read_engine()
|
||||
connection = db.connect()
|
||||
transaction= connection.begin()
|
||||
result = []
|
||||
|
||||
queries = queries if isinstance(queries, list) else [queries]
|
||||
|
||||
|
||||
try:
|
||||
for query in queries:
|
||||
result.append((query, connection.execute(query)))
|
||||
transaction.commit()
|
||||
connection.close()
|
||||
except:
|
||||
transaction.rollback()
|
||||
connection.close()
|
||||
result = None
|
||||
raise
|
||||
|
||||
return result
|
||||
|
||||
def create_foreign_key_constraint_object(self, localcol, remotecol, constraint_name=None):
|
||||
""" Génère une contrainte Clé étrangère
|
||||
|
||||
@param localcol str: Colonne locale
|
||||
@param remotecol str: Colonne distante (syntaxe : Table.col)
|
||||
@param constraint_name str: Nom de la contrainte (par défaut : fk_localcol_remotecol)
|
||||
|
||||
Returns:
|
||||
ForeignKeyConstraint.
|
||||
"""
|
||||
|
||||
foreignkeyobj = ForeignKeyConstraint([localcol], [remotecol], name=constraint_name)
|
||||
return foreignkeyobj
|
||||
|
||||
def create_column_object(self, column_name, column_type, column_extra=None):
|
||||
""" Génère un objet colonne
|
||||
|
||||
Exemple de dictionnaire pour column_extra : { "primarykey":True, "nullable":False, "default":"test" ... }
|
||||
@param column_name str: Nom de la colonne
|
||||
@param column_type str: Type de la colonne
|
||||
@param column_extra dict : Objet json contenant les paramètres optionnels comme PRIMARY KEY, NOT NULL, etc ...
|
||||
|
||||
@return Column. La méthode renvoie "None" si l'opération échoue
|
||||
|
||||
"""
|
||||
|
||||
# Traitement du type (mapping avec les types SQLAlchemy)
|
||||
# TODO créer une méthode qui fait un mapping plus complet
|
||||
column = None
|
||||
if column_type=='INTEGER':
|
||||
column = Column(column_name, INTEGER)
|
||||
elif 'VARCHAR' in column_type:
|
||||
check_length = re.search(re.compile('VARCHAR\(([\d]+)\)', re.IGNORECASE), column_type)
|
||||
column_length = int(check_length.groups()[0]) if check_length else None
|
||||
column = Column(column_name, VARCHAR(length=column_length))
|
||||
elif column_type=='TEXT':
|
||||
column = Column(column_name, TEXT)
|
||||
elif column_type=='DATE':
|
||||
column = Column(column_name, DATE)
|
||||
elif column_type=='BOOLEAN':
|
||||
column = Column(column_name, BOOLEAN)
|
||||
|
||||
if column is not None and column_extra:
|
||||
if 'nullable' in column_extra:
|
||||
column.nullable = column_extra['nullable']
|
||||
if 'primarykey' in column_extra:
|
||||
column.primary_key = column_extra['primarykey']
|
||||
if 'default' in column_extra:
|
||||
column.default = ColumnDefault(column_extra['default'])
|
||||
if 'foreignkey' in column_extra:
|
||||
column.append_foreign_key(ForeignKey(column_extra['foreignkey']))
|
||||
|
||||
|
||||
return column
|
||||
|
||||
def create_table(self, tableparams):
|
||||
""" Crée une nouvelle table
|
||||
|
||||
Les paramètres de la tables sont passés via un dictionnaire dont voici la description :
|
||||
{ 'name':'<nom_table>', 'columns': [ <col1>, <col2>, <coln> ], 'constraints': dict(...) }
|
||||
Avec <colX> : { 'name': '<name_colX>', 'type': '<type_colX>', 'extra': dict(...) }
|
||||
A noter que les types de col (<type_colX>) sont des chaines representant un type SQL (ex: "VARCHAR(50)")
|
||||
Les deux champs "name" et "columns" seulement sont obligatoires.
|
||||
Le champ "extra" de la définition des colonnes est facultatif.
|
||||
|
||||
@args tableparams dict: Dictionnaire avec les paramétrages de la table
|
||||
|
||||
@return bool. True si le process est allé au bout, False si on a rencontré une erreur.
|
||||
|
||||
"""
|
||||
|
||||
metadata = MetaData()
|
||||
table = Table(tableparams['name'], metadata)
|
||||
columns = tableparams['columns']
|
||||
for column in columns:
|
||||
column_extra = column['extra'] if 'extra' in column else None
|
||||
table.append_column(self.create_column_object(column['name'], column['type'], column_extra))
|
||||
|
||||
try:
|
||||
table.create(self.get_write_engine())
|
||||
return True
|
||||
except Exception as e:
|
||||
# TODO Ajuster le code d'erreur à retourner
|
||||
logger.warning("Error creating table : "+str(e))
|
||||
c.checkConf()
|
||||
if c.started():
|
||||
logger.warning('Starting SqlWrapper but it is allready \
|
||||
started')
|
||||
return False
|
||||
|
||||
c.rengine = c._getEngine(read=True, sqlaloggingi=None)
|
||||
c.wengine = c._getEngine(read=False, sqlalogging=None)
|
||||
return True
|
||||
|
||||
def get_table(self, table_name, action_type=sqlsettings.ACTION_TYPE_WRITE):
|
||||
""" Récupère une table dans un objet Table
|
||||
|
||||
@param table_name str: Nom de la table
|
||||
@param action_type str: Type d'action (read|write) (par défaut : "write")
|
||||
@return Table.
|
||||
"""
|
||||
db = self.get_write_engine() if action_type == sqlsettings.ACTION_TYPE_WRITE else self.get_read_engine()
|
||||
metadata = MetaData()
|
||||
|
||||
return Table(table_name, metadata, autoload=True, autoload_with=db)
|
||||
|
||||
|
||||
def drop_table(self, table_name):
|
||||
""" Supprime une table
|
||||
@param table_name str: Nom de la table
|
||||
@return bool. True si le process est allé au bout. False si on a rencontré une erreur
|
||||
"""
|
||||
|
||||
try:
|
||||
db = self.get_write_engine().connect()
|
||||
metadata = MetaData()
|
||||
table = Table(table_name, metadata, autoload=True, autoload_with=db)
|
||||
# Pour le drop, on utilise checkfirst qui permet de demander la suppression préalable des contraintes liées à la table
|
||||
table.drop(db, checkfirst=True)
|
||||
db.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
# TODO ajuster le code d'erreur
|
||||
logger.warning("Error droping table : "+str(e))
|
||||
return False
|
||||
|
||||
|
||||
def get_querystring(self, action, dialect):
|
||||
""" Renvoit un modèle de requete selon un dialect et une action
|
||||
|
||||
@param action str: Peut être 'add_column' 'alter_column' 'drop_column'
|
||||
@param dialect str: Pour le moment seul 'default', 'mysql' et 'postgresql' sont supportés
|
||||
|
||||
@return str: Un modèle de requète
|
||||
"""
|
||||
string_dialect = dialect if dialect in sqlsettings.querystrings[action] else 'default'
|
||||
querystring = sqlsettings.querystrings[action][string_dialect]
|
||||
return querystring
|
||||
|
||||
def add_column(self, table_name, column):
|
||||
""" Ajoute une colonne à une table existante
|
||||
|
||||
@param table_name str: nom de la table
|
||||
@param column dict: la colonne - {"name":"<nom de la colonne>", "type":"<type de la colonne>"}
|
||||
|
||||
@return bool. True si le process est allé au bout, False si on a rencontré une erreur
|
||||
"""
|
||||
|
||||
sqlquery = self.get_querystring('add_column', self.get_write_engine().dialect) % (table_name, column['name'], column['type'])
|
||||
sqlresult = self.execute(sqlquery, sqlsettings.ACTION_TYPE_WRITE)
|
||||
return True if sqlresult else False
|
||||
|
||||
def alter_column(self, table_name, column):
|
||||
""" Modifie le type d'une colonne
|
||||
|
||||
@param table_name str: nom de la table
|
||||
@param column_name dict: la colonne - {"name":"<nom de la colonne>","type":"<nouveau type de la colonne>"}
|
||||
|
||||
@return bool. True si le process est allé au bout. False si on a rencontré une erreur
|
||||
"""
|
||||
|
||||
sqlquery = self.get_querystring('alter_column', self.get_write_engine().dialect) % (table_name, column['name'], column['type'])
|
||||
sqlresult = self.execute(sqlquery, sqlsettings.ACTION_TYPE_WRITE)
|
||||
return True if sqlresult else False
|
||||
|
||||
def insert(self, table_name, newrecord):
|
||||
""" Insère un nouvel enregistrement
|
||||
|
||||
@param table_name str: nom de la table
|
||||
@param newrecord dict: dictionnaire contenant les informations sur le nouvel enregistrement à insérer - {"column1":"value1", "column2":"value2", etc ...)
|
||||
|
||||
@return int. Nombre de lignes insérées
|
||||
"""
|
||||
sqlresult = self.execute(self.get_table(table_name).insert().values(newrecord), sqlsettings.ACTION_TYPE_WRITE)
|
||||
return sqlresult.rowcount
|
||||
|
||||
def delete(self, table_name, whereclauses):
|
||||
""" Supprime un enregistrement
|
||||
|
||||
@param table_name str: nom de la table
|
||||
@param whereclauses list: liste des conditions sur les enregistrements (sous forme de textes SQL)
|
||||
|
||||
@return int. Nombre de lignes supprimées
|
||||
"""
|
||||
|
||||
deleteobject = self.get_table(table_name).delete()
|
||||
|
||||
for whereclause in whereclauses:
|
||||
deleteobject = deleteobject.where(whereclause)
|
||||
|
||||
sqlresult = self.execute(deleteobject, sqlsettings.ACTION_TYPE_WRITE)
|
||||
return sqlresult.rowcount
|
||||
|
||||
def update(self, table_name, whereclauses, newvalues):
|
||||
""" Met à jour des enregistrements
|
||||
|
||||
@param table_name str: nom de la table
|
||||
@param whereclauses list: liste des conditions sur les enregistrements (sous forme de textes SQL)
|
||||
@param newvalues dict: dictionnaire contenant les nouvelles valeurs à insérer - {"colonne1":"valeur1", "colonne2":"valeur2", etc ...}
|
||||
|
||||
@return int. Nombre de lignes modifiées
|
||||
|
||||
@throw DataError. Incompatibilité entre la valeur insérée et le type de colonne dans la table
|
||||
"""
|
||||
|
||||
table = self.get_table(table_name)
|
||||
update_object = table.update()
|
||||
|
||||
updated_lines_count = 0
|
||||
|
||||
try:
|
||||
for whereclause in whereclauses:
|
||||
update_object = update_object.where(whereclause)
|
||||
|
||||
update_object = update_object.values(newvalues)
|
||||
|
||||
sqlresult = self.execute(update_object, sqlsettings.ACTION_TYPE_WRITE)
|
||||
updated_lines_count = sqlresult.rowcount
|
||||
|
||||
except DataError as e:
|
||||
# TODO Voir si on garde "-1" ou si on place un "None" ou un "False"
|
||||
logger.warning("Error updating database : "+str(e))
|
||||
updated_lines_count = -1
|
||||
|
||||
return updated_lines_count
|
||||
|
||||
def select(self, select_params):
|
||||
""" Récupère un jeu d'enregistrements
|
||||
|
||||
Champs de select_params :
|
||||
- "what":"<liste des colonnes>",
|
||||
- "from":"<liste des tables>",
|
||||
- "where":"<liste des conditions>",
|
||||
- "distinct":True|False,
|
||||
- "join":{"left":"table_de_gauche.champ","right":"table_de_droite.champ", "options":{"outer":True|False}}
|
||||
- ...
|
||||
|
||||
Les listes sont supportées sous deux formats :
|
||||
- texte SQL : "colonne1, colonne2, ..."
|
||||
- liste Python : ["colonne1", "colonne2", ...]
|
||||
|
||||
@param select_params list: liste de dictionnaire permettant de caractériser la requête.
|
||||
|
||||
@return list. Liste des dictionnaires représentant les différents enregistrements.
|
||||
"""
|
||||
|
||||
if not 'what' in select_params or not 'from' in select_params:
|
||||
# TODO Lever une exception à ce niveau
|
||||
raise
|
||||
|
||||
query_what = select_params['what'] if isinstance(select_params['what'], list) else select_params['what'].replace(' ', '').split(',')
|
||||
query_from = select_params['from'] if isinstance(select_params['from'], list) else select_params['from'].replace(' ', '').split(',')
|
||||
query_where= select_params['where'] if isinstance(select_params['where'], list) else select_params['where'].replace(' ', '').split(',')
|
||||
query_distinct = select_params['distinct'] if 'distinct' in select_params else False
|
||||
query_limit = select_params['limit'] if 'limit' in select_params else False
|
||||
query_offset = select_params['offset'] if 'offset' in select_params else False
|
||||
query_orderby = select_params['order_by'] if 'order_by' in select_params else False
|
||||
query_groupby = select_params['group_by'] if 'group_by' in select_params else False
|
||||
query_having = select_params['having'] if 'having' in select_params else False
|
||||
|
||||
columns_list = []
|
||||
if len(query_from) > 1:
|
||||
for column_id in query_what:
|
||||
# Il y a plusieurs tables, les colonnes sont donc nommées sur le format suivant : <nom de la table>.<nom du champ>
|
||||
column_id_array = column_id.split('.')
|
||||
columns_list.append(getattr(self.get_table(column_id_array[0]).c, column_id_array[1]))
|
||||
else:
|
||||
table = self.get_table(query_from[0])
|
||||
|
||||
|
||||
select_object = select(columns=columnslist)
|
||||
|
||||
selected_lines = []
|
||||
|
||||
try:
|
||||
# Traitement des différents paramètres de la requête
|
||||
for query_where_item in query_where:
|
||||
select_object = select_object.where(query_where_item)
|
||||
|
||||
select_object = select_object.distinct(True) if query_distinct else select_object
|
||||
select_object = select_object.limit(query_limit) if query_limit else select_object
|
||||
select_object = select_object.offset(query_offset) if query_offset else select_object
|
||||
|
||||
if query_orderby:
|
||||
for query_orderby_column, query_orderby_direction in query_orderby:
|
||||
select_object = select_object.order_by("%s %s" % (query_orderby_column, query_orderby_direction))
|
||||
|
||||
#Traitement de la jointure
|
||||
if 'join' in select_params:
|
||||
select_join = self.create_join_object(select_params['join'])
|
||||
if select_join:
|
||||
select_object = select_object.select_from(select_join)
|
||||
else:
|
||||
raise # TODO Ajuster l'exception à lever (ici :"Données de jointure invalides ou manquantes")
|
||||
|
||||
#Traitement du group_by
|
||||
if query_groupby:
|
||||
for group_by_clause in query_groupby:
|
||||
select_object = select_object.group_by(self.get_table_column_from_sql_string(group_by_clause))
|
||||
|
||||
# Traitement du having
|
||||
if query_groupby and query_having:
|
||||
for having_clause in query_having:
|
||||
select_object = select_object.having(having_clause)
|
||||
|
||||
# Exécution de la requête
|
||||
sqlresult = self.execute(select_object, sqlsettings.ACTION_TYPE_READ)
|
||||
|
||||
# Transformation du résultat en une liste de dictionnaires
|
||||
records = sqlresult.fetchall()
|
||||
for record in records:
|
||||
selected_lines.append(dict(zip(record.keys(), record)))
|
||||
except Exception as e:
|
||||
logger.debug("Error selecting in database : "+str(e))
|
||||
selected_lines = None
|
||||
|
||||
return selected_lines
|
||||
|
||||
def get_table_column_from_sql_string(self,sql_string):
|
||||
|
||||
sql_string_array = sql_string.split('.')
|
||||
table_name = sql_string_array[0]
|
||||
column_name = sql_string_array[1]
|
||||
table_object = self.get_table(table_name,sqlsettings.ACTION_TYPE_READ)
|
||||
column_object = getattr(table_object.c, column_name)
|
||||
|
||||
return column_object
|
||||
|
||||
|
||||
def create_join_object(self, join_params):
|
||||
"""Génère un objet "Jointure" pour le greffer dans une requête
|
||||
|
||||
Format des dictionnaires de description des jointures :
|
||||
- Format1 liste de champs
|
||||
- "left":"table.champ",
|
||||
- "right":"table.champ",
|
||||
- "options":{"outer":True|False}
|
||||
- Format2 liste de champs
|
||||
- "left":{"table":"nom de la table","field":"nom du champ"},
|
||||
- "right":{"table":"nom de la table","field":"nom du champ"},
|
||||
- "options":{"outer":True|False}
|
||||
@classmethod
|
||||
def stop(c): c.rengine = c.wengine = None; pass
|
||||
@classmethod
|
||||
def restart(c): c.stop(); c.start(); pass
|
||||
@classmethod
|
||||
def started(c): return (c.rengine != None and c.rengine != None)
|
||||
|
||||
@param join_params (dict) : dictionnaire de données sur la jointure.
|
||||
@return join.
|
||||
@classmethod
|
||||
def _sqllog(c,sqlalogging = None):
|
||||
return bool(sqlalogging) if sqlalogging != None else c.sqlalogging
|
||||
|
||||
@classmethod
|
||||
def _getEngine(c, read=True, sqlalogging = None):
|
||||
""" Return a sqlalchemy engine
|
||||
@param read bool: If True return the read engine, else
|
||||
return the write one
|
||||
@return a sqlachemy engine instance
|
||||
|
||||
@todo Put the check on db config in SqlWrapper.checkConf()
|
||||
"""
|
||||
#Loading confs
|
||||
connconf = 'dbread' if read else 'dbwrite'
|
||||
dbconf = connconf if connconf in c.config['db'] else 'default'
|
||||
|
||||
join_object = None
|
||||
if dbconf not in c.config['db']: #This check should not be here
|
||||
raise NameError('Configuration error no db "'+dbconf+'" in \
|
||||
configuration files')
|
||||
|
||||
cfg = c.config['db'][dbconf] #Database config
|
||||
|
||||
edata = c.config['engines'][cfg['ENGINE']] #engine infos
|
||||
conn_str = cfg['ENGINE']+'+'+edata['driver']+'://'
|
||||
|
||||
if 'left' in join_params and 'right' in join_params:
|
||||
if isinstance(join_params['left'], dict):
|
||||
join_left_table_name = join_params['left']['table']
|
||||
join_left_field_name = join_params['left']['field']
|
||||
if cfg['ENGINE'] == 'sqlite':
|
||||
#Sqlite connection string
|
||||
conn_str = '%s+%s:///%s'%( cfg['ENGINE'],
|
||||
edata['driver'],
|
||||
cfg['NAME'])
|
||||
else:
|
||||
#Mysql and Postgres connection string
|
||||
user = cfg['USER']
|
||||
user += (':'+cfg['PASSWORD'] if 'PASSWORD' in cfg else '')
|
||||
|
||||
if 'HOST' not in cfg:
|
||||
logger.info('Not HOST in configuration, using \
|
||||
localhost')
|
||||
host = 'localhost'
|
||||
else:
|
||||
join_left_array = join_params['left'].split('.')
|
||||
join_left_table_name = join_left_array[0]
|
||||
join_left_field_name = join_left_array[1]
|
||||
host = cfg['HOST']
|
||||
|
||||
if isinstance(join_params['right'], dict):
|
||||
join_right_table_name = join_params['right']['table']
|
||||
join_right_field_name = join_params['right']['field']
|
||||
else:
|
||||
join_right_array = join_params['right'].split('.')
|
||||
join_right_table_name = join_right_array[0]
|
||||
join_right_field_name = join_right_array[1]
|
||||
host += (':'+cfg['PORT'] if 'PORT' in cfg else '')
|
||||
|
||||
left_table = self.get_table(join_left_table_name, sqlsettings.ACTION_TYPE_READ)
|
||||
left_field = getattr(left_table.c, join_left_field_name)
|
||||
conn_str = '%s+%s://'%(cfg['ENGINE'], edata['driver'])
|
||||
conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
|
||||
|
||||
right_table = self.get_table(join_right_table_name, sqlsettings.ACTION_TYPE_READ)
|
||||
right_field = getattr(right_table.c, join_right_field_name)
|
||||
return sqla.create_engine( conn_str, encoding=edata['encoding'],
|
||||
echo=c._sqllog(sqlalogging))
|
||||
|
||||
join_option_isouter = True if 'options' in join_params and 'outer' in join_params and join_params['outer']==True else False
|
||||
@classmethod
|
||||
def checkConf(c):
|
||||
""" Class method that check the configuration
|
||||
|
||||
Configuration looks like
|
||||
- db (mandatory)
|
||||
- ENGINE (mandatory)
|
||||
- NAME (mandatory)
|
||||
- USER
|
||||
- PASSWORD
|
||||
- engines (mandatory)
|
||||
- driver (mandatory)
|
||||
- encoding (mandatory)
|
||||
- dbread (mandatory if no default db)
|
||||
- dbwrite (mandatory if no default db)
|
||||
"""
|
||||
return True #TODO desactivate because buggy
|
||||
err = []
|
||||
if 'db' not in c.config:
|
||||
err.append('Missing "db" in configuration')
|
||||
else:
|
||||
if 'default' not in c.config['db']:
|
||||
if 'dbread' not in c.config:
|
||||
err.append('Missing "dbread" in configuration and\
|
||||
no "default" db declared')
|
||||
if 'dbwrite' not in c.config:
|
||||
err.append('Missing "dbwrite" in configuration and\
|
||||
no "default" db declared')
|
||||
for db in c.config['db']:
|
||||
if 'ENGINE' not in db:
|
||||
err.append('Missing "ENGINE" in database "'+db+'"')
|
||||
else:
|
||||
if db['ENGINE'] != 'sqlite' and 'USER' not in db:
|
||||
err.append('Missing "USER" in database "\
|
||||
'+db+'"')
|
||||
if 'NAME' not in db:
|
||||
err.append('Missing "NAME" in database "'+db+'"')
|
||||
|
||||
if len(c.config['engines']) == 0:
|
||||
err.append('Missing "engines" in configuration')
|
||||
for ename in c.config['engines']:
|
||||
engine = c.config['engines'][ename]
|
||||
if 'driver' not in engine:
|
||||
err.append('Missing "driver" in database engine "\
|
||||
'+ename+'"')
|
||||
if 'encoding' not in engine:
|
||||
err.append('Missing "encoding" in database engine "\
|
||||
'+ename+'"')
|
||||
|
||||
join_object = join(left_table, right_table, left_field == right_field,isouter=join_option_isouter)
|
||||
if len(err)>0:
|
||||
err_str = ""
|
||||
for e in err:
|
||||
err_str += e+"\n"
|
||||
raise NameError('Configuration errors in LODEL2SQLWRAPPER\
|
||||
:'+err_str)
|
||||
|
||||
|
||||
@property
|
||||
def cfg(self):
|
||||
""" Get the dict of options for the wrapper
|
||||
Its an alias to the classes property SqlWrapper.config
|
||||
@return a dict containing the Db settings"""
|
||||
return self.__class__.config
|
||||
|
||||
return join_object
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue