sync.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # orm/sync.py
  2. # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. # mypy: allow-untyped-defs, allow-untyped-calls
  8. """private module containing functions used for copying data
  9. between instances based on join conditions.
  10. """
  11. from __future__ import annotations
  12. from . import exc
  13. from . import util as orm_util
  14. from .base import PassiveFlag
  15. def populate(
  16. source,
  17. source_mapper,
  18. dest,
  19. dest_mapper,
  20. synchronize_pairs,
  21. uowcommit,
  22. flag_cascaded_pks,
  23. ):
  24. source_dict = source.dict
  25. dest_dict = dest.dict
  26. for l, r in synchronize_pairs:
  27. try:
  28. # inline of source_mapper._get_state_attr_by_column
  29. prop = source_mapper._columntoproperty[l]
  30. value = source.manager[prop.key].impl.get(
  31. source, source_dict, PassiveFlag.PASSIVE_OFF
  32. )
  33. except exc.UnmappedColumnError as err:
  34. _raise_col_to_prop(False, source_mapper, l, dest_mapper, r, err)
  35. try:
  36. # inline of dest_mapper._set_state_attr_by_column
  37. prop = dest_mapper._columntoproperty[r]
  38. dest.manager[prop.key].impl.set(dest, dest_dict, value, None)
  39. except exc.UnmappedColumnError as err:
  40. _raise_col_to_prop(True, source_mapper, l, dest_mapper, r, err)
  41. # technically the "r.primary_key" check isn't
  42. # needed here, but we check for this condition to limit
  43. # how often this logic is invoked for memory/performance
  44. # reasons, since we only need this info for a primary key
  45. # destination.
  46. if (
  47. flag_cascaded_pks
  48. and l.primary_key
  49. and r.primary_key
  50. and r.references(l)
  51. ):
  52. uowcommit.attributes[("pk_cascaded", dest, r)] = True
  53. def bulk_populate_inherit_keys(source_dict, source_mapper, synchronize_pairs):
  54. # a simplified version of populate() used by bulk insert mode
  55. for l, r in synchronize_pairs:
  56. try:
  57. prop = source_mapper._columntoproperty[l]
  58. value = source_dict[prop.key]
  59. except exc.UnmappedColumnError as err:
  60. _raise_col_to_prop(False, source_mapper, l, source_mapper, r, err)
  61. try:
  62. prop = source_mapper._columntoproperty[r]
  63. source_dict[prop.key] = value
  64. except exc.UnmappedColumnError as err:
  65. _raise_col_to_prop(True, source_mapper, l, source_mapper, r, err)
  66. def clear(dest, dest_mapper, synchronize_pairs):
  67. for l, r in synchronize_pairs:
  68. if (
  69. r.primary_key
  70. and dest_mapper._get_state_attr_by_column(dest, dest.dict, r)
  71. not in orm_util._none_set
  72. ):
  73. raise AssertionError(
  74. f"Dependency rule on column '{l}' "
  75. "tried to blank-out primary key "
  76. f"column '{r}' on instance '{orm_util.state_str(dest)}'"
  77. )
  78. try:
  79. dest_mapper._set_state_attr_by_column(dest, dest.dict, r, None)
  80. except exc.UnmappedColumnError as err:
  81. _raise_col_to_prop(True, None, l, dest_mapper, r, err)
  82. def update(source, source_mapper, dest, old_prefix, synchronize_pairs):
  83. for l, r in synchronize_pairs:
  84. try:
  85. oldvalue = source_mapper._get_committed_attr_by_column(
  86. source.obj(), l
  87. )
  88. value = source_mapper._get_state_attr_by_column(
  89. source, source.dict, l, passive=PassiveFlag.PASSIVE_OFF
  90. )
  91. except exc.UnmappedColumnError as err:
  92. _raise_col_to_prop(False, source_mapper, l, None, r, err)
  93. dest[r.key] = value
  94. dest[old_prefix + r.key] = oldvalue
  95. def populate_dict(source, source_mapper, dict_, synchronize_pairs):
  96. for l, r in synchronize_pairs:
  97. try:
  98. value = source_mapper._get_state_attr_by_column(
  99. source, source.dict, l, passive=PassiveFlag.PASSIVE_OFF
  100. )
  101. except exc.UnmappedColumnError as err:
  102. _raise_col_to_prop(False, source_mapper, l, None, r, err)
  103. dict_[r.key] = value
  104. def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
  105. """return true if the source object has changes from an old to a
  106. new value on the given synchronize pairs
  107. """
  108. for l, r in synchronize_pairs:
  109. try:
  110. prop = source_mapper._columntoproperty[l]
  111. except exc.UnmappedColumnError as err:
  112. _raise_col_to_prop(False, source_mapper, l, None, r, err)
  113. history = uowcommit.get_attribute_history(
  114. source, prop.key, PassiveFlag.PASSIVE_NO_INITIALIZE
  115. )
  116. if bool(history.deleted):
  117. return True
  118. else:
  119. return False
  120. def _raise_col_to_prop(
  121. isdest, source_mapper, source_column, dest_mapper, dest_column, err
  122. ):
  123. if isdest:
  124. raise exc.UnmappedColumnError(
  125. "Can't execute sync rule for "
  126. "destination column '%s'; mapper '%s' does not map "
  127. "this column. Try using an explicit `foreign_keys` "
  128. "collection which does not include this column (or use "
  129. "a viewonly=True relation)." % (dest_column, dest_mapper)
  130. ) from err
  131. else:
  132. raise exc.UnmappedColumnError(
  133. "Can't execute sync rule for "
  134. "source column '%s'; mapper '%s' does not map this "
  135. "column. Try using an explicit `foreign_keys` "
  136. "collection which does not include destination column "
  137. "'%s' (or use a viewonly=True relation)."
  138. % (source_column, source_mapper, dest_column)
  139. ) from err