Як інтегрувати PyDataverse у GitHub Actions (з прикладом workflow.yml)
Додано: П'ят серпня 15, 2025 8:32 am
Цей гайд показує, як автоматично оновлювати дані та метадані в DataverseUA з GitHub-репозиторію за допомогою PyDataverse і GitHub Actions. Підходить для регулярних оновлень (CI/CD), формування нових версій наборів і публікації.
В архіві окремі файли (dataverse-sync.yml, update_dataverse.py, metadata.json) - Розпакуйте в корені репозиторію GitHub, додайте Secrets, комітьте зміни в main
-----------------------------------------
Що потрібно підготувати
- Обліковий запис у DataverseUA та API-токен.
- DOI (persistentId) цільового датасету або створеної чернетки.
- Репозиторій на GitHub з папками data/ (файли) та metadata/ (метадані).
- Додайте у GitHub → Settings → Secrets and variables → Actions такі Secrets:
• DATAVERSE_BASE_URL = https://opendata.nas.gov.ua
• DATAVERSE_API_TOKEN = ваш_токен
• DATASET_PERSISTENT_ID = напр. doi:10.5072/FK2/ABCDEFG
Код: Виділити все
.
├─ data/
│ ├─ 2025-08/
│ │ └─ data.csv
│ └─ docs/report.pdf
└─ metadata/
└─ metadata.json
Приклад файлу workflow: .github/workflows/dataverse-sync.yml
Код: Виділити все
name: Dataverse Sync (PyDataverse)
on:
push:
branches: [ main ]
paths:
- 'data/**'
- 'metadata/**'
workflow_dispatch:
inputs:
publish:
description: 'Publish dataset after upload? (true/false)'
required: false
default: 'false'
release_type:
description: 'Publish type (major/minor)'
required: false
default: 'minor'
permissions:
contents: read
env:
DATAVERSE_BASE_URL: ${{ secrets.DATAVERSE_BASE_URL }}
DATAVERSE_API_TOKEN: ${{ secrets.DATAVERSE_API_TOKEN }}
DATASET_PERSISTENT_ID: ${{ secrets.DATASET_PERSISTENT_ID }}
DATA_DIR: data
METADATA_FILE: metadata/metadata.json
jobs:
sync:
runs-on: ubuntu-latest
concurrency:
group: dataverse-sync-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate secrets
run: |
for v in DATAVERSE_BASE_URL DATAVERSE_API_TOKEN DATASET_PERSISTENT_ID; do
if [ -z "${!v}" ]; then
echo "::error title=Missing secret::$v is not set"
exit 1
fi
done
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'pip'
- name: Install deps
run: |
python -m pip install --upgrade pip
pip install pydataverse requests python-dotenv
- name: Create updater script
run: |
mkdir -p .github/scripts
cat > .github/scripts/update_dataverse.py <<'PY'
import os, sys, json, pathlib, requests
from pyDataverse.api import Api
BASE = os.environ["DATAVERSE_BASE_URL"].rstrip("/")
TOKEN = os.environ["DATAVERSE_API_TOKEN"]
PID = os.environ["DATASET_PERSISTENT_ID"]
DATA_DIR = os.environ.get("DATA_DIR", "data")
META_FILE = os.environ.get("METADATA_FILE", "metadata/metadata.json")
PUBLISH = (sys.argv[1].lower() == "true") if len(sys.argv) > 1 else False
REL_TYPE = sys.argv[2] if len(sys.argv) > 2 else "minor"
headers = {"X-Dataverse-key": TOKEN}
api = Api(BASE, TOKEN)
def get_dataset_id_by_pid(pid: str) -> int:
r = requests.get(f"{BASE}/api/datasets/:persistentId", params={"persistentId": pid})
r.raise_for_status()
return r.json()["data"]["id"]
ds_id = get_dataset_id_by_pid(PID)
# (1) Оновити метадані чернетки, якщо є metadata/metadata.json
meta_path = pathlib.Path(META_FILE)
if meta_path.is_file():
with open(meta_path, "r", encoding="utf-8") as f:
meta_payload = json.load(f)
r = requests.put(f"{BASE}/api/datasets/{ds_id}/versions/:draft",
headers=headers, json=meta_payload)
if r.status_code not in (200, 201):
print("::warning::Metadata update failed:", r.status_code, r.text)
# (2) Завантажити всі файли з папки data/ у чернетку набору
data_path = pathlib.Path(DATA_DIR)
uploaded = []
if data_path.is_dir():
for p in sorted(data_path.rglob("*")):
if p.is_file():
# directoryLabel збереже підпапки у Dataverse
dir_label = ""
try:
dir_label = str(p.parent.relative_to(data_path))
except ValueError:
dir_label = ""
json_data = {"directoryLabel": dir_label} if dir_label else {}
files = {
"file": (p.name, open(p, "rb")),
"jsonData": (None, json.dumps(json_data))
}
r = requests.post(f"{BASE}/api/datasets/:persistentId/add",
params={"persistentId": PID},
headers=headers, files=files)
if r.status_code not in (200, 201):
print(f"::warning::Upload failed for {p}: {r.status_code} {r.text}")
else:
uploaded.append(str(p))
else:
print("::notice::No data directory found; skipping file upload")
print(f"Uploaded files: {uploaded}")
# (3) Опублікувати нову версію, якщо ввімкнено publish
if PUBLISH:
r = requests.post(f"{BASE}/api/datasets/:persistentId/actions/:publish",
params={"persistentId": PID, "type": REL_TYPE},
headers=headers)
if r.status_code not in (200, 201):
print("::error::Publish failed:", r.status_code, r.text)
sys.exit(1)
print(f"Published dataset as {REL_TYPE} release.")
PY
chmod +x .github/scripts/update_dataverse.py
- name: Run updater
run: |
python .github/scripts/update_dataverse.py "${{ github.event.inputs.publish || 'false' }}" "${{ github.event.inputs.release_type || 'minor' }}"
Мінімальний приклад metadata/metadata.json
Порада: це payload для PUT /api/datasets/{id}/versions/:draft (citation block).
Код: Виділити все
{
"metadataBlocks": {
"citation": {
"fields": [
{ "typeName": "title", "typeClass": "primitive", "value": "Щотижневе оновлення даних" },
{ "typeName": "author", "typeClass": "compound", "value": [
{ "authorName": { "value": "Прізвище Ім'я" } }
]},
{ "typeName": "datasetContact", "typeClass": "compound", "value": [
{ "datasetContactEmail": { "value": "you@example.org" } }
]},
{ "typeName": "dsDescription", "typeClass": "compound", "value": [
{ "dsDescriptionValue": { "value": "Автоматичне оновлення через GitHub Actions (PyDataverse)." } }
]}
]
}
}
}
Як це працює (коротко)
- Тригери: будь-які зміни у папках data/** або metadata/** запускають workflow. Також можна запустити вручну (Run workflow) і обрати publish=true/false, major/minor.
- Метадані: якщо існує metadata/metadata.json, чернетка набору оновлюється (PUT /versions/:draft).
- Файли: усі файли з data/ додаються до чернетки (POST /datasets/:persistentId/add). Підпапки зберігаються через directoryLabel.
- Публікація: якщо publish=true — викликається actions/:publish?type=major|minor.
Поширені помилки та підказки
Код: Виділити все
401 Unauthorized → неправильний або прострочений токен; перевірте DATAVERSE_API_TOKEN.
403 Forbidden → бракує прав на оновлення набору; перевірте ролі користувача в DataverseUA.
404 Not Found → неправильний DOI (DATASET_PERSISTENT_ID) або базовий URL.
413 Payload Too Large → занадто великий файл; завантажуйте частинами або збільшіть ліміти (на боці інстанції).
415 Unsupported Media Type → вкажіть правильний multipart/form-data для файлів (в прикладі вже так).
- Тримайте секрети тільки у GitHub Secrets, не в коді.
- Для великих наборів краще запускати workflow вручну (nightly) або з чергою.
- Ведіть CHANGELOG.md у репозиторії й додавайте його як файл до Dataverse.
Корисні посилання
• PyDataverse: https://pydataverse.readthedocs.io
• Dataverse API: https://guides.dataverse.org/en/latest/api/
• DataverseUA: https://opendata.nas.gov.ua
-----------------------------------------
Запитання? Діліться у відповідях: допоможемо адаптувати workflow під ваш кейс (наприклад, авто-версіювання, попередні перевірки якості, або інтеграцію з MEE/HPC).