oarepo-runtime 2.0.0.dev36__tar.gz → 2.0.0.dev38__tar.gz
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.
- {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/PKG-INFO +2 -4
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/__init__.py +1 -1
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/ext.py +6 -5
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/info/views.py +2 -3
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/pid_providers.py +2 -2
 - oarepo_runtime-2.0.0.dev38/oarepo_runtime/records/systemfields/relations.py +307 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/results.py +5 -4
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/pyproject.toml +1 -4
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/.gitignore +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/LICENSE +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/README.md +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/api.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/cli/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/cli/search.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/config.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/info/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/proxies.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/py.typed +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/drafts.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/mapping.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/systemfields/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/systemfields/base.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/systemfields/custom_fields.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/systemfields/mapping.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/systemfields/publication_status.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/systemfields/selectors.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/resources/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/resources/config.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/config/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/config/components.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/config/link_conditions.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/config/permissions.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/base.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/date.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/nested_facet.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/params.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/utils.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/generators.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/records/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/records/custom_fields.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/records/links.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/records/mapping.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/__init__.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/i18n.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/i18n_ui.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/ui.py +0 -0
 - {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/typing.py +0 -0
 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.4
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: oarepo-runtime
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 2.0.0. 
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 2.0.0.dev38
         
     | 
| 
       4 
4 
     | 
    
         
             
            Summary: A set of runtime extensions of Invenio repository
         
     | 
| 
       5 
5 
     | 
    
         
             
            Project-URL: Homepage, https://github.com/oarepo/oarepo-runtime
         
     | 
| 
       6 
6 
     | 
    
         
             
            License-Expression: MIT
         
     | 
| 
         @@ -8,11 +8,9 @@ License-File: LICENSE 
     | 
|
| 
       8 
8 
     | 
    
         
             
            Requires-Python: <3.14,>=3.13
         
     | 
| 
       9 
9 
     | 
    
         
             
            Requires-Dist: langcodes>=3.5.0
         
     | 
| 
       10 
10 
     | 
    
         
             
            Requires-Dist: oarepo-invenio-typing-stubs>=0.1.0
         
     | 
| 
       11 
     | 
    
         
            -
            Requires-Dist: oarepo[rdm,tests]<15,>= 
     | 
| 
      
 11 
     | 
    
         
            +
            Requires-Dist: oarepo[rdm,tests]<15,>=14
         
     | 
| 
       12 
12 
     | 
    
         
             
            Provides-Extra: dev
         
     | 
| 
       13 
13 
     | 
    
         
             
            Requires-Dist: pytest>=7.1.2; extra == 'dev'
         
     | 
| 
       14 
     | 
    
         
            -
            Provides-Extra: oarepo13
         
     | 
| 
       15 
     | 
    
         
            -
            Requires-Dist: oarepo[rdm]<14,>=13; extra == 'oarepo13'
         
     | 
| 
       16 
14 
     | 
    
         
             
            Provides-Extra: oarepo14
         
     | 
| 
       17 
15 
     | 
    
         
             
            Requires-Dist: oarepo[rdm]<15,>=14; extra == 'oarepo14'
         
     | 
| 
       18 
16 
     | 
    
         
             
            Provides-Extra: tests
         
     | 
| 
         @@ -15,6 +15,7 @@ from functools import cached_property 
     | 
|
| 
       15 
15 
     | 
    
         
             
            from typing import TYPE_CHECKING, Any, cast
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
17 
     | 
    
         
             
            from flask import current_app
         
     | 
| 
      
 18 
     | 
    
         
            +
            from invenio_db import db
         
     | 
| 
       18 
19 
     | 
    
         
             
            from invenio_pidstore.errors import PIDDoesNotExistError
         
     | 
| 
       19 
20 
     | 
    
         
             
            from invenio_pidstore.models import PersistentIdentifier
         
     | 
| 
       20 
21 
     | 
    
         
             
            from invenio_records.api import Record as RecordBase
         
     | 
| 
         @@ -146,20 +147,20 @@ class OARepoRuntime: 
     | 
|
| 
       146 
147 
     | 
    
         | 
| 
       147 
148 
     | 
    
         
             
                    If the filter matches multiple services, an error is raised.
         
     | 
| 
       148 
149 
     | 
    
         
             
                    """
         
     | 
| 
       149 
     | 
    
         
            -
                    pids =  
     | 
| 
      
 150 
     | 
    
         
            +
                    pids = db.session.query(PersistentIdentifier).filter_by(**filter_kwargs).all()
         
     | 
| 
       150 
151 
     | 
    
         | 
| 
       151 
152 
     | 
    
         
             
                    filtered_pids = [pid for pid in pids if pid.pid_type in self.record_class_by_pid_type]
         
     | 
| 
       152 
153 
     | 
    
         
             
                    if not filtered_pids:
         
     | 
| 
       153 
154 
     | 
    
         
             
                        raise PIDDoesNotExistError(
         
     | 
| 
       154 
     | 
    
         
            -
                             
     | 
| 
       155 
     | 
    
         
            -
                            filter_kwargs,
         
     | 
| 
      
 155 
     | 
    
         
            +
                            "unknown_pid",
         
     | 
| 
      
 156 
     | 
    
         
            +
                            str(filter_kwargs),
         
     | 
| 
       156 
157 
     | 
    
         
             
                            "The pid value/record uuid is not associated with any record.",
         
     | 
| 
       157 
158 
     | 
    
         
             
                        )
         
     | 
| 
       158 
159 
     | 
    
         | 
| 
       159 
160 
     | 
    
         
             
                    if len(filtered_pids) > 1:
         
     | 
| 
       160 
161 
     | 
    
         
             
                        raise PIDDoesNotExistError(
         
     | 
| 
       161 
     | 
    
         
            -
                             
     | 
| 
       162 
     | 
    
         
            -
                            filter_kwargs,
         
     | 
| 
      
 162 
     | 
    
         
            +
                            "unknown_pid",
         
     | 
| 
      
 163 
     | 
    
         
            +
                            str(filter_kwargs),
         
     | 
| 
       163 
164 
     | 
    
         
             
                            f"Multiple records found for pid value/record uuid: {filtered_pids}",
         
     | 
| 
       164 
165 
     | 
    
         
             
                        )
         
     | 
| 
       165 
166 
     | 
    
         
             
                    return filtered_pids[0]
         
     | 
| 
         @@ -1,13 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            #
         
     | 
| 
       2 
2 
     | 
    
         
             
            # Copyright (c) 2025 CESNET z.s.p.o.
         
     | 
| 
       3 
3 
     | 
    
         
             
            #
         
     | 
| 
       4 
     | 
    
         
            -
            # This file is a part of oarepo-runtime (see  
     | 
| 
      
 4 
     | 
    
         
            +
            # This file is a part of oarepo-runtime (see https://github.com/oarepo/oarepo-runtime).
         
     | 
| 
       5 
5 
     | 
    
         
             
            #
         
     | 
| 
       6 
6 
     | 
    
         
             
            # oarepo-runtime is free software; you can redistribute it and/or modify it
         
     | 
| 
       7 
7 
     | 
    
         
             
            # under the terms of the MIT License; see LICENSE file for more details.
         
     | 
| 
       8 
8 
     | 
    
         
             
            #
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
            """Info Resource."""
         
     | 
| 
      
 9 
     | 
    
         
            +
            """Resource for serving machine-readable information about the repository."""
         
     | 
| 
       11 
10 
     | 
    
         | 
| 
       12 
11 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       13 
12 
     | 
    
         | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/pid_providers.py
    RENAMED
    
    | 
         @@ -28,7 +28,7 @@ class UniversalPIDMixin(RecordIdProviderV2): 
     | 
|
| 
       28 
28 
     | 
    
         
             
                unpid_default_status = PIDStatus.REGISTERED
         
     | 
| 
       29 
29 
     | 
    
         | 
| 
       30 
30 
     | 
    
         
             
                @classmethod
         
     | 
| 
       31 
     | 
    
         
            -
                def create(
         
     | 
| 
      
 31 
     | 
    
         
            +
                def create(  # type: ignore[override] # as pid type and value are given
         
     | 
| 
       32 
32 
     | 
    
         
             
                    cls,
         
     | 
| 
       33 
33 
     | 
    
         
             
                    object_type: str | None = None,
         
     | 
| 
       34 
34 
     | 
    
         
             
                    object_uuid: str | None = None,
         
     | 
| 
         @@ -50,7 +50,7 @@ class UniversalPIDMixin(RecordIdProviderV2): 
     | 
|
| 
       50 
50 
     | 
    
         | 
| 
       51 
51 
     | 
    
         
             
                    PersistentIdentifier.create(
         
     | 
| 
       52 
52 
     | 
    
         
             
                        cls.unpid_pid_type,
         
     | 
| 
       53 
     | 
    
         
            -
                        pid.pid.pid_value,
         
     | 
| 
      
 53 
     | 
    
         
            +
                        cast("str", pid.pid.pid_value),
         
     | 
| 
       54 
54 
     | 
    
         
             
                        pid_provider=None,
         
     | 
| 
       55 
55 
     | 
    
         
             
                        object_type=object_type,
         
     | 
| 
       56 
56 
     | 
    
         
             
                        object_uuid=object_uuid,
         
     | 
| 
         @@ -0,0 +1,307 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Copyright (c) 2025 CESNET z.s.p.o.
         
     | 
| 
      
 3 
     | 
    
         
            +
            #
         
     | 
| 
      
 4 
     | 
    
         
            +
            # This file is a part of oarepo-runtime (see https://github.com/oarepo/oarepo-runtime).
         
     | 
| 
      
 5 
     | 
    
         
            +
            #
         
     | 
| 
      
 6 
     | 
    
         
            +
            # oarepo-runtime is free software; you can redistribute it and/or modify it
         
     | 
| 
      
 7 
     | 
    
         
            +
            # under the terms of the MIT License; see LICENSE file for more details.
         
     | 
| 
      
 8 
     | 
    
         
            +
            #
         
     | 
| 
      
 9 
     | 
    
         
            +
            """A relation field that allows arbitrarily nested lists of relations."""
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            from __future__ import annotations
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            from itertools import zip_longest
         
     | 
| 
      
 14 
     | 
    
         
            +
            from typing import TYPE_CHECKING, Any, override
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            from invenio_records.dictutils import dict_lookup, dict_set
         
     | 
| 
      
 17 
     | 
    
         
            +
            from invenio_records.systemfields.relations import (
         
     | 
| 
      
 18 
     | 
    
         
            +
                InvalidRelationValue,
         
     | 
| 
      
 19 
     | 
    
         
            +
                ListRelation,
         
     | 
| 
      
 20 
     | 
    
         
            +
                RelationListResult,
         
     | 
| 
      
 21 
     | 
    
         
            +
            )
         
     | 
| 
      
 22 
     | 
    
         
            +
            from invenio_records_resources.records.systemfields.relations import (
         
     | 
| 
      
 23 
     | 
    
         
            +
                PIDRelation,
         
     | 
| 
      
 24 
     | 
    
         
            +
            )
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            if TYPE_CHECKING:
         
     | 
| 
      
 27 
     | 
    
         
            +
                from collections.abc import Callable, Generator
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                from invenio_records.api import Record
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            class ArbitraryNestedListResult(RelationListResult):
         
     | 
| 
      
 33 
     | 
    
         
            +
                """Relation access result."""
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                @override
         
     | 
| 
      
 36 
     | 
    
         
            +
                def __call__(self, force: bool = True):
         
     | 
| 
      
 37 
     | 
    
         
            +
                    """Resolve the relation."""
         
     | 
| 
      
 38 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 39 
     | 
    
         
            +
                        # not as efficient as it could be as we create the list of lists first
         
     | 
| 
      
 40 
     | 
    
         
            +
                        # before returning the iterator, but simpler to implement
         
     | 
| 
      
 41 
     | 
    
         
            +
                        return iter(
         
     | 
| 
      
 42 
     | 
    
         
            +
                            _for_each_deep(
         
     | 
| 
      
 43 
     | 
    
         
            +
                                self._lookup_data(),
         
     | 
| 
      
 44 
     | 
    
         
            +
                                lambda v: self.resolve(v[self._value_key_suffix]),
         
     | 
| 
      
 45 
     | 
    
         
            +
                                levels=len(self.field.path_elements),
         
     | 
| 
      
 46 
     | 
    
         
            +
                            )
         
     | 
| 
      
 47 
     | 
    
         
            +
                        )
         
     | 
| 
      
 48 
     | 
    
         
            +
                    except KeyError:
         
     | 
| 
      
 49 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def _lookup_data(self) -> Any:
         
     | 
| 
      
 52 
     | 
    
         
            +
                    """Lookup the data from the record."""
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    # recursively lookup the data following the path elements. The end of the path
         
     | 
| 
      
 55 
     | 
    
         
            +
                    # must always be an array of objects.
         
     | 
| 
      
 56 
     | 
    
         
            +
                    def _lookup(r: Any, paths: list[str]) -> Any:
         
     | 
| 
      
 57 
     | 
    
         
            +
                        if not paths:
         
     | 
| 
      
 58 
     | 
    
         
            +
                            if self.field.relation_field:
         
     | 
| 
      
 59 
     | 
    
         
            +
                                try:
         
     | 
| 
      
 60 
     | 
    
         
            +
                                    return dict_lookup(r, self.field.relation_field)
         
     | 
| 
      
 61 
     | 
    
         
            +
                                except KeyError:  # pragma: no cover
         
     | 
| 
      
 62 
     | 
    
         
            +
                                    return None
         
     | 
| 
      
 63 
     | 
    
         
            +
                            return r
         
     | 
| 
      
 64 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 65 
     | 
    
         
            +
                            level_values = dict_lookup(r, paths[0])
         
     | 
| 
      
 66 
     | 
    
         
            +
                            if not isinstance(level_values, list):
         
     | 
| 
      
 67 
     | 
    
         
            +
                                raise InvalidRelationValue(  # pragma: no cover
         
     | 
| 
      
 68 
     | 
    
         
            +
                                    f'Invalid structure, expecting list at "{paths[0]}", got {level_values}. '
         
     | 
| 
      
 69 
     | 
    
         
            +
                                    f'Complete paths: "{self.field.path_elements}"'
         
     | 
| 
      
 70 
     | 
    
         
            +
                                )
         
     | 
| 
      
 71 
     | 
    
         
            +
                            ret = [_lookup(v, paths[1:]) for v in level_values]
         
     | 
| 
      
 72 
     | 
    
         
            +
                            return [v for v in ret if v is not None]
         
     | 
| 
      
 73 
     | 
    
         
            +
                        except KeyError:
         
     | 
| 
      
 74 
     | 
    
         
            +
                            return []
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    return _lookup(self.record, self.field.path_elements)
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                @override
         
     | 
| 
      
 79 
     | 
    
         
            +
                def validate(self) -> None:
         
     | 
| 
      
 80 
     | 
    
         
            +
                    """Validate the field."""
         
     | 
| 
      
 81 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 82 
     | 
    
         
            +
                        values = self._lookup_data()
         
     | 
| 
      
 83 
     | 
    
         
            +
                        # not as efficient as it could be as we create the list of lists first
         
     | 
| 
      
 84 
     | 
    
         
            +
                        # before returning, but simpler to implement
         
     | 
| 
      
 85 
     | 
    
         
            +
                        _for_each_deep(
         
     | 
| 
      
 86 
     | 
    
         
            +
                            values,
         
     | 
| 
      
 87 
     | 
    
         
            +
                            lambda v: self._validate_single_value(v),
         
     | 
| 
      
 88 
     | 
    
         
            +
                            levels=len(self.field.path_elements),
         
     | 
| 
      
 89 
     | 
    
         
            +
                        )
         
     | 
| 
      
 90 
     | 
    
         
            +
                    except KeyError:  # pragma: no cover
         
     | 
| 
      
 91 
     | 
    
         
            +
                        return
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                def _validate_single_value(self, v: Any) -> None:
         
     | 
| 
      
 94 
     | 
    
         
            +
                    """Validate a single value."""
         
     | 
| 
      
 95 
     | 
    
         
            +
                    if isinstance(v, list):
         
     | 
| 
      
 96 
     | 
    
         
            +
                        raise InvalidRelationValue(f"Invalid value {v}, should not be list.")
         
     | 
| 
      
 97 
     | 
    
         
            +
                    relation_id = self._lookup_id(v)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    if not self.exists(relation_id):
         
     | 
| 
      
 99 
     | 
    
         
            +
                        raise InvalidRelationValue(f"Invalid value {relation_id}.")
         
     | 
| 
      
 100 
     | 
    
         
            +
                    if self.value_check:  # pragma: no cover # not testing, copied from invenio
         
     | 
| 
      
 101 
     | 
    
         
            +
                        obj = self.resolve(v[self.field._value_key_suffix])  # noqa: SLF001 # private attr
         
     | 
| 
      
 102 
     | 
    
         
            +
                        self._value_check(self.value_check, obj)
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                @override
         
     | 
| 
      
 105 
     | 
    
         
            +
                def _apply_items(  # type: ignore[override]
         
     | 
| 
      
 106 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 107 
     | 
    
         
            +
                    func: Callable,
         
     | 
| 
      
 108 
     | 
    
         
            +
                    keys: list[str] | None = None,
         
     | 
| 
      
 109 
     | 
    
         
            +
                    attrs: list[str] | None = None,
         
     | 
| 
      
 110 
     | 
    
         
            +
                ) -> list[Any] | None:
         
     | 
| 
      
 111 
     | 
    
         
            +
                    """Iterate over the list of objects."""
         
     | 
| 
      
 112 
     | 
    
         
            +
                    # The attributes we want to get from the related record.
         
     | 
| 
      
 113 
     | 
    
         
            +
                    attrs = attrs or self.attrs
         
     | 
| 
      
 114 
     | 
    
         
            +
                    keys = keys or self.keys
         
     | 
| 
      
 115 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 116 
     | 
    
         
            +
                        # Get the list of objects we have to dereference/clean.
         
     | 
| 
      
 117 
     | 
    
         
            +
                        values = self._lookup_data()
         
     | 
| 
      
 118 
     | 
    
         
            +
                        return _for_each_deep(
         
     | 
| 
      
 119 
     | 
    
         
            +
                            values,
         
     | 
| 
      
 120 
     | 
    
         
            +
                            lambda v: func(v, keys, attrs),
         
     | 
| 
      
 121 
     | 
    
         
            +
                            levels=len(self.field.path_elements),
         
     | 
| 
      
 122 
     | 
    
         
            +
                        )
         
     | 
| 
      
 123 
     | 
    
         
            +
                    except KeyError:  # pragma: no cover
         
     | 
| 
      
 124 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            class ArbitraryNestedListRelation(ListRelation):
         
     | 
| 
      
 128 
     | 
    
         
            +
                """Arbitrary nested relation list type.
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                self.path_elements contain the segments of the path that are within lists.
         
     | 
| 
      
 131 
     | 
    
         
            +
                For example:
         
     | 
| 
      
 132 
     | 
    
         
            +
                - For paths like "a.b.c", path = [], relation_field="a.b.c"
         
     | 
| 
      
 133 
     | 
    
         
            +
                - For paths like "a.b.0.c", path = ["a.b"], relation_field="c"
         
     | 
| 
      
 134 
     | 
    
         
            +
                - For paths like "a.0.b.1.c", path = ["a", "b"], relation_field="c"
         
     | 
| 
      
 135 
     | 
    
         
            +
                - For paths like "a.1", path = ["a"], relation_field=None
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                ids and values are stored as lists that can contain other lists arbitrarily nested.
         
     | 
| 
      
 138 
     | 
    
         
            +
                The total depth of nesting is given by the length of self.path_elements + 1 if self.relation_field is not None
         
     | 
| 
      
 139 
     | 
    
         
            +
                or length of self.path_elements if self.relation_field is None.
         
     | 
| 
      
 140 
     | 
    
         
            +
                """
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                result_cls = ArbitraryNestedListResult
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                def __init__(
         
     | 
| 
      
 145 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 146 
     | 
    
         
            +
                    *args: Any,
         
     | 
| 
      
 147 
     | 
    
         
            +
                    array_paths: list[str] | None = None,
         
     | 
| 
      
 148 
     | 
    
         
            +
                    relation_field: str | None = None,
         
     | 
| 
      
 149 
     | 
    
         
            +
                    **kwargs: Any,
         
     | 
| 
      
 150 
     | 
    
         
            +
                ):
         
     | 
| 
      
 151 
     | 
    
         
            +
                    """Initialize the relation."""
         
     | 
| 
      
 152 
     | 
    
         
            +
                    if not array_paths:
         
     | 
| 
      
 153 
     | 
    
         
            +
                        raise ValueError("array_paths are required for ArbitraryNestedListRelation.")
         
     | 
| 
      
 154 
     | 
    
         
            +
                    self.path_elements = array_paths
         
     | 
| 
      
 155 
     | 
    
         
            +
                    super().__init__(*args, relation_field=relation_field, **kwargs)
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                @override
         
     | 
| 
      
 158 
     | 
    
         
            +
                def exists_many(self, ids: Any) -> bool:  # type: ignore[override]
         
     | 
| 
      
 159 
     | 
    
         
            +
                    """Return True if all ids exists."""
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                    # ids is a list that might recursively contain lists that contain ids
         
     | 
| 
      
 162 
     | 
    
         
            +
                    def flatten(nested_list: Any) -> Generator[Any]:
         
     | 
| 
      
 163 
     | 
    
         
            +
                        for item in nested_list:
         
     | 
| 
      
 164 
     | 
    
         
            +
                            if isinstance(item, (list, tuple)):
         
     | 
| 
      
 165 
     | 
    
         
            +
                                yield from flatten(item)
         
     | 
| 
      
 166 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 167 
     | 
    
         
            +
                                yield item
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                    return all(self.exists(i) for i in flatten(ids))
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                @override
         
     | 
| 
      
 172 
     | 
    
         
            +
                def parse_value(self, value: list[Any] | tuple[Any]) -> list[Any]:  # type: ignore[override]
         
     | 
| 
      
 173 
     | 
    
         
            +
                    """Parse a record (or ID) to the ID to be stored."""
         
     | 
| 
      
 174 
     | 
    
         
            +
                    return _for_each_deep(value, lambda v: self._parse_single_value(v), levels=len(self.path_elements))
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def _parse_single_value(self, value: Any) -> Any:
         
     | 
| 
      
 177 
     | 
    
         
            +
                    """Parse a single value using the parent class method.
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                    Note: we are skipping the list's parse_value here and calling
         
     | 
| 
      
 180 
     | 
    
         
            +
                    the next one after ListRelation in mro chain. That might be, for example,
         
     | 
| 
      
 181 
     | 
    
         
            +
                    PIDRelation.parse_value
         
     | 
| 
      
 182 
     | 
    
         
            +
                    """
         
     | 
| 
      
 183 
     | 
    
         
            +
                    if self.relation_field:
         
     | 
| 
      
 184 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 185 
     | 
    
         
            +
                            return super(ListRelation, self).parse_value(dict_lookup(value, self.relation_field))
         
     | 
| 
      
 186 
     | 
    
         
            +
                        except KeyError:  # pragma: no cover
         
     | 
| 
      
 187 
     | 
    
         
            +
                            return None
         
     | 
| 
      
 188 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 189 
     | 
    
         
            +
                        return super(ListRelation, self).parse_value(value)
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                @override
         
     | 
| 
      
 192 
     | 
    
         
            +
                def set_value(
         
     | 
| 
      
 193 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 194 
     | 
    
         
            +
                    record: Record,
         
     | 
| 
      
 195 
     | 
    
         
            +
                    value: list[Any] | tuple[Any],
         
     | 
| 
      
 196 
     | 
    
         
            +
                ) -> None:  # type: ignore[override]
         
     | 
| 
      
 197 
     | 
    
         
            +
                    """Set the relation value."""
         
     | 
| 
      
 198 
     | 
    
         
            +
                    store_values = self.parse_value(value)
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                    if not self.exists_many(store_values):
         
     | 
| 
      
 201 
     | 
    
         
            +
                        raise InvalidRelationValue(f'One of the values "{store_values}" is invalid.')
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                    total_depth = len(self.path_elements)
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                    for path_indices, path_value in _deep_enumerate(store_values, total_depth):
         
     | 
| 
      
 206 
     | 
    
         
            +
                        r: Any = record
         
     | 
| 
      
 207 
     | 
    
         
            +
                        self._set_value_at_path(r, path_indices, {self._value_key_suffix: path_value})
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                def _set_value_at_path(self, r: Any, path_indices: list[int], path_value: Any) -> None:
         
     | 
| 
      
 210 
     | 
    
         
            +
                    """Set the value at the given path indices."""
         
     | 
| 
      
 211 
     | 
    
         
            +
                    pe = [
         
     | 
| 
      
 212 
     | 
    
         
            +
                        *self.path_elements,
         
     | 
| 
      
 213 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 214 
     | 
    
         
            +
                    if self.relation_field:
         
     | 
| 
      
 215 
     | 
    
         
            +
                        pe.append(self.relation_field)
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                    # pe might be 1 longer than path_indices, that's why we use zip_longest
         
     | 
| 
      
 218 
     | 
    
         
            +
                    zipped = list(zip_longest(pe, path_indices, fillvalue=None))
         
     | 
| 
      
 219 
     | 
    
         
            +
                    for idx, (subpath, index) in enumerate(zipped[:-1]):
         
     | 
| 
      
 220 
     | 
    
         
            +
                        if not subpath:
         
     | 
| 
      
 221 
     | 
    
         
            +
                            raise InvalidRelationValue(  # pragma: no cover
         
     | 
| 
      
 222 
     | 
    
         
            +
                                f"Invalid structure, missing key at index {idx} in [{self.path_elements}, {path_indices}]."
         
     | 
| 
      
 223 
     | 
    
         
            +
                            )
         
     | 
| 
      
 224 
     | 
    
         
            +
                        if index is None:
         
     | 
| 
      
 225 
     | 
    
         
            +
                            raise InvalidRelationValue(  # pragma: no cover
         
     | 
| 
      
 226 
     | 
    
         
            +
                                f"Invalid structure, missing index at {subpath} in [{self.path_elements}, {path_indices}]."
         
     | 
| 
      
 227 
     | 
    
         
            +
                            )
         
     | 
| 
      
 228 
     | 
    
         
            +
                        r = self._set_default_value_at_path(r, subpath, index, path_indices)
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                    last_path, last_index = zipped[-1]
         
     | 
| 
      
 231 
     | 
    
         
            +
                    if last_path is None:  # pragma: no cover
         
     | 
| 
      
 232 
     | 
    
         
            +
                        raise InvalidRelationValue("Implementation error.")
         
     | 
| 
      
 233 
     | 
    
         
            +
                    if last_index is None:
         
     | 
| 
      
 234 
     | 
    
         
            +
                        # we have a relation_field at the end, so set it directly
         
     | 
| 
      
 235 
     | 
    
         
            +
                        dict_set(r, last_path, path_value)
         
     | 
| 
      
 236 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 237 
     | 
    
         
            +
                        # no relation_field at the end, so we set the whole object at the index
         
     | 
| 
      
 238 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 239 
     | 
    
         
            +
                            val = dict_lookup(r, last_path)
         
     | 
| 
      
 240 
     | 
    
         
            +
                        except KeyError:
         
     | 
| 
      
 241 
     | 
    
         
            +
                            val = []
         
     | 
| 
      
 242 
     | 
    
         
            +
                            dict_set(r, last_path, val)
         
     | 
| 
      
 243 
     | 
    
         
            +
                        if last_index < len(val):
         
     | 
| 
      
 244 
     | 
    
         
            +
                            val[last_index] = path_value
         
     | 
| 
      
 245 
     | 
    
         
            +
                        elif last_index == len(val):
         
     | 
| 
      
 246 
     | 
    
         
            +
                            val.append(path_value)
         
     | 
| 
      
 247 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 248 
     | 
    
         
            +
                            raise InvalidRelationValue(  # pragma: no cover # just sanity check
         
     | 
| 
      
 249 
     | 
    
         
            +
                                f"Invalid structure, missing index {last_index} "
         
     | 
| 
      
 250 
     | 
    
         
            +
                                f"at {last_path} in [{self.path_elements}, {path_indices}]."
         
     | 
| 
      
 251 
     | 
    
         
            +
                            )
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                def _set_default_value_at_path(self, r: Any, subpath: str, index: int, path_indices: list[int]) -> Any:
         
     | 
| 
      
 254 
     | 
    
         
            +
                    """Set default value of [] at the given path if missing."""
         
     | 
| 
      
 255 
     | 
    
         
            +
                    # look up the subpath and create it if missing
         
     | 
| 
      
 256 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 257 
     | 
    
         
            +
                        val = dict_lookup(r, subpath)
         
     | 
| 
      
 258 
     | 
    
         
            +
                    except KeyError:
         
     | 
| 
      
 259 
     | 
    
         
            +
                        dict_set(r, subpath, [])
         
     | 
| 
      
 260 
     | 
    
         
            +
                        val = dict_lookup(r, subpath)
         
     | 
| 
      
 261 
     | 
    
         
            +
                    if not isinstance(val, list):
         
     | 
| 
      
 262 
     | 
    
         
            +
                        raise InvalidRelationValue(  # pragma: no cover
         
     | 
| 
      
 263 
     | 
    
         
            +
                            f"Invalid structure, expecting list at {subpath} in [{self.path_elements}, {path_indices}]."
         
     | 
| 
      
 264 
     | 
    
         
            +
                        )
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
                    # now we have the array - if the index is within array, return the value at the index
         
     | 
| 
      
 267 
     | 
    
         
            +
                    if index < len(val):
         
     | 
| 
      
 268 
     | 
    
         
            +
                        return val[index]
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                    # if the index is exactly at the end of the array, we can append a new default value,
         
     | 
| 
      
 271 
     | 
    
         
            +
                    # which is always an empty dict
         
     | 
| 
      
 272 
     | 
    
         
            +
                    if index == len(val):
         
     | 
| 
      
 273 
     | 
    
         
            +
                        # append new default value which is always empty dict
         
     | 
| 
      
 274 
     | 
    
         
            +
                        r = {}
         
     | 
| 
      
 275 
     | 
    
         
            +
                        val.append(r)
         
     | 
| 
      
 276 
     | 
    
         
            +
                        return r
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                    # we can not skip indices, so if that would happen, raise error
         
     | 
| 
      
 279 
     | 
    
         
            +
                    raise InvalidRelationValue(  # pragma: no cover
         
     | 
| 
      
 280 
     | 
    
         
            +
                        f"Invalid structure, missing index {index} at {subpath} in [{self.path_elements}, {path_indices}]."
         
     | 
| 
      
 281 
     | 
    
         
            +
                    )
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
            def _deep_enumerate(nested_list: Any, max_depth: int, depth: int = 0) -> Generator[tuple[list[int], Any]]:
         
     | 
| 
      
 285 
     | 
    
         
            +
                """Enumerate all non-list items in a nested list structure."""
         
     | 
| 
      
 286 
     | 
    
         
            +
                for index, item in enumerate(nested_list):
         
     | 
| 
      
 287 
     | 
    
         
            +
                    current_path = [index]
         
     | 
| 
      
 288 
     | 
    
         
            +
                    if depth < max_depth - 1 and isinstance(item, (list, tuple)):
         
     | 
| 
      
 289 
     | 
    
         
            +
                        for sub_path, sub_item in _deep_enumerate(item, max_depth, depth + 1):
         
     | 
| 
      
 290 
     | 
    
         
            +
                            yield current_path + sub_path, sub_item
         
     | 
| 
      
 291 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 292 
     | 
    
         
            +
                        yield current_path, item
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
             
     | 
| 
      
 295 
     | 
    
         
            +
            def _for_each_deep(nested_list: Any, func: Any, levels: int) -> list[Any]:
         
     | 
| 
      
 296 
     | 
    
         
            +
                """Apply a function to each non-list item in a nested list structure."""
         
     | 
| 
      
 297 
     | 
    
         
            +
                result = []
         
     | 
| 
      
 298 
     | 
    
         
            +
                for item in nested_list:
         
     | 
| 
      
 299 
     | 
    
         
            +
                    if isinstance(item, (list, tuple)) and levels > 1:
         
     | 
| 
      
 300 
     | 
    
         
            +
                        result.append(_for_each_deep(item, func, levels=levels - 1))
         
     | 
| 
      
 301 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 302 
     | 
    
         
            +
                        result.append(func(item))
         
     | 
| 
      
 303 
     | 
    
         
            +
                return result
         
     | 
| 
      
 304 
     | 
    
         
            +
             
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
            class PIDArbitraryNestedListRelation(ArbitraryNestedListRelation, PIDRelation):  # type: ignore[override, misc]
         
     | 
| 
      
 307 
     | 
    
         
            +
                """PID list relation type."""
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/results.py
    RENAMED
    
    | 
         @@ -15,7 +15,6 @@ import logging 
     | 
|
| 
       15 
15 
     | 
    
         
             
            from typing import TYPE_CHECKING, Any
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
17 
     | 
    
         
             
            from invenio_access.permissions import Identity
         
     | 
| 
       18 
     | 
    
         
            -
            from invenio_records.api import RecordBase
         
     | 
| 
       19 
18 
     | 
    
         
             
            from invenio_records_resources.errors import _iter_errors_dict
         
     | 
| 
       20 
19 
     | 
    
         
             
            from invenio_records_resources.services.records.results import (
         
     | 
| 
       21 
20 
     | 
    
         
             
                RecordItem as BaseRecordItem,
         
     | 
| 
         @@ -27,7 +26,7 @@ from invenio_records_resources.services.records.results import ( 
     | 
|
| 
       27 
26 
     | 
    
         
             
            if TYPE_CHECKING:
         
     | 
| 
       28 
27 
     | 
    
         
             
                from invenio_access.permissions import Identity
         
     | 
| 
       29 
28 
     | 
    
         
             
                from invenio_drafts_resources.records.api import Draft
         
     | 
| 
       30 
     | 
    
         
            -
                from  
     | 
| 
      
 29 
     | 
    
         
            +
                from invenio_records_resources.records.api import Record
         
     | 
| 
       31 
30 
     | 
    
         | 
| 
       32 
31 
     | 
    
         | 
| 
       33 
32 
     | 
    
         
             
            log = logging.getLogger(__name__)
         
     | 
| 
         @@ -45,7 +44,7 @@ class ResultComponent: 
     | 
|
| 
       45 
44 
     | 
    
         
             
                    self._record_item = record_item
         
     | 
| 
       46 
45 
     | 
    
         
             
                    self._record_list = record_list
         
     | 
| 
       47 
46 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
                def update_data(self, identity: Identity, record:  
     | 
| 
      
 47 
     | 
    
         
            +
                def update_data(self, identity: Identity, record: Record, projection: dict, expand: bool) -> None:
         
     | 
| 
       49 
48 
     | 
    
         
             
                    """Update the projection data with additional information.
         
     | 
| 
       50 
49 
     | 
    
         | 
| 
       51 
50 
     | 
    
         
             
                    :param identity: The identity of the user making the request.
         
     | 
| 
         @@ -151,7 +150,9 @@ class RecordList(BaseRecordList): 
     | 
|
| 
       151 
150 
     | 
    
         
             
                            # Project the record
         
     | 
| 
       152 
151 
     | 
    
         
             
                            # TODO: check if this logic is correct
         
     | 
| 
       153 
152 
     | 
    
         
             
                            versions = hit_dict.get("versions", {})
         
     | 
| 
       154 
     | 
    
         
            -
                            if versions.get("is_latest_draft") and not versions.get("is_latest") 
     | 
| 
      
 153 
     | 
    
         
            +
                            if (versions.get("is_latest_draft") and not versions.get("is_latest")) or (
         
     | 
| 
      
 154 
     | 
    
         
            +
                                "publication_status" in hit_dict and hit_dict["publication_status"] == "draft"
         
     | 
| 
      
 155 
     | 
    
         
            +
                            ):
         
     | 
| 
       155 
156 
     | 
    
         
             
                                draft_class: type[Draft] | None = getattr(self._service, "draft_cls", None)
         
     | 
| 
       156 
157 
     | 
    
         
             
                                if draft_class is None:
         
     | 
| 
       157 
158 
     | 
    
         
             
                                    raise RuntimeError("Draft class is not defined in the service")  # pragma: no cover
         
     | 
| 
         @@ -9,7 +9,7 @@ description = "A set of runtime extensions of Invenio repository" 
     | 
|
| 
       9 
9 
     | 
    
         
             
            readme = "README.md"
         
     | 
| 
       10 
10 
     | 
    
         
             
            license = "MIT"
         
     | 
| 
       11 
11 
     | 
    
         
             
            dependencies = [
         
     | 
| 
       12 
     | 
    
         
            -
                "oarepo[rdm,tests]>= 
     | 
| 
      
 12 
     | 
    
         
            +
                "oarepo[rdm,tests]>=14,<15",
         
     | 
| 
       13 
13 
     | 
    
         
             
                "langcodes>=3.5.0",
         
     | 
| 
       14 
14 
     | 
    
         
             
                "oarepo-invenio-typing-stubs>=0.1.0",   # dependency on Descriptor for typed system fields
         
     | 
| 
       15 
15 
     | 
    
         
             
            ]
         
     | 
| 
         @@ -25,9 +25,6 @@ dev = [ 
     | 
|
| 
       25 
25 
     | 
    
         
             
            tests = [
         
     | 
| 
       26 
26 
     | 
    
         
             
                "pytest>=7.1.2",
         
     | 
| 
       27 
27 
     | 
    
         
             
            ]
         
     | 
| 
       28 
     | 
    
         
            -
            oarepo13=[
         
     | 
| 
       29 
     | 
    
         
            -
                "oarepo[rdm]>=13,<14",
         
     | 
| 
       30 
     | 
    
         
            -
            ]
         
     | 
| 
       31 
28 
     | 
    
         
             
            oarepo14=[
         
     | 
| 
       32 
29 
     | 
    
         
             
                "oarepo[rdm]>=14,<15",
         
     | 
| 
       33 
30 
     | 
    
         
             
            ]
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/records/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/resources/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/resources/config.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/config/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/base.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/date.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/params.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/facets/utils.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/generators.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/records/links.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/records/mapping.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/i18n.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/i18n_ui.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    
        {oarepo_runtime-2.0.0.dev36 → oarepo_runtime-2.0.0.dev38}/oarepo_runtime/services/schema/ui.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |