udata 12.0.2.dev15__py3-none-any.whl → 13.0.1.dev21__py3-none-any.whl

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 udata might be problematic. Click here for more details.

Files changed (258) hide show
  1. udata/api/__init__.py +1 -0
  2. udata/api_fields.py +10 -4
  3. udata/app.py +11 -10
  4. udata/auth/__init__.py +9 -10
  5. udata/auth/mails.py +137 -45
  6. udata/auth/views.py +5 -12
  7. udata/commands/__init__.py +2 -3
  8. udata/commands/info.py +1 -3
  9. udata/commands/tests/test_fixtures.py +6 -3
  10. udata/core/access_type/api.py +18 -0
  11. udata/core/access_type/constants.py +98 -0
  12. udata/core/access_type/models.py +44 -0
  13. udata/core/activity/models.py +1 -1
  14. udata/core/badges/models.py +1 -1
  15. udata/core/badges/tasks.py +35 -1
  16. udata/core/badges/tests/test_commands.py +2 -4
  17. udata/core/badges/tests/test_model.py +2 -2
  18. udata/core/badges/tests/test_tasks.py +55 -0
  19. udata/core/constants.py +1 -0
  20. udata/core/contact_point/models.py +8 -0
  21. udata/core/dataservices/api.py +3 -3
  22. udata/core/dataservices/apiv2.py +3 -1
  23. udata/core/dataservices/constants.py +0 -29
  24. udata/core/dataservices/models.py +44 -44
  25. udata/core/dataservices/rdf.py +2 -1
  26. udata/core/dataservices/search.py +5 -9
  27. udata/core/dataservices/tasks.py +33 -0
  28. udata/core/dataset/api_fields.py +11 -0
  29. udata/core/dataset/apiv2.py +11 -0
  30. udata/core/dataset/constants.py +0 -1
  31. udata/core/dataset/forms.py +29 -0
  32. udata/core/dataset/models.py +16 -4
  33. udata/core/dataset/rdf.py +2 -1
  34. udata/core/dataset/search.py +2 -2
  35. udata/core/dataset/tasks.py +86 -8
  36. udata/core/discussions/mails.py +63 -0
  37. udata/core/discussions/tasks.py +4 -18
  38. udata/core/metrics/__init__.py +0 -6
  39. udata/core/organization/api.py +3 -1
  40. udata/core/organization/mails.py +144 -0
  41. udata/core/organization/models.py +2 -1
  42. udata/core/organization/search.py +1 -1
  43. udata/core/organization/tasks.py +21 -49
  44. udata/core/pages/tests/test_api.py +0 -2
  45. udata/core/reuse/api.py +27 -1
  46. udata/core/reuse/mails.py +21 -0
  47. udata/core/reuse/models.py +10 -1
  48. udata/core/reuse/search.py +1 -1
  49. udata/core/reuse/tasks.py +2 -3
  50. udata/core/site/models.py +2 -6
  51. udata/core/spatial/tests/test_api.py +17 -20
  52. udata/core/spatial/tests/test_models.py +3 -3
  53. udata/core/user/mails.py +54 -0
  54. udata/core/user/models.py +2 -3
  55. udata/core/user/tasks.py +8 -23
  56. udata/core/user/tests/test_user_model.py +2 -6
  57. udata/entrypoints.py +0 -5
  58. udata/features/identicon/tests/test_backends.py +3 -13
  59. udata/forms/fields.py +3 -3
  60. udata/forms/widgets.py +2 -2
  61. udata/frontend/__init__.py +3 -32
  62. udata/harvest/actions.py +4 -9
  63. udata/harvest/api.py +5 -14
  64. udata/harvest/backends/__init__.py +20 -11
  65. udata/harvest/backends/base.py +2 -2
  66. udata/harvest/backends/ckan/harvesters.py +2 -1
  67. udata/harvest/backends/dcat.py +3 -0
  68. udata/harvest/backends/maaf.py +1 -0
  69. udata/harvest/commands.py +6 -4
  70. udata/harvest/forms.py +9 -6
  71. udata/harvest/tasks.py +3 -5
  72. udata/harvest/tests/ckan/test_ckan_backend.py +300 -337
  73. udata/harvest/tests/ckan/test_ckan_backend_errors.py +94 -99
  74. udata/harvest/tests/ckan/test_ckan_backend_filters.py +128 -122
  75. udata/harvest/tests/ckan/test_dkan_backend.py +39 -51
  76. udata/harvest/tests/dcat/datara--5a26b0f6-0ccf-46ad-ac58-734054b91977.rdf.xml +255 -0
  77. udata/harvest/tests/dcat/datara--f40c3860-7236-4b30-a141-23b8ae33f7b2.rdf.xml +289 -0
  78. udata/harvest/tests/factories.py +1 -1
  79. udata/harvest/tests/test_actions.py +11 -9
  80. udata/harvest/tests/test_api.py +4 -5
  81. udata/harvest/tests/test_base_backend.py +5 -4
  82. udata/harvest/tests/test_dcat_backend.py +50 -19
  83. udata/harvest/tests/test_models.py +2 -4
  84. udata/harvest/tests/test_notifications.py +2 -4
  85. udata/harvest/tests/test_tasks.py +2 -3
  86. udata/mail.py +90 -53
  87. udata/migrations/2025-01-05-dataservices-fields-changes.py +8 -14
  88. udata/migrations/2025-10-21-remove-ckan-harvest-modified-at.py +28 -0
  89. udata/migrations/2025-10-29-harvesters-sources-integrity.py +27 -0
  90. udata/mongo/taglist_field.py +3 -3
  91. udata/rdf.py +32 -15
  92. udata/sentry.py +3 -4
  93. udata/settings.py +7 -2
  94. udata/tags.py +5 -5
  95. udata/tasks.py +3 -3
  96. udata/templates/mail/message.html +65 -0
  97. udata/templates/mail/message.txt +16 -0
  98. udata/tests/__init__.py +40 -58
  99. udata/tests/api/__init__.py +87 -2
  100. udata/tests/api/test_activities_api.py +17 -23
  101. udata/tests/api/test_auth_api.py +2 -4
  102. udata/tests/api/test_contact_points.py +48 -54
  103. udata/tests/api/test_dataservices_api.py +57 -37
  104. udata/tests/api/test_datasets_api.py +146 -49
  105. udata/tests/api/test_me_api.py +4 -6
  106. udata/tests/api/test_organizations_api.py +19 -38
  107. udata/tests/api/test_reports_api.py +0 -4
  108. udata/tests/api/test_reuses_api.py +92 -19
  109. udata/tests/api/test_security_api.py +124 -0
  110. udata/tests/api/test_swagger.py +2 -3
  111. udata/tests/api/test_tags_api.py +6 -7
  112. udata/tests/api/test_transfer_api.py +0 -2
  113. udata/tests/api/test_user_api.py +8 -10
  114. udata/tests/apiv2/test_datasets.py +0 -4
  115. udata/tests/apiv2/test_me_api.py +0 -2
  116. udata/tests/apiv2/test_organizations.py +0 -2
  117. udata/tests/apiv2/test_swagger.py +2 -3
  118. udata/tests/apiv2/test_topics.py +0 -2
  119. udata/tests/cli/test_cli_base.py +14 -12
  120. udata/tests/cli/test_db_cli.py +51 -54
  121. udata/tests/contact_point/test_contact_point_models.py +2 -2
  122. udata/tests/dataservice/test_csv_adapter.py +2 -5
  123. udata/tests/dataservice/test_dataservice_rdf.py +8 -6
  124. udata/tests/dataservice/test_dataservice_tasks.py +36 -38
  125. udata/tests/dataset/test_csv_adapter.py +2 -5
  126. udata/tests/dataset/test_dataset_actions.py +2 -4
  127. udata/tests/dataset/test_dataset_commands.py +2 -4
  128. udata/tests/dataset/test_dataset_events.py +3 -3
  129. udata/tests/dataset/test_dataset_model.py +6 -7
  130. udata/tests/dataset/test_dataset_rdf.py +201 -12
  131. udata/tests/dataset/test_dataset_recommendations.py +2 -2
  132. udata/tests/dataset/test_dataset_tasks.py +66 -68
  133. udata/tests/dataset/test_resource_preview.py +39 -48
  134. udata/tests/dataset/test_transport_tasks.py +2 -2
  135. udata/tests/features/territories/__init__.py +0 -6
  136. udata/tests/features/territories/test_territories_api.py +25 -24
  137. udata/tests/forms/test_current_user_field.py +2 -2
  138. udata/tests/forms/test_dict_field.py +2 -4
  139. udata/tests/forms/test_extras_fields.py +2 -3
  140. udata/tests/forms/test_image_field.py +2 -2
  141. udata/tests/forms/test_model_field.py +2 -4
  142. udata/tests/forms/test_publish_as_field.py +2 -4
  143. udata/tests/forms/test_user_forms.py +26 -29
  144. udata/tests/frontend/test_auth.py +2 -3
  145. udata/tests/frontend/test_csv.py +5 -6
  146. udata/tests/frontend/test_error_handlers.py +2 -3
  147. udata/tests/frontend/test_hooks.py +5 -7
  148. udata/tests/frontend/test_markdown.py +3 -4
  149. udata/tests/helpers.py +2 -7
  150. udata/tests/metrics/test_metrics.py +52 -48
  151. udata/tests/metrics/test_tasks.py +154 -150
  152. udata/tests/organization/test_csv_adapter.py +2 -5
  153. udata/tests/organization/test_notifications.py +2 -4
  154. udata/tests/organization/test_organization_model.py +3 -4
  155. udata/tests/organization/test_organization_rdf.py +2 -8
  156. udata/tests/plugin.py +6 -110
  157. udata/tests/reuse/test_reuse_model.py +3 -4
  158. udata/tests/site/test_site_api.py +0 -2
  159. udata/tests/site/test_site_csv_exports.py +0 -2
  160. udata/tests/site/test_site_metrics.py +2 -4
  161. udata/tests/site/test_site_model.py +2 -2
  162. udata/tests/site/test_site_rdf.py +4 -7
  163. udata/tests/test_activity.py +3 -3
  164. udata/tests/test_api_fields.py +6 -9
  165. udata/tests/test_cors.py +0 -2
  166. udata/tests/test_dcat_commands.py +2 -3
  167. udata/tests/test_discussions.py +2 -7
  168. udata/tests/test_mail.py +150 -114
  169. udata/tests/test_migrations.py +413 -419
  170. udata/tests/test_model.py +10 -11
  171. udata/tests/test_notifications.py +2 -3
  172. udata/tests/test_owned.py +3 -3
  173. udata/tests/test_rdf.py +19 -15
  174. udata/tests/test_routing.py +5 -5
  175. udata/tests/test_storages.py +6 -5
  176. udata/tests/test_tags.py +2 -4
  177. udata/tests/test_topics.py +2 -4
  178. udata/tests/test_transfer.py +4 -5
  179. udata/tests/topic/test_topic_tasks.py +25 -27
  180. udata/tests/user/test_user_rdf.py +2 -8
  181. udata/tests/user/test_user_tasks.py +3 -5
  182. udata/tests/workers/test_jobs_commands.py +2 -2
  183. udata/tests/workers/test_tasks_routing.py +27 -27
  184. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  185. udata/translations/ar/LC_MESSAGES/udata.po +369 -435
  186. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  187. udata/translations/de/LC_MESSAGES/udata.po +371 -437
  188. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  189. udata/translations/es/LC_MESSAGES/udata.po +369 -435
  190. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  191. udata/translations/fr/LC_MESSAGES/udata.po +381 -447
  192. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  193. udata/translations/it/LC_MESSAGES/udata.po +371 -437
  194. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  195. udata/translations/pt/LC_MESSAGES/udata.po +371 -437
  196. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  197. udata/translations/sr/LC_MESSAGES/udata.po +372 -438
  198. udata/translations/udata.pot +379 -440
  199. udata/utils.py +14 -2
  200. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/METADATA +1 -2
  201. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/RECORD +205 -242
  202. udata/templates/mail/account_deleted.html +0 -5
  203. udata/templates/mail/account_deleted.txt +0 -6
  204. udata/templates/mail/account_inactivity.html +0 -40
  205. udata/templates/mail/account_inactivity.txt +0 -31
  206. udata/templates/mail/badge_added_association.html +0 -33
  207. udata/templates/mail/badge_added_association.txt +0 -11
  208. udata/templates/mail/badge_added_certified.html +0 -33
  209. udata/templates/mail/badge_added_certified.txt +0 -11
  210. udata/templates/mail/badge_added_company.html +0 -33
  211. udata/templates/mail/badge_added_company.txt +0 -11
  212. udata/templates/mail/badge_added_local_authority.html +0 -33
  213. udata/templates/mail/badge_added_local_authority.txt +0 -11
  214. udata/templates/mail/badge_added_public_service.html +0 -33
  215. udata/templates/mail/badge_added_public_service.txt +0 -11
  216. udata/templates/mail/discussion_closed.html +0 -47
  217. udata/templates/mail/discussion_closed.txt +0 -16
  218. udata/templates/mail/inactive_account_deleted.html +0 -5
  219. udata/templates/mail/inactive_account_deleted.txt +0 -6
  220. udata/templates/mail/membership_refused.html +0 -20
  221. udata/templates/mail/membership_refused.txt +0 -11
  222. udata/templates/mail/membership_request.html +0 -46
  223. udata/templates/mail/membership_request.txt +0 -12
  224. udata/templates/mail/new_discussion.html +0 -44
  225. udata/templates/mail/new_discussion.txt +0 -15
  226. udata/templates/mail/new_discussion_comment.html +0 -45
  227. udata/templates/mail/new_discussion_comment.txt +0 -16
  228. udata/templates/mail/new_member.html +0 -27
  229. udata/templates/mail/new_member.txt +0 -11
  230. udata/templates/mail/new_reuse.html +0 -37
  231. udata/templates/mail/new_reuse.txt +0 -9
  232. udata/templates/mail/test.html +0 -6
  233. udata/templates/mail/test.txt +0 -6
  234. udata/templates/mail/user_mail_card.html +0 -26
  235. udata/templates/security/email/base.html +0 -105
  236. udata/templates/security/email/base.txt +0 -6
  237. udata/templates/security/email/button.html +0 -3
  238. udata/templates/security/email/change_notice.html +0 -22
  239. udata/templates/security/email/change_notice.txt +0 -8
  240. udata/templates/security/email/confirmation_instructions.html +0 -20
  241. udata/templates/security/email/confirmation_instructions.txt +0 -7
  242. udata/templates/security/email/login_instructions.html +0 -19
  243. udata/templates/security/email/login_instructions.txt +0 -7
  244. udata/templates/security/email/reset_instructions.html +0 -24
  245. udata/templates/security/email/reset_instructions.txt +0 -9
  246. udata/templates/security/email/reset_notice.html +0 -11
  247. udata/templates/security/email/reset_notice.txt +0 -4
  248. udata/templates/security/email/welcome.html +0 -24
  249. udata/templates/security/email/welcome.txt +0 -9
  250. udata/templates/security/email/welcome_existing.html +0 -32
  251. udata/templates/security/email/welcome_existing.txt +0 -14
  252. udata/terms.md +0 -6
  253. udata/tests/frontend/__init__.py +0 -23
  254. udata/tests/metrics/conftest.py +0 -15
  255. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/WHEEL +0 -0
  256. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/entry_points.txt +0 -0
  257. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/licenses/LICENSE +0 -0
  258. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/top_level.txt +0 -0
@@ -3,8 +3,10 @@ from datetime import datetime
3
3
  from textwrap import dedent
4
4
 
5
5
  import pytest
6
+ from mongoengine.connection import get_db
6
7
 
7
8
  from udata import migrations
9
+ from udata.tests.api import PytestOnlyDBTestCase
8
10
  from udata.tests.helpers import assert_equal_dates
9
11
 
10
12
 
@@ -80,447 +82,439 @@ def mock(app, tmpdir, mocker):
80
82
  yield m
81
83
 
82
84
 
83
- @pytest.mark.parametrize(
84
- "args",
85
- [
86
- ("udata", "test.py"),
87
- ("udata", "test"),
88
- ("udata:test.py", None),
89
- ("udata:test.py", None),
90
- ],
91
- )
92
- def test_get_migration(args):
93
- migration = migrations.get(*args)
94
-
95
- assert isinstance(migration, migrations.Migration)
96
- assert migration.plugin == "udata"
97
- assert migration.filename == "test.py"
98
-
99
-
100
- def test_list_available_migrations(mock):
101
- mock.add_migration("udata", "01_core_migration.py")
102
- mock.add_migration("test", "02_test_migration.py")
103
- mock.add_migration("other", "03_other_migration.py")
104
- # Should not list `__*.py` files
105
- mock.add_migration("test", "__private.py")
106
- # Should not list migrations for disabled plugin
107
- mock.add_migration("disabled", "should_not_be_there.py", enable=False)
108
- # Should not fail on plugins without migrations dir
109
- mock.ensure_plugin("nomigrations")
110
-
111
- availables = migrations.list_available()
112
-
113
- assert len(availables) == 3
114
- assert availables == [
115
- migrations.Migration(p, f)
116
- for p, f in (
117
- ("udata", "01_core_migration.py"),
118
- ("test", "02_test_migration.py"),
119
- ("other", "03_other_migration.py"),
120
- )
121
- ]
122
-
123
-
124
- def test_get_record(db):
125
- inserted = {
126
- "plugin": "test",
127
- "filename": "filename.py",
128
- "ops": [
129
- {
130
- "date": datetime.utcnow(),
131
- "type": "migrate",
132
- "script": "script",
133
- "output": "output",
134
- "success": True,
135
- }
136
- ],
137
- }
138
- db.migrations.insert_one(inserted)
139
-
140
- record = migrations.get("test", "filename.py").record
141
-
142
- assert record["plugin"] == inserted["plugin"]
143
- assert record["filename"] == inserted["filename"]
144
-
145
- op = record["ops"][0]
146
- assert op["script"] == inserted["ops"][0]["script"]
147
- assert op["output"] == inserted["ops"][0]["output"]
148
- assert op["type"] == inserted["ops"][0]["type"]
149
- assert op["success"]
150
- assert_equal_dates(op["date"], inserted["ops"][0]["date"])
151
-
152
-
153
- def test_migration_execute(mock, db):
154
- mock.add_migration(
155
- "udata",
156
- "migration.py",
157
- """\
158
- import logging
159
-
160
- log = logging.getLogger(__name__)
85
+ class MigrationsTest(PytestOnlyDBTestCase):
86
+ @pytest.fixture
87
+ def db(self):
88
+ return get_db()
161
89
 
162
- def migrate(db):
163
- db.test.insert_one({'key': 'value'})
164
- log.info('test')
165
- """,
166
- )
167
-
168
- output = migrations.get("udata", "migration.py").execute()
169
-
170
- inserted = db.test.find_one()
171
- assert inserted is not None
172
- assert inserted["key"] == "value"
173
- assert output == [["info", "test"]]
174
-
175
- assert db.migrations.count_documents({}) == 1
176
- record = db.migrations.find_one()
177
- assert record["plugin"] == "udata"
178
- assert record["filename"] == "migration.py"
179
- assert len(record["ops"]) == 1
180
-
181
- op = record["ops"][0]
182
- assert op["type"] == "migrate"
183
- assert op["output"] == [["info", "test"]]
184
- assert op["state"] == {}
185
- assert isinstance(op["date"], datetime)
186
- assert op["success"]
187
-
188
-
189
- def test_migration_add_record(mock, db):
190
- mock.add_migration(
191
- "test",
192
- "filename.py",
193
- """\
194
- # whatever
195
-
196
- def migrate():
197
- pass
198
- """,
90
+ @pytest.mark.parametrize(
91
+ "args",
92
+ [
93
+ ("udata", "test.py"),
94
+ ("udata", "test"),
95
+ ("udata:test.py", None),
96
+ ("udata:test.py", None),
97
+ ],
199
98
  )
99
+ def test_get_migration(self, args):
100
+ migration = migrations.get(*args)
101
+
102
+ assert isinstance(migration, migrations.Migration)
103
+ assert migration.plugin == "udata"
104
+ assert migration.filename == "test.py"
105
+
106
+ def test_list_available_migrations(self, mock):
107
+ mock.add_migration("udata", "01_core_migration.py")
108
+ mock.add_migration("test", "02_test_migration.py")
109
+ mock.add_migration("other", "03_other_migration.py")
110
+ # Should not list `__*.py` files
111
+ mock.add_migration("test", "__private.py")
112
+ # Should not list migrations for disabled plugin
113
+ mock.add_migration("disabled", "should_not_be_there.py", enable=False)
114
+ # Should not fail on plugins without migrations dir
115
+ mock.ensure_plugin("nomigrations")
116
+
117
+ availables = migrations.list_available()
118
+
119
+ assert len(availables) == 3
120
+ assert availables == [
121
+ migrations.Migration(p, f)
122
+ for p, f in (
123
+ ("udata", "01_core_migration.py"),
124
+ ("test", "02_test_migration.py"),
125
+ ("other", "03_other_migration.py"),
126
+ )
127
+ ]
128
+
129
+ def test_get_record(self, db):
130
+ inserted = {
131
+ "plugin": "test",
132
+ "filename": "filename.py",
133
+ "ops": [
134
+ {
135
+ "date": datetime.utcnow(),
136
+ "type": "migrate",
137
+ "script": "script",
138
+ "output": "output",
139
+ "success": True,
140
+ }
141
+ ],
142
+ }
143
+ db.migrations.insert_one(inserted)
144
+
145
+ record = migrations.get("test", "filename.py").record
146
+
147
+ assert record["plugin"] == inserted["plugin"]
148
+ assert record["filename"] == inserted["filename"]
149
+
150
+ op = record["ops"][0]
151
+ assert op["script"] == inserted["ops"][0]["script"]
152
+ assert op["output"] == inserted["ops"][0]["output"]
153
+ assert op["type"] == inserted["ops"][0]["type"]
154
+ assert op["success"]
155
+ assert_equal_dates(op["date"], inserted["ops"][0]["date"])
156
+
157
+ def test_migration_execute(self, mock, db):
158
+ mock.add_migration(
159
+ "udata",
160
+ "migration.py",
161
+ """\
162
+ import logging
163
+
164
+ log = logging.getLogger(__name__)
165
+
166
+ def migrate(db):
167
+ db.test.insert_one({'key': 'value'})
168
+ log.info('test')
169
+ """,
170
+ )
200
171
 
201
- expected_output = [["info", "Recorded only"]]
202
-
203
- output = migrations.get("test", "filename.py").execute(recordonly=True)
204
-
205
- assert output == expected_output
206
-
207
- migration = db.migrations.find_one()
208
- assert migration["plugin"] == "test"
209
- assert migration["filename"] == "filename.py"
210
-
211
- op = migration["ops"][0]
212
- assert op["script"].startswith("# whatever\n")
213
- assert op["output"] == expected_output
214
- assert op["type"] == "migrate"
215
- assert op["success"]
216
-
172
+ output = migrations.get("udata", "migration.py").execute()
173
+
174
+ inserted = db.test.find_one()
175
+ assert inserted is not None
176
+ assert inserted["key"] == "value"
177
+ assert output == [["info", "test"]]
178
+
179
+ assert db.migrations.count_documents({}) == 1
180
+ record = db.migrations.find_one()
181
+ assert record["plugin"] == "udata"
182
+ assert record["filename"] == "migration.py"
183
+ assert len(record["ops"]) == 1
184
+
185
+ op = record["ops"][0]
186
+ assert op["type"] == "migrate"
187
+ assert op["output"] == [["info", "test"]]
188
+ assert op["state"] == {}
189
+ assert isinstance(op["date"], datetime)
190
+ assert op["success"]
191
+
192
+ def test_migration_add_record(self, mock, db):
193
+ mock.add_migration(
194
+ "test",
195
+ "filename.py",
196
+ """\
197
+ # whatever
198
+
199
+ def migrate():
200
+ pass
201
+ """,
202
+ )
217
203
 
218
- def test_record_migration(mock, db):
219
- mock.add_migration(
220
- "test",
221
- "filename.py",
222
- """\
223
- # whatever
204
+ expected_output = [["info", "Recorded only"]]
224
205
 
225
- def migrate():
226
- pass
227
- """,
228
- )
206
+ output = migrations.get("test", "filename.py").execute(recordonly=True)
229
207
 
230
- expected_output = [["info", "Recorded only"]]
208
+ assert output == expected_output
231
209
 
232
- output = migrations.get("test", "filename.py").execute(recordonly=True)
210
+ migration = db.migrations.find_one()
211
+ assert migration["plugin"] == "test"
212
+ assert migration["filename"] == "filename.py"
233
213
 
234
- assert output == expected_output
214
+ op = migration["ops"][0]
215
+ assert op["script"].startswith("# whatever\n")
216
+ assert op["output"] == expected_output
217
+ assert op["type"] == "migrate"
218
+ assert op["success"]
235
219
 
236
- migration = db.migrations.find_one()
237
- assert migration["plugin"] == "test"
238
- assert migration["filename"] == "filename.py"
220
+ def test_record_migration(self, mock, db):
221
+ mock.add_migration(
222
+ "test",
223
+ "filename.py",
224
+ """\
225
+ # whatever
239
226
 
240
- op = migration["ops"][0]
241
- assert op["script"].startswith("# whatever\n")
242
- assert op["output"] == expected_output
243
- assert op["type"] == "migrate"
244
- assert op["success"]
227
+ def migrate():
228
+ pass
229
+ """,
230
+ )
245
231
 
232
+ expected_output = [["info", "Recorded only"]]
246
233
 
247
- def test_execute_missing_plugin(db):
248
- with pytest.raises(migrations.MigrationError):
249
- migrations.get("test", "filename.py").execute()
250
- assert db.migrations.find_one() is None
234
+ output = migrations.get("test", "filename.py").execute(recordonly=True)
251
235
 
236
+ assert output == expected_output
252
237
 
253
- def test_execute_missing_migration(db, mock):
254
- mock.ensure_plugin("test")
255
- with pytest.raises(migrations.MigrationError):
256
- migrations.get("test", "filename.py").execute()
257
- assert db.migrations.find_one() is None
238
+ migration = db.migrations.find_one()
239
+ assert migration["plugin"] == "test"
240
+ assert migration["filename"] == "filename.py"
258
241
 
242
+ op = migration["ops"][0]
243
+ assert op["script"].startswith("# whatever\n")
244
+ assert op["output"] == expected_output
245
+ assert op["type"] == "migrate"
246
+ assert op["success"]
259
247
 
260
- def test_execute_migration_error(mock, db):
261
- mock.add_migration(
262
- "udata",
263
- "migration.py",
264
- """\
265
- import logging
248
+ def test_execute_missing_plugin(self, db):
249
+ with pytest.raises(migrations.MigrationError):
250
+ migrations.get("test", "filename.py").execute()
251
+ assert db.migrations.find_one() is None
266
252
 
267
- log = logging.getLogger(__name__)
253
+ def test_execute_missing_migration(self, db, mock):
254
+ mock.ensure_plugin("test")
255
+ with pytest.raises(migrations.MigrationError):
256
+ migrations.get("test", "filename.py").execute()
257
+ assert db.migrations.find_one() is None
268
258
 
269
- def migrate(db):
270
- db.test.insert_one({'key': 'value'})
271
- log.info('test')
272
- raise ValueError('error')
273
- """,
274
- )
259
+ def test_execute_migration_error(self, mock, db):
260
+ mock.add_migration(
261
+ "udata",
262
+ "migration.py",
263
+ """\
264
+ import logging
275
265
 
276
- with pytest.raises(migrations.MigrationError) as excinfo:
277
- migrations.get("udata", "migration.py").execute()
278
-
279
- exc = excinfo.value
280
- assert isinstance(exc, migrations.MigrationError)
281
- assert isinstance(exc.exc, ValueError)
282
- assert exc.msg == "Error while executing migration"
283
- assert exc.output == [["info", "test"]]
284
-
285
- # Without rollback DB is left as it is
286
- assert db.test.count_documents({}) == 1
287
- inserted = db.test.find_one()
288
- assert inserted is not None
289
- assert inserted["key"] == "value"
290
-
291
- # Failed migration is recorded
292
- assert db.migrations.count_documents({}) == 1
293
- record = db.migrations.find_one()
294
- assert record["plugin"] == "udata"
295
- assert record["filename"] == "migration.py"
296
- assert len(record["ops"]) == 1
297
-
298
- op = record["ops"][0]
299
- assert op["type"] == "migrate"
300
- assert op["output"] == [["info", "test"]]
301
- assert op["state"] == {}
302
- assert isinstance(op["date"], datetime)
303
- assert not op["success"]
304
-
305
-
306
- def test_execute_migration_error_with_rollback(mock, db):
307
- mock.add_migration(
308
- "udata",
309
- "migration.py",
310
- """\
311
- def migrate(db):
312
- db.test.insert_one({'key': 'value'})
313
- raise ValueError('error')
314
-
315
- def rollback(db):
316
- db.rollback_test.insert_one({'key': 'value'})
317
- """,
318
- )
266
+ log = logging.getLogger(__name__)
319
267
 
320
- with pytest.raises(migrations.MigrationError) as excinfo:
321
- migrations.get("udata", "migration.py").execute()
322
-
323
- exc = excinfo.value
324
- assert isinstance(exc, migrations.RollbackError)
325
- assert exc.exc is None
326
- assert exc.msg == "Error while executing migration, rollback has been applied"
327
-
328
- assert isinstance(exc.migrate_exc, migrations.MigrationError)
329
- assert isinstance(exc.migrate_exc.exc, ValueError)
330
- assert exc.migrate_exc.msg == "Error while executing migration"
331
-
332
- # Migrate value is inserted
333
- assert db.test.count_documents({}) == 1
334
- # Rollback should be executed
335
- assert db.rollback_test.count_documents({}) == 1
336
-
337
- # DB is rollbacked if possible
338
- assert db.migrations.count_documents({}) == 1
339
- record = db.migrations.find_one()
340
- assert record["plugin"] == "udata"
341
- assert record["filename"] == "migration.py"
342
- # Both failed migration and rollback are recorded
343
- assert len(record["ops"]) == 2
344
-
345
- # First the migration
346
- op = record["ops"][0]
347
- assert op["type"] == "migrate"
348
- assert op["output"] == []
349
- assert op["state"] == {}
350
- assert isinstance(op["date"], datetime)
351
- assert not op["success"]
352
-
353
- # Then the rollback
354
- op = record["ops"][1]
355
- assert op["type"] == "rollback"
356
- assert op["output"] == []
357
- assert op["state"] == {}
358
- assert isinstance(op["date"], datetime)
359
- assert op["success"]
360
-
361
-
362
- def test_execute_migration_error_with_state_rollback(mock, db):
363
- mock.add_migration(
364
- "udata",
365
- "migration.py",
366
- """\
367
- def migrate(db):
368
- db.test.insert_one({'key': 'first'})
369
- db._state['first'] = True
370
- raise ValueError('error')
371
- db.test.insert_two({'key': 'second'})
372
- db._state['second'] = True
373
-
374
- def rollback(db):
375
- if db._state.get('first', False):
376
- db.rollback_test.insert_one({'key': 'first'})
377
- if db._state.get('second', False):
378
- db.rollback_test.insert_one({'key': 'second'})
379
- """,
380
- )
268
+ def migrate(db):
269
+ db.test.insert_one({'key': 'value'})
270
+ log.info('test')
271
+ raise ValueError('error')
272
+ """,
273
+ )
381
274
 
382
- with pytest.raises(migrations.MigrationError) as excinfo:
383
- migrations.get("udata", "migration.py").execute()
384
-
385
- exc = excinfo.value
386
- assert isinstance(exc, migrations.RollbackError)
387
- assert exc.exc is None
388
- assert exc.msg == "Error while executing migration, rollback has been applied"
389
-
390
- assert isinstance(exc.migrate_exc, migrations.MigrationError)
391
- assert isinstance(exc.migrate_exc.exc, ValueError)
392
- assert exc.migrate_exc.msg == "Error while executing migration"
393
-
394
- # Only the first value is inserted
395
- assert db.test.count_documents({}) == 1
396
- # Only the first rollback operation is executed
397
- assert db.rollback_test.count_documents({}) == 1
398
-
399
- # DB is rollbacked if possible
400
- assert db.migrations.count_documents({}) == 1
401
- record = db.migrations.find_one()
402
- assert record["plugin"] == "udata"
403
- assert record["filename"] == "migration.py"
404
- # Both failed migration and rollback are recorded
405
- assert len(record["ops"]) == 2
406
-
407
- # First the migration
408
- op = record["ops"][0]
409
- assert op["type"] == "migrate"
410
- assert op["output"] == []
411
- assert op["state"] == {"first": True}
412
- assert isinstance(op["date"], datetime)
413
- assert not op["success"]
414
-
415
- # Then the rollback
416
- op = record["ops"][1]
417
- assert op["type"] == "rollback"
418
- assert op["output"] == []
419
- assert op["state"] == {"first": True}
420
- assert isinstance(op["date"], datetime)
421
- assert op["success"]
422
-
423
-
424
- def test_execute_migration_error_with_rollback_error(mock, db):
425
- mock.add_migration(
426
- "udata",
427
- "migration.py",
428
- """\
429
- def migrate(db):
430
- db.test.insert_one({'key': 'value'})
431
- raise ValueError('error')
432
-
433
- def rollback(db):
434
- db.rollback_test.insert_one({'key': 'value'})
435
- raise ValueError('error')
436
- """,
437
- )
275
+ with pytest.raises(migrations.MigrationError) as excinfo:
276
+ migrations.get("udata", "migration.py").execute()
277
+
278
+ exc = excinfo.value
279
+ assert isinstance(exc, migrations.MigrationError)
280
+ assert isinstance(exc.exc, ValueError)
281
+ assert exc.msg == "Error while executing migration"
282
+ assert exc.output == [["info", "test"]]
283
+
284
+ # Without rollback DB is left as it is
285
+ assert db.test.count_documents({}) == 1
286
+ inserted = db.test.find_one()
287
+ assert inserted is not None
288
+ assert inserted["key"] == "value"
289
+
290
+ # Failed migration is recorded
291
+ assert db.migrations.count_documents({}) == 1
292
+ record = db.migrations.find_one()
293
+ assert record["plugin"] == "udata"
294
+ assert record["filename"] == "migration.py"
295
+ assert len(record["ops"]) == 1
296
+
297
+ op = record["ops"][0]
298
+ assert op["type"] == "migrate"
299
+ assert op["output"] == [["info", "test"]]
300
+ assert op["state"] == {}
301
+ assert isinstance(op["date"], datetime)
302
+ assert not op["success"]
303
+
304
+ def test_execute_migration_error_with_rollback(self, mock, db):
305
+ mock.add_migration(
306
+ "udata",
307
+ "migration.py",
308
+ """\
309
+ def migrate(db):
310
+ db.test.insert_one({'key': 'value'})
311
+ raise ValueError('error')
312
+
313
+ def rollback(db):
314
+ db.rollback_test.insert_one({'key': 'value'})
315
+ """,
316
+ )
438
317
 
439
- with pytest.raises(migrations.MigrationError) as excinfo:
440
- migrations.get("udata", "migration.py").execute()
441
-
442
- exc = excinfo.value
443
- assert isinstance(exc, migrations.RollbackError)
444
- assert isinstance(exc.exc, ValueError)
445
- assert exc.msg == "Error while executing migration rollback"
446
-
447
- assert isinstance(exc.migrate_exc, migrations.MigrationError)
448
- assert isinstance(exc.migrate_exc.exc, ValueError)
449
- assert exc.migrate_exc.msg == "Error while executing migration"
450
-
451
- # Migrate value is inserted
452
- assert db.test.count_documents({}) == 1
453
- # Rollback should not be recorded
454
- assert db.rollback_test.count_documents({}) == 1
455
-
456
- # DB is rollbacked if possible
457
- assert db.migrations.count_documents({}) == 1
458
- record = db.migrations.find_one()
459
- assert record["plugin"] == "udata"
460
- assert record["filename"] == "migration.py"
461
- # Both failed migration and rollback are recorded
462
- assert len(record["ops"]) == 2
463
-
464
- # First the migration
465
- op = record["ops"][0]
466
- assert op["type"] == "migrate"
467
- assert op["output"] == []
468
- assert op["state"] == {}
469
- assert isinstance(op["date"], datetime)
470
- assert not op["success"]
471
-
472
- # Then the rollback
473
- op = record["ops"][1]
474
- assert op["type"] == "rollback"
475
- assert op["output"] == []
476
- assert op["state"] == {}
477
- assert isinstance(op["date"], datetime)
478
- assert not op["success"]
479
-
480
-
481
- def test_execute_migration_dry_run(mock, db):
482
- mock.add_migration(
483
- "udata",
484
- "migration.py",
485
- """\
486
- import logging
487
-
488
- log = logging.getLogger(__name__)
489
-
490
- def migrate(db):
491
- db.test.insert_one({'key': 'value'})
492
- log.info('test')
493
- """,
494
- )
318
+ with pytest.raises(migrations.MigrationError) as excinfo:
319
+ migrations.get("udata", "migration.py").execute()
320
+
321
+ exc = excinfo.value
322
+ assert isinstance(exc, migrations.RollbackError)
323
+ assert exc.exc is None
324
+ assert exc.msg == "Error while executing migration, rollback has been applied"
325
+
326
+ assert isinstance(exc.migrate_exc, migrations.MigrationError)
327
+ assert isinstance(exc.migrate_exc.exc, ValueError)
328
+ assert exc.migrate_exc.msg == "Error while executing migration"
329
+
330
+ # Migrate value is inserted
331
+ assert db.test.count_documents({}) == 1
332
+ # Rollback should be executed
333
+ assert db.rollback_test.count_documents({}) == 1
334
+
335
+ # DB is rollbacked if possible
336
+ assert db.migrations.count_documents({}) == 1
337
+ record = db.migrations.find_one()
338
+ assert record["plugin"] == "udata"
339
+ assert record["filename"] == "migration.py"
340
+ # Both failed migration and rollback are recorded
341
+ assert len(record["ops"]) == 2
342
+
343
+ # First the migration
344
+ op = record["ops"][0]
345
+ assert op["type"] == "migrate"
346
+ assert op["output"] == []
347
+ assert op["state"] == {}
348
+ assert isinstance(op["date"], datetime)
349
+ assert not op["success"]
350
+
351
+ # Then the rollback
352
+ op = record["ops"][1]
353
+ assert op["type"] == "rollback"
354
+ assert op["output"] == []
355
+ assert op["state"] == {}
356
+ assert isinstance(op["date"], datetime)
357
+ assert op["success"]
358
+
359
+ def test_execute_migration_error_with_state_rollback(self, mock, db):
360
+ mock.add_migration(
361
+ "udata",
362
+ "migration.py",
363
+ """\
364
+ def migrate(db):
365
+ db.test.insert_one({'key': 'first'})
366
+ db._state['first'] = True
367
+ raise ValueError('error')
368
+ db.test.insert_two({'key': 'second'})
369
+ db._state['second'] = True
370
+
371
+ def rollback(db):
372
+ if db._state.get('first', False):
373
+ db.rollback_test.insert_one({'key': 'first'})
374
+ if db._state.get('second', False):
375
+ db.rollback_test.insert_one({'key': 'second'})
376
+ """,
377
+ )
495
378
 
496
- output = migrations.get("udata", "migration.py").execute(dryrun=True)
497
-
498
- assert output == []
499
- assert db.test.find_one() is None
500
- assert db.migrations.count_documents({}) == 0
501
-
502
-
503
- def test_unrecord_migration(db):
504
- inserted = {
505
- "plugin": "test",
506
- "filename": "filename.py",
507
- "ops": [
508
- {
509
- "date": datetime.utcnow(),
510
- "type": "migrate",
511
- "script": "script",
512
- "output": "output",
513
- "state": {},
514
- "success": True,
515
- }
516
- ],
517
- }
518
- db.migrations.insert_one(inserted)
379
+ with pytest.raises(migrations.MigrationError) as excinfo:
380
+ migrations.get("udata", "migration.py").execute()
381
+
382
+ exc = excinfo.value
383
+ assert isinstance(exc, migrations.RollbackError)
384
+ assert exc.exc is None
385
+ assert exc.msg == "Error while executing migration, rollback has been applied"
386
+
387
+ assert isinstance(exc.migrate_exc, migrations.MigrationError)
388
+ assert isinstance(exc.migrate_exc.exc, ValueError)
389
+ assert exc.migrate_exc.msg == "Error while executing migration"
390
+
391
+ # Only the first value is inserted
392
+ assert db.test.count_documents({}) == 1
393
+ # Only the first rollback operation is executed
394
+ assert db.rollback_test.count_documents({}) == 1
395
+
396
+ # DB is rollbacked if possible
397
+ assert db.migrations.count_documents({}) == 1
398
+ record = db.migrations.find_one()
399
+ assert record["plugin"] == "udata"
400
+ assert record["filename"] == "migration.py"
401
+ # Both failed migration and rollback are recorded
402
+ assert len(record["ops"]) == 2
403
+
404
+ # First the migration
405
+ op = record["ops"][0]
406
+ assert op["type"] == "migrate"
407
+ assert op["output"] == []
408
+ assert op["state"] == {"first": True}
409
+ assert isinstance(op["date"], datetime)
410
+ assert not op["success"]
411
+
412
+ # Then the rollback
413
+ op = record["ops"][1]
414
+ assert op["type"] == "rollback"
415
+ assert op["output"] == []
416
+ assert op["state"] == {"first": True}
417
+ assert isinstance(op["date"], datetime)
418
+ assert op["success"]
419
+
420
+ def test_execute_migration_error_with_rollback_error(self, mock, db):
421
+ mock.add_migration(
422
+ "udata",
423
+ "migration.py",
424
+ """\
425
+ def migrate(db):
426
+ db.test.insert_one({'key': 'value'})
427
+ raise ValueError('error')
428
+
429
+ def rollback(db):
430
+ db.rollback_test.insert_one({'key': 'value'})
431
+ raise ValueError('error')
432
+ """,
433
+ )
519
434
 
520
- migration = migrations.get("test", "filename.py")
435
+ with pytest.raises(migrations.MigrationError) as excinfo:
436
+ migrations.get("udata", "migration.py").execute()
437
+
438
+ exc = excinfo.value
439
+ assert isinstance(exc, migrations.RollbackError)
440
+ assert isinstance(exc.exc, ValueError)
441
+ assert exc.msg == "Error while executing migration rollback"
442
+
443
+ assert isinstance(exc.migrate_exc, migrations.MigrationError)
444
+ assert isinstance(exc.migrate_exc.exc, ValueError)
445
+ assert exc.migrate_exc.msg == "Error while executing migration"
446
+
447
+ # Migrate value is inserted
448
+ assert db.test.count_documents({}) == 1
449
+ # Rollback should not be recorded
450
+ assert db.rollback_test.count_documents({}) == 1
451
+
452
+ # DB is rollbacked if possible
453
+ assert db.migrations.count_documents({}) == 1
454
+ record = db.migrations.find_one()
455
+ assert record["plugin"] == "udata"
456
+ assert record["filename"] == "migration.py"
457
+ # Both failed migration and rollback are recorded
458
+ assert len(record["ops"]) == 2
459
+
460
+ # First the migration
461
+ op = record["ops"][0]
462
+ assert op["type"] == "migrate"
463
+ assert op["output"] == []
464
+ assert op["state"] == {}
465
+ assert isinstance(op["date"], datetime)
466
+ assert not op["success"]
467
+
468
+ # Then the rollback
469
+ op = record["ops"][1]
470
+ assert op["type"] == "rollback"
471
+ assert op["output"] == []
472
+ assert op["state"] == {}
473
+ assert isinstance(op["date"], datetime)
474
+ assert not op["success"]
475
+
476
+ def test_execute_migration_dry_run(self, mock, db):
477
+ mock.add_migration(
478
+ "udata",
479
+ "migration.py",
480
+ """\
481
+ import logging
482
+
483
+ log = logging.getLogger(__name__)
484
+
485
+ def migrate(db):
486
+ db.test.insert_one({'key': 'value'})
487
+ log.info('test')
488
+ """,
489
+ )
521
490
 
522
- # Remove the migration record, return True
523
- assert migration.unrecord()
524
- assert db.migrations.find_one() is None
525
- # Already removed, return False
526
- assert not migration.unrecord()
491
+ output = migrations.get("udata", "migration.py").execute(dryrun=True)
492
+
493
+ assert output == []
494
+ assert db.test.find_one() is None
495
+ assert db.migrations.count_documents({}) == 0
496
+
497
+ def test_unrecord_migration(self, db):
498
+ inserted = {
499
+ "plugin": "test",
500
+ "filename": "filename.py",
501
+ "ops": [
502
+ {
503
+ "date": datetime.utcnow(),
504
+ "type": "migrate",
505
+ "script": "script",
506
+ "output": "output",
507
+ "state": {},
508
+ "success": True,
509
+ }
510
+ ],
511
+ }
512
+ db.migrations.insert_one(inserted)
513
+
514
+ migration = migrations.get("test", "filename.py")
515
+
516
+ # Remove the migration record, return True
517
+ assert migration.unrecord()
518
+ assert db.migrations.find_one() is None
519
+ # Already removed, return False
520
+ assert not migration.unrecord()