f3-data-models 0.1.7__tar.gz → 0.1.10__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.
- {f3_data_models-0.1.7 → f3_data_models-0.1.10}/PKG-INFO +17 -2
- {f3_data_models-0.1.7 → f3_data_models-0.1.10}/README.md +16 -1
- {f3_data_models-0.1.7 → f3_data_models-0.1.10}/f3_data_models/models.py +74 -28
- {f3_data_models-0.1.7 → f3_data_models-0.1.10}/f3_data_models/utils.py +29 -19
- {f3_data_models-0.1.7 → f3_data_models-0.1.10}/pyproject.toml +1 -1
- {f3_data_models-0.1.7 → f3_data_models-0.1.10}/f3_data_models/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: f3-data-models
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.10
|
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.
|
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.
|
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,5 +1,5 @@
|
|
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,
|
@@ -17,6 +17,7 @@ from sqlalchemy.orm import (
|
|
17
17
|
DeclarativeBase,
|
18
18
|
mapped_column,
|
19
19
|
Mapped,
|
20
|
+
relationship,
|
20
21
|
)
|
21
22
|
|
22
23
|
# Custom Annotations
|
@@ -282,7 +283,7 @@ class Org(Base):
|
|
282
283
|
|
283
284
|
__tablename__ = "orgs"
|
284
285
|
|
285
|
-
id: Mapped[
|
286
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
286
287
|
parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey("orgs.id"))
|
287
288
|
org_type_id: Mapped[int] = mapped_column(ForeignKey("org_types.id"))
|
288
289
|
default_location_id: Mapped[Optional[int]]
|
@@ -300,6 +301,19 @@ class Org(Base):
|
|
300
301
|
created: Mapped[dt_create]
|
301
302
|
updated: Mapped[dt_update]
|
302
303
|
|
304
|
+
event_types: Mapped[Optional[List["EventType"]]] = relationship(
|
305
|
+
"EventType", secondary="event_types_x_org", cascade="expunge"
|
306
|
+
)
|
307
|
+
event_tags: Mapped[Optional[List["EventTag"]]] = relationship(
|
308
|
+
"EventTag", secondary="event_tags_x_org", cascade="expunge"
|
309
|
+
)
|
310
|
+
achievements: Mapped[Optional[List["Achievement"]]] = relationship(
|
311
|
+
"Achievement", secondary="achievements_x_org", cascade="expunge"
|
312
|
+
)
|
313
|
+
parent_org: Mapped[Optional["Org"]] = relationship(
|
314
|
+
"Org", remote_side=[id], cascade="expunge"
|
315
|
+
)
|
316
|
+
|
303
317
|
|
304
318
|
class EventType(Base):
|
305
319
|
"""
|
@@ -545,6 +559,15 @@ class Event(Base):
|
|
545
559
|
created: Mapped[dt_create]
|
546
560
|
updated: Mapped[dt_update]
|
547
561
|
|
562
|
+
org: Mapped[Org] = relationship(innerjoin=True, cascade="expunge")
|
563
|
+
location: Mapped[Location] = relationship(innerjoin=True, cascade="expunge")
|
564
|
+
event_types: Mapped[List[EventType]] = relationship(
|
565
|
+
secondary="events_x_event_types", innerjoin=True, cascade="expunge"
|
566
|
+
)
|
567
|
+
event_tags: Mapped[Optional[List[EventTag]]] = relationship(
|
568
|
+
secondary="event_tags_x_events", cascade="expunge"
|
569
|
+
)
|
570
|
+
|
548
571
|
|
549
572
|
class AttendanceType(Base):
|
550
573
|
"""
|
@@ -564,32 +587,6 @@ class AttendanceType(Base):
|
|
564
587
|
updated: Mapped[dt_update]
|
565
588
|
|
566
589
|
|
567
|
-
class Attendance(Base):
|
568
|
-
"""
|
569
|
-
Model representing an attendance record.
|
570
|
-
|
571
|
-
Attributes:
|
572
|
-
id (int): Primary Key of the model.
|
573
|
-
event_id (int): The ID of the associated event.
|
574
|
-
user_id (Optional[int]): The ID of the associated user.
|
575
|
-
is_planned (bool): Whether this is planned attendance (True) vs actual attendance (False).
|
576
|
-
meta (Optional[Dict[str, Any]]): Additional metadata for the attendance.
|
577
|
-
created (datetime): The timestamp when the record was created.
|
578
|
-
updated (datetime): The timestamp when the record was last updated.
|
579
|
-
"""
|
580
|
-
|
581
|
-
__tablename__ = "attendance"
|
582
|
-
__table_args__ = (UniqueConstraint("event_id", "user_id", "is_planned"),)
|
583
|
-
|
584
|
-
id: Mapped[intpk]
|
585
|
-
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"))
|
586
|
-
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
|
587
|
-
is_planned: Mapped[bool]
|
588
|
-
meta: Mapped[Optional[Dict[str, Any]]]
|
589
|
-
created: Mapped[dt_create]
|
590
|
-
updated: Mapped[dt_update]
|
591
|
-
|
592
|
-
|
593
590
|
class Attendance_x_AttenanceType(Base):
|
594
591
|
"""
|
595
592
|
Model representing the association between attendance and attendance types.
|
@@ -692,6 +689,38 @@ class SlackUser(Base):
|
|
692
689
|
updated: Mapped[dt_update]
|
693
690
|
|
694
691
|
|
692
|
+
class Attendance(Base):
|
693
|
+
"""
|
694
|
+
Model representing an attendance record.
|
695
|
+
|
696
|
+
Attributes:
|
697
|
+
id (int): Primary Key of the model.
|
698
|
+
event_id (int): The ID of the associated event.
|
699
|
+
user_id (Optional[int]): The ID of the associated user.
|
700
|
+
is_planned (bool): Whether this is planned attendance (True) vs actual attendance (False).
|
701
|
+
meta (Optional[Dict[str, Any]]): Additional metadata for the attendance.
|
702
|
+
created (datetime): The timestamp when the record was created.
|
703
|
+
updated (datetime): The timestamp when the record was last updated.
|
704
|
+
"""
|
705
|
+
|
706
|
+
__tablename__ = "attendance"
|
707
|
+
__table_args__ = (UniqueConstraint("event_id", "user_id", "is_planned"),)
|
708
|
+
|
709
|
+
id: Mapped[intpk]
|
710
|
+
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"))
|
711
|
+
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
|
712
|
+
is_planned: Mapped[bool]
|
713
|
+
meta: Mapped[Optional[Dict[str, Any]]]
|
714
|
+
created: Mapped[dt_create]
|
715
|
+
updated: Mapped[dt_update]
|
716
|
+
|
717
|
+
event: Mapped[Event] = relationship(innerjoin=True, cascade="expunge")
|
718
|
+
user: Mapped[User] = relationship(innerjoin=True, cascade="expunge", viewonly=True)
|
719
|
+
slack_user: Mapped[Optional[SlackUser]] = relationship(
|
720
|
+
innerjoin=False, cascade="expunge", secondary="users"
|
721
|
+
)
|
722
|
+
|
723
|
+
|
695
724
|
class Achievement(Base):
|
696
725
|
"""
|
697
726
|
Model representing an achievement.
|
@@ -894,3 +923,20 @@ class MagicLinkAuthSession(Base):
|
|
894
923
|
session_token: Mapped[str]
|
895
924
|
created: Mapped[dt_create]
|
896
925
|
expiration: Mapped[dt_create]
|
926
|
+
|
927
|
+
|
928
|
+
# class Org_x_SlackChannel(Base):
|
929
|
+
# """
|
930
|
+
# Model representing the association between organizations (specifically AOs) and Slack channels.
|
931
|
+
|
932
|
+
# Attributes:
|
933
|
+
# org_id (int): The ID of the associated organization.
|
934
|
+
# slack_channel_id (str): The Slack-internal ID of the associated Slack channel.
|
935
|
+
# """
|
936
|
+
|
937
|
+
# __tablename__ = "orgs_x_slack_channels"
|
938
|
+
|
939
|
+
# org_id: Mapped[int] = mapped_column(ForeignKey("orgs.id"), primary_key=True)
|
940
|
+
# slack_channel_id: Mapped[str] = mapped_column(
|
941
|
+
# primary_key=True
|
942
|
+
# ) # 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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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])
|
90
86
|
|
91
|
-
|
87
|
+
|
88
|
+
class DbManager:
|
89
|
+
def get(cls: T, id: int, joinedloads: list | str = []) -> T:
|
92
90
|
session = get_session()
|
93
91
|
try:
|
94
|
-
|
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(
|
99
|
+
def find_records(
|
100
|
+
cls: T, filters: Optional[List], joinedloads: List | str = []
|
101
|
+
) -> List[T]:
|
100
102
|
session = get_session()
|
101
103
|
try:
|
102
|
-
|
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(
|
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
|
-
|
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
|
File without changes
|