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.
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/CHANGES.rst +15 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/PKG-INFO +16 -1
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/conf.py +0 -1
- risclog.sqlalchemy-6.2/pyproject.toml +60 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/requirements.txt +3 -1
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/setup.py +28 -34
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/cache.py +31 -24
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/db.py +73 -46
- risclog.sqlalchemy-6.2/src/risclog/sqlalchemy/functions.py +8 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/interfaces.py +7 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/model.py +8 -6
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/serializer.py +25 -9
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/testing.py +17 -11
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/testlayer.py +5 -3
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/conftest.py +9 -6
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/fixtures/alembic/env.py +6 -9
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/fixtures/alembic/versions/3f735078d5a8_foo.py +1 -2
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_cache.py +96 -37
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_db.py +49 -30
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_fixtures.py +3 -3
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_model.py +6 -3
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/test_serializer.py +11 -6
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/PKG-INFO +16 -1
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/SOURCES.txt +1 -0
- risclog.sqlalchemy-6.0/src/risclog/sqlalchemy/functions.py +0 -9
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/COPYRIGHT.rst +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/HACKING.rst +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/LICENSE.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/README.rst +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/about.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/api.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/changes.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/doc/index.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/setup.cfg +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/__init__.py +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/__init__.py +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/fixtures.py +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/__init__.py +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog/sqlalchemy/tests/fixtures/alembic/script.py.mako +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/dependency_links.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/namespace_packages.txt +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/not-zip-safe +0 -0
- {risclog.sqlalchemy-6.0 → risclog.sqlalchemy-6.2}/src/risclog.sqlalchemy.egg-info/requires.txt +0 -0
- {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.
|
|
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"]
|
|
@@ -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.
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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=
|
|
108
|
-
for unit in [
|
|
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
|
|
112
|
+
return f'{num:3.1f} {unit}{suffix}'
|
|
111
113
|
num /= 1024.0
|
|
112
|
-
return f
|
|
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 =
|
|
201
|
-
self.
|
|
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
|
|
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
|
|
23
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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(
|
|
117
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
193
|
-
|
|
194
|
-
|
|
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(
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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()
|
|
@@ -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
|