Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
(only up to `limit` users are returned, by default 20)
- enh: wait between retries of failed S3 upload requests (5 s, doubling
with each retry) instead of retrying immediately
- enh: use the API action `dcor_group_list_for_user` (ckanext-dcor_schemas
1.2.0) for listing the collections of a user, with a fallback for
older servers (#108)
- ref: convert `dcoraid.cli` module into a package; the upload task CLI
now lives in `dcoraid.cli.task_upload_task`
- ref: use `super()`, PEP 604 optional type hints, and builtin/
Expand Down
21 changes: 18 additions & 3 deletions dcoraid/dbmodel/db_api.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
from __future__ import annotations

from itertools import islice
import logging
import sys
import time
import urllib.parse

import numpy as np

from ..api.errors import APIBadRequest

from .db_core import DBInterrogator
from .extract import DBExtract


logger = logging.getLogger(__name__)


if sys.version_info >= (3, 12):
from itertools import batched
else:
Expand Down Expand Up @@ -50,13 +56,22 @@ def get_collections_user(self):
This includes all collections in which the user has any role
(also "member").
"""
user_id = self.api.user_id
if user_id is None:
return []
try:
# DCOR servers with ckanext-dcor_schemas >= 1.2.0
return self.api.get("dcor_group_list_for_user")
except APIBadRequest as e:
if "Action name not known" not in str(e):
raise
logger.info("The server does not support the API action "
"`dcor_group_list_for_user`, falling back to "
"checking the members of all collections.")
# CKAN's `group_list_authz` only returns the collections that the
# user may edit, and there is no API call that returns the
# collections in which the user is a "member". Therefore, we have
# to check the members of every collection.
user_id = self.api.user_id
if user_id is None:
return []
collections = []
for col in self.get_collections():
members = self.api.get("member_list",
Expand Down
42 changes: 39 additions & 3 deletions tests/test_dbmodel_api_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

import pytest

from dcoraid.api.errors import APIBadRequest
from dcoraid.dbmodel import db_api


def make_interrogator(num_circles=60, num_collections=40, user_id="user-1",
member_of=(), max_items=25):
member_of=(), max_items=25, group_list_action=False):
"""Return an APIInterrogator with a mocked CKAN API

Parameters
Expand All @@ -20,6 +21,9 @@ def make_interrogator(num_circles=60, num_collections=40, user_id="user-1",
Collection names in which the user is a member
max_items: int
Maximum number of items CKAN returns with `all_fields=True`
group_list_action: bool
Whether the server supports the `dcor_group_list_for_user` action
(ckanext-dcor_schemas >= 1.2.0)
"""
circles = [{"id": f"oid-{ii:03d}", "name": f"circle-{ii:03d}"}
for ii in range(num_circles)]
Expand All @@ -44,6 +48,12 @@ def get(api_call, **kwargs):
return [["other-user", "user", "admin"]]
elif api_call == "organization_list_for_user":
return circles[:2]
elif api_call == "dcor_group_list_for_user":
if group_list_action:
return [c for c in collections if c["name"] in member_of]
else:
raise APIBadRequest("Bad request - Action name not known: "
"dcor_group_list_for_user")
raise ValueError(f"Unexpected API call {api_call}")

api = mock.MagicMock()
Expand Down Expand Up @@ -99,10 +109,36 @@ def test_get_circles_user():

def test_get_collections_user():
"""Collections in which the user has any role (also "member")"""
ai, _ = make_interrogator(num_collections=40,
member_of=["collection-003", "collection-030"])
ai, calls = make_interrogator(num_collections=40,
member_of=["collection-003",
"collection-030"],
group_list_action=True)
assert [c["name"] for c in ai.get_collections_user()] == \
["collection-003", "collection-030"]
# the server-side action is used (one request)
assert [c[0] for c in calls] == ["dcor_group_list_for_user"]


def test_get_collections_user_fallback():
"""Older servers do not support `dcor_group_list_for_user`"""
ai, calls = make_interrogator(num_collections=40,
member_of=["collection-003",
"collection-030"],
group_list_action=False)
assert [c["name"] for c in ai.get_collections_user()] == \
["collection-003", "collection-030"]
# fall back to checking the members of all collections
api_calls = [c[0] for c in calls]
assert api_calls[0] == "dcor_group_list_for_user"
assert api_calls.count("member_list") == 40


def test_get_collections_user_other_bad_request():
"""Only fall back if the action is not known to the server"""
ai, _ = make_interrogator()
ai.api.get.side_effect = APIBadRequest("Bad request - something else")
with pytest.raises(APIBadRequest, match="something else"):
ai.get_collections_user()


def test_get_lists_user_anonymous():
Expand Down
Loading