dhclients 0.1.0__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.
dhclients-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dhclients
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DARIAH Python client library for repository.dariah.de access.
|
|
5
|
+
Author: Stefan Hynek, Ubbo Veentjer
|
|
6
|
+
Author-email: Stefan Hynek <hynek@sub.uni-goettingen.de>, Ubbo Veentjer <veentjer@sub.uni-goettingen.de>
|
|
7
|
+
License-Expression: LGPL-3.0-or-later
|
|
8
|
+
Requires-Dist: requests>=2.34.2
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
<!--
|
|
13
|
+
SPDX-FileCopyrightText: 2026 Georg-August-Universität Göttingen
|
|
14
|
+
|
|
15
|
+
SPDX-License-Identifier: CC0-1.0
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
# DHRep Python clients
|
|
19
|
+
|
|
20
|
+
The DHRep Python clients provide access to the [DARIAH Repository](https://repository.de.dariah.eu/)
|
|
21
|
+
services [API](https://doc.repository.de.dariah.eu/).
|
|
22
|
+
|
|
23
|
+
## Installation and Usage
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install dhclients
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Development
|
|
30
|
+
|
|
31
|
+
### Pre-Commit
|
|
32
|
+
|
|
33
|
+
install pre-commmit
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
uv tool install pre-commit --with pre-commit-uv
|
|
37
|
+
uv run pre-commit install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Running integration tests
|
|
42
|
+
|
|
43
|
+
We run integration tests against the dev repo (trep)
|
|
44
|
+
|
|
45
|
+
Get your auth token by log in on publikator: https://trep.de.dariah.eu/publikator/mainView
|
|
46
|
+
click on "Show storage token", copy your token and add to env
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
DHREP_ENDPOINT=development DARIAH_STORAGE_TOKEN=my-secret-token-here uv run pytest --integration
|
|
50
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Georg-August-Universität Göttingen
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC0-1.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# DHRep Python clients
|
|
8
|
+
|
|
9
|
+
The DHRep Python clients provide access to the [DARIAH Repository](https://repository.de.dariah.eu/)
|
|
10
|
+
services [API](https://doc.repository.de.dariah.eu/).
|
|
11
|
+
|
|
12
|
+
## Installation and Usage
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pip install dhclients
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Development
|
|
19
|
+
|
|
20
|
+
### Pre-Commit
|
|
21
|
+
|
|
22
|
+
install pre-commmit
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
uv tool install pre-commit --with pre-commit-uv
|
|
26
|
+
uv run pre-commit install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Running integration tests
|
|
31
|
+
|
|
32
|
+
We run integration tests against the dev repo (trep)
|
|
33
|
+
|
|
34
|
+
Get your auth token by log in on publikator: https://trep.de.dariah.eu/publikator/mainView
|
|
35
|
+
click on "Show storage token", copy your token and add to env
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
DHREP_ENDPOINT=development DARIAH_STORAGE_TOKEN=my-secret-token-here uv run pytest --integration
|
|
39
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Georg-August-Universität Göttingen
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: CC0-1.0
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "dhclients"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "DARIAH Python client library for repository.dariah.de access."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "LGPL-3.0-or-later"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Stefan Hynek", email = "hynek@sub.uni-goettingen.de" },
|
|
14
|
+
{ name = "Ubbo Veentjer", email = "veentjer@sub.uni-goettingen.de" }
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.10"
|
|
17
|
+
dependencies = [
|
|
18
|
+
"requests>=2.34.2",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"coverage>=7.14.3",
|
|
24
|
+
"pytest>=9.0.3",
|
|
25
|
+
"python-semantic-release>=10.5.3",
|
|
26
|
+
"requests-mock>=1.12.1",
|
|
27
|
+
"ruff>=0.15.14",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["uv_build>=0.9.13,<0.10.0"]
|
|
32
|
+
build-backend = "uv_build"
|
|
33
|
+
|
|
34
|
+
[tool.semantic_release]
|
|
35
|
+
build_command = """
|
|
36
|
+
uv lock --upgrade-package "$PACKAGE_NAME"
|
|
37
|
+
uv build
|
|
38
|
+
"""
|
|
39
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
40
|
+
version_variables = [
|
|
41
|
+
"src/dhclients/__init__.py:__version__",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
allow_zero_version = true
|
|
45
|
+
tag_format = "v{version}"
|
|
46
|
+
|
|
47
|
+
[tool.semantic_release.remote]
|
|
48
|
+
type = "gitlab"
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
exclude = [
|
|
52
|
+
"build",
|
|
53
|
+
"venv",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
line-length = 100
|
|
57
|
+
|
|
58
|
+
[tool.ruff.format]
|
|
59
|
+
quote-style = "single"
|
|
60
|
+
|
|
61
|
+
[tool.pydoclint]
|
|
62
|
+
style = 'google'
|
|
63
|
+
skip-checking-raises = true
|
|
64
|
+
allow-init-docstring = true
|
|
65
|
+
check-return-types = false # TODO: check auth.py zeep return types
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Georg-August-Universität Göttingen
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
DEFAULT_TIMEOUT: float = 120.00
|
|
11
|
+
|
|
12
|
+
ENDPOINTS: dict = {
|
|
13
|
+
'production': {
|
|
14
|
+
'storage': 'https://cdstar.de.dariah.eu/dariah',
|
|
15
|
+
'publish': 'https://repository.de.dariah.eu/1.0/dhpublish',
|
|
16
|
+
},
|
|
17
|
+
'development': {
|
|
18
|
+
'storage': 'https://cdstar.de.dariah.eu/test/dariah',
|
|
19
|
+
'publish': 'https://trep.de.dariah.eu/1.0/dhpublish',
|
|
20
|
+
},
|
|
21
|
+
'test': {
|
|
22
|
+
'storage': 'https://cdstar.de.dariah.eu/test/dariah',
|
|
23
|
+
'publish': 'https://dhrepworkshop.de.dariah.eu/1.0/dhpublish',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DhrepConfig:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
endpoint: Optional[str] = 'production',
|
|
32
|
+
storage_override: Optional[str] = '',
|
|
33
|
+
publish_override: Optional[str] = '',
|
|
34
|
+
) -> None:
|
|
35
|
+
"""DHrep Service Endpoint Configuration.
|
|
36
|
+
|
|
37
|
+
Overwrite the endpoint string to connect to other servers, e.g.:
|
|
38
|
+
* Test-Server: 'test'
|
|
39
|
+
* Development-Server: 'development'
|
|
40
|
+
|
|
41
|
+
It is also possible to override the storage url and the publish url separately
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
endpoint (Optional[str]): DHrep server. Defaults to 'production'.
|
|
45
|
+
storage_override (Optional[str]): overide storage url from ENDPOINTS dict. defaults to ''
|
|
46
|
+
publish_override (Optional[str]): overide publish url from ENDPOINTS dict. defaults to ''
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
if endpoint not in ENDPOINTS:
|
|
50
|
+
raise ConfigException('Endpoint config invalid')
|
|
51
|
+
|
|
52
|
+
if not isinstance(storage_override, str) or storage_override == '':
|
|
53
|
+
self._storage = ENDPOINTS[endpoint]['storage']
|
|
54
|
+
else:
|
|
55
|
+
if storage_override.endswith('/'):
|
|
56
|
+
storage_override = storage_override[:-1]
|
|
57
|
+
logger.info('trailing slash in storage location detected and removed')
|
|
58
|
+
self._storage = storage_override
|
|
59
|
+
|
|
60
|
+
if not isinstance(publish_override, str) or publish_override == '':
|
|
61
|
+
self._publish = ENDPOINTS[endpoint]['publish']
|
|
62
|
+
else:
|
|
63
|
+
if publish_override.endswith('/'):
|
|
64
|
+
publish_override = publish_override[:-1]
|
|
65
|
+
logger.info('trailing slash in publish location detected and removed')
|
|
66
|
+
self._publish = publish_override
|
|
67
|
+
|
|
68
|
+
logger.info(
|
|
69
|
+
'storage endpoint set to: %s / publish endpoint set to: %s',
|
|
70
|
+
self._storage,
|
|
71
|
+
self._publish,
|
|
72
|
+
)
|
|
73
|
+
self._http_timeout = DEFAULT_TIMEOUT
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def storage(self) -> str:
|
|
77
|
+
return self._storage
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def publish(self) -> str:
|
|
81
|
+
return self._publish
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ConfigException(Exception):
|
|
85
|
+
"""Thrown in case of problems with the storage"""
|
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Georg-August-Universität Göttingen
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""Connection to DARIAH-DE storage
|
|
6
|
+
|
|
7
|
+
Storage API docs: http://hdl.handle.net/11858/00-1734-0000-0009-FEA1-D
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import IO
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
from requests.models import Response
|
|
14
|
+
|
|
15
|
+
from dhclients.config import DhrepConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Storage:
|
|
19
|
+
"""
|
|
20
|
+
Methods to create and update data objects in DARIAH-DE storage
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: DhrepConfig = DhrepConfig()):
|
|
24
|
+
self._storage_url = config.storage
|
|
25
|
+
self._config = config
|
|
26
|
+
# reuse tcp connections: https://requests.readthedocs.io/en/latest/user/advanced/#session-objects
|
|
27
|
+
self._requests = requests.Session()
|
|
28
|
+
|
|
29
|
+
def create(self, token: str, content: IO[bytes] | str, content_type: str) -> str:
|
|
30
|
+
"""Create a new object in dariahstorage.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
token (str): authentication token
|
|
34
|
+
content (IO[bytes] | str): data to store
|
|
35
|
+
content_type (str): content type
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
Exception: if response from storage had no 201 status code
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
str: the storage id of created object
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
response = self._requests.post(
|
|
45
|
+
self._storage_url + '/',
|
|
46
|
+
headers={'Authorization': 'bearer ' + token, 'Content-type': content_type},
|
|
47
|
+
data=content,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if response.status_code != 201:
|
|
51
|
+
raise StorageException(
|
|
52
|
+
'Error creating new cdstar object: '
|
|
53
|
+
+ response.text
|
|
54
|
+
+ ' - '
|
|
55
|
+
+ str(response.status_code)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
storage_id = response.headers['location'].rsplit('/', 1)[-1]
|
|
59
|
+
return storage_id
|
|
60
|
+
|
|
61
|
+
def read(self, token: str, storage_id: str) -> Response:
|
|
62
|
+
"""Get an object from dariahstorage.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
token (str): authentication token
|
|
66
|
+
storage_id (str): the storage id
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
Exception: if response from storage had no 200 status code
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Response: HTTP response from service
|
|
73
|
+
"""
|
|
74
|
+
response = self._requests.get(
|
|
75
|
+
self._storage_url + '/' + storage_id,
|
|
76
|
+
headers={
|
|
77
|
+
'Authorization': 'bearer ' + token,
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if response.status_code != 200:
|
|
82
|
+
raise StorageException(
|
|
83
|
+
'Error reading cdstar object: ' + response.text + ' - ' + str(response.status_code)
|
|
84
|
+
)
|
|
85
|
+
return response
|
|
86
|
+
|
|
87
|
+
def update(self, token: str, storage_id: str, content: IO[bytes] | str, content_type: str):
|
|
88
|
+
"""Update an object in dariahstorage.
|
|
89
|
+
|
|
90
|
+
Arguments:
|
|
91
|
+
token (str): authentication token
|
|
92
|
+
storage_id (str): the storage id
|
|
93
|
+
content (IO[bytes] | str): the data to store
|
|
94
|
+
content_type (str): content type
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
Exception: if response from storage had no 201 status code
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
response = self._requests.put(
|
|
101
|
+
self._storage_url + '/' + storage_id,
|
|
102
|
+
headers={'Authorization': 'bearer ' + token, 'Content-type': content_type},
|
|
103
|
+
data=content,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if response.status_code != 201:
|
|
107
|
+
raise StorageException(
|
|
108
|
+
'Error updating cdstar object '
|
|
109
|
+
+ storage_id
|
|
110
|
+
+ ': '
|
|
111
|
+
+ response.text
|
|
112
|
+
+ ' - '
|
|
113
|
+
+ str(response.status_code)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def delete(self, token: str, storage_id: str):
|
|
117
|
+
"""Delete an object from dariahstorage.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
token (str): authentication token
|
|
121
|
+
storage_id (str): the storage id
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
Exception: if response from storage had no 204 status code
|
|
125
|
+
"""
|
|
126
|
+
response = self._requests.delete(
|
|
127
|
+
self._storage_url + '/' + storage_id,
|
|
128
|
+
headers={
|
|
129
|
+
'Authorization': 'bearer ' + token,
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if response.status_code != 204:
|
|
134
|
+
raise StorageException(
|
|
135
|
+
'Error deleting cdstar object: ' + response.text + ' - ' + str(response.status_code)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def get_storage_url(self):
|
|
139
|
+
"""Get the storage url
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
str: the actual storage url set
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
return self._storage_url
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class StorageException(Exception):
|
|
149
|
+
"""Thrown in case of problems with the storage"""
|