risclog.sqlalchemy 7.2__tar.gz → 9.0__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-9.0/.github/workflows/tests.yml +41 -0
- risclog_sqlalchemy-9.0/.gitignore +24 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/CHANGES.rst +12 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/HACKING.rst +1 -1
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/PKG-INFO +26 -14
- {risclog.sqlalchemy-7.2/doc → risclog_sqlalchemy-9.0/docs}/conf.py +8 -8
- risclog_sqlalchemy-9.0/pyproject.toml +105 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/cache.py +15 -15
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/db.py +52 -49
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/fixtures.py +1 -1
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/interfaces.py +2 -2
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/model.py +2 -3
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/serializer.py +4 -4
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/testing.py +7 -6
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/testlayer.py +1 -2
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/conftest.py +11 -11
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/fixtures/alembic/env.py +2 -2
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/fixtures/alembic/versions/3f735078d5a8_foo.py +4 -3
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/test_cache.py +58 -56
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/test_db.py +51 -49
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/test_fixtures.py +6 -6
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/test_model.py +1 -1
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/test_serializer.py +11 -11
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog.sqlalchemy.egg-info/PKG-INFO +26 -14
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog.sqlalchemy.egg-info/SOURCES.txt +8 -10
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog.sqlalchemy.egg-info/requires.txt +5 -1
- risclog_sqlalchemy-9.0/uv.lock +2671 -0
- risclog.sqlalchemy-7.2/pyproject.toml +0 -60
- risclog.sqlalchemy-7.2/requirements.txt +0 -10
- risclog.sqlalchemy-7.2/setup.py +0 -76
- risclog.sqlalchemy-7.2/src/risclog/__init__.py +0 -1
- risclog.sqlalchemy-7.2/src/risclog.sqlalchemy.egg-info/namespace_packages.txt +0 -1
- risclog.sqlalchemy-7.2/src/risclog.sqlalchemy.egg-info/not-zip-safe +0 -1
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/COPYRIGHT.rst +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/LICENSE.txt +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/README.rst +0 -0
- {risclog.sqlalchemy-7.2/doc → risclog_sqlalchemy-9.0/docs}/about.txt +0 -0
- {risclog.sqlalchemy-7.2/doc → risclog_sqlalchemy-9.0/docs}/api.txt +0 -0
- {risclog.sqlalchemy-7.2/doc → risclog_sqlalchemy-9.0/docs}/changes.txt +0 -0
- {risclog.sqlalchemy-7.2/doc → risclog_sqlalchemy-9.0/docs}/index.txt +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/setup.cfg +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/__init__.py +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/functions.py +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/__init__.py +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog/sqlalchemy/tests/fixtures/alembic/script.py.mako +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog.sqlalchemy.egg-info/dependency_links.txt +0 -0
- {risclog.sqlalchemy-7.2 → risclog_sqlalchemy-9.0}/src/risclog.sqlalchemy.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
- testing
|
|
8
|
+
pull_request:
|
|
9
|
+
schedule:
|
|
10
|
+
# * is a special character in YAML so you have to quote this string
|
|
11
|
+
- cron: '0 1 * * *'
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
uses: risclog-solution/gha_workflow_templates/.github/workflows/pytest_uv.yml@master
|
|
16
|
+
with:
|
|
17
|
+
timeout_minutes: 30
|
|
18
|
+
secrets:
|
|
19
|
+
UVCONF: ${{ secrets.UVCONF }}
|
|
20
|
+
|
|
21
|
+
coverage:
|
|
22
|
+
needs: test
|
|
23
|
+
uses: risclog-solution/gha_workflow_templates/.github/workflows/coverage.yml@master
|
|
24
|
+
with:
|
|
25
|
+
min_coverage: 80
|
|
26
|
+
|
|
27
|
+
pre-commit:
|
|
28
|
+
uses: risclog-solution/gha_workflow_templates/.github/workflows/pre_commit.yml@master
|
|
29
|
+
|
|
30
|
+
sonarqube:
|
|
31
|
+
needs: coverage
|
|
32
|
+
uses: risclog-solution/gha_workflow_templates/.github/workflows/sonarqube.yml@master
|
|
33
|
+
secrets:
|
|
34
|
+
SONARQUBETOKEN: ${{ secrets.SONARQUBETOKEN }}
|
|
35
|
+
|
|
36
|
+
publish-test-results:
|
|
37
|
+
needs: test
|
|
38
|
+
uses: risclog-solution/gha_workflow_templates/.github/workflows/publish_test_results.yml@master
|
|
39
|
+
|
|
40
|
+
cleanup:
|
|
41
|
+
uses: risclog-solution/gha_workflow_templates/.github/workflows/cleanup.yml@master
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
*.egg-info
|
|
2
|
+
*.pyc
|
|
3
|
+
.Python
|
|
4
|
+
.cache/v/cache/lastfailed
|
|
5
|
+
.coverage
|
|
6
|
+
.installed.cfg
|
|
7
|
+
.tags
|
|
8
|
+
.tox
|
|
9
|
+
__pycache__
|
|
10
|
+
bin
|
|
11
|
+
build
|
|
12
|
+
coverage-report
|
|
13
|
+
coverage.xml
|
|
14
|
+
develop-eggs
|
|
15
|
+
dist
|
|
16
|
+
docs
|
|
17
|
+
docs/_api
|
|
18
|
+
eggs
|
|
19
|
+
include
|
|
20
|
+
lib
|
|
21
|
+
local.cfg
|
|
22
|
+
parts
|
|
23
|
+
testreport.xml
|
|
24
|
+
.venv
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
Change log for risclog.sqlalchemy
|
|
3
3
|
=================================
|
|
4
4
|
|
|
5
|
+
9.0 (2026-08-24)
|
|
6
|
+
================
|
|
7
|
+
|
|
8
|
+
- deps: Switch to uv, pyproject.toml and PEP-420 namespace package.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
8.0 (2025-12-02)
|
|
12
|
+
================
|
|
13
|
+
|
|
14
|
+
- Add compatability to sqlalchemy 2. Break with compatability to sqlalchemy < 1.4.
|
|
15
|
+
|
|
16
|
+
|
|
5
17
|
7.2 (2024-04-19)
|
|
6
18
|
================
|
|
7
19
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: risclog.sqlalchemy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0
|
|
4
4
|
Summary: Encapsulate sqlalchemy modelling infrastructure.
|
|
5
|
-
|
|
6
|
-
Author: gocept <mail@gocept.com>
|
|
7
|
-
Author-email: mail@gocept.com
|
|
5
|
+
Author-email: gocept <mail@gocept.com>
|
|
8
6
|
License: ZPL 2.1
|
|
9
|
-
|
|
7
|
+
Project-URL: Homepage, https://github.com/gocept/risclog.sqlalchemy
|
|
8
|
+
Keywords: sqlalchemy,alembic,model,infrastructure,utility
|
|
10
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
10
|
Classifier: Framework :: Pyramid
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -16,20 +15,19 @@ Classifier: Natural Language :: English
|
|
|
16
15
|
Classifier: Operating System :: OS Independent
|
|
17
16
|
Classifier: Programming Language :: Python
|
|
18
17
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
22
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
20
|
Classifier: Programming Language :: Python :: Implementation
|
|
25
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
22
|
Classifier: Topic :: Database
|
|
27
23
|
Classifier: Topic :: Software Development :: Libraries
|
|
28
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
25
|
Requires-Python: >=3.7
|
|
26
|
+
Description-Content-Type: text/x-rst
|
|
30
27
|
License-File: LICENSE.txt
|
|
31
|
-
Requires-Dist: SQLAlchemy
|
|
28
|
+
Requires-Dist: SQLAlchemy>=1.4
|
|
32
29
|
Requires-Dist: alembic>=0.7
|
|
30
|
+
Requires-Dist: importlib_resources
|
|
33
31
|
Requires-Dist: pytz
|
|
34
32
|
Requires-Dist: setuptools
|
|
35
33
|
Requires-Dist: transaction
|
|
@@ -42,8 +40,12 @@ Provides-Extra: self-test
|
|
|
42
40
|
Requires-Dist: mock; extra == "self-test"
|
|
43
41
|
Requires-Dist: pyramid; extra == "self-test"
|
|
44
42
|
Requires-Dist: pytest; extra == "self-test"
|
|
43
|
+
Requires-Dist: psycopg2-binary; extra == "self-test"
|
|
44
|
+
Requires-Dist: greenlet; extra == "self-test"
|
|
45
|
+
Requires-Dist: pytest-cov; extra == "self-test"
|
|
45
46
|
Provides-Extra: pyramid
|
|
46
47
|
Requires-Dist: pyramid; extra == "pyramid"
|
|
48
|
+
Dynamic: license-file
|
|
47
49
|
|
|
48
50
|
==================
|
|
49
51
|
risclog.sqlalchemy
|
|
@@ -57,7 +59,6 @@ This package is compatible with Python version 3.7 up to 3.11.
|
|
|
57
59
|
|
|
58
60
|
.. _`sqlalchemy`: http://www.sqlalchemy.org
|
|
59
61
|
|
|
60
|
-
|
|
61
62
|
=============================
|
|
62
63
|
Developing risclog.sqlalchemy
|
|
63
64
|
=============================
|
|
@@ -84,7 +85,7 @@ Developing risclog.sqlalchemy
|
|
|
84
85
|
Running tests
|
|
85
86
|
=============
|
|
86
87
|
|
|
87
|
-
Simply run
|
|
88
|
+
Simply run `uv run pytest` in the root of the repository.
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
Documentation
|
|
@@ -101,11 +102,22 @@ python, where ``sphinx-pypi-upload`` is installed::
|
|
|
101
102
|
|
|
102
103
|
$ python setup.py upload_sphinx
|
|
103
104
|
|
|
104
|
-
|
|
105
105
|
=================================
|
|
106
106
|
Change log for risclog.sqlalchemy
|
|
107
107
|
=================================
|
|
108
108
|
|
|
109
|
+
9.0 (2026-08-24)
|
|
110
|
+
================
|
|
111
|
+
|
|
112
|
+
- deps: Switch to uv, pyproject.toml and PEP-420 namespace package.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
8.0 (2025-12-02)
|
|
116
|
+
================
|
|
117
|
+
|
|
118
|
+
- Add compatability to sqlalchemy 2. Break with compatability to sqlalchemy < 1.4.
|
|
119
|
+
|
|
120
|
+
|
|
109
121
|
7.2 (2024-04-19)
|
|
110
122
|
================
|
|
111
123
|
|
|
@@ -4,20 +4,20 @@ import sys
|
|
|
4
4
|
# If extensions (or modules to document with autodoc) are in another directory,
|
|
5
5
|
# add these directories to sys.path here. If the directory is relative to the
|
|
6
6
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
7
|
-
sys.path.insert(0, os.path.abspath(
|
|
7
|
+
sys.path.insert(0, os.path.abspath("_themes"))
|
|
8
8
|
extensions = []
|
|
9
9
|
|
|
10
|
-
source_suffix =
|
|
11
|
-
master_doc =
|
|
12
|
-
exclude_patterns = [
|
|
10
|
+
source_suffix = ".txt"
|
|
11
|
+
master_doc = "index"
|
|
12
|
+
exclude_patterns = ["_build"]
|
|
13
13
|
|
|
14
|
-
project =
|
|
15
|
-
copyright =
|
|
14
|
+
project = "risclog.sqlalchemy"
|
|
15
|
+
copyright = """\
|
|
16
16
|
2011 - 2017 Zope Foundation and Contributors.
|
|
17
17
|
<a href="http://sphinx.pocoo.org/">Sphinx</a>-Theme adapted from
|
|
18
18
|
<a href="http://jinja.pocoo.org/docs/">Jinja</a>
|
|
19
19
|
"""
|
|
20
|
-
version =
|
|
20
|
+
version = "2.0" # XXX determine automatically
|
|
21
21
|
release = version
|
|
22
22
|
|
|
23
|
-
pygments_style =
|
|
23
|
+
pygments_style = "sphinx"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=40.6.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "risclog.sqlalchemy"
|
|
7
|
+
version = "9.0"
|
|
8
|
+
description = "Encapsulate sqlalchemy modelling infrastructure."
|
|
9
|
+
keywords = ["sqlalchemy", "alembic", "model", "infrastructure", "utility"]
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
authors = [{ name = "gocept", email = "mail@gocept.com" }]
|
|
12
|
+
license = { text = "ZPL 2.1" }
|
|
13
|
+
urls = { Homepage = "https://github.com/gocept/risclog.sqlalchemy" }
|
|
14
|
+
dynamic = ["readme"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Framework :: Pyramid",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved",
|
|
20
|
+
"License :: OSI Approved :: Zope Public License",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: Implementation",
|
|
28
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
29
|
+
"Topic :: Database",
|
|
30
|
+
"Topic :: Software Development :: Libraries",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"SQLAlchemy >= 1.4",
|
|
35
|
+
"alembic >= 0.7",
|
|
36
|
+
"importlib_resources",
|
|
37
|
+
"pytz",
|
|
38
|
+
"setuptools",
|
|
39
|
+
"transaction",
|
|
40
|
+
"zope.component >= 4.0.1",
|
|
41
|
+
"zope.interface",
|
|
42
|
+
"zope.sqlalchemy >= 1.3",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
test = ["gocept.testdb"]
|
|
47
|
+
self-test = [
|
|
48
|
+
"mock",
|
|
49
|
+
"pyramid",
|
|
50
|
+
"pytest",
|
|
51
|
+
"psycopg2-binary",
|
|
52
|
+
"greenlet",
|
|
53
|
+
"pytest-cov",
|
|
54
|
+
]
|
|
55
|
+
pyramid = ["pyramid"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools]
|
|
58
|
+
include-package-data = true
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
62
|
+
include = ["risclog.*"]
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.dynamic]
|
|
65
|
+
readme = { file = ["README.rst", "HACKING.rst", "CHANGES.rst"], content-type = "text/x-rst" }
|
|
66
|
+
|
|
67
|
+
[tool.coverage.run]
|
|
68
|
+
relative_files = true
|
|
69
|
+
parallel = true
|
|
70
|
+
branch = true
|
|
71
|
+
source = ["risclog.sqlalchemy"]
|
|
72
|
+
|
|
73
|
+
[tool.coverage.paths]
|
|
74
|
+
source = ["src", ".pytest/*/site-packages"]
|
|
75
|
+
|
|
76
|
+
[tool.coverage.html]
|
|
77
|
+
directory = "coverage-report"
|
|
78
|
+
|
|
79
|
+
[tool.coverage.report]
|
|
80
|
+
precision = 2
|
|
81
|
+
show_missing = true
|
|
82
|
+
skip_covered = true
|
|
83
|
+
exclude_lines = [
|
|
84
|
+
# a more strict default pragma
|
|
85
|
+
"\\# pragma: no cover\\b",
|
|
86
|
+
|
|
87
|
+
# allow defensive code
|
|
88
|
+
"^\\s*raise AssertionError\\b",
|
|
89
|
+
"^\\s*raise NotImplementedError\\b",
|
|
90
|
+
"^\\s*return NotImplemented\\b",
|
|
91
|
+
"^\\s*raise$",
|
|
92
|
+
|
|
93
|
+
# typing-related code
|
|
94
|
+
"^if (False|TYPE_CHECKING):",
|
|
95
|
+
": \\.\\.\\.(\\s*#.*)?$",
|
|
96
|
+
"^ +\\.\\.\\.$",
|
|
97
|
+
"-> ['\"]?NoReturn['\"]?:",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
[tool.ruff]
|
|
101
|
+
target-version = "py312"
|
|
102
|
+
line-length = 79
|
|
103
|
+
|
|
104
|
+
[tool.check-manifest]
|
|
105
|
+
ignore-bad-ideas = ["*.mo"]
|
|
@@ -100,26 +100,26 @@ class ModelCache:
|
|
|
100
100
|
self.hp.setrelheap()
|
|
101
101
|
|
|
102
102
|
def __sizeof__(self):
|
|
103
|
-
if not hasattr(self,
|
|
103
|
+
if not hasattr(self, "hp"):
|
|
104
104
|
return 0
|
|
105
105
|
|
|
106
106
|
h = self.hp.heap()
|
|
107
107
|
return h.size
|
|
108
108
|
|
|
109
|
-
def _sizeof_fmt(self, num, suffix=
|
|
110
|
-
for unit in [
|
|
109
|
+
def _sizeof_fmt(self, num, suffix="B"):
|
|
110
|
+
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
|
111
111
|
if abs(num) < 1024.0:
|
|
112
|
-
return f
|
|
112
|
+
return f"{num:3.1f} {unit}{suffix}"
|
|
113
113
|
num /= 1024.0
|
|
114
|
-
return f
|
|
114
|
+
return f"{num:.1f}Yi{suffix}"
|
|
115
115
|
|
|
116
116
|
def log_memory_usage(self):
|
|
117
|
-
if not hasattr(self,
|
|
117
|
+
if not hasattr(self, "hp"):
|
|
118
118
|
return
|
|
119
119
|
|
|
120
120
|
bsize = sys.getsizeof(self)
|
|
121
121
|
size = self._sizeof_fmt(bsize)
|
|
122
|
-
self._log(
|
|
122
|
+
self._log("info", f"Memory usage: {size} ({bsize})")
|
|
123
123
|
|
|
124
124
|
def find(self, model, **kwargs):
|
|
125
125
|
"""
|
|
@@ -205,7 +205,7 @@ class ModelCache:
|
|
|
205
205
|
.connection.cursor()
|
|
206
206
|
)
|
|
207
207
|
|
|
208
|
-
self._log(
|
|
208
|
+
self._log("debug", "Flushing model cache.")
|
|
209
209
|
self._assign_sequences()
|
|
210
210
|
self._sync_relationship_attrs()
|
|
211
211
|
|
|
@@ -239,7 +239,7 @@ class ModelCache:
|
|
|
239
239
|
cursor.connection.commit()
|
|
240
240
|
|
|
241
241
|
self.clear(session)
|
|
242
|
-
self._log(
|
|
242
|
+
self._log("info", "Flushed model cache.")
|
|
243
243
|
|
|
244
244
|
def _save_by_copy(self, cursor, objects):
|
|
245
245
|
"""
|
|
@@ -273,7 +273,7 @@ class ModelCache:
|
|
|
273
273
|
if column.default is not None:
|
|
274
274
|
value = column.default.arg
|
|
275
275
|
else:
|
|
276
|
-
value = r
|
|
276
|
+
value = r"\N"
|
|
277
277
|
elif type(value) != column.type.python_type:
|
|
278
278
|
value = column.type.python_type(value)
|
|
279
279
|
row[column.key] = value
|
|
@@ -281,11 +281,11 @@ class ModelCache:
|
|
|
281
281
|
|
|
282
282
|
file.seek(0)
|
|
283
283
|
|
|
284
|
-
columns_string =
|
|
284
|
+
columns_string = ",".join(
|
|
285
285
|
[f'"{column}"' for column in table.columns.keys()]
|
|
286
286
|
)
|
|
287
287
|
cursor.copy_expert(
|
|
288
|
-
f
|
|
288
|
+
f"COPY {table} ({columns_string}) "
|
|
289
289
|
"FROM STDIN WITH CSV DELIMITER ',' NULL '\\N'",
|
|
290
290
|
file,
|
|
291
291
|
)
|
|
@@ -451,12 +451,12 @@ class ModelCache:
|
|
|
451
451
|
def _register_change_handler(self, model, event_handler):
|
|
452
452
|
"""Register an event handler for every column of a model."""
|
|
453
453
|
for attr in inspect(model).column_attrs:
|
|
454
|
-
sqlalchemy.event.listen(attr,
|
|
454
|
+
sqlalchemy.event.listen(attr, "set", event_handler)
|
|
455
455
|
|
|
456
456
|
def _deregister_change_handler(self, model, event_handler):
|
|
457
457
|
"""Removes an event handler for every column of a model."""
|
|
458
458
|
for attr in inspect(model).column_attrs:
|
|
459
|
-
sqlalchemy.event.listen(attr,
|
|
459
|
+
sqlalchemy.event.listen(attr, "set", event_handler)
|
|
460
460
|
|
|
461
461
|
def _instance_change_handler(self, instance, value, oldvalue, initiator):
|
|
462
462
|
"""
|
|
@@ -466,7 +466,7 @@ class ModelCache:
|
|
|
466
466
|
attribute was changed. For more information,
|
|
467
467
|
see: https://docs.sqlalchemy.org/en/13/orm/events.html
|
|
468
468
|
""" # noqa: E501
|
|
469
|
-
if oldvalue is sqlalchemy.util.symbol(
|
|
469
|
+
if oldvalue is sqlalchemy.util.symbol("NEVER_SET"):
|
|
470
470
|
return
|
|
471
471
|
|
|
472
472
|
model = type(instance)
|
|
@@ -14,18 +14,19 @@ import zope.interface
|
|
|
14
14
|
import zope.sqlalchemy
|
|
15
15
|
from packaging.version import Version as parse_version
|
|
16
16
|
from sqlalchemy import __version__ as sqlalchemy_version
|
|
17
|
+
from sqlalchemy import text
|
|
17
18
|
|
|
18
19
|
# Mapping engine name registered using Database.register_engine --> base class
|
|
19
20
|
_ENGINE_CLASS_MAPPING = {}
|
|
20
21
|
|
|
21
|
-
SA_GE_14 = parse_version(sqlalchemy_version) >= parse_version(
|
|
22
|
+
SA_GE_14 = parse_version(sqlalchemy_version) >= parse_version("1.4.0")
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def assert_engine_not_registered(name, mapping):
|
|
25
26
|
"""Ensure a consistent error message."""
|
|
26
27
|
assert (
|
|
27
28
|
name not in mapping
|
|
28
|
-
), f
|
|
29
|
+
), f"An engine for name `{name}` is already registered."
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def register_class(class_):
|
|
@@ -74,13 +75,10 @@ class RoutingSession(sqlalchemy.orm.Session):
|
|
|
74
75
|
if issubclass(mapper.class_, class_):
|
|
75
76
|
return db_util.get_engine(engine_name)
|
|
76
77
|
|
|
77
|
-
raise RuntimeError(f
|
|
78
|
+
raise RuntimeError(f"Did not find an engine for {mapper.class_}")
|
|
78
79
|
|
|
79
80
|
def _bound_execute(self, bind, *args, **kwargs):
|
|
80
|
-
|
|
81
|
-
return self.execute(*args, bind_arguments={'bind': bind}, **kwargs)
|
|
82
|
-
else:
|
|
83
|
-
return self.execute(*args, bind=bind, **kwargs)
|
|
81
|
+
return self.execute(*args, bind_arguments={"bind": bind}, **kwargs)
|
|
84
82
|
|
|
85
83
|
def execute_with_bind(self, engine_name, *args, **kwargs):
|
|
86
84
|
"""
|
|
@@ -141,10 +139,9 @@ def get_database(testing=False, keep_session=False, expire_on_commit=False):
|
|
|
141
139
|
db = zope.component.queryUtility(risclog.sqlalchemy.interfaces.IDatabase)
|
|
142
140
|
if db is None:
|
|
143
141
|
db = Database(testing, keep_session, expire_on_commit)
|
|
144
|
-
assert (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
testing
|
|
142
|
+
assert db.testing == testing, (
|
|
143
|
+
"Requested testing status `%s` does not match Database.testing."
|
|
144
|
+
% (testing)
|
|
148
145
|
)
|
|
149
146
|
return db
|
|
150
147
|
|
|
@@ -160,8 +157,8 @@ class Database:
|
|
|
160
157
|
)
|
|
161
158
|
is None
|
|
162
159
|
), (
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
"Cannot create Database twice, use `.get_database()` to get "
|
|
161
|
+
"the instance."
|
|
165
162
|
)
|
|
166
163
|
self._engines = {}
|
|
167
164
|
self.testing = testing
|
|
@@ -176,11 +173,11 @@ class Database:
|
|
|
176
173
|
self._setup_utility()
|
|
177
174
|
|
|
178
175
|
def register_engine(
|
|
179
|
-
self, dsn, engine_args={}, name=
|
|
176
|
+
self, dsn, engine_args={}, name="", alembic_location=None
|
|
180
177
|
):
|
|
181
178
|
assert_engine_not_registered(name, self._engines)
|
|
182
|
-
engine_args[
|
|
183
|
-
int(os.environ.get(
|
|
179
|
+
engine_args["echo"] = bool(
|
|
180
|
+
int(os.environ.get("ECHO_SQLALCHEMY_QUERIES", "0"))
|
|
184
181
|
)
|
|
185
182
|
engine = sqlalchemy.create_engine(dsn, **engine_args)
|
|
186
183
|
self._verify_engine(engine)
|
|
@@ -192,13 +189,13 @@ class Database:
|
|
|
192
189
|
# reflection now.
|
|
193
190
|
self.prepare_deferred(_ENGINE_CLASS_MAPPING.get(name))
|
|
194
191
|
|
|
195
|
-
def get_engine(self, name=
|
|
196
|
-
return self._engines[name][
|
|
192
|
+
def get_engine(self, name=""):
|
|
193
|
+
return self._engines[name]["engine"]
|
|
197
194
|
|
|
198
195
|
def get_all_engines(self):
|
|
199
|
-
return [x[
|
|
196
|
+
return [x["engine"] for x in self._engines.values()]
|
|
200
197
|
|
|
201
|
-
def drop_engine(self, name=
|
|
198
|
+
def drop_engine(self, name=""):
|
|
202
199
|
engine = self.get_engine(name)
|
|
203
200
|
engine.dispose()
|
|
204
201
|
del self._engines[name]
|
|
@@ -209,30 +206,32 @@ class Database:
|
|
|
209
206
|
if issubclass(class_, sqlalchemy.ext.declarative.DeferredReflection):
|
|
210
207
|
class_.prepare(self.get_engine(class_._engine_name))
|
|
211
208
|
|
|
212
|
-
def create_all(self, engine_name=
|
|
209
|
+
def create_all(self, engine_name="", create_defaults=True):
|
|
213
210
|
"""Create all tables etc. for an engine."""
|
|
214
211
|
engine = self._engines[engine_name]
|
|
215
212
|
_ENGINE_CLASS_MAPPING[engine_name].metadata.create_all(
|
|
216
|
-
engine[
|
|
213
|
+
engine["engine"]
|
|
217
214
|
)
|
|
218
215
|
|
|
219
216
|
# mark the database to be in the latest revision
|
|
220
|
-
location = engine[
|
|
217
|
+
location = engine["alembic_location"]
|
|
221
218
|
if location:
|
|
222
|
-
with alembic_context(engine[
|
|
223
|
-
ac.migration_context.
|
|
224
|
-
ac.
|
|
225
|
-
|
|
219
|
+
with alembic_context(engine["engine"], location) as ac:
|
|
220
|
+
with ac.migration_context.begin_transaction():
|
|
221
|
+
ac.migration_context.stamp(
|
|
222
|
+
ac.script, ac.script.get_current_head()
|
|
223
|
+
)
|
|
224
|
+
|
|
226
225
|
if create_defaults:
|
|
227
226
|
self.create_defaults(engine_name)
|
|
228
227
|
|
|
229
|
-
def create_defaults(self, engine_name=
|
|
228
|
+
def create_defaults(self, engine_name=""):
|
|
230
229
|
for name, class_ in risclog.sqlalchemy.model.class_registry.items():
|
|
231
230
|
# do not call create_defaults for foreign engines,
|
|
232
231
|
# since they may not be set up yet
|
|
233
|
-
if engine_name != getattr(class_,
|
|
232
|
+
if engine_name != getattr(class_, "_engine_name", NotImplemented):
|
|
234
233
|
continue
|
|
235
|
-
if hasattr(class_,
|
|
234
|
+
if hasattr(class_, "create_defaults"):
|
|
236
235
|
class_.create_defaults()
|
|
237
236
|
|
|
238
237
|
def _verify_engine(self, engine):
|
|
@@ -241,7 +240,7 @@ class Database:
|
|
|
241
240
|
from sqlalchemy import text
|
|
242
241
|
|
|
243
242
|
try:
|
|
244
|
-
conn.execute(text(
|
|
243
|
+
conn.execute(text("SELECT * FROM tmp_functest"))
|
|
245
244
|
except sqlalchemy.exc.DatabaseError:
|
|
246
245
|
db_is_testing = False
|
|
247
246
|
else:
|
|
@@ -258,25 +257,25 @@ class Database:
|
|
|
258
257
|
|
|
259
258
|
# We're not in a valid state. Bail out.
|
|
260
259
|
raise SystemExit(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
"Not working against correct database (live vs "
|
|
261
|
+
"testing). Refusing to set up database connection "
|
|
262
|
+
"to {}.".format(engine.url)
|
|
264
263
|
)
|
|
265
264
|
|
|
266
|
-
def assert_database_revision_is_current(self, engine_name=
|
|
265
|
+
def assert_database_revision_is_current(self, engine_name=""):
|
|
267
266
|
def assert_revision(ac, head_rev, db_rev):
|
|
268
267
|
if head_rev != db_rev:
|
|
269
268
|
raise ValueError(
|
|
270
269
|
'Database revision {} of engine "{}" does not match '
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
"current revision {}.\nMaybe you want to call "
|
|
271
|
+
"`bin/alembic upgrade head`.".format(
|
|
273
272
|
db_rev, engine_name, head_rev
|
|
274
273
|
)
|
|
275
274
|
)
|
|
276
275
|
|
|
277
276
|
self._run_in_alembic_context(assert_revision, engine_name)
|
|
278
277
|
|
|
279
|
-
def update_database_revision_to_current(self, engine_name=
|
|
278
|
+
def update_database_revision_to_current(self, engine_name=""):
|
|
280
279
|
def upgrade_revision(ac, head_rev, db_rev):
|
|
281
280
|
if head_rev != db_rev:
|
|
282
281
|
ac.upgrade(head_rev)
|
|
@@ -318,7 +317,7 @@ class Database:
|
|
|
318
317
|
|
|
319
318
|
try:
|
|
320
319
|
# never truncate the PostGIS table
|
|
321
|
-
table_names.remove(
|
|
320
|
+
table_names.remove("spatial_ref_sys")
|
|
322
321
|
except ValueError:
|
|
323
322
|
pass
|
|
324
323
|
|
|
@@ -328,25 +327,27 @@ class Database:
|
|
|
328
327
|
# does not change the schema. Clearing the alembic_version will
|
|
329
328
|
# cause "confusion and delay".
|
|
330
329
|
try:
|
|
331
|
-
table_names.remove(
|
|
330
|
+
table_names.remove("alembic_version")
|
|
332
331
|
except ValueError:
|
|
333
332
|
pass
|
|
334
333
|
if not table_names:
|
|
335
334
|
return
|
|
336
|
-
tables =
|
|
335
|
+
tables = ", ".join('"%s"' % x for x in table_names)
|
|
337
336
|
self.session._bound_execute(
|
|
338
337
|
engine,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
text(
|
|
339
|
+
"TRUNCATE {} {} {}".format(
|
|
340
|
+
tables,
|
|
341
|
+
"RESTART IDENTITY" if restart_sequences else "",
|
|
342
|
+
"CASCADE" if cascade else "",
|
|
343
|
+
)
|
|
343
344
|
),
|
|
344
345
|
)
|
|
345
346
|
zope.sqlalchemy.mark_changed(self.session)
|
|
346
347
|
if commit:
|
|
347
348
|
transaction.commit()
|
|
348
349
|
|
|
349
|
-
def _run_in_alembic_context(self, func, engine_name=
|
|
350
|
+
def _run_in_alembic_context(self, func, engine_name=""):
|
|
350
351
|
"""Run a function in `alembic_context`.
|
|
351
352
|
|
|
352
353
|
The function must take three parameters:
|
|
@@ -356,10 +357,10 @@ class Database:
|
|
|
356
357
|
|
|
357
358
|
"""
|
|
358
359
|
engine = self._engines[engine_name]
|
|
359
|
-
location = engine[
|
|
360
|
+
location = engine["alembic_location"]
|
|
360
361
|
if not location:
|
|
361
362
|
return
|
|
362
|
-
with alembic_context(engine[
|
|
363
|
+
with alembic_context(engine["engine"], location) as ac:
|
|
363
364
|
head_rev = ac.script.get_current_head()
|
|
364
365
|
db_rev = ac.migration_context.get_current_revision()
|
|
365
366
|
return func(ac, head_rev, db_rev)
|
|
@@ -385,7 +386,7 @@ class AlembicContext:
|
|
|
385
386
|
def __init__(self, conn, script_location):
|
|
386
387
|
self.conn = conn
|
|
387
388
|
self.config = alembic.config.Config()
|
|
388
|
-
self.config.set_main_option(
|
|
389
|
+
self.config.set_main_option("script_location", script_location)
|
|
389
390
|
self.migration_context = alembic.migration.MigrationContext.configure(
|
|
390
391
|
conn
|
|
391
392
|
)
|
|
@@ -402,3 +403,5 @@ class AlembicContext:
|
|
|
402
403
|
) as ec:
|
|
403
404
|
ec.configure(self.conn)
|
|
404
405
|
ec.run_migrations()
|
|
406
|
+
if hasattr(self.conn, "commit"):
|
|
407
|
+
self.conn.commit()
|