datanommer.models 1.0.4__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.
@@ -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,15 +165,10 @@ 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
 
169
- class JSONEncodedDict(TypeDecorator):
171
+ class _JSONEncodedDict(TypeDecorator):
170
172
  """Represents an immutable structure as a json-encoded string."""
171
173
 
172
174
  impl = UnicodeText
@@ -224,8 +226,8 @@ 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)
228
- msg = Column(JSONEncodedDict, nullable=False)
229
+ source_version = Column(Unicode, default=lambda context: __version__)
230
+ msg = Column(_JSONEncodedDict, nullable=False)
229
231
  headers = Column(postgresql.JSONB(none_as_null=True))
230
232
  users = relationship(
231
233
  "User",
@@ -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,17 +354,15 @@ 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
 
359
361
  @classmethod
360
- def grep(
362
+ def make_query(
361
363
  cls,
362
364
  start=None,
363
365
  end=None,
364
- page=1,
365
- rows_per_page=100,
366
- order="asc",
367
366
  msg_id=None,
368
367
  users=None,
369
368
  not_users=None,
@@ -374,7 +373,6 @@ class Message(DeclarativeBase):
374
373
  topics=None,
375
374
  not_topics=None,
376
375
  contains=None,
377
- defer=False,
378
376
  ):
379
377
  """Flexible query interface for messages.
380
378
 
@@ -402,11 +400,6 @@ class Message(DeclarativeBase):
402
400
 
403
401
  (user == 'ralph') AND
404
402
  NOT (category == 'bodhi' OR category == 'wiki')
405
-
406
- ----
407
-
408
- If the `defer` argument evaluates to True, the query won't actually
409
- be executed, but a SQLAlchemy query object returned instead.
410
403
  """
411
404
 
412
405
  users = users or []
@@ -419,87 +412,137 @@ class Message(DeclarativeBase):
419
412
  not_topics = not_topics or []
420
413
  contains = contains or []
421
414
 
422
- query = Message.query
415
+ Message = cls
416
+ query = select(Message)
423
417
 
424
418
  # A little argument validation. We could provide some defaults in
425
419
  # these mixed cases.. but instead we'll just leave it up to our caller.
426
420
  if (start is not None and end is None) or (end is not None and start is None):
427
421
  raise ValueError(
428
- "Either both start and end must be specified "
429
- "or neither must be specified"
422
+ "Either both start and end must be specified or neither must be specified"
430
423
  )
431
424
 
432
425
  if start and end:
433
- query = query.filter(between(Message.timestamp, start, end))
426
+ query = query.where(between(Message.timestamp, start, end))
434
427
 
435
428
  if msg_id:
436
- query = query.filter(Message.msg_id == msg_id)
429
+ query = query.where(Message.msg_id == msg_id)
437
430
 
438
431
  # Add the four positive filters as necessary
439
432
  if users:
440
- query = query.filter(
441
- or_(*(Message.users.any(User.name == u) for u in users))
442
- )
433
+ query = query.where(or_(*(Message.users.any(User.name == u) for u in users)))
443
434
 
444
435
  if packages:
445
- query = query.filter(
446
- or_(*(Message.packages.any(Package.name == p) for p in packages))
447
- )
436
+ query = query.where(or_(*(Message.packages.any(Package.name == p) for p in packages)))
448
437
 
449
438
  if categories:
450
- query = query.filter(
451
- or_(*(Message.category == category for category in categories))
452
- )
439
+ query = query.where(or_(*(Message.category == category for category in categories)))
453
440
 
454
441
  if topics:
455
- query = query.filter(or_(*(Message.topic == topic for topic in topics)))
442
+ query = query.where(or_(*(Message.topic == topic for topic in topics)))
456
443
 
457
444
  if contains:
458
- query = query.filter(
459
- or_(*(Message.msg.like(f"%{contain}%") for contain in contains))
460
- )
445
+ query = query.where(or_(*(Message.msg.like(f"%{contain}%") for contain in contains)))
461
446
 
462
447
  # And then the four negative filters as necessary
463
448
  if not_users:
464
- query = query.filter(
465
- not_(or_(*(Message.users.any(User.name == u) for u in not_users)))
466
- )
449
+ query = query.where(not_(or_(*(Message.users.any(User.name == u) for u in not_users))))
467
450
 
468
451
  if not_packs:
469
- query = query.filter(
452
+ query = query.where(
470
453
  not_(or_(*(Message.packages.any(Package.name == p) for p in not_packs)))
471
454
  )
472
455
 
473
456
  if not_cats:
474
- query = query.filter(
475
- not_(or_(*(Message.category == category for category in not_cats)))
476
- )
457
+ query = query.where(not_(or_(*(Message.category == category for category in not_cats))))
477
458
 
478
459
  if not_topics:
479
- query = query.filter(
480
- not_(or_(*(Message.topic == topic for topic in not_topics)))
481
- )
460
+ query = query.where(not_(or_(*(Message.topic == topic for topic in not_topics))))
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
482
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)
483
512
  # Finally, tag on our pagination arguments
484
- total = query.count()
513
+ Message = cls
514
+
515
+ query_total = query.with_only_columns(func.count(Message.id))
516
+ total = None
485
517
  query = query.order_by(getattr(Message.timestamp, order)())
486
518
 
487
519
  if not rows_per_page:
488
520
  pages = 1
489
521
  else:
522
+ total = session.scalar(query_total)
490
523
  pages = int(math.ceil(total / float(rows_per_page)))
491
524
  query = query.offset(rows_per_page * (page - 1)).limit(rows_per_page)
492
525
 
493
526
  if defer:
494
- return total, page, query
527
+ if total is None:
528
+ total = session.scalar(query_total)
529
+ return total, pages, query
495
530
  else:
496
531
  # Execute!
497
- messages = query.all()
532
+ messages = session.scalars(query).all()
533
+ if pages == 1:
534
+ total = len(messages)
498
535
  return total, pages, messages
499
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()
500
543
 
501
- class NamedSingleton:
502
544
 
545
+ class NamedSingleton:
503
546
  id = Column(Integer, primary_key=True, autoincrement=True)
504
547
  name = Column(UnicodeText, index=True, unique=True)
505
548
 
@@ -513,8 +556,8 @@ class NamedSingleton:
513
556
  if name in cls._cache:
514
557
  # If we cache the instance, SQLAlchemy will run this query anyway because the instance
515
558
  # 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()
559
+ return session.get(cls, cls._cache[name])
560
+ obj = session.execute(select(cls).where(cls.name == name)).scalar_one_or_none()
518
561
  if obj is None:
519
562
  obj = cls(name=name)
520
563
  session.add(obj)
@@ -546,16 +589,3 @@ def _setup_hypertable(table_class):
546
589
 
547
590
 
548
591
  _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
@@ -1,46 +1,56 @@
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
- @pytest.fixture(scope="session")
11
- def postgresql_proc_with_timescaledb(postgresql_proc):
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(pgsql):
15
+ @pytest.fixture(scope="session")
16
+ def datanommer_db_url(postgresql_proc):
29
17
  return (
30
- f"postgresql+psycopg2://{pgsql.info.user}:@"
31
- f"{pgsql.info.host}:{pgsql.info.port}"
32
- f"/{pgsql.info.dbname}"
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
 
24
+ @pytest.fixture(scope="session")
25
+ def datanommer_db_engine(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)
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
+ 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
49
+
50
+
36
51
  @pytest.fixture()
37
- def datanommer_models(datanommer_db_url):
38
- dm.session = scoped_session(dm.maker)
39
- dm.init(datanommer_db_url, create=True)
52
+ def datanommer_models(datanommer_db):
40
53
  dm.User.clear_cache()
41
54
  dm.Package.clear_cache()
42
55
  yield dm.session
43
56
  dm.session.rollback()
44
- # engine = dm.session.get_bind()
45
- dm.session.close()
46
- # dm.DeclarativeBase.metadata.drop_all(engine)
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.1
2
+ Name: datanommer.models
3
+ Version: 1.3.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: maubot-fedora-messages ; extra == "schemas"
36
+ Requires-Dist: mdapi-messages ; extra == "schemas"
37
+ Requires-Dist: mediawiki-messages ; extra == "schemas"
38
+ Requires-Dist: meetbot-messages ; extra == "schemas"
39
+ Requires-Dist: noggin-messages ; extra == "schemas"
40
+ Requires-Dist: nuancier-messages ; extra == "schemas"
41
+ Requires-Dist: pagure-messages ; extra == "schemas"
42
+ Requires-Dist: psycopg2 (>=2.9.1,<3.0.0)
43
+ Requires-Dist: tahrir-messages ; extra == "schemas"
44
+ Project-URL: Repository, https://github.com/fedora-infra/datanommer
45
+ Description-Content-Type: text/x-rst
46
+
47
+ datanommer.models
48
+ =================
49
+
50
+ This package contains the SQLAlchemy data model for datanommer.
51
+
52
+ Datanommer is a storage consumer for the Fedora Infrastructure Message Bus
53
+ (fedmsg). It is comprised of a `fedmsg <http://fedmsg.com>`_ consumer that
54
+ stuffs every message into a sqlalchemy database.
55
+
@@ -0,0 +1,10 @@
1
+ datanommer/models/__init__.py,sha256=j3ZIgc2XnXoHaTuC2edq9RoVEqx0g8dc9-3dKi3ctBU,18628
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=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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry 1.0.4
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1 +0,0 @@
1
- CREATE EXTENSION IF NOT EXISTS timescaledb;
@@ -1,43 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: datanommer.models
3
- Version: 1.0.4
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.3.24,<2.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: 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)
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: mdapi-messages; extra == "schemas"
28
- Requires-Dist: noggin-messages; extra == "schemas"
29
- Requires-Dist: nuancier-messages; extra == "schemas"
30
- Requires-Dist: pagure-messages; extra == "schemas"
31
- Requires-Dist: psycopg2 (>=2.9.1,<3.0.0)
32
- Project-URL: Repository, https://github.com/fedora-infra/datanommer
33
- Description-Content-Type: text/x-rst
34
-
35
- datanommer.models
36
- =================
37
-
38
- This package contains the SQLAlchemy data model for datanommer.
39
-
40
- Datanommer is a storage consumer for the Fedora Infrastructure Message Bus
41
- (fedmsg). It is comprised of a `fedmsg <http://fedmsg.com>`_ consumer that
42
- stuffs every message into a sqlalchemy database.
43
-
@@ -1,11 +0,0 @@
1
- datanommer/models/__init__.py,sha256=4smdWjO0DSxKQeOn0UAfH93eUKQPtTKklqnBdWbGP0Q,17280
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=4aNMycVGrYA6Ljo74ePweDrzSr9oaA7K_W2gtn0YZ74,1271
7
- datanommer/models/testing/startup.sql,sha256=tdjQMZcM7bVDoLnfSg1-fpKKiAPihiVPy-syF0H8mvo,44
8
- datanommer.models-1.0.4.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
9
- datanommer.models-1.0.4.dist-info/WHEEL,sha256=N0LZrBtofpkS5mJXgVHTCEy52Sam4D6PHQWC8HnMeTs,83
10
- datanommer.models-1.0.4.dist-info/METADATA,sha256=P6Jrz1-dqJlXlhbsANOKWcXNiVpJI4-W0jOiLB5vW0g,1828
11
- datanommer.models-1.0.4.dist-info/RECORD,,