SQLAlchemy-Utils 0.42.0__tar.gz → 0.43.0__tar.gz

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.
Files changed (206) hide show
  1. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/CHANGES.rst +20 -0
  2. {sqlalchemy_utils-0.42.0/SQLAlchemy_Utils.egg-info → sqlalchemy_utils-0.43.0}/PKG-INFO +6 -20
  3. sqlalchemy_utils-0.43.0/README.rst +12 -0
  4. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0/SQLAlchemy_Utils.egg-info}/PKG-INFO +6 -20
  5. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/SQLAlchemy_Utils.egg-info/requires.txt +3 -7
  6. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/installation.rst +1 -6
  7. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/pyproject.toml +6 -10
  8. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/__init__.py +1 -1
  9. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/database.py +2 -8
  10. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/mock.py +1 -4
  11. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/orm.py +9 -22
  12. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/sort_query.py +1 -4
  13. sqlalchemy_utils-0.43.0/sqlalchemy_utils/generic.py +146 -0
  14. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/listeners.py +3 -1
  15. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/choice.py +3 -3
  16. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/password.py +4 -2
  17. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/uuid.py +2 -13
  18. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/conftest.py +1 -1
  19. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_database.py +0 -4
  20. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/generic_relationship/__init__.py +46 -0
  21. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_instant_defaults_listener.py +19 -0
  22. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_choice.py +63 -0
  23. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_json.py +1 -4
  24. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_password.py +31 -1
  25. sqlalchemy_utils-0.42.0/README.rst +0 -22
  26. sqlalchemy_utils-0.42.0/sqlalchemy_utils/generic.py +0 -200
  27. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/LICENSE +0 -0
  28. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/MANIFEST.in +0 -0
  29. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/SQLAlchemy_Utils.egg-info/SOURCES.txt +0 -0
  30. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/SQLAlchemy_Utils.egg-info/dependency_links.txt +0 -0
  31. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/SQLAlchemy_Utils.egg-info/top_level.txt +0 -0
  32. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/aggregates.rst +0 -0
  33. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/conf.py +0 -0
  34. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/data_types.rst +0 -0
  35. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/database_helpers.rst +0 -0
  36. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/foreign_key_helpers.rst +0 -0
  37. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/generic_relationship.rst +0 -0
  38. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/index.rst +0 -0
  39. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/internationalization.rst +0 -0
  40. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/license.rst +0 -0
  41. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/listeners.rst +0 -0
  42. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/models.rst +0 -0
  43. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/observers.rst +0 -0
  44. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/orm_helpers.rst +0 -0
  45. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/range_data_types.rst +0 -0
  46. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/testing.rst +0 -0
  47. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/utility_classes.rst +0 -0
  48. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/docs/view.rst +0 -0
  49. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/setup.cfg +0 -0
  50. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/aggregates.py +0 -0
  51. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/asserts.py +0 -0
  52. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/compat.py +0 -0
  53. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/exceptions.py +0 -0
  54. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/expressions.py +0 -0
  55. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/__init__.py +0 -0
  56. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/foreign_keys.py +0 -0
  57. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/functions/render.py +0 -0
  58. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/i18n.py +0 -0
  59. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/models.py +0 -0
  60. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/observer.py +0 -0
  61. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/operators.py +0 -0
  62. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/path.py +0 -0
  63. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/primitives/__init__.py +0 -0
  64. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/primitives/country.py +0 -0
  65. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/primitives/currency.py +0 -0
  66. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/primitives/ltree.py +0 -0
  67. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/primitives/weekday.py +0 -0
  68. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/primitives/weekdays.py +0 -0
  69. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/proxy_dict.py +0 -0
  70. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/query_chain.py +0 -0
  71. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/relationships/__init__.py +0 -0
  72. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/relationships/chained_join.py +0 -0
  73. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/__init__.py +0 -0
  74. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/arrow.py +0 -0
  75. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/bit.py +0 -0
  76. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/color.py +0 -0
  77. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/country.py +0 -0
  78. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/currency.py +0 -0
  79. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/email.py +0 -0
  80. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/encrypted/__init__.py +0 -0
  81. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/encrypted/encrypted_type.py +0 -0
  82. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/encrypted/padding.py +0 -0
  83. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/enriched_datetime/__init__.py +0 -0
  84. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/enriched_datetime/arrow_datetime.py +0 -0
  85. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/enriched_datetime/enriched_date_type.py +0 -0
  86. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/enriched_datetime/enriched_datetime_type.py +0 -0
  87. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/enriched_datetime/pendulum_date.py +0 -0
  88. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/enriched_datetime/pendulum_datetime.py +0 -0
  89. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/ip_address.py +0 -0
  90. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/json.py +0 -0
  91. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/locale.py +0 -0
  92. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/ltree.py +0 -0
  93. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/pg_composite.py +0 -0
  94. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/phone_number.py +0 -0
  95. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/range.py +0 -0
  96. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/scalar_coercible.py +0 -0
  97. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/scalar_list.py +0 -0
  98. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/timezone.py +0 -0
  99. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/ts_vector.py +0 -0
  100. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/url.py +0 -0
  101. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/types/weekdays.py +0 -0
  102. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/utils.py +0 -0
  103. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/sqlalchemy_utils/view.py +0 -0
  104. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/__init__.py +0 -0
  105. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/__init__.py +0 -0
  106. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_backrefs.py +0 -0
  107. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_custom_select_expressions.py +0 -0
  108. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_join_table_inheritance.py +0 -0
  109. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_m2m.py +0 -0
  110. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_m2m_m2m.py +0 -0
  111. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_multiple_aggregates_per_class.py +0 -0
  112. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_o2m_m2m.py +0 -0
  113. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_o2m_o2m.py +0 -0
  114. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_o2m_o2m_o2m.py +0 -0
  115. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_search_vectors.py +0 -0
  116. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_simple_paths.py +0 -0
  117. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_with_column_alias.py +0 -0
  118. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/aggregate/test_with_ondelete_cascade.py +0 -0
  119. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/__init__.py +0 -0
  120. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_cast_if.py +0 -0
  121. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_dependent_objects.py +0 -0
  122. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_escape_like.py +0 -0
  123. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_bind.py +0 -0
  124. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_class_by_table.py +0 -0
  125. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_column_key.py +0 -0
  126. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_columns.py +0 -0
  127. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_hybrid_properties.py +0 -0
  128. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_mapper.py +0 -0
  129. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_primary_keys.py +0 -0
  130. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_referencing_foreign_keys.py +0 -0
  131. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_tables.py +0 -0
  132. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_get_type.py +0 -0
  133. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_getdotattr.py +0 -0
  134. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_has_changes.py +0 -0
  135. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_has_index.py +0 -0
  136. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_has_unique_index.py +0 -0
  137. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_identity.py +0 -0
  138. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_is_loaded.py +0 -0
  139. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_json_sql.py +0 -0
  140. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_jsonb_sql.py +0 -0
  141. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_make_order_by_deterministic.py +0 -0
  142. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_merge_references.py +0 -0
  143. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_naturally_equivalent.py +0 -0
  144. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_non_indexed_foreign_keys.py +0 -0
  145. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_quote.py +0 -0
  146. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_render.py +0 -0
  147. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/functions/test_table_name.py +0 -0
  148. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/generic_relationship/test_abstract_base_class.py +0 -0
  149. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/generic_relationship/test_column_aliases.py +0 -0
  150. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/generic_relationship/test_composite_keys.py +0 -0
  151. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/generic_relationship/test_hybrid_properties.py +0 -0
  152. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/generic_relationship/test_single_table_inheritance.py +0 -0
  153. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/mixins.py +0 -0
  154. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/__init__.py +0 -0
  155. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_column_property.py +0 -0
  156. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_dynamic_relationship.py +0 -0
  157. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_m2m_m2m_m2m.py +0 -0
  158. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_o2m_o2m_o2m.py +0 -0
  159. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_o2m_o2o_o2m.py +0 -0
  160. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_o2o_o2o.py +0 -0
  161. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/observes/test_o2o_o2o_o2o.py +0 -0
  162. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/primitives/__init__.py +0 -0
  163. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/primitives/test_country.py +0 -0
  164. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/primitives/test_currency.py +0 -0
  165. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/primitives/test_ltree.py +0 -0
  166. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/primitives/test_weekdays.py +0 -0
  167. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/relationships/__init__.py +0 -0
  168. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/relationships/test_chained_join.py +0 -0
  169. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/relationships/test_select_correlated_expression.py +0 -0
  170. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_asserts.py +0 -0
  171. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_auto_delete_orphans.py +0 -0
  172. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_case_insensitive_comparator.py +0 -0
  173. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_expressions.py +0 -0
  174. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_instrumented_list.py +0 -0
  175. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_models.py +0 -0
  176. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_path.py +0 -0
  177. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_proxy_dict.py +0 -0
  178. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_query_chain.py +0 -0
  179. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_translation_hybrid.py +0 -0
  180. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/test_views.py +0 -0
  181. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/__init__.py +0 -0
  182. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/encrypted/test_padding.py +0 -0
  183. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_arrow.py +0 -0
  184. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_color.py +0 -0
  185. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_composite.py +0 -0
  186. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_country.py +0 -0
  187. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_currency.py +0 -0
  188. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_date_range.py +0 -0
  189. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_datetime_range.py +0 -0
  190. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_email.py +0 -0
  191. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_encrypted.py +0 -0
  192. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_enriched_date_pendulum.py +0 -0
  193. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_enriched_datetime_arrow.py +0 -0
  194. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_enriched_datetime_pendulum.py +0 -0
  195. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_int_range.py +0 -0
  196. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_ip_address.py +0 -0
  197. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_locale.py +0 -0
  198. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_ltree.py +0 -0
  199. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_numeric_range.py +0 -0
  200. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_phonenumber.py +0 -0
  201. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_scalar_list.py +0 -0
  202. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_timezone.py +0 -0
  203. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_tsvector.py +0 -0
  204. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_url.py +0 -0
  205. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_uuid.py +0 -0
  206. {sqlalchemy_utils-0.42.0 → sqlalchemy_utils-0.43.0}/tests/types/test_weekdays.py +0 -0
@@ -3,6 +3,26 @@ Changelog
3
3
 
4
4
  Here you can see the full list of changes between each SQLAlchemy-Utils release.
5
5
 
6
+ 0.43.0 (2026-10-09)
7
+ ^^^^^^^^^^^^^^^^^^^
8
+
9
+ - Add support for SQLAlchemy 2.1.
10
+ - Reimplement ``generic_relationship`` as a hybrid property built only on public SQLAlchemy APIs, instead of private attribute internals.
11
+
12
+ Assigning to a generic relationship now sets the discriminator and id attributes through regular attribute instrumentation, so re-assigning it on a persistent object is flushed to the database.
13
+ - Support querying generic relationships through aliased models, including composite keys and renamed columns.
14
+ - Drop support for sqlalchemy 1.4.
15
+ - Drop support for Python 3.9.
16
+ - Support Python 3.14.
17
+ - Fix ``ChoiceType`` returning the raw scalar instead of a ``Choice`` for falsy codes such as ``0`` or the empty string. (#813)
18
+
19
+ NULL values continue to return ``None``.
20
+
21
+ 0.42.1 (2025-12-12)
22
+ ^^^^^^^^^^^^^^^^^^^
23
+
24
+ - Fix ``AttributeError`` with Sequence defaults in ``instant_defaults_listener`` (#793)
25
+
6
26
  0.42.0 (2025-08-30)
7
27
  ^^^^^^^^^^^^^^^^^^^
8
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SQLAlchemy-Utils
3
- Version: 0.42.0
3
+ Version: 0.43.0
4
4
  Summary: Various utility functions for SQLAlchemy.
5
5
  Author: Ryan Leckey, Janne Vanhala, Vesa Uimonen
6
6
  Author-email: Konsta Vesterinen <konsta@fastmonkeys.com>
@@ -10,19 +10,19 @@ Classifier: Environment :: Web Environment
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
18
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
21
21
  Description-Content-Type: text/x-rst
22
22
  License-File: LICENSE
23
- Requires-Dist: SQLAlchemy>=1.4
23
+ Requires-Dist: SQLAlchemy>=2.0
24
24
  Provides-Extra: test
25
- Requires-Dist: pytest==7.4.4; extra == "test"
25
+ Requires-Dist: pytest<10,>=9.0; extra == "test"
26
26
  Requires-Dist: Pygments>=1.2; extra == "test"
27
27
  Requires-Dist: Jinja2>=2.3; extra == "test"
28
28
  Requires-Dist: docutils>=0.10; extra == "test"
@@ -34,8 +34,6 @@ Requires-Dist: pg8000>=1.12.4; extra == "test"
34
34
  Requires-Dist: pytz>=2014.2; extra == "test"
35
35
  Requires-Dist: python-dateutil>=2.6; extra == "test"
36
36
  Requires-Dist: pymysql; extra == "test"
37
- Requires-Dist: flake8>=2.4.0; extra == "test"
38
- Requires-Dist: isort>=4.2.2; extra == "test"
39
37
  Requires-Dist: pyodbc; extra == "test"
40
38
  Provides-Extra: babel
41
39
  Requires-Dist: Babel>=1.3; extra == "babel"
@@ -58,7 +56,7 @@ Requires-Dist: furl>=0.4.1; extra == "url"
58
56
  Provides-Extra: encrypted
59
57
  Requires-Dist: cryptography>=0.6; extra == "encrypted"
60
58
  Provides-Extra: test-all
61
- Requires-Dist: pytest==7.4.4; extra == "test-all"
59
+ Requires-Dist: pytest<10,>=9.0; extra == "test-all"
62
60
  Requires-Dist: Pygments>=1.2; extra == "test-all"
63
61
  Requires-Dist: Jinja2>=2.3; extra == "test-all"
64
62
  Requires-Dist: docutils>=0.10; extra == "test-all"
@@ -70,8 +68,6 @@ Requires-Dist: pg8000>=1.12.4; extra == "test-all"
70
68
  Requires-Dist: pytz>=2014.2; extra == "test-all"
71
69
  Requires-Dist: python-dateutil>=2.6; extra == "test-all"
72
70
  Requires-Dist: pymysql; extra == "test-all"
73
- Requires-Dist: flake8>=2.4.0; extra == "test-all"
74
- Requires-Dist: isort>=4.2.2; extra == "test-all"
75
71
  Requires-Dist: pyodbc; extra == "test-all"
76
72
  Requires-Dist: Babel>=1.3; extra == "test-all"
77
73
  Requires-Dist: arrow>=0.3.4; extra == "test-all"
@@ -88,8 +84,6 @@ Dynamic: license-file
88
84
  SQLAlchemy-Utils
89
85
  ================
90
86
 
91
- |Build| |Version Status| |Downloads|
92
-
93
87
  Various utility functions, new data types and helpers for SQLAlchemy.
94
88
 
95
89
 
@@ -99,11 +93,3 @@ Resources
99
93
  - `Documentation <https://sqlalchemy-utils.readthedocs.io/>`_
100
94
  - `Issue Tracker <https://github.com/kvesteri/sqlalchemy-utils/issues>`_
101
95
  - `Code <https://github.com/kvesteri/sqlalchemy-utils/>`_
102
-
103
- .. |Build| image:: https://github.com/kvesteri/sqlalchemy-utils/actions/workflows/test.yaml/badge.svg?branch=master
104
- :target: https://github.com/kvesteri/sqlalchemy-utils/actions/workflows/test.yaml
105
- :alt: Tests
106
- .. |Version Status| image:: https://img.shields.io/pypi/v/SQLAlchemy-Utils.svg
107
- :target: https://pypi.python.org/pypi/SQLAlchemy-Utils/
108
- .. |Downloads| image:: https://img.shields.io/pypi/dm/SQLAlchemy-Utils.svg
109
- :target: https://pypi.python.org/pypi/SQLAlchemy-Utils/
@@ -0,0 +1,12 @@
1
+ SQLAlchemy-Utils
2
+ ================
3
+
4
+ Various utility functions, new data types and helpers for SQLAlchemy.
5
+
6
+
7
+ Resources
8
+ ---------
9
+
10
+ - `Documentation <https://sqlalchemy-utils.readthedocs.io/>`_
11
+ - `Issue Tracker <https://github.com/kvesteri/sqlalchemy-utils/issues>`_
12
+ - `Code <https://github.com/kvesteri/sqlalchemy-utils/>`_
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SQLAlchemy-Utils
3
- Version: 0.42.0
3
+ Version: 0.43.0
4
4
  Summary: Various utility functions for SQLAlchemy.
5
5
  Author: Ryan Leckey, Janne Vanhala, Vesa Uimonen
6
6
  Author-email: Konsta Vesterinen <konsta@fastmonkeys.com>
@@ -10,19 +10,19 @@ Classifier: Environment :: Web Environment
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
18
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
21
21
  Description-Content-Type: text/x-rst
22
22
  License-File: LICENSE
23
- Requires-Dist: SQLAlchemy>=1.4
23
+ Requires-Dist: SQLAlchemy>=2.0
24
24
  Provides-Extra: test
25
- Requires-Dist: pytest==7.4.4; extra == "test"
25
+ Requires-Dist: pytest<10,>=9.0; extra == "test"
26
26
  Requires-Dist: Pygments>=1.2; extra == "test"
27
27
  Requires-Dist: Jinja2>=2.3; extra == "test"
28
28
  Requires-Dist: docutils>=0.10; extra == "test"
@@ -34,8 +34,6 @@ Requires-Dist: pg8000>=1.12.4; extra == "test"
34
34
  Requires-Dist: pytz>=2014.2; extra == "test"
35
35
  Requires-Dist: python-dateutil>=2.6; extra == "test"
36
36
  Requires-Dist: pymysql; extra == "test"
37
- Requires-Dist: flake8>=2.4.0; extra == "test"
38
- Requires-Dist: isort>=4.2.2; extra == "test"
39
37
  Requires-Dist: pyodbc; extra == "test"
40
38
  Provides-Extra: babel
41
39
  Requires-Dist: Babel>=1.3; extra == "babel"
@@ -58,7 +56,7 @@ Requires-Dist: furl>=0.4.1; extra == "url"
58
56
  Provides-Extra: encrypted
59
57
  Requires-Dist: cryptography>=0.6; extra == "encrypted"
60
58
  Provides-Extra: test-all
61
- Requires-Dist: pytest==7.4.4; extra == "test-all"
59
+ Requires-Dist: pytest<10,>=9.0; extra == "test-all"
62
60
  Requires-Dist: Pygments>=1.2; extra == "test-all"
63
61
  Requires-Dist: Jinja2>=2.3; extra == "test-all"
64
62
  Requires-Dist: docutils>=0.10; extra == "test-all"
@@ -70,8 +68,6 @@ Requires-Dist: pg8000>=1.12.4; extra == "test-all"
70
68
  Requires-Dist: pytz>=2014.2; extra == "test-all"
71
69
  Requires-Dist: python-dateutil>=2.6; extra == "test-all"
72
70
  Requires-Dist: pymysql; extra == "test-all"
73
- Requires-Dist: flake8>=2.4.0; extra == "test-all"
74
- Requires-Dist: isort>=4.2.2; extra == "test-all"
75
71
  Requires-Dist: pyodbc; extra == "test-all"
76
72
  Requires-Dist: Babel>=1.3; extra == "test-all"
77
73
  Requires-Dist: arrow>=0.3.4; extra == "test-all"
@@ -88,8 +84,6 @@ Dynamic: license-file
88
84
  SQLAlchemy-Utils
89
85
  ================
90
86
 
91
- |Build| |Version Status| |Downloads|
92
-
93
87
  Various utility functions, new data types and helpers for SQLAlchemy.
94
88
 
95
89
 
@@ -99,11 +93,3 @@ Resources
99
93
  - `Documentation <https://sqlalchemy-utils.readthedocs.io/>`_
100
94
  - `Issue Tracker <https://github.com/kvesteri/sqlalchemy-utils/issues>`_
101
95
  - `Code <https://github.com/kvesteri/sqlalchemy-utils/>`_
102
-
103
- .. |Build| image:: https://github.com/kvesteri/sqlalchemy-utils/actions/workflows/test.yaml/badge.svg?branch=master
104
- :target: https://github.com/kvesteri/sqlalchemy-utils/actions/workflows/test.yaml
105
- :alt: Tests
106
- .. |Version Status| image:: https://img.shields.io/pypi/v/SQLAlchemy-Utils.svg
107
- :target: https://pypi.python.org/pypi/SQLAlchemy-Utils/
108
- .. |Downloads| image:: https://img.shields.io/pypi/dm/SQLAlchemy-Utils.svg
109
- :target: https://pypi.python.org/pypi/SQLAlchemy-Utils/
@@ -1,4 +1,4 @@
1
- SQLAlchemy>=1.4
1
+ SQLAlchemy>=2.0
2
2
 
3
3
  [arrow]
4
4
  arrow>=0.3.4
@@ -25,7 +25,7 @@ pendulum>=2.0.5
25
25
  phonenumbers>=5.9.2
26
26
 
27
27
  [test]
28
- pytest==7.4.4
28
+ pytest<10,>=9.0
29
29
  Pygments>=1.2
30
30
  Jinja2>=2.3
31
31
  docutils>=0.10
@@ -37,12 +37,10 @@ pg8000>=1.12.4
37
37
  pytz>=2014.2
38
38
  python-dateutil>=2.6
39
39
  pymysql
40
- flake8>=2.4.0
41
- isort>=4.2.2
42
40
  pyodbc
43
41
 
44
42
  [test_all]
45
- pytest==7.4.4
43
+ pytest<10,>=9.0
46
44
  Pygments>=1.2
47
45
  Jinja2>=2.3
48
46
  docutils>=0.10
@@ -54,8 +52,6 @@ pg8000>=1.12.4
54
52
  pytz>=2014.2
55
53
  python-dateutil>=2.6
56
54
  pymysql
57
- flake8>=2.4.0
58
- isort>=4.2.2
59
55
  pyodbc
60
56
  Babel>=1.3
61
57
  arrow>=0.3.4
@@ -6,12 +6,7 @@ This part of the documentation covers the installation of SQLAlchemy-Utils.
6
6
  Supported platforms
7
7
  -------------------
8
8
 
9
- SQLAlchemy-Utils is currently tested against the following Python platforms:
10
-
11
- - CPython 3.9
12
- - CPython 3.10
13
- - CPython 3.11
14
- - CPython 3.12
9
+ SQLAlchemy-Utils is tested against supported CPython versions.
15
10
 
16
11
 
17
12
  Installing an official release
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "SQLAlchemy-Utils"
7
- version = "0.42.0"
7
+ version = "0.43.0"
8
8
  description = "Various utility functions for SQLAlchemy."
9
9
  readme = "README.rst"
10
10
  license = "BSD-3-Clause"
@@ -14,22 +14,22 @@ authors = [
14
14
  {name = "Janne Vanhala"},
15
15
  {name = "Vesa Uimonen"}
16
16
  ]
17
- requires-python = ">=3.9"
17
+ requires-python = ">=3.10"
18
18
  classifiers = [
19
19
  "Environment :: Web Environment",
20
20
  "Intended Audience :: Developers",
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python",
23
- "Programming Language :: Python :: 3.9",
24
23
  "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
26
25
  "Programming Language :: Python :: 3.12",
27
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
28
  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
29
29
  "Topic :: Software Development :: Libraries :: Python Modules",
30
30
  ]
31
31
  dependencies = [
32
- "SQLAlchemy>=1.4",
32
+ "SQLAlchemy>=2.0",
33
33
  ]
34
34
 
35
35
  [project.urls]
@@ -37,7 +37,7 @@ Homepage = "https://github.com/kvesteri/sqlalchemy-utils"
37
37
 
38
38
  [project.optional-dependencies]
39
39
  test = [
40
- "pytest==7.4.4",
40
+ "pytest>=9.0,<10",
41
41
  "Pygments>=1.2",
42
42
  "Jinja2>=2.3",
43
43
  "docutils>=0.10",
@@ -49,8 +49,6 @@ test = [
49
49
  "pytz>=2014.2",
50
50
  "python-dateutil>=2.6",
51
51
  "pymysql",
52
- "flake8>=2.4.0",
53
- "isort>=4.2.2",
54
52
  "pyodbc",
55
53
  ]
56
54
  babel = ["Babel>=1.3"]
@@ -64,7 +62,7 @@ timezone = ["python-dateutil"]
64
62
  url = ["furl >= 0.4.1"]
65
63
  encrypted = ["cryptography>=0.6"]
66
64
  test_all = [
67
- "pytest==7.4.4",
65
+ "pytest>=9.0,<10",
68
66
  "Pygments>=1.2",
69
67
  "Jinja2>=2.3",
70
68
  "docutils>=0.10",
@@ -76,8 +74,6 @@ test_all = [
76
74
  "pytz>=2014.2",
77
75
  "python-dateutil>=2.6",
78
76
  "pymysql",
79
- "flake8>=2.4.0",
80
- "isort>=4.2.2",
81
77
  "pyodbc",
82
78
  "Babel>=1.3",
83
79
  "arrow>=0.3.4",
@@ -101,4 +101,4 @@ from .view import ( # noqa
101
101
  refresh_materialized_view,
102
102
  )
103
103
 
104
- __version__ = '0.42.0'
104
+ __version__ = '0.43.0'
@@ -1,7 +1,6 @@
1
1
  import itertools
2
2
  import os
3
3
  from collections.abc import Mapping, Sequence
4
- from copy import copy
5
4
 
6
5
  import sqlalchemy as sa
7
6
  from sqlalchemy.engine.url import make_url
@@ -416,13 +415,8 @@ def _set_url_database(url: sa.engine.url.URL, database):
416
415
  :param database: New database to set.
417
416
 
418
417
  """
419
- if hasattr(url, '_replace'):
420
- # Cannot use URL.set() as database may need to be set to None.
421
- ret = url._replace(database=database)
422
- else: # SQLAlchemy <1.4
423
- url = copy(url)
424
- url.database = database
425
- ret = url
418
+ # Cannot use URL.set() as database may need to be set to None.
419
+ ret = url._replace(database=database)
426
420
  assert ret.database == database, ret
427
421
  return ret
428
422
 
@@ -47,10 +47,7 @@ def create_mock_engine(bind, stream=None):
47
47
  def dump(*args, **kw):
48
48
  return None
49
49
 
50
- try:
51
- engine = sa.create_mock_engine(bind_url, executor=dump)
52
- except AttributeError: # SQLAlchemy <1.4
53
- engine = sa.create_engine(bind_url, strategy='mock', executor=dump)
50
+ engine = sa.create_mock_engine(bind_url, executor=dump)
54
51
  return engine
55
52
 
56
53
 
@@ -10,10 +10,7 @@ from sqlalchemy.orm import ColumnProperty, mapperlib, RelationshipProperty
10
10
  from sqlalchemy.orm.attributes import InstrumentedAttribute
11
11
  from sqlalchemy.orm.exc import UnmappedInstanceError
12
12
 
13
- try:
14
- from sqlalchemy.orm.context import _ColumnEntity, _MapperEntity
15
- except ImportError: # SQLAlchemy <1.4
16
- from sqlalchemy.orm.query import _ColumnEntity, _MapperEntity
13
+ from sqlalchemy.orm.context import _ColumnEntity, _MapperEntity
17
14
 
18
15
  from sqlalchemy.orm.session import object_session
19
16
  from sqlalchemy.orm.util import AliasedInsp
@@ -281,12 +278,9 @@ def get_mapper(mixed):
281
278
  if isinstance(mixed, sa.orm.attributes.InstrumentedAttribute):
282
279
  mixed = mixed.class_
283
280
  if isinstance(mixed, sa.Table):
284
- if hasattr(mapperlib, '_all_registries'):
285
- all_mappers = set()
286
- for mapper_registry in mapperlib._all_registries():
287
- all_mappers.update(mapper_registry.mappers)
288
- else: # SQLAlchemy <1.4
289
- all_mappers = mapperlib._mapper_registry
281
+ all_mappers = set()
282
+ for mapper_registry in mapperlib._all_registries():
283
+ all_mappers.update(mapper_registry.mappers)
290
284
  mappers = [mapper for mapper in all_mappers if mixed in mapper.tables]
291
285
  if len(mappers) > 1:
292
286
  raise ValueError("Multiple mappers found for table '%s'." % mixed.name)
@@ -449,11 +443,10 @@ def get_columns(mixed):
449
443
  SA Table object, SA Mapper, SA declarative class, SA declarative class
450
444
  instance or an alias of any of these objects
451
445
  """
446
+ if isinstance(mixed, sa.SelectBase):
447
+ return mixed.subquery().c
452
448
  if isinstance(mixed, sa.sql.selectable.Selectable):
453
- try:
454
- return mixed.selected_columns
455
- except AttributeError: # SQLAlchemy <1.4
456
- return mixed.c
449
+ return mixed.c
457
450
  if isinstance(mixed, sa.orm.util.AliasedClass):
458
451
  return sa.inspect(mixed).mapper.columns
459
452
  if isinstance(mixed, sa.orm.Mapper):
@@ -520,10 +513,7 @@ def quote(mixed, ident):
520
513
 
521
514
 
522
515
  def _get_query_compile_state(query):
523
- if hasattr(query, '_compile_state'):
524
- return query._compile_state()
525
- else: # SQLAlchemy <1.4
526
- return query
516
+ return query._compile_state()
527
517
 
528
518
 
529
519
  def get_polymorphic_mappers(mixed):
@@ -878,7 +868,4 @@ def naturally_equivalent(obj, obj2):
878
868
 
879
869
 
880
870
  def _get_class_registry(class_):
881
- try:
882
- return class_.registry._class_registry
883
- except AttributeError: # SQLAlchemy <1.4
884
- return class_._decl_class_registry
871
+ return class_.registry._class_registry
@@ -40,10 +40,7 @@ def make_order_by_deterministic(query):
40
40
  """
41
41
  order_by_func = sa.asc
42
42
 
43
- try:
44
- order_by_clauses = query._order_by_clauses
45
- except AttributeError: # SQLAlchemy <1.4
46
- order_by_clauses = query._order_by
43
+ order_by_clauses = query._order_by_clauses
47
44
  if not order_by_clauses:
48
45
  column = None
49
46
  else:
@@ -0,0 +1,146 @@
1
+ import builtins
2
+ from collections.abc import Iterable
3
+
4
+ import sqlalchemy as sa
5
+ from sqlalchemy.ext.hybrid import Comparator, hybrid_property
6
+ from sqlalchemy.orm import object_session
7
+
8
+ from .exceptions import ImproperlyConfigured
9
+ from .functions import identity
10
+
11
+
12
+ class GenericComparator(Comparator):
13
+ def __init__(self, relationship, cls):
14
+ self.relationship = relationship
15
+ self.cls = cls
16
+
17
+ def __clause_element__(self):
18
+ raise NotImplementedError(
19
+ 'A generic relationship can only be compared against a model '
20
+ 'instance or with is_type().'
21
+ )
22
+
23
+ def __eq__(self, other):
24
+ discriminator, ids = self.relationship._attributes(self.cls)
25
+ q = discriminator == type(other).__name__
26
+ for column, value in zip(ids, identity(other)):
27
+ q &= column == value
28
+ return q
29
+
30
+ def __ne__(self, other):
31
+ return ~(self == other)
32
+
33
+ def is_type(self, other):
34
+ discriminator, _ = self.relationship._attributes(self.cls)
35
+ class_names = [
36
+ mapper.class_.__name__ for mapper in sa.inspect(other).self_and_descendants
37
+ ]
38
+ return discriminator.in_(class_names)
39
+
40
+
41
+ class GenericRelationship:
42
+ """A generic form of the relationship property.
43
+
44
+ Creates a 1 to many relationship between the parent model
45
+ and any other models using a discriminator (the class name).
46
+
47
+ Implemented as a hybrid property on top of the discriminator and id
48
+ attributes, so it only relies on public SQLAlchemy APIs.
49
+
50
+ :param discriminator:
51
+ Field to discriminate which model we are referring to.
52
+ :param id:
53
+ Field to point to the model we are referring to.
54
+ """
55
+
56
+ def __init__(self, discriminator, id):
57
+ self.discriminator = discriminator
58
+ if isinstance(id, Iterable) and not isinstance(id, str):
59
+ self.ids = list(id)
60
+ else:
61
+ self.ids = [id]
62
+ # Key for caching the assigned target object on the instance dict.
63
+ self.cache_key = f'_generic_relationship_{builtins.id(self)}'
64
+
65
+ def _key(self, cls, column):
66
+ if isinstance(column, str):
67
+ return column
68
+ if isinstance(column, hybrid_property):
69
+ return column.__name__
70
+ try:
71
+ return sa.inspect(cls).mapper.get_property_by_column(column).key
72
+ except sa.orm.exc.UnmappedColumnError:
73
+ raise ImproperlyConfigured(
74
+ f'Could not find generic relationship attribute for {column!r}.'
75
+ )
76
+
77
+ def _keys(self, cls):
78
+ return (
79
+ self._key(cls, self.discriminator),
80
+ [self._key(cls, column) for column in self.ids],
81
+ )
82
+
83
+ def _attributes(self, cls):
84
+ discriminator, ids = self._keys(cls)
85
+ return getattr(cls, discriminator), [getattr(cls, key) for key in ids]
86
+
87
+ def _target_class(self, cls, discriminator):
88
+ for mapper in sa.inspect(cls).registry.mappers:
89
+ if mapper.class_.__name__ == discriminator:
90
+ return mapper.class_
91
+
92
+ def get(self, obj):
93
+ discriminator_key, id_keys = self._keys(type(obj))
94
+ discriminator = getattr(obj, discriminator_key)
95
+ id = tuple(getattr(obj, key) for key in id_keys)
96
+
97
+ # Return the object assigned to this relationship as long as the
98
+ # discriminator and id still point to it.
99
+ cached = obj.__dict__.get(self.cache_key)
100
+ if (
101
+ cached is not None
102
+ and type(cached).__name__ == discriminator
103
+ and identity(cached) == id
104
+ ):
105
+ return cached
106
+
107
+ # Use the session the object is bound to in order to perform
108
+ # a lazy query for the target.
109
+ session = object_session(obj)
110
+ if session is None:
111
+ return None
112
+
113
+ target_class = self._target_class(type(obj), discriminator)
114
+ if target_class is None:
115
+ # Unknown discriminator; return nothing.
116
+ return None
117
+
118
+ return session.get(target_class, id)
119
+
120
+ def set(self, obj, value):
121
+ discriminator_key, id_keys = self._keys(type(obj))
122
+ obj.__dict__[self.cache_key] = value
123
+
124
+ if value is None:
125
+ # Nullify relationship args
126
+ for key in id_keys:
127
+ setattr(obj, key, None)
128
+ setattr(obj, discriminator_key, None)
129
+ else:
130
+ for key, pk in zip(id_keys, identity(value)):
131
+ setattr(obj, key, pk)
132
+ setattr(obj, discriminator_key, type(value).__name__)
133
+
134
+ def comparator(self, cls):
135
+ return GenericComparator(self, cls)
136
+
137
+
138
+ def generic_relationship(discriminator, id, doc=None):
139
+ relationship = GenericRelationship(discriminator, id)
140
+ prop = hybrid_property(
141
+ relationship.get,
142
+ relationship.set,
143
+ custom_comparator=relationship.comparator,
144
+ )
145
+ prop.__doc__ = doc
146
+ return prop
@@ -24,7 +24,9 @@ def instant_defaults_listener(target, args, kwargs):
24
24
 
25
25
  for key, column in sa.inspect(target.__class__).columns.items():
26
26
  if hasattr(column, 'default') and column.default is not None:
27
- if callable(column.default.arg):
27
+ if not hasattr(column.default, 'arg'):
28
+ continue
29
+ elif callable(column.default.arg):
28
30
  kwargs[key] = column.default.arg(target)
29
31
  else:
30
32
  kwargs[key] = column.default.arg
@@ -183,9 +183,9 @@ class ChoiceTypeImpl:
183
183
  return value
184
184
 
185
185
  def process_result_value(self, value, dialect):
186
- if value:
187
- return Choice(value, self.choices_dict[value])
188
- return value
186
+ if value is None:
187
+ return None
188
+ return Choice(value, self.choices_dict[value])
189
189
 
190
190
 
191
191
  class EnumTypeImpl:
@@ -42,8 +42,10 @@ class Password(Mutable):
42
42
 
43
43
  def __eq__(self, value):
44
44
  if self.hash is None or value is None:
45
- # Ensure that we don't continue comparison if one of us is None.
46
- return self.hash is value
45
+ # If either the hash or the value is None,
46
+ # equivalence depends on *all* values being None,
47
+ # including the secret.
48
+ return self.hash is None and self.secret is None and value is None
47
49
 
48
50
  if isinstance(value, Password):
49
51
  # Comparing 2 hashes isn't very useful; but this equality
@@ -3,11 +3,8 @@ import uuid
3
3
  from sqlalchemy import types, util
4
4
  from sqlalchemy.dialects import mssql, postgresql
5
5
 
6
- from ..compat import get_sqlalchemy_version
7
6
  from .scalar_coercible import ScalarCoercible
8
7
 
9
- sqlalchemy_version = get_sqlalchemy_version()
10
-
11
8
 
12
9
  class UUIDType(ScalarCoercible, types.TypeDecorator):
13
10
  """
@@ -71,16 +68,8 @@ class UUIDType(ScalarCoercible, types.TypeDecorator):
71
68
 
72
69
  return value
73
70
 
74
- # sqlalchemy >= 1.4.30 quotes UUID's automatically.
75
- # It is only necessary to quote UUID's in sqlalchemy < 1.4.30.
76
- if sqlalchemy_version < (1, 4, 30):
77
-
78
- def process_literal_param(self, value, dialect):
79
- return f"'{value}'" if value else value
80
- else:
81
-
82
- def process_literal_param(self, value, dialect):
83
- return value
71
+ def process_literal_param(self, value, dialect):
72
+ return value
84
73
 
85
74
  def process_bind_param(self, value, dialect):
86
75
  if value is None:
@@ -64,7 +64,7 @@ def mysql_db_user():
64
64
  def postgresql_dsn(
65
65
  postgresql_db_user, postgresql_db_password, postgresql_db_host, db_name
66
66
  ):
67
- return 'postgresql://{}:{}@{}/{}'.format(
67
+ return 'postgresql+psycopg2://{}:{}@{}/{}'.format(
68
68
  postgresql_db_user, postgresql_db_password, postgresql_db_host, db_name
69
69
  )
70
70