persistence.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #
  2. # ---- persistence global module ----
  3. #
  4. # this module has the purpose to manage the database (sqlite) general mechanism (creating, accessing, etc...)
  5. #
  6. import logging
  7. from utility.app_logging import logger_name
  8. from sqlalchemy import create_engine
  9. from sqlalchemy.ext.declarative import declarative_base
  10. from sqlalchemy.orm.session import sessionmaker
  11. from sqlalchemy.orm import scoped_session
  12. from sqlalchemy.pool import SingletonThreadPool
  13. from sqlalchemy_utils import database_exists, create_database
  14. logger = logging.getLogger(logger_name + ".PERSISTENCE")
  15. __db_engine = None
  16. __db_session = None
  17. __sqlite_db_path = "OpenISP.db"
  18. __sqlite_db_pattern = 'sqlite:///' + __sqlite_db_path
  19. __initialized = False
  20. __db_Base = declarative_base()
  21. def get_session():
  22. if not __initialized:
  23. logger.error(
  24. "can't get session, persistence engine is not initialized")
  25. raise "can't get session, persistence engine is not initialized"
  26. return __db_session
  27. def get_Session_Instance():
  28. if not __initialized:
  29. logger.error(
  30. "can't get session, persistence engine is not initialized")
  31. raise "can't get session, persistence engine is not initialized"
  32. session_instance = __db_session()
  33. return session_instance
  34. def get_db_base():
  35. if __initialized:
  36. logger.error(
  37. "can't get declarative Base, persistence engine is already initialized")
  38. raise "can't get declarative Base, persistence engine is already initialized"
  39. return __db_Base
  40. def is_init():
  41. return __initialized
  42. def init():
  43. logger.debug("creating db engine....")
  44. global __db_engine
  45. __db_engine = create_engine(__sqlite_db_pattern,
  46. connect_args={'check_same_thread': False}) # Warning that's not so safe.
  47. # from https://stackoverflow.com/questions/2614984/sqlite-sqlalchemy-how-to-enforce-foreign-keys
  48. def _fk_pragma_on_connect(dbapi_con, con_record):
  49. dbapi_con.execute('pragma foreign_keys=ON')
  50. from sqlalchemy import event
  51. event.listen(__db_engine, 'connect', _fk_pragma_on_connect)
  52. # enforce sqlite foreign keys constraint option
  53. __db_engine.execute('pragma foreign_keys=on')
  54. logger.info("Database Engine Created.")
  55. logger.debug("building declarative model...")
  56. import Model.model_manager
  57. Model.model_manager.declare_model()
  58. logger.info("declarative model built.")
  59. if not database_exists(__db_engine.url):
  60. logger.info("Database is not created, creating one...")
  61. create_database(__db_engine.url)
  62. logger.info("Done !")
  63. __db_Base.metadata.create_all(__db_engine)
  64. if is_sane_database(__db_Base, __db_engine):
  65. logger.info("database is compliant to the model")
  66. else:
  67. logger.error("database is NOT compliant to the model")
  68. raise Exception("database is not compliant to the model !")
  69. global __db_session
  70. __db_session = scoped_session(sessionmaker(bind=__db_engine))
  71. global __initialized
  72. __initialized = True
  73. logger.info("Persistence Session started.")
  74. def is_sane_database(Base, engine):
  75. from sqlalchemy import inspect
  76. from sqlalchemy.orm.clsregistry import _ModuleMarker
  77. from sqlalchemy.orm import RelationshipProperty
  78. iengine = inspect(engine)
  79. current_db_tables = iengine.get_table_names()
  80. model_tables = Base.metadata.sorted_tables
  81. for model_table in model_tables:
  82. logger.debug("table '" + model_table.name +
  83. "' in model, checking in database...")
  84. table_found = False
  85. for db_table in current_db_tables:
  86. logger.debug("checking db table : " + db_table)
  87. if db_table == model_table.name:
  88. table_found = True
  89. logger.debug("model table found in db : " + model_table.name)
  90. break
  91. if not table_found:
  92. logger.error("model table not found in db : " + model_table.name)
  93. return False
  94. logger.debug(
  95. "now checking columns informations between model and database for table : " + model_table.name)
  96. for model_col in model_table.columns:
  97. logger.debug("checking model column : " + model_col.name)
  98. col_found = False
  99. for db_col in iengine.get_columns(model_table.name):
  100. logger.debug("with db table column : " + db_col["name"])
  101. if model_col.name == db_col["name"]:
  102. # TODO type checking and other attributes
  103. """
  104. #checking type (model type are formatted like "VARCHAR(500)")
  105. # (db type are formatted like "VARCHAR(length=500)")
  106. print(type(model_col.type))
  107. str1 = re.sub(r'[A-Z]+', ' ', str(model_col.type))
  108. str2 = re.sub(r'[A-Z]+', ' ', db_col["type"])
  109. if str1 != str2 :
  110. logger.debug("table column : '" + db_col["name"] + "' does not have the same type.")
  111. """
  112. col_found = True
  113. logger.debug(
  114. "table column : '" + db_col["name"] + "' found and compliant to the model table informations")
  115. break
  116. if not col_found:
  117. logger.error("column '" + model_col.name +
  118. "' in table '" + model_table.name + "'not found in db ")
  119. return False
  120. # checking name and type
  121. return True
  122. def wipeout_database():
  123. import os
  124. os.remove(__sqlite_db_path)
  125. # TODO adapt to program, find what is an inspector.
  126. def drop_all_tables(engine, inspector, schema=None, include_names=None):
  127. from sqlalchemy import Column, Table, Integer, MetaData, \
  128. ForeignKeyConstraint
  129. from sqlalchemy.schema import DropTable, DropConstraint
  130. if include_names is not None:
  131. include_names = set(include_names)
  132. with engine.connect() as conn:
  133. for tname, fkcs in reversed(
  134. inspector.get_sorted_table_and_fkc_names(schema=schema)):
  135. if tname:
  136. if include_names is not None and tname not in include_names:
  137. continue
  138. conn.execute(DropTable(
  139. Table(tname, MetaData(), schema=schema)
  140. ))
  141. elif fkcs:
  142. if not engine.dialect.supports_alter:
  143. continue
  144. for tname, fkc in fkcs:
  145. if include_names is not None and \
  146. tname not in include_names:
  147. continue
  148. tb = Table(
  149. tname, MetaData(),
  150. Column('x', Integer),
  151. Column('y', Integer),
  152. schema=schema
  153. )
  154. conn.execute(DropConstraint(
  155. ForeignKeyConstraint(
  156. [tb.c.x], [tb.c.y], name=fkc)
  157. ))