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 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
- session = get_session()
171
- # Fetch the object to be updated
172
- obj = session.query(cls).get(id)
173
- if not obj:
174
- raise ValueError(f"Object with id {id} not found")
175
-
176
- # Get the list of valid attributes for the class
177
- valid_attributes = {attr.key for attr in inspect(cls).mapper.column_attrs}
178
- valid_relationships = {rel.key for rel in inspect(cls).mapper.relationships}
179
-
180
- # Update simple fields
181
- for key, value in fields.items():
182
- if key in valid_attributes and not isinstance(value, InstrumentedList):
183
- setattr(obj, key, value)
184
-
185
- # Update relationships
186
- for key, value in fields.items():
187
- if key in valid_relationships and isinstance(value, InstrumentedList):
188
- related_objects = getattr(obj, key)
189
- related_objects.clear()
190
- related_objects.extend(value)
191
-
192
- # Flag the object as modified to ensure changes are detected
193
- flag_modified(obj, key)
194
-
195
- # Commit the changes
196
- session.commit()
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 key, value in fields.items():
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 key, value in fields.items():
218
- if key in valid_relationships and isinstance(
219
- value, InstrumentedList
220
- ):
221
- related_objects = getattr(obj, key)
222
- related_objects.clear()
223
- related_objects.extend(value)
224
- flag_modified(obj, key)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: f3-data-models
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: The data schema and models for F3 Nation applications.
5
5
  License: MIT
6
6
  Author: Evan Petzoldt
@@ -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,,