datanommer.models 1.0.4__tar.gz → 1.2.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.
Files changed (24) hide show
  1. datanommer_models-1.2.0/NEWS.rst +72 -0
  2. datanommer_models-1.2.0/PKG-INFO +54 -0
  3. datanommer_models-1.2.0/coverage.xml +239 -0
  4. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/datanommer/models/__init__.py +35 -61
  5. datanommer_models-1.2.0/datanommer/models/testing/__init__.py +48 -0
  6. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/pyproject.toml +39 -53
  7. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/tests/test_jsonencodeddict.py +10 -15
  8. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/tests/test_model.py +144 -141
  9. datanommer_models-1.2.0/tox.ini +26 -0
  10. datanommer.models-1.0.4/NEWS.rst +0 -34
  11. datanommer.models-1.0.4/PKG-INFO +0 -43
  12. datanommer.models-1.0.4/coverage.xml +0 -239
  13. datanommer.models-1.0.4/datanommer/models/testing/__init__.py +0 -46
  14. datanommer.models-1.0.4/datanommer/models/testing/startup.sql +0 -1
  15. datanommer.models-1.0.4/setup.py +0 -54
  16. datanommer.models-1.0.4/tox.ini +0 -15
  17. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/LICENSE +0 -0
  18. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/README.rst +0 -0
  19. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/alembic.ini +0 -0
  20. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/datanommer/models/alembic/env.py +0 -0
  21. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/datanommer/models/alembic/script.py.mako +0 -0
  22. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/datanommer/models/alembic/versions/5db25abc63be_init.py +0 -0
  23. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/datanommer/models/alembic/versions/951c40020acc_unique.py +0 -0
  24. {datanommer.models-1.0.4 → datanommer_models-1.2.0}/tests/conftest.py +0 -0
@@ -0,0 +1,72 @@
1
+ =============
2
+ Release Notes
3
+ =============
4
+
5
+ .. towncrier release notes start
6
+
7
+ v1.2.0
8
+ ======
9
+
10
+ Released on 2024-04-15.
11
+ This is a feature release that adds schema packages and upgrades the SQLAlchemy
12
+ API to the 2.0 style.
13
+
14
+ Features
15
+ ^^^^^^^^
16
+
17
+ * Upgrade to the SQLAlchemy 2.0 API (`981e2a4
18
+ <https://github.com/fedora-infra/datanommer/commit/981e2a4>`_).
19
+ * Add a few schema packages to the dependencies.
20
+
21
+ Development Improvements
22
+ ^^^^^^^^^^^^^^^^^^^^^^^^
23
+
24
+ * Use Ruff instead of flake8 and isort and bandit (:issue:`4f7ffaa`
25
+ `#4f7ffaa <https://github.com/fedora-infra/datanommer/issues/4f7ffaa>`_).
26
+
27
+
28
+ v1.1.0
29
+ ======
30
+
31
+ Released on 2023-09-22.
32
+ This is a feature release that adds ``koji-fedoramessaging-messages`` as a
33
+ dependency to interpret koji messages, and updates a lot of our other
34
+ dependencies.
35
+
36
+ Dependency Changes
37
+ ^^^^^^^^^^^^^^^^^^
38
+
39
+ * Drop support for python 3.7, add support for python 3.10 (`PR#890
40
+ <https://github.com/fedora-infra/datanommer/pull/890>`_).
41
+ * Add the ``koji-fedoramessaging-messages`` package (:issue:`1257`
42
+ `#1257 <https://github.com/fedora-infra/datanommer/issues/1257>`_).
43
+
44
+
45
+ v1.0.4
46
+ ======
47
+
48
+ Released on 2022-05-31.
49
+ This is a minor release:
50
+
51
+ - adds fedora-messaging schema packages
52
+ - doesn't require a version of bodhi-messages in the dev deps
53
+ - adjusts pyproject for spec needs
54
+ - fixes integration of Alembic
55
+
56
+
57
+ v1.0.3
58
+ ======
59
+
60
+ Released on 2022-03-18. This is a minor release:
61
+
62
+ - support fedora-messaging 3.0+
63
+ - update dependencies
64
+
65
+
66
+ v1.0.0
67
+ ======
68
+
69
+ Released on 2022-01-17.
70
+
71
+ This is a major release that uses TimescaleDB to store the data.
72
+ The list of changes is too big to list here.
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.1
2
+ Name: datanommer.models
3
+ Version: 1.2.0
4
+ Summary: SQLAlchemy models for datanommer
5
+ Home-page: https://github.com/fedora-infra/datanommer
6
+ License: GPL-3.0-or-later
7
+ Author: Fedora Infrastructure
8
+ Author-email: admin@fedoraproject.org
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Provides-Extra: schemas
16
+ Requires-Dist: SQLAlchemy (>=1.3.24,<3.0.0)
17
+ Requires-Dist: alembic (>=1.6.5,<2.0.0)
18
+ Requires-Dist: anitya-schema ; extra == "schemas"
19
+ Requires-Dist: bodhi-messages ; extra == "schemas"
20
+ Requires-Dist: bugzilla2fedmsg-schema ; extra == "schemas"
21
+ Requires-Dist: ci-messages ; extra == "schemas"
22
+ Requires-Dist: copr-messaging ; extra == "schemas"
23
+ Requires-Dist: discourse2fedmsg-messages ; extra == "schemas"
24
+ Requires-Dist: fedocal-messages ; extra == "schemas"
25
+ Requires-Dist: fedora-elections-messages ; extra == "schemas"
26
+ Requires-Dist: fedora-messaging (>=2.1.0)
27
+ Requires-Dist: fedora-messaging-git-hook-messages ; extra == "schemas"
28
+ Requires-Dist: fedora-messaging-the-new-hotness-schema ; extra == "schemas"
29
+ Requires-Dist: fedora-planet-messages ; extra == "schemas"
30
+ Requires-Dist: fedorainfra-ansible-messages ; extra == "schemas"
31
+ Requires-Dist: fmn-messages ; extra == "schemas"
32
+ Requires-Dist: kerneltest-messages (>=1.0.0,<2.0.0) ; extra == "schemas"
33
+ Requires-Dist: koji-fedoramessaging-messages (>=1.2.2,<2.0.0) ; extra == "schemas"
34
+ Requires-Dist: koschei-messages ; extra == "schemas"
35
+ Requires-Dist: mdapi-messages ; extra == "schemas"
36
+ Requires-Dist: mediawiki-messages ; extra == "schemas"
37
+ Requires-Dist: meetbot-messages ; extra == "schemas"
38
+ Requires-Dist: noggin-messages ; extra == "schemas"
39
+ Requires-Dist: nuancier-messages ; extra == "schemas"
40
+ Requires-Dist: pagure-messages ; extra == "schemas"
41
+ Requires-Dist: psycopg2 (>=2.9.1,<3.0.0)
42
+ Requires-Dist: tahrir-messages ; extra == "schemas"
43
+ Project-URL: Repository, https://github.com/fedora-infra/datanommer
44
+ Description-Content-Type: text/x-rst
45
+
46
+ datanommer.models
47
+ =================
48
+
49
+ This package contains the SQLAlchemy data model for datanommer.
50
+
51
+ Datanommer is a storage consumer for the Fedora Infrastructure Message Bus
52
+ (fedmsg). It is comprised of a `fedmsg <http://fedmsg.com>`_ consumer that
53
+ stuffs every message into a sqlalchemy database.
54
+
@@ -0,0 +1,239 @@
1
+ <?xml version="1.0" ?>
2
+ <coverage version="7.4.4" timestamp="1712820460896" lines-valid="220" lines-covered="217" line-rate="0.9864" branches-valid="100" branches-covered="100" branch-rate="1" complexity="0">
3
+ <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.4.4 -->
4
+ <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
5
+ <sources>
6
+ <source>/home/abompard/Fedora/apps/datanommer/datanommer.models/datanommer</source>
7
+ </sources>
8
+ <packages>
9
+ <package name="models" line-rate="0.9864" branch-rate="1" complexity="0">
10
+ <classes>
11
+ <class name="__init__.py" filename="models/__init__.py" complexity="0" line-rate="0.9864" branch-rate="1">
12
+ <methods/>
13
+ <lines>
14
+ <line number="16" hits="1"/>
15
+ <line number="17" hits="1"/>
16
+ <line number="18" hits="1"/>
17
+ <line number="19" hits="1"/>
18
+ <line number="20" hits="1"/>
19
+ <line number="21" hits="1"/>
20
+ <line number="22" hits="1"/>
21
+ <line number="23" hits="1"/>
22
+ <line number="25" hits="1"/>
23
+ <line number="47" hits="1"/>
24
+ <line number="48" hits="1"/>
25
+ <line number="49" hits="1"/>
26
+ <line number="56" hits="1"/>
27
+ <line number="59" hits="1"/>
28
+ <line number="60" hits="1"/>
29
+ <line number="67" hits="1"/>
30
+ <line number="70" hits="1"/>
31
+ <line number="72" hits="1"/>
32
+ <line number="73" hits="1"/>
33
+ <line number="75" hits="1"/>
34
+ <line number="76" hits="1"/>
35
+ <line number="79" hits="1"/>
36
+ <line number="82" hits="1" branch="true" condition-coverage="100% (2/2)"/>
37
+ <line number="83" hits="1"/>
38
+ <line number="85" hits="1" branch="true" condition-coverage="100% (2/2)"/>
39
+ <line number="86" hits="1"/>
40
+ <line number="88" hits="1" branch="true" condition-coverage="100% (2/2)"/>
41
+ <line number="89" hits="1"/>
42
+ <line number="94" hits="1" branch="true" condition-coverage="100% (2/2)"/>
43
+ <line number="95" hits="1"/>
44
+ <line number="96" hits="1"/>
45
+ <line number="97" hits="1"/>
46
+ <line number="99" hits="1"/>
47
+ <line number="100" hits="1"/>
48
+ <line number="102" hits="1" branch="true" condition-coverage="100% (2/2)"/>
49
+ <line number="103" hits="1" branch="true" condition-coverage="100% (2/2)"/>
50
+ <line number="104" hits="1"/>
51
+ <line number="105" hits="1"/>
52
+ <line number="116" hits="1"/>
53
+ <line number="120" hits="1"/>
54
+ <line number="121" hits="1"/>
55
+ <line number="123" hits="1" branch="true" condition-coverage="100% (2/2)"/>
56
+ <line number="126" hits="1"/>
57
+ <line number="127" hits="1"/>
58
+ <line number="128" hits="1"/>
59
+ <line number="129" hits="1"/>
60
+ <line number="130" hits="1"/>
61
+ <line number="132" hits="1"/>
62
+ <line number="135" hits="1"/>
63
+ <line number="136" hits="1"/>
64
+ <line number="137" hits="0"/>
65
+ <line number="138" hits="0"/>
66
+ <line number="143" hits="0"/>
67
+ <line number="144" hits="1"/>
68
+ <line number="145" hits="1"/>
69
+ <line number="146" hits="1"/>
70
+ <line number="147" hits="1"/>
71
+ <line number="152" hits="1"/>
72
+ <line number="154" hits="1"/>
73
+ <line number="165" hits="1"/>
74
+ <line number="171" hits="1"/>
75
+ <line number="174" hits="1"/>
76
+ <line number="176" hits="1"/>
77
+ <line number="178" hits="1"/>
78
+ <line number="179" hits="1" branch="true" condition-coverage="100% (2/2)"/>
79
+ <line number="180" hits="1"/>
80
+ <line number="182" hits="1"/>
81
+ <line number="184" hits="1"/>
82
+ <line number="185" hits="1" branch="true" condition-coverage="100% (2/2)"/>
83
+ <line number="186" hits="1"/>
84
+ <line number="187" hits="1"/>
85
+ <line number="189" hits="1"/>
86
+ <line number="191" hits="1" branch="true" condition-coverage="100% (2/2)"/>
87
+ <line number="192" hits="1"/>
88
+ <line number="194" hits="1"/>
89
+ <line number="197" hits="1"/>
90
+ <line number="205" hits="1"/>
91
+ <line number="214" hits="1"/>
92
+ <line number="215" hits="1"/>
93
+ <line number="216" hits="1"/>
94
+ <line number="218" hits="1"/>
95
+ <line number="219" hits="1"/>
96
+ <line number="220" hits="1"/>
97
+ <line number="221" hits="1"/>
98
+ <line number="222" hits="1"/>
99
+ <line number="223" hits="1"/>
100
+ <line number="224" hits="1"/>
101
+ <line number="225" hits="1"/>
102
+ <line number="226" hits="1"/>
103
+ <line number="227" hits="1"/>
104
+ <line number="228" hits="1"/>
105
+ <line number="229" hits="1" branch="true" condition-coverage="100% (2/2)"/>
106
+ <line number="230" hits="1"/>
107
+ <line number="231" hits="1"/>
108
+ <line number="232" hits="1" branch="true" condition-coverage="100% (2/2)"/>
109
+ <line number="241" hits="1" branch="true" condition-coverage="100% (2/2)"/>
110
+ <line number="251" hits="1"/>
111
+ <line number="252" hits="1" branch="true" condition-coverage="100% (2/2)"/>
112
+ <line number="258" hits="1"/>
113
+ <line number="259" hits="1"/>
114
+ <line number="260" hits="1"/>
115
+ <line number="261" hits="1"/>
116
+ <line number="262" hits="1"/>
117
+ <line number="263" hits="1"/>
118
+ <line number="264" hits="1"/>
119
+ <line number="266" hits="1"/>
120
+ <line number="267" hits="1" branch="true" condition-coverage="100% (2/2)"/>
121
+ <line number="268" hits="1"/>
122
+ <line number="269" hits="1"/>
123
+ <line number="270" hits="1" branch="true" condition-coverage="100% (2/2)"/>
124
+ <line number="271" hits="1"/>
125
+ <line number="272" hits="1"/>
126
+ <line number="273" hits="1"/>
127
+ <line number="275" hits="1"/>
128
+ <line number="276" hits="1"/>
129
+ <line number="277" hits="1"/>
130
+ <line number="278" hits="1"/>
131
+ <line number="279" hits="1" branch="true" condition-coverage="100% (2/2)"/>
132
+ <line number="280" hits="1"/>
133
+ <line number="286" hits="1"/>
134
+ <line number="291" hits="1"/>
135
+ <line number="292" hits="1"/>
136
+ <line number="294" hits="1"/>
137
+ <line number="295" hits="1"/>
138
+ <line number="297" hits="1"/>
139
+ <line number="298" hits="1" branch="true" condition-coverage="100% (2/2)"/>
140
+ <line number="299" hits="1"/>
141
+ <line number="300" hits="1"/>
142
+ <line number="301" hits="1"/>
143
+ <line number="302" hits="1" branch="true" condition-coverage="100% (2/2)"/>
144
+ <line number="303" hits="1"/>
145
+ <line number="307" hits="1"/>
146
+ <line number="314" hits="1"/>
147
+ <line number="315" hits="1"/>
148
+ <line number="317" hits="1"/>
149
+ <line number="318" hits="1" branch="true" condition-coverage="100% (2/2)"/>
150
+ <line number="319" hits="1"/>
151
+ <line number="321" hits="1"/>
152
+ <line number="322" hits="1" branch="true" condition-coverage="100% (2/2)"/>
153
+ <line number="339" hits="1"/>
154
+ <line number="340" hits="1"/>
155
+ <line number="341" hits="1" branch="true" condition-coverage="100% (2/2)"/>
156
+ <line number="342" hits="1"/>
157
+ <line number="343" hits="1"/>
158
+ <line number="352" hits="1"/>
159
+ <line number="353" hits="1"/>
160
+ <line number="359" hits="1"/>
161
+ <line number="361" hits="1"/>
162
+ <line number="362" hits="1" branch="true" condition-coverage="100% (2/2)"/>
163
+ <line number="414" hits="1"/>
164
+ <line number="415" hits="1"/>
165
+ <line number="416" hits="1"/>
166
+ <line number="417" hits="1"/>
167
+ <line number="418" hits="1"/>
168
+ <line number="419" hits="1"/>
169
+ <line number="420" hits="1"/>
170
+ <line number="421" hits="1"/>
171
+ <line number="422" hits="1"/>
172
+ <line number="424" hits="1"/>
173
+ <line number="425" hits="1"/>
174
+ <line number="429" hits="1" branch="true" condition-coverage="100% (2/2)"/>
175
+ <line number="430" hits="1"/>
176
+ <line number="434" hits="1" branch="true" condition-coverage="100% (2/2)"/>
177
+ <line number="435" hits="1"/>
178
+ <line number="437" hits="1" branch="true" condition-coverage="100% (2/2)"/>
179
+ <line number="438" hits="1"/>
180
+ <line number="441" hits="1" branch="true" condition-coverage="100% (2/2)"/>
181
+ <line number="442" hits="1" branch="true" condition-coverage="100% (2/2)"/>
182
+ <line number="444" hits="1" branch="true" condition-coverage="100% (2/2)"/>
183
+ <line number="445" hits="1" branch="true" condition-coverage="100% (2/2)"/>
184
+ <line number="447" hits="1" branch="true" condition-coverage="100% (2/2)"/>
185
+ <line number="448" hits="1" branch="true" condition-coverage="100% (2/2)"/>
186
+ <line number="450" hits="1" branch="true" condition-coverage="100% (2/2)"/>
187
+ <line number="451" hits="1" branch="true" condition-coverage="100% (2/2)"/>
188
+ <line number="453" hits="1" branch="true" condition-coverage="100% (2/2)"/>
189
+ <line number="454" hits="1" branch="true" condition-coverage="100% (2/2)"/>
190
+ <line number="457" hits="1" branch="true" condition-coverage="100% (2/2)"/>
191
+ <line number="458" hits="1" branch="true" condition-coverage="100% (2/2)"/>
192
+ <line number="460" hits="1" branch="true" condition-coverage="100% (2/2)"/>
193
+ <line number="461" hits="1" branch="true" condition-coverage="100% (2/2)"/>
194
+ <line number="465" hits="1" branch="true" condition-coverage="100% (2/2)"/>
195
+ <line number="466" hits="1" branch="true" condition-coverage="100% (2/2)"/>
196
+ <line number="468" hits="1" branch="true" condition-coverage="100% (2/2)"/>
197
+ <line number="469" hits="1" branch="true" condition-coverage="100% (2/2)"/>
198
+ <line number="472" hits="1"/>
199
+ <line number="473" hits="1"/>
200
+ <line number="475" hits="1" branch="true" condition-coverage="100% (2/2)"/>
201
+ <line number="476" hits="1"/>
202
+ <line number="478" hits="1"/>
203
+ <line number="479" hits="1"/>
204
+ <line number="481" hits="1" branch="true" condition-coverage="100% (2/2)"/>
205
+ <line number="482" hits="1"/>
206
+ <line number="485" hits="1"/>
207
+ <line number="486" hits="1"/>
208
+ <line number="489" hits="1"/>
209
+ <line number="490" hits="1"/>
210
+ <line number="491" hits="1"/>
211
+ <line number="493" hits="1"/>
212
+ <line number="494" hits="1" branch="true" condition-coverage="100% (2/2)"/>
213
+ <line number="500" hits="1" branch="true" condition-coverage="100% (2/2)"/>
214
+ <line number="503" hits="1"/>
215
+ <line number="504" hits="1"/>
216
+ <line number="505" hits="1" branch="true" condition-coverage="100% (2/2)"/>
217
+ <line number="506" hits="1"/>
218
+ <line number="507" hits="1"/>
219
+ <line number="508" hits="1"/>
220
+ <line number="509" hits="1"/>
221
+ <line number="510" hits="1"/>
222
+ <line number="512" hits="1"/>
223
+ <line number="513" hits="1" branch="true" condition-coverage="100% (2/2)"/>
224
+ <line number="514" hits="1"/>
225
+ <line number="517" hits="1"/>
226
+ <line number="518" hits="1"/>
227
+ <line number="519" hits="1"/>
228
+ <line number="522" hits="1"/>
229
+ <line number="523" hits="1"/>
230
+ <line number="524" hits="1"/>
231
+ <line number="527" hits="1"/>
232
+ <line number="528" hits="1"/>
233
+ <line number="535" hits="1"/>
234
+ </lines>
235
+ </class>
236
+ </classes>
237
+ </package>
238
+ </packages>
239
+ </coverage>
@@ -14,6 +14,7 @@
14
14
  # You should have received a copy of the GNU General Public License along
15
15
  # with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  import datetime
17
+ import importlib.metadata
17
18
  import json
18
19
  import logging
19
20
  import math
@@ -21,7 +22,6 @@ import traceback
21
22
  import uuid
22
23
  from warnings import warn
23
24
 
24
- import pkg_resources
25
25
  from sqlalchemy import (
26
26
  and_,
27
27
  between,
@@ -31,11 +31,14 @@ from sqlalchemy import (
31
31
  DDL,
32
32
  event,
33
33
  ForeignKey,
34
+ func,
34
35
  Integer,
35
36
  not_,
36
37
  or_,
38
+ select,
37
39
  String,
38
40
  Table,
41
+ text,
39
42
  TypeDecorator,
40
43
  Unicode,
41
44
  UnicodeText,
@@ -61,6 +64,9 @@ except ImportError: # pragma: no cover
61
64
  UniqueViolation = lookup_error("23505")
62
65
 
63
66
 
67
+ __version__ = importlib.metadata.version("datanommer.models")
68
+
69
+
64
70
  log = logging.getLogger("datanommer")
65
71
 
66
72
  maker = sessionmaker()
@@ -80,7 +86,7 @@ def init(uri=None, alembic_ini=None, engine=None, create=False):
80
86
  raise ValueError("One of uri or engine must be specified")
81
87
 
82
88
  if uri and not engine:
83
- engine = create_engine(uri)
89
+ engine = create_engine(uri, future=True)
84
90
 
85
91
  # We need to hang our own attribute on the sqlalchemy session to stop
86
92
  # ourselves from initializing twice. That is only a problem if the code
@@ -90,11 +96,12 @@ def init(uri=None, alembic_ini=None, engine=None, create=False):
90
96
  return
91
97
  session._datanommer_initialized = True
92
98
 
93
- session.configure(bind=engine)
99
+ maker.configure(bind=engine)
94
100
  DeclarativeBase.query = session.query_property()
95
101
 
96
102
  if create:
97
- session.execute("CREATE EXTENSION IF NOT EXISTS timescaledb")
103
+ with engine.begin() as connection:
104
+ connection.execute(text("CREATE EXTENSION IF NOT EXISTS timescaledb"))
98
105
  DeclarativeBase.metadata.create_all(engine)
99
106
  # Loads the alembic configuration and generates the version table, with
100
107
  # the most recent revision stamped as head
@@ -122,7 +129,7 @@ def add(message):
122
129
  log.exception("Failed to parse sent-at timestamp value")
123
130
  return
124
131
  else:
125
- sent_at = datetime.datetime.utcnow()
132
+ sent_at = datetime.datetime.now(tz=datetime.timezone.utc)
126
133
 
127
134
  # Workaround schemas misbehaving
128
135
  try:
@@ -158,11 +165,6 @@ def add(message):
158
165
  session.commit()
159
166
 
160
167
 
161
- def source_version_default(context):
162
- dist = pkg_resources.get_distribution("datanommer.models")
163
- return dist.version
164
-
165
-
166
168
  # https://docs.sqlalchemy.org/en/14/core/custom_types.html#marshal-json-strings
167
169
 
168
170
 
@@ -224,7 +226,7 @@ class Message(DeclarativeBase):
224
226
  username = Column(Unicode)
225
227
  crypto = Column(UnicodeText)
226
228
  source_name = Column(Unicode, default="datanommer")
227
- source_version = Column(Unicode, default=source_version_default)
229
+ source_version = Column(Unicode, default=lambda context: __version__)
228
230
  msg = Column(JSONEncodedDict, nullable=False)
229
231
  headers = Column(postgresql.JSONB(none_as_null=True))
230
232
  users = relationship(
@@ -314,7 +316,7 @@ class Message(DeclarativeBase):
314
316
 
315
317
  @classmethod
316
318
  def from_msg_id(cls, msg_id):
317
- return cls.query.filter(cls.msg_id == msg_id).first()
319
+ return session.execute(select(cls).where(cls.msg_id == msg_id)).scalar_one_or_none()
318
320
 
319
321
  def as_dict(self, request=None):
320
322
  return dict(
@@ -337,13 +339,12 @@ class Message(DeclarativeBase):
337
339
  def as_fedora_message_dict(self):
338
340
  headers = self.headers or {}
339
341
  if "sent-at" not in headers:
340
- headers["sent-at"] = self.timestamp.astimezone(
341
- datetime.timezone.utc
342
- ).isoformat()
342
+ headers["sent-at"] = self.timestamp.astimezone(datetime.timezone.utc).isoformat()
343
343
  return dict(
344
344
  body=self.msg,
345
345
  headers=headers,
346
346
  id=self.msg_id,
347
+ priority=headers.get("priority", 0),
347
348
  queue=None,
348
349
  topic=self.topic,
349
350
  )
@@ -353,6 +354,7 @@ class Message(DeclarativeBase):
353
354
  "The __json__() method has been renamed to as_dict(), and will be removed "
354
355
  "in the next major version",
355
356
  DeprecationWarning,
357
+ stacklevel=2,
356
358
  )
357
359
  return self.as_dict(request)
358
360
 
@@ -419,69 +421,55 @@ class Message(DeclarativeBase):
419
421
  not_topics = not_topics or []
420
422
  contains = contains or []
421
423
 
422
- query = Message.query
424
+ Message = cls
425
+ query = select(Message)
423
426
 
424
427
  # A little argument validation. We could provide some defaults in
425
428
  # these mixed cases.. but instead we'll just leave it up to our caller.
426
429
  if (start is not None and end is None) or (end is not None and start is None):
427
430
  raise ValueError(
428
- "Either both start and end must be specified "
429
- "or neither must be specified"
431
+ "Either both start and end must be specified or neither must be specified"
430
432
  )
431
433
 
432
434
  if start and end:
433
- query = query.filter(between(Message.timestamp, start, end))
435
+ query = query.where(between(Message.timestamp, start, end))
434
436
 
435
437
  if msg_id:
436
- query = query.filter(Message.msg_id == msg_id)
438
+ query = query.where(Message.msg_id == msg_id)
437
439
 
438
440
  # Add the four positive filters as necessary
439
441
  if users:
440
- query = query.filter(
441
- or_(*(Message.users.any(User.name == u) for u in users))
442
- )
442
+ query = query.where(or_(*(Message.users.any(User.name == u) for u in users)))
443
443
 
444
444
  if packages:
445
- query = query.filter(
446
- or_(*(Message.packages.any(Package.name == p) for p in packages))
447
- )
445
+ query = query.where(or_(*(Message.packages.any(Package.name == p) for p in packages)))
448
446
 
449
447
  if categories:
450
- query = query.filter(
451
- or_(*(Message.category == category for category in categories))
452
- )
448
+ query = query.where(or_(*(Message.category == category for category in categories)))
453
449
 
454
450
  if topics:
455
- query = query.filter(or_(*(Message.topic == topic for topic in topics)))
451
+ query = query.where(or_(*(Message.topic == topic for topic in topics)))
456
452
 
457
453
  if contains:
458
- query = query.filter(
459
- or_(*(Message.msg.like(f"%{contain}%") for contain in contains))
460
- )
454
+ query = query.where(or_(*(Message.msg.like(f"%{contain}%") for contain in contains)))
461
455
 
462
456
  # And then the four negative filters as necessary
463
457
  if not_users:
464
- query = query.filter(
465
- not_(or_(*(Message.users.any(User.name == u) for u in not_users)))
466
- )
458
+ query = query.where(not_(or_(*(Message.users.any(User.name == u) for u in not_users))))
467
459
 
468
460
  if not_packs:
469
- query = query.filter(
461
+ query = query.where(
470
462
  not_(or_(*(Message.packages.any(Package.name == p) for p in not_packs)))
471
463
  )
472
464
 
473
465
  if not_cats:
474
- query = query.filter(
475
- not_(or_(*(Message.category == category for category in not_cats)))
476
- )
466
+ query = query.where(not_(or_(*(Message.category == category for category in not_cats))))
477
467
 
478
468
  if not_topics:
479
- query = query.filter(
480
- not_(or_(*(Message.topic == topic for topic in not_topics)))
481
- )
469
+ query = query.where(not_(or_(*(Message.topic == topic for topic in not_topics))))
482
470
 
483
471
  # Finally, tag on our pagination arguments
484
- total = query.count()
472
+ total = session.scalar(query.with_only_columns(func.count(Message.id)))
485
473
  query = query.order_by(getattr(Message.timestamp, order)())
486
474
 
487
475
  if not rows_per_page:
@@ -494,12 +482,11 @@ class Message(DeclarativeBase):
494
482
  return total, page, query
495
483
  else:
496
484
  # Execute!
497
- messages = query.all()
485
+ messages = session.scalars(query).all()
498
486
  return total, pages, messages
499
487
 
500
488
 
501
489
  class NamedSingleton:
502
-
503
490
  id = Column(Integer, primary_key=True, autoincrement=True)
504
491
  name = Column(UnicodeText, index=True, unique=True)
505
492
 
@@ -513,8 +500,8 @@ class NamedSingleton:
513
500
  if name in cls._cache:
514
501
  # If we cache the instance, SQLAlchemy will run this query anyway because the instance
515
502
  # will be from a different transaction. So just cache the id.
516
- return cls.query.get(cls._cache[name])
517
- obj = cls.query.filter_by(name=name).one_or_none()
503
+ return session.get(cls, cls._cache[name])
504
+ obj = session.execute(select(cls).where(cls.name == name)).scalar_one_or_none()
518
505
  if obj is None:
519
506
  obj = cls(name=name)
520
507
  session.add(obj)
@@ -546,16 +533,3 @@ def _setup_hypertable(table_class):
546
533
 
547
534
 
548
535
  _setup_hypertable(Message)
549
-
550
-
551
- # Set the version
552
- try: # pragma: no cover
553
- import importlib.metadata
554
-
555
- __version__ = importlib.metadata.version("datanommer.models")
556
- except ImportError: # pragma: no cover
557
- try:
558
- __version__ = pkg_resources.get_distribution("datanommer.models").version
559
- except pkg_resources.DistributionNotFound:
560
- # The app is not installed, but the flask dev server can run it nonetheless.
561
- __version__ = None
@@ -0,0 +1,48 @@
1
+ import pytest
2
+ import sqlalchemy as sa
3
+ from pytest_postgresql import factories
4
+ from pytest_postgresql.janitor import DatabaseJanitor
5
+ from sqlalchemy.orm import scoped_session
6
+
7
+ import datanommer.models as dm
8
+
9
+
10
+ postgresql_proc = factories.postgresql_proc(
11
+ postgres_options="-c shared_preload_libraries=timescaledb -c timescaledb.telemetry_level=off",
12
+ )
13
+
14
+
15
+ @pytest.fixture(scope="session")
16
+ def datanommer_db_url(postgresql_proc):
17
+ return (
18
+ f"postgresql+psycopg2://{postgresql_proc.user}:@"
19
+ f"{postgresql_proc.host}:{postgresql_proc.port}"
20
+ f"/{postgresql_proc.dbname}"
21
+ )
22
+
23
+
24
+ @pytest.fixture()
25
+ def datanommer_db(postgresql_proc, datanommer_db_url):
26
+ with DatabaseJanitor(
27
+ user=postgresql_proc.user,
28
+ host=postgresql_proc.host,
29
+ port=postgresql_proc.port,
30
+ dbname=postgresql_proc.dbname,
31
+ # Don't use a template database
32
+ # template_dbname=postgresql_proc.template_dbname,
33
+ version=postgresql_proc.version,
34
+ ):
35
+ engine = sa.create_engine(datanommer_db_url, future=True, poolclass=sa.pool.NullPool)
36
+ # Renew the global object, dm.init checks a custom attribute
37
+ dm.session = scoped_session(dm.maker)
38
+ dm.init(engine=engine, create=True)
39
+ yield engine
40
+ dm.session.close()
41
+
42
+
43
+ @pytest.fixture()
44
+ def datanommer_models(datanommer_db):
45
+ dm.User.clear_cache()
46
+ dm.Package.clear_cache()
47
+ yield dm.session
48
+ dm.session.rollback()