eventsourcing 9.3.0a1__tar.gz → 9.3.1__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.

Potentially problematic release.


This version of eventsourcing might be problematic. Click here for more details.

Files changed (146) hide show
  1. eventsourcing-9.3.1/AUTHORS +10 -0
  2. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/PKG-INFO +6 -5
  3. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/application.py +8 -1
  4. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/domain.py +87 -88
  5. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate4/domainmodel.py +14 -28
  6. eventsourcing-9.3.1/eventsourcing/examples/contentmanagementsystem/test_system.py +180 -0
  7. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/test_application.py +4 -5
  8. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/test_recorder.py +4 -5
  9. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +8 -5
  10. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/postgres.py +28 -22
  11. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/system.py +10 -0
  12. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/docs_tests/test_docs.py +10 -10
  13. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/domain_tests/test_aggregate.py +41 -0
  14. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence.py +3 -0
  15. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_postgres.py +104 -106
  16. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/system_tests/test_runner.py +17 -17
  17. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/system_tests/test_system.py +1 -4
  18. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/pyproject.toml +26 -43
  19. eventsourcing-9.3.0a1/eventsourcing/examples/contentmanagementsystem/test_system.py +0 -174
  20. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/LICENSE +0 -0
  21. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/README.md +0 -0
  22. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/__init__.py +0 -0
  23. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/cipher.py +0 -0
  24. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/compressor.py +0 -0
  25. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/dispatch.py +0 -0
  26. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/__init__.py +0 -0
  27. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate1/__init__.py +0 -0
  28. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate1/application.py +0 -0
  29. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate1/domainmodel.py +0 -0
  30. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate1/test_application.py +0 -0
  31. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate2/__init__.py +0 -0
  32. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate2/application.py +0 -0
  33. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate2/domainmodel.py +0 -0
  34. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate2/test_application.py +0 -0
  35. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate3/__init__.py +0 -0
  36. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate3/application.py +0 -0
  37. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate3/domainmodel.py +0 -0
  38. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate3/test_application.py +0 -0
  39. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate4/__init__.py +0 -0
  40. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate4/application.py +0 -0
  41. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate4/test_application.py +0 -0
  42. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate5/__init__.py +0 -0
  43. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate5/application.py +0 -0
  44. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate5/domainmodel.py +0 -0
  45. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate5/test_application.py +0 -0
  46. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6/__init__.py +0 -0
  47. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6/application.py +0 -0
  48. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6/domainmodel.py +0 -0
  49. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6/test_application.py +0 -0
  50. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6a/__init__.py +0 -0
  51. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6a/application.py +0 -0
  52. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6a/domainmodel.py +0 -0
  53. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate6a/test_application.py +0 -0
  54. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/__init__.py +0 -0
  55. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/application.py +0 -0
  56. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/domainmodel.py +0 -0
  57. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/persistence.py +0 -0
  58. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/test_application.py +0 -0
  59. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -0
  60. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -0
  61. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7a/__init__.py +0 -0
  62. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7a/application.py +0 -0
  63. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7a/domainmodel.py +0 -0
  64. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7a/test_application.py +0 -0
  65. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -0
  66. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/__init__.py +0 -0
  67. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/application.py +0 -0
  68. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/domainmodel.py +0 -0
  69. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/persistence.py +0 -0
  70. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/test_application.py +0 -0
  71. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -0
  72. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -0
  73. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/bankaccounts/__init__.py +0 -0
  74. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/bankaccounts/application.py +0 -0
  75. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/bankaccounts/domainmodel.py +0 -0
  76. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/bankaccounts/test.py +0 -0
  77. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/cargoshipping/__init__.py +0 -0
  78. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/cargoshipping/application.py +0 -0
  79. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/cargoshipping/domainmodel.py +0 -0
  80. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/cargoshipping/interface.py +0 -0
  81. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/cargoshipping/test.py +0 -0
  82. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagement/__init__.py +0 -0
  83. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagement/application.py +0 -0
  84. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagement/domainmodel.py +0 -0
  85. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagement/test.py +0 -0
  86. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagement/utils.py +0 -0
  87. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  88. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagementsystem/application.py +0 -0
  89. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagementsystem/postgres.py +0 -0
  90. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -0
  91. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/contentmanagementsystem/system.py +0 -0
  92. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/__init__.py +0 -0
  93. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/application.py +0 -0
  94. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/persistence.py +0 -0
  95. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/postgres.py +0 -0
  96. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchablecontent/sqlite.py +0 -0
  97. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  98. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchabletimestamps/application.py +0 -0
  99. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchabletimestamps/persistence.py +0 -0
  100. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchabletimestamps/postgres.py +0 -0
  101. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/searchabletimestamps/sqlite.py +0 -0
  102. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/test_invoice.py +0 -0
  103. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/examples/test_parking_lot.py +0 -0
  104. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/interface.py +0 -0
  105. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/persistence.py +0 -0
  106. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/popo.py +0 -0
  107. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/py.typed +0 -0
  108. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/sqlite.py +0 -0
  109. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/__init__.py +0 -0
  110. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application.py +0 -0
  111. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/__init__.py +0 -0
  112. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -0
  113. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_application_with_popo.py +0 -0
  114. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -0
  115. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -0
  116. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_cache.py +0 -0
  117. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -0
  118. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_notificationlog.py +0 -0
  119. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -0
  120. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_processapplication.py +0 -0
  121. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_processingpolicy.py +0 -0
  122. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_repository.py +0 -0
  123. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_snapshotting.py +0 -0
  124. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/application_tests/test_upcasting.py +0 -0
  125. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/docs_tests/__init__.py +0 -0
  126. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/domain.py +0 -0
  127. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/domain_tests/__init__.py +0 -0
  128. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -0
  129. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/domain_tests/test_domainevent.py +0 -0
  130. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/interface_tests/__init__.py +0 -0
  131. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -0
  132. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/__init__.py +0 -0
  133. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_aes.py +0 -0
  134. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -0
  135. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_eventstore.py +0 -0
  136. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -0
  137. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_mapper.py +0 -0
  138. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -0
  139. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_popo.py +0 -0
  140. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_sqlite.py +0 -0
  141. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/persistence_tests/test_transcoder.py +0 -0
  142. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/postgres_utils.py +0 -0
  143. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/system_tests/__init__.py +0 -0
  144. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/utils_tests/__init__.py +0 -0
  145. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/tests/utils_tests/test_utils.py +0 -0
  146. {eventsourcing-9.3.0a1 → eventsourcing-9.3.1}/eventsourcing/utils.py +0 -0
@@ -0,0 +1,10 @@
1
+ John Bywater
2
+ Denis Kyorov
3
+ Chris May
4
+ Bo Jin
5
+ Leon Harris
6
+ Julian Pistorius
7
+ Lukasz Balcerzak
8
+ James Rivett-Carnac
9
+ Vladimir Nani
10
+ Russ Ferriday
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eventsourcing
3
- Version: 9.3.0a1
3
+ Version: 9.3.1
4
4
  Summary: Event sourcing in Python
5
5
  Home-page: https://github.com/pyeventsourcing/eventsourcing
6
6
  License: BSD 3-Clause
@@ -8,7 +8,7 @@ Keywords: event sourcing,event store,domain driven design,domain-driven design,d
8
8
  Author: John Bywater
9
9
  Author-email: john.bywater@appropriatesoftware.net
10
10
  Requires-Python: >=3.8,<4.0
11
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Intended Audience :: Education
14
14
  Classifier: Intended Audience :: Science/Research
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.9
22
22
  Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
25
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
27
  Provides-Extra: crypto
27
28
  Provides-Extra: docs
@@ -29,11 +30,11 @@ Provides-Extra: postgres
29
30
  Requires-Dist: Sphinx ; extra == "docs"
30
31
  Requires-Dist: backports.zoneinfo ; python_version < "3.9"
31
32
  Requires-Dist: orjson ; extra == "docs"
32
- Requires-Dist: psycopg[c,pool] (<=3.9.99999) ; extra == "postgres"
33
- Requires-Dist: pycryptodome (<=3.20.99999) ; extra == "crypto"
33
+ Requires-Dist: psycopg[c,pool] (<=3.2.1) ; (python_full_version <= "3.12.999999") and (extra == "postgres")
34
+ Requires-Dist: pycryptodome (>=3.20,<3.21) ; extra == "crypto"
34
35
  Requires-Dist: pydantic ; extra == "docs"
35
36
  Requires-Dist: sphinx_rtd_theme ; extra == "docs"
36
- Requires-Dist: typing_extensions ; python_version < "3.8"
37
+ Requires-Dist: typing_extensions
37
38
  Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
38
39
  Description-Content-Type: text/markdown
39
40
 
@@ -25,6 +25,8 @@ from typing import (
25
25
  )
26
26
  from warnings import warn
27
27
 
28
+ from typing_extensions import deprecated
29
+
28
30
  from eventsourcing.domain import (
29
31
  Aggregate,
30
32
  CanMutateProtocol,
@@ -908,7 +910,12 @@ class Application:
908
910
  TApplication = TypeVar("TApplication", bound=Application)
909
911
 
910
912
 
911
- class AggregateNotFoundError(EventSourcingError):
913
+ @deprecated("AggregateNotFound is deprecated, use AggregateNotFoundError instead")
914
+ class AggregateNotFound(EventSourcingError): # noqa: N818
915
+ pass
916
+
917
+
918
+ class AggregateNotFoundError(AggregateNotFound):
912
919
  """
913
920
  Raised when an :class:`~eventsourcing.domain.Aggregate`
914
921
  object is not found in a :class:`Repository`.
@@ -941,6 +941,7 @@ class MetaAggregate(type, Generic[TAggregate]):
941
941
  setattr(cls, base_event_name, base_event_cls)
942
942
 
943
943
  # Make sure all events defined on aggregate subclass the base event class.
944
+ created_event_classes: Dict[str, Type[CanInitAggregate]] = {}
944
945
  for name, value in tuple(cls.__dict__.items()):
945
946
  if name == base_event_name:
946
947
  # Don't subclass the base event class again.
@@ -955,31 +956,20 @@ class MetaAggregate(type, Generic[TAggregate]):
955
956
  ):
956
957
  sub_class = cls._define_event_class(name, (value, base_event_cls), None)
957
958
  setattr(cls, name, sub_class)
958
-
959
- # Identify or define the aggregate's "created" event class.
960
- created_event_class: Type[CanInitAggregate] | None = None
961
-
962
- # Has the "created" event class been indicated with '_created_event_class'.
963
- if "_created_event_class" in cls.__dict__:
964
- created_event_class = cls.__dict__["_created_event_class"]
965
- if isinstance(created_event_class, type) and issubclass(
966
- created_event_class, CanInitAggregate
967
- ):
968
- # We just subclassed the event classes, so reassign this.
969
- created_event_class = getattr(cls, created_event_class.__name__)
970
- assert created_event_class
971
- cls._created_event_class = created_event_class
972
- else:
973
- msg = (
974
- f"{created_event_class} not subclass of {CanInitAggregate.__name__}"
975
- )
976
- raise TypeError(msg)
959
+ for name, value in tuple(cls.__dict__.items()):
960
+ if isinstance(value, type) and issubclass(value, CanInitAggregate):
961
+ created_event_classes[name] = value
977
962
 
978
963
  # Disallow using both '_created_event_class' and 'created_event_name'.
964
+ created_event_class: Type[CanInitAggregate] | None = cls.__dict__.get(
965
+ "_created_event_class"
966
+ )
979
967
  if created_event_class and created_event_name:
980
968
  msg = "Can't use both '_created_event_class' and 'created_event_name'"
981
969
  raise TypeError(msg)
982
970
 
971
+ # Identify or define the aggregate's "created" event class.
972
+
983
973
  # Is the init method decorated with a CommandMethodDecorator?
984
974
  if isinstance(cls.__dict__.get("__init__"), CommandMethodDecorator):
985
975
  init_decorator: CommandMethodDecorator = cls.__dict__["__init__"]
@@ -987,31 +977,20 @@ class MetaAggregate(type, Generic[TAggregate]):
987
977
  # Set the original method on the class (un-decorate __init__).
988
978
  cls.__init__ = init_decorator.decorated_method # type: ignore
989
979
 
990
- # Disallow using both 'created_event_name' and '_created_event_class'.
980
+ # Disallow using both 'created_event_name' and decorator on __init__.
991
981
  if created_event_name:
992
982
  msg = "Can't use both 'created_event_name' and decorator on __init__"
993
983
  raise TypeError(msg)
984
+ # Disallow using both '_created_event_class' and decorator on __init__.
994
985
  if created_event_class:
995
986
  msg = "Can't use both '_created_event_class' and decorator on __init__"
996
987
  raise TypeError(msg)
997
988
 
998
- # Does the decorator specify a "create" event class?
989
+ # Does the decorator specify a "created" event class?
999
990
  if init_decorator.given_event_cls:
1000
- created_event_class = getattr(
1001
- cls, init_decorator.given_event_cls.__name__
991
+ created_event_class = cast(
992
+ Type[CanInitAggregate], init_decorator.given_event_cls
1002
993
  )
1003
- if isinstance(created_event_class, type) and issubclass(
1004
- created_event_class, CanInitAggregate
1005
- ):
1006
- assert created_event_class
1007
- cls._created_event_class = created_event_class
1008
- else:
1009
- msg = (
1010
- f"{created_event_class} not subclass of "
1011
- f"{CanInitAggregate.__name__}"
1012
- )
1013
- raise TypeError(msg)
1014
-
1015
994
  # Does the decorator specify a "created" event name?
1016
995
  elif init_decorator.event_cls_name:
1017
996
  created_event_name = init_decorator.event_cls_name
@@ -1028,65 +1007,85 @@ class MetaAggregate(type, Generic[TAggregate]):
1028
1007
  _init_mentions_id.add(cls)
1029
1008
  break
1030
1009
 
1031
- # If no "created" event class has been specified, find or create one.
1032
- if created_event_class is None:
1033
- # Discover all the "created" event classes already defined.
1034
- created_event_classes: Dict[str, Type[AggregateCreated]] = {}
1035
- for name, value in tuple(cls.__dict__.items()):
1036
- if isinstance(value, type) and issubclass(value, AggregateCreated):
1037
- created_event_classes[name] = value
1038
-
1039
- # Is a "created" event class already defined that matches the name?
1040
- if created_event_name in created_event_classes:
1041
- cls._created_event_class = created_event_classes[created_event_name]
1042
-
1043
- # If there is only one class defined, and we have no name, use it.
1044
- elif len(created_event_classes) == 1 and not created_event_name:
1045
- cls._created_event_class = next(iter(created_event_classes.values()))
1046
-
1047
- # If there are no "created" event classes already defined, or a name is
1048
- # specified that hasn't matched, then define a "created" event class.
1049
- elif len(created_event_classes) == 0 or created_event_name:
1050
- # If no "created" event name has been specified, use default name.
1051
- if not created_event_name:
1052
- # This is safe because len(created_event_classes) == 0.
1053
- created_event_name = "Created"
1054
-
1055
- # Disallow init method from having variable params if
1056
- # we are using it to define a "created" event class.
1010
+ if created_event_class:
1011
+ # Check specified "created" event class can init aggregate.
1012
+ if not issubclass(created_event_class, CanInitAggregate):
1013
+ msg = (
1014
+ f"{created_event_class} not subclass of {CanInitAggregate.__name__}"
1015
+ )
1016
+ raise TypeError(msg)
1017
+
1018
+ for sub_class in created_event_classes.values():
1019
+ if issubclass(sub_class, created_event_class):
1020
+ # We just subclassed the created event class, so reassign it.
1021
+ created_event_class = sub_class
1022
+
1023
+ # Is a "created" event class already defined that matches the name?
1024
+ elif created_event_name and created_event_name in created_event_classes:
1025
+ created_event_class = created_event_classes[created_event_name]
1026
+
1027
+ # If there is only one class defined, then use it.
1028
+ elif len(created_event_classes) == 1 and not created_event_name:
1029
+ created_event_class = next(iter(created_event_classes.values()))
1030
+
1031
+ # If there are no "created" event classes already defined, or a name is
1032
+ # specified that hasn't matched, then define a "created" event class.
1033
+ elif len(created_event_classes) == 0 or created_event_name:
1034
+
1035
+ # Decide the base classes for the new "created" event class.
1036
+ if created_event_name and len(created_event_classes) == 1:
1037
+ base_created_event_cls = next(iter(created_event_classes.values()))
1038
+ else:
1039
+ for base_cls in cls.__mro__:
1040
+ if base_cls is cls:
1041
+ continue
1042
+ base_created_event_cls = base_cls.__dict__.get(
1043
+ "_created_event_class",
1044
+ base_cls.__dict__.get("Created"),
1045
+ )
1046
+ if base_created_event_cls:
1047
+ break
1048
+ else: # pragma: no cover
1049
+ msg = "Can't decide base class for new 'created' event class"
1050
+ raise TypeError(msg)
1051
+
1052
+ if not created_event_name:
1053
+ created_event_name = base_created_event_cls.__name__
1054
+
1055
+ # Disallow init method from having variable params if
1056
+ # we are using it to define a "created" event class.
1057
+ try:
1058
+ init_method = cls.__dict__["__init__"]
1059
+ except KeyError:
1060
+ init_method = None
1061
+ else:
1057
1062
  try:
1058
- init_method = cls.__dict__["__init__"]
1059
- except KeyError:
1060
- init_method = None
1061
- else:
1062
- try:
1063
- _check_no_variable_params(init_method)
1064
- except TypeError:
1065
- raise
1066
-
1067
- # Define a "created" event class for this aggregate.
1068
- if issubclass(cls.Created, base_event_cls):
1069
- # Don't subclass from base event class twice.
1070
- bases: Tuple[Type[CanMutateAggregate], ...] = (cls.Created,)
1071
- else:
1072
- bases = (cls.Created, base_event_cls)
1073
- event_cls = cls._define_event_class(
1063
+ _check_no_variable_params(init_method)
1064
+ except TypeError:
1065
+ raise
1066
+
1067
+ # Define a "created" event class for this aggregate.
1068
+ if issubclass(base_created_event_cls, base_event_cls):
1069
+ # Don't subclass from base event class twice.
1070
+ bases: Tuple[Type[CanMutateAggregate], ...] = (base_created_event_cls,)
1071
+ else:
1072
+ bases = (base_created_event_cls, base_event_cls)
1073
+ created_event_class = cast(
1074
+ Type[CanInitAggregate],
1075
+ cls._define_event_class(
1074
1076
  created_event_name,
1075
1077
  bases,
1076
1078
  init_method,
1077
- )
1078
-
1079
- # Set the event class as an attribute of the aggregate class.
1080
- setattr(cls, created_event_name, event_cls)
1081
-
1082
- # Remember which is the "created" event class.
1083
- cls._created_event_class = cast(Type[AggregateCreated], event_cls)
1079
+ ),
1080
+ )
1081
+ # Set the event class as an attribute of the aggregate class.
1082
+ setattr(cls, created_event_name, created_event_class)
1084
1083
 
1084
+ if created_event_class:
1085
+ cls._created_event_class = created_event_class
1086
+ else:
1085
1087
  # Prepare to disallow ambiguity of choice between created event classes.
1086
- else:
1087
- aggregate_has_many_created_event_classes[cls] = list(
1088
- created_event_classes
1089
- )
1088
+ aggregate_has_many_created_event_classes[cls] = list(created_event_classes)
1090
1089
 
1091
1090
  # Prepare the subsequent event classes.
1092
1091
  for attr_name, attr_value in tuple(cls.__dict__.items()):
@@ -1,10 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import contextlib
4
- from collections import defaultdict
5
3
  from dataclasses import dataclass
6
4
  from datetime import datetime, timezone
7
- from typing import Any, ClassVar, Dict, Iterable, List, Type, TypeVar, cast
5
+ from typing import Any, Iterable, List, Type, TypeVar, cast
8
6
  from uuid import UUID, uuid4
9
7
 
10
8
  from eventsourcing.dispatch import singledispatchmethod
@@ -29,6 +27,7 @@ class Aggregate:
29
27
  id: UUID
30
28
  version: int
31
29
  created_on: datetime
30
+ _pending_events: List[DomainEvent]
32
31
 
33
32
  def __init__(self, event: DomainEvent):
34
33
  self.id = event.originator_id
@@ -47,15 +46,15 @@ class Aggregate:
47
46
  timestamp=event_class.create_timestamp(),
48
47
  )
49
48
  new_event = event_class(**kwargs)
50
- self.apply(new_event)
51
- self.pending_events.append(new_event)
49
+ self._apply(new_event)
50
+ self._pending_events.append(new_event)
52
51
 
53
52
  @singledispatchmethod
54
- def apply(self, event: DomainEvent) -> None:
53
+ def _apply(self, event: DomainEvent) -> None:
55
54
  """Applies event to aggregate."""
56
55
 
57
56
  def collect_events(self) -> List[DomainEvent]:
58
- events, self.pending_events = self.pending_events, []
57
+ events, self._pending_events = self._pending_events, []
59
58
  return events
60
59
 
61
60
  @classmethod
@@ -64,25 +63,12 @@ class Aggregate:
64
63
  _: TAggregate | None,
65
64
  events: Iterable[DomainEvent],
66
65
  ) -> TAggregate | None:
67
- aggregate = object.__new__(cls)
66
+ aggregate: TAggregate = object.__new__(cls)
67
+ aggregate._pending_events = []
68
68
  for event in events:
69
- aggregate.apply(event)
69
+ aggregate._apply(event)
70
70
  return aggregate
71
71
 
72
- @property
73
- def pending_events(self) -> List[DomainEvent]:
74
- return type(self).__pending_events[id(self)]
75
-
76
- @pending_events.setter
77
- def pending_events(self, pending_events: List[DomainEvent]) -> None:
78
- type(self).__pending_events[id(self)] = pending_events
79
-
80
- __pending_events: ClassVar[Dict[int, List[DomainEvent]]] = defaultdict(list)
81
-
82
- def __del__(self) -> None:
83
- with contextlib.suppress(KeyError):
84
- type(self).__pending_events.pop(id(self))
85
-
86
72
 
87
73
  class Dog(Aggregate):
88
74
  @dataclass(frozen=True)
@@ -102,27 +88,27 @@ class Dog(Aggregate):
102
88
  name=name,
103
89
  )
104
90
  dog = cast(Dog, cls.projector(None, [event]))
105
- dog.pending_events.append(event)
91
+ dog._pending_events.append(event)
106
92
  return dog
107
93
 
108
94
  def add_trick(self, trick: str) -> None:
109
95
  self.trigger_event(self.TrickAdded, trick=trick)
110
96
 
111
97
  @singledispatchmethod
112
- def apply(self, event: DomainEvent) -> None:
98
+ def _apply(self, event: DomainEvent) -> None:
113
99
  """Applies event to aggregate."""
114
100
 
115
- @apply.register(Registered)
101
+ @_apply.register(Registered)
116
102
  def _(self, event: Registered) -> None:
117
103
  super().__init__(event)
118
104
  self.name = event.name
119
105
  self.tricks: List[str] = []
120
106
 
121
- @apply.register(TrickAdded)
107
+ @_apply.register(TrickAdded)
122
108
  def _(self, event: TrickAdded) -> None:
123
109
  self.tricks.append(event.trick)
124
110
  self.version = event.originator_version
125
111
 
126
- @apply.register(Snapshot)
112
+ @_apply.register(Snapshot)
127
113
  def _(self, event: Snapshot) -> None:
128
114
  self.__dict__.update(event.state)
@@ -0,0 +1,180 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import ClassVar, Dict
4
+ from unittest import TestCase
5
+ from uuid import uuid4
6
+
7
+ from eventsourcing.examples.contentmanagement.application import (
8
+ ContentManagementApplication,
9
+ )
10
+ from eventsourcing.examples.contentmanagement.domainmodel import user_id_cvar
11
+ from eventsourcing.examples.contentmanagementsystem.application import (
12
+ SearchIndexApplication,
13
+ )
14
+ from eventsourcing.examples.contentmanagementsystem.system import (
15
+ ContentManagementSystem,
16
+ )
17
+ from eventsourcing.postgres import PostgresDatastore
18
+ from eventsourcing.system import SingleThreadedRunner
19
+ from eventsourcing.tests.postgres_utils import drop_postgres_table
20
+
21
+
22
+ class ContentManagementSystemTestCase(TestCase):
23
+ env: ClassVar[Dict[str, str]] = {}
24
+
25
+ def test_system(self) -> None:
26
+ with SingleThreadedRunner(
27
+ system=ContentManagementSystem(), env=self.env
28
+ ) as runner:
29
+
30
+ content_management_app = runner.get(ContentManagementApplication)
31
+ search_index_app = runner.get(SearchIndexApplication)
32
+
33
+ # Set user_id context variable.
34
+ user_id = uuid4()
35
+ user_id_cvar.set(user_id)
36
+
37
+ # Create empty pages.
38
+ content_management_app.create_page(title="Animals", slug="animals")
39
+ content_management_app.create_page(title="Plants", slug="plants")
40
+ content_management_app.create_page(title="Minerals", slug="minerals")
41
+
42
+ # Search, expect no results.
43
+ self.assertEqual(0, len(search_index_app.search("cat")))
44
+ self.assertEqual(0, len(search_index_app.search("rose")))
45
+ self.assertEqual(0, len(search_index_app.search("calcium")))
46
+
47
+ # Update the pages.
48
+ content_management_app.update_body(slug="animals", body="cat")
49
+ content_management_app.update_body(slug="plants", body="rose")
50
+ content_management_app.update_body(slug="minerals", body="calcium")
51
+
52
+ # Search for single words.
53
+ page_ids = search_index_app.search("cat")
54
+ self.assertEqual(1, len(page_ids))
55
+ page = content_management_app.get_page_by_id(page_ids[0])
56
+ self.assertEqual(page["slug"], "animals")
57
+ self.assertEqual(page["body"], "cat")
58
+
59
+ page_ids = search_index_app.search("rose")
60
+ self.assertEqual(1, len(page_ids))
61
+ page = content_management_app.get_page_by_id(page_ids[0])
62
+ self.assertEqual(page["slug"], "plants")
63
+ self.assertEqual(page["body"], "rose")
64
+
65
+ page_ids = search_index_app.search("calcium")
66
+ self.assertEqual(1, len(page_ids))
67
+ page = content_management_app.get_page_by_id(page_ids[0])
68
+ self.assertEqual(page["slug"], "minerals")
69
+ self.assertEqual(page["body"], "calcium")
70
+
71
+ self.assertEqual(len(search_index_app.search("dog")), 0)
72
+ self.assertEqual(len(search_index_app.search("bluebell")), 0)
73
+ self.assertEqual(len(search_index_app.search("zinc")), 0)
74
+
75
+ # Update the pages again.
76
+ content_management_app.update_body(slug="animals", body="cat dog zebra")
77
+ content_management_app.update_body(
78
+ slug="plants", body="bluebell rose jasmine"
79
+ )
80
+ content_management_app.update_body(
81
+ slug="minerals", body="iron zinc calcium"
82
+ )
83
+
84
+ # Search for single words.
85
+ page_ids = search_index_app.search("cat")
86
+ self.assertEqual(1, len(page_ids))
87
+ page = content_management_app.get_page_by_id(page_ids[0])
88
+ self.assertEqual(page["slug"], "animals")
89
+ self.assertEqual(page["body"], "cat dog zebra")
90
+
91
+ page_ids = search_index_app.search("rose")
92
+ self.assertEqual(1, len(page_ids))
93
+ page = content_management_app.get_page_by_id(page_ids[0])
94
+ self.assertEqual(page["slug"], "plants")
95
+ self.assertEqual(page["body"], "bluebell rose jasmine")
96
+
97
+ page_ids = search_index_app.search("calcium")
98
+ self.assertEqual(1, len(page_ids))
99
+ page = content_management_app.get_page_by_id(page_ids[0])
100
+ self.assertEqual(page["slug"], "minerals")
101
+ self.assertEqual(page["body"], "iron zinc calcium")
102
+
103
+ page_ids = search_index_app.search("dog")
104
+ self.assertEqual(1, len(page_ids))
105
+ page = content_management_app.get_page_by_id(page_ids[0])
106
+ self.assertEqual(page["slug"], "animals")
107
+ self.assertEqual(page["body"], "cat dog zebra")
108
+
109
+ page_ids = search_index_app.search("bluebell")
110
+ self.assertEqual(1, len(page_ids))
111
+ page = content_management_app.get_page_by_id(page_ids[0])
112
+ self.assertEqual(page["slug"], "plants")
113
+ self.assertEqual(page["body"], "bluebell rose jasmine")
114
+
115
+ page_ids = search_index_app.search("zinc")
116
+ self.assertEqual(1, len(page_ids))
117
+ page = content_management_app.get_page_by_id(page_ids[0])
118
+ self.assertEqual(page["slug"], "minerals")
119
+ self.assertEqual(page["body"], "iron zinc calcium")
120
+
121
+ # Search for multiple words in same page.
122
+ page_ids = search_index_app.search("dog cat")
123
+ self.assertEqual(1, len(page_ids))
124
+ page = content_management_app.get_page_by_id(page_ids[0])
125
+ self.assertEqual(page["slug"], "animals")
126
+ self.assertEqual(page["body"], "cat dog zebra")
127
+
128
+ # Search for multiple words in same page, expect no results.
129
+ page_ids = search_index_app.search("rose zebra")
130
+ self.assertEqual(0, len(page_ids))
131
+
132
+ # Search for alternative words, expect two results.
133
+ page_ids = search_index_app.search("rose OR zebra")
134
+ pages = [
135
+ content_management_app.get_page_by_id(page_id) for page_id in page_ids
136
+ ]
137
+ self.assertEqual(2, len(pages))
138
+ self.assertEqual(["animals", "plants"], sorted(p["slug"] for p in pages))
139
+
140
+
141
+ class TestWithSQLite(ContentManagementSystemTestCase):
142
+ env: ClassVar[Dict[str, str]] = {
143
+ "PERSISTENCE_MODULE": "eventsourcing.examples.contentmanagementsystem.sqlite",
144
+ "SQLITE_DBNAME": ":memory:",
145
+ }
146
+
147
+
148
+ class TestWithPostgres(ContentManagementSystemTestCase):
149
+ env: ClassVar[Dict[str, str]] = {
150
+ "PERSISTENCE_MODULE": "eventsourcing.examples.contentmanagementsystem.postgres",
151
+ "POSTGRES_DBNAME": "eventsourcing",
152
+ "POSTGRES_HOST": "127.0.0.1",
153
+ "POSTGRES_PORT": "5432",
154
+ "POSTGRES_USER": "eventsourcing",
155
+ "POSTGRES_PASSWORD": "eventsourcing",
156
+ }
157
+
158
+ def setUp(self) -> None:
159
+ super().setUp()
160
+ self.drop_tables()
161
+
162
+ def tearDown(self) -> None:
163
+ self.drop_tables()
164
+ super().tearDown()
165
+
166
+ def drop_tables(self) -> None:
167
+ with PostgresDatastore(
168
+ self.env["POSTGRES_DBNAME"],
169
+ self.env["POSTGRES_HOST"],
170
+ self.env["POSTGRES_PORT"],
171
+ self.env["POSTGRES_USER"],
172
+ self.env["POSTGRES_PASSWORD"],
173
+ ) as datastore:
174
+ drop_postgres_table(datastore, "public.contentmanagementapplication_events")
175
+ drop_postgres_table(datastore, "public.pages_projection_example")
176
+ drop_postgres_table(datastore, "public.searchindexapplication_events")
177
+ drop_postgres_table(datastore, "public.searchindexapplication_tracking")
178
+
179
+
180
+ del ContentManagementSystemTestCase
@@ -96,16 +96,15 @@ class TestWithPostgres(SearchableContentApplicationTestCase):
96
96
  super().tearDown()
97
97
 
98
98
  def drop_tables(self) -> None:
99
- db = PostgresDatastore(
99
+ with PostgresDatastore(
100
100
  os.environ["POSTGRES_DBNAME"],
101
101
  os.environ["POSTGRES_HOST"],
102
102
  os.environ["POSTGRES_PORT"],
103
103
  os.environ["POSTGRES_USER"],
104
104
  os.environ["POSTGRES_PASSWORD"],
105
- )
106
- drop_postgres_table(db, "public.searchablecontentapplication_events")
107
- drop_postgres_table(db, "public.pages_projection_example")
108
- db.close()
105
+ ) as datastore:
106
+ drop_postgres_table(datastore, "public.searchablecontentapplication_events")
107
+ drop_postgres_table(datastore, "public.pages_projection_example")
109
108
 
110
109
 
111
110
  del SearchableContentApplicationTestCase
@@ -54,16 +54,15 @@ class TestWithPostgres(SearchableContentRecorderTestCase):
54
54
  super().tearDown()
55
55
 
56
56
  def drop_tables(self) -> None:
57
- db = PostgresDatastore(
57
+ with PostgresDatastore(
58
58
  os.environ["POSTGRES_DBNAME"],
59
59
  os.environ["POSTGRES_HOST"],
60
60
  os.environ["POSTGRES_PORT"],
61
61
  os.environ["POSTGRES_USER"],
62
62
  os.environ["POSTGRES_PASSWORD"],
63
- )
64
- drop_postgres_table(db, "public.searchablecontentapplication_events")
65
- drop_postgres_table(db, "public.pages_projection_example")
66
- db.close()
63
+ ) as datastore:
64
+ drop_postgres_table(datastore, "public.searchablecontentapplication_events")
65
+ drop_postgres_table(datastore, "public.pages_projection_example")
67
66
 
68
67
 
69
68
  del SearchableContentRecorderTestCase
@@ -76,16 +76,19 @@ class WithPostgreSQL(SearchableTimestampsTestCase):
76
76
  super().tearDown()
77
77
 
78
78
  def drop_tables(self) -> None:
79
- db = PostgresDatastore(
79
+ with PostgresDatastore(
80
80
  os.environ["POSTGRES_DBNAME"],
81
81
  os.environ["POSTGRES_HOST"],
82
82
  os.environ["POSTGRES_PORT"],
83
83
  os.environ["POSTGRES_USER"],
84
84
  os.environ["POSTGRES_PASSWORD"],
85
- )
86
- drop_postgres_table(db, "public.searchabletimestampsapplication_events")
87
- drop_postgres_table(db, "public.searchabletimestampsapplication_timestamps")
88
- db.close()
85
+ ) as datastore:
86
+ drop_postgres_table(
87
+ datastore, "public.searchabletimestampsapplication_events"
88
+ )
89
+ drop_postgres_table(
90
+ datastore, "public.searchabletimestampsapplication_timestamps"
91
+ )
89
92
 
90
93
 
91
94
  del SearchableTimestampsTestCase