You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

112 lines
3.2KB

  1. # testing/entities.py
  2. # Copyright (C) 2005-2021 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: http://www.opensource.org/licenses/mit-license.php
  7. import sqlalchemy as sa
  8. from .. import exc as sa_exc
  9. from ..util import compat
  10. _repr_stack = set()
  11. class BasicEntity(object):
  12. def __init__(self, **kw):
  13. for key, value in kw.items():
  14. setattr(self, key, value)
  15. def __repr__(self):
  16. if id(self) in _repr_stack:
  17. return object.__repr__(self)
  18. _repr_stack.add(id(self))
  19. try:
  20. return "%s(%s)" % (
  21. (self.__class__.__name__),
  22. ", ".join(
  23. [
  24. "%s=%r" % (key, getattr(self, key))
  25. for key in sorted(self.__dict__.keys())
  26. if not key.startswith("_")
  27. ]
  28. ),
  29. )
  30. finally:
  31. _repr_stack.remove(id(self))
  32. _recursion_stack = set()
  33. class ComparableMixin(object):
  34. def __ne__(self, other):
  35. return not self.__eq__(other)
  36. def __eq__(self, other):
  37. """'Deep, sparse compare.
  38. Deeply compare two entities, following the non-None attributes of the
  39. non-persisted object, if possible.
  40. """
  41. if other is self:
  42. return True
  43. elif not self.__class__ == other.__class__:
  44. return False
  45. if id(self) in _recursion_stack:
  46. return True
  47. _recursion_stack.add(id(self))
  48. try:
  49. # pick the entity that's not SA persisted as the source
  50. try:
  51. self_key = sa.orm.attributes.instance_state(self).key
  52. except sa.orm.exc.NO_STATE:
  53. self_key = None
  54. if other is None:
  55. a = self
  56. b = other
  57. elif self_key is not None:
  58. a = other
  59. b = self
  60. else:
  61. a = self
  62. b = other
  63. for attr in list(a.__dict__):
  64. if attr.startswith("_"):
  65. continue
  66. value = getattr(a, attr)
  67. try:
  68. # handle lazy loader errors
  69. battr = getattr(b, attr)
  70. except (AttributeError, sa_exc.UnboundExecutionError):
  71. return False
  72. if hasattr(value, "__iter__") and not isinstance(
  73. value, compat.string_types
  74. ):
  75. if hasattr(value, "__getitem__") and not hasattr(
  76. value, "keys"
  77. ):
  78. if list(value) != list(battr):
  79. return False
  80. else:
  81. if set(value) != set(battr):
  82. return False
  83. else:
  84. if value is not None and value != battr:
  85. return False
  86. return True
  87. finally:
  88. _recursion_stack.remove(id(self))
  89. class ComparableEntity(ComparableMixin, BasicEntity):
  90. def __hash__(self):
  91. return hash(self.__class__)