f3-data-models 0.3.6__py3-none-any.whl → 0.3.8__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.
- f3_data_models/models.py +22 -0
- f3_data_models/testing.py +33 -0
- f3_data_models/utils.py +93 -40
- {f3_data_models-0.3.6.dist-info → f3_data_models-0.3.8.dist-info}/METADATA +1 -1
- f3_data_models-0.3.8.dist-info/RECORD +7 -0
- f3_data_models-0.3.6.dist-info/RECORD +0 -6
- {f3_data_models-0.3.6.dist-info → f3_data_models-0.3.8.dist-info}/WHEEL +0 -0
f3_data_models/models.py
CHANGED
@@ -17,6 +17,7 @@ from sqlalchemy import (
|
|
17
17
|
UniqueConstraint,
|
18
18
|
Enum,
|
19
19
|
Uuid,
|
20
|
+
inspect,
|
20
21
|
)
|
21
22
|
from typing_extensions import Annotated
|
22
23
|
from sqlalchemy.orm import (
|
@@ -25,6 +26,7 @@ from sqlalchemy.orm import (
|
|
25
26
|
Mapped,
|
26
27
|
relationship,
|
27
28
|
)
|
29
|
+
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
28
30
|
import enum
|
29
31
|
|
30
32
|
# Custom Annotations
|
@@ -150,6 +152,26 @@ class Base(DeclarativeBase):
|
|
150
152
|
if c.key not in ["created", "updated"]
|
151
153
|
}
|
152
154
|
|
155
|
+
def to_update_dict(self) -> Dict[InstrumentedAttribute, Any]:
|
156
|
+
update_dict = {}
|
157
|
+
mapper = inspect(self).mapper
|
158
|
+
|
159
|
+
# Add simple attributes
|
160
|
+
for attr in mapper.column_attrs:
|
161
|
+
if attr.key not in ["created", "updated", "id"]:
|
162
|
+
update_dict[attr] = getattr(self, attr.key)
|
163
|
+
|
164
|
+
# Add relationships
|
165
|
+
for rel in mapper.relationships:
|
166
|
+
related_value = getattr(self, rel.key)
|
167
|
+
if related_value is not None:
|
168
|
+
if rel.uselist:
|
169
|
+
update_dict[rel] = [item for item in related_value]
|
170
|
+
print(rel, update_dict[rel])
|
171
|
+
else:
|
172
|
+
update_dict[rel] = related_value
|
173
|
+
return update_dict
|
174
|
+
|
153
175
|
def __repr__(self):
|
154
176
|
"""
|
155
177
|
Get a string representation of the model instance.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from f3_data_models.models import Event, Day_Of_Week, Event_Cadence, EventType_x_Event
|
2
|
+
import datetime
|
3
|
+
from f3_data_models.utils import DbManager
|
4
|
+
|
5
|
+
|
6
|
+
def test_update_event():
|
7
|
+
event = Event(
|
8
|
+
org_id=3,
|
9
|
+
location_id=2,
|
10
|
+
is_series=True,
|
11
|
+
is_active=True,
|
12
|
+
highlight=True,
|
13
|
+
start_date=datetime.date(2025, 2, 17),
|
14
|
+
end_date=datetime.date(2026, 2, 17),
|
15
|
+
start_time="0500",
|
16
|
+
end_time="0600",
|
17
|
+
event_x_event_types=[
|
18
|
+
EventType_x_Event(event_type_id=3),
|
19
|
+
],
|
20
|
+
recurrence_pattern=Event_Cadence.weekly,
|
21
|
+
day_of_week=Day_Of_Week.monday,
|
22
|
+
recurrence_interval=1,
|
23
|
+
index_within_interval=1,
|
24
|
+
name="Test Event",
|
25
|
+
)
|
26
|
+
update_dict = event.to_update_dict()
|
27
|
+
DbManager.update_records(Event, [Event.id == 3], update_dict)
|
28
|
+
|
29
|
+
event = DbManager.get(Event, 3)
|
30
|
+
|
31
|
+
|
32
|
+
if __name__ == "__main__":
|
33
|
+
test_update_event()
|
f3_data_models/utils.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List, Optional, Tuple, TypeVar
|
3
|
+
from typing import List, Optional, Tuple, TypeVar, Type, Generic # noqa
|
4
4
|
|
5
5
|
import sqlalchemy
|
6
6
|
from sqlalchemy import Select, and_, select, inspect
|
@@ -9,7 +9,6 @@ from sqlalchemy.dialects.postgresql import insert
|
|
9
9
|
from sqlalchemy.engine import Engine
|
10
10
|
from sqlalchemy.orm import sessionmaker, joinedload
|
11
11
|
from sqlalchemy.orm.collections import InstrumentedList
|
12
|
-
from sqlalchemy.orm.attributes import flag_modified
|
13
12
|
|
14
13
|
from f3_data_models.models import Base
|
15
14
|
|
@@ -17,6 +16,8 @@ from pydot import Dot
|
|
17
16
|
from sqlalchemy_schemadisplay import create_schema_graph
|
18
17
|
from google.cloud.sql.connector import Connector, IPTypes
|
19
18
|
import pg8000
|
19
|
+
from sqlalchemy.orm import class_mapper
|
20
|
+
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
20
21
|
|
21
22
|
|
22
23
|
@dataclass
|
@@ -88,7 +89,7 @@ def _joinedloads(cls: T, query: Select, joinedloads: list | str) -> Select:
|
|
88
89
|
|
89
90
|
|
90
91
|
class DbManager:
|
91
|
-
def get(cls: T, id: int, joinedloads: list | str = []) -> T:
|
92
|
+
def get(cls: Type[T], id: int, joinedloads: list | str = []) -> T:
|
92
93
|
session = get_session()
|
93
94
|
try:
|
94
95
|
query = select(cls).filter(cls.id == id)
|
@@ -167,33 +168,57 @@ class DbManager:
|
|
167
168
|
close_session(session)
|
168
169
|
|
169
170
|
def update_record(cls: T, id, fields):
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
171
|
+
with get_session() as session:
|
172
|
+
record = session.query(cls).get(id)
|
173
|
+
if not record:
|
174
|
+
raise ValueError(f"Record with id {id} not found in {cls.__name__}")
|
175
|
+
|
176
|
+
mapper = class_mapper(cls)
|
177
|
+
relationships = mapper.relationships.keys()
|
178
|
+
for attr, value in fields.items():
|
179
|
+
key = attr.key
|
180
|
+
if hasattr(cls, key) and key not in relationships:
|
181
|
+
if isinstance(attr, InstrumentedAttribute):
|
182
|
+
setattr(record, key, value)
|
183
|
+
elif key in relationships:
|
184
|
+
# Handle relationships separately
|
185
|
+
relationship = mapper.relationships[key]
|
186
|
+
related_class = relationship.mapper.class_
|
187
|
+
# find mapping of related_class
|
188
|
+
og_primary_key = None
|
189
|
+
for k in related_class.__table__.foreign_keys:
|
190
|
+
if k.references(cls.__table__):
|
191
|
+
og_primary_key = k.constraint.columns[0].name
|
192
|
+
break
|
193
|
+
|
194
|
+
if isinstance(value, list) and og_primary_key:
|
195
|
+
# Delete existing related records
|
196
|
+
related_class = relationship.mapper.class_
|
197
|
+
related_relationships = class_mapper(
|
198
|
+
related_class
|
199
|
+
).relationships.keys()
|
200
|
+
session.query(related_class).filter(
|
201
|
+
getattr(related_class, og_primary_key) == id
|
202
|
+
).delete()
|
203
|
+
# Add new related records
|
204
|
+
items = [item.__dict__ for item in value]
|
205
|
+
for related_item in items:
|
206
|
+
update_dict = {
|
207
|
+
k: v
|
208
|
+
for k, v in related_item.items()
|
209
|
+
if hasattr(related_class, k)
|
210
|
+
and k not in related_relationships
|
211
|
+
}
|
212
|
+
related_record = related_class(
|
213
|
+
**{og_primary_key: id, **update_dict}
|
214
|
+
)
|
215
|
+
session.add(related_record)
|
216
|
+
|
217
|
+
try:
|
218
|
+
session.commit()
|
219
|
+
except pg8000.IntegrityError as e:
|
220
|
+
session.rollback()
|
221
|
+
raise e
|
197
222
|
|
198
223
|
def update_records(cls, filters, fields):
|
199
224
|
session = get_session()
|
@@ -207,21 +232,49 @@ class DbManager:
|
|
207
232
|
|
208
233
|
for obj in objects:
|
209
234
|
# Update simple fields
|
210
|
-
for
|
235
|
+
for attr, value in fields.items():
|
236
|
+
key = attr.key
|
211
237
|
if key in valid_attributes and not isinstance(
|
212
238
|
value, InstrumentedList
|
213
239
|
):
|
214
240
|
setattr(obj, key, value)
|
215
241
|
|
216
|
-
# Update relationships
|
217
|
-
for
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
242
|
+
# Update relationships separately
|
243
|
+
for attr, value in fields.items():
|
244
|
+
key = attr.key
|
245
|
+
if key in valid_relationships:
|
246
|
+
# Handle relationships separately
|
247
|
+
relationship = inspect(cls).mapper.relationships[key]
|
248
|
+
related_class = relationship.mapper.class_
|
249
|
+
# find mapping of related_class
|
250
|
+
og_primary_key = None
|
251
|
+
for k in related_class.__table__.foreign_keys:
|
252
|
+
if k.references(cls.__table__):
|
253
|
+
og_primary_key = k.constraint.columns[0].name
|
254
|
+
break
|
255
|
+
|
256
|
+
if isinstance(value, list) and og_primary_key:
|
257
|
+
# Delete existing related records
|
258
|
+
related_class = relationship.mapper.class_
|
259
|
+
related_relationships = class_mapper(
|
260
|
+
related_class
|
261
|
+
).relationships.keys()
|
262
|
+
session.query(related_class).filter(
|
263
|
+
getattr(related_class, og_primary_key) == obj.id
|
264
|
+
).delete()
|
265
|
+
# Add new related records
|
266
|
+
items = [item.__dict__ for item in value]
|
267
|
+
for related_item in items:
|
268
|
+
update_dict = {
|
269
|
+
k: v
|
270
|
+
for k, v in related_item.items()
|
271
|
+
if hasattr(related_class, k)
|
272
|
+
and k not in related_relationships
|
273
|
+
}
|
274
|
+
related_record = related_class(
|
275
|
+
**{og_primary_key: obj.id, **update_dict}
|
276
|
+
)
|
277
|
+
session.add(related_record)
|
225
278
|
|
226
279
|
session.flush()
|
227
280
|
finally:
|
@@ -0,0 +1,7 @@
|
|
1
|
+
f3_data_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
f3_data_models/models.py,sha256=jGxAG5Dmzhf2u4cLMUxY48yKENn5HC5N9_eUUSb31yc,45109
|
3
|
+
f3_data_models/testing.py,sha256=Pd-TYvkeai2DuN9ubPPdqjo4xS_Zd3T1WGV7J_SXPdc,921
|
4
|
+
f3_data_models/utils.py,sha256=UpNx1E_kmt8_1vN4fdFSy55yPCoDA2algzof_5EAJ2k,13835
|
5
|
+
f3_data_models-0.3.8.dist-info/METADATA,sha256=Hza4-PUUC0sK4D-BJHGIx81WXrSjcS8PFYB98euxGhE,2716
|
6
|
+
f3_data_models-0.3.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
7
|
+
f3_data_models-0.3.8.dist-info/RECORD,,
|
@@ -1,6 +0,0 @@
|
|
1
|
-
f3_data_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
f3_data_models/models.py,sha256=SLOXwM2BMnlGTwkVZfG3fM9hoksvPkcFRqzpEI4bm2Y,44293
|
3
|
-
f3_data_models/utils.py,sha256=fGv-qHviSsUqHmYElBvYgWbqX5LAlTJCoKSMIqd8ioA,10738
|
4
|
-
f3_data_models-0.3.6.dist-info/METADATA,sha256=G6dtENVh5a3JKZ4NdCl3DVY6dTybhdEl5jLvQmKnFHY,2716
|
5
|
-
f3_data_models-0.3.6.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
6
|
-
f3_data_models-0.3.6.dist-info/RECORD,,
|
File without changes
|