invenio-vocabularies 3.4.1__py2.py3-none-any.whl → 4.0.0__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 (24) hide show
  1. invenio_vocabularies/__init__.py +1 -1
  2. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js +2 -2
  3. invenio_vocabularies/cli.py +10 -39
  4. invenio_vocabularies/contrib/awards/datastreams.py +90 -3
  5. invenio_vocabularies/contrib/common/__init__.py +9 -0
  6. invenio_vocabularies/contrib/common/ror/__init__.py +9 -0
  7. invenio_vocabularies/contrib/common/ror/datastreams.py +66 -0
  8. invenio_vocabularies/contrib/funders/config.py +2 -0
  9. invenio_vocabularies/contrib/funders/datastreams.py +47 -12
  10. invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json +33 -1
  11. invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json +18 -0
  12. invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json +18 -0
  13. invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json +18 -0
  14. invenio_vocabularies/contrib/funders/schema.py +8 -0
  15. invenio_vocabularies/datastreams/readers.py +12 -3
  16. invenio_vocabularies/factories.py +78 -0
  17. invenio_vocabularies/services/tasks.py +30 -0
  18. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/METADATA +10 -5
  19. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/RECORD +24 -20
  20. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/entry_points.txt +3 -0
  21. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/AUTHORS.rst +0 -0
  22. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/LICENSE +0 -0
  23. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/WHEEL +0 -0
  24. {invenio_vocabularies-3.4.1.dist-info → invenio_vocabularies-4.0.0.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,6 @@
10
10
 
11
11
  from .ext import InvenioVocabularies
12
12
 
13
- __version__ = "3.4.1"
13
+ __version__ = "4.0.0"
14
14
 
15
15
  __all__ = ("__version__", "InvenioVocabularies")
@@ -28,8 +28,8 @@ function CustomAwardForm({ deserializeFunder, selectedFunding }) {
28
28
  funderPID = funderItem.id;
29
29
  }
30
30
 
31
- if (funderItem.country) {
32
- funderCountry = funderItem.country;
31
+ if (funderItem.country_name) {
32
+ funderCountry = funderItem.country_name;
33
33
  }
34
34
 
35
35
  if (!funderName && !funderPID) {
@@ -9,46 +9,14 @@
9
9
 
10
10
  """Commands to create and manage vocabularies."""
11
11
 
12
- from copy import deepcopy
13
12
 
14
13
  import click
15
- import yaml
16
14
  from flask.cli import with_appcontext
17
15
  from invenio_access.permissions import system_identity
18
16
  from invenio_pidstore.errors import PIDDeletedError, PIDDoesNotExistError
19
- from invenio_records_resources.proxies import current_service_registry
20
17
 
21
- from .contrib.awards.datastreams import DATASTREAM_CONFIG as awards_ds_config
22
- from .contrib.funders.datastreams import DATASTREAM_CONFIG as funders_ds_config
23
- from .contrib.names.datastreams import DATASTREAM_CONFIG as names_ds_config
24
18
  from .datastreams import DataStreamFactory
25
-
26
-
27
- def get_config_for_ds(vocabulary, filepath=None, origin=None):
28
- """Calculates the configuration for a Data Stream."""
29
- config = None
30
- if vocabulary == "names": # FIXME: turn into a proper factory
31
- config = deepcopy(names_ds_config)
32
- elif vocabulary == "funders":
33
- config = deepcopy(funders_ds_config)
34
- elif vocabulary == "awards":
35
- config = deepcopy(awards_ds_config)
36
-
37
- if config:
38
- if filepath:
39
- with open(filepath) as f:
40
- config = yaml.safe_load(f).get(vocabulary)
41
- if origin:
42
- config["readers"][0].setdefault("args", {})
43
- config["readers"][0]["args"]["origin"] = origin
44
-
45
- return config
46
-
47
-
48
- def get_service_for_vocabulary(vocabulary):
49
- """Calculates the configuration for a Data Stream."""
50
- if vocabulary == "names": # FIXME: turn into a proper factory
51
- return current_service_registry.get("names")
19
+ from .factories import get_vocabulary_config
52
20
 
53
21
 
54
22
  @click.group()
@@ -111,7 +79,9 @@ def import_vocab(vocabulary, filepath=None, origin=None, num_samples=None):
111
79
  click.secho("One of --filepath or --origin must be present.", fg="red")
112
80
  exit(1)
113
81
 
114
- config = get_config_for_ds(vocabulary, filepath, origin)
82
+ vc = get_vocabulary_config(vocabulary)
83
+ config = vc.get_config(filepath, origin)
84
+
115
85
  success, errored, filtered = _process_vocab(config, num_samples)
116
86
 
117
87
  _output_process(vocabulary, "imported", success, errored, filtered)
@@ -127,8 +97,8 @@ def update(vocabulary, filepath=None, origin=None):
127
97
  if not filepath and not origin:
128
98
  click.secho("One of --filepath or --origin must be present.", fg="red")
129
99
  exit(1)
130
-
131
- config = get_config_for_ds(vocabulary, filepath, origin)
100
+ vc = get_vocabulary_config(vocabulary)
101
+ config = vc.get_config(filepath, origin)
132
102
 
133
103
  for w_conf in config["writers"]:
134
104
  w_conf["args"]["update"] = True
@@ -153,7 +123,8 @@ def convert(vocabulary, filepath=None, origin=None, target=None, num_samples=Non
153
123
  )
154
124
  exit(1)
155
125
 
156
- config = get_config_for_ds(vocabulary, filepath, origin)
126
+ vc = get_vocabulary_config(vocabulary)
127
+ config = vc.get_config(filepath, origin)
157
128
  if not filepath:
158
129
  config["writers"] = [{"type": "yaml", "args": {"filepath": target}}]
159
130
 
@@ -176,8 +147,8 @@ def delete(vocabulary, identifier, all):
176
147
  if not id and not all:
177
148
  click.secho("An identifier or the --all flag must be present.", fg="red")
178
149
  exit(1)
179
-
180
- service = get_service_for_vocabulary(vocabulary)
150
+ vc = get_vocabulary_config(vocabulary)
151
+ service = vc.get_service()
181
152
  if identifier:
182
153
  try:
183
154
  if service.delete(identifier, system_identity):
@@ -1,22 +1,79 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2022 CERN.
3
+ # Copyright (C) 2022-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
7
7
  # details.
8
8
 
9
9
  """Awards datastreams, transformers, writers and readers."""
10
+ import io
10
11
 
12
+ import requests
11
13
  from invenio_access.permissions import system_identity
12
14
  from invenio_i18n import lazy_gettext as _
13
15
 
14
- from ...datastreams.errors import TransformerError
16
+ from ...datastreams.errors import ReaderError, TransformerError
17
+ from ...datastreams.readers import BaseReader
15
18
  from ...datastreams.transformers import BaseTransformer
16
19
  from ...datastreams.writers import ServiceWriter
17
20
  from .config import awards_ec_ror_id, awards_openaire_funders_mapping
18
21
 
19
22
 
23
+ class OpenAIREProjectHTTPReader(BaseReader):
24
+ """OpenAIRE Project HTTP Reader returning an in-memory binary stream of the latest OpenAIRE Graph Dataset project tar file."""
25
+
26
+ def _iter(self, fp, *args, **kwargs):
27
+ raise NotImplementedError(
28
+ "OpenAIREProjectHTTPReader downloads one file and therefore does not iterate through items"
29
+ )
30
+
31
+ def read(self, item=None, *args, **kwargs):
32
+ """Reads the latest OpenAIRE Graph Dataset project tar file from Zenodo and yields an in-memory binary stream of it."""
33
+ if item:
34
+ raise NotImplementedError(
35
+ "OpenAIREProjectHTTPReader does not support being chained after another reader"
36
+ )
37
+
38
+ if self._origin == "full":
39
+ # OpenAIRE Graph Dataset
40
+ api_url = "https://zenodo.org/api/records/3516917"
41
+ elif self._origin == "diff":
42
+ # OpenAIRE Graph dataset: new collected projects
43
+ api_url = "https://zenodo.org/api/records/6419021"
44
+ else:
45
+ raise ReaderError("The --origin option should be either 'full' or 'diff'")
46
+
47
+ # Call the signposting `linkset+json` endpoint for the Concept DOI (i.e. latest version) of the OpenAIRE Graph Dataset.
48
+ # See: https://github.com/inveniosoftware/rfcs/blob/master/rfcs/rdm-0071-signposting.md#provide-an-applicationlinksetjson-endpoint
49
+ headers = {"Accept": "application/linkset+json"}
50
+ api_resp = requests.get(api_url, headers=headers)
51
+ api_resp.raise_for_status()
52
+
53
+ # Extract the Landing page Link Set Object located as the first (index 0) item.
54
+ landing_page_linkset = api_resp.json()["linkset"][0]
55
+
56
+ # Extract the URL of the only project tar file linked to the record.
57
+ landing_page_project_tar_items = [
58
+ item
59
+ for item in landing_page_linkset["item"]
60
+ if item["type"] == "application/x-tar"
61
+ and item["href"].endswith("/project.tar")
62
+ ]
63
+ if len(landing_page_project_tar_items) != 1:
64
+ raise ReaderError(
65
+ f"Expected 1 project tar item but got {len(landing_page_project_tar_items)}"
66
+ )
67
+ file_url = landing_page_project_tar_items[0]["href"]
68
+
69
+ # Download the project tar file and fully load the response bytes content in memory.
70
+ # The bytes content are then wrapped by a BytesIO to be file-like object (as required by `tarfile.open`).
71
+ # Using directly `file_resp.raw` is not possible since `tarfile.open` requires the file-like object to be seekable.
72
+ file_resp = requests.get(file_url)
73
+ file_resp.raise_for_status()
74
+ yield io.BytesIO(file_resp.content)
75
+
76
+
20
77
  class AwardsServiceWriter(ServiceWriter):
21
78
  """Funders service writer."""
22
79
 
@@ -39,7 +96,20 @@ class OpenAIREProjectTransformer(BaseTransformer):
39
96
  award = {}
40
97
 
41
98
  code = record["code"]
42
- openaire_funder_prefix = record["id"].split("::")[0].split("|")[1]
99
+
100
+ # The `id` should follow the format `sourcePrefix::md5(localId)` where `sourcePrefix` is 12 characters long.
101
+ # See: https://graph.openaire.eu/docs/data-model/pids-and-identifiers#identifiers-in-the-graph
102
+ #
103
+ # The format of `id` in the full OpenAIRE Graph Dataset (https://doi.org/10.5281/zenodo.3516917)
104
+ # follows this format (e.g. 'abc_________::0123456789abcdef0123456789abcdef').
105
+ # However, the format of `id` in the new collected projects dataset (https://doi.org/10.5281/zenodo.6419021)
106
+ # does not follow this format, and has a `40|` prefix (e.g. '40|abc_________::0123456789abcdef0123456789abcdef').
107
+ #
108
+ # The number '40' corresponds to the entity types 'Project'.
109
+ # See: https://ec.europa.eu/research/participants/documents/downloadPublic?documentIds=080166e5a3a1a213&appId=PPGMS
110
+ # See: https://graph.openaire.eu/docs/5.0.0/data-model/entities/project#id
111
+ openaire_funder_prefix = record["id"].split("::", 1)[0].split("|", 1)[-1]
112
+
43
113
  funder_id = awards_openaire_funders_mapping.get(openaire_funder_prefix)
44
114
  if funder_id is None:
45
115
  raise TransformerError(
@@ -78,7 +148,20 @@ class OpenAIREProjectTransformer(BaseTransformer):
78
148
  award["identifiers"] = identifiers
79
149
 
80
150
  award["number"] = code
151
+
152
+ # `title` is a mandatory attribute of the `Project` object in the OpenAIRE Graph Data Model.
153
+ # See: https://graph.openaire.eu/docs/data-model/entities/project#title
154
+ # However, 15'000+ awards for the FCT funder (and 1 record the NIH funder) are missing a title attribute.
155
+ if "title" not in record:
156
+ raise TransformerError(
157
+ _(
158
+ "Missing title attribute for award {award_id}".format(
159
+ award_id=award["id"]
160
+ )
161
+ )
162
+ )
81
163
  award["title"] = {"en": record["title"]}
164
+
82
165
  award["funder"] = {"id": funder_id}
83
166
  acronym = record.get("acronym")
84
167
  if acronym:
@@ -88,6 +171,10 @@ class OpenAIREProjectTransformer(BaseTransformer):
88
171
  return stream_entry
89
172
 
90
173
 
174
+ VOCABULARIES_DATASTREAM_READERS = {
175
+ "openaire-project-http": OpenAIREProjectHTTPReader,
176
+ }
177
+
91
178
  VOCABULARIES_DATASTREAM_TRANSFORMERS = {
92
179
  "openaire-award": OpenAIREProjectTransformer,
93
180
  }
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 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
+ """Vocabularies common module."""
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 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
+ """ROR-related module."""
@@ -0,0 +1,66 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 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
+ """ROR-related Datastreams Readers/Writers/Transformers module."""
10
+
11
+ import io
12
+
13
+ import requests
14
+
15
+ from invenio_vocabularies.datastreams.errors import ReaderError
16
+ from invenio_vocabularies.datastreams.readers import BaseReader
17
+
18
+
19
+ class RORHTTPReader(BaseReader):
20
+ """ROR HTTP Reader returning an in-memory binary stream of the latest ROR data dump ZIP file."""
21
+
22
+ def _iter(self, fp, *args, **kwargs):
23
+ raise NotImplementedError(
24
+ "RORHTTPReader downloads one file and therefore does not iterate through items"
25
+ )
26
+
27
+ def read(self, item=None, *args, **kwargs):
28
+ """Reads the latest ROR data dump ZIP file from Zenodo and yields an in-memory binary stream of it."""
29
+ if item:
30
+ raise NotImplementedError(
31
+ "RORHTTPReader does not support being chained after another reader"
32
+ )
33
+
34
+ # Call the signposting `linkset+json` endpoint for the Concept DOI (i.e. latest version) of the ROR data dump.
35
+ # See: https://github.com/inveniosoftware/rfcs/blob/master/rfcs/rdm-0071-signposting.md#provide-an-applicationlinksetjson-endpoint
36
+ headers = {"Accept": "application/linkset+json"}
37
+ api_url = "https://zenodo.org/api/records/6347574"
38
+ api_resp = requests.get(api_url, headers=headers)
39
+ api_resp.raise_for_status()
40
+
41
+ # Extract the Landing page Link Set Object located as the first (index 0) item.
42
+ landing_page_linkset = api_resp.json()["linkset"][0]
43
+
44
+ # Extract the URL of the only ZIP file linked to the record.
45
+ landing_page_zip_items = [
46
+ item
47
+ for item in landing_page_linkset["item"]
48
+ if item["type"] == "application/zip"
49
+ ]
50
+ if len(landing_page_zip_items) != 1:
51
+ raise ReaderError(
52
+ f"Expected 1 ZIP item but got {len(landing_page_zip_items)}"
53
+ )
54
+ file_url = landing_page_zip_items[0]["href"]
55
+
56
+ # Download the ZIP file and fully load the response bytes content in memory.
57
+ # The bytes content are then wrapped by a BytesIO to be file-like object (as required by `zipfile.ZipFile`).
58
+ # Using directly `file_resp.raw` is not possible since `zipfile.ZipFile` requires the file-like object to be seekable.
59
+ file_resp = requests.get(file_url)
60
+ file_resp.raise_for_status()
61
+ yield io.BytesIO(file_resp.content)
62
+
63
+
64
+ VOCABULARIES_DATASTREAM_READERS = {
65
+ "ror-http": RORHTTPReader,
66
+ }
@@ -31,6 +31,8 @@ class FundersSearchOptions(SearchOptions):
31
31
  fields=[
32
32
  "name^100",
33
33
  "identifiers.identifier^10",
34
+ "acronym^10",
35
+ "aliases^10",
34
36
  ]
35
37
  )
36
38
 
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
3
  # Copyright (C) 2022 CERN.
4
+ # Copyright (C) 2024 California Institute 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
@@ -38,36 +39,70 @@ class RORTransformer(BaseTransformer):
38
39
  """Applies the transformation to the stream entry."""
39
40
  record = stream_entry.entry
40
41
  funder = {}
42
+ funder["title"] = {}
41
43
 
42
44
  funder["id"] = normalize_ror(record.get("id"))
43
45
  if not funder["id"]:
44
46
  raise TransformerError(_("Id not found in ROR entry."))
45
47
 
46
- funder["name"] = record.get("name")
48
+ aliases = []
49
+ acronym = None
50
+ for name in record.get("names"):
51
+ # Some name entries have a `lang` key with a `None` value.
52
+ # Therefore, providing a default value to `name.get("lang")` is not enough,
53
+ # and we need instead to check if the result of `get` is None.
54
+ lang = name.get("lang")
55
+ if lang is None:
56
+ lang = "en"
57
+
58
+ if "ror_display" in name["types"]:
59
+ funder["name"] = name["value"]
60
+ if "label" in name["types"]:
61
+ funder["title"][lang] = name["value"]
62
+ if "alias" in name["types"]:
63
+ aliases.append(name["value"])
64
+ if "acronym" in name["types"]:
65
+ # The first acronyn goes in acronym field to maintain
66
+ # compatability with existing data structure
67
+ if not acronym:
68
+ acronym = name["value"]
69
+ else:
70
+ aliases.append(name["value"])
71
+ if acronym:
72
+ funder["acronym"] = acronym
73
+ if aliases:
74
+ funder["aliases"] = aliases
75
+
76
+ # ror_display is required and should be in every entry
47
77
  if not funder["name"]:
48
- raise TransformerError(_("Name not found in ROR entry."))
78
+ raise TransformerError(
79
+ _("Name with type ror_display not found in ROR entry.")
80
+ )
49
81
 
50
- country_code = record.get("country", {}).get("country_code")
51
- if country_code:
52
- funder["country"] = country_code
82
+ # This only gets the first location, to maintain compatability
83
+ # with existing data structure
84
+ location = record.get("locations", [{}])[0].get("geonames_details", {})
85
+ funder["country"] = location.get("country_code")
86
+ funder["country_name"] = location.get("country_name")
87
+ funder["location_name"] = location.get("name")
53
88
 
54
- funder["title"] = {"en": funder["name"]}
55
- for label in record.get("labels", []):
56
- funder["title"][label["iso639"]] = label["label"]
89
+ funder["types"] = record.get("types")
90
+
91
+ status = record.get("status")
92
+ funder["status"] = status
57
93
 
58
94
  # The ROR is always listed in identifiers, expected by serialization
59
95
  funder["identifiers"] = [{"identifier": funder["id"], "scheme": "ror"}]
60
96
  valid_schemes = set(funder_schemes.keys())
61
97
  fund_ref = "fundref"
62
98
  valid_schemes.add(fund_ref)
63
- for scheme, identifier in record.get("external_ids", {}).items():
64
- scheme = scheme.lower()
99
+ for identifier in record.get("external_ids"):
100
+ scheme = identifier["type"]
65
101
  if scheme in valid_schemes:
66
102
  value = identifier.get("preferred") or identifier.get("all")[0]
67
103
  if scheme == fund_ref:
68
104
  value = f"{funder_fundref_doi_prefix}/{value}"
69
105
  scheme = "doi"
70
-
71
106
  funder["identifiers"].append(
72
107
  {
73
108
  "identifier": value,
@@ -96,7 +131,7 @@ DATASTREAM_CONFIG = {
96
131
  {
97
132
  "type": "zip",
98
133
  "args": {
99
- "regex": "(?<!_schema_v2)\\.json$",
134
+ "regex": "_schema_v2\\.json$",
100
135
  },
101
136
  },
102
137
  {"type": "json"},
@@ -9,7 +9,15 @@
9
9
  },
10
10
  "country": {
11
11
  "type": "string",
12
- "description": "Represents a funder's origin country."
12
+ "description": "Represents a funder's origin country as a country code."
13
+ },
14
+ "country_name": {
15
+ "type": "string",
16
+ "description": "Represents a funder's origin country as a full name."
17
+ },
18
+ "location_name": {
19
+ "type": "string",
20
+ "description": "Represents a funder's location name (usually a city)."
13
21
  },
14
22
  "identifiers": {
15
23
  "description": "Alternate identifiers for the record.",
@@ -23,6 +31,30 @@
23
31
  "type": "string",
24
32
  "description": "Funders name."
25
33
  },
34
+ "acronym": {
35
+ "type": "string",
36
+ "description": "Acronym for funders name."
37
+ },
38
+ "status": {
39
+ "type": "string",
40
+ "description": "Status of the funder."
41
+ },
42
+ "aliases": {
43
+ "description": "Alternate names for the funder.",
44
+ "type": "array",
45
+ "items": {
46
+ "type": "string"
47
+ },
48
+ "uniqueItems": true
49
+ },
50
+ "types": {
51
+ "description": "Types of funders.",
52
+ "type": "array",
53
+ "items": {
54
+ "type": "string"
55
+ },
56
+ "uniqueItems": true
57
+ },
26
58
  "title": {
27
59
  "$ref": "local://vocabularies/definitions-v1.0.0.json#/title"
28
60
  }
@@ -57,6 +57,24 @@
57
57
  "country": {
58
58
  "type": "text"
59
59
  },
60
+ "country_name": {
61
+ "type": "text"
62
+ },
63
+ "location_name": {
64
+ "type": "text"
65
+ },
66
+ "acronym": {
67
+ "type": "text"
68
+ },
69
+ "status": {
70
+ "type": "keyword"
71
+ },
72
+ "aliases": {
73
+ "type": "text"
74
+ },
75
+ "types": {
76
+ "type": "keyword"
77
+ },
60
78
  "id": {
61
79
  "type": "keyword"
62
80
  },
@@ -57,6 +57,24 @@
57
57
  "country": {
58
58
  "type": "text"
59
59
  },
60
+ "country_name": {
61
+ "type": "text"
62
+ },
63
+ "location_name": {
64
+ "type": "text"
65
+ },
66
+ "acronym": {
67
+ "type": "text"
68
+ },
69
+ "status": {
70
+ "type": "keyword"
71
+ },
72
+ "aliases": {
73
+ "type": "text"
74
+ },
75
+ "types": {
76
+ "type": "keyword"
77
+ },
60
78
  "id": {
61
79
  "type": "keyword"
62
80
  },
@@ -57,6 +57,24 @@
57
57
  "country": {
58
58
  "type": "text"
59
59
  },
60
+ "country_name": {
61
+ "type": "text"
62
+ },
63
+ "location_name": {
64
+ "type": "text"
65
+ },
66
+ "acronym": {
67
+ "type": "text"
68
+ },
69
+ "status": {
70
+ "type": "keyword"
71
+ },
72
+ "aliases": {
73
+ "type": "text"
74
+ },
75
+ "types": {
76
+ "type": "keyword"
77
+ },
60
78
  "id": {
61
79
  "type": "keyword"
62
80
  },
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
3
  # Copyright (C) 2021-2022 CERN.
4
+ # Copyright (C) 2024 California Institute 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
@@ -43,6 +44,8 @@ class FunderSchema(BaseVocabularySchema):
43
44
  required=True, validate=validate.Length(min=1, error=_("Name cannot be blank."))
44
45
  )
45
46
  country = SanitizedUnicode()
47
+ country_name = SanitizedUnicode()
48
+ location_name = SanitizedUnicode()
46
49
  identifiers = IdentifierSet(
47
50
  fields.Nested(
48
51
  partial(
@@ -57,6 +60,11 @@ class FunderSchema(BaseVocabularySchema):
57
60
  validate=validate.Length(min=1, error=_("PID cannot be blank."))
58
61
  )
59
62
 
63
+ acronym = SanitizedUnicode()
64
+ aliases = fields.List(SanitizedUnicode())
65
+ status = SanitizedUnicode()
66
+ types = fields.List(SanitizedUnicode())
67
+
60
68
  @validates_schema
61
69
  def validate_id(self, data, **kwargs):
62
70
  """Validates ID."""
@@ -15,7 +15,6 @@ import re
15
15
  import tarfile
16
16
  import zipfile
17
17
  from abc import ABC, abstractmethod
18
- from collections import defaultdict
19
18
  from json.decoder import JSONDecodeError
20
19
 
21
20
  import requests
@@ -80,7 +79,12 @@ class TarReader(BaseReader):
80
79
  def read(self, item=None, *args, **kwargs):
81
80
  """Opens a tar archive or uses the given file pointer."""
82
81
  if item:
83
- yield from self._iter(fp=item, *args, **kwargs)
82
+ if isinstance(item, tarfile.TarFile):
83
+ yield from self._iter(fp=item, *args, **kwargs)
84
+ else:
85
+ # If the item is not already a TarFile (e.g. if it is a BytesIO), try to create a TarFile from the item.
86
+ with tarfile.open(mode=self._mode, fileobj=item) as archive:
87
+ yield from self._iter(fp=archive, *args, **kwargs)
84
88
  else:
85
89
  with tarfile.open(self._origin, self._mode) as archive:
86
90
  yield from self._iter(fp=archive, *args, **kwargs)
@@ -136,7 +140,12 @@ class ZipReader(BaseReader):
136
140
  """Opens a Zip archive or uses the given file pointer."""
137
141
  # https://docs.python.org/3/library/zipfile.html
138
142
  if item:
139
- yield from self._iter(fp=item, *args, **kwargs)
143
+ if isinstance(item, zipfile.ZipFile):
144
+ yield from self._iter(fp=item, *args, **kwargs)
145
+ else:
146
+ # If the item is not already a ZipFile (e.g. if it is a BytesIO), try to create a ZipFile from the item.
147
+ with zipfile.ZipFile(item, **self._options) as archive:
148
+ yield from self._iter(fp=archive, *args, **kwargs)
140
149
  else:
141
150
  with zipfile.ZipFile(self._origin, **self._options) as archive:
142
151
  yield from self._iter(fp=archive, *args, **kwargs)
@@ -0,0 +1,78 @@
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
+ from copy import deepcopy
11
+
12
+ import yaml
13
+ from invenio_records_resources.proxies import current_service_registry
14
+
15
+ from .contrib.awards.datastreams import DATASTREAM_CONFIG as awards_ds_config
16
+ from .contrib.funders.datastreams import DATASTREAM_CONFIG as funders_ds_config
17
+ from .contrib.names.datastreams import DATASTREAM_CONFIG as names_ds_config
18
+
19
+
20
+ class VocabularyConfig:
21
+ """Vocabulary Config Factory."""
22
+
23
+ config = None
24
+ vocabulary_name = None
25
+
26
+ def get_config(self, filepath=None, origin=None):
27
+ """Get the configuration for the vocabulary."""
28
+ config = deepcopy(self.config)
29
+ if filepath:
30
+ with open(filepath, encoding="utf-8") as f:
31
+ config = yaml.safe_load(f).get(self.vocabulary_name)
32
+ if origin:
33
+ config["readers"][0].setdefault("args", {})
34
+ config["readers"][0]["args"]["origin"] = origin
35
+ return config
36
+
37
+ def get_service(self):
38
+ """Get the service for the vocabulary."""
39
+ return current_service_registry.get(self.vocabulary_name)
40
+
41
+
42
+ class NamesVocabularyConfig(VocabularyConfig):
43
+ """Names Vocabulary Config."""
44
+
45
+ config = names_ds_config
46
+ vocabulary_name = "names"
47
+
48
+
49
+ class FundersVocabularyConfig(VocabularyConfig):
50
+ """Funders Vocabulary Config."""
51
+
52
+ config = funders_ds_config
53
+ vocabulary_name = "funders"
54
+
55
+ def get_service(self):
56
+ """Get the service for the vocabulary."""
57
+ raise NotImplementedError("Service not implemented for Funders")
58
+
59
+
60
+ class AwardsVocabularyConfig(VocabularyConfig):
61
+ """Awards Vocabulary Config."""
62
+
63
+ config = awards_ds_config
64
+ vocabulary_name = "awards"
65
+
66
+ def get_service(self):
67
+ """Get the service for the vocabulary."""
68
+ raise NotImplementedError("Service not implemented for Awards")
69
+
70
+
71
+ def get_vocabulary_config(vocabulary):
72
+ """Factory function to get the appropriate Vocabulary Config."""
73
+ vocab_config = {
74
+ "names": NamesVocabularyConfig,
75
+ "funders": FundersVocabularyConfig,
76
+ "awards": AwardsVocabularyConfig,
77
+ }
78
+ return vocab_config.get(vocabulary, VocabularyConfig)()
@@ -11,6 +11,7 @@ from celery import shared_task
11
11
  from flask import current_app
12
12
 
13
13
  from ..datastreams.factories import DataStreamFactory
14
+ from ..factories import get_vocabulary_config
14
15
 
15
16
 
16
17
  @shared_task(ignore_result=True)
@@ -26,3 +27,32 @@ def process_datastream(config):
26
27
  if result.errors:
27
28
  for err in result.errors:
28
29
  current_app.logger.error(err)
30
+
31
+
32
+ @shared_task()
33
+ def import_funders():
34
+ """Import the funders vocabulary.
35
+
36
+ Only new records are imported.
37
+ Existing records are not updated.
38
+ """
39
+ vc = get_vocabulary_config("funders")
40
+ config = vc.get_config()
41
+
42
+ # When importing funders via a Celery task, make sure that we are automatically downloading the ROR file,
43
+ # instead of relying on a local file on the file system.
44
+ if config["readers"][0]["type"] == "ror-http":
45
+ readers_config_with_ror_http = config["readers"]
46
+ else:
47
+ readers_config_with_ror_http = [{"type": "ror-http"}] + config["readers"]
48
+
49
+ ds = DataStreamFactory.create(
50
+ readers_config=readers_config_with_ror_http,
51
+ transformers_config=config.get("transformers"),
52
+ writers_config=config["writers"],
53
+ )
54
+
55
+ for result in ds.process():
56
+ if result.errors:
57
+ for err in result.errors:
58
+ current_app.logger.error(err)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: invenio-vocabularies
3
- Version: 3.4.1
3
+ Version: 4.0.0
4
4
  Summary: "Invenio module for managing vocabularies."
5
5
  Home-page: https://github.com/inveniosoftware/invenio-vocabularies
6
6
  Author: CERN
@@ -11,7 +11,7 @@ Platform: any
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Requires-Python: >=3.7
13
13
  Requires-Dist: invenio-i18n <3.0.0,>=2.0.0
14
- Requires-Dist: invenio-records-resources <6.0.0,>=5.0.0
14
+ Requires-Dist: invenio-records-resources <7.0.0,>=6.0.0
15
15
  Requires-Dist: lxml >=4.5.0
16
16
  Requires-Dist: PyYAML >=5.4.1
17
17
  Provides-Extra: elasticsearch7
@@ -74,10 +74,15 @@ https://invenio-vocabularies.readthedocs.io/
74
74
  Changes
75
75
  =======
76
76
 
77
- Version 3.4.1 (released 2024-04-19)
78
-
79
- - funders: exclude ROR schema v2 json (#309)
77
+ Version 4.0.0 (released 2024-06-04)
80
78
 
79
+ - datastreams: implement factories for generating vocabulary configurations
80
+ - datastreams: added ROR HTTP reader
81
+ - funders: use ROR v2 dump instead of v1
82
+ - datastreams: added celery task for funders using ROR HTTP reader
83
+ - datastreams: add OpenAIRE Project HTTP Reader
84
+ - datastreams: fix OpenAIRE graph dataset parsing
85
+ - installation: upgrade invenio-records-resources
81
86
 
82
87
  Version 3.4.0 (released 2024-04-19)
83
88
 
@@ -1,7 +1,8 @@
1
- invenio_vocabularies/__init__.py,sha256=XQVii6AE-S9xrNhp1WfDG0rzm6QbbjbisAMnQix5Qt4,377
2
- invenio_vocabularies/cli.py,sha256=Ymuy0l846eJXIA4UybunSqq7P9m2N0OdTtj6nEgd1-0,6355
1
+ invenio_vocabularies/__init__.py,sha256=V5z9SuZ3KH-yIAm6O_EOrw27GQcnW3S76XWEknRVwBU,377
2
+ invenio_vocabularies/cli.py,sha256=9Gfm3jjNDX3oQnYXupxSuqwXvWnJmsQ9GmyekJ1h5Kg,5239
3
3
  invenio_vocabularies/config.py,sha256=mLypkeVrPKZPtokvHSF-_Q7YcV4sCVONiyhGhu-34hI,3772
4
4
  invenio_vocabularies/ext.py,sha256=ukuvkhkLPBy2AITFLojLYTIUlP2qcbHNkt6ES8i1TwY,5310
5
+ invenio_vocabularies/factories.py,sha256=iz1MzCwzZoj0cSxlYmr_RWSOL6gRXKHqJnFznshpcvQ,2421
5
6
  invenio_vocabularies/fixtures.py,sha256=nNWwH04HFASjfj1oy5kMdcQGKmVjzUuA5wSw-ER1QAg,1585
6
7
  invenio_vocabularies/proxies.py,sha256=H_bcJXPTwGbErx-pF89ChayOZcXJXJGZW7O0xkJkYQ4,693
7
8
  invenio_vocabularies/views.py,sha256=JCUA6yzF_160U1oAFUIgGt4MigsLQ9MiHOInHBQw98M,1381
@@ -23,7 +24,7 @@ invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/index.js,sha
23
24
  invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/index.js,sha256=iSx-bdQkKj6XA9NAam31bdcQmFygljQnjLcFjjK3lwU,245
24
25
  invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/index.js,sha256=7sSg482yJODQHU4jkP-hWJjpBOw7ubFr5nPZl5D_1gQ,262
25
26
  invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/AwardResults.js,sha256=AgqJg9GEcJvKZR4plZsH0j7cm9C3yjT9YCPI6uvmOyc,3499
26
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js,sha256=PDgCRtbOXkXOQCodbxoieHenhXrXhSTMQk4LcS-AjYg,4794
27
+ invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js,sha256=DTEt6SV56bc7LC1KczUixHkdBUF3lGQs8rCc-YLii4Q,4804
27
28
  invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FunderDropdown.js,sha256=pPMB9Hirc8z3efquecrO_CKlfXE1ws6OqDB7fB30l5k,2431
28
29
  invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js,sha256=aqQ92Z0WJ4rfXz0E8KIIN4-qjORdQYxXPa5eS5b_g8A,6912
29
30
  invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.test.js,sha256=z_4lWkf3h9Uin8OzSBSKY1wpUxtbnjE2kRUjW80jAhA,35
@@ -60,7 +61,7 @@ invenio_vocabularies/contrib/awards/__init__.py,sha256=KwCmwFalz-3pDs9iTa5TKUidB
60
61
  invenio_vocabularies/contrib/awards/api.py,sha256=OXukE7PLXs45BTtqVrhvGBNqLmQaI-CgXmHTCi36LZk,303
61
62
  invenio_vocabularies/contrib/awards/awards.py,sha256=N-BNE2ffUu-IlfKFBjMjfYqxL0vmpO8YMPe4q4e-rTo,2695
62
63
  invenio_vocabularies/contrib/awards/config.py,sha256=PlDHabkWDUzwa1Fvk_U2hG83kQYBqM1IyChg8Yg_VlY,1630
63
- invenio_vocabularies/contrib/awards/datastreams.py,sha256=wsr-H2qJ2TjFBGoHDx1eLkoDh1DcH9UOAKzXgMdtUpg,3849
64
+ invenio_vocabularies/contrib/awards/datastreams.py,sha256=4BRifAmmEw0fUxo9JK5ZUXfSrSv1wllPvuGVOfick54,8244
64
65
  invenio_vocabularies/contrib/awards/models.py,sha256=mM-kSNf7kDH3oIbV8epxxbUi7muYqi4JreXxgWXlVzw,318
65
66
  invenio_vocabularies/contrib/awards/resources.py,sha256=_9YTqbhz8axFXGhG5y4WyjE27p9n-7e3c6HoBRditPA,411
66
67
  invenio_vocabularies/contrib/awards/schema.py,sha256=bg-09nvhEn-ipzaztK1iNbKc59saAM8v8hAW9zpKDAc,2786
@@ -75,26 +76,29 @@ invenio_vocabularies/contrib/awards/mappings/os-v2/__init__.py,sha256=9gRLFRtjhN
75
76
  invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json,sha256=KBAVvfuZDe_K8YISgbKCcpCZVVh-vkQIbt00OP1MPZA,1477
76
77
  invenio_vocabularies/contrib/awards/mappings/v7/__init__.py,sha256=fERdPp7K7ajaoUu_4_HVLfF-WD_qcl7QgST6yGb68s4,253
77
78
  invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json,sha256=KBAVvfuZDe_K8YISgbKCcpCZVVh-vkQIbt00OP1MPZA,1477
79
+ invenio_vocabularies/contrib/common/__init__.py,sha256=DdbGYRthEpQtObhY_YK4vOT9Zf7FFagQ6qtUjJOYw-M,247
80
+ invenio_vocabularies/contrib/common/ror/__init__.py,sha256=3u2-fre1SQ-4nz3Ay0nxj3ntmMZ8Ujh_4eV-fyxfmtc,239
81
+ invenio_vocabularies/contrib/common/ror/datastreams.py,sha256=osd_-QarJkzsPQAAAK4LEs7BJLNtWUCGGuq1yztnnIU,2665
78
82
  invenio_vocabularies/contrib/funders/__init__.py,sha256=YxFXBDnT7NM8rFwxT_Ge3xXR2n17EM0alknQq7r_Bt8,478
79
83
  invenio_vocabularies/contrib/funders/api.py,sha256=QKGGeSnPHSoBfucvpaVruXT_txYidofZ080G3IxFkIo,306
80
- invenio_vocabularies/contrib/funders/config.py,sha256=-GncyorRv86fbM1WXi-w7X9QW7GqJz_uqs75Icx5Y3w,1704
81
- invenio_vocabularies/contrib/funders/datastreams.py,sha256=3jDLhRD_tAjvgwY_kEQMi4GLROKycgq3wURnZRJLmP0,3542
84
+ invenio_vocabularies/contrib/funders/config.py,sha256=BbzCRIcPxSwDllhjnGmdec5A126Zs1I4ZyVUCGi3jRA,1756
85
+ invenio_vocabularies/contrib/funders/datastreams.py,sha256=ezBtXebBmUmlPSLD8hA8BuR55kREtGShWeKTVXa3QUk,4995
82
86
  invenio_vocabularies/contrib/funders/facets.py,sha256=a068TVtt74Ncu0latb177LFK8EdnpbMOWecAKozA04M,1245
83
87
  invenio_vocabularies/contrib/funders/funders.py,sha256=nHqUoyRRW9oruN-ghmwzb9QIz_jyBgxNUpk41yvcso8,2292
84
88
  invenio_vocabularies/contrib/funders/models.py,sha256=RAU-_YVOUNVCn03_XGJ2czcVwXTaZPk5w7X_bMAgMOk,314
85
89
  invenio_vocabularies/contrib/funders/resources.py,sha256=He4gXd737ovdrHL-HB9dX7AGxp1BVJ9QteIO7JWUVSE,415
86
- invenio_vocabularies/contrib/funders/schema.py,sha256=REXr_Jh3No7lCLjOo-ZuCQqsrPjQwm-pIHV5GQ6-O64,2190
90
+ invenio_vocabularies/contrib/funders/schema.py,sha256=tEOdMU2i0z_3PkXL1yD1qIJeoAy_8n22xxv8V7iltz0,2480
87
91
  invenio_vocabularies/contrib/funders/serializer.py,sha256=UwFsPnQiMUWx2lMQgh3Bv1uosubTUF5GFIAxwECNoAk,927
88
92
  invenio_vocabularies/contrib/funders/services.py,sha256=M-kQwtVOGygHe44vXr1L4E5Vvnpoco4KO0LRYwzhvMc,375
89
93
  invenio_vocabularies/contrib/funders/jsonschemas/__init__.py,sha256=O4XwUXCk-gx_K2LDVJOLkq07-ibsUkpT1vPaebgd0Gk,247
90
- invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json,sha256=V_E72iXF__W4SzxlHw8u3wQC9feZJWIalDRO95fTICw,783
94
+ invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json,sha256=5Pyqf5ig16D2OnrFf1I3ws9PIShUPtZBrT8O-bfIMbw,1598
91
95
  invenio_vocabularies/contrib/funders/mappings/__init__.py,sha256=aSr-tZd9rsjet6leeS336gdSdZHXwZKdaPStNtVNQVk,244
92
96
  invenio_vocabularies/contrib/funders/mappings/os-v1/__init__.py,sha256=xXEX3tacmXp0I1KFtDw7ohIahozd2oIGp1UN40IhFic,251
93
- invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json,sha256=hxiPQnaZfb6V2v1GrrWiZAeBQRvlrAJ6-P-79wrJDho,1275
97
+ invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json,sha256=HtYnmonywSGEov-1twath81ugIUFj-kV2oIsWB0JG5w,1595
94
98
  invenio_vocabularies/contrib/funders/mappings/os-v2/__init__.py,sha256=YvMRlKYTnEmyTzI9smZp_lO3w-zcK-8IpqT-jGUXEEY,251
95
- invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json,sha256=hxiPQnaZfb6V2v1GrrWiZAeBQRvlrAJ6-P-79wrJDho,1275
99
+ invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json,sha256=HtYnmonywSGEov-1twath81ugIUFj-kV2oIsWB0JG5w,1595
96
100
  invenio_vocabularies/contrib/funders/mappings/v7/__init__.py,sha256=yFHmi3QYD65YKzLU5vMEtwAZn0gwkFYa6Db_tSbHjKE,254
97
- invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json,sha256=hxiPQnaZfb6V2v1GrrWiZAeBQRvlrAJ6-P-79wrJDho,1275
101
+ invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json,sha256=HtYnmonywSGEov-1twath81ugIUFj-kV2oIsWB0JG5w,1595
98
102
  invenio_vocabularies/contrib/names/__init__.py,sha256=DBfsM7JMETZGaV5QmXEwE7zhCaAXvc2SZN6uXnW_V-c,451
99
103
  invenio_vocabularies/contrib/names/api.py,sha256=sEPn_jFX3gyoxgbdEUSIvOoPCUI8pocI6qCZO6mzCgQ,300
100
104
  invenio_vocabularies/contrib/names/config.py,sha256=hKDTEEBYGYOY6sMOArZjjkq2HJ6MJtRZp1geGLAFgRg,1735
@@ -135,7 +139,7 @@ invenio_vocabularies/datastreams/__init__.py,sha256=VPefh6k4Q3eYxKIW8I5zXUGucntp
135
139
  invenio_vocabularies/datastreams/datastreams.py,sha256=_jSXv2yAvSjt8btMoLJlqXOkqBzYb3Xe9m2GH50Nwag,3987
136
140
  invenio_vocabularies/datastreams/errors.py,sha256=IDUZ3gNtYGrhcOgApHCms1gNNJTyJzoMPmG5JtIeYNU,678
137
141
  invenio_vocabularies/datastreams/factories.py,sha256=js76i-cjcEzslgemsrwSzXQMy3SXtY3iFcsO-OlrMIM,2182
138
- invenio_vocabularies/datastreams/readers.py,sha256=iUA04MbuyEnTyQL8yImLCn6XLLTdGMssQnWf9yDZp8c,6768
142
+ invenio_vocabularies/datastreams/readers.py,sha256=M4CRKyj7GH4iMernMqNRWgPlXjgVZTWD2X-Tp4spxEA,7409
139
143
  invenio_vocabularies/datastreams/transformers.py,sha256=wspny-kazYMRHjkkyFfRVNIYzJxbjAqokRCBQ_-gXcY,1357
140
144
  invenio_vocabularies/datastreams/writers.py,sha256=a85RYE6FAQqNF7obmlqHLW_ztJ1a6biS_i9a_yaKJw4,3615
141
145
  invenio_vocabularies/datastreams/xml.py,sha256=HFa-lfxj7kFrr2IjeN1jxSLDfcvpBwO9nZLZF2-BryE,997
@@ -167,7 +171,7 @@ invenio_vocabularies/services/permissions.py,sha256=one3NvNFYq-q15e6xxf85OkH1bWZ
167
171
  invenio_vocabularies/services/querystr.py,sha256=X3JHVF9B0O0iLWrnW3ok_bf_8jA-Cs_oAcYYkGOm3Uw,1829
168
172
  invenio_vocabularies/services/schema.py,sha256=ShnnH_ILHZGxE546J6Jsdwdeix6jLubSRomzf472DK8,4307
169
173
  invenio_vocabularies/services/service.py,sha256=W3wtKOttQjOr8Nkaus6m3KRuCMBqBsWUCAVv7Dj8bvM,7392
170
- invenio_vocabularies/services/tasks.py,sha256=zTAWdnI5celWBKrF986wQzCmkOTGOwTghtN7U5FMZ5Q,783
174
+ invenio_vocabularies/services/tasks.py,sha256=rk-foPEhLL8kKoi8EPuUIJSjLX07NM3Fx8KjuY6Kqyk,1767
171
175
  invenio_vocabularies/services/custom_fields/__init__.py,sha256=QgvSsn-S1xLzbZ57pjjGTt5oI3HqzXHVjwGTtuPgzN8,421
172
176
  invenio_vocabularies/services/custom_fields/subject.py,sha256=k-qXbCjXPTk-Xo_W5sojFpQbkntv50odROiNyJHMzL8,2234
173
177
  invenio_vocabularies/services/custom_fields/vocabulary.py,sha256=eCvqrNloMMCCvqR49IQwzk2p4xapx5_bmQhd6ByJZFM,3019
@@ -267,10 +271,10 @@ invenio_vocabularies/translations/zh_CN/LC_MESSAGES/messages.mo,sha256=g1I5aNO8r
267
271
  invenio_vocabularies/translations/zh_CN/LC_MESSAGES/messages.po,sha256=vg8qC8ofpAdJ3mQz7mWM1ylKDpiNWXFs7rlMdSPkgKk,4629
268
272
  invenio_vocabularies/translations/zh_TW/LC_MESSAGES/messages.mo,sha256=cqSm8NtMAwrP9O6qbmtkDtRT1e9D93qpsJN5X9_PPVw,600
269
273
  invenio_vocabularies/translations/zh_TW/LC_MESSAGES/messages.po,sha256=9ACePz_EpB-LfcIJajZ2kp8Q04tcdrQLOtug162ZUss,4115
270
- invenio_vocabularies-3.4.1.dist-info/AUTHORS.rst,sha256=8d0p_WWE1r9DavvzMDi2D4YIGBHiMYcN3LYxqQOj8sY,291
271
- invenio_vocabularies-3.4.1.dist-info/LICENSE,sha256=UvI8pR8jGWqe0sTkb_hRG6eIrozzWwWzyCGEpuXX4KE,1062
272
- invenio_vocabularies-3.4.1.dist-info/METADATA,sha256=uBy7poGYcqlln0zyJcfHFu_wsECKRyt9E-jcmzOlWA4,6627
273
- invenio_vocabularies-3.4.1.dist-info/WHEEL,sha256=-G_t0oGuE7UD0DrSpVZnq1hHMBV9DD2XkS5v7XpmTnk,110
274
- invenio_vocabularies-3.4.1.dist-info/entry_points.txt,sha256=Cca3c5XGXkQItZ2-1oeN5xffwM8I1J3XjSvdq9-l_Bw,2497
275
- invenio_vocabularies-3.4.1.dist-info/top_level.txt,sha256=x1gRNbaODF_bCD0SBLM3nVOFPGi06cmGX5X94WKrFKk,21
276
- invenio_vocabularies-3.4.1.dist-info/RECORD,,
274
+ invenio_vocabularies-4.0.0.dist-info/AUTHORS.rst,sha256=8d0p_WWE1r9DavvzMDi2D4YIGBHiMYcN3LYxqQOj8sY,291
275
+ invenio_vocabularies-4.0.0.dist-info/LICENSE,sha256=UvI8pR8jGWqe0sTkb_hRG6eIrozzWwWzyCGEpuXX4KE,1062
276
+ invenio_vocabularies-4.0.0.dist-info/METADATA,sha256=qTKnTHUD2J1jWkNiPAHYhDGDBLQaVvhT_j02lM1KtrE,6950
277
+ invenio_vocabularies-4.0.0.dist-info/WHEEL,sha256=-G_t0oGuE7UD0DrSpVZnq1hHMBV9DD2XkS5v7XpmTnk,110
278
+ invenio_vocabularies-4.0.0.dist-info/entry_points.txt,sha256=wQd9reAcv3btbEyMD9FHdkMbM7GvYDJiGugCuqc14sA,2589
279
+ invenio_vocabularies-4.0.0.dist-info/top_level.txt,sha256=x1gRNbaODF_bCD0SBLM3nVOFPGi06cmGX5X94WKrFKk,21
280
+ invenio_vocabularies-4.0.0.dist-info/RECORD,,
@@ -28,6 +28,9 @@ invenio_vocabularies_ext = invenio_vocabularies.views:blueprint
28
28
  [invenio_base.finalize_app]
29
29
  invenio_vocabularies = invenio_vocabularies.ext:finalize_app
30
30
 
31
+ [invenio_celery.tasks]
32
+ invenio_vocabularies_services = invenio_vocabularies.services.tasks
33
+
31
34
  [invenio_db.alembic]
32
35
  invenio_vocabularies = invenio_vocabularies:alembic
33
36