invenio-vocabularies 9.1.2__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.
- invenio_vocabularies/__init__.py +16 -0
- invenio_vocabularies/administration/__init__.py +10 -0
- invenio_vocabularies/administration/views/__init__.py +10 -0
- invenio_vocabularies/administration/views/vocabularies.py +43 -0
- invenio_vocabularies/alembic/17c703ce1eb7_create_names_table.py +54 -0
- invenio_vocabularies/alembic/3ba812d80559_add_internal_name_id.py +36 -0
- invenio_vocabularies/alembic/4a9a4fd235f8_create_vocabulary_schemes.py +37 -0
- invenio_vocabularies/alembic/4f365fced43f_create_vocabularies_tables.py +92 -0
- invenio_vocabularies/alembic/55a700f897b6_add_names_and_afiliations_pid_column.py +96 -0
- invenio_vocabularies/alembic/6312f33645c1_create_affiliations_table.py +54 -0
- invenio_vocabularies/alembic/676dd587542d_create_funders_vocabulary_table.py +58 -0
- invenio_vocabularies/alembic/8ff82dfb0be8_create_vocabularies_branch.py +28 -0
- invenio_vocabularies/alembic/__init__.py +9 -0
- invenio_vocabularies/alembic/af2457652217_drop_unique_constraint_from_internal_id.py +37 -0
- invenio_vocabularies/alembic/e1146238edd3_create_awards_table.py +56 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.eslintrc.yml +11 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/.prettierrc +1 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/index.js +7 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/package.json +25 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/AwardResults.js +95 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js +139 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FunderDropdown.js +87 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js +244 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.test.js +1 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingFieldItem.js +152 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js +246 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/NoAwardResults.js +37 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/index.js +8 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/index.js +7 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/index.js +7 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/index.js +7 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/i18next.js +36 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/_generatedTranslations.js +66 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ar/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ar/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/bg/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/bg/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ca/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ca/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/cs/messages.po +97 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/cs/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/da/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/da/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/de/messages.po +98 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/de/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/el/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/el/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/en/messages.po +88 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/en/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/es/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/es/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/et/messages.po +95 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/et/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/fa/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/fa/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/fr/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/fr/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/hr/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/hr/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/hu/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/hu/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/index.js +24 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/it/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/it/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ja/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ja/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ka/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ka/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ko/messages.po +90 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ko/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/lt/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/lt/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/no/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/no/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/pl/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/pl/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/pt/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/pt/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ro/messages.po +95 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ro/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ru/messages.po +95 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/ru/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/sk/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/sk/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/sv/messages.po +98 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/sv/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/tr/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/tr/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/uk/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/uk/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/zh_CN/messages.po +96 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/zh_CN/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/zh_TW/messages.po +94 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/messages/zh_TW/translations.json +28 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/package.json +19 -0
- invenio_vocabularies/assets/semantic-ui/translations/invenio_vocabularies/translations.pot +88 -0
- invenio_vocabularies/cli.py +175 -0
- invenio_vocabularies/config.py +231 -0
- invenio_vocabularies/contrib/__init__.py +9 -0
- invenio_vocabularies/contrib/affiliations/__init__.py +20 -0
- invenio_vocabularies/contrib/affiliations/affiliations.py +61 -0
- invenio_vocabularies/contrib/affiliations/api.py +13 -0
- invenio_vocabularies/contrib/affiliations/config.py +79 -0
- invenio_vocabularies/contrib/affiliations/datastreams.py +301 -0
- invenio_vocabularies/contrib/affiliations/facets.py +36 -0
- invenio_vocabularies/contrib/affiliations/jsonschemas/__init__.py +9 -0
- invenio_vocabularies/contrib/affiliations/jsonschemas/affiliations/affiliation-v1.0.0.json +63 -0
- invenio_vocabularies/contrib/affiliations/mappings/__init__.py +10 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v1/__init__.py +9 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json +112 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v2.0.0.json +171 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v2/__init__.py +9 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json +112 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v2.0.0.json +171 -0
- invenio_vocabularies/contrib/affiliations/mappings/v7/__init__.py +9 -0
- invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json +112 -0
- invenio_vocabularies/contrib/affiliations/models.py +13 -0
- invenio_vocabularies/contrib/affiliations/resources.py +16 -0
- invenio_vocabularies/contrib/affiliations/schema.py +71 -0
- invenio_vocabularies/contrib/affiliations/services.py +15 -0
- invenio_vocabularies/contrib/awards/__init__.py +19 -0
- invenio_vocabularies/contrib/awards/api.py +13 -0
- invenio_vocabularies/contrib/awards/awards.py +96 -0
- invenio_vocabularies/contrib/awards/config.py +59 -0
- invenio_vocabularies/contrib/awards/datastreams.py +372 -0
- invenio_vocabularies/contrib/awards/jsonschemas/__init__.py +9 -0
- invenio_vocabularies/contrib/awards/jsonschemas/awards/award-v1.0.0.json +91 -0
- invenio_vocabularies/contrib/awards/mappings/__init__.py +9 -0
- invenio_vocabularies/contrib/awards/mappings/os-v1/__init__.py +9 -0
- invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json +147 -0
- invenio_vocabularies/contrib/awards/mappings/os-v2/__init__.py +9 -0
- invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json +147 -0
- invenio_vocabularies/contrib/awards/mappings/v7/__init__.py +9 -0
- invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json +147 -0
- invenio_vocabularies/contrib/awards/models.py +13 -0
- invenio_vocabularies/contrib/awards/resources.py +16 -0
- invenio_vocabularies/contrib/awards/schema.py +119 -0
- invenio_vocabularies/contrib/awards/serializer.py +47 -0
- invenio_vocabularies/contrib/awards/services.py +15 -0
- invenio_vocabularies/contrib/common/__init__.py +9 -0
- invenio_vocabularies/contrib/common/openaire/__init__.py +9 -0
- invenio_vocabularies/contrib/common/openaire/datastreams.py +84 -0
- invenio_vocabularies/contrib/common/ror/__init__.py +9 -0
- invenio_vocabularies/contrib/common/ror/datastreams.py +230 -0
- invenio_vocabularies/contrib/funders/__init__.py +19 -0
- invenio_vocabularies/contrib/funders/api.py +13 -0
- invenio_vocabularies/contrib/funders/config.py +78 -0
- invenio_vocabularies/contrib/funders/datastreams.py +97 -0
- invenio_vocabularies/contrib/funders/facets.py +36 -0
- invenio_vocabularies/contrib/funders/funders.py +72 -0
- invenio_vocabularies/contrib/funders/jsonschemas/__init__.py +9 -0
- invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json +65 -0
- invenio_vocabularies/contrib/funders/mappings/__init__.py +9 -0
- invenio_vocabularies/contrib/funders/mappings/os-v1/__init__.py +9 -0
- invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json +90 -0
- invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v2.0.0.json +156 -0
- invenio_vocabularies/contrib/funders/mappings/os-v2/__init__.py +9 -0
- invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json +90 -0
- invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v2.0.0.json +156 -0
- invenio_vocabularies/contrib/funders/mappings/v7/__init__.py +9 -0
- invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json +90 -0
- invenio_vocabularies/contrib/funders/models.py +13 -0
- invenio_vocabularies/contrib/funders/resources.py +16 -0
- invenio_vocabularies/contrib/funders/schema.py +88 -0
- invenio_vocabularies/contrib/funders/serializer.py +33 -0
- invenio_vocabularies/contrib/funders/services.py +15 -0
- invenio_vocabularies/contrib/names/__init__.py +19 -0
- invenio_vocabularies/contrib/names/api.py +13 -0
- invenio_vocabularies/contrib/names/components.py +24 -0
- invenio_vocabularies/contrib/names/config.py +75 -0
- invenio_vocabularies/contrib/names/datastreams.py +483 -0
- invenio_vocabularies/contrib/names/jsonschemas/__init__.py +9 -0
- invenio_vocabularies/contrib/names/jsonschemas/names/name-v1.0.0.json +68 -0
- invenio_vocabularies/contrib/names/mappings/__init__.py +9 -0
- invenio_vocabularies/contrib/names/mappings/os-v1/__init__.py +9 -0
- invenio_vocabularies/contrib/names/mappings/os-v1/names/name-v1.0.0.json +101 -0
- invenio_vocabularies/contrib/names/mappings/os-v1/names/name-v2.0.0.json +165 -0
- invenio_vocabularies/contrib/names/mappings/os-v2/__init__.py +9 -0
- invenio_vocabularies/contrib/names/mappings/os-v2/names/name-v1.0.0.json +101 -0
- invenio_vocabularies/contrib/names/mappings/os-v2/names/name-v2.0.0.json +165 -0
- invenio_vocabularies/contrib/names/mappings/v7/__init__.py +9 -0
- invenio_vocabularies/contrib/names/mappings/v7/names/name-v1.0.0.json +101 -0
- invenio_vocabularies/contrib/names/models.py +13 -0
- invenio_vocabularies/contrib/names/names.py +80 -0
- invenio_vocabularies/contrib/names/permissions.py +30 -0
- invenio_vocabularies/contrib/names/resources.py +54 -0
- invenio_vocabularies/contrib/names/s3client.py +50 -0
- invenio_vocabularies/contrib/names/schema.py +121 -0
- invenio_vocabularies/contrib/names/services.py +64 -0
- invenio_vocabularies/contrib/subjects/__init__.py +22 -0
- invenio_vocabularies/contrib/subjects/api.py +14 -0
- invenio_vocabularies/contrib/subjects/config.py +90 -0
- invenio_vocabularies/contrib/subjects/datastreams.py +63 -0
- invenio_vocabularies/contrib/subjects/euroscivoc/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/euroscivoc/datastreams.py +101 -0
- invenio_vocabularies/contrib/subjects/facets.py +23 -0
- invenio_vocabularies/contrib/subjects/gemet/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/gemet/datastreams.py +140 -0
- invenio_vocabularies/contrib/subjects/jsonschemas/__init__.py +10 -0
- invenio_vocabularies/contrib/subjects/jsonschemas/subjects/subject-v1.0.0.json +69 -0
- invenio_vocabularies/contrib/subjects/mappings/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/mappings/os-v1/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/mappings/os-v1/subjects/subject-v1.0.0.json +96 -0
- invenio_vocabularies/contrib/subjects/mappings/os-v2/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/mappings/os-v2/subjects/subject-v1.0.0.json +96 -0
- invenio_vocabularies/contrib/subjects/mappings/v7/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/mappings/v7/subjects/subject-v1.0.0.json +96 -0
- invenio_vocabularies/contrib/subjects/mesh/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/mesh/datastreams.py +48 -0
- invenio_vocabularies/contrib/subjects/models.py +14 -0
- invenio_vocabularies/contrib/subjects/nvs/__init__.py +9 -0
- invenio_vocabularies/contrib/subjects/nvs/datastreams.py +114 -0
- invenio_vocabularies/contrib/subjects/resources.py +17 -0
- invenio_vocabularies/contrib/subjects/schema.py +101 -0
- invenio_vocabularies/contrib/subjects/services.py +30 -0
- invenio_vocabularies/contrib/subjects/subjects.py +55 -0
- invenio_vocabularies/datastreams/__init__.py +18 -0
- invenio_vocabularies/datastreams/datastreams.py +239 -0
- invenio_vocabularies/datastreams/errors.py +29 -0
- invenio_vocabularies/datastreams/factories.py +86 -0
- invenio_vocabularies/datastreams/readers.py +448 -0
- invenio_vocabularies/datastreams/tasks.py +115 -0
- invenio_vocabularies/datastreams/transformers.py +130 -0
- invenio_vocabularies/datastreams/writers.py +222 -0
- invenio_vocabularies/datastreams/xml.py +34 -0
- invenio_vocabularies/ext.py +179 -0
- invenio_vocabularies/factories.py +193 -0
- invenio_vocabularies/fixtures.py +52 -0
- invenio_vocabularies/jobs.py +207 -0
- invenio_vocabularies/proxies.py +27 -0
- invenio_vocabularies/records/__init__.py +9 -0
- invenio_vocabularies/records/api.py +53 -0
- invenio_vocabularies/records/jsonschemas/__init__.py +9 -0
- invenio_vocabularies/records/jsonschemas/vocabularies/definitions-v1.0.0.json +30 -0
- invenio_vocabularies/records/jsonschemas/vocabularies/vocabulary-v1.0.0.json +55 -0
- invenio_vocabularies/records/mappings/__init__.py +9 -0
- invenio_vocabularies/records/mappings/os-v1/__init__.py +9 -0
- invenio_vocabularies/records/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json +109 -0
- invenio_vocabularies/records/mappings/os-v2/__init__.py +9 -0
- invenio_vocabularies/records/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json +109 -0
- invenio_vocabularies/records/mappings/v7/__init__.py +9 -0
- invenio_vocabularies/records/mappings/v7/vocabularies/vocabulary-v1.0.0.json +109 -0
- invenio_vocabularies/records/models.py +90 -0
- invenio_vocabularies/records/pidprovider.py +118 -0
- invenio_vocabularies/records/systemfields/__init__.py +16 -0
- invenio_vocabularies/records/systemfields/pid.py +125 -0
- invenio_vocabularies/records/systemfields/relations.py +51 -0
- invenio_vocabularies/resources/__init__.py +23 -0
- invenio_vocabularies/resources/config.py +105 -0
- invenio_vocabularies/resources/resource.py +156 -0
- invenio_vocabularies/resources/schema.py +21 -0
- invenio_vocabularies/resources/serializer.py +39 -0
- invenio_vocabularies/services/__init__.py +19 -0
- invenio_vocabularies/services/components.py +58 -0
- invenio_vocabularies/services/config.py +173 -0
- invenio_vocabularies/services/custom_fields/__init__.py +17 -0
- invenio_vocabularies/services/custom_fields/subject.py +82 -0
- invenio_vocabularies/services/custom_fields/vocabulary.py +96 -0
- invenio_vocabularies/services/facets.py +114 -0
- invenio_vocabularies/services/generators.py +38 -0
- invenio_vocabularies/services/permissions.py +30 -0
- invenio_vocabularies/services/querystr.py +57 -0
- invenio_vocabularies/services/results.py +110 -0
- invenio_vocabularies/services/schema.py +163 -0
- invenio_vocabularies/services/service.py +189 -0
- invenio_vocabularies/services/tasks.py +38 -0
- invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/subjects.html +23 -0
- invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabularies-list.html +12 -0
- invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabulary-details.html +71 -0
- invenio_vocabularies/translations/ar/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ar/LC_MESSAGES/messages.po +277 -0
- invenio_vocabularies/translations/bg/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/bg/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/ca/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ca/LC_MESSAGES/messages.po +276 -0
- invenio_vocabularies/translations/cs/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/cs/LC_MESSAGES/messages.po +281 -0
- invenio_vocabularies/translations/da/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/da/LC_MESSAGES/messages.po +271 -0
- invenio_vocabularies/translations/de/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/de/LC_MESSAGES/messages.po +293 -0
- invenio_vocabularies/translations/el/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/el/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/es/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/es/LC_MESSAGES/messages.po +281 -0
- invenio_vocabularies/translations/et/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/et/LC_MESSAGES/messages.po +276 -0
- invenio_vocabularies/translations/fa/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/fa/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/fr/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/fr/LC_MESSAGES/messages.po +279 -0
- invenio_vocabularies/translations/hr/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/hr/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/hu/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/hu/LC_MESSAGES/messages.po +280 -0
- invenio_vocabularies/translations/it/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/it/LC_MESSAGES/messages.po +277 -0
- invenio_vocabularies/translations/ja/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ja/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/ka/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ka/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/ko/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ko/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/lt/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/lt/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/messages.pot +270 -0
- invenio_vocabularies/translations/no/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/no/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/pl/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/pl/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/pt/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/pt/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/ro/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ro/LC_MESSAGES/messages.po +280 -0
- invenio_vocabularies/translations/ru/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/ru/LC_MESSAGES/messages.po +276 -0
- invenio_vocabularies/translations/sk/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/sk/LC_MESSAGES/messages.po +276 -0
- invenio_vocabularies/translations/sv/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/sv/LC_MESSAGES/messages.po +280 -0
- invenio_vocabularies/translations/tr/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/tr/LC_MESSAGES/messages.po +277 -0
- invenio_vocabularies/translations/uk/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/uk/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/zh_CN/LC_MESSAGES/messages.po +276 -0
- invenio_vocabularies/translations/zh_TW/LC_MESSAGES/messages.mo +0 -0
- invenio_vocabularies/translations/zh_TW/LC_MESSAGES/messages.po +275 -0
- invenio_vocabularies/views.py +53 -0
- invenio_vocabularies/webpack.py +51 -0
- invenio_vocabularies-9.1.2.dist-info/METADATA +517 -0
- invenio_vocabularies-9.1.2.dist-info/RECORD +337 -0
- invenio_vocabularies-9.1.2.dist-info/WHEEL +6 -0
- invenio_vocabularies-9.1.2.dist-info/entry_points.txt +73 -0
- invenio_vocabularies-9.1.2.dist-info/licenses/AUTHORS.rst +13 -0
- invenio_vocabularies-9.1.2.dist-info/licenses/LICENSE +21 -0
- invenio_vocabularies-9.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2024-2025 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
|
+
"""NVS subjects datastreams, readers, transformers, and writers."""
|
|
10
|
+
|
|
11
|
+
from invenio_vocabularies.datastreams.errors import TransformerError
|
|
12
|
+
from invenio_vocabularies.datastreams.readers import RDFReader
|
|
13
|
+
from invenio_vocabularies.datastreams.transformers import RDFTransformer
|
|
14
|
+
|
|
15
|
+
from ..config import nvs_file_url
|
|
16
|
+
|
|
17
|
+
# Available with the "rdf" extra
|
|
18
|
+
try:
|
|
19
|
+
import rdflib
|
|
20
|
+
except ImportError:
|
|
21
|
+
rdflib = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NVSSubjectsTransformer(RDFTransformer):
|
|
25
|
+
"""
|
|
26
|
+
Transformer class to convert NVS RDF data to a dictionary format.
|
|
27
|
+
|
|
28
|
+
Input:
|
|
29
|
+
- Relevant fields:
|
|
30
|
+
- `skos:notation`: Primary identifier for the concept.
|
|
31
|
+
- `skos:prefLabel`: Preferred labels with language codes.
|
|
32
|
+
- `skos:altLabel`: Alternative labels (optional).
|
|
33
|
+
- `skos:definition`: Definitions of the concept.
|
|
34
|
+
- `owl:deprecated`: Boolean flag indicating if the concept is deprecated.
|
|
35
|
+
|
|
36
|
+
Output:
|
|
37
|
+
- A dictionary with the following structure:
|
|
38
|
+
{
|
|
39
|
+
"id": "SDN:P01::SAGEMSFM", # NVS-specific parameter ID (skos:notation).
|
|
40
|
+
"scheme": "NVS-P01", # The scheme name indicating this is a collection P01 from NERC Vocabulary Server (NVS).
|
|
41
|
+
"subject": "AMSSedAge", # The alternative label (skos:altLabel), if available, or None.
|
|
42
|
+
"title": {
|
|
43
|
+
"en": "14C age of Foraminiferida" # English preferred label (skos:prefLabel).
|
|
44
|
+
},
|
|
45
|
+
"props": {
|
|
46
|
+
"definitions": "Accelerated mass spectrometry on picked tests", # Definition of subject (skos:definition).
|
|
47
|
+
},
|
|
48
|
+
"identifiers": [
|
|
49
|
+
{
|
|
50
|
+
"scheme": "url", # Type of identifier (URL).
|
|
51
|
+
"identifier": "http://vocab.nerc.ac.uk/collection/P01/current/SAGEMSFM" # URI of the concept.
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def _get_subject_data(self, rdf_graph, subject):
|
|
58
|
+
"""Fetch all triples for a subject and organize them into a dictionary."""
|
|
59
|
+
data = {}
|
|
60
|
+
for predicate, obj in rdf_graph.predicate_objects(subject=subject):
|
|
61
|
+
predicate_name = str(predicate)
|
|
62
|
+
if predicate_name not in data:
|
|
63
|
+
data[predicate_name] = []
|
|
64
|
+
data[predicate_name].append(obj)
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
def _transform_entry(self, subject, rdf_graph):
|
|
68
|
+
"""Transform an entry to the required dictionary format."""
|
|
69
|
+
labels = self._get_labels(subject, rdf_graph)
|
|
70
|
+
subject_data = self._get_subject_data(rdf_graph, subject)
|
|
71
|
+
deprecated = subject_data.get(str(rdflib.namespace.OWL.deprecated), [False])
|
|
72
|
+
if deprecated and str(deprecated[0]).lower() == "true":
|
|
73
|
+
raise TransformerError(f"Skipping deprecated subject: {subject_data}")
|
|
74
|
+
|
|
75
|
+
notation = subject_data.get(str(self.skos_core.notation), [])
|
|
76
|
+
if notation:
|
|
77
|
+
id = str(notation[0])
|
|
78
|
+
else:
|
|
79
|
+
raise TransformerError(f"No id found for: {subject}")
|
|
80
|
+
|
|
81
|
+
pref_labels = [
|
|
82
|
+
obj for obj in subject_data.get(str(self.skos_core.prefLabel), [])
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
subject_text = str(pref_labels[0]) if pref_labels else labels["en"]
|
|
86
|
+
definition = str(subject_data.get(str(self.skos_core.definition), [None])[0])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"id": id,
|
|
90
|
+
"scheme": "NVS-P02",
|
|
91
|
+
"subject": subject_text,
|
|
92
|
+
"title": labels,
|
|
93
|
+
"props": {"definition": definition} if definition else {},
|
|
94
|
+
"identifiers": self._get_identifiers(subject),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Configuration for datastream
|
|
99
|
+
|
|
100
|
+
VOCABULARIES_DATASTREAM_TRANSFORMERS = {"nvs-transformer": NVSSubjectsTransformer}
|
|
101
|
+
|
|
102
|
+
DATASTREAM_CONFIG = {
|
|
103
|
+
"readers": [
|
|
104
|
+
{
|
|
105
|
+
"type": "http",
|
|
106
|
+
"args": {
|
|
107
|
+
"origin": nvs_file_url,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{"type": "rdf"},
|
|
111
|
+
],
|
|
112
|
+
"transformers": [{"type": "nvs-transformer"}],
|
|
113
|
+
"writers": [{"args": {"writer": {"type": "subjects-service"}}, "type": "async"}],
|
|
114
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 CERN.
|
|
4
|
+
# Copyright (C) 2021 Northwestern University.
|
|
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
|
+
|
|
10
|
+
"""Test the affiliation vocabulary resources."""
|
|
11
|
+
|
|
12
|
+
from .subjects import record_type
|
|
13
|
+
|
|
14
|
+
SubjectsResourceConfig = record_type.resource_config_cls
|
|
15
|
+
SubjectsResourceConfig.routes["item"] = "/<path:pid_value>"
|
|
16
|
+
|
|
17
|
+
SubjectsResource = record_type.resource_cls
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Northwestern University.
|
|
4
|
+
# Copyright (C) 2021-2024 CERN.
|
|
5
|
+
# Copyright (C) 2024 University of Münster.
|
|
6
|
+
# Copyright (C) 2025 Graz University of Technology.
|
|
7
|
+
#
|
|
8
|
+
# Invenio-Vocabularies is free software; you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the MIT License; see LICENSE file for more
|
|
10
|
+
# details.
|
|
11
|
+
|
|
12
|
+
"""Subjects schema."""
|
|
13
|
+
|
|
14
|
+
from functools import partial
|
|
15
|
+
|
|
16
|
+
from invenio_i18n import get_locale
|
|
17
|
+
from marshmallow import EXCLUDE, Schema, ValidationError, fields, pre_load, validate
|
|
18
|
+
from marshmallow_utils.fields import URL, IdentifierSet, SanitizedUnicode
|
|
19
|
+
from marshmallow_utils.schemas import IdentifierSchema
|
|
20
|
+
|
|
21
|
+
from ...services.schema import (
|
|
22
|
+
BaseVocabularySchema,
|
|
23
|
+
ContribVocabularyRelationSchema,
|
|
24
|
+
i18n_strings,
|
|
25
|
+
)
|
|
26
|
+
from .config import subject_schemes
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StringOrListOfStrings(fields.Raw):
|
|
30
|
+
"""Custom field to handle both string and list of strings."""
|
|
31
|
+
|
|
32
|
+
# TODO: Move this to marshmallow-utils for broader type support.
|
|
33
|
+
def _deserialize(self, value, attr, data, **kwargs):
|
|
34
|
+
if isinstance(value, str):
|
|
35
|
+
return fields.String()._deserialize(value, attr, data, **kwargs)
|
|
36
|
+
elif isinstance(value, list) and all(isinstance(item, str) for item in value):
|
|
37
|
+
return [
|
|
38
|
+
fields.String()._deserialize(item, attr, data, **kwargs)
|
|
39
|
+
for item in value
|
|
40
|
+
]
|
|
41
|
+
raise ValidationError("Invalid value. Must be a string or a list of strings.")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SubjectSchema(BaseVocabularySchema):
|
|
45
|
+
"""Service schema for subjects."""
|
|
46
|
+
|
|
47
|
+
# id in BaseRecordSchema is not required, but I don't see why it shouldn't
|
|
48
|
+
# be, while scheme and subject are required. So I am making it required
|
|
49
|
+
# here.
|
|
50
|
+
id = SanitizedUnicode(required=True)
|
|
51
|
+
scheme = SanitizedUnicode(required=True)
|
|
52
|
+
subject = SanitizedUnicode(required=True)
|
|
53
|
+
title = i18n_strings
|
|
54
|
+
props = fields.Dict(keys=SanitizedUnicode(), values=StringOrListOfStrings())
|
|
55
|
+
identifiers = IdentifierSet(
|
|
56
|
+
fields.Nested(
|
|
57
|
+
partial(
|
|
58
|
+
IdentifierSchema,
|
|
59
|
+
allowed_schemes=subject_schemes,
|
|
60
|
+
identifier_required=False,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
synonyms = fields.List(SanitizedUnicode())
|
|
65
|
+
|
|
66
|
+
@pre_load
|
|
67
|
+
def add_subject_from_title(self, data, **kwargs):
|
|
68
|
+
"""Add subject from title if not present."""
|
|
69
|
+
locale = get_locale().language
|
|
70
|
+
if "subject" not in data:
|
|
71
|
+
data["subject"] = data["title"].get(locale) or data["title"].values()[0]
|
|
72
|
+
return data
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SubjectRelationSchema(ContribVocabularyRelationSchema):
|
|
76
|
+
"""Schema to define an optional subject relation in another schema."""
|
|
77
|
+
|
|
78
|
+
# If re-running an OpenAIRE awards update on existing awards which already have subjects,
|
|
79
|
+
# the subject entries will contains `scheme` and `props`, which are unknown since they are `dump_only`.
|
|
80
|
+
# This makes the update exclude unknown field and go through with the update.
|
|
81
|
+
class Meta:
|
|
82
|
+
"""Metadata class."""
|
|
83
|
+
|
|
84
|
+
unknown = EXCLUDE
|
|
85
|
+
|
|
86
|
+
ftf_name = "subject"
|
|
87
|
+
parent_field_name = "subjects"
|
|
88
|
+
subject = SanitizedUnicode()
|
|
89
|
+
scheme = SanitizedUnicode(dump_only=True)
|
|
90
|
+
title = fields.Dict(dump_only=True)
|
|
91
|
+
props = fields.Dict(dump_only=True)
|
|
92
|
+
identifiers = IdentifierSet(
|
|
93
|
+
fields.Nested(
|
|
94
|
+
partial(
|
|
95
|
+
IdentifierSchema,
|
|
96
|
+
allowed_schemes=subject_schemes,
|
|
97
|
+
identifier_required=False,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
synonyms = fields.List(SanitizedUnicode(), dump_only=True)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 CERN.
|
|
4
|
+
# Copyright (C) 2021 Northwestern University.
|
|
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
|
+
|
|
10
|
+
"""Subjects services."""
|
|
11
|
+
|
|
12
|
+
from invenio_db import db
|
|
13
|
+
|
|
14
|
+
from ...records.models import VocabularyScheme
|
|
15
|
+
from .subjects import record_type
|
|
16
|
+
|
|
17
|
+
SubjectsServiceConfig = record_type.service_config_cls
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SubjectsService(record_type.service_cls):
|
|
21
|
+
"""Subjects service."""
|
|
22
|
+
|
|
23
|
+
def create_scheme(self, identity, id_, name="", uri=""):
|
|
24
|
+
"""Create a row for the subject scheme metadata."""
|
|
25
|
+
self.require_permission(identity, "manage")
|
|
26
|
+
scheme = VocabularyScheme.create(
|
|
27
|
+
id=id_, parent_id="subjects", name=name, uri=uri
|
|
28
|
+
)
|
|
29
|
+
db.session.commit()
|
|
30
|
+
return scheme
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2020-2021 CERN.
|
|
4
|
+
# Copyright (C) 2021 Northwestern University.
|
|
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
|
+
|
|
10
|
+
"""Vocabulary subjects."""
|
|
11
|
+
|
|
12
|
+
from flask_resources import JSONSerializer, ResponseHandler
|
|
13
|
+
from invenio_records.dumpers import SearchDumper
|
|
14
|
+
from invenio_records.dumpers.indexedat import IndexedAtDumperExt
|
|
15
|
+
from invenio_records_resources.factories.factory import RecordTypeFactory
|
|
16
|
+
from invenio_records_resources.resources.records.headers import etag_headers
|
|
17
|
+
|
|
18
|
+
from ...records.pidprovider import PIDProviderFactory
|
|
19
|
+
from ...records.systemfields import BaseVocabularyPIDFieldContext
|
|
20
|
+
from ...services.permissions import PermissionPolicy
|
|
21
|
+
from .config import SubjectsSearchOptions, service_components
|
|
22
|
+
from .schema import SubjectSchema
|
|
23
|
+
|
|
24
|
+
record_type = RecordTypeFactory(
|
|
25
|
+
"Subject",
|
|
26
|
+
# Data layer
|
|
27
|
+
pid_field_kwargs={
|
|
28
|
+
"create": False,
|
|
29
|
+
"provider": PIDProviderFactory.create(pid_type="sub"),
|
|
30
|
+
"context_cls": BaseVocabularyPIDFieldContext,
|
|
31
|
+
},
|
|
32
|
+
schema_version="1.0.0",
|
|
33
|
+
schema_path="local://subjects/subject-v1.0.0.json",
|
|
34
|
+
record_dumper=SearchDumper(
|
|
35
|
+
extensions=[
|
|
36
|
+
IndexedAtDumperExt(),
|
|
37
|
+
]
|
|
38
|
+
),
|
|
39
|
+
# Service layer
|
|
40
|
+
service_id="subjects",
|
|
41
|
+
service_schema=SubjectSchema,
|
|
42
|
+
search_options=SubjectsSearchOptions,
|
|
43
|
+
service_components=service_components,
|
|
44
|
+
permission_policy_cls=PermissionPolicy,
|
|
45
|
+
# Resource layer
|
|
46
|
+
endpoint_route="/subjects",
|
|
47
|
+
resource_cls_attrs={
|
|
48
|
+
"response_handlers": {
|
|
49
|
+
"application/json": ResponseHandler(JSONSerializer(), headers=etag_headers),
|
|
50
|
+
"application/vnd.inveniordm.v1+json": ResponseHandler(
|
|
51
|
+
JSONSerializer(), headers=etag_headers
|
|
52
|
+
),
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021-2022 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
|
+
"""Datastreams module."""
|
|
10
|
+
|
|
11
|
+
from .datastreams import DataStream, StreamEntry
|
|
12
|
+
from .factories import DataStreamFactory
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
"DataStream",
|
|
16
|
+
"DataStreamFactory",
|
|
17
|
+
"StreamEntry",
|
|
18
|
+
)
|
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
"""Base data stream."""
|
|
10
|
+
|
|
11
|
+
from flask import current_app
|
|
12
|
+
from invenio_access.permissions import system_identity, system_user_id
|
|
13
|
+
from invenio_access.utils import get_identity
|
|
14
|
+
from invenio_accounts.proxies import current_datastore
|
|
15
|
+
from invenio_jobs.logging.jobs import EMPTY_JOB_CTX, job_context
|
|
16
|
+
from invenio_jobs.proxies import current_runs_service
|
|
17
|
+
|
|
18
|
+
from .errors import ReaderError, TransformerError, WriterError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StreamEntry:
|
|
22
|
+
"""Object to encapsulate streams processing."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, entry, record=None, errors=None, op_type=None, exc=None):
|
|
25
|
+
"""Constructor for the StreamEntry class.
|
|
26
|
+
|
|
27
|
+
:param entry (object): The entry object, usually a record dict.
|
|
28
|
+
:param record (object): The record object, usually a record class.
|
|
29
|
+
:param errors (list, optional): List of errors. Defaults to None.
|
|
30
|
+
:param op_type (str, optional): The operation type. Defaults to None.
|
|
31
|
+
:param exc (str, optional): The raised unhandled exception. Defaults to None.
|
|
32
|
+
"""
|
|
33
|
+
self.entry = entry
|
|
34
|
+
self.record = record
|
|
35
|
+
self.filtered = False
|
|
36
|
+
self.errors = errors or []
|
|
37
|
+
self.op_type = op_type
|
|
38
|
+
self.exc = exc
|
|
39
|
+
|
|
40
|
+
def log_errors(self, logger=None):
|
|
41
|
+
"""Log the errors using the provided logger or the default logger.
|
|
42
|
+
|
|
43
|
+
:param logger (logging.Logger, optional): Logger instance to use. Defaults to None.
|
|
44
|
+
"""
|
|
45
|
+
if logger is None:
|
|
46
|
+
logger = current_app.logger
|
|
47
|
+
for error in self.errors:
|
|
48
|
+
logger.error(f"Error in entry {self.entry}: {error}")
|
|
49
|
+
if self.exc:
|
|
50
|
+
logger.error(f"Exception in entry {self.entry}: {self.exc}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DataStream:
|
|
54
|
+
"""Data stream."""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
readers,
|
|
59
|
+
writers,
|
|
60
|
+
transformers=None,
|
|
61
|
+
batch_size=100,
|
|
62
|
+
write_many=False,
|
|
63
|
+
*args,
|
|
64
|
+
**kwargs,
|
|
65
|
+
):
|
|
66
|
+
"""Constructor.
|
|
67
|
+
|
|
68
|
+
:param readers: an ordered list of readers.
|
|
69
|
+
:param writers: an ordered list of writers.
|
|
70
|
+
:param transformers: an ordered list of transformers to apply.
|
|
71
|
+
"""
|
|
72
|
+
self._readers = readers
|
|
73
|
+
self._transformers = transformers
|
|
74
|
+
self._writers = writers
|
|
75
|
+
self.batch_size = batch_size
|
|
76
|
+
self.write_many = write_many
|
|
77
|
+
|
|
78
|
+
def filter(self, stream_entry, *args, **kwargs):
|
|
79
|
+
"""Checks if an stream_entry should be filtered out (skipped)."""
|
|
80
|
+
current_app.logger.debug(f"Filtering entry: {stream_entry.entry}")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def process_batch(self, batch):
|
|
84
|
+
"""Process a batch of entries."""
|
|
85
|
+
current_app.logger.info(f"Processing batch of size: {len(batch)}")
|
|
86
|
+
if job_context.get() is not EMPTY_JOB_CTX:
|
|
87
|
+
run_id = job_context.get()["run_id"]
|
|
88
|
+
current_runs_service.add_total_entries(
|
|
89
|
+
system_identity,
|
|
90
|
+
run_id=run_id,
|
|
91
|
+
job_id=job_context.get()["job_id"],
|
|
92
|
+
total_entries=len(batch),
|
|
93
|
+
)
|
|
94
|
+
transformed_entries = []
|
|
95
|
+
transformed_entries_with_errors = []
|
|
96
|
+
for stream_entry in batch:
|
|
97
|
+
if stream_entry.errors:
|
|
98
|
+
current_app.logger.warning(
|
|
99
|
+
f"Skipping entry with errors: {stream_entry.errors}"
|
|
100
|
+
)
|
|
101
|
+
yield stream_entry # reading errors
|
|
102
|
+
else:
|
|
103
|
+
transformed_entry = self.transform(stream_entry)
|
|
104
|
+
if transformed_entry.errors:
|
|
105
|
+
transformed_entries_with_errors.append(transformed_entry)
|
|
106
|
+
yield transformed_entry
|
|
107
|
+
elif self.filter(transformed_entry):
|
|
108
|
+
transformed_entry.filtered = True
|
|
109
|
+
yield transformed_entry
|
|
110
|
+
else:
|
|
111
|
+
transformed_entries.append(transformed_entry)
|
|
112
|
+
if transformed_entries_with_errors:
|
|
113
|
+
current_app.logger.warning(
|
|
114
|
+
f"Skipping {len(transformed_entries_with_errors)} transformed entries with errors."
|
|
115
|
+
)
|
|
116
|
+
if transformed_entries:
|
|
117
|
+
if self.write_many:
|
|
118
|
+
yield from self.batch_write(transformed_entries)
|
|
119
|
+
else:
|
|
120
|
+
yield from (self.write(entry) for entry in transformed_entries)
|
|
121
|
+
|
|
122
|
+
def process(self, *args, **kwargs):
|
|
123
|
+
"""Iterates over the entries.
|
|
124
|
+
|
|
125
|
+
Uses the reader to get the raw entries and transforms them.
|
|
126
|
+
It will iterate over the `StreamEntry` objects returned by
|
|
127
|
+
the reader, apply the transformations and yield the result of
|
|
128
|
+
writing it.
|
|
129
|
+
"""
|
|
130
|
+
current_app.logger.info("Starting data stream processing")
|
|
131
|
+
batch = []
|
|
132
|
+
for stream_entry in self.read():
|
|
133
|
+
batch.append(stream_entry)
|
|
134
|
+
if len(batch) >= self.batch_size:
|
|
135
|
+
current_app.logger.debug(f"Processing batch of size: {len(batch)}")
|
|
136
|
+
yield from self.process_batch(batch)
|
|
137
|
+
batch = []
|
|
138
|
+
|
|
139
|
+
# Process any remaining entries in the last batch
|
|
140
|
+
if batch:
|
|
141
|
+
current_app.logger.debug(f"Processing final batch of size: {len(batch)}")
|
|
142
|
+
yield from self.process_batch(batch)
|
|
143
|
+
|
|
144
|
+
def read(self):
|
|
145
|
+
"""Recursively read the entries."""
|
|
146
|
+
current_app.logger.debug("Reading entries from readers")
|
|
147
|
+
|
|
148
|
+
def pipe_gen(gen_funcs, piped_item=None):
|
|
149
|
+
_gen_funcs = list(gen_funcs) # copy to avoid modifying ref list
|
|
150
|
+
# use and remove the current generator
|
|
151
|
+
current_gen_func = _gen_funcs.pop(0)
|
|
152
|
+
for item in current_gen_func(piped_item):
|
|
153
|
+
try:
|
|
154
|
+
# exhaust iterations of subsequent generators
|
|
155
|
+
if _gen_funcs:
|
|
156
|
+
yield from pipe_gen(_gen_funcs, piped_item=item)
|
|
157
|
+
# there is no subsequent generator, return the current item
|
|
158
|
+
else:
|
|
159
|
+
yield StreamEntry(item)
|
|
160
|
+
except ReaderError as err:
|
|
161
|
+
current_app.logger.error(f"Reader error: {str(err)}")
|
|
162
|
+
yield StreamEntry(
|
|
163
|
+
entry=item,
|
|
164
|
+
errors=[f"{current_gen_func.__qualname__}: {str(err)}"],
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
read_gens = [r.read for r in self._readers]
|
|
168
|
+
yield from pipe_gen(read_gens)
|
|
169
|
+
|
|
170
|
+
def transform(self, stream_entry, *args, **kwargs):
|
|
171
|
+
"""Apply the transformations to an stream_entry."""
|
|
172
|
+
current_app.logger.debug(f"Transforming entry: {stream_entry.entry}")
|
|
173
|
+
for transformer in self._transformers:
|
|
174
|
+
try:
|
|
175
|
+
stream_entry = transformer.apply(stream_entry)
|
|
176
|
+
except TransformerError as err:
|
|
177
|
+
stream_entry.errors.append(
|
|
178
|
+
f"{transformer.__class__.__name__}: {str(err)}"
|
|
179
|
+
)
|
|
180
|
+
return stream_entry # break loop
|
|
181
|
+
|
|
182
|
+
return stream_entry
|
|
183
|
+
|
|
184
|
+
def _prepare_async_context(self):
|
|
185
|
+
"""Prepare the async context for writers."""
|
|
186
|
+
job_ctx = job_context.get()
|
|
187
|
+
run_id = job_ctx.get("run_id")
|
|
188
|
+
identity_id = job_ctx.get("identity_id")
|
|
189
|
+
job_id = job_ctx.get("job_id")
|
|
190
|
+
# System user needs to be handled separately because it doesn't exist in the database
|
|
191
|
+
if identity_id == system_user_id:
|
|
192
|
+
identity = system_identity
|
|
193
|
+
else:
|
|
194
|
+
user = current_datastore.get_user(identity_id)
|
|
195
|
+
if user is None:
|
|
196
|
+
raise ValueError(f"User with ID:{identity_id} not found.")
|
|
197
|
+
identity = get_identity(user)
|
|
198
|
+
|
|
199
|
+
subtask_run = current_runs_service.create_subtask_run(
|
|
200
|
+
identity, parent_run_id=run_id, job_id=job_id
|
|
201
|
+
)
|
|
202
|
+
return str(subtask_run.id)
|
|
203
|
+
|
|
204
|
+
def write(self, stream_entry, *args, **kwargs):
|
|
205
|
+
"""Write a single stream entry."""
|
|
206
|
+
current_app.logger.debug(f"Writing entry: {stream_entry.entry}")
|
|
207
|
+
for writer in self._writers:
|
|
208
|
+
try:
|
|
209
|
+
if writer.is_async and job_context.get() is not EMPTY_JOB_CTX:
|
|
210
|
+
subtask_run_id = self._prepare_async_context()
|
|
211
|
+
writer.write(stream_entry, subtask_run_id=subtask_run_id)
|
|
212
|
+
else:
|
|
213
|
+
writer.write(stream_entry)
|
|
214
|
+
except WriterError as err:
|
|
215
|
+
current_app.logger.error(f"Writer error: {str(err)}")
|
|
216
|
+
stream_entry.errors.append(f"{writer.__class__.__name__}: {str(err)}")
|
|
217
|
+
|
|
218
|
+
return stream_entry
|
|
219
|
+
|
|
220
|
+
def batch_write(self, stream_entries, *args, **kwargs):
|
|
221
|
+
"""Write a batch of stream entries."""
|
|
222
|
+
current_app.logger.debug(f"Batch writing entries: {len(stream_entries)}")
|
|
223
|
+
for writer in self._writers:
|
|
224
|
+
try:
|
|
225
|
+
if writer.is_async and job_context.get() is not EMPTY_JOB_CTX:
|
|
226
|
+
subtask_run_id = self._prepare_async_context()
|
|
227
|
+
yield from writer.write_many(
|
|
228
|
+
stream_entries, subtask_run_id=subtask_run_id
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
yield from writer.write_many(stream_entries)
|
|
232
|
+
except WriterError as err:
|
|
233
|
+
current_app.logger.error(f"Writer error: {str(err)}")
|
|
234
|
+
for entry in stream_entries:
|
|
235
|
+
entry.errors.append(f"{writer.__class__.__name__}: {str(err)}")
|
|
236
|
+
|
|
237
|
+
def total(self, *args, **kwargs):
|
|
238
|
+
"""The total of entries obtained from the origin."""
|
|
239
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 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
|
+
"""Datastream errors."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ReaderError(Exception):
|
|
13
|
+
"""Transformer application exception."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TransformerError(Exception):
|
|
17
|
+
"""Transformer application exception."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WriterError(Exception):
|
|
21
|
+
"""Transformer application exception."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FactoryError(Exception):
|
|
25
|
+
"""Transformer application exception."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, name, key):
|
|
28
|
+
"""Initialise error."""
|
|
29
|
+
super().__init__(f"{name} {key} not configured.")
|