invenio-vocabularies 1.2.0__py2.py3-none-any.whl → 6.3.1__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 invenio-vocabularies might be problematic. Click here for more details.

Files changed (239) hide show
  1. invenio_vocabularies/__init__.py +2 -2
  2. invenio_vocabularies/administration/__init__.py +10 -0
  3. invenio_vocabularies/administration/views/__init__.py +10 -0
  4. invenio_vocabularies/administration/views/vocabularies.py +45 -0
  5. invenio_vocabularies/alembic/4a9a4fd235f8_create_vocabulary_schemes.py +4 -4
  6. invenio_vocabularies/alembic/4f365fced43f_create_vocabularies_tables.py +2 -2
  7. invenio_vocabularies/alembic/55a700f897b6_add_names_and_afiliations_pid_column.py +96 -0
  8. invenio_vocabularies/alembic/676dd587542d_create_funders_vocabulary_table.py +1 -1
  9. invenio_vocabularies/alembic/e1146238edd3_create_awards_table.py +1 -1
  10. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.eslintrc.yml +11 -0
  11. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.prettierrc +1 -0
  12. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/index.js +7 -0
  13. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/package.json +25 -0
  14. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/AwardResults.js +95 -0
  15. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js +139 -0
  16. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FunderDropdown.js +87 -0
  17. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js +223 -0
  18. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.test.js +1 -0
  19. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingFieldItem.js +152 -0
  20. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js +270 -0
  21. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/NoAwardResults.js +37 -0
  22. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/index.js +8 -0
  23. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/index.js +7 -0
  24. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/index.js +7 -0
  25. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/index.js +7 -0
  26. invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/i18next-scanner.config.js +63 -0
  27. invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/i18next.js +36 -0
  28. invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/index.js +1 -0
  29. invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/package.json +53 -0
  30. invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/scripts/compileCatalog.js +39 -0
  31. invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/scripts/initCatalog.js +19 -0
  32. invenio_vocabularies/cli.py +31 -44
  33. invenio_vocabularies/config.py +74 -7
  34. invenio_vocabularies/contrib/affiliations/affiliations.py +22 -6
  35. invenio_vocabularies/contrib/affiliations/api.py +1 -2
  36. invenio_vocabularies/contrib/affiliations/config.py +10 -5
  37. invenio_vocabularies/contrib/affiliations/datastreams.py +186 -0
  38. invenio_vocabularies/contrib/affiliations/facets.py +36 -0
  39. invenio_vocabularies/contrib/affiliations/jsonschemas/affiliations/affiliation-v1.0.0.json +38 -7
  40. invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json +22 -1
  41. invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v2.0.0.json +171 -0
  42. invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json +22 -1
  43. invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v2.0.0.json +171 -0
  44. invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json +22 -1
  45. invenio_vocabularies/contrib/affiliations/schema.py +23 -5
  46. invenio_vocabularies/contrib/affiliations/services.py +1 -2
  47. invenio_vocabularies/contrib/awards/awards.py +18 -6
  48. invenio_vocabularies/contrib/awards/config.py +1 -3
  49. invenio_vocabularies/contrib/awards/datastreams.py +246 -3
  50. invenio_vocabularies/contrib/awards/jsonschemas/awards/award-v1.0.0.json +41 -0
  51. invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json +53 -1
  52. invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json +53 -1
  53. invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json +53 -1
  54. invenio_vocabularies/contrib/awards/schema.py +27 -35
  55. invenio_vocabularies/contrib/awards/serializer.py +9 -1
  56. invenio_vocabularies/contrib/awards/services.py +1 -2
  57. invenio_vocabularies/contrib/common/__init__.py +9 -0
  58. invenio_vocabularies/contrib/common/openaire/__init__.py +9 -0
  59. invenio_vocabularies/contrib/common/openaire/datastreams.py +84 -0
  60. invenio_vocabularies/contrib/common/ror/__init__.py +9 -0
  61. invenio_vocabularies/contrib/common/ror/datastreams.py +220 -0
  62. invenio_vocabularies/contrib/funders/config.py +12 -5
  63. invenio_vocabularies/contrib/funders/datastreams.py +40 -62
  64. invenio_vocabularies/contrib/funders/facets.py +13 -5
  65. invenio_vocabularies/contrib/funders/funders.py +4 -2
  66. invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json +36 -1
  67. invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json +22 -1
  68. invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v2.0.0.json +156 -0
  69. invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json +22 -1
  70. invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v2.0.0.json +156 -0
  71. invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json +22 -1
  72. invenio_vocabularies/contrib/funders/schema.py +8 -0
  73. invenio_vocabularies/contrib/funders/serializer.py +2 -1
  74. invenio_vocabularies/contrib/names/config.py +5 -3
  75. invenio_vocabularies/contrib/names/datastreams.py +177 -38
  76. invenio_vocabularies/contrib/names/jsonschemas/names/name-v1.0.0.json +2 -6
  77. invenio_vocabularies/contrib/names/mappings/os-v1/names/name-v1.0.0.json +3 -0
  78. invenio_vocabularies/contrib/names/mappings/os-v1/names/name-v2.0.0.json +150 -0
  79. invenio_vocabularies/contrib/names/mappings/os-v2/names/name-v1.0.0.json +3 -0
  80. invenio_vocabularies/contrib/names/mappings/os-v2/names/name-v2.0.0.json +150 -0
  81. invenio_vocabularies/contrib/names/mappings/v7/names/name-v1.0.0.json +3 -0
  82. invenio_vocabularies/contrib/names/names.py +29 -13
  83. invenio_vocabularies/contrib/names/permissions.py +20 -0
  84. invenio_vocabularies/contrib/names/s3client.py +44 -0
  85. invenio_vocabularies/contrib/names/schema.py +31 -4
  86. invenio_vocabularies/contrib/subjects/config.py +9 -3
  87. invenio_vocabularies/contrib/subjects/datastreams.py +61 -0
  88. invenio_vocabularies/contrib/subjects/euroscivoc/__init__.py +9 -0
  89. invenio_vocabularies/contrib/subjects/euroscivoc/datastreams.py +171 -0
  90. invenio_vocabularies/contrib/subjects/jsonschemas/subjects/subject-v1.0.0.json +31 -0
  91. invenio_vocabularies/contrib/subjects/mappings/os-v1/subjects/subject-v1.0.0.json +35 -0
  92. invenio_vocabularies/contrib/subjects/mappings/os-v2/subjects/subject-v1.0.0.json +35 -0
  93. invenio_vocabularies/contrib/subjects/mappings/v7/subjects/subject-v1.0.0.json +35 -0
  94. invenio_vocabularies/contrib/subjects/mesh/__init__.py +9 -0
  95. invenio_vocabularies/contrib/subjects/mesh/datastreams.py +43 -0
  96. invenio_vocabularies/contrib/subjects/schema.py +47 -5
  97. invenio_vocabularies/contrib/subjects/subjects.py +10 -0
  98. invenio_vocabularies/datastreams/datastreams.py +61 -13
  99. invenio_vocabularies/datastreams/factories.py +1 -2
  100. invenio_vocabularies/datastreams/readers.py +138 -29
  101. invenio_vocabularies/datastreams/tasks.py +37 -0
  102. invenio_vocabularies/datastreams/transformers.py +17 -27
  103. invenio_vocabularies/datastreams/writers.py +116 -14
  104. invenio_vocabularies/datastreams/xml.py +34 -0
  105. invenio_vocabularies/ext.py +59 -5
  106. invenio_vocabularies/factories.py +137 -0
  107. invenio_vocabularies/jobs.py +133 -0
  108. invenio_vocabularies/proxies.py +2 -2
  109. invenio_vocabularies/records/jsonschemas/vocabularies/definitions-v1.0.0.json +7 -0
  110. invenio_vocabularies/records/jsonschemas/vocabularies/vocabulary-v1.0.0.json +1 -4
  111. invenio_vocabularies/records/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json +3 -3
  112. invenio_vocabularies/records/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json +3 -3
  113. invenio_vocabularies/records/mappings/v7/vocabularies/vocabulary-v1.0.0.json +3 -3
  114. invenio_vocabularies/records/models.py +8 -10
  115. invenio_vocabularies/records/pidprovider.py +1 -2
  116. invenio_vocabularies/records/systemfields/relations.py +2 -2
  117. invenio_vocabularies/resources/__init__.py +9 -1
  118. invenio_vocabularies/resources/config.py +105 -0
  119. invenio_vocabularies/resources/resource.py +31 -41
  120. invenio_vocabularies/resources/schema.py +2 -1
  121. invenio_vocabularies/services/__init__.py +5 -2
  122. invenio_vocabularies/services/config.py +179 -0
  123. invenio_vocabularies/services/custom_fields/__init__.py +6 -2
  124. invenio_vocabularies/services/custom_fields/subject.py +82 -0
  125. invenio_vocabularies/services/custom_fields/vocabulary.py +19 -9
  126. invenio_vocabularies/services/facets.py +67 -37
  127. invenio_vocabularies/services/permissions.py +3 -1
  128. invenio_vocabularies/services/results.py +110 -0
  129. invenio_vocabularies/services/schema.py +39 -2
  130. invenio_vocabularies/services/service.py +46 -94
  131. invenio_vocabularies/services/tasks.py +1 -1
  132. invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/subjects.html +23 -0
  133. invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabularies-list.html +12 -0
  134. invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabulary-details.html +71 -0
  135. invenio_vocabularies/translations/af/LC_MESSAGES/messages.mo +0 -0
  136. invenio_vocabularies/translations/af/LC_MESSAGES/messages.po +1 -1
  137. invenio_vocabularies/translations/ar/LC_MESSAGES/messages.mo +0 -0
  138. invenio_vocabularies/translations/ar/LC_MESSAGES/messages.po +9 -8
  139. invenio_vocabularies/translations/bg/LC_MESSAGES/messages.mo +0 -0
  140. invenio_vocabularies/translations/bg/LC_MESSAGES/messages.po +1 -1
  141. invenio_vocabularies/translations/ca/LC_MESSAGES/messages.mo +0 -0
  142. invenio_vocabularies/translations/ca/LC_MESSAGES/messages.po +1 -1
  143. invenio_vocabularies/translations/cs/LC_MESSAGES/messages.mo +0 -0
  144. invenio_vocabularies/translations/cs/LC_MESSAGES/messages.po +1 -1
  145. invenio_vocabularies/translations/da/LC_MESSAGES/messages.mo +0 -0
  146. invenio_vocabularies/translations/da/LC_MESSAGES/messages.po +1 -1
  147. invenio_vocabularies/translations/de/LC_MESSAGES/messages.mo +0 -0
  148. invenio_vocabularies/translations/de/LC_MESSAGES/messages.po +1 -1
  149. invenio_vocabularies/translations/de_AT/LC_MESSAGES/messages.mo +0 -0
  150. invenio_vocabularies/translations/de_AT/LC_MESSAGES/messages.po +139 -0
  151. invenio_vocabularies/translations/de_DE/LC_MESSAGES/messages.mo +0 -0
  152. invenio_vocabularies/translations/de_DE/LC_MESSAGES/messages.po +139 -0
  153. invenio_vocabularies/translations/el/LC_MESSAGES/messages.mo +0 -0
  154. invenio_vocabularies/translations/el/LC_MESSAGES/messages.po +1 -1
  155. invenio_vocabularies/translations/en/LC_MESSAGES/messages.mo +0 -0
  156. invenio_vocabularies/translations/en_AT/LC_MESSAGES/messages.mo +0 -0
  157. invenio_vocabularies/translations/en_AT/LC_MESSAGES/messages.po +139 -0
  158. invenio_vocabularies/translations/en_HU/LC_MESSAGES/messages.mo +0 -0
  159. invenio_vocabularies/translations/en_HU/LC_MESSAGES/messages.po +139 -0
  160. invenio_vocabularies/translations/es/LC_MESSAGES/messages.mo +0 -0
  161. invenio_vocabularies/translations/es/LC_MESSAGES/messages.po +1 -1
  162. invenio_vocabularies/translations/es_CU/LC_MESSAGES/messages.mo +0 -0
  163. invenio_vocabularies/translations/es_CU/LC_MESSAGES/messages.po +139 -0
  164. invenio_vocabularies/translations/es_MX/LC_MESSAGES/messages.mo +0 -0
  165. invenio_vocabularies/translations/es_MX/LC_MESSAGES/messages.po +139 -0
  166. invenio_vocabularies/translations/et/LC_MESSAGES/messages.mo +0 -0
  167. invenio_vocabularies/translations/et/LC_MESSAGES/messages.po +1 -1
  168. invenio_vocabularies/translations/et_EE/LC_MESSAGES/messages.mo +0 -0
  169. invenio_vocabularies/translations/et_EE/LC_MESSAGES/messages.po +1 -1
  170. invenio_vocabularies/translations/fa/LC_MESSAGES/messages.mo +0 -0
  171. invenio_vocabularies/translations/fa/LC_MESSAGES/messages.po +1 -1
  172. invenio_vocabularies/translations/fa_IR/LC_MESSAGES/messages.mo +0 -0
  173. invenio_vocabularies/translations/fa_IR/LC_MESSAGES/messages.po +139 -0
  174. invenio_vocabularies/translations/fr/LC_MESSAGES/messages.mo +0 -0
  175. invenio_vocabularies/translations/fr/LC_MESSAGES/messages.po +1 -1
  176. invenio_vocabularies/translations/fr_CI/LC_MESSAGES/messages.mo +0 -0
  177. invenio_vocabularies/translations/fr_CI/LC_MESSAGES/messages.po +139 -0
  178. invenio_vocabularies/translations/fr_FR/LC_MESSAGES/messages.mo +0 -0
  179. invenio_vocabularies/translations/fr_FR/LC_MESSAGES/messages.po +139 -0
  180. invenio_vocabularies/translations/gl/LC_MESSAGES/messages.mo +0 -0
  181. invenio_vocabularies/translations/gl/LC_MESSAGES/messages.po +1 -1
  182. invenio_vocabularies/translations/hi_IN/LC_MESSAGES/messages.mo +0 -0
  183. invenio_vocabularies/translations/hi_IN/LC_MESSAGES/messages.po +139 -0
  184. invenio_vocabularies/translations/hr/LC_MESSAGES/messages.mo +0 -0
  185. invenio_vocabularies/translations/hr/LC_MESSAGES/messages.po +1 -1
  186. invenio_vocabularies/translations/hu/LC_MESSAGES/messages.mo +0 -0
  187. invenio_vocabularies/translations/hu/LC_MESSAGES/messages.po +4 -4
  188. invenio_vocabularies/translations/hu_HU/LC_MESSAGES/messages.mo +0 -0
  189. invenio_vocabularies/translations/hu_HU/LC_MESSAGES/messages.po +139 -0
  190. invenio_vocabularies/translations/it/LC_MESSAGES/messages.mo +0 -0
  191. invenio_vocabularies/translations/it/LC_MESSAGES/messages.po +4 -3
  192. invenio_vocabularies/translations/ja/LC_MESSAGES/messages.mo +0 -0
  193. invenio_vocabularies/translations/ja/LC_MESSAGES/messages.po +1 -1
  194. invenio_vocabularies/translations/ka/LC_MESSAGES/messages.mo +0 -0
  195. invenio_vocabularies/translations/ka/LC_MESSAGES/messages.po +1 -1
  196. invenio_vocabularies/translations/lt/LC_MESSAGES/messages.mo +0 -0
  197. invenio_vocabularies/translations/lt/LC_MESSAGES/messages.po +1 -1
  198. invenio_vocabularies/translations/messages.pot +95 -48
  199. invenio_vocabularies/translations/ne/LC_MESSAGES/messages.mo +0 -0
  200. invenio_vocabularies/translations/ne/LC_MESSAGES/messages.po +139 -0
  201. invenio_vocabularies/translations/no/LC_MESSAGES/messages.mo +0 -0
  202. invenio_vocabularies/translations/no/LC_MESSAGES/messages.po +1 -1
  203. invenio_vocabularies/translations/pl/LC_MESSAGES/messages.mo +0 -0
  204. invenio_vocabularies/translations/pl/LC_MESSAGES/messages.po +1 -1
  205. invenio_vocabularies/translations/pt/LC_MESSAGES/messages.mo +0 -0
  206. invenio_vocabularies/translations/pt/LC_MESSAGES/messages.po +1 -1
  207. invenio_vocabularies/translations/ro/LC_MESSAGES/messages.mo +0 -0
  208. invenio_vocabularies/translations/ro/LC_MESSAGES/messages.po +1 -1
  209. invenio_vocabularies/translations/ru/LC_MESSAGES/messages.mo +0 -0
  210. invenio_vocabularies/translations/ru/LC_MESSAGES/messages.po +1 -1
  211. invenio_vocabularies/translations/rw/LC_MESSAGES/messages.mo +0 -0
  212. invenio_vocabularies/translations/rw/LC_MESSAGES/messages.po +1 -1
  213. invenio_vocabularies/translations/sk/LC_MESSAGES/messages.mo +0 -0
  214. invenio_vocabularies/translations/sk/LC_MESSAGES/messages.po +1 -1
  215. invenio_vocabularies/translations/sv/LC_MESSAGES/messages.mo +0 -0
  216. invenio_vocabularies/translations/sv/LC_MESSAGES/messages.po +4 -3
  217. invenio_vocabularies/translations/sv_SE/LC_MESSAGES/messages.mo +0 -0
  218. invenio_vocabularies/translations/sv_SE/LC_MESSAGES/messages.po +139 -0
  219. invenio_vocabularies/translations/tr/LC_MESSAGES/messages.mo +0 -0
  220. invenio_vocabularies/translations/tr/LC_MESSAGES/messages.po +1 -1
  221. invenio_vocabularies/translations/uk/LC_MESSAGES/messages.mo +0 -0
  222. invenio_vocabularies/translations/uk/LC_MESSAGES/messages.po +17 -13
  223. invenio_vocabularies/translations/uk_UA/LC_MESSAGES/messages.mo +0 -0
  224. invenio_vocabularies/translations/uk_UA/LC_MESSAGES/messages.po +139 -0
  225. invenio_vocabularies/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
  226. invenio_vocabularies/translations/zh_CN/LC_MESSAGES/messages.po +1 -1
  227. invenio_vocabularies/translations/zh_TW/LC_MESSAGES/messages.mo +0 -0
  228. invenio_vocabularies/translations/zh_TW/LC_MESSAGES/messages.po +1 -1
  229. invenio_vocabularies/views.py +12 -26
  230. invenio_vocabularies/webpack.py +51 -0
  231. invenio_vocabularies-6.3.1.dist-info/METADATA +346 -0
  232. invenio_vocabularies-6.3.1.dist-info/RECORD +306 -0
  233. {invenio_vocabularies-1.2.0.dist-info → invenio_vocabularies-6.3.1.dist-info}/WHEEL +1 -1
  234. {invenio_vocabularies-1.2.0.dist-info → invenio_vocabularies-6.3.1.dist-info}/entry_points.txt +20 -0
  235. invenio_vocabularies-1.2.0.dist-info/METADATA +0 -133
  236. invenio_vocabularies-1.2.0.dist-info/RECORD +0 -220
  237. {invenio_vocabularies-1.2.0.dist-info → invenio_vocabularies-6.3.1.dist-info}/AUTHORS.rst +0 -0
  238. {invenio_vocabularies-1.2.0.dist-info → invenio_vocabularies-6.3.1.dist-info}/LICENSE +0 -0
  239. {invenio_vocabularies-1.2.0.dist-info → invenio_vocabularies-6.3.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2021-2022 CERN.
3
+ # Copyright (C) 2021-2024 CERN.
4
4
  #
5
5
  # Invenio-Vocabularies is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the MIT License; see LICENSE file for more
@@ -13,18 +13,25 @@ from pathlib import Path
13
13
 
14
14
  import yaml
15
15
  from invenio_access.permissions import system_identity
16
- from invenio_pidstore.errors import PIDAlreadyExists
16
+ from invenio_pidstore.errors import PIDAlreadyExists, PIDDoesNotExistError
17
17
  from invenio_records.systemfields.relations.errors import InvalidRelationValue
18
18
  from invenio_records_resources.proxies import current_service_registry
19
19
  from marshmallow import ValidationError
20
+ from sqlalchemy.exc import NoResultFound
20
21
 
21
22
  from .datastreams import StreamEntry
22
23
  from .errors import WriterError
24
+ from .tasks import write_entry, write_many_entry
23
25
 
24
26
 
25
27
  class BaseWriter(ABC):
26
28
  """Base writer."""
27
29
 
30
+ def __init__(self, *args, **kwargs):
31
+ """Base initialization logic."""
32
+ # Add any base initialization here if needed
33
+ pass
34
+
28
35
  @abstractmethod
29
36
  def write(self, stream_entry, *args, **kwargs):
30
37
  """Writes the input stream entry to the target output.
@@ -35,16 +42,29 @@ class BaseWriter(ABC):
35
42
  """
36
43
  pass
37
44
 
45
+ @abstractmethod
46
+ def write_many(self, stream_entries, *args, **kwargs):
47
+ """Writes the input streams entry to the target output.
48
+
49
+ :returns: A List of StreamEntry. The result of writing the entry.
50
+ Raises WriterException in case of errors.
51
+
52
+ """
53
+ pass
54
+
38
55
 
39
56
  class ServiceWriter(BaseWriter):
40
57
  """Writes the entries to an RDM instance using a Service object."""
41
58
 
42
- def __init__(self, service_or_name, *args, identity=None, update=False, **kwargs):
59
+ def __init__(
60
+ self, service_or_name, *args, identity=None, insert=True, update=False, **kwargs
61
+ ):
43
62
  """Constructor.
44
63
 
45
64
  :param service_or_name: a service instance or a key of the
46
65
  service registry.
47
66
  :param identity: access identity.
67
+ :param insert: if True it will insert records which do not exist.
48
68
  :param update: if True it will update records if they exist.
49
69
  """
50
70
  if isinstance(service_or_name, str):
@@ -52,6 +72,7 @@ class ServiceWriter(BaseWriter):
52
72
 
53
73
  self._service = service_or_name
54
74
  self._identity = identity or system_identity
75
+ self._insert = insert
55
76
  self._update = update
56
77
 
57
78
  super().__init__(*args, **kwargs)
@@ -63,20 +84,47 @@ class ServiceWriter(BaseWriter):
63
84
  def _resolve(self, id_):
64
85
  return self._service.read(self._identity, id_)
65
86
 
87
+ def _do_update(self, entry):
88
+ vocab_id = self._entry_id(entry)
89
+ current = self._resolve(vocab_id)
90
+ combined_dict = current.to_dict()
91
+
92
+ # Update fields from entry
93
+ for key, value in entry.items():
94
+ if key in combined_dict:
95
+ if isinstance(combined_dict[key], list) and isinstance(value, list):
96
+ combined_dict[key].extend(
97
+ item for item in value if item not in combined_dict[key]
98
+ )
99
+ else:
100
+ combined_dict[key] = value
101
+ else:
102
+ combined_dict[key] = value
103
+
104
+ return StreamEntry(
105
+ self._service.update(self._identity, vocab_id, combined_dict)
106
+ )
107
+
66
108
  def write(self, stream_entry, *args, **kwargs):
67
109
  """Writes the input entry using a given service."""
68
110
  entry = stream_entry.entry
111
+
69
112
  try:
70
- try:
71
- return StreamEntry(self._service.create(self._identity, entry))
72
- except PIDAlreadyExists:
73
- if not self._update:
74
- raise WriterError([f"Vocabulary entry already exists: {entry}"])
75
- vocab_id = self._entry_id(entry)
76
- current = self._resolve(vocab_id)
77
- updated = dict(current.to_dict(), **entry)
78
- return StreamEntry(
79
- self._service.update(self._identity, vocab_id, updated)
113
+ if self._insert:
114
+ try:
115
+ return StreamEntry(self._service.create(self._identity, entry))
116
+ except PIDAlreadyExists:
117
+ if not self._update:
118
+ raise WriterError([f"Vocabulary entry already exists: {entry}"])
119
+ return self._do_update(entry)
120
+ elif self._update:
121
+ try:
122
+ return self._do_update(entry)
123
+ except (NoResultFound, PIDDoesNotExistError):
124
+ raise WriterError([f"Vocabulary entry does not exist: {entry}"])
125
+ else:
126
+ raise WriterError(
127
+ ["Writer wrongly configured to not insert and to not update"]
80
128
  )
81
129
 
82
130
  except ValidationError as err:
@@ -85,6 +133,25 @@ class ServiceWriter(BaseWriter):
85
133
  # TODO: Check if we can get the error message easier
86
134
  raise WriterError([{"InvalidRelationValue": err.args[0]}])
87
135
 
136
+ def write_many(self, stream_entries, *args, **kwargs):
137
+ """Writes the input entries using a given service."""
138
+ entries = [entry.entry for entry in stream_entries]
139
+ entries_with_id = [(self._entry_id(entry), entry) for entry in entries]
140
+ results = self._service.create_or_update_many(self._identity, entries_with_id)
141
+ stream_entries_processed = []
142
+ for entry, result in zip(entries, results):
143
+ processed_stream_entry = StreamEntry(
144
+ entry=entry,
145
+ record=result.record,
146
+ errors=result.errors,
147
+ op_type=result.op_type,
148
+ exc=result.exc,
149
+ )
150
+ processed_stream_entry.log_errors()
151
+ stream_entries_processed.append(processed_stream_entry)
152
+
153
+ return stream_entries_processed
154
+
88
155
 
89
156
  class YamlWriter(BaseWriter):
90
157
  """Writes the entries to a YAML file."""
@@ -103,6 +170,41 @@ class YamlWriter(BaseWriter):
103
170
  with open(self._filepath, "a") as file:
104
171
  # made into array for safer append
105
172
  # will always read array (good for reader)
106
- yaml.safe_dump([stream_entry.entry], file)
173
+ yaml.safe_dump([stream_entry.entry], file, allow_unicode=True)
107
174
 
108
175
  return stream_entry
176
+
177
+ def write_many(self, stream_entries, *args, **kwargs):
178
+ """Writes the yaml input entries."""
179
+ with open(self._filepath, "a") as file:
180
+ yaml.safe_dump(
181
+ [stream_entry.entry for stream_entry in stream_entries],
182
+ file,
183
+ allow_unicode=True,
184
+ )
185
+
186
+
187
+ class AsyncWriter(BaseWriter):
188
+ """Writes the entries asynchronously (celery task)."""
189
+
190
+ def __init__(self, writer, *args, **kwargs):
191
+ """Constructor.
192
+
193
+ :param writer: writer to use.
194
+ """
195
+ super().__init__(*args, **kwargs)
196
+ self._writer = writer
197
+
198
+ def write(self, stream_entry, *args, **kwargs):
199
+ """Launches a celery task to write an entry."""
200
+ write_entry.delay(self._writer, stream_entry.entry)
201
+
202
+ return stream_entry
203
+
204
+ def write_many(self, stream_entries, *args, **kwargs):
205
+ """Launches a celery task to write an entry."""
206
+ write_many_entry.delay(
207
+ self._writer, [stream_entry.entry for stream_entry in stream_entries]
208
+ )
209
+
210
+ return stream_entries
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2021-2024 CERN.
4
+ #
5
+ # Invenio-Vocabularies is free software; you can redistribute it and/or
6
+ # modify it under the terms of the MIT License; see LICENSE file for more
7
+ # details.
8
+
9
+ """XML utils."""
10
+
11
+ from collections import defaultdict
12
+
13
+
14
+ def etree_to_dict(tree):
15
+ """Convert an ElementTree to a dictionary."""
16
+ tag = tree.tag.split(":")[-1] # strip namespace
17
+ d = {tag: {} if tree.attrib else None}
18
+ children = list(tree)
19
+ if children:
20
+ dd = defaultdict(list)
21
+ for dc in map(etree_to_dict, children):
22
+ for k, v in dc.items():
23
+ dd[k].append(v)
24
+ d = {tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
25
+ if tree.attrib:
26
+ d[tag].update(("@" + k, v) for k, v in tree.attrib.items())
27
+ if tree.text:
28
+ text = tree.text.strip()
29
+ if children or tree.attrib:
30
+ if text:
31
+ d[tag]["#text"] = text
32
+ else:
33
+ d[tag] = text
34
+ return d
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2020-2022 CERN.
3
+ # Copyright (C) 2020-2024 CERN.
4
+ # Copyright (C) 2023 Graz University of Technology.
4
5
  #
5
6
  # Invenio-Vocabularies is free software; you can redistribute it and/or
6
7
  # modify it under the terms of the MIT License; see LICENSE file for more
@@ -39,8 +40,14 @@ from .contrib.subjects import (
39
40
  SubjectsService,
40
41
  SubjectsServiceConfig,
41
42
  )
42
- from .resources.resource import VocabulariesResource
43
- from .services.service import VocabulariesService
43
+ from .resources import (
44
+ VocabulariesAdminResource,
45
+ VocabulariesResource,
46
+ VocabulariesResourceConfig,
47
+ VocabularyTypeResourceConfig,
48
+ )
49
+ from .services.config import VocabularyTypesServiceConfig
50
+ from .services.service import VocabulariesService, VocabularyTypeService
44
51
 
45
52
 
46
53
  class InvenioVocabularies(object):
@@ -75,6 +82,7 @@ class InvenioVocabularies(object):
75
82
  funders = FundersServiceConfig
76
83
  names = NamesServiceConfig
77
84
  subjects = SubjectsServiceConfig
85
+ vocabulary_types = VocabularyTypesServiceConfig
78
86
 
79
87
  return ServiceConfigs
80
88
 
@@ -92,9 +100,12 @@ class InvenioVocabularies(object):
92
100
  self.funders_service = FundersService(config=service_configs.funders)
93
101
  self.names_service = NamesService(config=service_configs.names)
94
102
  self.subjects_service = SubjectsService(config=service_configs.subjects)
95
- self.service = VocabulariesService(
103
+ self.vocabularies_service = VocabulariesService(
96
104
  config=app.config["VOCABULARIES_SERVICE_CONFIG"],
97
105
  )
106
+ self.vocabulary_types_service = VocabularyTypeService(
107
+ config=service_configs.vocabulary_types
108
+ )
98
109
 
99
110
  def init_resource(self, app):
100
111
  """Initialize vocabulary resources."""
@@ -120,6 +131,49 @@ class InvenioVocabularies(object):
120
131
  config=SubjectsResourceConfig,
121
132
  )
122
133
  self.resource = VocabulariesResource(
123
- service=self.service,
134
+ service=self.vocabularies_service,
124
135
  config=app.config["VOCABULARIES_RESOURCE_CONFIG"],
125
136
  )
137
+ self.vocabulary_admin_resource = VocabulariesAdminResource(
138
+ service=self.vocabulary_types_service,
139
+ config=VocabularyTypeResourceConfig,
140
+ )
141
+
142
+
143
+ def finalize_app(app):
144
+ """Finalize app.
145
+
146
+ NOTE: replace former @record_once decorator
147
+ """
148
+ init(app)
149
+
150
+
151
+ def api_finalize_app(app):
152
+ """Api Finalize app.
153
+
154
+ NOTE: replace former @record_once decorator
155
+ """
156
+ init(app)
157
+
158
+
159
+ def init(app):
160
+ """Init app."""
161
+ # Register services - cannot be done in extension because
162
+ # Invenio-Records-Resources might not have been initialized.
163
+ sregistry = app.extensions["invenio-records-resources"].registry
164
+ ext = app.extensions["invenio-vocabularies"]
165
+ sregistry.register(ext.affiliations_service, service_id="affiliations")
166
+ sregistry.register(ext.awards_service, service_id="awards")
167
+ sregistry.register(ext.funders_service, service_id="funders")
168
+ sregistry.register(ext.names_service, service_id="names")
169
+ sregistry.register(ext.subjects_service, service_id="subjects")
170
+ sregistry.register(ext.vocabularies_service, service_id="vocabularies")
171
+ sregistry.register(ext.vocabulary_types_service, service_id="vocabulary-types")
172
+ # Register indexers
173
+ iregistry = app.extensions["invenio-indexer"].registry
174
+ iregistry.register(ext.affiliations_service.indexer, indexer_id="affiliations")
175
+ iregistry.register(ext.awards_service.indexer, indexer_id="awards")
176
+ iregistry.register(ext.funders_service.indexer, indexer_id="funders")
177
+ iregistry.register(ext.names_service.indexer, indexer_id="names")
178
+ iregistry.register(ext.subjects_service.indexer, indexer_id="subjects")
179
+ iregistry.register(ext.vocabularies_service.indexer, indexer_id="vocabularies")
@@ -0,0 +1,137 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2024 CERN.
4
+ # Copyright (C) 2024 KTH Royal Institute of Technology.
5
+ #
6
+ # Invenio-Vocabularies is free software; you can redistribute it and/or
7
+ # modify it under the terms of the MIT License; see LICENSE file for more
8
+ # details.
9
+ """Generate Vocabulary Config."""
10
+
11
+ from copy import deepcopy
12
+
13
+ import yaml
14
+ from invenio_records_resources.proxies import current_service_registry
15
+
16
+ from .contrib.affiliations.datastreams import (
17
+ DATASTREAM_CONFIG as affiliations_ds_config,
18
+ )
19
+ from .contrib.affiliations.datastreams import (
20
+ DATASTREAM_CONFIG_OPENAIRE as affiliations_openaire_ds_config,
21
+ )
22
+ from .contrib.awards.datastreams import DATASTREAM_CONFIG as awards_ds_config
23
+ from .contrib.awards.datastreams import (
24
+ DATASTREAM_CONFIG_CORDIS as awards_cordis_ds_config,
25
+ )
26
+ from .contrib.funders.datastreams import DATASTREAM_CONFIG as funders_ds_config
27
+ from .contrib.names.datastreams import DATASTREAM_CONFIG as names_ds_config
28
+ from .contrib.subjects.datastreams import DATASTREAM_CONFIG as subjects_ds_config
29
+
30
+
31
+ class VocabularyConfig:
32
+ """Vocabulary Config Factory."""
33
+
34
+ config = None
35
+ vocabulary_name = None
36
+
37
+ def get_config(self, filepath=None, origin=None):
38
+ """Get the configuration for the vocabulary."""
39
+ config = deepcopy(self.config)
40
+ if filepath:
41
+ with open(filepath, encoding="utf-8") as f:
42
+ config = yaml.safe_load(f).get(self.vocabulary_name)
43
+ if origin:
44
+ config["readers"][0].setdefault("args", {})
45
+ config["readers"][0]["args"]["origin"] = origin
46
+ return config
47
+
48
+ def get_service(self):
49
+ """Get the service for the vocabulary."""
50
+ return current_service_registry.get(self.vocabulary_name)
51
+
52
+
53
+ class NamesVocabularyConfig(VocabularyConfig):
54
+ """Names Vocabulary Config."""
55
+
56
+ config = names_ds_config
57
+ vocabulary_name = "names"
58
+
59
+
60
+ class FundersVocabularyConfig(VocabularyConfig):
61
+ """Funders Vocabulary Config."""
62
+
63
+ config = funders_ds_config
64
+ vocabulary_name = "funders"
65
+
66
+ def get_service(self):
67
+ """Get the service for the vocabulary."""
68
+ raise NotImplementedError("Service not implemented for Funders")
69
+
70
+
71
+ class SubjectsVocabularyConfig(VocabularyConfig):
72
+ """Subjects Vocabulary Config."""
73
+
74
+ config = subjects_ds_config
75
+ vocabulary_name = "subjects"
76
+
77
+ def get_service(self):
78
+ """Get the service for the vocabulary."""
79
+ raise NotImplementedError("Service not implemented for Subjects")
80
+
81
+
82
+ class AwardsVocabularyConfig(VocabularyConfig):
83
+ """Awards Vocabulary Config."""
84
+
85
+ config = awards_ds_config
86
+ vocabulary_name = "awards"
87
+
88
+ def get_service(self):
89
+ """Get the service for the vocabulary."""
90
+ raise NotImplementedError("Service not implemented for Awards")
91
+
92
+
93
+ class AwardsCordisVocabularyConfig(VocabularyConfig):
94
+ """Awards Vocabulary Config."""
95
+
96
+ config = awards_cordis_ds_config
97
+ vocabulary_name = "awards:cordis"
98
+
99
+ def get_service(self):
100
+ """Get the service for the vocabulary."""
101
+ raise NotImplementedError("Service not implemented for CORDIS Awards")
102
+
103
+
104
+ class AffiliationsVocabularyConfig(VocabularyConfig):
105
+ """Affiliations Vocabulary Config."""
106
+
107
+ config = affiliations_ds_config
108
+ vocabulary_name = "affiliations"
109
+
110
+ def get_service(self):
111
+ """Get the service for the vocabulary."""
112
+ raise NotImplementedError("Service not implemented for Affiliations")
113
+
114
+
115
+ class AffiliationsOpenAIREVocabularyConfig(VocabularyConfig):
116
+ """OpenAIRE Affiliations Vocabulary Config."""
117
+
118
+ config = affiliations_openaire_ds_config
119
+ vocabulary_name = "affiliations:openaire"
120
+
121
+ def get_service(self):
122
+ """Get the service for the vocabulary."""
123
+ raise NotImplementedError("Service not implemented for OpenAIRE Affiliations")
124
+
125
+
126
+ def get_vocabulary_config(vocabulary):
127
+ """Factory function to get the appropriate Vocabulary Config."""
128
+ vocab_config = {
129
+ "names": NamesVocabularyConfig,
130
+ "funders": FundersVocabularyConfig,
131
+ "awards": AwardsVocabularyConfig,
132
+ "awards:cordis": AwardsCordisVocabularyConfig,
133
+ "affiliations": AffiliationsVocabularyConfig,
134
+ "affiliations:openaire": AffiliationsOpenAIREVocabularyConfig,
135
+ "subjects": SubjectsVocabularyConfig,
136
+ }
137
+ return vocab_config.get(vocabulary, VocabularyConfig)()
@@ -0,0 +1,133 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2021-2024 CERN.
4
+ #
5
+ # Invenio-Vocabularies is free software; you can redistribute it and/or
6
+ # modify it under the terms of the MIT License; see LICENSE file for more
7
+ # details.
8
+
9
+ """Jobs module."""
10
+
11
+ import datetime
12
+ from datetime import timezone
13
+
14
+ from invenio_i18n import gettext as _
15
+ from invenio_jobs.jobs import JobType
16
+ from marshmallow import Schema, fields
17
+ from marshmallow_utils.fields import TZDateTime
18
+
19
+ from invenio_vocabularies.services.tasks import process_datastream
20
+
21
+
22
+ class ArgsSchema(Schema):
23
+ """Schema of task input arguments."""
24
+
25
+ since = TZDateTime(
26
+ timezone=timezone.utc,
27
+ format="iso",
28
+ metadata={
29
+ "description": _(
30
+ "YYYY-MM-DD HH:mm format. "
31
+ "Leave field empty if it should continue since last successful run."
32
+ )
33
+ },
34
+ )
35
+ job_arg_schema = fields.String(
36
+ metadata={"type": "hidden"},
37
+ dump_default="ArgsSchema",
38
+ load_default="ArgsSchema",
39
+ )
40
+
41
+
42
+ class ProcessDataStreamJob(JobType):
43
+ """Generic process data stream job type."""
44
+
45
+ arguments_schema = ArgsSchema
46
+ task = process_datastream
47
+ id = None
48
+
49
+
50
+ class ProcessRORAffiliationsJob(ProcessDataStreamJob):
51
+ """Process ROR affiliations datastream registered task."""
52
+
53
+ description = "Process ROR affiliations"
54
+ title = "Load ROR affiliations"
55
+ id = "process_ror_affiliations"
56
+
57
+ @classmethod
58
+ def default_args(cls, job_obj, since=None, **kwargs):
59
+ """Generate default job arguments here."""
60
+ if since is None and job_obj.last_runs["success"]:
61
+ since = job_obj.last_runs["success"].started_at
62
+ else:
63
+ since = since or datetime.datetime.now()
64
+
65
+ # NOTE: Update is set to False for now given we don't have the logic to re-index dependent records yet.
66
+ # Since jobs support custom args, update true can be passed via that.
67
+ return {
68
+ "config": {
69
+ "readers": [
70
+ {
71
+ "args": {"since": str(since)},
72
+ "type": "ror-http",
73
+ },
74
+ {"args": {"regex": "_schema_v2\\.json$"}, "type": "zip"},
75
+ {"type": "json"},
76
+ ],
77
+ "writers": [
78
+ {
79
+ "args": {
80
+ "writer": {
81
+ "type": "affiliations-service",
82
+ "args": {"update": False},
83
+ }
84
+ },
85
+ "type": "async",
86
+ }
87
+ ],
88
+ "transformers": [{"type": "ror-affiliations"}],
89
+ }
90
+ }
91
+
92
+
93
+ class ProcessRORFundersJob(ProcessDataStreamJob):
94
+ """Process ROR funders datastream registered task."""
95
+
96
+ description = "Process ROR funders"
97
+ title = "Load ROR funders"
98
+ id = "process_ror_funders"
99
+
100
+ @classmethod
101
+ def default_args(cls, job_obj, since=None, **kwargs):
102
+ """Generate default job arguments here."""
103
+ if since is None and job_obj.last_runs["success"]:
104
+ since = job_obj.last_runs["success"].started_at
105
+ else:
106
+ since = since or datetime.datetime.now()
107
+
108
+ # NOTE: Update is set to False for now given we don't have the logic to re-index dependent records yet.
109
+ # Since jobs support custom args, update true can be passed via that.
110
+ return {
111
+ "config": {
112
+ "readers": [
113
+ {
114
+ "args": {"since": str(since)},
115
+ "type": "ror-http",
116
+ },
117
+ {"args": {"regex": "_schema_v2\\.json$"}, "type": "zip"},
118
+ {"type": "json"},
119
+ ],
120
+ "writers": [
121
+ {
122
+ "args": {
123
+ "writer": {
124
+ "type": "funders-service",
125
+ "args": {"update": False},
126
+ }
127
+ },
128
+ "type": "async",
129
+ }
130
+ ],
131
+ "transformers": [{"type": "ror-funders"}],
132
+ }
133
+ }
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2021 CERN.
3
+ # Copyright (C) 2021-2024 CERN.
4
4
  # Copyright (C) 2021 Northwestern University.
5
5
  #
6
6
  # Invenio-Vocabularies is free software; you can redistribute it and/or
@@ -19,7 +19,7 @@ def _ext_proxy(attr):
19
19
  )
20
20
 
21
21
 
22
- current_service = _ext_proxy("service")
22
+ current_service = _ext_proxy("vocabularies_service")
23
23
  """Proxy to the instantiated vocabulary service."""
24
24
 
25
25
 
@@ -10,5 +10,12 @@
10
10
  },
11
11
  "icon": {
12
12
  "type": "string"
13
+ },
14
+ "tags": {
15
+ "type": "array",
16
+ "description": "Tags for a vocabulary item.",
17
+ "items": {
18
+ "type": "string"
19
+ }
13
20
  }
14
21
  }
@@ -34,10 +34,7 @@
34
34
  }
35
35
  },
36
36
  "tags": {
37
- "type": "array",
38
- "items": {
39
- "type": "string"
40
- }
37
+ "$ref": "local://vocabularies/definitions-v1.0.0.json#/tags"
41
38
  },
42
39
  "title": {
43
40
  "$ref": "local://vocabularies/definitions-v1.0.0.json#/title"
@@ -81,7 +81,7 @@
81
81
  },
82
82
  "title": {
83
83
  "type": "object",
84
- "dynamic": true,
84
+ "dynamic": "true",
85
85
  "properties": {
86
86
  "en": {
87
87
  "type": "search_as_you_type",
@@ -91,7 +91,7 @@
91
91
  },
92
92
  "description": {
93
93
  "type": "object",
94
- "dynamic": true
94
+ "dynamic": "true"
95
95
  },
96
96
  "icon": {
97
97
  "type": "keyword",
@@ -102,7 +102,7 @@
102
102
  },
103
103
  "props": {
104
104
  "type": "object",
105
- "dynamic": true
105
+ "dynamic": "true"
106
106
  }
107
107
  }
108
108
  }