risclog.sqlalchemy 6.0__tar.gz → 6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/CHANGES.rst +15 -0
  2. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/PKG-INFO +16 -1
  3. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/conf.py +0 -1
  4. risclog.sqlalchemy-6.2/pyproject.toml +60 -0
  5. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/requirements.txt +3 -1
  6. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/setup.py +28 -34
  7. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/cache.py +31 -24
  8. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/db.py +73 -46
  9. risclog.sqlalchemy-6.2/src/risclog/sqlalchemy/functions.py +8 -0
  10. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/interfaces.py +7 -0
  11. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/model.py +8 -6
  12. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/serializer.py +25 -9
  13. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/testing.py +17 -11
  14. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/testlayer.py +5 -3
  15. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/conftest.py +9 -6
  16. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/fixtures/alembic/env.py +6 -9
  17. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/fixtures/alembic/versions/3f735078d5a8_foo.py +1 -2
  18. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_cache.py +96 -37
  19. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_db.py +49 -30
  20. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_fixtures.py +3 -3
  21. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_model.py +6 -3
  22. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_serializer.py +11 -6
  23. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/PKG-INFO +16 -1
  24. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/SOURCES.txt +1 -0
  25. risclog.sqlalchemy-6.0/src/risclog/sqlalchemy/functions.py +0 -9
  26. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/COPYRIGHT.rst +0 -0
  27. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/HACKING.rst +0 -0
  28. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/LICENSE.txt +0 -0
  29. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/README.rst +0 -0
  30. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/about.txt +0 -0
  31. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/api.txt +0 -0
  32. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/changes.txt +0 -0
  33. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/index.txt +0 -0
  34. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/setup.cfg +0 -0
  35. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/__init__.py +0 -0
  36. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/__init__.py +0 -0
  37. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/fixtures.py +0 -0
  38. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/__init__.py +0 -0
  39. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/fixtures/alembic/script.py.mako +0 -0
  40. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/dependency_links.txt +0 -0
  41. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/namespace_packages.txt +0 -0
  42. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/not-zip-safe +0 -0
  43. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/requires.txt +0 -0
  44. {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/top_level.txt +0 -0
@@ -2,6 +2,21 @@
2
2
  Change log for risclog.sqlalchemy
3
3
  =================================
4
4
 
5
+ 6.2 (2024-04-19)
6
+ ================
7
+
8
+ - Add json encoder for `datetime.time` objects.
9
+
10
+
11
+ 6.1 (2024-03-12)
12
+ ================
13
+
14
+ - Add json encoder for `uuid.UUID` objects.
15
+
16
+ - Add `risclog.sqlalchemy.interfaces.Deleted` event which is fired when an
17
+ object is deleted from the database.
18
+
19
+
5
20
  6.0 (2023-09-26)
6
21
  ================
7
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risclog.sqlalchemy
3
- Version: 6.0
3
+ Version: 6.2
4
4
  Summary: Encapsulate sqlalchemy modelling infrastructure.
5
5
  Home-page: https://github.com/gocept/risclog.sqlalchemy
6
6
  Author: gocept <mail@gocept.com>
@@ -106,6 +106,21 @@ python, where ``sphinx-pypi-upload`` is installed::
106
106
  Change log for risclog.sqlalchemy
107
107
  =================================
108
108
 
109
+ 6.2 (2024-04-19)
110
+ ================
111
+
112
+ - Add json encoder for `datetime.time` objects.
113
+
114
+
115
+ 6.1 (2024-03-12)
116
+ ================
117
+
118
+ - Add json encoder for `uuid.UUID` objects.
119
+
120
+ - Add `risclog.sqlalchemy.interfaces.Deleted` event which is fired when an
121
+ object is deleted from the database.
122
+
123
+
109
124
  6.0 (2023-09-26)
110
125
  ================
111
126
 
@@ -1,7 +1,6 @@
1
1
  import os
2
2
  import sys
3
3
 
4
-
5
4
  # If extensions (or modules to document with autodoc) are in another directory,
6
5
  # add these directories to sys.path here. If the directory is relative to the
7
6
  # documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["setuptools>=40.6.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.coverage.run]
6
+ relative_files = true
7
+ parallel = true
8
+ branch = true
9
+ source = ["risclog.sqlalchemy"]
10
+
11
+ [tool.coverage.paths]
12
+ source = ["src", ".pytest/*/site-packages"]
13
+
14
+ [tool.coverage.html]
15
+ directory = "coverage-report"
16
+
17
+ [tool.coverage.report]
18
+ precision = 2
19
+ show_missing = true
20
+ skip_covered = true
21
+ exclude_lines = [
22
+ # a more strict default pragma
23
+ "\\# pragma: no cover\\b",
24
+
25
+ # allow defensive code
26
+ "^\\s*raise AssertionError\\b",
27
+ "^\\s*raise NotImplementedError\\b",
28
+ "^\\s*return NotImplemented\\b",
29
+ "^\\s*raise$",
30
+
31
+ # typing-related code
32
+ "^if (False|TYPE_CHECKING):",
33
+ ": \\.\\.\\.(\\s*#.*)?$",
34
+ "^ +\\.\\.\\.$",
35
+ "-> ['\"]?NoReturn['\"]?:",
36
+ ]
37
+
38
+ [tool.black]
39
+ line-length = 79
40
+ target-version = ['py38']
41
+ exclude = '''
42
+ /(
43
+ \.eggs
44
+ | eggs
45
+ | venv
46
+ | \.git
47
+ | \.hg
48
+ | \.mypy_cache
49
+ | \.tox
50
+ | \.venv
51
+ | _build
52
+ | buck-out
53
+ | build
54
+ | dist
55
+ )/
56
+ '''
57
+ skip-string-normalization = true
58
+
59
+ [tool.check-manifest]
60
+ ignore-bad-ideas = ["*.mo"]
@@ -1,8 +1,10 @@
1
1
  # Created by batou. Do not edit manually.
2
2
  # appenv-python-preference: 3.7,3.8,3.9
3
3
  -e .[test]
4
- sqlalchemy<1.4
4
+ sqlalchemy<2.0
5
5
  alembic
6
6
  psycopg2-binary
7
7
  pytest
8
8
  pytest-timeout
9
+ pytest-cov
10
+ greenlet
@@ -3,14 +3,13 @@
3
3
  """Encapsulate sqlalchemy modelling infrastructure.
4
4
  """
5
5
 
6
- from setuptools import find_packages
7
- from setuptools import setup
8
6
  import glob
9
7
 
8
+ from setuptools import find_packages, setup
10
9
 
11
10
  setup(
12
11
  name='risclog.sqlalchemy',
13
- version='6.0',
12
+ version='6.2',
14
13
  python_requires='>=3.7',
15
14
  install_requires=[
16
15
  'SQLAlchemy >= 1.0, < 2',
@@ -22,7 +21,6 @@ setup(
22
21
  'zope.interface',
23
22
  'zope.sqlalchemy >= 1.3',
24
23
  ],
25
-
26
24
  extras_require={
27
25
  'test': [
28
26
  'gocept.testdb',
@@ -32,47 +30,43 @@ setup(
32
30
  'pyramid',
33
31
  'pytest',
34
32
  ],
35
- 'pyramid': [
36
- 'pyramid'
37
- ],
33
+ 'pyramid': ['pyramid'],
38
34
  },
39
-
40
35
  entry_points={},
41
-
42
36
  author='gocept <mail@gocept.com>',
43
37
  author_email='mail@gocept.com',
44
38
  license='ZPL 2.1',
45
39
  url='https://github.com/gocept/risclog.sqlalchemy',
46
-
47
40
  keywords='sqlalchemy alembic model infrastructure utility',
48
41
  classifiers=[
49
- "Development Status :: 5 - Production/Stable",
50
- "Framework :: Pyramid",
51
- "Intended Audience :: Developers",
52
- "License :: OSI Approved",
53
- "License :: OSI Approved :: Zope Public License",
54
- "Natural Language :: English",
55
- "Operating System :: OS Independent",
56
- "Programming Language :: Python",
57
- "Programming Language :: Python :: 3",
58
- "Programming Language :: Python :: 3.7",
59
- "Programming Language :: Python :: 3.8",
60
- "Programming Language :: Python :: 3.9",
61
- "Programming Language :: Python :: 3.10",
62
- "Programming Language :: Python :: 3.11",
63
- "Programming Language :: Python :: Implementation",
64
- "Programming Language :: Python :: Implementation :: CPython",
65
- "Topic :: Database",
66
- "Topic :: Software Development :: Libraries",
67
- "Topic :: Software Development :: Libraries :: Python Modules",
42
+ 'Development Status :: 5 - Production/Stable',
43
+ 'Framework :: Pyramid',
44
+ 'Intended Audience :: Developers',
45
+ 'License :: OSI Approved',
46
+ 'License :: OSI Approved :: Zope Public License',
47
+ 'Natural Language :: English',
48
+ 'Operating System :: OS Independent',
49
+ 'Programming Language :: Python',
50
+ 'Programming Language :: Python :: 3',
51
+ 'Programming Language :: Python :: 3.7',
52
+ 'Programming Language :: Python :: 3.8',
53
+ 'Programming Language :: Python :: 3.9',
54
+ 'Programming Language :: Python :: 3.10',
55
+ 'Programming Language :: Python :: 3.11',
56
+ 'Programming Language :: Python :: Implementation',
57
+ 'Programming Language :: Python :: Implementation :: CPython',
58
+ 'Topic :: Database',
59
+ 'Topic :: Software Development :: Libraries',
60
+ 'Topic :: Software Development :: Libraries :: Python Modules',
68
61
  ],
69
62
  description=__doc__.strip(),
70
63
  long_description=(
71
- open('README.rst').read() +
72
- '\n\n' +
73
- open('HACKING.rst').read() +
74
- '\n\n' +
75
- open('CHANGES.rst').read()),
64
+ open('README.rst').read()
65
+ + '\n\n'
66
+ + open('HACKING.rst').read()
67
+ + '\n\n'
68
+ + open('CHANGES.rst').read()
69
+ ),
76
70
  namespace_packages=['risclog'],
77
71
  packages=find_packages('src'),
78
72
  package_dir={'': 'src'},
@@ -1,12 +1,13 @@
1
- from risclog.sqlalchemy.model import ObjectBase
2
- from sqlalchemy.inspection import inspect
3
- from sqlalchemy.orm import attributes
4
1
  import csv
5
2
  import gc
6
3
  import io
7
- import sqlalchemy
8
4
  import sys
9
5
 
6
+ import sqlalchemy
7
+ from risclog.sqlalchemy.model import ObjectBase
8
+ from sqlalchemy.inspection import inspect
9
+ from sqlalchemy.orm import attributes
10
+
10
11
 
11
12
  class MultipleObjectsFoundException(Exception):
12
13
  """Raised when a single result was expected but multiple were found."""
@@ -49,18 +50,18 @@ class ModelCache:
49
50
  """
50
51
 
51
52
  def __init__(
52
- self,
53
- save_order,
54
- sequences,
55
- session,
56
- engine_name,
57
- prefetch=None,
58
- preload_models=True,
59
- preload_models_data={},
60
- preload_models_filter={},
61
- logger=None,
62
- use_copy=False,
63
- check_memory_usage=False,
53
+ self,
54
+ save_order,
55
+ sequences,
56
+ session,
57
+ engine_name,
58
+ prefetch=None,
59
+ preload_models=True,
60
+ preload_models_data={},
61
+ preload_models_filter={},
62
+ logger=None,
63
+ use_copy=False,
64
+ check_memory_usage=False,
64
65
  ):
65
66
  """
66
67
  Args:
@@ -94,6 +95,7 @@ class ModelCache:
94
95
 
95
96
  if check_memory_usage:
96
97
  from guppy import hpy
98
+
97
99
  self.hp = hpy()
98
100
  self.hp.setrelheap()
99
101
 
@@ -104,12 +106,12 @@ class ModelCache:
104
106
  h = self.hp.heap()
105
107
  return h.size
106
108
 
107
- def _sizeof_fmt(self, num, suffix="B"):
108
- for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
109
+ def _sizeof_fmt(self, num, suffix='B'):
110
+ for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
109
111
  if abs(num) < 1024.0:
110
- return f"{num:3.1f} {unit}{suffix}"
112
+ return f'{num:3.1f} {unit}{suffix}'
111
113
  num /= 1024.0
112
- return f"{num:.1f}Yi{suffix}"
114
+ return f'{num:.1f}Yi{suffix}'
113
115
 
114
116
  def log_memory_usage(self):
115
117
  if not hasattr(self, 'hp'):
@@ -197,8 +199,11 @@ class ModelCache:
197
199
  if session is None:
198
200
  session = self.session
199
201
  if cursor is None and self._use_copy:
200
- cursor = self.session.using_bind(
201
- self._engine_name).connection().connection.cursor()
202
+ cursor = (
203
+ self.session.using_bind(self._engine_name)
204
+ .connection()
205
+ .connection.cursor()
206
+ )
202
207
 
203
208
  self._log('debug', 'Flushing model cache.')
204
209
  self._assign_sequences()
@@ -213,7 +218,8 @@ class ModelCache:
213
218
  continue
214
219
 
215
220
  self._deregister_change_handler(
216
- type(objects[0]), self._instance_change_handler)
221
+ type(objects[0]), self._instance_change_handler
222
+ )
217
223
  new_objects, updated_objects = [], []
218
224
 
219
225
  for object in objects:
@@ -254,7 +260,8 @@ class ModelCache:
254
260
  file = io.StringIO()
255
261
  writer = csv.DictWriter(file, table.columns.keys())
256
262
  columns = [
257
- c for c in model.c.items()
263
+ c
264
+ for c in model.c.items()
258
265
  if c[1].table == table or c[1].primary_key
259
266
  ]
260
267
 
@@ -1,8 +1,9 @@
1
+ import os
2
+
1
3
  import alembic.config
2
4
  import alembic.environment
3
5
  import alembic.migration
4
6
  import alembic.script
5
- import os
6
7
  import risclog.sqlalchemy.interfaces
7
8
  import sqlalchemy
8
9
  import sqlalchemy.ext.declarative
@@ -12,15 +13,15 @@ import zope.component
12
13
  import zope.interface
13
14
  import zope.sqlalchemy
14
15
 
15
-
16
16
  # Mapping engine name registered using Database.register_engine --> base class
17
17
  _ENGINE_CLASS_MAPPING = {}
18
18
 
19
19
 
20
20
  def assert_engine_not_registered(name, mapping):
21
21
  """Ensure a consistent error message."""
22
- assert name not in mapping, \
23
- f'An engine for name `{name}` is already registered.'
22
+ assert (
23
+ name not in mapping
24
+ ), f'An engine for name `{name}` is already registered.'
24
25
 
25
26
 
26
27
  def register_class(class_):
@@ -51,23 +52,25 @@ class RoutingSession(sqlalchemy.orm.Session):
51
52
  if bind:
52
53
  return bind
53
54
  db_util = zope.component.getUtility(
54
- risclog.sqlalchemy.interfaces.IDatabase)
55
+ risclog.sqlalchemy.interfaces.IDatabase
56
+ )
55
57
  if self._name:
56
58
  # Engine was set using self.using_bind:
57
59
  return db_util.get_engine(self._name)
58
60
  if not mapper:
59
61
  if len(_ENGINE_CLASS_MAPPING) == 1:
60
62
  return db_util.get_engine(
61
- list(_ENGINE_CLASS_MAPPING.keys())[0])
63
+ list(_ENGINE_CLASS_MAPPING.keys())[0]
64
+ )
62
65
  raise RuntimeError(
63
- "Don't know how to determine engine, no mapper.")
66
+ "Don't know how to determine engine, no mapper."
67
+ )
64
68
 
65
69
  for engine_name, class_ in _ENGINE_CLASS_MAPPING.items():
66
70
  if issubclass(mapper.class_, class_):
67
71
  return db_util.get_engine(engine_name)
68
72
 
69
- raise RuntimeError(
70
- f"Did not find an engine for {mapper.class_}")
73
+ raise RuntimeError(f'Did not find an engine for {mapper.class_}')
71
74
 
72
75
  def using_bind(self, name):
73
76
  """Select an engine name if not using mappers."""
@@ -83,45 +86,55 @@ def get_database(testing=False, keep_session=False, expire_on_commit=False):
83
86
  # Keep the old testing behaviour by default
84
87
  keep_session = True
85
88
  expire_on_commit = True
86
- db = zope.component.queryUtility(
87
- risclog.sqlalchemy.interfaces.IDatabase)
89
+ db = zope.component.queryUtility(risclog.sqlalchemy.interfaces.IDatabase)
88
90
  if db is None:
89
91
  db = Database(testing, keep_session, expire_on_commit)
90
- assert db.testing == testing, \
91
- 'Requested testing status `%s` does not match Database.testing.' % (
92
- testing)
92
+ assert (
93
+ db.testing == testing
94
+ ), 'Requested testing status `%s` does not match Database.testing.' % (
95
+ testing
96
+ )
93
97
  return db
94
98
 
95
99
 
96
100
  @zope.interface.implementer(risclog.sqlalchemy.interfaces.IDatabase)
97
101
  class Database:
98
-
99
102
  def __init__(
100
103
  self, testing=False, keep_session=False, expire_on_commit=False
101
104
  ):
102
- assert zope.component.queryUtility(
103
- risclog.sqlalchemy.interfaces.IDatabase) is None, \
104
- 'Cannot create Database twice, use `.get_database()` to get '\
105
+ assert (
106
+ zope.component.queryUtility(
107
+ risclog.sqlalchemy.interfaces.IDatabase
108
+ )
109
+ is None
110
+ ), (
111
+ 'Cannot create Database twice, use `.get_database()` to get '
105
112
  'the instance.'
113
+ )
106
114
  self._engines = {}
107
115
  self.testing = testing
108
116
  self.session_factory = sqlalchemy.orm.scoped_session(
109
117
  sqlalchemy.orm.sessionmaker(
110
- class_=RoutingSession,
111
- expire_on_commit=expire_on_commit))
118
+ class_=RoutingSession, expire_on_commit=expire_on_commit
119
+ )
120
+ )
112
121
  self.zope_transaction_events = zope.sqlalchemy.register(
113
- self.session_factory, keep_session=keep_session)
122
+ self.session_factory, keep_session=keep_session
123
+ )
114
124
  self._setup_utility()
115
125
 
116
- def register_engine(self, dsn, engine_args={}, name='',
117
- alembic_location=None):
126
+ def register_engine(
127
+ self, dsn, engine_args={}, name='', alembic_location=None
128
+ ):
118
129
  assert_engine_not_registered(name, self._engines)
119
- engine_args['echo'] = bool(int(os.environ.get(
120
- 'ECHO_SQLALCHEMY_QUERIES', '0')))
130
+ engine_args['echo'] = bool(
131
+ int(os.environ.get('ECHO_SQLALCHEMY_QUERIES', '0'))
132
+ )
121
133
  engine = sqlalchemy.create_engine(dsn, **engine_args)
122
134
  self._verify_engine(engine)
123
135
  self._engines[name] = dict(
124
- engine=engine, alembic_location=alembic_location)
136
+ engine=engine, alembic_location=alembic_location
137
+ )
125
138
  # Some model classes may already have been constructed without having
126
139
  # had access to a db engine so far, so give them a chance to do the
127
140
  # reflection now.
@@ -148,15 +161,16 @@ class Database:
148
161
  """Create all tables etc. for an engine."""
149
162
  engine = self._engines[engine_name]
150
163
  _ENGINE_CLASS_MAPPING[engine_name].metadata.create_all(
151
- engine['engine'])
164
+ engine['engine']
165
+ )
152
166
 
153
167
  # mark the database to be in the latest revision
154
168
  location = engine['alembic_location']
155
169
  if location:
156
170
  with alembic_context(engine['engine'], location) as ac:
157
171
  ac.migration_context.stamp(
158
- ac.script,
159
- ac.script.get_current_head())
172
+ ac.script, ac.script.get_current_head()
173
+ )
160
174
  if create_defaults:
161
175
  self.create_defaults(engine_name)
162
176
 
@@ -173,25 +187,27 @@ class Database:
173
187
  # Step 1: Try to identify a testing table
174
188
  conn = engine.connect()
175
189
  try:
176
- conn.execute("SELECT * FROM tmp_functest")
190
+ conn.execute('SELECT * FROM tmp_functest')
177
191
  except sqlalchemy.exc.DatabaseError:
178
192
  db_is_testing = False
179
193
  else:
180
194
  db_is_testing = True
181
195
  conn.invalidate()
182
196
 
183
- if (self.testing and db_is_testing):
197
+ if self.testing and db_is_testing:
184
198
  # We're running as a test and it is a test dabase. Continue
185
199
  return
186
- if (not self.testing and not db_is_testing):
200
+ if not self.testing and not db_is_testing:
187
201
  # We're running as a production system and we have a production
188
202
  # database. Continue.
189
203
  return
190
204
 
191
205
  # We're not in a valid state. Bail out.
192
- raise SystemExit("Not working against correct database (live vs "
193
- "testing). Refusing to set up database connection "
194
- "to {}.".format(engine.url))
206
+ raise SystemExit(
207
+ 'Not working against correct database (live vs '
208
+ 'testing). Refusing to set up database connection '
209
+ 'to {}.'.format(engine.url)
210
+ )
195
211
 
196
212
  def assert_database_revision_is_current(self, engine_name=''):
197
213
  def assert_revision(ac, head_rev, db_rev):
@@ -200,7 +216,9 @@ class Database:
200
216
  'Database revision {} of engine "{}" does not match '
201
217
  'current revision {}.\nMaybe you want to call '
202
218
  '`bin/alembic upgrade head`.'.format(
203
- db_rev, engine_name, head_rev))
219
+ db_rev, engine_name, head_rev
220
+ )
221
+ )
204
222
 
205
223
  self._run_in_alembic_context(assert_revision, engine_name)
206
224
 
@@ -208,6 +226,7 @@ class Database:
208
226
  def upgrade_revision(ac, head_rev, db_rev):
209
227
  if head_rev != db_rev:
210
228
  ac.upgrade(head_rev)
229
+
211
230
  self._run_in_alembic_context(upgrade_revision, engine_name)
212
231
 
213
232
  @property
@@ -229,9 +248,15 @@ class Database:
229
248
  def _teardown_utility(self):
230
249
  zope.component.getGlobalSiteManager().unregisterUtility(self)
231
250
 
232
- def empty(self, engine, table_names=None, cascade=False,
233
- restart_sequences=True,
234
- empty_alembic_version=False, commit=True):
251
+ def empty(
252
+ self,
253
+ engine,
254
+ table_names=None,
255
+ cascade=False,
256
+ restart_sequences=True,
257
+ empty_alembic_version=False,
258
+ commit=True,
259
+ ):
235
260
  transaction.abort()
236
261
  if table_names is None:
237
262
  inspector = sqlalchemy.inspect(engine)
@@ -259,8 +284,10 @@ class Database:
259
284
  'TRUNCATE {} {} {}'.format(
260
285
  tables,
261
286
  'RESTART IDENTITY' if restart_sequences else '',
262
- 'CASCADE' if cascade else ''),
263
- bind=engine)
287
+ 'CASCADE' if cascade else '',
288
+ ),
289
+ bind=engine,
290
+ )
264
291
  zope.sqlalchemy.mark_changed(self.session)
265
292
  if commit:
266
293
  transaction.commit()
@@ -285,7 +312,6 @@ class Database:
285
312
 
286
313
 
287
314
  class alembic_context:
288
-
289
315
  def __init__(self, engine, script_location):
290
316
  self.engine = engine
291
317
  self.script_location = script_location
@@ -302,22 +328,23 @@ class alembic_context:
302
328
 
303
329
 
304
330
  class AlembicContext:
305
-
306
331
  def __init__(self, conn, script_location):
307
332
  self.conn = conn
308
333
  self.config = alembic.config.Config()
309
334
  self.config.set_main_option('script_location', script_location)
310
335
  self.migration_context = alembic.migration.MigrationContext.configure(
311
- conn)
336
+ conn
337
+ )
312
338
  self.script = alembic.script.ScriptDirectory.from_config(self.config)
313
339
 
314
340
  def upgrade(self, dest_rev):
315
341
  """Upgrade from current to `dest_rev`."""
342
+
316
343
  def upgrade_fn(rev, context):
317
344
  return self.script._upgrade_revs(dest_rev, rev)
318
345
 
319
346
  with alembic.environment.EnvironmentContext(
320
- self.config, self.script, fn=upgrade_fn,
321
- destination_rev=dest_rev) as ec:
347
+ self.config, self.script, fn=upgrade_fn, destination_rev=dest_rev
348
+ ) as ec:
322
349
  ec.configure(self.conn)
323
350
  ec.run_migrations()
@@ -0,0 +1,8 @@
1
+ from sqlalchemy.sql.expression import and_, between, or_
2
+
3
+
4
+ def in_interval(point, left, right):
5
+ return or_(
6
+ between(point, left, right),
7
+ and_(point >= left, right == None), # noqa: E711 comparison to None
8
+ )
@@ -30,3 +30,10 @@ class Added(zope.interface.interfaces.ObjectEvent):
30
30
 
31
31
  def __init__(self, object):
32
32
  self.object = object
33
+
34
+
35
+ class Deleted(zope.interface.interfaces.ObjectEvent):
36
+ """An object has been removed from the session."""
37
+
38
+ def __init__(self, object):
39
+ self.object = object