Skip to main content

OARepo Workflows

Workflow management for Invenio records.

Overview

This package enables state-based workflow management for Invenio records with:

  • State-based record lifecycle management with timestamps
  • Configurable permission policies per workflow state
  • Request-based state transitions with approval workflows
  • Auto-approval and escalation mechanisms
  • Model presets for automatic integration with oarepo-model
  • Multiple recipient support for requests

Installation

pip install oarepo-workflows

Requirements

  • Python 3.14+
  • Invenio 14.x (RDM)
  • oarepo-runtime >= 2.0.0

Key Features

1. Workflow Definition and Management

Source: oarepo_workflows/base.py, oarepo_workflows/ext.py

Define workflows with state-based permissions and request policies:

from oarepo_workflows import Workflow
from flask_babel import lazy_gettext as _

WORKFLOWS = {
    "default": Workflow(
        code="default",
        label=_("Default Workflow"),
        permission_policy_cls=DefaultWorkflowPermissions,
        request_policy_cls=DefaultWorkflowRequests,
    )
}

Access workflows through the extension:

from oarepo_workflows import current_oarepo_workflows

# Get workflow by code
workflow = current_oarepo_workflows.workflow_by_code["default"]

# Get workflow from record
workflow = current_oarepo_workflows.get_workflow(record)

# List all workflows
workflows = current_oarepo_workflows.record_workflows

2. Record System Fields

Source: oarepo_workflows/records/systemfields/

State Field

Tracks the current state of a record with automatic timestamp updates:

from oarepo_workflows.records.systemfields import (
    RecordStateField,
    RecordStateTimestampField,
)


class MyRecord(Record):
    state = RecordStateField(initial="draft")
    state_timestamp = RecordStateTimestampField()

Set state programmatically:

from oarepo_workflows import current_oarepo_workflows

# Change state with automatic notification
current_oarepo_workflows.set_state(identity, record, "published", commit=True, notify_later=True)

Workflow Field

Links parent records to their workflow definition:

from oarepo_workflows.records.systemfields import WorkflowField


class MyParentRecord(ParentRecord):
    workflow = WorkflowField()

3. Permission Management

Source: oarepo_workflows/services/permissions/

Workflow Permission Policy

Define state-based permissions for record operations:

from oarepo_workflows.services.permissions import (
    DefaultWorkflowPermissions,
    IfInState,
)
from invenio_rdm_records.services.generators import RecordOwners
from invenio_records_permissions.generators import AuthenticatedUser


class MyWorkflowPermissions(DefaultWorkflowPermissions):
    can_create = [AuthenticatedUser()]

    can_read = [
        IfInState("draft", [RecordOwners()]),
        IfInState("published", [AuthenticatedUser()]),
    ]

    can_update = [
        IfInState("draft", [RecordOwners()]),
    ]

    can_delete = [
        IfInState("draft", [RecordOwners()]),
    ]

Key permission generators:

  • IfInState(state, then_generators, else_generators) - Conditional permissions based on record state
  • FromRecordWorkflow(action) - Delegate permission check to workflow policy
  • SameAs(permission_name) - Reuse permissions from another action

Record Permission Policy

Use WorkflowRecordPermissionPolicy on RecordServiceConfig to delegate all permissions to workflows:

from oarepo_workflows.services.permissions import (
    WorkflowRecordPermissionPolicy,
)


class MyServiceConfig(RecordServiceConfig):
    permission_policy_cls = WorkflowRecordPermissionPolicy

4. Request-Based Workflows

Source: oarepo_workflows/requests/

Request Definition

Define requests that move records through workflow states:

from oarepo_workflows import (
    WorkflowRequest,
    WorkflowRequestPolicy,
    WorkflowTransitions,
    IfInState,
)
from invenio_rdm_records.services.generators import RecordOwners


class MyWorkflowRequests(WorkflowRequestPolicy):
    publish_request = WorkflowRequest(
        requesters=[IfInState("draft", [RecordOwners()])],
        recipients=[CommunityRole("curator")],
        transitions=WorkflowTransitions(submitted="submitted", accepted="published", declined="draft"),
    )

Request configuration:

  • requesters - Generators defining who can create the request
  • recipients - Generators defining who can approve the request
  • transitions - State changes for submitted/accepted/declined/cancelled
  • events - Additional events that can be submitted with the request
  • escalations - Auto-escalation if not resolved in time

Auto-Approval

Automatically approve requests when submitted:

from oarepo_workflows import AutoApprove


class MyWorkflowRequests(WorkflowRequestPolicy):
    edit_request = WorkflowRequest(
        requesters=[IfInState("published", [RecordOwners()])],
        recipients=[AutoApprove()],
    )

Request Escalation

Escalate unresolved requests to higher authority:

from datetime import timedelta
from oarepo_workflows import WorkflowRequestEscalation


class MyWorkflowRequests(WorkflowRequestPolicy):
    delete_request = WorkflowRequest(
        requesters=[IfInState("published", [RecordOwners()])],
        recipients=[CommunityRole("curator")],
        transitions=WorkflowTransitions(submitted="deleting", accepted="deleted", declined="published"),
        escalations=[WorkflowRequestEscalation(after=timedelta(days=14), recipients=[UserWithRole("administrator")])],
    )

Request Events

Define custom events that can be submitted on requests:

from oarepo_workflows.requests import WorkflowEvent


class MyWorkflowRequests(WorkflowRequestPolicy):
    review_request = WorkflowRequest(
        requesters=[RecordOwners()],
        recipients=[CommunityRole("reviewer")],
        events={"request_changes": WorkflowEvent(submitters=[CommunityRole("reviewer")])},
    )

5. Request Permissions

Source: oarepo_workflows/requests/permissions.py

The package provides CreatorsFromWorkflowRequestsPermissionPolicy which automatically extracts request creators from workflow definitions:

# In invenio.cfg
from oarepo_workflows.requests.permissions import (
    CreatorsFromWorkflowRequestsPermissionPolicy,
)

REQUESTS_PERMISSION_POLICY = CreatorsFromWorkflowRequestsPermissionPolicy

This policy:

  • Checks workflow request definitions for can_create permissions
  • Supports event-specific permissions (e.g., can_<request>_<event>_create)
  • Allows any user to search requests (but filters results by actual permissions)

6. Service Components

Source: oarepo_workflows/services/components/

Workflow Component

Ensures workflow is set when creating records:

from oarepo_workflows.services.components import WorkflowComponent


class MyServiceConfig(RecordServiceConfig):
    components = [
        WorkflowComponent,
        # ... other components
    ]

The component:

  • Validates workflow presence in input data
  • Sets workflow on parent record during creation
  • Runs before metadata component to ensure workflow-based permissions apply

7. Model Presets

Source: oarepo_workflows/model/presets/

Automatic integration with oarepo-model code generator:

Record Presets

  • WorkflowsParentRecordPreset - Adds WorkflowField to parent records
  • WorkflowsDraftPreset - Adds RecordStateField and RecordStateTimestampField to drafts
  • WorkflowsRecordPreset - Adds state fields to published records
  • WorkflowsParentRecordMetadataPreset - Adds workflow column to parent metadata table
  • WorkflowsMappingPreset - Adds OpenSearch mappings for state and workflow fields

Service Presets

  • WorkflowsServiceConfigPreset - Adds WorkflowComponent to service components
  • WorkflowsPermissionPolicyPreset - Sets WorkflowRecordPermissionPolicy on service config
  • WorkflowsParentRecordSchemaPreset - Adds workflow field to parent schema
  • WorkflowsRecordSchemaPreset - Adds state fields to record schema

8. State Change Notifications

Source: oarepo_workflows/services/uow.py

Register custom handlers for state changes via entry points:

# In your package
def my_state_change_handler(
    identity,
    record,
    previous_state,
    new_state,
    *args,
    uow=None,
    **kwargs
):
    # Handle state change
    pass

# In pyproject.toml
[project.entry-points."oarepo_workflows.state_changed_notifiers"]
my_handler = "my_package.handlers:my_state_change_handler"

9. Multiple Recipients

Source: oarepo_workflows/services/multiple_entities/

Support for requests with multiple recipients:

from oarepo_workflows import WorkflowRequest


class MyWorkflowRequests(WorkflowRequestPolicy):
    review_request = WorkflowRequest(
        requesters=[RecordOwners()], recipients=[CommunityRole("reviewer"), CommunityRole("curator")]
    )

The first recipient becomes the primary recipient. Multiple recipients are tracked via the multiple entity resolver.

Configuration

Basic Configuration

In invenio.cfg:

from oarepo_workflows import Workflow
from my_workflows.permissions import DefaultPermissions
from my_workflows.requests import DefaultRequests

WORKFLOWS = {
    "default": Workflow(
        code="default",
        label="Default Workflow",
        permission_policy_cls=DefaultPermissions,
        request_policy_cls=DefaultRequests,
    )
}

Community Roles

Define roles used in workflow permissions:

from invenio_i18n import lazy_gettext as _

COMMUNITIES_ROLES = [
    dict(name="curator", title=_("Curator"), description=_("Curator of the community")),
    dict(name="reviewer", title=_("Reviewer"), description=_("Reviewer of submissions")),
]

Default Workflow Events

Define events available to all workflows:

from oarepo_workflows.requests import WorkflowEvent

DEFAULT_WORKFLOW_EVENTS = {"comment": WorkflowEvent(submitters=[Creator(), Receiver()])}

Development

Setup

git clone https://github.com/oarepo/oarepo-workflows.git
cd oarepo-workflows
./run.sh venv

Running Tests

./run.sh test

Entry Points

The package registers several Invenio entry points:

[project.entry-points."invenio_base.apps"]
oarepo_workflows = "oarepo_workflows.ext:OARepoWorkflows"

[project.entry-points."invenio_base.api_apps"]
oarepo_workflows = "oarepo_workflows.ext:OARepoWorkflows"

[project.entry-points."invenio_requests.entity_resolvers"]
auto_approve = "oarepo_workflows.resolvers.auto_approve:AutoApproveResolver"
multiple = "oarepo_workflows.resolvers.multiple_entities:MultipleEntitiesResolver"

[project.entry-points."invenio_base.finalize_app"]
oarepo_workflows = "oarepo_workflows.ext:finalize_app"

[project.entry-points."invenio_base.api_finalize_app"]
oarepo_workflows = "oarepo_workflows.ext:finalize_app"

[project.entry-points."invenio_config.module"]
oarepo_workflows = "oarepo_workflows.initial_config"

License

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

OARepo Workflows 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-workflows 7.3.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-workflows 7.3.0
File Size Uploaded
oarepo_workflows-7.3.0.tar.gz 36.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for oarepo-workflows 7.3.0
File Interpreter ABI Platform
oarepo_workflows-7.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 109.3 kB

Release files / oarepo_workflows-7.3.0.tar.gz

Download URL oarepo_workflows-7.3.0.tar.gz
Size 36.5 kB
Tags Source
SHA-256 checksum
How to use checksums
265a3d34d6a0315e8c727284469236ea4960cbdc2dd72deb8d6c9b363b29f7d3
BLAKE2b-256 checksum
How to use checksums
e1ba7b7581e6126791957e5eb53c35e840edca8a75754a6c1afef9c969a20b17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / oarepo_workflows-7.3.0-py3-none-any.whl

Download URL oarepo_workflows-7.3.0-py3-none-any.whl
Size 72.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ccfe6e41450f7590520c71a558b5a5ef42c76e830710d41bbd14ad8174e9e8c8
BLAKE2b-256 checksum
How to use checksums
4041fec235b4126329e951a9b46bf94936fab4919addf84eab5ecbae865e773e
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

This release

7.3.0 This release

2 release files

7.2.0

2 release files

7.1.0

2 release files

7.0.0

2 release files

6.1.0

2 release files

6.0.1

2 release files

6.0.0

2 release files

5.0.1

2 release files

5.0.0

2 release files

4.2.1

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.2

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.0.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.16

2 release files

1.1.15

2 release files

1.1.14

2 release files

1.1.13

2 release files

1.1.12

2 release files

1.1.10

2 release files

1.1.9

2 release files

1.1.8

2 release files

1.1.7

2 release files

1.1.6

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

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

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