datanommer.models 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
@@ -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
  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,10 +1,10 @@
1
- datanommer/models/__init__.py,sha256=ImCJYqPGAPBjx5OoVu6Lbo0mC6Zu9buGfrdgbectsTc,16874
1
+ datanommer/models/__init__.py,sha256=j3ZIgc2XnXoHaTuC2edq9RoVEqx0g8dc9-3dKi3ctBU,18628
2
2
  datanommer/models/alembic/env.py,sha256=WNTimgnH70CakhvuV5QCilCnOcjTy7kcx0nD7hryYx0,2793
3
3
  datanommer/models/alembic/script.py.mako,sha256=D8kFI44_9vBJZrAYSkZxDTX2-S5Y-oEetEd9BKlo9S8,412
4
4
  datanommer/models/alembic/versions/5db25abc63be_init.py,sha256=xMD7WGCOqeVNFroCZds_aS_jta2yTrAHc_XhtmZLZRs,249
5
5
  datanommer/models/alembic/versions/951c40020acc_unique.py,sha256=GwKDhppKW7y5BUV8BqILZCAiR7GsNyIvoTXUu2A1ZMI,843
6
- datanommer/models/testing/__init__.py,sha256=T_uy2jBp5TG507WAVTx4A96_SsFgy4UrY6WKOuG_l1Q,1453
7
- datanommer_models-1.2.0.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
8
- datanommer_models-1.2.0.dist-info/METADATA,sha256=KxGFi0ZMyjdK7oslLs8ImIlD9oHogCWF4MXOj2Yyg0k,2502
9
- datanommer_models-1.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
10
- datanommer_models-1.2.0.dist-info/RECORD,,
6
+ datanommer/models/testing/__init__.py,sha256=wwAZ-s1U4M7nYNxHgJsn5ZIqLuHMzHrJwGJOwgHAFgc,1693
7
+ datanommer_models-1.3.0.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
8
+ datanommer_models-1.3.0.dist-info/METADATA,sha256=wKhrCYMD6cECmPOoosFOEIZdtAezEhw1SxwDxlotE54,2561
9
+ datanommer_models-1.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
10
+ datanommer_models-1.3.0.dist-info/RECORD,,