web-based photo library management software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dbutils.py 4.1 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import sqlalchemy
  2. import cherrypy
  3. from cherrypy.process import plugins
  4. from sqlalchemy.ext.declarative import declarative_base
  5. from sqlalchemy.pool import NullPool
  6. from sqlalchemy.orm import sessionmaker
  7. from sqlalchemy import func
  8. Base = declarative_base()
  9. engine_specific_options = {"sqlite": dict(connect_args={'check_same_thread': False},
  10. poolclass=NullPool,
  11. pool_pre_ping=True),
  12. "mysql": dict(pool_pre_ping=True)}
  13. def get_engine_options(uri):
  14. for engine_prefix, options in engine_specific_options.items():
  15. if uri.startswith(engine_prefix):
  16. return options
  17. return {}
  18. def get_db_engine(uri, debug=False):
  19. engine = sqlalchemy.create_engine(uri, **get_engine_options(uri), echo=debug)
  20. Base.metadata.create_all(engine)
  21. return engine
  22. def get_db_session(uri):
  23. engine = get_db_engine(uri)
  24. session = sessionmaker()
  25. session.configure(bind=engine)
  26. return session
  27. def driver_statement(statements):
  28. """
  29. Select a value from the passed dict based on the sql driver in use. Must be used in request context. For example:
  30. Sqlite and mysql use different date functions. This function can be used to build queries supporting either:
  31. date_format = driver_statement({"sqlite": lambda date_format, value: func.strftime(date_format, value),
  32. "mysql": lambda date_format, value: func.date_format(value, date_format)})
  33. rows = db.query(PhotoSet.id, date_format('%Y-%m=%d', PhotoSet.date).label('year')).all()
  34. :param statements: dict of driver_type->value. since sqlalchemy drivers vary per language (e.g. pymysql, pysqlite),
  35. it is checked if the driver_type is a substring of sqlalchemy's driver name.
  36. :type statements: dict
  37. """
  38. driver = cherrypy.request.db.connection().engine.driver
  39. for key, lambda_ in statements.items():
  40. if key in driver:
  41. return lambda_
  42. raise Exception(f"Statement not supported for driver {driver}")
  43. def date_format(date_format, value):
  44. stmt = driver_statement({"sqlite": lambda date_format, value: func.strftime(date_format, value),
  45. "mysql": lambda date_format, value: func.date_format(value, date_format)})
  46. return stmt(date_format, value)
  47. class DbAlias(object):
  48. """
  49. This provides a shorter alias for the cherrypy.request.db object, which is a database session created bound to the
  50. current request. Since the `db` attribute doesn't exist until a request is received, we cannot simply reference it
  51. with another variable.
  52. And instance of this class acts as an object proxy to the database object in cherrypy.request.db.
  53. """
  54. def __getattr__(self, attr):
  55. return getattr(cherrypy.request.db, attr)
  56. db = DbAlias()
  57. class SAEnginePlugin(plugins.SimplePlugin):
  58. def __init__(self, bus, dbcon):
  59. plugins.SimplePlugin.__init__(self, bus)
  60. self.sa_engine = dbcon
  61. self.bus.subscribe("bind", self.bind)
  62. def start(self):
  63. Base.metadata.create_all(self.sa_engine)
  64. def bind(self, session):
  65. session.configure(bind=self.sa_engine)
  66. class SATool(cherrypy.Tool):
  67. def __init__(self):
  68. cherrypy.Tool.__init__(self, 'before_request_body',
  69. self.bind_session,
  70. priority=49) # slightly earlier than Sessions tool, which is 50 or 60
  71. self.session = sqlalchemy.orm.scoped_session(
  72. sqlalchemy.orm.sessionmaker(autoflush=True, autocommit=False))
  73. def _setup(self):
  74. cherrypy.Tool._setup(self)
  75. cherrypy.request.hooks.attach('on_end_resource', self.commit_transaction, priority=80)
  76. def bind_session(self):
  77. cherrypy.engine.publish('bind', self.session)
  78. cherrypy.request.db = self.session
  79. def commit_transaction(self):
  80. cherrypy.request.db = None
  81. try:
  82. self.session.commit()
  83. except Exception:
  84. self.session.rollback()
  85. raise
  86. finally:
  87. self.session.remove()