udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30382__py2.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 (425) hide show
  1. tasks/__init__.py +109 -107
  2. tasks/helpers.py +18 -18
  3. udata/__init__.py +4 -4
  4. udata/admin/views.py +5 -5
  5. udata/api/__init__.py +135 -124
  6. udata/api/commands.py +45 -37
  7. udata/api/errors.py +5 -4
  8. udata/api/fields.py +23 -21
  9. udata/api/oauth2.py +55 -74
  10. udata/api/parsers.py +15 -15
  11. udata/api/signals.py +1 -1
  12. udata/api_fields.py +137 -89
  13. udata/app.py +56 -54
  14. udata/assets.py +5 -5
  15. udata/auth/__init__.py +37 -26
  16. udata/auth/forms.py +23 -15
  17. udata/auth/helpers.py +1 -1
  18. udata/auth/mails.py +3 -3
  19. udata/auth/password_validation.py +19 -15
  20. udata/auth/views.py +94 -68
  21. udata/commands/__init__.py +71 -69
  22. udata/commands/cache.py +7 -7
  23. udata/commands/db.py +201 -140
  24. udata/commands/dcat.py +36 -30
  25. udata/commands/fixtures.py +100 -84
  26. udata/commands/images.py +21 -20
  27. udata/commands/info.py +17 -20
  28. udata/commands/init.py +10 -10
  29. udata/commands/purge.py +12 -13
  30. udata/commands/serve.py +41 -29
  31. udata/commands/static.py +16 -18
  32. udata/commands/test.py +20 -20
  33. udata/commands/tests/fixtures.py +26 -24
  34. udata/commands/worker.py +31 -33
  35. udata/core/__init__.py +12 -12
  36. udata/core/activity/__init__.py +0 -1
  37. udata/core/activity/api.py +59 -49
  38. udata/core/activity/models.py +28 -26
  39. udata/core/activity/signals.py +1 -1
  40. udata/core/activity/tasks.py +16 -10
  41. udata/core/badges/api.py +6 -6
  42. udata/core/badges/commands.py +14 -13
  43. udata/core/badges/fields.py +8 -5
  44. udata/core/badges/forms.py +7 -4
  45. udata/core/badges/models.py +16 -31
  46. udata/core/badges/permissions.py +1 -3
  47. udata/core/badges/signals.py +2 -2
  48. udata/core/badges/tasks.py +3 -2
  49. udata/core/badges/tests/test_commands.py +10 -10
  50. udata/core/badges/tests/test_model.py +24 -31
  51. udata/core/contact_point/api.py +19 -18
  52. udata/core/contact_point/api_fields.py +21 -14
  53. udata/core/contact_point/factories.py +2 -2
  54. udata/core/contact_point/forms.py +7 -6
  55. udata/core/contact_point/models.py +3 -5
  56. udata/core/dataservices/api.py +26 -21
  57. udata/core/dataservices/factories.py +13 -11
  58. udata/core/dataservices/models.py +35 -40
  59. udata/core/dataservices/permissions.py +4 -4
  60. udata/core/dataservices/rdf.py +40 -17
  61. udata/core/dataservices/tasks.py +4 -3
  62. udata/core/dataset/actions.py +10 -10
  63. udata/core/dataset/activities.py +21 -23
  64. udata/core/dataset/api.py +321 -298
  65. udata/core/dataset/api_fields.py +443 -271
  66. udata/core/dataset/apiv2.py +305 -229
  67. udata/core/dataset/commands.py +38 -36
  68. udata/core/dataset/constants.py +61 -54
  69. udata/core/dataset/csv.py +70 -74
  70. udata/core/dataset/events.py +39 -32
  71. udata/core/dataset/exceptions.py +8 -4
  72. udata/core/dataset/factories.py +57 -65
  73. udata/core/dataset/forms.py +87 -63
  74. udata/core/dataset/models.py +336 -280
  75. udata/core/dataset/permissions.py +9 -6
  76. udata/core/dataset/preview.py +15 -17
  77. udata/core/dataset/rdf.py +156 -122
  78. udata/core/dataset/search.py +92 -77
  79. udata/core/dataset/signals.py +1 -1
  80. udata/core/dataset/tasks.py +63 -54
  81. udata/core/discussions/actions.py +5 -5
  82. udata/core/discussions/api.py +124 -120
  83. udata/core/discussions/factories.py +2 -2
  84. udata/core/discussions/forms.py +9 -7
  85. udata/core/discussions/metrics.py +1 -3
  86. udata/core/discussions/models.py +25 -24
  87. udata/core/discussions/notifications.py +18 -14
  88. udata/core/discussions/permissions.py +3 -3
  89. udata/core/discussions/signals.py +4 -4
  90. udata/core/discussions/tasks.py +24 -28
  91. udata/core/followers/api.py +32 -33
  92. udata/core/followers/models.py +9 -9
  93. udata/core/followers/signals.py +3 -3
  94. udata/core/jobs/actions.py +7 -7
  95. udata/core/jobs/api.py +99 -92
  96. udata/core/jobs/commands.py +48 -49
  97. udata/core/jobs/forms.py +11 -11
  98. udata/core/jobs/models.py +6 -6
  99. udata/core/metrics/__init__.py +2 -2
  100. udata/core/metrics/commands.py +34 -30
  101. udata/core/metrics/models.py +2 -4
  102. udata/core/metrics/signals.py +1 -1
  103. udata/core/metrics/tasks.py +3 -3
  104. udata/core/organization/activities.py +12 -15
  105. udata/core/organization/api.py +167 -174
  106. udata/core/organization/api_fields.py +183 -124
  107. udata/core/organization/apiv2.py +32 -32
  108. udata/core/organization/commands.py +20 -22
  109. udata/core/organization/constants.py +11 -11
  110. udata/core/organization/csv.py +17 -15
  111. udata/core/organization/factories.py +8 -11
  112. udata/core/organization/forms.py +32 -26
  113. udata/core/organization/metrics.py +2 -1
  114. udata/core/organization/models.py +87 -67
  115. udata/core/organization/notifications.py +18 -14
  116. udata/core/organization/permissions.py +10 -11
  117. udata/core/organization/rdf.py +14 -14
  118. udata/core/organization/search.py +30 -28
  119. udata/core/organization/signals.py +7 -7
  120. udata/core/organization/tasks.py +42 -61
  121. udata/core/owned.py +38 -27
  122. udata/core/post/api.py +82 -81
  123. udata/core/post/constants.py +8 -5
  124. udata/core/post/factories.py +4 -4
  125. udata/core/post/forms.py +13 -14
  126. udata/core/post/models.py +20 -22
  127. udata/core/post/tests/test_api.py +30 -32
  128. udata/core/reports/api.py +8 -7
  129. udata/core/reports/constants.py +1 -3
  130. udata/core/reports/models.py +10 -10
  131. udata/core/reuse/activities.py +15 -19
  132. udata/core/reuse/api.py +123 -126
  133. udata/core/reuse/api_fields.py +120 -85
  134. udata/core/reuse/apiv2.py +11 -10
  135. udata/core/reuse/constants.py +23 -23
  136. udata/core/reuse/csv.py +18 -18
  137. udata/core/reuse/factories.py +5 -9
  138. udata/core/reuse/forms.py +24 -21
  139. udata/core/reuse/models.py +55 -51
  140. udata/core/reuse/permissions.py +2 -2
  141. udata/core/reuse/search.py +49 -46
  142. udata/core/reuse/signals.py +1 -1
  143. udata/core/reuse/tasks.py +4 -5
  144. udata/core/site/api.py +47 -50
  145. udata/core/site/factories.py +2 -2
  146. udata/core/site/forms.py +4 -5
  147. udata/core/site/models.py +94 -63
  148. udata/core/site/rdf.py +14 -14
  149. udata/core/spam/api.py +16 -9
  150. udata/core/spam/constants.py +4 -4
  151. udata/core/spam/fields.py +13 -7
  152. udata/core/spam/models.py +27 -20
  153. udata/core/spam/signals.py +1 -1
  154. udata/core/spam/tests/test_spam.py +6 -5
  155. udata/core/spatial/api.py +72 -80
  156. udata/core/spatial/api_fields.py +73 -58
  157. udata/core/spatial/commands.py +67 -64
  158. udata/core/spatial/constants.py +3 -3
  159. udata/core/spatial/factories.py +37 -54
  160. udata/core/spatial/forms.py +27 -26
  161. udata/core/spatial/geoids.py +17 -17
  162. udata/core/spatial/models.py +43 -47
  163. udata/core/spatial/tasks.py +2 -1
  164. udata/core/spatial/tests/test_api.py +115 -130
  165. udata/core/spatial/tests/test_fields.py +74 -77
  166. udata/core/spatial/tests/test_geoid.py +22 -22
  167. udata/core/spatial/tests/test_models.py +5 -7
  168. udata/core/spatial/translations.py +16 -16
  169. udata/core/storages/__init__.py +16 -18
  170. udata/core/storages/api.py +66 -64
  171. udata/core/storages/tasks.py +7 -7
  172. udata/core/storages/utils.py +15 -15
  173. udata/core/storages/views.py +5 -6
  174. udata/core/tags/api.py +17 -14
  175. udata/core/tags/csv.py +4 -4
  176. udata/core/tags/models.py +8 -5
  177. udata/core/tags/tasks.py +11 -13
  178. udata/core/tags/views.py +4 -4
  179. udata/core/topic/api.py +84 -73
  180. udata/core/topic/apiv2.py +157 -127
  181. udata/core/topic/factories.py +3 -4
  182. udata/core/topic/forms.py +12 -14
  183. udata/core/topic/models.py +14 -19
  184. udata/core/topic/parsers.py +26 -26
  185. udata/core/user/activities.py +30 -29
  186. udata/core/user/api.py +151 -152
  187. udata/core/user/api_fields.py +132 -100
  188. udata/core/user/apiv2.py +7 -7
  189. udata/core/user/commands.py +38 -38
  190. udata/core/user/factories.py +8 -9
  191. udata/core/user/forms.py +14 -11
  192. udata/core/user/metrics.py +2 -2
  193. udata/core/user/models.py +68 -69
  194. udata/core/user/permissions.py +4 -5
  195. udata/core/user/rdf.py +7 -8
  196. udata/core/user/tasks.py +2 -2
  197. udata/core/user/tests/test_user_model.py +24 -16
  198. udata/db/tasks.py +2 -1
  199. udata/entrypoints.py +35 -31
  200. udata/errors.py +2 -1
  201. udata/event/values.py +6 -6
  202. udata/factories.py +2 -2
  203. udata/features/identicon/api.py +5 -6
  204. udata/features/identicon/backends.py +48 -55
  205. udata/features/identicon/tests/test_backends.py +4 -5
  206. udata/features/notifications/__init__.py +0 -1
  207. udata/features/notifications/actions.py +9 -9
  208. udata/features/notifications/api.py +17 -13
  209. udata/features/territories/__init__.py +12 -10
  210. udata/features/territories/api.py +14 -15
  211. udata/features/territories/models.py +23 -28
  212. udata/features/transfer/actions.py +8 -11
  213. udata/features/transfer/api.py +84 -77
  214. udata/features/transfer/factories.py +2 -1
  215. udata/features/transfer/models.py +11 -12
  216. udata/features/transfer/notifications.py +19 -15
  217. udata/features/transfer/permissions.py +5 -5
  218. udata/forms/__init__.py +5 -2
  219. udata/forms/fields.py +164 -172
  220. udata/forms/validators.py +19 -22
  221. udata/forms/widgets.py +9 -13
  222. udata/frontend/__init__.py +31 -26
  223. udata/frontend/csv.py +68 -58
  224. udata/frontend/markdown.py +40 -44
  225. udata/harvest/actions.py +89 -77
  226. udata/harvest/api.py +294 -238
  227. udata/harvest/backends/__init__.py +4 -4
  228. udata/harvest/backends/base.py +128 -111
  229. udata/harvest/backends/dcat.py +80 -66
  230. udata/harvest/commands.py +56 -60
  231. udata/harvest/csv.py +8 -8
  232. udata/harvest/exceptions.py +6 -3
  233. udata/harvest/filters.py +24 -23
  234. udata/harvest/forms.py +27 -28
  235. udata/harvest/models.py +88 -80
  236. udata/harvest/notifications.py +15 -10
  237. udata/harvest/signals.py +13 -13
  238. udata/harvest/tasks.py +11 -10
  239. udata/harvest/tests/factories.py +23 -24
  240. udata/harvest/tests/test_actions.py +136 -166
  241. udata/harvest/tests/test_api.py +220 -214
  242. udata/harvest/tests/test_base_backend.py +117 -112
  243. udata/harvest/tests/test_dcat_backend.py +380 -308
  244. udata/harvest/tests/test_filters.py +33 -22
  245. udata/harvest/tests/test_models.py +11 -14
  246. udata/harvest/tests/test_notifications.py +6 -7
  247. udata/harvest/tests/test_tasks.py +7 -6
  248. udata/i18n.py +237 -78
  249. udata/linkchecker/backends.py +5 -11
  250. udata/linkchecker/checker.py +23 -22
  251. udata/linkchecker/commands.py +4 -6
  252. udata/linkchecker/models.py +6 -6
  253. udata/linkchecker/tasks.py +18 -20
  254. udata/mail.py +21 -21
  255. udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
  256. udata/migrations/2020-08-24-add-fs-filename.py +9 -8
  257. udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
  258. udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
  259. udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
  260. udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
  261. udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
  262. udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
  263. udata/migrations/2021-08-17-follow-integrity.py +5 -4
  264. udata/migrations/2021-08-17-harvest-integrity.py +13 -12
  265. udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
  266. udata/migrations/2021-08-17-transfer-integrity.py +5 -4
  267. udata/migrations/2021-08-17-users-integrity.py +9 -8
  268. udata/migrations/2021-12-14-reuse-topics.py +7 -6
  269. udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
  270. udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
  271. udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
  272. udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
  273. udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
  274. udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
  275. udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
  276. udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
  277. udata/migrations/__init__.py +123 -105
  278. udata/models/__init__.py +4 -4
  279. udata/mongo/__init__.py +13 -11
  280. udata/mongo/badges_field.py +3 -2
  281. udata/mongo/datetime_fields.py +13 -12
  282. udata/mongo/document.py +17 -16
  283. udata/mongo/engine.py +15 -16
  284. udata/mongo/errors.py +2 -1
  285. udata/mongo/extras_fields.py +30 -20
  286. udata/mongo/queryset.py +12 -12
  287. udata/mongo/slug_fields.py +38 -28
  288. udata/mongo/taglist_field.py +1 -2
  289. udata/mongo/url_field.py +5 -5
  290. udata/mongo/uuid_fields.py +4 -3
  291. udata/notifications/__init__.py +1 -1
  292. udata/notifications/mattermost.py +10 -9
  293. udata/rdf.py +167 -188
  294. udata/routing.py +40 -45
  295. udata/search/__init__.py +18 -19
  296. udata/search/adapter.py +17 -16
  297. udata/search/commands.py +44 -51
  298. udata/search/fields.py +13 -20
  299. udata/search/query.py +23 -18
  300. udata/search/result.py +9 -10
  301. udata/sentry.py +21 -19
  302. udata/settings.py +262 -198
  303. udata/sitemap.py +8 -6
  304. udata/static/chunks/{11.e9b9ca1f3e03d4020377.js → 11.52e531c19f8de80c00cf.js} +3 -3
  305. udata/static/chunks/{11.e9b9ca1f3e03d4020377.js.map → 11.52e531c19f8de80c00cf.js.map} +1 -1
  306. udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js → 13.c3343a7f1070061c0e10.js} +2 -2
  307. udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js.map → 13.c3343a7f1070061c0e10.js.map} +1 -1
  308. udata/static/chunks/{16.0baa2b64a74a2dcde25c.js → 16.8fa42440ad75ca172e6d.js} +2 -2
  309. udata/static/chunks/{16.0baa2b64a74a2dcde25c.js.map → 16.8fa42440ad75ca172e6d.js.map} +1 -1
  310. udata/static/chunks/{19.350a9f150b074b4ecefa.js → 19.9c6c8412729cd6d59cfa.js} +3 -3
  311. udata/static/chunks/{19.350a9f150b074b4ecefa.js.map → 19.9c6c8412729cd6d59cfa.js.map} +1 -1
  312. udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js → 5.71d15c2e4f21feee2a9a.js} +3 -3
  313. udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js.map → 5.71d15c2e4f21feee2a9a.js.map} +1 -1
  314. udata/static/chunks/{6.d8a5f7b017bcbd083641.js → 6.9139dc098b8ea640b890.js} +3 -3
  315. udata/static/chunks/{6.d8a5f7b017bcbd083641.js.map → 6.9139dc098b8ea640b890.js.map} +1 -1
  316. udata/static/common.js +1 -1
  317. udata/static/common.js.map +1 -1
  318. udata/storage/s3.py +20 -13
  319. udata/tags.py +4 -5
  320. udata/tasks.py +43 -42
  321. udata/tests/__init__.py +9 -6
  322. udata/tests/api/__init__.py +5 -6
  323. udata/tests/api/test_auth_api.py +395 -321
  324. udata/tests/api/test_base_api.py +31 -33
  325. udata/tests/api/test_contact_points.py +7 -9
  326. udata/tests/api/test_dataservices_api.py +211 -158
  327. udata/tests/api/test_datasets_api.py +823 -812
  328. udata/tests/api/test_follow_api.py +13 -15
  329. udata/tests/api/test_me_api.py +95 -112
  330. udata/tests/api/test_organizations_api.py +301 -339
  331. udata/tests/api/test_reports_api.py +35 -25
  332. udata/tests/api/test_reuses_api.py +134 -139
  333. udata/tests/api/test_swagger.py +5 -5
  334. udata/tests/api/test_tags_api.py +18 -25
  335. udata/tests/api/test_topics_api.py +94 -94
  336. udata/tests/api/test_transfer_api.py +53 -48
  337. udata/tests/api/test_user_api.py +128 -141
  338. udata/tests/apiv2/test_datasets.py +290 -198
  339. udata/tests/apiv2/test_me_api.py +10 -11
  340. udata/tests/apiv2/test_organizations.py +56 -74
  341. udata/tests/apiv2/test_swagger.py +5 -5
  342. udata/tests/apiv2/test_topics.py +69 -87
  343. udata/tests/cli/test_cli_base.py +8 -8
  344. udata/tests/cli/test_db_cli.py +21 -19
  345. udata/tests/dataservice/test_dataservice_tasks.py +8 -12
  346. udata/tests/dataset/test_csv_adapter.py +44 -35
  347. udata/tests/dataset/test_dataset_actions.py +2 -3
  348. udata/tests/dataset/test_dataset_commands.py +7 -8
  349. udata/tests/dataset/test_dataset_events.py +36 -29
  350. udata/tests/dataset/test_dataset_model.py +224 -217
  351. udata/tests/dataset/test_dataset_rdf.py +142 -131
  352. udata/tests/dataset/test_dataset_tasks.py +15 -15
  353. udata/tests/dataset/test_resource_preview.py +10 -13
  354. udata/tests/features/territories/__init__.py +9 -13
  355. udata/tests/features/territories/test_territories_api.py +71 -91
  356. udata/tests/forms/test_basic_fields.py +7 -7
  357. udata/tests/forms/test_current_user_field.py +39 -66
  358. udata/tests/forms/test_daterange_field.py +31 -39
  359. udata/tests/forms/test_dict_field.py +28 -26
  360. udata/tests/forms/test_extras_fields.py +102 -76
  361. udata/tests/forms/test_form_field.py +8 -8
  362. udata/tests/forms/test_image_field.py +33 -26
  363. udata/tests/forms/test_model_field.py +134 -123
  364. udata/tests/forms/test_model_list_field.py +7 -7
  365. udata/tests/forms/test_nested_model_list_field.py +117 -79
  366. udata/tests/forms/test_publish_as_field.py +36 -65
  367. udata/tests/forms/test_reference_field.py +34 -53
  368. udata/tests/forms/test_user_forms.py +23 -21
  369. udata/tests/forms/test_uuid_field.py +6 -10
  370. udata/tests/frontend/__init__.py +9 -6
  371. udata/tests/frontend/test_auth.py +7 -6
  372. udata/tests/frontend/test_csv.py +81 -96
  373. udata/tests/frontend/test_hooks.py +43 -43
  374. udata/tests/frontend/test_markdown.py +211 -191
  375. udata/tests/helpers.py +32 -37
  376. udata/tests/models.py +2 -2
  377. udata/tests/organization/test_csv_adapter.py +21 -16
  378. udata/tests/organization/test_notifications.py +11 -18
  379. udata/tests/organization/test_organization_model.py +13 -13
  380. udata/tests/organization/test_organization_rdf.py +29 -22
  381. udata/tests/organization/test_organization_tasks.py +16 -17
  382. udata/tests/plugin.py +76 -73
  383. udata/tests/reuse/test_reuse_model.py +21 -21
  384. udata/tests/reuse/test_reuse_task.py +11 -13
  385. udata/tests/search/__init__.py +11 -12
  386. udata/tests/search/test_adapter.py +60 -70
  387. udata/tests/search/test_query.py +16 -16
  388. udata/tests/search/test_results.py +10 -7
  389. udata/tests/site/test_site_api.py +11 -16
  390. udata/tests/site/test_site_metrics.py +20 -30
  391. udata/tests/site/test_site_model.py +4 -5
  392. udata/tests/site/test_site_rdf.py +94 -78
  393. udata/tests/test_activity.py +17 -17
  394. udata/tests/test_discussions.py +292 -299
  395. udata/tests/test_i18n.py +37 -40
  396. udata/tests/test_linkchecker.py +91 -85
  397. udata/tests/test_mail.py +13 -17
  398. udata/tests/test_migrations.py +219 -180
  399. udata/tests/test_model.py +164 -157
  400. udata/tests/test_notifications.py +17 -17
  401. udata/tests/test_owned.py +14 -14
  402. udata/tests/test_rdf.py +25 -23
  403. udata/tests/test_routing.py +89 -93
  404. udata/tests/test_storages.py +137 -128
  405. udata/tests/test_tags.py +44 -46
  406. udata/tests/test_topics.py +7 -7
  407. udata/tests/test_transfer.py +42 -49
  408. udata/tests/test_uris.py +160 -161
  409. udata/tests/test_utils.py +79 -71
  410. udata/tests/user/test_user_rdf.py +5 -9
  411. udata/tests/workers/test_jobs_commands.py +57 -58
  412. udata/tests/workers/test_tasks_routing.py +23 -29
  413. udata/tests/workers/test_workers_api.py +125 -131
  414. udata/tests/workers/test_workers_helpers.py +6 -6
  415. udata/tracking.py +4 -6
  416. udata/uris.py +45 -46
  417. udata/utils.py +68 -66
  418. udata/wsgi.py +1 -1
  419. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/METADATA +3 -2
  420. udata-9.1.2.dev30382.dist-info/RECORD +704 -0
  421. udata-9.1.2.dev30355.dist-info/RECORD +0 -704
  422. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/LICENSE +0 -0
  423. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/WHEEL +0 -0
  424. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/entry_points.txt +0 -0
  425. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/top_level.txt +0 -0
udata/core/dataset/api.py CHANGED
@@ -1,4 +1,4 @@
1
- '''
1
+ """
2
2
  TODO: We need to cleanup and give more coherence to these APIs
3
3
  - upload enpoints should be singular instead of plural
4
4
  - community resource APIs should be consistent
@@ -15,34 +15,34 @@ TODO: We need to cleanup and give more coherence to these APIs
15
15
  These changes might lead to backward compatibility breakage meaning:
16
16
  - new API version
17
17
  - admin changes
18
- '''
18
+ """
19
19
 
20
- import os
21
20
  import logging
22
- import mongoengine
21
+ import os
23
22
  from datetime import datetime
24
23
 
24
+ import mongoengine
25
25
  from bson.objectid import ObjectId
26
- from flask import request, current_app, abort, redirect, url_for, make_response
26
+ from flask import abort, current_app, make_response, redirect, request, url_for
27
27
  from flask_security import current_user
28
28
  from mongoengine.queryset.visitor import Q
29
29
 
30
- from udata.auth import admin_permission
31
- from udata.api import api, API, errors
30
+ from udata.api import API, api, errors
32
31
  from udata.api.parsers import ModelApiParser
32
+ from udata.auth import admin_permission
33
33
  from udata.core import storages
34
- from udata.core.dataset.models import CHECKSUM_TYPES
35
- from udata.core.storages.api import handle_upload, upload_parser
36
34
  from udata.core.badges import api as badges_api
37
35
  from udata.core.badges.fields import badge_fields
36
+ from udata.core.dataset.models import CHECKSUM_TYPES
38
37
  from udata.core.followers.api import FollowAPI
38
+ from udata.core.storages.api import handle_upload, upload_parser
39
+ from udata.core.topic.models import Topic
40
+ from udata.linkchecker.checker import check_resource
41
+ from udata.rdf import RDF_EXTENSIONS, graph_response, negociate_content
39
42
  from udata.utils import get_by
40
- from udata.rdf import (
41
- RDF_EXTENSIONS,
42
- negociate_content, graph_response
43
- )
44
43
 
45
44
  from .api_fields import (
45
+ catalog_schema_fields,
46
46
  community_resource_fields,
47
47
  community_resource_page_fields,
48
48
  dataset_fields,
@@ -53,95 +53,97 @@ from .api_fields import (
53
53
  resource_fields,
54
54
  resource_type_fields,
55
55
  upload_fields,
56
- catalog_schema_fields,
57
56
  )
58
- from udata.linkchecker.checker import check_resource
59
- from udata.core.topic.models import Topic
57
+ from .constants import RESOURCE_TYPES, UPDATE_FREQUENCIES
58
+ from .exceptions import (
59
+ SchemasCacheUnavailableException,
60
+ SchemasCatalogNotFoundException,
61
+ )
62
+ from .forms import CommunityResourceForm, DatasetForm, ResourceForm, ResourcesListForm
60
63
  from .models import (
61
- Dataset, Resource, Checksum, License,
62
- CommunityResource, ResourceSchema, get_resource
64
+ Checksum,
65
+ CommunityResource,
66
+ Dataset,
67
+ License,
68
+ Resource,
69
+ ResourceSchema,
70
+ get_resource,
63
71
  )
64
- from .constants import UPDATE_FREQUENCIES, RESOURCE_TYPES
65
72
  from .permissions import DatasetEditPermission, ResourceEditPermission
66
- from .forms import (
67
- ResourceForm, DatasetForm, CommunityResourceForm, ResourcesListForm
68
- )
69
- from .exceptions import (
70
- SchemasCatalogNotFoundException, SchemasCacheUnavailableException
71
- )
72
73
  from .rdf import dataset_to_rdf
73
74
 
74
-
75
- DEFAULT_SORTING = '-created_at_internal'
76
- SUGGEST_SORTING = '-metrics.followers'
75
+ DEFAULT_SORTING = "-created_at_internal"
76
+ SUGGEST_SORTING = "-metrics.followers"
77
77
 
78
78
 
79
79
  class DatasetApiParser(ModelApiParser):
80
80
  sorts = {
81
- 'title': 'title',
82
- 'created': 'created_at_internal',
83
- 'last_update': 'last_modified_internal',
84
- 'reuses': 'metrics.reuses',
85
- 'followers': 'metrics.followers',
86
- 'views': 'metrics.views',
81
+ "title": "title",
82
+ "created": "created_at_internal",
83
+ "last_update": "last_modified_internal",
84
+ "reuses": "metrics.reuses",
85
+ "followers": "metrics.followers",
86
+ "views": "metrics.views",
87
87
  }
88
88
 
89
89
  def __init__(self):
90
90
  super().__init__()
91
- self.parser.add_argument('tag', type=str, location='args')
92
- self.parser.add_argument('license', type=str, location='args')
93
- self.parser.add_argument('featured', type=bool, location='args')
94
- self.parser.add_argument('geozone', type=str, location='args')
95
- self.parser.add_argument('granularity', type=str, location='args')
96
- self.parser.add_argument('temporal_coverage', type=str, location='args')
97
- self.parser.add_argument('organization', type=str, location='args')
98
- self.parser.add_argument('owner', type=str, location='args')
99
- self.parser.add_argument('format', type=str, location='args')
100
- self.parser.add_argument('schema', type=str, location='args')
101
- self.parser.add_argument('schema_version', type=str, location='args')
102
- self.parser.add_argument('topic', type=str, location='args')
91
+ self.parser.add_argument("tag", type=str, location="args")
92
+ self.parser.add_argument("license", type=str, location="args")
93
+ self.parser.add_argument("featured", type=bool, location="args")
94
+ self.parser.add_argument("geozone", type=str, location="args")
95
+ self.parser.add_argument("granularity", type=str, location="args")
96
+ self.parser.add_argument("temporal_coverage", type=str, location="args")
97
+ self.parser.add_argument("organization", type=str, location="args")
98
+ self.parser.add_argument("owner", type=str, location="args")
99
+ self.parser.add_argument("format", type=str, location="args")
100
+ self.parser.add_argument("schema", type=str, location="args")
101
+ self.parser.add_argument("schema_version", type=str, location="args")
102
+ self.parser.add_argument("topic", type=str, location="args")
103
103
 
104
104
  @staticmethod
105
105
  def parse_filters(datasets, args):
106
- if args.get('q'):
106
+ if args.get("q"):
107
107
  # Following code splits the 'q' argument by spaces to surround
108
108
  # every word in it with quotes before rebuild it.
109
109
  # This allows the search_text method to tokenise with an AND
110
110
  # between tokens whereas an OR is used without it.
111
- phrase_query = ' '.join([f'"{elem}"' for elem in args['q'].split(' ')])
111
+ phrase_query = " ".join([f'"{elem}"' for elem in args["q"].split(" ")])
112
112
  datasets = datasets.search_text(phrase_query)
113
- if args.get('tag'):
114
- datasets = datasets.filter(tags=args['tag'])
115
- if args.get('license'):
116
- datasets = datasets.filter(license__in=License.objects.filter(id=args['license']))
117
- if args.get('geozone'):
118
- datasets = datasets.filter(spatial__zones=args['geozone'])
119
- if args.get('granularity'):
120
- datasets = datasets.filter(spatial__granularity=args['granularity'])
121
- if args.get('temporal_coverage'):
122
- datasets = datasets.filter(temporal_coverage__start__gte=args['temporal_coverage'][:9],
123
- temporal_coverage__start__lte=args['temporal_coverage'][11:])
124
- if args.get('featured'):
125
- datasets = datasets.filter(featured=args['featured'])
126
- if args.get('organization'):
127
- if not ObjectId.is_valid(args['organization']):
128
- api.abort(400, 'Organization arg must be an identifier')
129
- datasets = datasets.filter(organization=args['organization'])
130
- if args.get('owner'):
131
- if not ObjectId.is_valid(args['owner']):
132
- api.abort(400, 'Owner arg must be an identifier')
133
- datasets = datasets.filter(owner=args['owner'])
134
- if args.get('format'):
135
- datasets = datasets.filter(resources__format=args['format'])
136
- if args.get('schema'):
137
- datasets = datasets.filter(resources__schema__name=args['schema'])
138
- if args.get('schema_version'):
139
- datasets = datasets.filter(resources__schema__version=args['schema_version'])
140
- if args.get('topic'):
141
- if not ObjectId.is_valid(args['topic']):
142
- api.abort(400, 'Topic arg must be an identifier')
113
+ if args.get("tag"):
114
+ datasets = datasets.filter(tags=args["tag"])
115
+ if args.get("license"):
116
+ datasets = datasets.filter(license__in=License.objects.filter(id=args["license"]))
117
+ if args.get("geozone"):
118
+ datasets = datasets.filter(spatial__zones=args["geozone"])
119
+ if args.get("granularity"):
120
+ datasets = datasets.filter(spatial__granularity=args["granularity"])
121
+ if args.get("temporal_coverage"):
122
+ datasets = datasets.filter(
123
+ temporal_coverage__start__gte=args["temporal_coverage"][:9],
124
+ temporal_coverage__start__lte=args["temporal_coverage"][11:],
125
+ )
126
+ if args.get("featured"):
127
+ datasets = datasets.filter(featured=args["featured"])
128
+ if args.get("organization"):
129
+ if not ObjectId.is_valid(args["organization"]):
130
+ api.abort(400, "Organization arg must be an identifier")
131
+ datasets = datasets.filter(organization=args["organization"])
132
+ if args.get("owner"):
133
+ if not ObjectId.is_valid(args["owner"]):
134
+ api.abort(400, "Owner arg must be an identifier")
135
+ datasets = datasets.filter(owner=args["owner"])
136
+ if args.get("format"):
137
+ datasets = datasets.filter(resources__format=args["format"])
138
+ if args.get("schema"):
139
+ datasets = datasets.filter(resources__schema__name=args["schema"])
140
+ if args.get("schema_version"):
141
+ datasets = datasets.filter(resources__schema__version=args["schema_version"])
142
+ if args.get("topic"):
143
+ if not ObjectId.is_valid(args["topic"]):
144
+ api.abort(400, "Topic arg must be an identifier")
143
145
  try:
144
- topic = Topic.objects.get(id=args['topic'])
146
+ topic = Topic.objects.get(id=args["topic"])
145
147
  except Topic.DoesNotExist:
146
148
  pass
147
149
  else:
@@ -151,84 +153,84 @@ class DatasetApiParser(ModelApiParser):
151
153
 
152
154
  log = logging.getLogger(__name__)
153
155
 
154
- ns = api.namespace('datasets', 'Dataset related operations')
156
+ ns = api.namespace("datasets", "Dataset related operations")
155
157
 
156
158
  dataset_parser = DatasetApiParser()
157
159
 
158
160
  community_parser = api.parser()
159
161
  community_parser.add_argument(
160
- 'sort', type=str, default='-created_at_internal', location='args',
161
- help='The sorting attribute')
162
+ "sort", type=str, default="-created_at_internal", location="args", help="The sorting attribute"
163
+ )
162
164
  community_parser.add_argument(
163
- 'page', type=int, default=1, location='args', help='The page to fetch')
165
+ "page", type=int, default=1, location="args", help="The page to fetch"
166
+ )
164
167
  community_parser.add_argument(
165
- 'page_size', type=int, default=20, location='args',
166
- help='The page size to fetch')
168
+ "page_size", type=int, default=20, location="args", help="The page size to fetch"
169
+ )
167
170
  community_parser.add_argument(
168
- 'organization', type=str,
169
- help='Filter activities for that particular organization',
170
- location='args')
171
+ "organization",
172
+ type=str,
173
+ help="Filter activities for that particular organization",
174
+ location="args",
175
+ )
171
176
  community_parser.add_argument(
172
- 'dataset', type=str,
173
- help='Filter activities for that particular dataset',
174
- location='args')
177
+ "dataset", type=str, help="Filter activities for that particular dataset", location="args"
178
+ )
175
179
  community_parser.add_argument(
176
- 'owner', type=str,
177
- help='Filter activities for that particular user',
178
- location='args')
180
+ "owner", type=str, help="Filter activities for that particular user", location="args"
181
+ )
179
182
 
180
- common_doc = {
181
- 'params': {'dataset': 'The dataset ID or slug'}
182
- }
183
+ common_doc = {"params": {"dataset": "The dataset ID or slug"}}
183
184
 
184
185
 
185
- @ns.route('/', endpoint='datasets')
186
+ @ns.route("/", endpoint="datasets")
186
187
  class DatasetListAPI(API):
187
- '''Datasets collection endpoint'''
188
- @api.doc('list_datasets')
188
+ """Datasets collection endpoint"""
189
+
190
+ @api.doc("list_datasets")
189
191
  @api.expect(dataset_parser.parser)
190
192
  @api.marshal_with(dataset_page_fields)
191
193
  def get(self):
192
- '''List or search all datasets'''
194
+ """List or search all datasets"""
193
195
  args = dataset_parser.parse()
194
196
  datasets = Dataset.objects(archived=None, deleted=None, private=False)
195
197
  datasets = dataset_parser.parse_filters(datasets, args)
196
- sort = args['sort'] or ('$text_score' if args['q'] else None) or DEFAULT_SORTING
197
- return datasets.order_by(sort).paginate(args['page'], args['page_size'])
198
+ sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
199
+ return datasets.order_by(sort).paginate(args["page"], args["page_size"])
198
200
 
199
201
  @api.secure
200
- @api.doc('create_dataset', responses={400: 'Validation error'})
202
+ @api.doc("create_dataset", responses={400: "Validation error"})
201
203
  @api.expect(dataset_fields)
202
204
  @api.marshal_with(dataset_fields, code=201)
203
205
  def post(self):
204
- '''Create a new dataset'''
206
+ """Create a new dataset"""
205
207
  form = api.validate(DatasetForm)
206
208
  dataset = form.save()
207
209
  return dataset, 201
208
210
 
209
211
 
210
- @ns.route('/<dataset:dataset>/', endpoint='dataset', doc=common_doc)
211
- @api.response(404, 'Dataset not found')
212
- @api.response(410, 'Dataset has been deleted')
212
+ @ns.route("/<dataset:dataset>/", endpoint="dataset", doc=common_doc)
213
+ @api.response(404, "Dataset not found")
214
+ @api.response(410, "Dataset has been deleted")
213
215
  class DatasetAPI(API):
214
- @api.doc('get_dataset')
216
+ @api.doc("get_dataset")
215
217
  @api.marshal_with(dataset_fields)
216
218
  def get(self, dataset):
217
- '''Get a dataset given its identifier'''
219
+ """Get a dataset given its identifier"""
218
220
  if dataset.deleted and not DatasetEditPermission(dataset).can():
219
- api.abort(410, 'Dataset has been deleted')
221
+ api.abort(410, "Dataset has been deleted")
220
222
  return dataset
221
223
 
222
224
  @api.secure
223
- @api.doc('update_dataset')
225
+ @api.doc("update_dataset")
224
226
  @api.expect(dataset_fields)
225
227
  @api.marshal_with(dataset_fields)
226
228
  @api.response(400, errors.VALIDATION_ERROR)
227
229
  def put(self, dataset):
228
- '''Update a dataset given its identifier'''
229
- request_deleted = request.json.get('deleted', True)
230
+ """Update a dataset given its identifier"""
231
+ request_deleted = request.json.get("deleted", True)
230
232
  if dataset.deleted and request_deleted is not None:
231
- api.abort(410, 'Dataset has been deleted')
233
+ api.abort(410, "Dataset has been deleted")
232
234
  DatasetEditPermission(dataset).test()
233
235
  dataset.last_modified_internal = datetime.utcnow()
234
236
  form = api.validate(DatasetForm, dataset)
@@ -240,57 +242,57 @@ class DatasetAPI(API):
240
242
  api.abort(400, e.message)
241
243
 
242
244
  @api.secure
243
- @api.doc('delete_dataset')
244
- @api.response(204, 'Dataset deleted')
245
+ @api.doc("delete_dataset")
246
+ @api.response(204, "Dataset deleted")
245
247
  def delete(self, dataset):
246
- '''Delete a dataset given its identifier'''
248
+ """Delete a dataset given its identifier"""
247
249
  if dataset.deleted:
248
- api.abort(410, 'Dataset has been deleted')
250
+ api.abort(410, "Dataset has been deleted")
249
251
  DatasetEditPermission(dataset).test()
250
252
  dataset.deleted = datetime.utcnow()
251
253
  dataset.last_modified_internal = datetime.utcnow()
252
254
  dataset.save()
253
- return '', 204
255
+ return "", 204
254
256
 
255
257
 
256
- @ns.route('/<dataset:dataset>/featured/', endpoint='dataset_featured')
258
+ @ns.route("/<dataset:dataset>/featured/", endpoint="dataset_featured")
257
259
  @api.doc(**common_doc)
258
260
  class DatasetFeaturedAPI(API):
259
261
  @api.secure(admin_permission)
260
- @api.doc('feature_dataset')
262
+ @api.doc("feature_dataset")
261
263
  @api.marshal_with(dataset_fields)
262
264
  def post(self, dataset):
263
- '''Mark the dataset as featured'''
265
+ """Mark the dataset as featured"""
264
266
  dataset.featured = True
265
267
  dataset.save()
266
268
  return dataset
267
269
 
268
270
  @api.secure(admin_permission)
269
- @api.doc('unfeature_dataset')
271
+ @api.doc("unfeature_dataset")
270
272
  @api.marshal_with(dataset_fields)
271
273
  def delete(self, dataset):
272
- '''Unmark the dataset as featured'''
274
+ """Unmark the dataset as featured"""
273
275
  dataset.featured = False
274
276
  dataset.save()
275
277
  return dataset
276
278
 
277
279
 
278
- @ns.route('/<dataset:dataset>/rdf', endpoint='dataset_rdf', doc=common_doc)
279
- @api.response(404, 'Dataset not found')
280
- @api.response(410, 'Dataset has been deleted')
280
+ @ns.route("/<dataset:dataset>/rdf", endpoint="dataset_rdf", doc=common_doc)
281
+ @api.response(404, "Dataset not found")
282
+ @api.response(410, "Dataset has been deleted")
281
283
  class DatasetRdfAPI(API):
282
- @api.doc('rdf_dataset')
284
+ @api.doc("rdf_dataset")
283
285
  def get(self, dataset):
284
286
  format = RDF_EXTENSIONS[negociate_content()]
285
- url = url_for('api.dataset_rdf_format', dataset=dataset.id, format=format)
287
+ url = url_for("api.dataset_rdf_format", dataset=dataset.id, format=format)
286
288
  return redirect(url)
287
289
 
288
290
 
289
- @ns.route('/<dataset:dataset>/rdf.<format>', endpoint='dataset_rdf_format', doc=common_doc)
290
- @api.response(404, 'Dataset not found')
291
- @api.response(410, 'Dataset has been deleted')
291
+ @ns.route("/<dataset:dataset>/rdf.<format>", endpoint="dataset_rdf_format", doc=common_doc)
292
+ @api.response(404, "Dataset not found")
293
+ @api.response(410, "Dataset has been deleted")
292
294
  class DatasetRdfFormatAPI(API):
293
- @api.doc('rdf_dataset_format')
295
+ @api.doc("rdf_dataset_format")
294
296
  def get(self, dataset, format):
295
297
  if not DatasetEditPermission(dataset).can():
296
298
  if dataset.private:
@@ -304,58 +306,58 @@ class DatasetRdfFormatAPI(API):
304
306
  return make_response(*graph_response(resource, format))
305
307
 
306
308
 
307
- @ns.route('/badges/', endpoint='available_dataset_badges')
309
+ @ns.route("/badges/", endpoint="available_dataset_badges")
308
310
  class AvailableDatasetBadgesAPI(API):
309
- @api.doc('available_dataset_badges')
311
+ @api.doc("available_dataset_badges")
310
312
  def get(self):
311
- '''List all available dataset badges and their labels'''
313
+ """List all available dataset badges and their labels"""
312
314
  return Dataset.__badges__
313
315
 
314
316
 
315
- @ns.route('/<dataset:dataset>/badges/', endpoint='dataset_badges')
317
+ @ns.route("/<dataset:dataset>/badges/", endpoint="dataset_badges")
316
318
  class DatasetBadgesAPI(API):
317
- @api.doc('add_dataset_badge', **common_doc)
319
+ @api.doc("add_dataset_badge", **common_doc)
318
320
  @api.expect(badge_fields)
319
321
  @api.marshal_with(badge_fields)
320
322
  @api.secure(admin_permission)
321
323
  def post(self, dataset):
322
- '''Create a new badge for a given dataset'''
324
+ """Create a new badge for a given dataset"""
323
325
  return badges_api.add(dataset)
324
326
 
325
327
 
326
- @ns.route('/<dataset:dataset>/badges/<badge_kind>/', endpoint='dataset_badge')
328
+ @ns.route("/<dataset:dataset>/badges/<badge_kind>/", endpoint="dataset_badge")
327
329
  class DatasetBadgeAPI(API):
328
- @api.doc('delete_dataset_badge', **common_doc)
330
+ @api.doc("delete_dataset_badge", **common_doc)
329
331
  @api.secure(admin_permission)
330
332
  def delete(self, dataset, badge_kind):
331
- '''Delete a badge for a given dataset'''
333
+ """Delete a badge for a given dataset"""
332
334
  return badges_api.remove(dataset, badge_kind)
333
335
 
334
336
 
335
- @ns.route('/r/<uuid:id>', endpoint='resource_redirect')
337
+ @ns.route("/r/<uuid:id>", endpoint="resource_redirect")
336
338
  class ResourceRedirectAPI(API):
337
- @api.doc('redirect_resource', **common_doc)
339
+ @api.doc("redirect_resource", **common_doc)
338
340
  def get(self, id):
339
- '''
341
+ """
340
342
  Redirect to the latest version of a resource given its identifier.
341
- '''
343
+ """
342
344
  resource = get_resource(id)
343
345
  return redirect(resource.url.strip()) if resource else abort(404)
344
346
 
345
347
 
346
- @ns.route('/<dataset:dataset>/resources/', endpoint='resources')
348
+ @ns.route("/<dataset:dataset>/resources/", endpoint="resources")
347
349
  class ResourcesAPI(API):
348
350
  @api.secure
349
- @api.doc('create_resource', **common_doc, responses={400: 'Validation error'})
351
+ @api.doc("create_resource", **common_doc, responses={400: "Validation error"})
350
352
  @api.expect(resource_fields)
351
353
  @api.marshal_with(resource_fields, code=201)
352
354
  def post(self, dataset):
353
- '''Create a new resource for a given dataset'''
355
+ """Create a new resource for a given dataset"""
354
356
  ResourceEditPermission(dataset).test()
355
357
  form = api.validate(ResourceForm)
356
358
  resource = Resource()
357
- if form._fields.get('filetype').data != 'remote':
358
- api.abort(400, 'This endpoint only supports remote resources')
359
+ if form._fields.get("filetype").data != "remote":
360
+ api.abort(400, "This endpoint only supports remote resources")
359
361
  form.populate_obj(resource)
360
362
  dataset.add_resource(resource)
361
363
  dataset.last_modified_internal = datetime.utcnow()
@@ -363,17 +365,18 @@ class ResourcesAPI(API):
363
365
  return resource, 201
364
366
 
365
367
  @api.secure
366
- @api.doc('update_resources', **common_doc, responses={400: 'Validation error'})
368
+ @api.doc("update_resources", **common_doc, responses={400: "Validation error"})
367
369
  @api.expect([resource_fields])
368
370
  @api.marshal_list_with(resource_fields)
369
371
  def put(self, dataset):
370
- '''Reorder resources'''
372
+ """Reorder resources"""
371
373
  ResourceEditPermission(dataset).test()
372
- data = {'resources': request.json}
373
- form = ResourcesListForm.from_json(data, obj=dataset, instance=dataset,
374
- meta={'csrf': False})
374
+ data = {"resources": request.json}
375
+ form = ResourcesListForm.from_json(
376
+ data, obj=dataset, instance=dataset, meta={"csrf": False}
377
+ )
375
378
  if not form.validate():
376
- api.abort(400, errors=form.errors['resources'])
379
+ api.abort(400, errors=form.errors["resources"])
377
380
 
378
381
  dataset = form.save()
379
382
  return dataset.resources, 200
@@ -381,29 +384,32 @@ class ResourcesAPI(API):
381
384
 
382
385
  class UploadMixin(object):
383
386
  def handle_upload(self, dataset):
384
- prefix = '/'.join((dataset.slug,
385
- datetime.utcnow().strftime('%Y%m%d-%H%M%S')))
387
+ prefix = "/".join((dataset.slug, datetime.utcnow().strftime("%Y%m%d-%H%M%S")))
386
388
  infos = handle_upload(storages.resources, prefix)
387
- if 'html' in infos['mime']:
388
- api.abort(415, 'Incorrect file content type: HTML')
389
- infos['title'] = os.path.basename(infos['filename'])
390
- checksum_type = next(checksum_type for checksum_type in CHECKSUM_TYPES
391
- if checksum_type in infos)
392
- infos['checksum'] = Checksum(type=checksum_type, value=infos.pop(checksum_type))
393
- infos['filesize'] = infos.pop('size')
394
- del infos['filename']
389
+ if "html" in infos["mime"]:
390
+ api.abort(415, "Incorrect file content type: HTML")
391
+ infos["title"] = os.path.basename(infos["filename"])
392
+ checksum_type = next(
393
+ checksum_type for checksum_type in CHECKSUM_TYPES if checksum_type in infos
394
+ )
395
+ infos["checksum"] = Checksum(type=checksum_type, value=infos.pop(checksum_type))
396
+ infos["filesize"] = infos.pop("size")
397
+ del infos["filename"]
395
398
  return infos
396
399
 
397
400
 
398
- @ns.route('/<dataset:dataset>/upload/', endpoint='upload_new_dataset_resource')
401
+ @ns.route("/<dataset:dataset>/upload/", endpoint="upload_new_dataset_resource")
399
402
  @api.doc(**common_doc)
400
403
  class UploadNewDatasetResource(UploadMixin, API):
401
404
  @api.secure
402
- @api.doc('upload_new_dataset_resource', responses={415: 'Incorrect file content type', 400: 'Upload error'})
405
+ @api.doc(
406
+ "upload_new_dataset_resource",
407
+ responses={415: "Incorrect file content type", 400: "Upload error"},
408
+ )
403
409
  @api.expect(upload_parser)
404
410
  @api.marshal_with(upload_fields, code=201)
405
411
  def post(self, dataset):
406
- '''Upload a new dataset resource'''
412
+ """Upload a new dataset resource"""
407
413
  ResourceEditPermission(dataset).test()
408
414
  infos = self.handle_upload(dataset)
409
415
  resource = Resource(**infos)
@@ -413,41 +419,48 @@ class UploadNewDatasetResource(UploadMixin, API):
413
419
  return resource, 201
414
420
 
415
421
 
416
- @ns.route('/<dataset:dataset>/upload/community/',
417
- endpoint='upload_new_community_resource')
422
+ @ns.route("/<dataset:dataset>/upload/community/", endpoint="upload_new_community_resource")
418
423
  @api.doc(**common_doc)
419
424
  class UploadNewCommunityResources(UploadMixin, API):
420
425
  @api.secure
421
- @api.doc('upload_new_community_resource', responses={415: 'Incorrect file content type', 400: 'Upload error'})
426
+ @api.doc(
427
+ "upload_new_community_resource",
428
+ responses={415: "Incorrect file content type", 400: "Upload error"},
429
+ )
422
430
  @api.expect(upload_parser)
423
431
  @api.marshal_with(upload_fields, code=201)
424
432
  def post(self, dataset):
425
- '''Upload a new community resource'''
433
+ """Upload a new community resource"""
426
434
  infos = self.handle_upload(dataset)
427
- infos['owner'] = current_user._get_current_object()
428
- infos['dataset'] = dataset
435
+ infos["owner"] = current_user._get_current_object()
436
+ infos["dataset"] = dataset
429
437
  community_resource = CommunityResource.objects.create(**infos)
430
438
  return community_resource, 201
431
439
 
432
440
 
433
441
  class ResourceMixin(object):
434
-
435
442
  def get_resource_or_404(self, dataset, id):
436
- resource = get_by(dataset.resources, 'id', id)
443
+ resource = get_by(dataset.resources, "id", id)
437
444
  if not resource:
438
- api.abort(404, 'Resource does not exist')
445
+ api.abort(404, "Resource does not exist")
439
446
  return resource
440
447
 
441
448
 
442
- @ns.route('/<dataset:dataset>/resources/<uuid:rid>/upload/',
443
- endpoint='upload_dataset_resource', doc=common_doc)
444
- @api.param('rid', 'The resource unique identifier')
449
+ @ns.route(
450
+ "/<dataset:dataset>/resources/<uuid:rid>/upload/",
451
+ endpoint="upload_dataset_resource",
452
+ doc=common_doc,
453
+ )
454
+ @api.param("rid", "The resource unique identifier")
445
455
  class UploadDatasetResource(ResourceMixin, UploadMixin, API):
446
456
  @api.secure
447
- @api.doc('upload_dataset_resource', responses={415: 'Incorrect file content type', 400: 'Upload error'})
457
+ @api.doc(
458
+ "upload_dataset_resource",
459
+ responses={415: "Incorrect file content type", 400: "Upload error"},
460
+ )
448
461
  @api.marshal_with(upload_fields)
449
462
  def post(self, dataset, rid):
450
- '''Upload a file related to a given resource on a given dataset'''
463
+ """Upload a file related to a given resource on a given dataset"""
451
464
  ResourceEditPermission(dataset).test()
452
465
  resource = self.get_resource_or_404(dataset, rid)
453
466
  fs_filename_to_remove = resource.fs_filename
@@ -462,15 +475,21 @@ class UploadDatasetResource(ResourceMixin, UploadMixin, API):
462
475
  return resource
463
476
 
464
477
 
465
- @ns.route('/community_resources/<crid:community>/upload/',
466
- endpoint='upload_community_resource', doc=common_doc)
467
- @api.param('community', 'The community resource unique identifier')
478
+ @ns.route(
479
+ "/community_resources/<crid:community>/upload/",
480
+ endpoint="upload_community_resource",
481
+ doc=common_doc,
482
+ )
483
+ @api.param("community", "The community resource unique identifier")
468
484
  class ReuploadCommunityResource(ResourceMixin, UploadMixin, API):
469
485
  @api.secure
470
- @api.doc('upload_community_resource', responses={415: 'Incorrect file content type', 400: 'Upload error'})
486
+ @api.doc(
487
+ "upload_community_resource",
488
+ responses={415: "Incorrect file content type", 400: "Upload error"},
489
+ )
471
490
  @api.marshal_with(upload_fields)
472
491
  def post(self, community):
473
- '''Update the file related to a given community resource'''
492
+ """Update the file related to a given community resource"""
474
493
  ResourceEditPermission(community).test()
475
494
  fs_filename_to_remove = community.fs_filename
476
495
  infos = self.handle_upload(community.dataset)
@@ -481,31 +500,30 @@ class ReuploadCommunityResource(ResourceMixin, UploadMixin, API):
481
500
  return community
482
501
 
483
502
 
484
- @ns.route('/<dataset:dataset>/resources/<uuid:rid>/', endpoint='resource',
485
- doc=common_doc)
486
- @api.param('rid', 'The resource unique identifier')
503
+ @ns.route("/<dataset:dataset>/resources/<uuid:rid>/", endpoint="resource", doc=common_doc)
504
+ @api.param("rid", "The resource unique identifier")
487
505
  class ResourceAPI(ResourceMixin, API):
488
- @api.doc('get_resource')
506
+ @api.doc("get_resource")
489
507
  @api.marshal_with(resource_fields)
490
508
  def get(self, dataset, rid):
491
- '''Get a resource given its identifier'''
509
+ """Get a resource given its identifier"""
492
510
  if dataset.deleted and not DatasetEditPermission(dataset).can():
493
- api.abort(410, 'Dataset has been deleted')
511
+ api.abort(410, "Dataset has been deleted")
494
512
  resource = self.get_resource_or_404(dataset, rid)
495
513
  return resource
496
514
 
497
515
  @api.secure
498
- @api.doc('update_resource', responses={400: 'Validation error'})
516
+ @api.doc("update_resource", responses={400: "Validation error"})
499
517
  @api.expect(resource_fields)
500
518
  @api.marshal_with(resource_fields)
501
519
  def put(self, dataset, rid):
502
- '''Update a given resource on a given dataset'''
520
+ """Update a given resource on a given dataset"""
503
521
  ResourceEditPermission(dataset).test()
504
522
  resource = self.get_resource_or_404(dataset, rid)
505
523
  form = api.validate(ResourceForm, resource)
506
524
  # ensure API client does not override url on self-hosted resources
507
- if resource.filetype == 'file':
508
- form._fields.get('url').data = resource.url
525
+ if resource.filetype == "file":
526
+ form._fields.get("url").data = resource.url
509
527
  # populate_obj populates existing resource object with the content of the form.
510
528
  # update_resource saves the updated resource dict to the database
511
529
  # the additional dataset.save is required as we update the last_modified date.
@@ -517,51 +535,47 @@ class ResourceAPI(ResourceMixin, API):
517
535
  return resource
518
536
 
519
537
  @api.secure
520
- @api.doc('delete_resource')
538
+ @api.doc("delete_resource")
521
539
  def delete(self, dataset, rid):
522
- '''Delete a given resource on a given dataset'''
540
+ """Delete a given resource on a given dataset"""
523
541
  ResourceEditPermission(dataset).test()
524
542
  resource = self.get_resource_or_404(dataset, rid)
525
543
  dataset.remove_resource(resource)
526
544
  dataset.last_modified_internal = datetime.utcnow()
527
545
  dataset.save()
528
- return '', 204
546
+ return "", 204
529
547
 
530
548
 
531
- @ns.route('/community_resources/', endpoint='community_resources')
549
+ @ns.route("/community_resources/", endpoint="community_resources")
532
550
  class CommunityResourcesAPI(API):
533
- @api.doc('list_community_resources')
551
+ @api.doc("list_community_resources")
534
552
  @api.expect(community_parser)
535
553
  @api.marshal_with(community_resource_page_fields)
536
554
  def get(self):
537
- '''List all community resources'''
555
+ """List all community resources"""
538
556
  args = community_parser.parse_args()
539
557
  community_resources = CommunityResource.objects
540
- if args['owner']:
541
- community_resources = community_resources(owner=args['owner'])
542
- if args['dataset']:
543
- community_resources = community_resources(dataset=args['dataset'])
544
- if args['organization']:
545
- community_resources = community_resources(
546
- organization=args['organization'])
547
- return (community_resources.order_by(args['sort'])
548
- .paginate(args['page'], args['page_size']))
558
+ if args["owner"]:
559
+ community_resources = community_resources(owner=args["owner"])
560
+ if args["dataset"]:
561
+ community_resources = community_resources(dataset=args["dataset"])
562
+ if args["organization"]:
563
+ community_resources = community_resources(organization=args["organization"])
564
+ return community_resources.order_by(args["sort"]).paginate(args["page"], args["page_size"])
549
565
 
550
566
  @api.secure
551
- @api.doc('create_community_resource', responses={400: 'Validation error'})
567
+ @api.doc("create_community_resource", responses={400: "Validation error"})
552
568
  @api.expect(community_resource_fields)
553
569
  @api.marshal_with(community_resource_fields, code=201)
554
570
  def post(self):
555
- '''Create a new community resource'''
571
+ """Create a new community resource"""
556
572
  form = api.validate(CommunityResourceForm)
557
- if form._fields.get('filetype').data != 'remote':
558
- api.abort(400, 'This endpoint only supports remote community resources')
573
+ if form._fields.get("filetype").data != "remote":
574
+ api.abort(400, "This endpoint only supports remote community resources")
559
575
  resource = CommunityResource()
560
576
  form.populate_obj(resource)
561
577
  if not resource.dataset:
562
- api.abort(400, errors={
563
- 'dataset': 'A dataset identifier is required'
564
- })
578
+ api.abort(400, errors={"dataset": "A dataset identifier is required"})
565
579
  if not resource.organization:
566
580
  resource.owner = current_user._get_current_object()
567
581
  resource.last_modified_internal = datetime.utcnow()
@@ -569,26 +583,25 @@ class CommunityResourcesAPI(API):
569
583
  return resource, 201
570
584
 
571
585
 
572
- @ns.route('/community_resources/<crid:community>/',
573
- endpoint='community_resource', doc=common_doc)
574
- @api.param('community', 'The community resource unique identifier')
586
+ @ns.route("/community_resources/<crid:community>/", endpoint="community_resource", doc=common_doc)
587
+ @api.param("community", "The community resource unique identifier")
575
588
  class CommunityResourceAPI(API):
576
- @api.doc('retrieve_community_resource')
589
+ @api.doc("retrieve_community_resource")
577
590
  @api.marshal_with(community_resource_fields)
578
591
  def get(self, community):
579
- '''Retrieve a community resource given its identifier'''
592
+ """Retrieve a community resource given its identifier"""
580
593
  return community
581
594
 
582
595
  @api.secure
583
- @api.doc('update_community_resource', responses={400: 'Validation error'})
596
+ @api.doc("update_community_resource", responses={400: "Validation error"})
584
597
  @api.expect(community_resource_fields)
585
598
  @api.marshal_with(community_resource_fields)
586
599
  def put(self, community):
587
- '''Update a given community resource'''
600
+ """Update a given community resource"""
588
601
  ResourceEditPermission(community).test()
589
602
  form = api.validate(CommunityResourceForm, community)
590
- if community.filetype == 'file':
591
- form._fields.get('url').data = community.url
603
+ if community.filetype == "file":
604
+ form._fields.get("url").data = community.url
592
605
  form.populate_obj(community)
593
606
  if not community.organization and not community.owner:
594
607
  community.owner = current_user._get_current_object()
@@ -597,146 +610,156 @@ class CommunityResourceAPI(API):
597
610
  return community
598
611
 
599
612
  @api.secure
600
- @api.doc('delete_community_resource')
613
+ @api.doc("delete_community_resource")
601
614
  def delete(self, community):
602
- '''Delete a given community resource'''
615
+ """Delete a given community resource"""
603
616
  ResourceEditPermission(community).test()
604
617
  # Deletes community resource's file from file storage
605
618
  if community.fs_filename is not None:
606
619
  storages.resources.delete(community.fs_filename)
607
620
  community.delete()
608
- return '', 204
621
+ return "", 204
609
622
 
610
623
 
611
- @ns.route('/<id>/followers/', endpoint='dataset_followers')
612
- @ns.doc(get={'id': 'list_dataset_followers'},
613
- post={'id': 'follow_dataset'},
614
- delete={'id': 'unfollow_dataset'})
624
+ @ns.route("/<id>/followers/", endpoint="dataset_followers")
625
+ @ns.doc(
626
+ get={"id": "list_dataset_followers"},
627
+ post={"id": "follow_dataset"},
628
+ delete={"id": "unfollow_dataset"},
629
+ )
615
630
  class DatasetFollowersAPI(FollowAPI):
616
631
  model = Dataset
617
632
 
618
633
 
619
634
  suggest_parser = api.parser()
620
635
  suggest_parser.add_argument(
621
- 'q', help='The string to autocomplete/suggest', location='args',
622
- required=True)
636
+ "q", help="The string to autocomplete/suggest", location="args", required=True
637
+ )
623
638
  suggest_parser.add_argument(
624
- 'size', type=int, help='The amount of suggestion to fetch',
625
- location='args', default=10)
639
+ "size", type=int, help="The amount of suggestion to fetch", location="args", default=10
640
+ )
626
641
 
627
642
 
628
- @ns.route('/suggest/', endpoint='suggest_datasets')
643
+ @ns.route("/suggest/", endpoint="suggest_datasets")
629
644
  class DatasetSuggestAPI(API):
630
- @api.doc('suggest_datasets')
645
+ @api.doc("suggest_datasets")
631
646
  @api.expect(suggest_parser)
632
647
  @api.marshal_with(dataset_suggestion_fields)
633
648
  def get(self):
634
- '''Datasets suggest endpoint using mongoDB contains'''
649
+ """Datasets suggest endpoint using mongoDB contains"""
635
650
  args = suggest_parser.parse_args()
636
651
  datasets_query = Dataset.objects(archived=None, deleted=None, private=False)
637
652
  datasets = datasets_query.filter(
638
- Q(title__icontains=args['q']) | Q(acronym__icontains=args['q']))
653
+ Q(title__icontains=args["q"]) | Q(acronym__icontains=args["q"])
654
+ )
639
655
  return [
640
656
  {
641
- 'id': dataset.id,
642
- 'title': dataset.title,
643
- 'acronym': dataset.acronym,
644
- 'slug': dataset.slug,
645
- 'image_url': (
646
- dataset.organization.logo if dataset.organization
647
- else dataset.owner.avatar if dataset.owner else None
648
- )
657
+ "id": dataset.id,
658
+ "title": dataset.title,
659
+ "acronym": dataset.acronym,
660
+ "slug": dataset.slug,
661
+ "image_url": (
662
+ dataset.organization.logo
663
+ if dataset.organization
664
+ else dataset.owner.avatar
665
+ if dataset.owner
666
+ else None
667
+ ),
649
668
  }
650
- for dataset in datasets.order_by(SUGGEST_SORTING).limit(args['size'])
669
+ for dataset in datasets.order_by(SUGGEST_SORTING).limit(args["size"])
651
670
  ]
652
671
 
653
672
 
654
- @ns.route('/suggest/formats/', endpoint='suggest_formats')
673
+ @ns.route("/suggest/formats/", endpoint="suggest_formats")
655
674
  class FormatsSuggestAPI(API):
656
- @api.doc('suggest_formats')
675
+ @api.doc("suggest_formats")
657
676
  @api.expect(suggest_parser)
658
677
  def get(self):
659
- '''Suggest file formats'''
678
+ """Suggest file formats"""
660
679
  args = suggest_parser.parse_args()
661
- results = [{'text': i} for i in current_app.config['ALLOWED_RESOURCES_EXTENSIONS']
662
- if args['q'] in i]
663
- results = results[:args['size']]
664
- return sorted(results, key=lambda o: len(o['text']))
680
+ results = [
681
+ {"text": i}
682
+ for i in current_app.config["ALLOWED_RESOURCES_EXTENSIONS"]
683
+ if args["q"] in i
684
+ ]
685
+ results = results[: args["size"]]
686
+ return sorted(results, key=lambda o: len(o["text"]))
665
687
 
666
688
 
667
- @ns.route('/suggest/mime/', endpoint='suggest_mime')
689
+ @ns.route("/suggest/mime/", endpoint="suggest_mime")
668
690
  class MimesSuggestAPI(API):
669
- @api.doc('suggest_mime')
691
+ @api.doc("suggest_mime")
670
692
  @api.expect(suggest_parser)
671
693
  def get(self):
672
- '''Suggest mime types'''
694
+ """Suggest mime types"""
673
695
  args = suggest_parser.parse_args()
674
- results = [{'text': i} for i in current_app.config['ALLOWED_RESOURCES_MIMES']
675
- if args['q'] in i]
676
- results = results[:args['size']]
677
- return sorted(results, key=lambda o: len(o['text']))
696
+ results = [
697
+ {"text": i} for i in current_app.config["ALLOWED_RESOURCES_MIMES"] if args["q"] in i
698
+ ]
699
+ results = results[: args["size"]]
700
+ return sorted(results, key=lambda o: len(o["text"]))
678
701
 
679
702
 
680
- @ns.route('/licenses/', endpoint='licenses')
703
+ @ns.route("/licenses/", endpoint="licenses")
681
704
  class LicensesAPI(API):
682
- @api.doc('list_licenses')
705
+ @api.doc("list_licenses")
683
706
  @api.marshal_list_with(license_fields)
684
707
  def get(self):
685
- '''List all available licenses'''
708
+ """List all available licenses"""
686
709
  return list(License.objects)
687
710
 
688
711
 
689
- @ns.route('/frequencies/', endpoint='dataset_frequencies')
712
+ @ns.route("/frequencies/", endpoint="dataset_frequencies")
690
713
  class FrequenciesAPI(API):
691
- @api.doc('list_frequencies')
714
+ @api.doc("list_frequencies")
692
715
  @api.marshal_list_with(frequency_fields)
693
716
  def get(self):
694
- '''List all available frequencies'''
695
- return [{'id': id, 'label': label}
696
- for id, label in UPDATE_FREQUENCIES.items()]
717
+ """List all available frequencies"""
718
+ return [{"id": id, "label": label} for id, label in UPDATE_FREQUENCIES.items()]
697
719
 
698
720
 
699
- @ns.route('/extensions/', endpoint='allowed_extensions')
721
+ @ns.route("/extensions/", endpoint="allowed_extensions")
700
722
  class AllowedExtensionsAPI(API):
701
- @api.doc('allowed_extensions')
702
- @api.response(200, 'Success', [str])
723
+ @api.doc("allowed_extensions")
724
+ @api.response(200, "Success", [str])
703
725
  def get(self):
704
- '''List all allowed resources extensions'''
705
- return current_app.config['ALLOWED_RESOURCES_EXTENSIONS']
726
+ """List all allowed resources extensions"""
727
+ return current_app.config["ALLOWED_RESOURCES_EXTENSIONS"]
706
728
 
707
729
 
708
- @ns.route('/<dataset:dataset>/resources/<uuid:rid>/check/',
709
- endpoint='check_dataset_resource', doc=common_doc)
710
- @api.param('rid', 'The resource unique identifier')
730
+ @ns.route(
731
+ "/<dataset:dataset>/resources/<uuid:rid>/check/",
732
+ endpoint="check_dataset_resource",
733
+ doc=common_doc,
734
+ )
735
+ @api.param("rid", "The resource unique identifier")
711
736
  class CheckDatasetResource(API, ResourceMixin):
712
-
713
- @api.doc('check_dataset_resource')
737
+ @api.doc("check_dataset_resource")
714
738
  def get(self, dataset, rid):
715
- '''Checks that a resource's URL exists and returns metadata.'''
739
+ """Checks that a resource's URL exists and returns metadata."""
716
740
  resource = self.get_resource_or_404(dataset, rid)
717
741
  return check_resource(resource)
718
742
 
719
743
 
720
- @ns.route('/resource_types/', endpoint='resource_types')
744
+ @ns.route("/resource_types/", endpoint="resource_types")
721
745
  class ResourceTypesAPI(API):
722
- @api.doc('resource_types')
746
+ @api.doc("resource_types")
723
747
  @api.marshal_list_with(resource_type_fields)
724
748
  def get(self):
725
- '''List all resource types'''
726
- return [{'id': id, 'label': label}
727
- for id, label in RESOURCE_TYPES.items()]
749
+ """List all resource types"""
750
+ return [{"id": id, "label": label} for id, label in RESOURCE_TYPES.items()]
728
751
 
729
752
 
730
- @ns.route('/schemas/', endpoint='schemas')
753
+ @ns.route("/schemas/", endpoint="schemas")
731
754
  class SchemasAPI(API):
732
- @api.doc('schemas')
755
+ @api.doc("schemas")
733
756
  @api.marshal_list_with(catalog_schema_fields)
734
757
  def get(self):
735
- '''List all available schemas'''
758
+ """List all available schemas"""
736
759
  try:
737
760
  # This method call is cached as it makes HTTP requests
738
761
  return ResourceSchema.assignable_schemas()
739
762
  except SchemasCacheUnavailableException:
740
- abort(503, description='No schemas in cache and endpoint unavailable')
763
+ abort(503, description="No schemas in cache and endpoint unavailable")
741
764
  except SchemasCatalogNotFoundException:
742
- abort(404, description='Schema catalog endpoint was not found')
765
+ abort(404, description="Schema catalog endpoint was not found")