Skip to main content

OARepo Vocabularies

Enhanced Invenio-Vocabularies extension providing hierarchical vocabulary support, custom fields integration, and advanced permission controls for vocabulary management in Invenio-based repositories.

Features

  • Hierarchical Vocabularies: Parent-child relationships with automatic level tracking, ancestor chains, and leaf detection
  • Custom Fields: Extend vocabulary records with custom metadata fields using Invenio custom fields system
  • Advanced Permissions: Fine-grained vocabulary-type-specific permission policies with dangerous operation detection
  • Multi-language Support: ICU collation support for sorting and suggestions across multiple languages
  • UI Components: Complete UI resource layer with search, detail, edit, and create views
  • API Extensions: REST API endpoints for vocabulary types and hierarchy operations

Installation

pip install oarepo-vocabularies

Configuration

Add to invenio.cfg:

from oarepo_vocabularies.services.config import VocabulariesConfig
from oarepo_vocabularies.resources.config import VocabulariesResourceConfig

VOCABULARIES_SERVICE_CONFIG = VocabulariesConfig
VOCABULARIES_RESOURCE_CONFIG = VocabulariesResourceConfig

Core Components

Vocabulary Record Model

The Vocabulary record class extends invenio_vocabularies.records.api.Vocabulary:

  • System Fields:

    • hierarchy: HierarchySystemField - manages hierarchy metadata (level, titles, ancestors, leaf status)
    • parent: ParentSystemField - handles parent-child relationships
    • custom_fields: DictField - stores custom metadata
    • relations: MultiRelationsField - manages parent and custom field relations
  • Hierarchy Database Model (VocabularyHierarchy):

    • id: UUID reference to vocabulary metadata
    • parent_id: UUID reference to parent hierarchy record
    • pid: String PID of the vocabulary term
    • level: Integer depth in hierarchy (1 for root)
    • titles: JSON array of title objects in hierarchy order
    • ancestors: JSON array of ancestor PIDs (parent to root)
    • ancestors_or_self: JSON array including self PID
    • leaf: Boolean indicating if term has children

Hierarchy Operations

Automatic Hierarchy Management:

  • On record creation: sets level, initializes ancestors, marks parent as non-leaf
  • On parent change: updates entire descendant subtree, fixes previous parent leaf status
  • On deletion: validates no children exist, updates parent leaf status

Query Methods:

# Get direct children
VocabularyHierarchy.get_direct_subterms_ids(parent_id)

# Get all descendants (recursive)
VocabularyHierarchy.get_subterms_ids(parent_id)

# Access from record
record.hierarchy.query_subterms()  # direct children
record.hierarchy.query_descendants()  # all descendants

Hierarchy Properties:

record.hierarchy.level  # depth in tree
record.hierarchy.leaf  # has no children
record.hierarchy.titles  # [self_title, parent_title, ...]
record.hierarchy.ancestors_ids  # ['parent', 'grandparent', ...]
record.hierarchy.parent_id  # direct parent PID

Parent Management

Setting Parent:

# On creation
data = {"id": "eng.US", "title": {"en": "English (US)"}, "parent": {"id": "eng"}, "type": "languages"}
vocab = Vocabulary.create(data=data)

# On update
record.parent.set("new_parent_id")  # or None to remove
record.commit()

Constraints:

  • Cannot delete record with children (raises ValidationError)
  • Cannot create cycles (parent cannot be descendant)
  • Parent change triggers full descendant tree update

Custom Fields

Configuration:

from invenio_records_resources.services.custom_fields import TextCF

VOCABULARIES_CF = [
    TextCF(name="blah"),
    # ... other custom fields
]

Usage:

vocab_service.create(
    system_identity, {"id": "eng", "title": {"en": "English"}, "type": "languages", "custom_fields": {"blah": "Hello"}}
)

SKOS Mappings

Vocabulary items can declare SKOS mapping relations to concepts in other vocabularies/schemes (e.g. a local "languages" term mapped to an external authority like LOC or ISO 639). Mappings are stored on the mappings field as a list of objects validated by MappingSchema:

  • identifier (string, required): the identifier of the matched concept in the target scheme (e.g. a URI or code)
  • scheme (string, required): the name/identifier of the target scheme the mapping points to
  • relation (string, required): the SKOS relation type between this vocabulary item and the target concept. Must be one of:
    • exactMatch: this item matches the target exactly. Usable for both import and export.
    • broadMatch: the target identifier is broader than this item. Usable for export only (not import).
    • narrowMatch: the target identifier is narrower than this item. Usable for import only (not export).
    • closeMatch: the target identifier is close to this item. Not usable for import or export.
    • relatedMatch: the target identifier is related to this item. Not usable for import or export.

Setting mappings:

vocab_service.create(
    system_identity,
    {
        "id": "eng",
        "title": {"en": "English"},
        "type": "languages",
        "mappings": [
            {"identifier": "http://id.loc.gov/vocabulary/iso639-2/eng", "scheme": "loc", "relation": "exactMatch"},
            {"identifier": "eng-related-id", "scheme": "otherscheme", "relation": "relatedMatch"},
        ],
    },
)

Searching by mapping - use the skos search parameter with relation:identifier or relation:identifier@scheme values (multiple values are OR-ed together):

# Match items with an exact-match mapping to a given identifier in a given scheme
vocab_service.search(
    system_identity,
    {"skos": ["exactMatch:http://id.loc.gov/vocabulary/iso639-2/eng@loc"]},
    type="languages",
)

# scheme is optional - match on relation + identifier only
vocab_service.search(system_identity, {"skos": ["exactMatch:http://id.loc.gov/vocabulary/iso639-2/eng"]})

Services

VocabulariesConfig (oarepo_vocabularies.services.config.VocabulariesConfig):

  • Extends invenio_vocabularies.services.VocabulariesServiceConfig
  • Adds KeepVocabularyIdComponent - prevents ID changes on update
  • Adds ScanningOrderComponent - manages vocabulary ordering
  • Schema: VocabularySchema with hierarchy and custom_fields support
  • Search: VocabularySearchOptions with hierarchy filters

VocabularyTypeService (oarepo_vocabularies.services.service.VocabularyTypeService):

  • Lists available vocabulary types with metadata
  • Aggregates term counts per vocabulary type
  • Provides links to vocabulary listings

Service Components:

  • KeepVocabularyIdComponent: Ensures vocabulary ID remains constant during updates
  • ScanningOrderComponent: Handles vocabulary item ordering logic

Search Options

VocabularySearchOptions parameters:

  • type: vocabulary type filter
  • h-level: filter by hierarchy level
  • h-parent: filter by direct parent PID
  • h-ancestor: filter by any ancestor PID
  • h-ancestor-or-self: filter including self
  • tags: filter by tags
  • updated_after: filter by update timestamp
  • ids: list of (type, id) tuples for specific records
  • source: specify returned fields
  • skos: filter by SKOS mapping, values of form relation:identifier or relation:identifier@scheme (see SKOS Mappings)

Sort Options:

  • bestmatch: relevance score (default for queries)
  • title: alphabetical by title (language-aware)
  • newest: most recently created first
  • oldest: creation date ascending

Query Parser:

  • Boosts matches in current language (10x for title, 5x for hierarchy titles)
  • Default operator: AND

Resources

VocabulariesResourceConfig:

  • Extends invenio_vocabularies.resources.config.VocabulariesResourceConfig
  • Adds hierarchy search parameters (h-parent, h-ancestor, h-level)
  • Adds UI JSON serializer (application/vnd.inveniordm.v1+json)

Vocabulary Type Resource:

  • Endpoint: /api/vocabularies/
  • Lists available vocabulary types with counts
  • Returns configured metadata (name, description, icons)

API Links (generated for each vocabulary):

  • self: API detail endpoint
  • self_html: UI detail page
  • vocabulary: API list for vocabulary type
  • vocabulary_html: UI search page
  • edit_html: UI edit form
  • parent: parent record (if exists)
  • parent_html: parent UI page
  • children: API list of direct children
  • children_html: UI list of children
  • descendants: API list of all descendants
  • descendants_html: UI list of descendants

Permissions

Permission Generators:

IfVocabularyType(vocabulary_type, then_, else_):

  • Conditional permission based on vocabulary type
  • Example: Allow all users to manage "languages" but restrict "countries"
can_create = [IfVocabularyType("languages", then_=[AnyUser()], else_=[])]

IfNonDangerousVocabularyOperation(then_, else_):

  • Detects dangerous operations (ID change, parent change)
  • Example: Allow custom field updates but restrict hierarchy changes
can_update = [IfNonDangerousVocabularyOperation(then_=[AnyUser()], else_=[Admin()])]

Configuration:

from invenio_vocabularies.services.permissions import PermissionPolicy

# Set custom permission policy
VOCABULARIES_PERMISSIONS_POLICY = MyCustomPermissionPolicy

# Or use presets
OAREPO_PERMISSIONS_PRESETS = {"vocabularies": PermissionPolicy}

Dangerous Operations:

  • Changing vocabulary id field
  • Changing hierarchy parent (adding/removing/changing)

UI Views

Blueprints:

  • oarepo_vocabularies_ui: main vocabulary UI (search, detail, edit, create)
  • oarepo_vocabulary_type_ui: vocabulary type listing UI

UI Components:

  • VocabularyTypeAndProps: provides vocabulary type metadata to templates

Templates:

  • Search results page with hierarchy breadcrumbs
  • Detail page showing hierarchy position
  • Edit form with parent selector
  • Create form with vocabulary type selection

CLI Commands

# Vocabulary management (extensible)
invenio oarepo vocabularies --help

Configuration Options

# Permission policy factory
VOCABULARIES_PERMISSIONS_POLICY = "path.to.PermissionPolicy"

# Vocabulary type metadata
INVENIO_VOCABULARY_TYPE_METADATA = {
    "languages": {
        "name": {"en": "Languages", "cs": "Jazyky"},
        "description": {"en": "Language vocabulary", "cs": "Slovník jazyků"},
        # ... additional metadata
    }
}

# Custom fields definition
VOCABULARIES_CF = [
    TextCF(name="custom_field_1"),
    # ...
]

# Sort/suggest custom fields
OAREPO_VOCABULARIES_SORT_CF = ["field1", "field2"]
OAREPO_VOCABULARIES_SUGGEST_CF = ["field1"]

# Cache settings for facets
VOCABULARIES_FACET_CACHE_SIZE = 2048
VOCABULARIES_FACET_CACHE_TTL = 60 * 60 * 24  # 24 hours

# Service/resource overrides
OAREPO_VOCABULARY_TYPE_SERVICE = VocabularyTypeService
OAREPO_VOCABULARY_TYPE_SERVICE_CONFIG = VocabularyTypeServiceConfig
OAREPO_VOCABULARY_TYPE_RESOURCE = VocabularyTypeResource
OAREPO_VOCABULARY_TYPE_RESOURCE_CONFIG = VocabularyTypeResourceConfig

API Examples

Create Hierarchical Vocabulary

from invenio_access.permissions import system_identity
from invenio_vocabularies.proxies import current_service as vocab_service

# Create parent
parent = vocab_service.create(system_identity, {"id": "eng", "title": {"en": "English"}, "type": "languages"})

# Create child
child = vocab_service.create(
    system_identity,
    {"id": "eng.US", "title": {"en": "English (US)"}, "hierarchy": {"parent": "eng"}, "type": "languages"},
)

# Access hierarchy data
print(child.data["hierarchy"])
# {
#     "level": 2,
#     "titles": [{"en": "English (US)"}, {"en": "English"}],
#     "ancestors": ["eng"],
#     "ancestors_or_self": ["eng.US", "eng"],
#     "leaf": True,
#     "parent": "eng"
# }

Search with Hierarchy Filters

# Get all children of a term
results = vocab_service.search(system_identity, {"h-parent": "eng"}, type="languages")

# Get all descendants
results = vocab_service.search(system_identity, {"h-ancestor": "eng"}, type="languages")

# Filter by level
results = vocab_service.search(system_identity, {"h-level": 2}, type="languages")

Create Vocabulary with SKOS Mappings

vocab_service.create(
    system_identity,
    {
        "id": "eng",
        "title": {"en": "English"},
        "type": "languages",
        "mappings": [
            {"identifier": "http://id.loc.gov/vocabulary/iso639-2/eng", "scheme": "loc", "relation": "exactMatch"},
        ],
    },
)

# Search for items mapped to a given identifier
results = vocab_service.search(
    system_identity,
    {"skos": ["exactMatch:http://id.loc.gov/vocabulary/iso639-2/eng@loc"]},
    type="languages",
)

Update Parent Relationship

# Change parent
vocab_service.update(
    system_identity,
    ("languages", "eng.US"),
    {"hierarchy": {"parent": "eng.UK"}, "title": {"en": "English (US)"}, "type": "languages"},
)

# Remove parent (make root)
vocab_service.update(
    system_identity,
    ("languages", "eng.US"),
    {"hierarchy": {"parent": None}, "title": {"en": "English (US)"}, "type": "languages"},
)

Using Record API

from oarepo_vocabularies.records.api import Vocabulary

# Get record
record = Vocabulary.pid.with_type_ctx("languages").resolve("eng.US")

# Access hierarchy
print(record.hierarchy.level)  # 2
print(record.hierarchy.parent_id)  # "eng"
print(record.hierarchy.leaf)  # True
print(record.hierarchy.ancestors_ids)  # ["eng"]

# Query children
children = record.hierarchy.query_subterms()

# Update parent programmatically
record.parent.set("new_parent_id")
record.commit()

Testing

# Run all tests
./run.sh test

# Run specific test
pytest tests/test_hierarchy.py -v

# Run with coverage
pytest --cov=oarepo_vocabularies tests/

Development

# Install development dependencies
pip install -e ".[dev,tests]"

# Format code
black oarepo_vocabularies tests
isort oarepo_vocabularies tests
autoflake --in-place --remove-all-unused-imports -r oarepo_vocabularies

# Type checking
mypy oarepo_vocabularies

Entry Points

The package registers several Invenio entry points:

[project.entry-points."invenio_base.apps"]
oarepo_vocabularies = "oarepo_vocabularies.ext:OARepoVocabularies"
oarepo_vocabularies_ui = "oarepo_vocabularies.ui.ext:InvenioVocabulariesAppExtension"

[project.entry-points."invenio_base.api_apps"]
oarepo_vocabularies = "oarepo_vocabularies.ext:OARepoVocabularies"
oarepo_vocabularies_ui = "oarepo_vocabularies.ui.ext:InvenioVocabulariesAppExtension"

[project.entry-points."invenio_jsonschemas.schemas"]
oarepo_vocabularies = "oarepo_vocabularies.records.jsonschemas"

[project.entry-points."invenio_base.blueprints"]
oarepo_ui = "oarepo_vocabularies.views.app:create_app_blueprint"
oarepo_vocabularies_ui = "oarepo_vocabularies.ui.views:create_blueprint"
oarepo_vocabulary_type_ui = "oarepo_vocabularies.ui.views:create_vocabulary_type_blueprint"

[project.entry-points."invenio_base.api_blueprints"]
oarepo_vocabulary_type_api = "oarepo_vocabularies.views.api:create_api_blueprint"

[project.entry-points."invenio_assets.webpack"]
oarepo_vocabularies_ui_theme = "oarepo_vocabularies.ui.theme.webpack:theme"

[project.entry-points."invenio_i18n.translations"]
oarepo_vocabularies_ui = "oarepo_vocabularies"

License

Copyright (c) 2025 CESNET z.s.p.o.

OARepo Vocabularies is free software; you can redistribute it and/or modify it under the terms of the MIT License. See LICENSE file for more details.

Links

Acknowledgments

This project builds upon Invenio Framework and is developed as part of the OARepo ecosystem.

Metadata

Release files for oarepo-vocabularies 10.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oarepo-vocabularies 10.2.0
File Size Uploaded
oarepo_vocabularies-10.2.0.tar.gz 75.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for oarepo-vocabularies 10.2.0
File Interpreter ABI Platform
oarepo_vocabularies-10.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 234.3 kB

Release files / oarepo_vocabularies-10.2.0.tar.gz

Download URL oarepo_vocabularies-10.2.0.tar.gz
Size 75.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e6446bed1eecd25e960b89a5056aad11f0fdc27d3dac1b7abce3209a1e3a76b2
BLAKE2b-256 checksum
How to use checksums
8d835f73ba637c21413d2c9fa51d8396de515d3cbaee78d0f6c5eccd08eb69e6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / oarepo_vocabularies-10.2.0-py3-none-any.whl

Download URL oarepo_vocabularies-10.2.0-py3-none-any.whl
Size 158.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
951cad906b58da3ac489a833d051d171c6528d6bea20474e4c71372e48389826
BLAKE2b-256 checksum
How to use checksums
ba0478734c6e3faf93d1ff2b651dc87361b576fd494ebc48042645296f88c804
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

10.2.1

2 release files

This release

10.2.0 This release

2 release files

10.1.0

2 release files

10.0.0

2 release files

9.1.0

2 release files

9.0.1

2 release files

9.0.0

2 release files

8.0.0

2 release files

7.0.0

2 release files

6.0.0

2 release files

5.0.0

2 release files

4.0.0

2 release files

2.2.6

2 release files

2.2.5

2 release files

2.2.4

2 release files

2.2.3

2 release files

2.2.2

2 release files

2.2.1

2 release files

2.2.0

2 release files

2.1.25

2 release files

2.1.24

2 release files

2.1.21

2 release files

2.1.20

2 release files

2.1.18

2 release files

2.1.17

2 release files

2.1.16

2 release files

2.1.14

2 release files

2.1.13

2 release files

2.1.12

2 release files

2.1.11

2 release files

2.1.8

2 release files

2.1.6

2 release files

2.1.4

2 release files

2.1.3

2 release files

2.1.2

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.96

2 release files

2.0.95

2 release files

2.0.93

2 release files

2.0.90

2 release files

2.0.89

2 release files

2.0.88

2 release files

2.0.87

2 release files

2.0.86

2 release files

2.0.85

2 release files

2.0.84

2 release files

2.0.81

2 release files

2.0.80

2 release files

2.0.79

2 release files

2.0.75

2 release files

2.0.74

2 release files

2.0.73

2 release files

2.0.72

2 release files

2.0.71

2 release files

2.0.69

2 release files

2.0.68

2 release files

2.0.67

2 release files

2.0.66

2 release files

2.0.65

2 release files

2.0.62

2 release files

2.0.61

2 release files

2.0.60

2 release files

2.0.59

2 release files

2.0.58

2 release files

2.0.57

2 release files

2.0.56

2 release files

2.0.55

2 release files

2.0.54

2 release files

2.0.53

2 release files

2.0.52

2 release files

2.0.51

2 release files

2.0.50

2 release files

2.0.48

2 release files

2.0.47

2 release files

2.0.38

2 release files

2.0.37

2 release files

2.0.36

2 release files

2.0.35

2 release files

2.0.34

2 release files

2.0.33

2 release files

2.0.32

2 release files

2.0.31

2 release files

2.0.30

2 release files

2.0.26

2 release files

2.0.25

2 release files

2.0.19

2 release files

2.0.18

2 release files

2.0.17

2 release files

2.0.16

2 release files

2.0.15

2 release files

2.0.14

2 release files

2.0.13

2 release files

2.0.12

2 release files

2.0.11

2 release files

2.0.10

2 release files

2.0.8

2 release files

2.0.7

2 release files

2.0.6

2 release files

2.0.5

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.0.22

2 release files

1.0.21

2 release files

1.0.20

2 release files

1.0.19

2 release files

1.0.18

2 release files

1.0.17

2 release files

1.0.16

2 release files

1.0.15

2 release files

1.0.14

2 release files

1.0.13

2 release files

1.0.12

2 release files

1.0.11

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.0.15

2 release files

0.0.13

2 release files

0.0.12

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page