|
1 | 1 | # Do not edit this file directly. It has been autogenerated from
|
2 | 2 | # advanced_alchemy/repository/_async.py
|
| 3 | +import contextlib |
3 | 4 | import datetime
|
4 | 5 | import decimal
|
5 | 6 | import random
|
|
26 | 27 | Update,
|
27 | 28 | any_,
|
28 | 29 | delete,
|
| 30 | + inspect, |
29 | 31 | over,
|
30 | 32 | select,
|
31 | 33 | text,
|
32 | 34 | update,
|
33 | 35 | )
|
34 | 36 | from sqlalchemy import func as sql_func
|
| 37 | +from sqlalchemy.exc import MissingGreenlet, NoInspectionAvailable |
35 | 38 | from sqlalchemy.orm import InstrumentedAttribute, Session
|
36 | 39 | from sqlalchemy.orm.scoping import scoped_session
|
37 | 40 | from sqlalchemy.orm.strategy_options import _AbstractLoad # pyright: ignore[reportPrivateUsage]
|
|
46 | 49 | FilterableRepository,
|
47 | 50 | FilterableRepositoryProtocol,
|
48 | 51 | LoadSpec,
|
| 52 | + column_has_defaults, |
49 | 53 | get_abstract_loader_options,
|
50 | 54 | get_instrumented_attr,
|
51 | 55 | )
|
@@ -1479,10 +1483,45 @@ def update(
|
1479 | 1483 | data,
|
1480 | 1484 | id_attribute=id_attribute,
|
1481 | 1485 | )
|
1482 |
| - # this will raise for not found, and will put the item in the session |
1483 |
| - self.get(item_id, id_attribute=id_attribute, load=load, execution_options=execution_options) |
1484 |
| - # this will merge the inbound data to the instance we just put in the session |
1485 |
| - instance = self._attach_to_session(data, strategy="merge") |
| 1486 | + existing_instance = self.get( |
| 1487 | + item_id, id_attribute=id_attribute, load=load, execution_options=execution_options |
| 1488 | + ) |
| 1489 | + mapper = None |
| 1490 | + with ( |
| 1491 | + self.session.no_autoflush, |
| 1492 | + contextlib.suppress(MissingGreenlet, NoInspectionAvailable), |
| 1493 | + ): |
| 1494 | + mapper = inspect(data) |
| 1495 | + if mapper is not None: |
| 1496 | + for column in mapper.mapper.columns: |
| 1497 | + field_name = column.key |
| 1498 | + new_field_value = getattr(data, field_name, MISSING) |
| 1499 | + if new_field_value is not MISSING: |
| 1500 | + # Skip setting columns with defaults/onupdate to None during updates |
| 1501 | + # This prevents overwriting columns that should use their defaults |
| 1502 | + if new_field_value is None and column_has_defaults(column): |
| 1503 | + continue |
| 1504 | + existing_field_value = getattr(existing_instance, field_name, MISSING) |
| 1505 | + if existing_field_value is not MISSING and existing_field_value != new_field_value: |
| 1506 | + setattr(existing_instance, field_name, new_field_value) |
| 1507 | + |
| 1508 | + # Handle relationships by merging objects into session first |
| 1509 | + for relationship in mapper.mapper.relationships: |
| 1510 | + if (new_value := getattr(data, relationship.key, MISSING)) is not MISSING: |
| 1511 | + if isinstance(new_value, list): |
| 1512 | + merged_values = [ # pyright: ignore |
| 1513 | + self.session.merge(item, load=False) # pyright: ignore |
| 1514 | + for item in new_value # pyright: ignore |
| 1515 | + ] |
| 1516 | + setattr(existing_instance, relationship.key, merged_values) |
| 1517 | + elif new_value is not None: |
| 1518 | + merged_value = self.session.merge(new_value, load=False) |
| 1519 | + setattr(existing_instance, relationship.key, merged_value) |
| 1520 | + else: |
| 1521 | + setattr(existing_instance, relationship.key, new_value) |
| 1522 | + |
| 1523 | + instance = self._attach_to_session(existing_instance, strategy="merge") |
| 1524 | + |
1486 | 1525 | self._flush_or_commit(auto_commit=auto_commit)
|
1487 | 1526 | self._refresh(
|
1488 | 1527 | instance,
|
|
0 commit comments