datanommer.models 1.0.2__py3-none-any.whl → 1.2.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.
- datanommer/models/__init__.py +35 -61
- datanommer/models/alembic/env.py +93 -0
- datanommer/models/alembic/script.py.mako +22 -0
- datanommer/models/alembic/versions/5db25abc63be_init.py +19 -0
- datanommer/models/alembic/versions/951c40020acc_unique.py +27 -0
- datanommer/models/testing/__init__.py +29 -27
- datanommer_models-1.2.0.dist-info/METADATA +54 -0
- datanommer_models-1.2.0.dist-info/RECORD +10 -0
- {datanommer.models-1.0.2.dist-info → datanommer_models-1.2.0.dist-info}/WHEEL +1 -1
- datanommer/models/testing/startup.sql +0 -1
- datanommer.models-1.0.2.dist-info/METADATA +0 -41
- datanommer.models-1.0.2.dist-info/RECORD +0 -11
- tests/conftest.py +0 -1
- tests/test_jsonencodeddict.py +0 -61
- tests/test_model.py +0 -628
- tox.ini +0 -15
- {datanommer.models-1.0.2.dist-info → datanommer_models-1.2.0.dist-info}/LICENSE +0 -0
datanommer/models/__init__.py
CHANGED
@@ -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
|
-
|
99
|
+
maker.configure(bind=engine)
|
94
100
|
DeclarativeBase.query = session.query_property()
|
95
101
|
|
96
102
|
if create:
|
97
|
-
|
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.
|
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=
|
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.
|
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
|
-
|
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.
|
435
|
+
query = query.where(between(Message.timestamp, start, end))
|
434
436
|
|
435
437
|
if msg_id:
|
436
|
-
query = query.
|
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.
|
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.
|
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.
|
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.
|
451
|
+
query = query.where(or_(*(Message.topic == topic for topic in topics)))
|
456
452
|
|
457
453
|
if contains:
|
458
|
-
query = query.
|
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.
|
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.
|
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.
|
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.
|
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
|
517
|
-
obj = cls.
|
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,93 @@
|
|
1
|
+
# This file is a part of datanommer, a message sink for fedmsg.
|
2
|
+
# Copyright (C) 2014, Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License along
|
15
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
from logging.config import fileConfig
|
18
|
+
|
19
|
+
from alembic import context
|
20
|
+
from sqlalchemy import engine_from_config, pool
|
21
|
+
|
22
|
+
# add your model's MetaData object here
|
23
|
+
# for 'autogenerate' support
|
24
|
+
# from myapp import mymodel
|
25
|
+
# target_metadata = mymodel.Base.metadata
|
26
|
+
from datanommer.models import DeclarativeBase
|
27
|
+
|
28
|
+
|
29
|
+
target_metadata = DeclarativeBase.metadata
|
30
|
+
|
31
|
+
|
32
|
+
# this is the Alembic Config object, which provides
|
33
|
+
# access to the values within the .ini file in use.
|
34
|
+
config = context.config
|
35
|
+
|
36
|
+
# Interpret the config file for Python logging.
|
37
|
+
# This line sets up loggers basically.
|
38
|
+
fileConfig(config.config_file_name)
|
39
|
+
|
40
|
+
# other values from the config, defined by the needs of env.py,
|
41
|
+
# can be acquired:
|
42
|
+
# my_important_option = config.get_main_option("my_important_option")
|
43
|
+
# ... etc.
|
44
|
+
|
45
|
+
|
46
|
+
def run_migrations_offline():
|
47
|
+
"""Run migrations in 'offline' mode.
|
48
|
+
|
49
|
+
This configures the context with just a URL
|
50
|
+
and not an Engine, though an Engine is acceptable
|
51
|
+
here as well. By skipping the Engine creation
|
52
|
+
we don't even need a DBAPI to be available.
|
53
|
+
|
54
|
+
Calls to context.execute() here emit the given string to the
|
55
|
+
script output.
|
56
|
+
|
57
|
+
"""
|
58
|
+
# TODO: Pull this from datanommer's fedmsg.d config isntead of using
|
59
|
+
# the alembic.ini
|
60
|
+
url = config.get_main_option("sqlalchemy.url")
|
61
|
+
context.configure(url=url)
|
62
|
+
|
63
|
+
with context.begin_transaction():
|
64
|
+
context.run_migrations()
|
65
|
+
|
66
|
+
|
67
|
+
def run_migrations_online():
|
68
|
+
"""Run migrations in 'online' mode.
|
69
|
+
|
70
|
+
In this scenario we need to create an Engine
|
71
|
+
and associate a connection with the context.
|
72
|
+
|
73
|
+
"""
|
74
|
+
engine = engine_from_config(
|
75
|
+
config.get_section(config.config_ini_section),
|
76
|
+
prefix="sqlalchemy.",
|
77
|
+
poolclass=pool.NullPool,
|
78
|
+
)
|
79
|
+
|
80
|
+
connection = engine.connect()
|
81
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
82
|
+
|
83
|
+
try:
|
84
|
+
with context.begin_transaction():
|
85
|
+
context.run_migrations()
|
86
|
+
finally:
|
87
|
+
connection.close()
|
88
|
+
|
89
|
+
|
90
|
+
if context.is_offline_mode():
|
91
|
+
run_migrations_offline()
|
92
|
+
else:
|
93
|
+
run_migrations_online()
|
@@ -0,0 +1,22 @@
|
|
1
|
+
"""${message}
|
2
|
+
|
3
|
+
Revision ID: ${up_revision}
|
4
|
+
Revises: ${down_revision}
|
5
|
+
Create Date: ${create_date}
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
# revision identifiers, used by Alembic.
|
10
|
+
revision = ${repr(up_revision)}
|
11
|
+
down_revision = ${repr(down_revision)}
|
12
|
+
|
13
|
+
from alembic import op
|
14
|
+
import sqlalchemy as sa
|
15
|
+
${imports if imports else ""}
|
16
|
+
|
17
|
+
def upgrade():
|
18
|
+
${upgrades if upgrades else "pass"}
|
19
|
+
|
20
|
+
|
21
|
+
def downgrade():
|
22
|
+
${downgrades if downgrades else "pass"}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"""Initial revision
|
2
|
+
|
3
|
+
Revision ID: 5db25abc63be
|
4
|
+
Revises: None
|
5
|
+
Create Date: 2021-09-15 16:15:37.188484
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
# revision identifiers, used by Alembic.
|
10
|
+
revision = "5db25abc63be"
|
11
|
+
down_revision = None
|
12
|
+
|
13
|
+
|
14
|
+
def upgrade():
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
def downgrade():
|
19
|
+
pass
|
@@ -0,0 +1,27 @@
|
|
1
|
+
"""Add a unique index on packages and users
|
2
|
+
|
3
|
+
Revision ID: 951c40020acc
|
4
|
+
Revises: 5db25abc63be
|
5
|
+
Create Date: 2021-09-22 15:38:57.339646
|
6
|
+
"""
|
7
|
+
|
8
|
+
from alembic import op
|
9
|
+
|
10
|
+
|
11
|
+
# revision identifiers, used by Alembic.
|
12
|
+
revision = "951c40020acc"
|
13
|
+
down_revision = "5db25abc63be"
|
14
|
+
|
15
|
+
|
16
|
+
def upgrade():
|
17
|
+
op.drop_index("ix_packages_name", table_name="packages")
|
18
|
+
op.create_index(op.f("ix_packages_name"), "packages", ["name"], unique=True)
|
19
|
+
op.drop_index("ix_users_name", table_name="users")
|
20
|
+
op.create_index(op.f("ix_users_name"), "users", ["name"], unique=True)
|
21
|
+
|
22
|
+
|
23
|
+
def downgrade():
|
24
|
+
op.drop_index(op.f("ix_users_name"), table_name="users")
|
25
|
+
op.create_index("ix_users_name", "users", ["name"], unique=False)
|
26
|
+
op.drop_index(op.f("ix_packages_name"), table_name="packages")
|
27
|
+
op.create_index("ix_packages_name", "packages", ["name"], unique=False)
|
@@ -1,46 +1,48 @@
|
|
1
|
-
import os
|
2
|
-
|
3
1
|
import pytest
|
2
|
+
import sqlalchemy as sa
|
4
3
|
from pytest_postgresql import factories
|
4
|
+
from pytest_postgresql.janitor import DatabaseJanitor
|
5
5
|
from sqlalchemy.orm import scoped_session
|
6
6
|
|
7
7
|
import datanommer.models as dm
|
8
8
|
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
with open(os.path.join(postgresql_proc.datadir, "postgresql.conf"), "a") as pgconf:
|
13
|
-
pgconf.write("\nshared_preload_libraries = 'timescaledb'\n")
|
14
|
-
pgconf.write("timescaledb.telemetry_level=off\n")
|
15
|
-
postgresql_proc.stop()
|
16
|
-
postgresql_proc.start()
|
17
|
-
yield postgresql_proc
|
18
|
-
|
19
|
-
|
20
|
-
_inital_sql = os.path.abspath(os.path.join(os.path.dirname(__file__), "startup.sql"))
|
21
|
-
pgsql = factories.postgresql(
|
22
|
-
"postgresql_proc_with_timescaledb",
|
23
|
-
load=[_inital_sql],
|
10
|
+
postgresql_proc = factories.postgresql_proc(
|
11
|
+
postgres_options="-c shared_preload_libraries=timescaledb -c timescaledb.telemetry_level=off",
|
24
12
|
)
|
25
13
|
|
26
14
|
|
27
|
-
@pytest.fixture()
|
28
|
-
def datanommer_db_url(
|
15
|
+
@pytest.fixture(scope="session")
|
16
|
+
def datanommer_db_url(postgresql_proc):
|
29
17
|
return (
|
30
|
-
f"postgresql+psycopg2://{
|
31
|
-
f"{
|
32
|
-
f"/{
|
18
|
+
f"postgresql+psycopg2://{postgresql_proc.user}:@"
|
19
|
+
f"{postgresql_proc.host}:{postgresql_proc.port}"
|
20
|
+
f"/{postgresql_proc.dbname}"
|
33
21
|
)
|
34
22
|
|
35
23
|
|
36
24
|
@pytest.fixture()
|
37
|
-
def
|
38
|
-
|
39
|
-
|
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):
|
40
45
|
dm.User.clear_cache()
|
41
46
|
dm.Package.clear_cache()
|
42
47
|
yield dm.session
|
43
48
|
dm.session.rollback()
|
44
|
-
# engine = dm.session.get_bind()
|
45
|
-
dm.session.close()
|
46
|
-
# dm.DeclarativeBase.metadata.drop_all(engine)
|
@@ -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,10 @@
|
|
1
|
+
datanommer/models/__init__.py,sha256=ImCJYqPGAPBjx5OoVu6Lbo0mC6Zu9buGfrdgbectsTc,16874
|
2
|
+
datanommer/models/alembic/env.py,sha256=WNTimgnH70CakhvuV5QCilCnOcjTy7kcx0nD7hryYx0,2793
|
3
|
+
datanommer/models/alembic/script.py.mako,sha256=D8kFI44_9vBJZrAYSkZxDTX2-S5Y-oEetEd9BKlo9S8,412
|
4
|
+
datanommer/models/alembic/versions/5db25abc63be_init.py,sha256=xMD7WGCOqeVNFroCZds_aS_jta2yTrAHc_XhtmZLZRs,249
|
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,,
|
@@ -1 +0,0 @@
|
|
1
|
-
CREATE EXTENSION IF NOT EXISTS timescaledb;
|
@@ -1,41 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: datanommer.models
|
3
|
-
Version: 1.0.2
|
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.7,<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.7
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
15
|
-
Provides-Extra: schemas
|
16
|
-
Requires-Dist: SQLAlchemy (>=1.0.9,<1.4.23)
|
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: copr-messaging; extra == "schemas"
|
21
|
-
Requires-Dist: discourse2fedmsg-messages; extra == "schemas"
|
22
|
-
Requires-Dist: fedocal-messages; extra == "schemas"
|
23
|
-
Requires-Dist: fedora-messaging (>=2.1.0,<3.0.0)
|
24
|
-
Requires-Dist: fedora-messaging-the-new-hotness-schema; extra == "schemas"
|
25
|
-
Requires-Dist: fedora-planet-messages; extra == "schemas"
|
26
|
-
Requires-Dist: fedorainfra-ansible-messages; extra == "schemas"
|
27
|
-
Requires-Dist: noggin-messages; extra == "schemas"
|
28
|
-
Requires-Dist: nuancier-messages; extra == "schemas"
|
29
|
-
Requires-Dist: psycopg2 (>=2.9.1,<3.0.0)
|
30
|
-
Project-URL: Repository, https://github.com/fedora-infra/datanommer
|
31
|
-
Description-Content-Type: text/x-rst
|
32
|
-
|
33
|
-
datanommer.models
|
34
|
-
=================
|
35
|
-
|
36
|
-
This package contains the SQLAlchemy data model for datanommer.
|
37
|
-
|
38
|
-
Datanommer is a storage consumer for the Fedora Infrastructure Message Bus
|
39
|
-
(fedmsg). It is comprised of a `fedmsg <http://fedmsg.com>`_ consumer that
|
40
|
-
stuffs every message into a sqlalchemy database.
|
41
|
-
|
@@ -1,11 +0,0 @@
|
|
1
|
-
datanommer/models/__init__.py,sha256=4smdWjO0DSxKQeOn0UAfH93eUKQPtTKklqnBdWbGP0Q,17280
|
2
|
-
datanommer/models/testing/__init__.py,sha256=4aNMycVGrYA6Ljo74ePweDrzSr9oaA7K_W2gtn0YZ74,1271
|
3
|
-
datanommer/models/testing/startup.sql,sha256=tdjQMZcM7bVDoLnfSg1-fpKKiAPihiVPy-syF0H8mvo,44
|
4
|
-
tests/conftest.py,sha256=W9WzREc28ksZGv2m1IFgHcrffKG_V1PFPqnRgxsxTSs,45
|
5
|
-
tests/test_jsonencodeddict.py,sha256=C3cEFTx-hQHoHZUczAQ2LDFVC6bvPV92DR17Ig2VnX4,1928
|
6
|
-
tests/test_model.py,sha256=NWV5Z7guirls57A73HUsRxiyOnpWAOYIDUh0Afk4nX8,18950
|
7
|
-
tox.ini,sha256=5ichc5c0HCb16Du3ZB9Kv4tDmI9VGjAfRLJB7Vo_XVs,260
|
8
|
-
datanommer.models-1.0.2.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
9
|
-
datanommer.models-1.0.2.dist-info/WHEEL,sha256=V7iVckP-GYreevsTDnv1eAinQt_aArwnAxmnP0gygBY,83
|
10
|
-
datanommer.models-1.0.2.dist-info/METADATA,sha256=zGoHqTFLbwPrHB0gCuHOGKj6sk75UYv2SHJkckmMYDw,1734
|
11
|
-
datanommer.models-1.0.2.dist-info/RECORD,,
|
tests/conftest.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
pytest_plugins = "datanommer.models.testing"
|
tests/test_jsonencodeddict.py
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from sqlalchemy import Column, create_engine, Integer, MetaData, Table
|
3
|
-
from sqlalchemy.sql import select
|
4
|
-
|
5
|
-
from datanommer.models import JSONEncodedDict
|
6
|
-
|
7
|
-
|
8
|
-
@pytest.fixture
|
9
|
-
def connection():
|
10
|
-
engine = create_engine("sqlite:///:memory:")
|
11
|
-
with engine.connect() as connection:
|
12
|
-
yield connection
|
13
|
-
|
14
|
-
|
15
|
-
@pytest.fixture
|
16
|
-
def table(connection):
|
17
|
-
metadata = MetaData()
|
18
|
-
table = Table(
|
19
|
-
"test_table",
|
20
|
-
metadata,
|
21
|
-
Column("id", Integer, primary_key=True),
|
22
|
-
Column("data", JSONEncodedDict),
|
23
|
-
)
|
24
|
-
metadata.create_all(connection)
|
25
|
-
yield table
|
26
|
-
metadata.drop_all(connection)
|
27
|
-
|
28
|
-
|
29
|
-
def test_jsonencodeddict(connection, table):
|
30
|
-
connection.execute(table.insert().values(data={"foo": "bar"}))
|
31
|
-
# Check that it's stored as a string
|
32
|
-
for row in connection.execute("SELECT data FROM test_table"):
|
33
|
-
assert row["data"] == '{"foo": "bar"}'
|
34
|
-
# Check that SQLAlchemy retrieves it as a dict
|
35
|
-
for row in connection.execute(select(table.c.data)):
|
36
|
-
assert row["data"] == {"foo": "bar"}
|
37
|
-
|
38
|
-
|
39
|
-
def test_jsonencodeddict_null(connection, table):
|
40
|
-
# Make sure NULL values are supported
|
41
|
-
connection.execute(table.insert().values(data=None))
|
42
|
-
for row in connection.execute(select(table.c.data)):
|
43
|
-
assert row["data"] is None
|
44
|
-
|
45
|
-
|
46
|
-
def test_jsonencodeddict_compare(connection, table):
|
47
|
-
# Make sure NULL values are supported
|
48
|
-
connection.execute(table.insert().values(data={"foo": "bar"}))
|
49
|
-
for row in connection.execute(
|
50
|
-
select(table.c.data).filter(table.c.data == {"foo": "bar"})
|
51
|
-
):
|
52
|
-
assert row["data"] == {"foo": "bar"}
|
53
|
-
|
54
|
-
|
55
|
-
def test_jsonencodeddict_compare_like(connection, table):
|
56
|
-
# Make sure NULL values are supported
|
57
|
-
connection.execute(table.insert().values(data={"foo": "bar"}))
|
58
|
-
for row in connection.execute(
|
59
|
-
select(table.c.data).filter(table.c.data.like("%foo%"))
|
60
|
-
):
|
61
|
-
assert row["data"] == {"foo": "bar"}
|
tests/test_model.py
DELETED
@@ -1,628 +0,0 @@
|
|
1
|
-
# This file is a part of datanommer, a message sink for fedmsg.
|
2
|
-
# Copyright (C) 2014, Red Hat, Inc.
|
3
|
-
#
|
4
|
-
# This program is free software: you can redistribute it and/or modify it under
|
5
|
-
# the terms of the GNU General Public License as published by the Free Software
|
6
|
-
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
-
# version.
|
8
|
-
#
|
9
|
-
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
-
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
-
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
12
|
-
# details.
|
13
|
-
#
|
14
|
-
# You should have received a copy of the GNU General Public License along
|
15
|
-
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
-
import datetime
|
17
|
-
import json
|
18
|
-
import logging
|
19
|
-
|
20
|
-
import pytest
|
21
|
-
from bodhi.messages.schemas.update import UpdateCommentV1
|
22
|
-
from fedora_messaging import message as fedora_message
|
23
|
-
from sqlalchemy import create_engine
|
24
|
-
from sqlalchemy.exc import IntegrityError
|
25
|
-
from sqlalchemy.orm.query import Query
|
26
|
-
|
27
|
-
from datanommer.models import add, init, Message, Package, session, User
|
28
|
-
|
29
|
-
|
30
|
-
def generate_message(
|
31
|
-
topic="org.fedoraproject.test.a.nice.message",
|
32
|
-
body={"encouragement": "You're doing great!"},
|
33
|
-
headers=None,
|
34
|
-
):
|
35
|
-
return fedora_message.Message(topic=topic, body=body, headers=headers)
|
36
|
-
|
37
|
-
|
38
|
-
def generate_bodhi_update_complete_message(text="testing testing"):
|
39
|
-
msg = UpdateCommentV1(
|
40
|
-
body={
|
41
|
-
"comment": {
|
42
|
-
"karma": -1,
|
43
|
-
"text": text,
|
44
|
-
"timestamp": "2019-03-18 16:54:48",
|
45
|
-
"update": {
|
46
|
-
"alias": "FEDORA-EPEL-2021-f2d195dada",
|
47
|
-
"builds": [
|
48
|
-
{"nvr": "abrt-addon-python3-2.1.11-50.el7"},
|
49
|
-
{"nvr": "kernel-10.4.0-2.el7"},
|
50
|
-
],
|
51
|
-
"status": "pending",
|
52
|
-
"release": {"name": "F35"},
|
53
|
-
"request": "testing",
|
54
|
-
"user": {"name": "ryanlerch"},
|
55
|
-
},
|
56
|
-
"user": {"name": "dudemcpants"},
|
57
|
-
}
|
58
|
-
}
|
59
|
-
)
|
60
|
-
msg.topic = f"org.fedoraproject.stg.{msg.topic}"
|
61
|
-
return msg
|
62
|
-
|
63
|
-
|
64
|
-
def test_init_uri_and_engine():
|
65
|
-
uri = "sqlite:///db.db"
|
66
|
-
engine = create_engine(uri)
|
67
|
-
|
68
|
-
with pytest.raises(ValueError, match="uri and engine cannot both be specified"):
|
69
|
-
init(uri, engine=engine)
|
70
|
-
|
71
|
-
|
72
|
-
def test_init_no_uri_and_no_engine():
|
73
|
-
with pytest.raises(ValueError, match="One of uri or engine must be specified"):
|
74
|
-
init()
|
75
|
-
|
76
|
-
|
77
|
-
def test_init_with_engine(caplog):
|
78
|
-
uri = "sqlite:///db.db"
|
79
|
-
engine = create_engine(uri)
|
80
|
-
|
81
|
-
init(engine=engine)
|
82
|
-
|
83
|
-
assert not caplog.records
|
84
|
-
|
85
|
-
# if the init with just the engine worked, trying it again will fail
|
86
|
-
init(engine=engine)
|
87
|
-
assert caplog.records[0].message == "Session already initialized. Bailing"
|
88
|
-
|
89
|
-
|
90
|
-
def test_init_no_init_twice(datanommer_models, mocker, caplog):
|
91
|
-
init("sqlite:///db.db")
|
92
|
-
assert caplog.records[0].message == "Session already initialized. Bailing"
|
93
|
-
|
94
|
-
|
95
|
-
def test_unclassified_category(datanommer_models):
|
96
|
-
example_message = generate_message(topic="too.short")
|
97
|
-
add(example_message)
|
98
|
-
dbmsg = Message.query.first()
|
99
|
-
|
100
|
-
assert dbmsg.category == "Unclassified"
|
101
|
-
|
102
|
-
|
103
|
-
def test_from_msg_id(datanommer_models):
|
104
|
-
example_message = generate_message()
|
105
|
-
example_message.id = "ACUSTOMMESSAGEID"
|
106
|
-
add(example_message)
|
107
|
-
dbmsg = Message.from_msg_id("ACUSTOMMESSAGEID")
|
108
|
-
|
109
|
-
assert dbmsg.msg_id == "ACUSTOMMESSAGEID"
|
110
|
-
|
111
|
-
|
112
|
-
def test_add_missing_msg_id(datanommer_models, caplog):
|
113
|
-
caplog.set_level(logging.INFO)
|
114
|
-
example_message = generate_message()
|
115
|
-
example_message._properties.message_id = None
|
116
|
-
add(example_message)
|
117
|
-
dbmsg = Message.query.first()
|
118
|
-
assert (
|
119
|
-
"Message on org.fedoraproject.test.a.nice.message was received without a msg_id"
|
120
|
-
in caplog.records[-1].message
|
121
|
-
)
|
122
|
-
assert dbmsg.msg_id is not None
|
123
|
-
|
124
|
-
|
125
|
-
def test_add_missing_timestamp(datanommer_models):
|
126
|
-
example_message = generate_message()
|
127
|
-
example_message._properties.headers["sent-at"] = None
|
128
|
-
|
129
|
-
add(example_message)
|
130
|
-
|
131
|
-
dbmsg = Message.query.first()
|
132
|
-
timediff = datetime.datetime.utcnow() - dbmsg.timestamp
|
133
|
-
# 10 seconds between adding the message and checking
|
134
|
-
# the timestamp should be more than enough.
|
135
|
-
assert timediff < datetime.timedelta(seconds=10)
|
136
|
-
|
137
|
-
|
138
|
-
def test_add_timestamp_with_Z(datanommer_models):
|
139
|
-
example_message = generate_message()
|
140
|
-
example_message._properties.headers["sent-at"] = "2021-07-27T04:22:42Z"
|
141
|
-
|
142
|
-
add(example_message)
|
143
|
-
|
144
|
-
dbmsg = Message.query.first()
|
145
|
-
assert dbmsg.timestamp.astimezone(datetime.timezone.utc) == datetime.datetime(
|
146
|
-
2021, 7, 27, 4, 22, 42, tzinfo=datetime.timezone.utc
|
147
|
-
)
|
148
|
-
|
149
|
-
|
150
|
-
def test_add_timestamp_with_junk(datanommer_models, caplog):
|
151
|
-
example_message = generate_message()
|
152
|
-
example_message._properties.headers["sent-at"] = "2021-07-27T04:22:42JUNK"
|
153
|
-
|
154
|
-
add(example_message)
|
155
|
-
|
156
|
-
assert "Failed to parse sent-at timestamp value" in caplog.records[0].message
|
157
|
-
|
158
|
-
assert Message.query.count() == 0
|
159
|
-
|
160
|
-
|
161
|
-
def test_add_and_check_for_others(datanommer_models):
|
162
|
-
|
163
|
-
# There are no users or packages at the start
|
164
|
-
assert User.query.count() == 0
|
165
|
-
assert Package.query.count() == 0
|
166
|
-
|
167
|
-
# Then add a message
|
168
|
-
add(generate_bodhi_update_complete_message())
|
169
|
-
|
170
|
-
# There should now be two of each
|
171
|
-
assert User.query.count() == 2
|
172
|
-
assert Package.query.count() == 2
|
173
|
-
|
174
|
-
# If we add it again, there should be no duplicates
|
175
|
-
add(generate_bodhi_update_complete_message())
|
176
|
-
assert User.query.count() == 2
|
177
|
-
assert Package.query.count() == 2
|
178
|
-
|
179
|
-
# Add a new username
|
180
|
-
add(generate_bodhi_update_complete_message(text="this is @abompard in a comment"))
|
181
|
-
assert User.query.count() == 3
|
182
|
-
assert Package.query.count() == 2
|
183
|
-
|
184
|
-
|
185
|
-
def test_add_nothing(datanommer_models):
|
186
|
-
assert Message.query.count() == 0
|
187
|
-
|
188
|
-
|
189
|
-
def test_add_and_check(datanommer_models):
|
190
|
-
add(generate_message())
|
191
|
-
session.flush()
|
192
|
-
assert Message.query.count() == 1
|
193
|
-
|
194
|
-
|
195
|
-
def test_categories(datanommer_models):
|
196
|
-
add(generate_bodhi_update_complete_message())
|
197
|
-
session.flush()
|
198
|
-
obj = Message.query.first()
|
199
|
-
assert obj.category == "bodhi"
|
200
|
-
|
201
|
-
|
202
|
-
def test_categories_with_umb(datanommer_models):
|
203
|
-
add(generate_message(topic="/topic/VirtualTopic.eng.brew.task.closed"))
|
204
|
-
session.flush()
|
205
|
-
obj = Message.query.first()
|
206
|
-
assert obj.category == "brew"
|
207
|
-
|
208
|
-
|
209
|
-
def test_grep_all(datanommer_models):
|
210
|
-
example_message = generate_message()
|
211
|
-
add(example_message)
|
212
|
-
session.flush()
|
213
|
-
t, p, r = Message.grep()
|
214
|
-
assert t == 1
|
215
|
-
assert p == 1
|
216
|
-
assert len(r) == 1
|
217
|
-
assert r[0].msg == example_message.body
|
218
|
-
|
219
|
-
|
220
|
-
def test_grep_category(datanommer_models):
|
221
|
-
example_message = generate_message(topic="org.fedoraproject.prod.bodhi.newupdate")
|
222
|
-
add(example_message)
|
223
|
-
session.flush()
|
224
|
-
t, p, r = Message.grep(categories=["bodhi"])
|
225
|
-
assert t == 1
|
226
|
-
assert p == 1
|
227
|
-
assert len(r) == 1
|
228
|
-
assert r[0].msg == example_message.body
|
229
|
-
|
230
|
-
|
231
|
-
def test_grep_not_category(datanommer_models):
|
232
|
-
example_message = generate_message(topic="org.fedoraproject.prod.bodhi.newupdate")
|
233
|
-
add(example_message)
|
234
|
-
session.flush()
|
235
|
-
t, p, r = Message.grep(not_categories=["bodhi"])
|
236
|
-
assert t == 0
|
237
|
-
assert p == 0
|
238
|
-
assert len(r) == 0
|
239
|
-
|
240
|
-
|
241
|
-
def test_add_headers(datanommer_models):
|
242
|
-
example_headers = {"foo": "bar", "baz": 1, "wibble": ["zork", "zap"]}
|
243
|
-
example_message = generate_message(
|
244
|
-
topic="org.fedoraproject.prod.bodhi.newupdate", headers=example_headers
|
245
|
-
)
|
246
|
-
add(example_message)
|
247
|
-
dbmsg = Message.query.first()
|
248
|
-
assert dbmsg.headers["foo"] == "bar"
|
249
|
-
assert dbmsg.headers["baz"] == 1
|
250
|
-
assert dbmsg.headers["wibble"] == ["zork", "zap"]
|
251
|
-
|
252
|
-
|
253
|
-
def test_grep_topics(datanommer_models):
|
254
|
-
example_message = generate_message(topic="org.fedoraproject.prod.bodhi.newupdate")
|
255
|
-
add(example_message)
|
256
|
-
session.flush()
|
257
|
-
t, p, r = Message.grep(topics=["org.fedoraproject.prod.bodhi.newupdate"])
|
258
|
-
assert t == 1
|
259
|
-
assert p == 1
|
260
|
-
assert len(r) == 1
|
261
|
-
assert r[0].msg == example_message.body
|
262
|
-
|
263
|
-
|
264
|
-
def test_grep_not_topics(datanommer_models):
|
265
|
-
example_message = generate_message(topic="org.fedoraproject.prod.bodhi.newupdate")
|
266
|
-
add(example_message)
|
267
|
-
session.flush()
|
268
|
-
t, p, r = Message.grep(not_topics=["org.fedoraproject.prod.bodhi.newupdate"])
|
269
|
-
assert t == 0
|
270
|
-
assert p == 0
|
271
|
-
assert len(r) == 0
|
272
|
-
|
273
|
-
|
274
|
-
def test_grep_start_end_validation(datanommer_models):
|
275
|
-
with pytest.raises(
|
276
|
-
ValueError,
|
277
|
-
match="Either both start and end must be specified or neither must be specified",
|
278
|
-
):
|
279
|
-
Message.grep(start="2020-03-26")
|
280
|
-
with pytest.raises(
|
281
|
-
ValueError,
|
282
|
-
match="Either both start and end must be specified or neither must be specified",
|
283
|
-
):
|
284
|
-
Message.grep(end="2020-03-26")
|
285
|
-
|
286
|
-
|
287
|
-
def test_grep_start_end(datanommer_models):
|
288
|
-
example_message = generate_message()
|
289
|
-
example_message._properties.headers["sent-at"] = "2021-04-01T00:00:01"
|
290
|
-
add(example_message)
|
291
|
-
|
292
|
-
bodhi_example_message = generate_bodhi_update_complete_message()
|
293
|
-
bodhi_example_message._properties.headers["sent-at"] = "2021-06-01T00:00:01"
|
294
|
-
add(bodhi_example_message)
|
295
|
-
|
296
|
-
session.flush()
|
297
|
-
total, pages, messages = Message.grep(start="2021-04-01", end="2021-05-01")
|
298
|
-
assert total == 1
|
299
|
-
assert pages == 1
|
300
|
-
assert len(messages) == 1
|
301
|
-
assert messages[0].msg == example_message.body
|
302
|
-
|
303
|
-
total, pages, messages = Message.grep(start="2021-06-01", end="2021-07-01")
|
304
|
-
assert total == 1
|
305
|
-
assert pages == 1
|
306
|
-
assert len(messages) == 1
|
307
|
-
assert messages[0].msg == bodhi_example_message.body
|
308
|
-
|
309
|
-
|
310
|
-
def test_grep_msg_id(datanommer_models):
|
311
|
-
example_message = generate_message()
|
312
|
-
add(example_message)
|
313
|
-
|
314
|
-
bodhi_example_message = generate_bodhi_update_complete_message()
|
315
|
-
add(bodhi_example_message)
|
316
|
-
|
317
|
-
session.flush()
|
318
|
-
total, pages, messages = Message.grep(msg_id=example_message.id)
|
319
|
-
assert total == 1
|
320
|
-
assert pages == 1
|
321
|
-
assert len(messages) == 1
|
322
|
-
assert messages[0].msg == example_message.body
|
323
|
-
|
324
|
-
total, pages, messages = Message.grep(msg_id=bodhi_example_message.id)
|
325
|
-
assert total == 1
|
326
|
-
assert pages == 1
|
327
|
-
assert len(messages) == 1
|
328
|
-
assert messages[0].msg == bodhi_example_message.body
|
329
|
-
|
330
|
-
total, pages, messages = Message.grep(msg_id="NOTAMESSAGEID")
|
331
|
-
assert total == 0
|
332
|
-
assert pages == 0
|
333
|
-
assert len(messages) == 0
|
334
|
-
|
335
|
-
|
336
|
-
def test_grep_users(datanommer_models):
|
337
|
-
example_message = generate_message()
|
338
|
-
add(example_message)
|
339
|
-
|
340
|
-
bodhi_example_message = generate_bodhi_update_complete_message()
|
341
|
-
add(bodhi_example_message)
|
342
|
-
|
343
|
-
session.flush()
|
344
|
-
|
345
|
-
total, pages, messages = Message.grep(users=["dudemcpants"])
|
346
|
-
|
347
|
-
assert total == 1
|
348
|
-
assert pages == 1
|
349
|
-
assert len(messages) == 1
|
350
|
-
|
351
|
-
assert messages[0].msg == bodhi_example_message.body
|
352
|
-
|
353
|
-
|
354
|
-
def test_grep_not_users(datanommer_models):
|
355
|
-
example_message = generate_message()
|
356
|
-
add(example_message)
|
357
|
-
|
358
|
-
bodhi_example_message = generate_bodhi_update_complete_message()
|
359
|
-
add(bodhi_example_message)
|
360
|
-
|
361
|
-
session.flush()
|
362
|
-
|
363
|
-
total, pages, messages = Message.grep(not_users=["dudemcpants"])
|
364
|
-
|
365
|
-
assert total == 1
|
366
|
-
assert pages == 1
|
367
|
-
assert len(messages) == 1
|
368
|
-
|
369
|
-
assert messages[0].msg == example_message.body
|
370
|
-
|
371
|
-
|
372
|
-
def test_grep_packages(datanommer_models):
|
373
|
-
example_message = generate_message()
|
374
|
-
add(example_message)
|
375
|
-
|
376
|
-
bodhi_example_message = generate_bodhi_update_complete_message()
|
377
|
-
add(bodhi_example_message)
|
378
|
-
|
379
|
-
session.flush()
|
380
|
-
|
381
|
-
total, pages, messages = Message.grep(packages=["kernel"])
|
382
|
-
|
383
|
-
assert total == 1
|
384
|
-
assert pages == 1
|
385
|
-
assert len(messages) == 1
|
386
|
-
|
387
|
-
assert messages[0].msg == bodhi_example_message.body
|
388
|
-
|
389
|
-
|
390
|
-
def test_grep_not_packages(datanommer_models):
|
391
|
-
example_message = generate_message()
|
392
|
-
add(example_message)
|
393
|
-
|
394
|
-
bodhi_example_message = generate_bodhi_update_complete_message()
|
395
|
-
add(bodhi_example_message)
|
396
|
-
|
397
|
-
session.flush()
|
398
|
-
|
399
|
-
total, pages, messages = Message.grep(not_packages=["kernel"])
|
400
|
-
|
401
|
-
assert total == 1
|
402
|
-
assert pages == 1
|
403
|
-
assert len(messages) == 1
|
404
|
-
|
405
|
-
assert messages[0].msg == example_message.body
|
406
|
-
|
407
|
-
|
408
|
-
def test_grep_contains(datanommer_models):
|
409
|
-
example_message = generate_message(topic="org.fedoraproject.prod.bodhi.newupdate")
|
410
|
-
add(example_message)
|
411
|
-
session.flush()
|
412
|
-
t, p, r = Message.grep(contains=["doing"])
|
413
|
-
assert t == 1
|
414
|
-
assert p == 1
|
415
|
-
assert len(r) == 1
|
416
|
-
assert r[0].msg == example_message.body
|
417
|
-
|
418
|
-
|
419
|
-
def test_grep_rows_per_page_none(datanommer_models):
|
420
|
-
for x in range(0, 200):
|
421
|
-
example_message = generate_message()
|
422
|
-
example_message.id = f"{x}"
|
423
|
-
add(example_message)
|
424
|
-
|
425
|
-
session.flush()
|
426
|
-
|
427
|
-
total, pages, messages = Message.grep()
|
428
|
-
assert total == 200
|
429
|
-
assert pages == 2
|
430
|
-
assert len(messages) == 100
|
431
|
-
|
432
|
-
total, pages, messages = Message.grep(rows_per_page=None)
|
433
|
-
assert total == 200
|
434
|
-
assert pages == 1
|
435
|
-
assert len(messages) == 200
|
436
|
-
|
437
|
-
|
438
|
-
def test_grep_rows_per_page_zero(datanommer_models):
|
439
|
-
for x in range(0, 200):
|
440
|
-
example_message = generate_message()
|
441
|
-
example_message.id = f"{x}"
|
442
|
-
add(example_message)
|
443
|
-
session.flush()
|
444
|
-
|
445
|
-
try:
|
446
|
-
total, pages, messages = Message.grep(rows_per_page=0)
|
447
|
-
except ZeroDivisionError as e:
|
448
|
-
assert False, e
|
449
|
-
assert total == 200
|
450
|
-
assert pages == 1
|
451
|
-
assert len(messages) == 200
|
452
|
-
|
453
|
-
|
454
|
-
def test_grep_defer(datanommer_models):
|
455
|
-
example_message = generate_message()
|
456
|
-
add(example_message)
|
457
|
-
|
458
|
-
session.flush()
|
459
|
-
|
460
|
-
total, pages, query = Message.grep(defer=True)
|
461
|
-
assert isinstance(query, Query)
|
462
|
-
|
463
|
-
assert query.all() == Message.grep()[2]
|
464
|
-
|
465
|
-
|
466
|
-
def test_add_duplicate(datanommer_models, caplog):
|
467
|
-
example_message = generate_message()
|
468
|
-
add(example_message)
|
469
|
-
add(example_message)
|
470
|
-
# if no exception was thrown, then we successfully ignored the
|
471
|
-
# duplicate message
|
472
|
-
assert Message.query.count() == 1
|
473
|
-
assert (
|
474
|
-
"Skipping message from org.fedoraproject.test.a.nice.message"
|
475
|
-
in caplog.records[0].message
|
476
|
-
)
|
477
|
-
|
478
|
-
|
479
|
-
def test_add_integrity_error(datanommer_models, mocker, caplog):
|
480
|
-
mock_session_add = mocker.patch("datanommer.models.session.add")
|
481
|
-
mock_session_add.side_effect = IntegrityError("asdf", "asd", "asdas")
|
482
|
-
example_message = generate_message()
|
483
|
-
add(example_message)
|
484
|
-
assert "Unknown Integrity Error: message" in caplog.records[0].message
|
485
|
-
assert Message.query.count() == 0
|
486
|
-
|
487
|
-
|
488
|
-
def test_add_duplicate_package(datanommer_models):
|
489
|
-
# Define a special message schema and register it
|
490
|
-
class MessageWithPackages(fedora_message.Message):
|
491
|
-
@property
|
492
|
-
def packages(self):
|
493
|
-
return ["pkg", "pkg"]
|
494
|
-
|
495
|
-
fedora_message._schema_name_to_class["MessageWithPackages"] = MessageWithPackages
|
496
|
-
fedora_message._class_to_schema_name[MessageWithPackages] = "MessageWithPackages"
|
497
|
-
example_message = MessageWithPackages(
|
498
|
-
topic="org.fedoraproject.test.a.nice.message",
|
499
|
-
body={"encouragement": "You're doing great!"},
|
500
|
-
headers=None,
|
501
|
-
)
|
502
|
-
try:
|
503
|
-
add(example_message)
|
504
|
-
except IntegrityError as e:
|
505
|
-
assert False, e
|
506
|
-
assert Message.query.count() == 1
|
507
|
-
dbmsg = Message.query.first()
|
508
|
-
assert len(dbmsg.packages) == 1
|
509
|
-
assert dbmsg.packages[0].name == "pkg"
|
510
|
-
|
511
|
-
|
512
|
-
def test_add_message_with_error_on_packages(datanommer_models, caplog):
|
513
|
-
# Define a special message schema and register it
|
514
|
-
class CustomMessage(fedora_message.Message):
|
515
|
-
@property
|
516
|
-
def packages(self):
|
517
|
-
raise KeyError
|
518
|
-
|
519
|
-
def _filter_headers(self):
|
520
|
-
return {}
|
521
|
-
|
522
|
-
fedora_message._schema_name_to_class["CustomMessage"] = CustomMessage
|
523
|
-
fedora_message._class_to_schema_name[CustomMessage] = "CustomMessage"
|
524
|
-
example_message = CustomMessage(
|
525
|
-
topic="org.fedoraproject.test.a.nice.message",
|
526
|
-
body={"encouragement": "You're doing great!"},
|
527
|
-
headers=None,
|
528
|
-
)
|
529
|
-
try:
|
530
|
-
add(example_message)
|
531
|
-
except KeyError as e:
|
532
|
-
assert False, e
|
533
|
-
assert Message.query.count() == 1
|
534
|
-
assert caplog.records[0].message == (
|
535
|
-
f"Could not get the list of packages from a message on "
|
536
|
-
f"org.fedoraproject.test.a.nice.message with id {example_message.id}"
|
537
|
-
)
|
538
|
-
|
539
|
-
|
540
|
-
def test_as_fedora_message_dict(datanommer_models):
|
541
|
-
example_message = generate_message()
|
542
|
-
add(example_message)
|
543
|
-
|
544
|
-
dbmsg = Message.query.first()
|
545
|
-
|
546
|
-
message_json = json.dumps(dbmsg.as_fedora_message_dict())
|
547
|
-
|
548
|
-
# this should be the same as if we use the fedora_messaging dump function
|
549
|
-
assert json.loads(fedora_message.dumps(example_message)) == json.loads(message_json)
|
550
|
-
|
551
|
-
|
552
|
-
def test_as_fedora_message_dict_old_headers(datanommer_models):
|
553
|
-
# Messages received with fedmsg don't have the sent-at header
|
554
|
-
example_message = generate_message()
|
555
|
-
add(example_message)
|
556
|
-
|
557
|
-
dbmsg = Message.query.first()
|
558
|
-
del dbmsg.headers["sent-at"]
|
559
|
-
|
560
|
-
message_dict = dbmsg.as_fedora_message_dict()
|
561
|
-
print(message_dict)
|
562
|
-
print(json.loads(fedora_message.dumps(example_message)))
|
563
|
-
|
564
|
-
# this should be the same as if we use the fedora_messaging dump function
|
565
|
-
assert json.loads(fedora_message.dumps(example_message)) == message_dict
|
566
|
-
|
567
|
-
|
568
|
-
def test_as_fedora_message_dict_no_headers(datanommer_models):
|
569
|
-
# Messages can have no headers
|
570
|
-
example_message = generate_message()
|
571
|
-
add(example_message)
|
572
|
-
|
573
|
-
dbmsg = Message.query.first()
|
574
|
-
assert len(dbmsg.headers.keys()) == 3
|
575
|
-
|
576
|
-
# Clear the headers
|
577
|
-
dbmsg.headers = None
|
578
|
-
|
579
|
-
try:
|
580
|
-
message_dict = dbmsg.as_fedora_message_dict()
|
581
|
-
except TypeError as e:
|
582
|
-
assert False, e
|
583
|
-
|
584
|
-
assert list(message_dict["headers"].keys()) == ["sent-at"]
|
585
|
-
|
586
|
-
|
587
|
-
def test_as_dict(datanommer_models):
|
588
|
-
add(generate_message())
|
589
|
-
dbmsg = Message.query.first()
|
590
|
-
message_dict = dbmsg.as_dict()
|
591
|
-
|
592
|
-
# we should have 14 keys in this dict
|
593
|
-
assert len(message_dict) == 14
|
594
|
-
assert message_dict["msg"] == {"encouragement": "You're doing great!"}
|
595
|
-
assert message_dict["topic"] == "org.fedoraproject.test.a.nice.message"
|
596
|
-
|
597
|
-
|
598
|
-
def test_as_dict_with_users_and_packages(datanommer_models):
|
599
|
-
add(generate_bodhi_update_complete_message())
|
600
|
-
dbmsg = Message.query.first()
|
601
|
-
message_dict = dbmsg.as_dict()
|
602
|
-
|
603
|
-
assert message_dict["users"] == ["dudemcpants", "ryanlerch"]
|
604
|
-
assert message_dict["packages"] == ["abrt-addon-python3", "kernel"]
|
605
|
-
|
606
|
-
|
607
|
-
def test___json__deprecated(datanommer_models, caplog, mocker):
|
608
|
-
mock_as_dict = mocker.patch("datanommer.models.Message.as_dict")
|
609
|
-
|
610
|
-
add(generate_message())
|
611
|
-
|
612
|
-
with pytest.warns(DeprecationWarning):
|
613
|
-
Message.query.first().__json__()
|
614
|
-
|
615
|
-
mock_as_dict.assert_called_once()
|
616
|
-
|
617
|
-
|
618
|
-
def test_singleton_create(datanommer_models):
|
619
|
-
Package.get_or_create("foobar")
|
620
|
-
assert [p.name for p in Package.query.all()] == ["foobar"]
|
621
|
-
|
622
|
-
|
623
|
-
def test_singleton_get_existing(datanommer_models):
|
624
|
-
p1 = Package.get_or_create("foobar")
|
625
|
-
# Clear the in-memory cache
|
626
|
-
Package._cache.clear()
|
627
|
-
p2 = Package.get_or_create("foobar")
|
628
|
-
assert p1.id == p2.id
|
tox.ini
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
[tox]
|
2
|
-
envlist = py{37,38,39},licenses
|
3
|
-
skipsdist = True
|
4
|
-
isolated_build = true
|
5
|
-
requires =
|
6
|
-
poetry
|
7
|
-
tox-poetry
|
8
|
-
|
9
|
-
[testenv]
|
10
|
-
commands = pytest -c ../pyproject.toml {posargs}
|
11
|
-
|
12
|
-
[testenv:licenses]
|
13
|
-
deps = poetry
|
14
|
-
commands =
|
15
|
-
{toxinidir}/../tools/run-liccheck.sh
|
File without changes
|