datanommer.models 1.2.0__tar.gz → 1.3.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.
@@ -2,8 +2,39 @@
2
2
  Release Notes
3
3
  =============
4
4
 
5
+ For ``datanommer.models``
6
+
5
7
  .. towncrier release notes start
6
8
 
9
+ v1.3.0
10
+ ======
11
+
12
+ Released on 2024-05-22.
13
+
14
+ Features
15
+ ^^^^^^^^
16
+
17
+ * Add a ``get_first()`` method on ``Message`` to get the first message matching
18
+ a grep-like query (`99fb739 <https://github.com/fedora-infra/datanommer/commit/99fb739>`_).
19
+
20
+ Bug Fixes
21
+ ^^^^^^^^^
22
+
23
+ * Don't compute the total when not necessary (`99fb739 <https://github.com/fedora-infra/datanommer/commit/99fb739>`_).
24
+
25
+ Documentation Improvements
26
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
27
+
28
+ * Add online documentation with Sphinx, see https://datanommer.readthedocs.io
29
+ (`2631885 <https://github.com/fedora-infra/datanommer/commit/2631885>`_).
30
+
31
+ Other Changes
32
+ ^^^^^^^^^^^^^
33
+
34
+ * Improve the unit tests (`610067f <https://github.com/fedora-infra/datanommer/commit/610067f>`_, `075052c <https://github.com/fedora-infra/datanommer/commit/075052c>`_).
35
+ * Update dependencies
36
+
37
+
7
38
  v1.2.0
8
39
  ======
9
40
 
@@ -21,8 +52,8 @@ Features
21
52
  Development Improvements
22
53
  ^^^^^^^^^^^^^^^^^^^^^^^^
23
54
 
24
- * Use Ruff instead of flake8 and isort and bandit (:issue:`4f7ffaa`
25
- `#4f7ffaa <https://github.com/fedora-infra/datanommer/issues/4f7ffaa>`_).
55
+ * Use Ruff instead of flake8 and isort and bandit (`4f7ffaa
56
+ <https://github.com/fedora-infra/datanommer/commit/4f7ffaa>`_).
26
57
 
27
58
 
28
59
  v1.1.0
@@ -38,8 +69,8 @@ Dependency Changes
38
69
 
39
70
  * Drop support for python 3.7, add support for python 3.10 (`PR#890
40
71
  <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>`_).
72
+ * Add the ``koji-fedoramessaging-messages`` package (`#1257
73
+ <https://github.com/fedora-infra/datanommer/issues/1257>`_).
43
74
 
44
75
 
45
76
  v1.0.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datanommer.models
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: SQLAlchemy models for datanommer
5
5
  Home-page: https://github.com/fedora-infra/datanommer
6
6
  License: GPL-3.0-or-later
@@ -32,6 +32,7 @@ Requires-Dist: fmn-messages ; extra == "schemas"
32
32
  Requires-Dist: kerneltest-messages (>=1.0.0,<2.0.0) ; extra == "schemas"
33
33
  Requires-Dist: koji-fedoramessaging-messages (>=1.2.2,<2.0.0) ; extra == "schemas"
34
34
  Requires-Dist: koschei-messages ; extra == "schemas"
35
+ Requires-Dist: maubot-fedora-messages ; extra == "schemas"
35
36
  Requires-Dist: mdapi-messages ; extra == "schemas"
36
37
  Requires-Dist: mediawiki-messages ; extra == "schemas"
37
38
  Requires-Dist: meetbot-messages ; extra == "schemas"
@@ -1,14 +1,14 @@
1
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 -->
2
+ <coverage version="7.5.1" timestamp="1716382532447" lines-valid="236" lines-covered="236" line-rate="1" branches-valid="108" branches-covered="108" branch-rate="1" complexity="0">
3
+ <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.5.1 -->
4
4
  <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
5
5
  <sources>
6
6
  <source>/home/abompard/Fedora/apps/datanommer/datanommer.models/datanommer</source>
7
7
  </sources>
8
8
  <packages>
9
- <package name="models" line-rate="0.9864" branch-rate="1" complexity="0">
9
+ <package name="models" line-rate="1" branch-rate="1" complexity="0">
10
10
  <classes>
11
- <class name="__init__.py" filename="models/__init__.py" complexity="0" line-rate="0.9864" branch-rate="1">
11
+ <class name="__init__.py" filename="models/__init__.py" complexity="0" line-rate="1" branch-rate="1">
12
12
  <methods/>
13
13
  <lines>
14
14
  <line number="16" hits="1"/>
@@ -61,9 +61,9 @@
61
61
  <line number="132" hits="1"/>
62
62
  <line number="135" hits="1"/>
63
63
  <line number="136" hits="1"/>
64
- <line number="137" hits="0"/>
65
- <line number="138" hits="0"/>
66
- <line number="143" hits="0"/>
64
+ <line number="137" hits="1"/>
65
+ <line number="138" hits="1"/>
66
+ <line number="143" hits="1"/>
67
67
  <line number="144" hits="1"/>
68
68
  <line number="145" hits="1"/>
69
69
  <line number="146" hits="1"/>
@@ -160,77 +160,93 @@
160
160
  <line number="359" hits="1"/>
161
161
  <line number="361" hits="1"/>
162
162
  <line number="362" hits="1" branch="true" condition-coverage="100% (2/2)"/>
163
- <line number="414" hits="1"/>
163
+ <line number="405" hits="1"/>
164
+ <line number="406" hits="1"/>
165
+ <line number="407" hits="1"/>
166
+ <line number="408" hits="1"/>
167
+ <line number="409" hits="1"/>
168
+ <line number="410" hits="1"/>
169
+ <line number="411" hits="1"/>
170
+ <line number="412" hits="1"/>
171
+ <line number="413" hits="1"/>
164
172
  <line number="415" hits="1"/>
165
173
  <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"/>
174
+ <line number="420" hits="1" branch="true" condition-coverage="100% (2/2)"/>
170
175
  <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"/>
176
+ <line number="425" hits="1" branch="true" condition-coverage="100% (2/2)"/>
177
+ <line number="426" hits="1"/>
178
+ <line number="428" hits="1" branch="true" condition-coverage="100% (2/2)"/>
179
+ <line number="429" hits="1"/>
180
+ <line number="432" hits="1" branch="true" condition-coverage="100% (2/2)"/>
181
+ <line number="433" hits="1" branch="true" condition-coverage="100% (2/2)"/>
182
+ <line number="435" hits="1" branch="true" condition-coverage="100% (2/2)"/>
183
+ <line number="436" hits="1" branch="true" condition-coverage="100% (2/2)"/>
184
+ <line number="438" hits="1" branch="true" condition-coverage="100% (2/2)"/>
185
+ <line number="439" hits="1" branch="true" condition-coverage="100% (2/2)"/>
180
186
  <line number="441" hits="1" branch="true" condition-coverage="100% (2/2)"/>
181
187
  <line number="442" hits="1" branch="true" condition-coverage="100% (2/2)"/>
182
188
  <line number="444" hits="1" branch="true" condition-coverage="100% (2/2)"/>
183
189
  <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
190
  <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)"/>
191
+ <line number="449" hits="1" branch="true" condition-coverage="100% (2/2)"/>
187
192
  <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)"/>
193
+ <line number="452" hits="1" branch="true" condition-coverage="100% (2/2)"/>
194
+ <line number="456" hits="1" branch="true" condition-coverage="100% (2/2)"/>
190
195
  <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)"/>
196
+ <line number="459" hits="1" branch="true" condition-coverage="100% (2/2)"/>
192
197
  <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)"/>
198
+ <line number="462" hits="1"/>
199
+ <line number="464" hits="1"/>
194
200
  <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"/>
201
+ <line number="511" hits="1"/>
202
+ <line number="513" hits="1"/>
203
+ <line number="515" hits="1"/>
204
+ <line number="516" hits="1"/>
225
205
  <line number="517" hits="1"/>
226
- <line number="518" hits="1"/>
227
- <line number="519" hits="1"/>
206
+ <line number="519" hits="1" branch="true" condition-coverage="100% (2/2)"/>
207
+ <line number="520" hits="1"/>
228
208
  <line number="522" hits="1"/>
229
209
  <line number="523" hits="1"/>
230
210
  <line number="524" hits="1"/>
231
- <line number="527" hits="1"/>
211
+ <line number="526" hits="1" branch="true" condition-coverage="100% (2/2)"/>
212
+ <line number="527" hits="1" branch="true" condition-coverage="100% (2/2)"/>
232
213
  <line number="528" hits="1"/>
214
+ <line number="529" hits="1"/>
215
+ <line number="532" hits="1"/>
216
+ <line number="533" hits="1" branch="true" condition-coverage="100% (2/2)"/>
217
+ <line number="534" hits="1"/>
233
218
  <line number="535" hits="1"/>
219
+ <line number="537" hits="1"/>
220
+ <line number="538" hits="1" branch="true" condition-coverage="100% (2/2)"/>
221
+ <line number="540" hits="1"/>
222
+ <line number="541" hits="1"/>
223
+ <line number="542" hits="1"/>
224
+ <line number="545" hits="1"/>
225
+ <line number="546" hits="1"/>
226
+ <line number="547" hits="1"/>
227
+ <line number="549" hits="1"/>
228
+ <line number="550" hits="1" branch="true" condition-coverage="100% (2/2)"/>
229
+ <line number="556" hits="1" branch="true" condition-coverage="100% (2/2)"/>
230
+ <line number="559" hits="1"/>
231
+ <line number="560" hits="1"/>
232
+ <line number="561" hits="1" branch="true" condition-coverage="100% (2/2)"/>
233
+ <line number="562" hits="1"/>
234
+ <line number="563" hits="1"/>
235
+ <line number="564" hits="1"/>
236
+ <line number="565" hits="1"/>
237
+ <line number="566" hits="1"/>
238
+ <line number="568" hits="1"/>
239
+ <line number="569" hits="1" branch="true" condition-coverage="100% (2/2)"/>
240
+ <line number="570" hits="1"/>
241
+ <line number="573" hits="1"/>
242
+ <line number="574" hits="1"/>
243
+ <line number="575" hits="1"/>
244
+ <line number="578" hits="1"/>
245
+ <line number="579" hits="1"/>
246
+ <line number="580" hits="1"/>
247
+ <line number="583" hits="1"/>
248
+ <line number="584" hits="1"/>
249
+ <line number="591" hits="1"/>
234
250
  </lines>
235
251
  </class>
236
252
  </classes>
@@ -168,7 +168,7 @@ def add(message):
168
168
  # https://docs.sqlalchemy.org/en/14/core/custom_types.html#marshal-json-strings
169
169
 
170
170
 
171
- class JSONEncodedDict(TypeDecorator):
171
+ class _JSONEncodedDict(TypeDecorator):
172
172
  """Represents an immutable structure as a json-encoded string."""
173
173
 
174
174
  impl = UnicodeText
@@ -227,7 +227,7 @@ class Message(DeclarativeBase):
227
227
  crypto = Column(UnicodeText)
228
228
  source_name = Column(Unicode, default="datanommer")
229
229
  source_version = Column(Unicode, default=lambda context: __version__)
230
- msg = Column(JSONEncodedDict, nullable=False)
230
+ msg = Column(_JSONEncodedDict, nullable=False)
231
231
  headers = Column(postgresql.JSONB(none_as_null=True))
232
232
  users = relationship(
233
233
  "User",
@@ -359,13 +359,10 @@ class Message(DeclarativeBase):
359
359
  return self.as_dict(request)
360
360
 
361
361
  @classmethod
362
- def grep(
362
+ def make_query(
363
363
  cls,
364
364
  start=None,
365
365
  end=None,
366
- page=1,
367
- rows_per_page=100,
368
- order="asc",
369
366
  msg_id=None,
370
367
  users=None,
371
368
  not_users=None,
@@ -376,7 +373,6 @@ class Message(DeclarativeBase):
376
373
  topics=None,
377
374
  not_topics=None,
378
375
  contains=None,
379
- defer=False,
380
376
  ):
381
377
  """Flexible query interface for messages.
382
378
 
@@ -404,11 +400,6 @@ class Message(DeclarativeBase):
404
400
 
405
401
  (user == 'ralph') AND
406
402
  NOT (category == 'bodhi' OR category == 'wiki')
407
-
408
- ----
409
-
410
- If the `defer` argument evaluates to True, the query won't actually
411
- be executed, but a SQLAlchemy query object returned instead.
412
403
  """
413
404
 
414
405
  users = users or []
@@ -468,23 +459,88 @@ class Message(DeclarativeBase):
468
459
  if not_topics:
469
460
  query = query.where(not_(or_(*(Message.topic == topic for topic in not_topics))))
470
461
 
462
+ return query
463
+
464
+ @classmethod
465
+ def grep(
466
+ cls,
467
+ *,
468
+ page=1,
469
+ rows_per_page=100,
470
+ order="asc",
471
+ defer=False,
472
+ **kwargs,
473
+ ):
474
+ """Flexible query interface for messages.
475
+
476
+ Arguments are filters. start and end should be :mod:`datetime` objs.
477
+
478
+ Other filters should be lists of strings. They are applied in a
479
+ conjunctive-normal-form (CNF) kind of way
480
+
481
+ for example, the following::
482
+
483
+ users = ['ralph', 'lmacken']
484
+ categories = ['bodhi', 'wiki']
485
+
486
+ should return messages where
487
+
488
+ (user=='ralph' OR user=='lmacken') AND
489
+ (category=='bodhi' OR category=='wiki')
490
+
491
+ Furthermore, you can use a negative version of each argument.
492
+
493
+ users = ['ralph']
494
+ not_categories = ['bodhi', 'wiki']
495
+
496
+ should return messages where
497
+
498
+ (user == 'ralph') AND
499
+ NOT (category == 'bodhi' OR category == 'wiki')
500
+
501
+ ----
502
+
503
+ The ``jsons`` argument is a list of jsonpath filters, please refer to
504
+ `PostgreSQL's documentation
505
+ <https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-SQLJSON-PATH>`_
506
+ on the matter to learn how to build the jsonpath expression.
507
+
508
+ The ``jsons_and`` argument is similar to the ``jsons`` argument, but all
509
+ the values must match for a message to be returned.
510
+ """
511
+ query = cls.make_query(**kwargs)
471
512
  # Finally, tag on our pagination arguments
472
- total = session.scalar(query.with_only_columns(func.count(Message.id)))
513
+ Message = cls
514
+
515
+ query_total = query.with_only_columns(func.count(Message.id))
516
+ total = None
473
517
  query = query.order_by(getattr(Message.timestamp, order)())
474
518
 
475
519
  if not rows_per_page:
476
520
  pages = 1
477
521
  else:
522
+ total = session.scalar(query_total)
478
523
  pages = int(math.ceil(total / float(rows_per_page)))
479
524
  query = query.offset(rows_per_page * (page - 1)).limit(rows_per_page)
480
525
 
481
526
  if defer:
482
- return total, page, query
527
+ if total is None:
528
+ total = session.scalar(query_total)
529
+ return total, pages, query
483
530
  else:
484
531
  # Execute!
485
532
  messages = session.scalars(query).all()
533
+ if pages == 1:
534
+ total = len(messages)
486
535
  return total, pages, messages
487
536
 
537
+ @classmethod
538
+ def get_first(cls, *, order="asc", **kwargs):
539
+ """Get the first message matching the regular grep filters."""
540
+ query = cls.make_query(**kwargs)
541
+ query = query.order_by(getattr(Message.timestamp, order)())
542
+ return session.scalars(query).first()
543
+
488
544
 
489
545
  class NamedSingleton:
490
546
  id = Column(Integer, primary_key=True, autoincrement=True)
@@ -21,8 +21,8 @@ def datanommer_db_url(postgresql_proc):
21
21
  )
22
22
 
23
23
 
24
- @pytest.fixture()
25
- def datanommer_db(postgresql_proc, datanommer_db_url):
24
+ @pytest.fixture(scope="session")
25
+ def datanommer_db_engine(postgresql_proc, datanommer_db_url):
26
26
  with DatabaseJanitor(
27
27
  user=postgresql_proc.user,
28
28
  host=postgresql_proc.host,
@@ -32,12 +32,20 @@ def datanommer_db(postgresql_proc, datanommer_db_url):
32
32
  # template_dbname=postgresql_proc.template_dbname,
33
33
  version=postgresql_proc.version,
34
34
  ):
35
- engine = sa.create_engine(datanommer_db_url, future=True, poolclass=sa.pool.NullPool)
35
+ engine = sa.create_engine(datanommer_db_url, future=True)
36
36
  # Renew the global object, dm.init checks a custom attribute
37
37
  dm.session = scoped_session(dm.maker)
38
38
  dm.init(engine=engine, create=True)
39
39
  yield engine
40
- dm.session.close()
40
+ engine.dispose()
41
+
42
+
43
+ @pytest.fixture()
44
+ def datanommer_db(datanommer_db_url, datanommer_db_engine):
45
+ for table in reversed(dm.DeclarativeBase.metadata.sorted_tables):
46
+ dm.session.execute(table.delete())
47
+ dm.session.commit()
48
+ yield datanommer_db_engine
41
49
 
42
50
 
43
51
  @pytest.fixture()
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "datanommer.models"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  description = "SQLAlchemy models for datanommer"
5
5
  authors = [
6
6
  "Fedora Infrastructure <admin@fedoraproject.org>"
@@ -48,6 +48,7 @@ fmn-messages = {version = "*", optional = true}
48
48
  kerneltest-messages = {version = "^1.0.0", optional = true}
49
49
  koji-fedoramessaging-messages = {version = "^1.2.2", optional = true}
50
50
  koschei-messages = {version = "*", optional = true}
51
+ maubot-fedora-messages = {version = "*", optional = true}
51
52
  mediawiki-messages = {version = "*", optional = true}
52
53
  meetbot-messages = {version = "*", optional = true}
53
54
  mdapi-messages = {version = "*", optional = true}
@@ -86,6 +87,7 @@ schemas = [
86
87
  "kerneltest-messages",
87
88
  "koji-fedoramessaging-messages",
88
89
  "koschei-messages",
90
+ "maubot-fedora-messages",
89
91
  "mediawiki-messages",
90
92
  "meetbot-messages",
91
93
  "mdapi-messages",
@@ -1,7 +1,7 @@
1
1
  import pytest
2
2
  from sqlalchemy import Column, create_engine, Integer, MetaData, select, Table, text
3
3
 
4
- from datanommer.models import JSONEncodedDict
4
+ from datanommer.models import _JSONEncodedDict
5
5
 
6
6
 
7
7
  @pytest.fixture
@@ -18,7 +18,7 @@ def table(connection):
18
18
  "test_table",
19
19
  metadata,
20
20
  Column("id", Integer, primary_key=True),
21
- Column("data", JSONEncodedDict),
21
+ Column("data", _JSONEncodedDict),
22
22
  )
23
23
  metadata.create_all(connection)
24
24
  yield table
@@ -62,6 +62,15 @@ def generate_bodhi_update_complete_message(text="testing testing"):
62
62
  return msg
63
63
 
64
64
 
65
+ @pytest.fixture
66
+ def add_200_messages(datanommer_models):
67
+ for x in range(0, 200):
68
+ example_message = generate_message()
69
+ example_message.id = f"{x}"
70
+ dm.add(example_message)
71
+ dm.session.flush()
72
+
73
+
65
74
  def test_init_uri_and_engine():
66
75
  uri = "sqlite:///db.db"
67
76
  engine = create_engine(uri, future=True)
@@ -131,9 +140,9 @@ def test_add_missing_timestamp(datanommer_models):
131
140
 
132
141
  dbmsg = dm.session.scalar(select(dm.Message))
133
142
  timediff = datetime.datetime.now() - dbmsg.timestamp
134
- # 10 seconds between adding the message and checking
143
+ # 60 seconds between adding the message and checking
135
144
  # the timestamp should be more than enough.
136
- assert timediff < datetime.timedelta(seconds=10)
145
+ assert timediff < datetime.timedelta(seconds=60)
137
146
 
138
147
 
139
148
  def test_add_timestamp_with_Z(datanommer_models):
@@ -419,39 +428,20 @@ def test_grep_contains(datanommer_models):
419
428
  assert r[0].msg == example_message.body
420
429
 
421
430
 
422
- def test_grep_rows_per_page_none(datanommer_models):
423
- for x in range(0, 200):
424
- example_message = generate_message()
425
- example_message.id = f"{x}"
426
- dm.add(example_message)
427
-
428
- dm.session.flush()
429
-
431
+ def test_grep_rows_per_page(datanommer_models, add_200_messages):
430
432
  total, pages, messages = dm.Message.grep()
431
433
  assert total == 200
432
434
  assert pages == 2
433
435
  assert len(messages) == 100
434
436
 
435
- total, pages, messages = dm.Message.grep(rows_per_page=None)
436
- assert total == 200
437
- assert pages == 1
438
- assert len(messages) == 200
439
-
440
-
441
- def test_grep_rows_per_page_zero(datanommer_models):
442
- for x in range(0, 200):
443
- example_message = generate_message()
444
- example_message.id = f"{x}"
445
- dm.add(example_message)
446
- dm.session.flush()
447
-
448
- try:
449
- total, pages, messages = dm.Message.grep(rows_per_page=0)
450
- except ZeroDivisionError as e:
451
- pytest.fail(e)
452
- assert total == 200
453
- assert pages == 1
454
- assert len(messages) == 200
437
+ for rows_per_page in (None, 0):
438
+ try:
439
+ total, pages, messages = dm.Message.grep(rows_per_page=rows_per_page)
440
+ except ZeroDivisionError as e:
441
+ pytest.fail(e)
442
+ assert total == 200
443
+ assert pages == 1
444
+ assert len(messages) == 200
455
445
 
456
446
 
457
447
  def test_grep_defer(datanommer_models):
@@ -466,6 +456,33 @@ def test_grep_defer(datanommer_models):
466
456
  assert dm.session.scalars(query).all() == dm.Message.grep()[2]
467
457
 
468
458
 
459
+ def test_grep_no_paging_and_defer(datanommer_models, add_200_messages):
460
+ total, pages, messages = dm.Message.grep(rows_per_page=0, defer=True)
461
+ assert total == 200
462
+ assert pages == 1
463
+
464
+
465
+ def test_grep_no_total_if_single_page(datanommer_models, add_200_messages, mocker):
466
+ # Assert we don't query the total of messages if we're getting them all anyway
467
+ scalar_spy = mocker.spy(dm.session, "scalar")
468
+ total, pages, messages = dm.Message.grep(rows_per_page=0)
469
+ assert total == 200
470
+ scalar_spy.assert_not_called()
471
+
472
+
473
+ def test_get_first(datanommer_models):
474
+ messages = []
475
+ for x in range(0, 200):
476
+ example_message = generate_message()
477
+ example_message.id = f"{x}"
478
+ dm.add(example_message)
479
+ messages.append(example_message)
480
+ dm.session.flush()
481
+ msg = dm.Message.get_first()
482
+ assert msg.msg_id == "0"
483
+ assert msg.msg == messages[0].body
484
+
485
+
469
486
  def test_add_duplicate(datanommer_models, caplog):
470
487
  example_message = generate_message()
471
488
  dm.add(example_message)
@@ -511,7 +528,10 @@ def test_add_duplicate_package(datanommer_models):
511
528
  assert dbmsg.packages[0].name == "pkg"
512
529
 
513
530
 
514
- def test_add_message_with_error_on_packages(datanommer_models, caplog):
531
+ @pytest.mark.parametrize(
532
+ "property_name,name_in_msg", [("usernames", "users"), ("packages", "packages")]
533
+ )
534
+ def test_add_message_with_error_on_property(datanommer_models, caplog, property_name, name_in_msg):
515
535
  # Define a special message schema and register it
516
536
  class CustomMessage(fedora_message.Message):
517
537
  @property
@@ -521,6 +541,11 @@ def test_add_message_with_error_on_packages(datanommer_models, caplog):
521
541
  def _filter_headers(self):
522
542
  return {}
523
543
 
544
+ def _crash(self):
545
+ raise KeyError
546
+
547
+ setattr(CustomMessage, property_name, property(_crash))
548
+
524
549
  fedora_message._schema_name_to_class["CustomMessage"] = CustomMessage
525
550
  fedora_message._class_to_schema_name[CustomMessage] = "CustomMessage"
526
551
  example_message = CustomMessage(
@@ -534,7 +559,7 @@ def test_add_message_with_error_on_packages(datanommer_models, caplog):
534
559
  pytest.fail(e)
535
560
  assert dm.session.scalar(select(func.count(dm.Message.id))) == 1
536
561
  assert caplog.records[0].message == (
537
- f"Could not get the list of packages from a message on "
562
+ f"Could not get the list of {name_in_msg} from a message on "
538
563
  f"org.fedoraproject.test.a.nice.message with id {example_message.id}"
539
564
  )
540
565