f3-data-models 0.1.7__tar.gz → 0.1.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: f3-data-models
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: The data schema and models for F3 Nation applications.
5
5
  Home-page: https://github.com/F3-Nation/f3-data-models
6
6
  License: MIT
@@ -56,11 +56,26 @@ If you would like to make a change, you will need to:
56
56
  source .env && alembic revision --autogenerate -m "Your Message Here"
57
57
  ```
58
58
  3. Make any edits to the migration script in `alembic/versions`
59
- 4. The github pages documentation will be updated when you push to `main`, but if you would like to preview locally, run:
59
+ 4. Run the upgrade on your local db:
60
+ ```sh
61
+ source .env && alembic upgrade head
62
+ ```
63
+ 5. Bump the version on `pyproject.toml`:
64
+ ```sh
65
+ poetry version patch[minor][major]
66
+ ```
67
+ 6. Tag your final commit and make sure to push those tags to trigger the pypi package build:
68
+ ```sh
69
+ git tag <new_version> -a -m "Your message here"
70
+ git push origin --tags
71
+ ```
72
+ > [!NOTE] The github pages documentation will be updated when you push to `main`, but if you would like to preview locally, run:
73
+
60
74
  ```sh
61
75
  poetry run sphinx-build -b html docs docs/_build/html
62
76
  cd docs
63
77
  poetry run python -m http.server --directory _build/html
64
78
  ```
79
+
65
80
  > [!TIP]
66
81
  > Adding new fields as nullable (ie `Optional[]`) has the best chance of reducing breaking changes to the apps.
@@ -27,11 +27,26 @@ If you would like to make a change, you will need to:
27
27
  source .env && alembic revision --autogenerate -m "Your Message Here"
28
28
  ```
29
29
  3. Make any edits to the migration script in `alembic/versions`
30
- 4. The github pages documentation will be updated when you push to `main`, but if you would like to preview locally, run:
30
+ 4. Run the upgrade on your local db:
31
+ ```sh
32
+ source .env && alembic upgrade head
33
+ ```
34
+ 5. Bump the version on `pyproject.toml`:
35
+ ```sh
36
+ poetry version patch[minor][major]
37
+ ```
38
+ 6. Tag your final commit and make sure to push those tags to trigger the pypi package build:
39
+ ```sh
40
+ git tag <new_version> -a -m "Your message here"
41
+ git push origin --tags
42
+ ```
43
+ > [!NOTE] The github pages documentation will be updated when you push to `main`, but if you would like to preview locally, run:
44
+
31
45
  ```sh
32
46
  poetry run sphinx-build -b html docs docs/_build/html
33
47
  cd docs
34
48
  poetry run python -m http.server --directory _build/html
35
49
  ```
50
+
36
51
  > [!TIP]
37
52
  > Adding new fields as nullable (ie `Optional[]`) has the best chance of reducing breaking changes to the apps.
@@ -1,14 +1,16 @@
1
1
  from datetime import datetime, date, time
2
- from typing import Any, Dict, Optional
2
+ from typing import Any, Dict, List, Optional
3
3
  from sqlalchemy import (
4
4
  JSON,
5
5
  TEXT,
6
6
  TIME,
7
7
  VARCHAR,
8
8
  Boolean,
9
+ Column,
9
10
  DateTime,
10
11
  ForeignKey,
11
12
  Integer,
13
+ Table,
12
14
  func,
13
15
  UniqueConstraint,
14
16
  )
@@ -17,6 +19,7 @@ from sqlalchemy.orm import (
17
19
  DeclarativeBase,
18
20
  mapped_column,
19
21
  Mapped,
22
+ relationship,
20
23
  )
21
24
 
22
25
  # Custom Annotations
@@ -256,6 +259,30 @@ class Role_x_User_x_Org(Base):
256
259
  org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
257
260
 
258
261
 
262
+ event_type_x_org_table = Table(
263
+ "event_types_x_org",
264
+ Base.metadata,
265
+ Column("event_type_id", Integer, ForeignKey("event_types.id"), primary_key=True),
266
+ Column("org_id", Integer, ForeignKey("orgs.id"), primary_key=True),
267
+ Column("is_default", Boolean, default=False, nullable=False),
268
+ )
269
+
270
+ event_tag_x_org_table = Table(
271
+ "event_tags_x_org",
272
+ Base.metadata,
273
+ Column("event_tag_id", Integer, ForeignKey("event_tags.id"), primary_key=True),
274
+ Column("org_id", Integer, ForeignKey("orgs.id"), primary_key=True),
275
+ Column("color_override", VARCHAR),
276
+ )
277
+
278
+ achievement_x_org_table = Table(
279
+ "achievements_x_org",
280
+ Base.metadata,
281
+ Column("achievement_id", Integer, ForeignKey("achievements.id"), primary_key=True),
282
+ Column("org_id", Integer, ForeignKey("orgs.id"), primary_key=True),
283
+ )
284
+
285
+
259
286
  class Org(Base):
260
287
  """
261
288
  Model representing an organization. The same model is used for all levels of organization (AOs, Regions, etc.).
@@ -282,7 +309,7 @@ class Org(Base):
282
309
 
283
310
  __tablename__ = "orgs"
284
311
 
285
- id: Mapped[intpk]
312
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
286
313
  parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey("orgs.id"))
287
314
  org_type_id: Mapped[int] = mapped_column(ForeignKey("org_types.id"))
288
315
  default_location_id: Mapped[Optional[int]]
@@ -300,6 +327,19 @@ class Org(Base):
300
327
  created: Mapped[dt_create]
301
328
  updated: Mapped[dt_update]
302
329
 
330
+ event_types: Mapped[Optional[List["EventType"]]] = relationship(
331
+ "EventType", secondary=event_type_x_org_table, cascade="expunge"
332
+ )
333
+ event_tags: Mapped[Optional[List["EventTag"]]] = relationship(
334
+ "EventTag", secondary=event_tag_x_org_table, cascade="expunge"
335
+ )
336
+ achievements: Mapped[Optional[List["Achievement"]]] = relationship(
337
+ "Achievement", secondary=achievement_x_org_table, cascade="expunge"
338
+ )
339
+ parent_org: Mapped[Optional["Org"]] = relationship(
340
+ "Org", remote_side=[id], cascade="expunge"
341
+ )
342
+
303
343
 
304
344
  class EventType(Base):
305
345
  """
@@ -326,40 +366,40 @@ class EventType(Base):
326
366
  updated: Mapped[dt_update]
327
367
 
328
368
 
329
- class EventType_x_Event(Base):
330
- """
331
- Model representing the association between events and event types. The intention is that a single event can be associated with multiple event types.
369
+ # class EventType_x_Event(Base):
370
+ # """
371
+ # Model representing the association between events and event types. The intention is that a single event can be associated with multiple event types.
332
372
 
333
- Attributes:
334
- event_id (int): The ID of the associated event.
335
- event_type_id (int): The ID of the associated event type.
336
- """
373
+ # Attributes:
374
+ # event_id (int): The ID of the associated event.
375
+ # event_type_id (int): The ID of the associated event type.
376
+ # """
337
377
 
338
- __tablename__ = "events_x_event_types"
378
+ # __tablename__ = "events_x_event_types"
339
379
 
340
- event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), primary_key=True)
341
- event_type_id: Mapped[int] = mapped_column(
342
- ForeignKey("event_types.id"), primary_key=True
343
- )
380
+ # event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), primary_key=True)
381
+ # event_type_id: Mapped[int] = mapped_column(
382
+ # ForeignKey("event_types.id"), primary_key=True
383
+ # )
344
384
 
345
385
 
346
- class EventType_x_Org(Base):
347
- """
348
- Model representing the association between event types and organizations. This controls which event types are available for selection at the region level, as well as default types for each AO.
386
+ # class EventType_x_Org(Base):
387
+ # """
388
+ # Model representing the association between event types and organizations. This controls which event types are available for selection at the region level, as well as default types for each AO.
349
389
 
350
- Attributes:
351
- event_type_id (int): The ID of the associated event type.
352
- org_id (int): The ID of the associated organization.
353
- is_default (bool): Whether this is the default event type for the organization. Default is False.
354
- """
390
+ # Attributes:
391
+ # event_type_id (int): The ID of the associated event type.
392
+ # org_id (int): The ID of the associated organization.
393
+ # is_default (bool): Whether this is the default event type for the organization. Default is False.
394
+ # """
355
395
 
356
- __tablename__ = "event_types_x_org"
396
+ # __tablename__ = "event_types_x_org"
357
397
 
358
- event_type_id: Mapped[int] = mapped_column(
359
- ForeignKey("event_types.id"), primary_key=True
360
- )
361
- org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
362
- is_default: Mapped[bool] = mapped_column(Boolean, default=False)
398
+ # event_type_id: Mapped[int] = mapped_column(
399
+ # ForeignKey("event_types.id"), primary_key=True
400
+ # )
401
+ # org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
402
+ # is_default: Mapped[bool] = mapped_column(Boolean, default=False)
363
403
 
364
404
 
365
405
  class EventTag(Base):
@@ -385,40 +425,40 @@ class EventTag(Base):
385
425
  updated: Mapped[dt_update]
386
426
 
387
427
 
388
- class EventTag_x_Event(Base):
389
- """
390
- Model representing the association between event tags and events. The intention is that a single event can be associated with multiple event tags.
428
+ # class EventTag_x_Event(Base):
429
+ # """
430
+ # Model representing the association between event tags and events. The intention is that a single event can be associated with multiple event tags.
391
431
 
392
- Attributes:
393
- event_id (int): The ID of the associated event.
394
- event_tag_id (int): The ID of the associated event tag.
395
- """
432
+ # Attributes:
433
+ # event_id (int): The ID of the associated event.
434
+ # event_tag_id (int): The ID of the associated event tag.
435
+ # """
396
436
 
397
- __tablename__ = "event_tags_x_events"
437
+ # __tablename__ = "event_tags_x_events"
398
438
 
399
- event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), primary_key=True)
400
- event_tag_id: Mapped[int] = mapped_column(
401
- ForeignKey("event_tags.id"), primary_key=True
402
- )
439
+ # event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), primary_key=True)
440
+ # event_tag_id: Mapped[int] = mapped_column(
441
+ # ForeignKey("event_tags.id"), primary_key=True
442
+ # )
403
443
 
404
444
 
405
- class EventTag_x_Org(Base):
406
- """
407
- Model representing the association between event tags and organizations. Controls which event tags are available for selection at the region level.
445
+ # class EventTag_x_Org(Base):
446
+ # """
447
+ # Model representing the association between event tags and organizations. Controls which event tags are available for selection at the region level.
408
448
 
409
- Attributes:
410
- event_tag_id (int): The ID of the associated event tag.
411
- org_id (int): The ID of the associated organization.
412
- color_override (Optional[str]): The calendar color override for the event tag (if the region wants to use something other than the default).
413
- """
449
+ # Attributes:
450
+ # event_tag_id (int): The ID of the associated event tag.
451
+ # org_id (int): The ID of the associated organization.
452
+ # color_override (Optional[str]): The calendar color override for the event tag (if the region wants to use something other than the default).
453
+ # """
414
454
 
415
- __tablename__ = "event_tags_x_org"
455
+ # __tablename__ = "event_tags_x_org"
416
456
 
417
- event_tag_id: Mapped[int] = mapped_column(
418
- ForeignKey("event_tags.id"), primary_key=True
419
- )
420
- org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
421
- color_override: Mapped[Optional[str]]
457
+ # event_tag_id: Mapped[int] = mapped_column(
458
+ # ForeignKey("event_tags.id"), primary_key=True
459
+ # )
460
+ # org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
461
+ # color_override: Mapped[Optional[str]]
422
462
 
423
463
 
424
464
  class Org_x_SlackSpace(Base):
@@ -479,6 +519,21 @@ class Location(Base):
479
519
  updated: Mapped[dt_update]
480
520
 
481
521
 
522
+ event_x_event_type_table = Table(
523
+ "events_x_event_types",
524
+ Base.metadata,
525
+ Column("event_id", Integer, ForeignKey("events.id"), primary_key=True),
526
+ Column("event_type_id", Integer, ForeignKey("event_types.id"), primary_key=True),
527
+ )
528
+
529
+ event_x_event_tag_table = Table(
530
+ "event_tags_x_events",
531
+ Base.metadata,
532
+ Column("event_id", Integer, ForeignKey("events.id"), primary_key=True),
533
+ Column("event_tag_id", Integer, ForeignKey("event_tags.id"), primary_key=True),
534
+ )
535
+
536
+
482
537
  class Event(Base):
483
538
  """
484
539
  Model representing an event or series; the same model is used for both with a self-referential relationship for series.
@@ -545,6 +600,15 @@ class Event(Base):
545
600
  created: Mapped[dt_create]
546
601
  updated: Mapped[dt_update]
547
602
 
603
+ org: Mapped[Org] = relationship(innerjoin=True, cascade="expunge")
604
+ location: Mapped[Location] = relationship(innerjoin=True, cascade="expunge")
605
+ event_types: Mapped[List[EventType]] = relationship(
606
+ secondary=event_x_event_type_table, innerjoin=True, cascade="expunge"
607
+ )
608
+ event_tags: Mapped[Optional[List[EventTag]]] = relationship(
609
+ secondary=event_x_event_tag_table, cascade="expunge"
610
+ )
611
+
548
612
 
549
613
  class AttendanceType(Base):
550
614
  """
@@ -738,21 +802,21 @@ class Achievement_x_User(Base):
738
802
  )
739
803
 
740
804
 
741
- class Achievement_x_Org(Base):
742
- """
743
- Model representing the association between achievements and organizations.
805
+ # class Achievement_x_Org(Base):
806
+ # """
807
+ # Model representing the association between achievements and organizations.
744
808
 
745
- Attributes:
746
- achievement_id (int): The ID of the associated achievement.
747
- org_id (int): The ID of the associated organization.
748
- """
809
+ # Attributes:
810
+ # achievement_id (int): The ID of the associated achievement.
811
+ # org_id (int): The ID of the associated organization.
812
+ # """
749
813
 
750
- __tablename__ = "achievements_x_org"
814
+ # __tablename__ = "achievements_x_org"
751
815
 
752
- achievement_id: Mapped[int] = mapped_column(
753
- ForeignKey("achievements.id"), primary_key=True
754
- )
755
- org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
816
+ # achievement_id: Mapped[int] = mapped_column(
817
+ # ForeignKey("achievements.id"), primary_key=True
818
+ # )
819
+ # org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
756
820
 
757
821
 
758
822
  class Position(Base):
@@ -894,3 +958,20 @@ class MagicLinkAuthSession(Base):
894
958
  session_token: Mapped[str]
895
959
  created: Mapped[dt_create]
896
960
  expiration: Mapped[dt_create]
961
+
962
+
963
+ # class Org_x_SlackChannel(Base):
964
+ # """
965
+ # Model representing the association between organizations (specifically AOs) and Slack channels.
966
+
967
+ # Attributes:
968
+ # org_id (int): The ID of the associated organization.
969
+ # slack_channel_id (str): The Slack-internal ID of the associated Slack channel.
970
+ # """
971
+
972
+ # __tablename__ = "orgs_x_slack_channels"
973
+
974
+ # org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
975
+ # slack_channel_id: Mapped[str] = mapped_column(
976
+ # primary_key=True
977
+ # ) # Do we need a slack channel table?
@@ -3,11 +3,11 @@ from dataclasses import dataclass
3
3
  from typing import List, Optional, Tuple, TypeVar
4
4
 
5
5
  import sqlalchemy
6
- from sqlalchemy import and_, select
6
+ from sqlalchemy import Select, and_, select
7
7
 
8
8
  from sqlalchemy.dialects.postgresql import insert
9
9
  from sqlalchemy.engine import Engine
10
- from sqlalchemy.orm import sessionmaker
10
+ from sqlalchemy.orm import sessionmaker, joinedload
11
11
 
12
12
  from f3_data_models.models import Base
13
13
 
@@ -76,30 +76,35 @@ def close_session(session):
76
76
  T = TypeVar("T")
77
77
 
78
78
 
79
- class DbManager:
80
- def get_record(cls: T, id) -> T:
81
- session = get_session()
82
- try:
83
- x = session.query(cls).filter(cls.get_id() == id).first()
84
- if x:
85
- session.expunge(x)
86
- return x
87
- finally:
88
- session.rollback()
89
- close_session(session)
79
+ def _joinedloads(cls: T, query: Select, joinedloads: list | str) -> Select:
80
+ if joinedloads == "all":
81
+ joinedloads = [
82
+ getattr(cls, relationship.key)
83
+ for relationship in cls.__mapper__.relationships
84
+ ]
85
+ return query.options(*[joinedload(load) for load in joinedloads])
86
+
90
87
 
91
- def get(cls: T, id: int) -> T:
88
+ class DbManager:
89
+ def get(cls: T, id: int, joinedloads: list | str = []) -> T:
92
90
  session = get_session()
93
91
  try:
94
- return session.scalars(select(cls).filter(cls.id == id)).one()
92
+ query = select(cls).filter(cls.id == id)
93
+ query = _joinedloads(cls, query, joinedloads)
94
+ return session.scalars(query).unique().one()
95
95
  finally:
96
96
  session.rollback()
97
97
  close_session(session)
98
98
 
99
- def find_records(cls: T, filters: Optional[List]) -> List[T]:
100
- session = get_session()
99
+ def find_records(
100
+ cls: T, filters: Optional[List], joinedloads: List | str = []
101
+ ) -> List[T]:
102
+ session = get_session(echo=True)
101
103
  try:
102
- records = session.scalars(select(cls).filter(*filters)).all()
104
+ query = select(cls)
105
+ query = _joinedloads(cls, query, joinedloads)
106
+ query = query.filter(*filters)
107
+ records = session.scalars(query).unique().all()
103
108
  for r in records:
104
109
  session.expunge(r)
105
110
  return records
@@ -107,10 +112,15 @@ class DbManager:
107
112
  session.rollback()
108
113
  close_session(session)
109
114
 
110
- def find_first_record(cls: T, filters: Optional[List]) -> T:
115
+ def find_first_record(
116
+ cls: T, filters: Optional[List], joinedloads: List | str = []
117
+ ) -> T:
111
118
  session = get_session()
112
119
  try:
113
- record = session.scalars(select(cls).filter(*filters)).first()
120
+ query = select(cls)
121
+ query = _joinedloads(cls, query, joinedloads)
122
+ query = query.filter(*filters)
123
+ record = session.scalars(query).unique().first()
114
124
  if record:
115
125
  session.expunge(record)
116
126
  return record
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "f3-data-models"
3
- version = "0.1.7"
3
+ version = "0.1.8"
4
4
  description = "The data schema and models for F3 Nation applications."
5
5
  authors = ["Evan Petzoldt <evan.petzoldt@protonmail.com>"]
6
6
  readme = "README.md"