fix: require zope.sqlalchemy >= 1.5
so we can do away with some old cruft, since latest zope.sqlalchemy is 3.1 from 2023-09-12
This commit is contained in:
		
							parent
							
								
									aab4dec27e
								
							
						
					
					
						commit
						d72d6f8c7c
					
				
					 5 changed files with 129 additions and 118 deletions
				
			
		
							
								
								
									
										6
									
								
								docs/api/db.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docs/api/db.rst
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | 
 | ||||||
|  | ``tailbone.db`` | ||||||
|  | =============== | ||||||
|  | 
 | ||||||
|  | .. automodule:: tailbone.db | ||||||
|  |   :members: | ||||||
|  | @ -44,6 +44,7 @@ Package API: | ||||||
| 
 | 
 | ||||||
|    api/api/batch/core |    api/api/batch/core | ||||||
|    api/api/batch/ordering |    api/api/batch/ordering | ||||||
|  |    api/db | ||||||
|    api/diffs |    api/diffs | ||||||
|    api/forms |    api/forms | ||||||
|    api/forms.widgets |    api/forms.widgets | ||||||
|  |  | ||||||
|  | @ -61,7 +61,7 @@ install_requires = | ||||||
|         transaction |         transaction | ||||||
|         waitress |         waitress | ||||||
|         WebHelpers2 |         WebHelpers2 | ||||||
|         zope.sqlalchemy |         zope.sqlalchemy>=1.5 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| [options.packages.find] | [options.packages.find] | ||||||
|  |  | ||||||
							
								
								
									
										159
									
								
								tailbone/db.py
									
										
									
									
									
								
							
							
						
						
									
										159
									
								
								tailbone/db.py
									
										
									
									
									
								
							|  | @ -2,7 +2,7 @@ | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # | # | ||||||
| #  Rattail -- Retail Software Framework | #  Rattail -- Retail Software Framework | ||||||
| #  Copyright © 2010-2023 Lance Edgar | #  Copyright © 2010-2024 Lance Edgar | ||||||
| # | # | ||||||
| #  This file is part of Rattail. | #  This file is part of Rattail. | ||||||
| # | # | ||||||
|  | @ -21,14 +21,13 @@ | ||||||
| # | # | ||||||
| ################################################################################ | ################################################################################ | ||||||
| """ | """ | ||||||
| Database Stuff | Database sessions etc. | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| import sqlalchemy as sa | import sqlalchemy as sa | ||||||
| from zope.sqlalchemy import datamanager | from zope.sqlalchemy import datamanager | ||||||
| import sqlalchemy_continuum as continuum | import sqlalchemy_continuum as continuum | ||||||
| from sqlalchemy.orm import sessionmaker, scoped_session | from sqlalchemy.orm import sessionmaker, scoped_session | ||||||
| from pkg_resources import get_distribution, parse_version |  | ||||||
| 
 | 
 | ||||||
| from rattail.db import SessionBase | from rattail.db import SessionBase | ||||||
| from rattail.db.continuum import versioning_manager | from rattail.db.continuum import versioning_manager | ||||||
|  | @ -43,23 +42,28 @@ TrainwreckSession = scoped_session(sessionmaker()) | ||||||
| # empty dict for now, this must populated on app startup (if needed) | # empty dict for now, this must populated on app startup (if needed) | ||||||
| ExtraTrainwreckSessions = {} | ExtraTrainwreckSessions = {} | ||||||
| 
 | 
 | ||||||
| # some of the logic below may need to vary somewhat, based on which version of |  | ||||||
| # zope.sqlalchemy we have installed |  | ||||||
| zope_sqlalchemy_version = get_distribution('zope.sqlalchemy').version |  | ||||||
| zope_sqlalchemy_version_parsed = parse_version(zope_sqlalchemy_version) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class TailboneSessionDataManager(datamanager.SessionDataManager): | class TailboneSessionDataManager(datamanager.SessionDataManager): | ||||||
|     """Integrate a top level sqlalchemy session transaction into a zope transaction |     """ | ||||||
|  |     Integrate a top level sqlalchemy session transaction into a zope | ||||||
|  |     transaction | ||||||
| 
 | 
 | ||||||
|     One phase variant. |     One phase variant. | ||||||
| 
 | 
 | ||||||
|     .. note:: |     .. note:: | ||||||
|        This class appears to be necessary in order for the Continuum | 
 | ||||||
|        integration to work alongside the Zope transaction integration. |        This class appears to be necessary in order for the | ||||||
|  |        SQLAlchemy-Continuum integration to work alongside the Zope | ||||||
|  |        transaction integration. | ||||||
|  | 
 | ||||||
|  |        It subclasses | ||||||
|  |        ``zope.sqlalchemy.datamanager.SessionDataManager`` but injects | ||||||
|  |        some SQLAlchemy-Continuum logic within :meth:`tpc_vote()`, and | ||||||
|  |        is sort of monkey-patched into the mix. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def tpc_vote(self, trans): |     def tpc_vote(self, trans): | ||||||
|  |         """ """ | ||||||
|         # for a one phase data manager commit last in tpc_vote |         # for a one phase data manager commit last in tpc_vote | ||||||
|         if self.tx is not None:  # there may have been no work to do |         if self.tx is not None:  # there may have been no work to do | ||||||
| 
 | 
 | ||||||
|  | @ -71,28 +75,41 @@ class TailboneSessionDataManager(datamanager.SessionDataManager): | ||||||
|             self._finish('committed') |             self._finish('committed') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def join_transaction(session, initial_state=datamanager.STATUS_ACTIVE, transaction_manager=datamanager.zope_transaction.manager, keep_session=False): | def join_transaction( | ||||||
|     """Join a session to a transaction using the appropriate datamanager. |         session, | ||||||
|  |         initial_state=datamanager.STATUS_ACTIVE, | ||||||
|  |         transaction_manager=datamanager.zope_transaction.manager, | ||||||
|  |         keep_session=False, | ||||||
|  | ): | ||||||
|  |     """ | ||||||
|  |     Join a session to a transaction using the appropriate datamanager. | ||||||
| 
 | 
 | ||||||
|     It is safe to call this multiple times, if the session is already joined |     It is safe to call this multiple times, if the session is already | ||||||
|     then it just returns. |     joined then it just returns. | ||||||
| 
 | 
 | ||||||
|     `initial_state` is either STATUS_ACTIVE, STATUS_INVALIDATED or STATUS_READONLY |     `initial_state` is either STATUS_ACTIVE, STATUS_INVALIDATED or | ||||||
|  |     STATUS_READONLY | ||||||
| 
 | 
 | ||||||
|     If using the default initial status of STATUS_ACTIVE, you must ensure that |     If using the default initial status of STATUS_ACTIVE, you must | ||||||
|     mark_changed(session) is called when data is written to the database. |     ensure that mark_changed(session) is called when data is written | ||||||
|  |     to the database. | ||||||
| 
 | 
 | ||||||
|     The ZopeTransactionExtesion SessionExtension can be used to ensure that this is |     The ZopeTransactionExtesion SessionExtension can be used to ensure | ||||||
|     called automatically after session write operations. |     that this is called automatically after session write operations. | ||||||
| 
 | 
 | ||||||
|     .. note:: |     .. note:: | ||||||
|        This function is copied from upstream, and tweaked so that our custom | 
 | ||||||
|        :class:`TailboneSessionDataManager` will be used. |        This function appears to be necessary in order for the | ||||||
|  |        SQLAlchemy-Continuum integration to work alongside the Zope | ||||||
|  |        transaction integration. | ||||||
|  | 
 | ||||||
|  |        It overrides ``zope.sqlalchemy.datamanager.join_transaction()`` | ||||||
|  |        to ensure the custom :class:`TailboneSessionDataManager` is | ||||||
|  |        used, and is sort of monkey-patched into the mix. | ||||||
|     """ |     """ | ||||||
|     # the upstream internals of this function has changed a little over time. |     # the upstream internals of this function has changed a little over time. | ||||||
|     # unfortunately for us, that means we must include each variant here. |     # unfortunately for us, that means we must include each variant here. | ||||||
| 
 | 
 | ||||||
|     if zope_sqlalchemy_version_parsed >= parse_version('1.1'): # 1.1+ |  | ||||||
|     if datamanager._SESSION_STATE.get(session, None) is None: |     if datamanager._SESSION_STATE.get(session, None) is None: | ||||||
|         if session.twophase: |         if session.twophase: | ||||||
|             DataManager = datamanager.TwoPhaseSessionDataManager |             DataManager = datamanager.TwoPhaseSessionDataManager | ||||||
|  | @ -100,98 +117,79 @@ def join_transaction(session, initial_state=datamanager.STATUS_ACTIVE, transacti | ||||||
|             DataManager = TailboneSessionDataManager |             DataManager = TailboneSessionDataManager | ||||||
|         DataManager(session, initial_state, transaction_manager, keep_session=keep_session) |         DataManager(session, initial_state, transaction_manager, keep_session=keep_session) | ||||||
| 
 | 
 | ||||||
|     else: # pre-1.1 |  | ||||||
|         if datamanager._SESSION_STATE.get(id(session), None) is None: |  | ||||||
|             if session.twophase: |  | ||||||
|                 DataManager = datamanager.TwoPhaseSessionDataManager |  | ||||||
|             else: |  | ||||||
|                 DataManager = TailboneSessionDataManager |  | ||||||
|             DataManager(session, initial_state, transaction_manager, keep_session=keep_session) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if zope_sqlalchemy_version_parsed >= parse_version('1.2'): # 1.2+ |  | ||||||
| 
 | 
 | ||||||
| class ZopeTransactionEvents(datamanager.ZopeTransactionEvents): | class ZopeTransactionEvents(datamanager.ZopeTransactionEvents): | ||||||
|     """ |     """ | ||||||
|         Record that a flush has occurred on a session's |     Record that a flush has occurred on a session's connection. This | ||||||
|         connection. This allows the DataManager to rollback rather |     allows the DataManager to rollback rather than commit on read only | ||||||
|         than commit on read only transactions. |     transactions. | ||||||
| 
 | 
 | ||||||
|     .. note:: |     .. note:: | ||||||
|            This class is copied from upstream, and tweaked so that our | 
 | ||||||
|            custom :func:`join_transaction()` will be used. |        This class appears to be necessary in order for the | ||||||
|  |        SQLAlchemy-Continuum integration to work alongside the Zope | ||||||
|  |        transaction integration. | ||||||
|  | 
 | ||||||
|  |        It subclasses | ||||||
|  |        ``zope.sqlalchemy.datamanager.ZopeTransactionEvents`` but | ||||||
|  |        overrides various methods to ensure the custom | ||||||
|  |        :func:`join_transaction()` is called, and is sort of | ||||||
|  |        monkey-patched into the mix. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def after_begin(self, session, transaction, connection): |     def after_begin(self, session, transaction, connection): | ||||||
|  |         """ """ | ||||||
|         join_transaction(session, self.initial_state, |         join_transaction(session, self.initial_state, | ||||||
|                          self.transaction_manager, self.keep_session) |                          self.transaction_manager, self.keep_session) | ||||||
| 
 | 
 | ||||||
|     def after_attach(self, session, instance): |     def after_attach(self, session, instance): | ||||||
|  |         """ """ | ||||||
|         join_transaction(session, self.initial_state, |         join_transaction(session, self.initial_state, | ||||||
|                          self.transaction_manager, self.keep_session) |                          self.transaction_manager, self.keep_session) | ||||||
| 
 | 
 | ||||||
|     def join_transaction(self, session): |     def join_transaction(self, session): | ||||||
|  |         """ """ | ||||||
|         join_transaction(session, self.initial_state, |         join_transaction(session, self.initial_state, | ||||||
|                          self.transaction_manager, self.keep_session) |                          self.transaction_manager, self.keep_session) | ||||||
| 
 | 
 | ||||||
| else: # pre-1.2 |  | ||||||
| 
 | 
 | ||||||
|     class ZopeTransactionExtension(datamanager.ZopeTransactionExtension): | def register( | ||||||
|  |         session, | ||||||
|  |         initial_state=datamanager.STATUS_ACTIVE, | ||||||
|  |         transaction_manager=datamanager.zope_transaction.manager, | ||||||
|  |         keep_session=False, | ||||||
|  | ): | ||||||
|     """ |     """ | ||||||
|         Record that a flush has occurred on a session's |     Register ZopeTransaction listener events on the given Session or | ||||||
|         connection. This allows the DataManager to rollback rather |     Session factory/class. | ||||||
|         than commit on read only transactions. |  | ||||||
| 
 | 
 | ||||||
|         .. note:: |     This function requires at least SQLAlchemy 0.7 and makes use of | ||||||
|            This class is copied from upstream, and tweaked so that our |     the newer sqlalchemy.event package in order to register event | ||||||
|            custom :func:`join_transaction()` will be used. |     listeners on the given Session. | ||||||
|         """ |  | ||||||
| 
 |  | ||||||
|         def after_begin(self, session, transaction, connection): |  | ||||||
|             join_transaction(session, self.initial_state, |  | ||||||
|                              self.transaction_manager, self.keep_session) |  | ||||||
| 
 |  | ||||||
|         def after_attach(self, session, instance): |  | ||||||
|             join_transaction(session, self.initial_state, |  | ||||||
|                              self.transaction_manager, self.keep_session) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def register(session, initial_state=datamanager.STATUS_ACTIVE, |  | ||||||
|              transaction_manager=datamanager.zope_transaction.manager, keep_session=False): |  | ||||||
|     """Register ZopeTransaction listener events on the |  | ||||||
|     given Session or Session factory/class. |  | ||||||
| 
 |  | ||||||
|     This function requires at least SQLAlchemy 0.7 and makes use |  | ||||||
|     of the newer sqlalchemy.event package in order to register event listeners |  | ||||||
|     on the given Session. |  | ||||||
| 
 | 
 | ||||||
|     The session argument here may be a Session class or subclass, a |     The session argument here may be a Session class or subclass, a | ||||||
|     sessionmaker or scoped_session instance, or a specific Session instance. |     sessionmaker or scoped_session instance, or a specific Session | ||||||
|     Event listening will be specific to the scope of the type of argument |     instance.  Event listening will be specific to the scope of the | ||||||
|     passed, including specificity to its subclass as well as its identity. |     type of argument passed, including specificity to its subclass as | ||||||
|  |     well as its identity. | ||||||
| 
 | 
 | ||||||
|     .. note:: |     .. note:: | ||||||
|        This function is copied from upstream, and tweaked so that our custom | 
 | ||||||
|        :class:`ZopeTransactionExtension` will be used. |        This function appears to be necessary in order for the | ||||||
|  |        SQLAlchemy-Continuum integration to work alongside the Zope | ||||||
|  |        transaction integration. | ||||||
|  | 
 | ||||||
|  |        It overrides ``zope.sqlalchemy.datamanager.regsiter()`` to | ||||||
|  |        ensure the custom :class:`ZopeTransactionEvents` is used. | ||||||
|     """ |     """ | ||||||
|     from sqlalchemy import event |     from sqlalchemy import event | ||||||
| 
 | 
 | ||||||
|     if zope_sqlalchemy_version_parsed >= parse_version('1.2'): # 1.2+ |  | ||||||
| 
 |  | ||||||
|     ext = ZopeTransactionEvents( |     ext = ZopeTransactionEvents( | ||||||
|         initial_state=initial_state, |         initial_state=initial_state, | ||||||
|         transaction_manager=transaction_manager, |         transaction_manager=transaction_manager, | ||||||
|         keep_session=keep_session, |         keep_session=keep_session, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     else: # pre-1.2 |  | ||||||
| 
 |  | ||||||
|         ext = ZopeTransactionExtension( |  | ||||||
|             initial_state=initial_state, |  | ||||||
|             transaction_manager=transaction_manager, |  | ||||||
|             keep_session=keep_session, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     event.listen(session, "after_begin", ext.after_begin) |     event.listen(session, "after_begin", ext.after_begin) | ||||||
|     event.listen(session, "after_attach", ext.after_attach) |     event.listen(session, "after_attach", ext.after_attach) | ||||||
|     event.listen(session, "after_flush", ext.after_flush) |     event.listen(session, "after_flush", ext.after_flush) | ||||||
|  | @ -199,7 +197,6 @@ def register(session, initial_state=datamanager.STATUS_ACTIVE, | ||||||
|     event.listen(session, "after_bulk_delete", ext.after_bulk_delete) |     event.listen(session, "after_bulk_delete", ext.after_bulk_delete) | ||||||
|     event.listen(session, "before_commit", ext.before_commit) |     event.listen(session, "before_commit", ext.before_commit) | ||||||
| 
 | 
 | ||||||
|     if zope_sqlalchemy_version_parsed >= parse_version('1.5'): # 1.5+ |  | ||||||
|     if datamanager.SA_GE_14: |     if datamanager.SA_GE_14: | ||||||
|         event.listen(session, "do_orm_execute", ext.do_orm_execute) |         event.listen(session, "do_orm_execute", ext.do_orm_execute) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								tests/test_db.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/test_db.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | # -*- coding: utf-8; -*- | ||||||
|  | 
 | ||||||
|  | # TODO: add real tests at some point but this at least gives us basic | ||||||
|  | # coverage when running this "test" module alone | ||||||
|  | 
 | ||||||
|  | from tailbone import db | ||||||
|  | 
 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar