Skip to content

Commit

Permalink
Merge branch 'hotfix/4.1.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
peichman-umd committed Apr 30, 2024
2 parents 96e887a + 58afb37 commit c465af3
Show file tree
Hide file tree
Showing 22 changed files with 276 additions and 156 deletions.
2 changes: 1 addition & 1 deletion plastron-cli/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "plastron-cli"
version = "4.1.3"
version = "4.1.4"
requires-python = ">= 3.8"
dependencies = [
"BeautifulSoup4",
Expand Down
2 changes: 1 addition & 1 deletion plastron-cli/src/plastron/cli/commands/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def publish(ctx, uris: Iterable[str], force_hidden: bool = False, force_visible:
try:
handle = resource.publish(
handle_client=ctx.obj.handle_client,
public_url=ctx.obj.get_public_url(uri),
public_url=ctx.obj.get_public_url(resource),
force_hidden=force_hidden,
force_visible=force_visible,
)
Expand Down
46 changes: 27 additions & 19 deletions plastron-cli/tests/commands/test_publish.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import dataclasses
from argparse import Namespace
from typing import Optional
from random import randint
from unittest.mock import MagicMock
from uuid import uuid4

import pytest

from plastron.cli.commands.publish import Command, publish
from plastron.client import Endpoint, Client, TypedText
from plastron.context import PlastronContext
from plastron.handles import Handle
from plastron.handles import HandleInfo, HandleServerError
from plastron.models.umd import Item
from plastron.namespaces import umdaccess
from plastron.repo import Repository, RepositoryError
Expand All @@ -18,17 +17,21 @@

@pytest.fixture
def handle():
return Handle(
return HandleInfo(
exists=True,
prefix='1903.1',
suffix='123',
url='http://digital-local/foo',
)


class MockHandleClient:
default_repo = 'fcrepo'

GET_HANDLE_LOOKUP = {
# existing handle with fcrepo target URL
'1903.1/123': Handle(
'1903.1/123': HandleInfo(
exists=True,
prefix='1903.1',
suffix='123',
url='http://digital-local/foo',
Expand All @@ -37,45 +40,50 @@ class MockHandleClient:
# if publishing the resource with this handle,
# should call the handle server to update the
# target URL
'1903.1/456': Handle(
'1903.1/456': HandleInfo(
exists=True,
prefix='1903.1',
suffix='456',
url='http://fedora2-local/bar',
),
# there is no handle with this prefix/suffix pair
'1903.1/789': None,
'1903.1/789': HandleInfo(
exists=False,
),
}
FIND_HANDLE_LOOKUP = {
# this fcrepo resource has a handle and its target
# URL points to the correct public URL
'http://fcrepo-local:8080/fcrepo/rest/foo': Handle(
'http://fcrepo-local:8080/fcrepo/rest/foo': HandleInfo(
exists=True,
prefix='1903.1',
suffix='123',
url='http://digital-local/foo',
),
# this fcrepo resource has a handle and its target
# URL needs to be updated to the correct public URL
'http://fcrepo-local:8080/fcrepo/rest/bar': Handle(
'http://fcrepo-local:8080/fcrepo/rest/bar': HandleInfo(
exists=True,
prefix='1903.1',
suffix='456',
url='http://fedora2-local/bar',
),
}

def get_handle(self, handle: str, _repo: str = None) -> Optional[Handle]:
return self.GET_HANDLE_LOOKUP.get(handle, None)
def get_info(self, prefix: str, suffix: str) -> HandleInfo:
return self.GET_HANDLE_LOOKUP.get(f'{prefix}/{suffix}', HandleInfo(exists=False))

def find_handle(self, repo_uri: str, _repo: str = None) -> Optional[Handle]:
return self.FIND_HANDLE_LOOKUP.get(repo_uri, None)
def find_handle(self, repo_id: str, _repo: str = None) -> HandleInfo:
return self.FIND_HANDLE_LOOKUP.get(repo_id, HandleInfo(exists=False))

@staticmethod
def create_handle(repo_uri: str, url: str, prefix: str = None, _repo: str = None) -> Optional[Handle]:
if repo_uri.endswith('NO_HANDLE'):
return None
return Handle(prefix=prefix, suffix=str(uuid4()), url=url)
def create_handle(repo_id: str, url: str, prefix: str = None, _repo: str = None) -> HandleInfo:
if repo_id.endswith('NO_HANDLE'):
raise HandleServerError('no handle')
return HandleInfo(exists=True, prefix=prefix, suffix=str(randint(1000, 10000)), url=url)

@staticmethod
def update_handle(handle: Handle, **fields) -> Handle:
def update_handle(handle: HandleInfo, **fields) -> HandleInfo:
return dataclasses.replace(handle, **fields)


Expand All @@ -91,7 +99,7 @@ def get_mock_context(obj, path):
spec=PlastronContext,
repo=mock_repo,
handle_client=MockHandleClient(),
get_public_url=lambda uri: uri.replace('fcrepo-local:8080/fcrepo/rest', 'digital-local')
get_public_url=lambda res: res.url.replace('fcrepo-local:8080/fcrepo/rest', 'digital-local')
)


Expand Down
2 changes: 1 addition & 1 deletion plastron-client/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "plastron-client"
version = "4.1.3"
version = "4.1.4"
requires-python = ">= 3.8"
dependencies = [
"rdflib",
Expand Down
2 changes: 1 addition & 1 deletion plastron-models/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "plastron-models"
version = "4.1.3"
version = "4.1.4"
requires-python = ">= 3.8"
dependencies = [
"edtf_validate",
Expand Down
117 changes: 50 additions & 67 deletions plastron-models/src/plastron/handles/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import dataclasses
import logging
from dataclasses import dataclass
from http import HTTPStatus
from typing import Optional, List
from typing import List, Dict, Any

import requests
from requests import Session
from requests_jwtauth import HTTPBearerAuth

from plastron.namespaces import dcterms, umdtype
Expand All @@ -26,113 +25,97 @@ def parse_handle_string(handle: str) -> List[str]:
) from e


def parse_result(result: Dict[str, Any]) -> Dict[str, Any]:
logger.debug(f'Raw result: {result}')
if 'request' in result:
request = result['request']
del result['request']
result.update(request)
return result


@dataclass
class Handle:
prefix: str
suffix: str
class HandleInfo:
exists: bool
handle_url: str = None
prefix: str = None
suffix: str = None
url: str = None

@classmethod
def parse(cls, handle_string: str) -> 'Handle':
prefix, suffix = parse_handle_string(handle_string)
return cls(prefix=prefix, suffix=suffix)
repo: str = None
repo_id: str = None

def __str__(self):
return '/'.join((self.prefix, self.suffix))
"""The handle in `{prefix}/{suffix}` form"""
return f'{self.prefix}/{self.suffix}'

@property
def hdl_uri(self):
"""The handle in `hdl:{prefix}/{suffix}` form"""
return f'hdl:{self}'

@property
def has_url(self):
return self.url is not None


class HandleServiceClient:
def __init__(self, endpoint_url: str, jwt_token: str, default_prefix: str = None, default_repo: str = None):
self.endpoint_url = endpoint_url
self.auth = HTTPBearerAuth(jwt_token)
self.default_prefix = default_prefix
self.default_repo = default_repo
self.session = Session()
self.session.auth = HTTPBearerAuth(jwt_token)

def resolve(self, handle: Handle) -> Optional[Handle]:
"""Attempt to resolve the given handle. If successful, returns a new `Handle`
object with the `url` field populated. If the handle is not found, returns
`None`. For any other error response, raises a `HandleServerError`."""
path = f'/handles/{handle.prefix}/{handle.suffix}'
url = self.endpoint_url + path
response = requests.get(url=url, auth=self.auth)
if response.status_code == HTTPStatus.NOT_FOUND:
return None
elif not response.ok:
raise HandleServerError(str(response))
return Handle(
prefix=handle.prefix,
suffix=handle.suffix,
url=response.json().get('url', None),
def get_info(self, prefix: str, suffix: str):
url = self.endpoint_url + '/handles/info'
response = self.session.get(
url=url,
params={
'prefix': prefix,
'suffix': suffix,
},
)
if not response.ok:
raise HandleServerError(str(response))

return HandleInfo(**parse_result(response.json()))

def find_handle(self, repo_uri: str, repo: str = None) -> Optional[Handle]:
def find_handle(self, repo_id: str, repo: str = None) -> HandleInfo:
url = self.endpoint_url + '/handles/exists'
response = requests.get(
response = self.session.get(
url=url,
auth=self.auth,
params={
'repo': repo or self.default_repo,
'repo_id': repo_uri,
'repo_id': repo_id,
},
)
if not response.ok:
raise HandleServerError(str(response))

result = response.json()
logger.debug(result)
return HandleInfo(**parse_result(response.json()))

if not result['exists']:
return None

return Handle(
prefix=result['prefix'],
suffix=result['suffix'],
url=result['url'],
)

def create_handle(self, repo_uri: str, url: str, prefix: str = None, repo: str = None) -> Handle:
def create_handle(self, repo_id: str, url: str, prefix: str = None, repo: str = None) -> HandleInfo:
request = {
'prefix': prefix or self.default_prefix,
'repo': repo or self.default_repo,
'repo_id': repo_uri,
'repo_id': repo_id,
'url': url,
}
response = requests.post(
response = self.session.post(
f'{self.endpoint_url}/handles',
json=request,
auth=self.auth,
)
if not response.ok:
raise HandleServerError(str(response))
result = response.json()
logger.debug(result)
return Handle(
prefix=result['request']['prefix'],
suffix=result['suffix'],
url=result['request']['url'],
)

def update_handle(self, handle: Handle, **fields) -> Handle:
updated_handle = dataclasses.replace(handle, **fields)
response = requests.patch(
f'{self.endpoint_url}/handles/{handle.prefix}/{handle.suffix}',
json=dataclasses.asdict(updated_handle),
auth=self.auth,
return HandleInfo(exists=True, **parse_result(response.json()))

def update_handle(self, handle_info: HandleInfo, **fields) -> HandleInfo:
updated_handle_info = dataclasses.replace(handle_info, **fields)
response = self.session.patch(
f'{self.endpoint_url}/handles/{handle_info.prefix}/{handle_info.suffix}',
json=dataclasses.asdict(updated_handle_info),
)
if not response.ok:
raise HandleServerError(str(response))
result = response.json()
logger.debug(result)
return updated_handle

return updated_handle_info


class HandleError(Exception):
Expand Down
7 changes: 7 additions & 0 deletions plastron-models/src/plastron/models/fedora.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from plastron.namespaces import fedora
from plastron.rdfmapping.descriptors import ObjectProperty
from plastron.rdfmapping.resources import RDFResource


class FedoraResource(RDFResource):
parent = ObjectProperty(fedora.hasParent)
17 changes: 10 additions & 7 deletions plastron-models/tests/test_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import pytest
from rdflib import Literal

from plastron.handles import Handle, HandleBearingResource, HandleServerError, HandleServiceClient
from plastron.handles import HandleBearingResource, HandleServerError, HandleServiceClient, HandleInfo
from plastron.namespaces import umdtype


@pytest.fixture
def handle():
return Handle(prefix='1903.1', suffix='123', url='http://example.com/foobar')
return HandleInfo(exists=True, prefix='1903.1', suffix='123', url='http://example.com/foobar')


def test_handle_attributes(handle):
Expand Down Expand Up @@ -57,7 +57,7 @@ def test_get_handle_does_not_exist(handle_client):
uri='http://handle-local:3000/handles/exists',
body=json.dumps({'exists': False})
)
assert handle_client.find_handle('http://example.com/foobar') is None
assert not handle_client.find_handle('http://example.com/foobar').exists


@httpretty.activate
Expand All @@ -82,7 +82,7 @@ def test_create_handle_success(handle_client):
{'suffix': '123', 'request': {'url': 'http://example.com/foobar', 'prefix': '1903.1'}}
)
)
handle = handle_client.create_handle(repo_uri='http://localhost/fcrepo/foobar', url='http://example.com/foobar')
handle = handle_client.create_handle(repo_id='http://localhost/fcrepo/foobar', url='http://example.com/foobar')
assert handle.prefix == '1903.1'
assert handle.suffix == '123'
assert handle.url == 'http://example.com/foobar'
Expand All @@ -96,16 +96,19 @@ def test_create_handle_error(handle_client):
status=HTTPStatus.BAD_REQUEST,
)
with pytest.raises(HandleServerError):
handle_client.create_handle(repo_uri='http://localhost/fcrepo/foobar', url='http://example.com/foobar')
handle_client.create_handle(repo_id='http://localhost/fcrepo/foobar', url='http://example.com/foobar')


@httpretty.activate
def test_update_handle(handle, handle_client):
httpretty.register_uri(
httpretty.PATCH,
uri='http://handle-local:3000/handles/1903.1/123',
body=json.dumps(
{'suffix': '123', 'request': {'url': 'http://example.com/foobar', 'prefix': '1903.1'}}
)
)
updated_handle = handle_client.update_handle(handle=handle, url='http://example.com/new-url')
updated_handle = handle_client.update_handle(handle_info=handle, url='http://example.com/new-url')
assert updated_handle.url == 'http://example.com/new-url'


Expand All @@ -117,4 +120,4 @@ def test_update_handle_error(handle, handle_client):
status=HTTPStatus.BAD_REQUEST,
)
with pytest.raises(HandleServerError):
handle_client.update_handle(handle=handle, url='http://example.com/new-url')
handle_client.update_handle(handle_info=handle, url='http://example.com/new-url')
Loading

0 comments on commit c465af3

Please sign in to comment.