dump-things-pyclient 0.2.2__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- dump_things_pyclient/commands/dtc_plugins/delete_record.py +150 -0
- dump_things_pyclient/commands/dtc_plugins/export.py +194 -0
- dump_things_pyclient/commands/dtc_plugins/get_records.py +2 -6
- dump_things_pyclient/communicate.py +17 -0
- {dump_things_pyclient-0.2.2.dist-info → dump_things_pyclient-0.2.3.dist-info}/METADATA +1 -1
- {dump_things_pyclient-0.2.2.dist-info → dump_things_pyclient-0.2.3.dist-info}/RECORD +9 -7
- {dump_things_pyclient-0.2.2.dist-info → dump_things_pyclient-0.2.3.dist-info}/WHEEL +1 -1
- {dump_things_pyclient-0.2.2.dist-info → dump_things_pyclient-0.2.3.dist-info}/entry_points.txt +0 -0
- {dump_things_pyclient-0.2.2.dist-info → dump_things_pyclient-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from functools import partial
|
|
5
|
+
|
|
6
|
+
import rich_click as click
|
|
7
|
+
|
|
8
|
+
from ...communicate import (
|
|
9
|
+
HTTPError,
|
|
10
|
+
collection_delete_record,
|
|
11
|
+
curated_delete_record,
|
|
12
|
+
incoming_delete_record,
|
|
13
|
+
incoming_read_labels,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
subcommand_name = 'delete-records'
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger('delete-records')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command(short_help='Delete records from a dump-things collection')
|
|
23
|
+
@click.pass_obj
|
|
24
|
+
@click.argument(
|
|
25
|
+
'service_url',
|
|
26
|
+
metavar='SERVICE_URL',
|
|
27
|
+
)
|
|
28
|
+
@click.argument(
|
|
29
|
+
'collection',
|
|
30
|
+
metavar='COLLECTION',
|
|
31
|
+
)
|
|
32
|
+
@click.argument(
|
|
33
|
+
'pids',
|
|
34
|
+
metavar='PIDS',
|
|
35
|
+
nargs=-1,
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
'--curated', '-c',
|
|
39
|
+
default=False,
|
|
40
|
+
is_flag=True,
|
|
41
|
+
help='delete record from the curated area of the collection. (Note: requires a token with curator rights)',
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
'--incoming', '-i',
|
|
45
|
+
metavar='LABEL',
|
|
46
|
+
help='delete from the collection\'s inbox with label LABEL, if LABEL is "-", return labels of all collection inboxes and exit',
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
'--ignore-errors',
|
|
50
|
+
default=False,
|
|
51
|
+
is_flag=True,
|
|
52
|
+
help='ignore errors when deleting a pid and continue with remaining pids',
|
|
53
|
+
)
|
|
54
|
+
def cli(
|
|
55
|
+
obj,
|
|
56
|
+
service_url,
|
|
57
|
+
collection,
|
|
58
|
+
pids,
|
|
59
|
+
curated,
|
|
60
|
+
incoming,
|
|
61
|
+
ignore_errors,
|
|
62
|
+
):
|
|
63
|
+
"""Delete records from a collection on a dump-things-service
|
|
64
|
+
|
|
65
|
+
This command delete the records given by PIDS from the collection COLLECTION
|
|
66
|
+
of the dump-things service SERVICE_URL. If no PIDS are provided on the
|
|
67
|
+
command line, the pids that should be deleted are read from stdin (one pid
|
|
68
|
+
per line, lines are stripped).
|
|
69
|
+
|
|
70
|
+
By default, the records will be deleted from the inbox associated with the
|
|
71
|
+
token. If the option -c/--curated is given, the records are deleted from
|
|
72
|
+
the curated area of the collection (this requires a token with
|
|
73
|
+
curator rights).
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
return delete_records(
|
|
77
|
+
obj,
|
|
78
|
+
service_url,
|
|
79
|
+
collection,
|
|
80
|
+
pids,
|
|
81
|
+
curated,
|
|
82
|
+
incoming,
|
|
83
|
+
ignore_errors,
|
|
84
|
+
)
|
|
85
|
+
except HTTPError as e:
|
|
86
|
+
click.echo(f'ERROR: {e}: {e.response.text}', err=True)
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def delete_records(
|
|
91
|
+
obj,
|
|
92
|
+
service_url,
|
|
93
|
+
collection,
|
|
94
|
+
pids,
|
|
95
|
+
curated,
|
|
96
|
+
incoming,
|
|
97
|
+
ignore_errors,
|
|
98
|
+
):
|
|
99
|
+
token = obj
|
|
100
|
+
|
|
101
|
+
if token is None:
|
|
102
|
+
click.echo(f'WARNING: no token provided', err=True)
|
|
103
|
+
|
|
104
|
+
if incoming and curated:
|
|
105
|
+
click.echo(
|
|
106
|
+
'ERROR: -i/--incoming and -c/--curated are mutually exclusive',
|
|
107
|
+
err=True,
|
|
108
|
+
)
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
kwargs = dict(
|
|
112
|
+
service_url=service_url,
|
|
113
|
+
collection=collection,
|
|
114
|
+
token=token,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if incoming == '-':
|
|
118
|
+
result = incoming_read_labels(**kwargs)
|
|
119
|
+
click.echo('\n'.join(
|
|
120
|
+
map(
|
|
121
|
+
partial(json.dumps, ensure_ascii=False),
|
|
122
|
+
result
|
|
123
|
+
)
|
|
124
|
+
))
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
if incoming:
|
|
128
|
+
operation = partial(incoming_delete_record, label=incoming)
|
|
129
|
+
elif curated:
|
|
130
|
+
operation = curated_delete_record
|
|
131
|
+
else:
|
|
132
|
+
operation = collection_delete_record
|
|
133
|
+
|
|
134
|
+
if not pids:
|
|
135
|
+
pids = sys.stdin
|
|
136
|
+
|
|
137
|
+
for pid in pids:
|
|
138
|
+
try:
|
|
139
|
+
operation(
|
|
140
|
+
service_url=service_url,
|
|
141
|
+
collection=collection,
|
|
142
|
+
pid=pid.strip(),
|
|
143
|
+
token=token,
|
|
144
|
+
)
|
|
145
|
+
except HTTPError as e:
|
|
146
|
+
if ignore_errors:
|
|
147
|
+
click.echo(f'ERROR: while deleting pid {pid}: {e}', err=True)
|
|
148
|
+
continue
|
|
149
|
+
raise
|
|
150
|
+
return 0
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from itertools import count
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Iterable,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
import rich_click as click
|
|
11
|
+
|
|
12
|
+
from ...communicate import (
|
|
13
|
+
HTTPError,
|
|
14
|
+
curated_read_records,
|
|
15
|
+
incoming_read_labels,
|
|
16
|
+
incoming_read_records,
|
|
17
|
+
server,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
subcommand_name = 'export'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command(short_help='Export a collection to the file system')
|
|
25
|
+
@click.pass_obj
|
|
26
|
+
@click.argument(
|
|
27
|
+
'service_url',
|
|
28
|
+
metavar='SERVICE_URL',
|
|
29
|
+
)
|
|
30
|
+
@click.argument(
|
|
31
|
+
'collection',
|
|
32
|
+
metavar='COLLECTION',
|
|
33
|
+
)
|
|
34
|
+
@click.argument(
|
|
35
|
+
'destination',
|
|
36
|
+
type=click.Path(
|
|
37
|
+
exists=False,
|
|
38
|
+
file_okay=False,
|
|
39
|
+
dir_okay=True,
|
|
40
|
+
writable=True,
|
|
41
|
+
allow_dash=False,
|
|
42
|
+
path_type=Path,
|
|
43
|
+
),
|
|
44
|
+
metavar='DESTINATION_DIR',
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
'--ignore-errors',
|
|
48
|
+
default=False,
|
|
49
|
+
is_flag=True,
|
|
50
|
+
help='ignore records with missing `schema_type` instead of raising an error',
|
|
51
|
+
)
|
|
52
|
+
def cli(
|
|
53
|
+
obj: Any,
|
|
54
|
+
service_url: str,
|
|
55
|
+
collection: str,
|
|
56
|
+
destination: Path,
|
|
57
|
+
ignore_errors,
|
|
58
|
+
):
|
|
59
|
+
"""Export a collection to disk
|
|
60
|
+
|
|
61
|
+
This command exports all records that are stored in curated area and in the
|
|
62
|
+
incoming areas of collection COLLECTION of the dump-things service
|
|
63
|
+
SERVICE_URL.
|
|
64
|
+
|
|
65
|
+
Exported records are written to the directory DESTINATION_DIR.
|
|
66
|
+
DESTINATION_DIR must not exist, `export` will create it.
|
|
67
|
+
|
|
68
|
+
A token with curator rights has to be provided.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
return export(
|
|
72
|
+
obj,
|
|
73
|
+
service_url,
|
|
74
|
+
collection,
|
|
75
|
+
destination,
|
|
76
|
+
ignore_errors,
|
|
77
|
+
)
|
|
78
|
+
except HTTPError as e:
|
|
79
|
+
click.echo(f'ERROR: {e}: {e.response.text}', err=True)
|
|
80
|
+
except ValueError as e:
|
|
81
|
+
click.echo(f'ERROR: {e}', err=True)
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def export(
|
|
86
|
+
obj: Any,
|
|
87
|
+
service_url: str,
|
|
88
|
+
collection: str,
|
|
89
|
+
destination: Path,
|
|
90
|
+
ignore_errors: bool,
|
|
91
|
+
):
|
|
92
|
+
token = obj
|
|
93
|
+
|
|
94
|
+
if token is None:
|
|
95
|
+
click.echo(f'ERROR: no token provided', err=True)
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
server_info = server(service_url)
|
|
99
|
+
collection_info = ([c for c in server_info['collections'] if c['name'] == collection] or None)[0]
|
|
100
|
+
|
|
101
|
+
if not collection_info:
|
|
102
|
+
click.echo(f'ERROR: no collection {collection} on service', err=True)
|
|
103
|
+
return 1
|
|
104
|
+
|
|
105
|
+
description = {
|
|
106
|
+
'name': collection,
|
|
107
|
+
'schema': collection_info['schema'],
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
destination.mkdir(parents=True, exist_ok=False)
|
|
111
|
+
(destination / 'description.json').write_text(
|
|
112
|
+
json.dumps(description, ensure_ascii=False),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Store the curated records
|
|
116
|
+
curated_destination = destination / 'curated'
|
|
117
|
+
curated_destination.mkdir()
|
|
118
|
+
|
|
119
|
+
_store_records(
|
|
120
|
+
map(
|
|
121
|
+
lambda x: x[0],
|
|
122
|
+
curated_read_records(
|
|
123
|
+
service_url=service_url,
|
|
124
|
+
collection=collection,
|
|
125
|
+
token=token,
|
|
126
|
+
)
|
|
127
|
+
),
|
|
128
|
+
curated_destination,
|
|
129
|
+
ignore_errors,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Store the incoming records
|
|
133
|
+
for label in incoming_read_labels(
|
|
134
|
+
service_url=service_url,
|
|
135
|
+
collection=collection,
|
|
136
|
+
token=token,
|
|
137
|
+
):
|
|
138
|
+
incoming_destination = destination / 'incoming' / label
|
|
139
|
+
incoming_destination.mkdir(parents=True, exist_ok=False)
|
|
140
|
+
_store_records(
|
|
141
|
+
map(
|
|
142
|
+
lambda x: x[0],
|
|
143
|
+
incoming_read_records(
|
|
144
|
+
service_url=service_url,
|
|
145
|
+
collection=collection,
|
|
146
|
+
label=label,
|
|
147
|
+
token=token,
|
|
148
|
+
)
|
|
149
|
+
),
|
|
150
|
+
incoming_destination,
|
|
151
|
+
ignore_errors,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _store_records(
|
|
158
|
+
source: Iterable,
|
|
159
|
+
destination: Path,
|
|
160
|
+
ignore_errors: bool = False,
|
|
161
|
+
):
|
|
162
|
+
created_dirs = set()
|
|
163
|
+
class_counters = defaultdict(count)
|
|
164
|
+
|
|
165
|
+
for record in source:
|
|
166
|
+
class_name = _de_prefix(record.get('schema_type', None))
|
|
167
|
+
if class_name is None:
|
|
168
|
+
if ignore_errors:
|
|
169
|
+
click.echo(
|
|
170
|
+
f'WARNING: no `schema_type` in record `{record["pid"]}`',
|
|
171
|
+
err=True
|
|
172
|
+
)
|
|
173
|
+
continue
|
|
174
|
+
msg = f'no `schema_type` in record `{record["pid"]}`'
|
|
175
|
+
raise ValueError(msg)
|
|
176
|
+
|
|
177
|
+
next_name_for_class = f'{next(class_counters[class_name]):09d}.json'
|
|
178
|
+
file_dir, file_name = (
|
|
179
|
+
destination / class_name / next_name_for_class[:3],
|
|
180
|
+
next_name_for_class[3:]
|
|
181
|
+
)
|
|
182
|
+
if file_dir not in created_dirs:
|
|
183
|
+
file_dir.mkdir(parents=True, exist_ok=False)
|
|
184
|
+
created_dirs.add(file_dir)
|
|
185
|
+
|
|
186
|
+
(file_dir / file_name).write_text(
|
|
187
|
+
json.dumps(record, indent=2, ensure_ascii=False),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _de_prefix(
|
|
192
|
+
name: str,
|
|
193
|
+
):
|
|
194
|
+
return name.split(':', 1)[-1]
|
|
@@ -33,8 +33,6 @@ subcommand_name = 'get-records'
|
|
|
33
33
|
)
|
|
34
34
|
@click.option(
|
|
35
35
|
'--class', '-C', 'cls',
|
|
36
|
-
default=False,
|
|
37
|
-
is_flag=True,
|
|
38
36
|
help='only read records of this class, ignored if "--pid" is provided',
|
|
39
37
|
)
|
|
40
38
|
@click.option(
|
|
@@ -50,7 +48,7 @@ subcommand_name = 'get-records'
|
|
|
50
48
|
@click.option(
|
|
51
49
|
'--incoming', '-i',
|
|
52
50
|
metavar='LABEL',
|
|
53
|
-
help='read from the collection inbox with label LABEL, if LABEL is "-", return labels of all collection inboxes',
|
|
51
|
+
help='read from the collection\'s inbox with label LABEL, if LABEL is "-", return labels of all collection inboxes and exit',
|
|
54
52
|
)
|
|
55
53
|
@click.option(
|
|
56
54
|
'--curated', '-c',
|
|
@@ -60,8 +58,6 @@ subcommand_name = 'get-records'
|
|
|
60
58
|
)
|
|
61
59
|
@click.option(
|
|
62
60
|
'--matching', '-m',
|
|
63
|
-
default=False,
|
|
64
|
-
is_flag=True,
|
|
65
61
|
help='return only records that have a matching value (use % as wildcard). Ignored if "--pid" is provided. (Note: not all endpoints and backends support matching)',
|
|
66
62
|
)
|
|
67
63
|
@click.option(
|
|
@@ -188,7 +184,7 @@ def get_records(
|
|
|
188
184
|
|
|
189
185
|
if incoming == '-':
|
|
190
186
|
result = incoming_read_labels(**kwargs)
|
|
191
|
-
|
|
187
|
+
click.echo('\n'.join(
|
|
192
188
|
map(
|
|
193
189
|
partial(json.dumps, ensure_ascii=False),
|
|
194
190
|
result)))
|
|
@@ -677,6 +677,23 @@ def incoming_delete_record(
|
|
|
677
677
|
params={'pid': pid})
|
|
678
678
|
|
|
679
679
|
|
|
680
|
+
def server(
|
|
681
|
+
service_url: str,
|
|
682
|
+
) -> JSON:
|
|
683
|
+
"""Get server-information from the service
|
|
684
|
+
|
|
685
|
+
:param service_url: the base URL of the service, i.e., the URL up to
|
|
686
|
+
`/<collection>/...` or `/server`
|
|
687
|
+
|
|
688
|
+
:return: information returned by the `<service_url>/server` endpoint
|
|
689
|
+
"""
|
|
690
|
+
url = (
|
|
691
|
+
(f'{service_url[:-1]}' if service_url.endswith('/') else service_url)
|
|
692
|
+
+ '/server'
|
|
693
|
+
)
|
|
694
|
+
return _do_request(requests.get, url=url, token=None, params=None)
|
|
695
|
+
|
|
696
|
+
|
|
680
697
|
def _get_from_url(url: str,
|
|
681
698
|
token: str | None,
|
|
682
699
|
params: dict[str, str] | None = None,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
dump_things_pyclient/__init__.py,sha256=cn-U3TRIalN6aYHp1cMBRkQm1x98XBwquLFbgFEIf_Q,113
|
|
2
|
-
dump_things_pyclient/communicate.py,sha256=
|
|
2
|
+
dump_things_pyclient/communicate.py,sha256=R1lifyuP5Cm2oQdyaWxTsQAbiMBU9NxYA5-9yU4M3qA,31008
|
|
3
3
|
dump_things_pyclient/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
dump_things_pyclient/commands/dtc.py,sha256=dxW5RuogqwhzfVujZ_EEsQMk8BcVMbZyMdg5c8EvYIA,1726
|
|
5
5
|
dump_things_pyclient/commands/json2ttl.py,sha256=8BkvdjLWZ_H0L6fTmuR2M2MglKiMUiuNUcuWr_w6_dQ,2133
|
|
@@ -7,12 +7,14 @@ dump_things_pyclient/commands/redirect.py,sha256=kl8pGj8khjxk4lhk8AJLfgtCIm5Ptje
|
|
|
7
7
|
dump_things_pyclient/commands/dtc_plugins/__init__.py,sha256=0YLByLiofhHkhJcDCkokldcCw3Jj0rsKJinRX4tt3Hc,514
|
|
8
8
|
dump_things_pyclient/commands/dtc_plugins/auto_curate.py,sha256=3_SHXPQCXmY6GqTMTNVkKh5vvshfiZpMGFY0gvJxRbo,7411
|
|
9
9
|
dump_things_pyclient/commands/dtc_plugins/clean_incoming.py,sha256=slk3xn1-DgMl88WZqgyemyscwof97TMXt3rley4mU1w,2086
|
|
10
|
-
dump_things_pyclient/commands/dtc_plugins/
|
|
10
|
+
dump_things_pyclient/commands/dtc_plugins/delete_record.py,sha256=TVXyGetob8d75zuSBGuRKjtQPAcRqqjw-CcwYYHSC28,3626
|
|
11
|
+
dump_things_pyclient/commands/dtc_plugins/export.py,sha256=FF1DmSmIA3GIhosUrTrXy_qyypMSJUalxdLJ844XhIE,4850
|
|
12
|
+
dump_things_pyclient/commands/dtc_plugins/get_records.py,sha256=00W-agALqO_qFhjq14MI9N6TnJCgscMioy-Ll1ClrB4,7451
|
|
11
13
|
dump_things_pyclient/commands/dtc_plugins/list_incoming.py,sha256=tmM0Qs4MVwMMLyERsWCxWGTM90rSNOShLpHH32wObd8,1959
|
|
12
14
|
dump_things_pyclient/commands/dtc_plugins/post_records.py,sha256=s3j9THe-RszKxyIISkQZRCTKplWWLlomHbS5dyRlep0,2908
|
|
13
15
|
dump_things_pyclient/commands/dtc_plugins/read_pages.py,sha256=hpw7vtG7joIMrNqEqZFCwzbQFd3ATzv7iyySsX8nKWk,3385
|
|
14
|
-
dump_things_pyclient-0.2.
|
|
15
|
-
dump_things_pyclient-0.2.
|
|
16
|
-
dump_things_pyclient-0.2.
|
|
17
|
-
dump_things_pyclient-0.2.
|
|
18
|
-
dump_things_pyclient-0.2.
|
|
16
|
+
dump_things_pyclient-0.2.3.dist-info/METADATA,sha256=hgtqkFRDuld7OTe2Alt-U-2YDqZurOqYT7PHphJ-uGQ,999
|
|
17
|
+
dump_things_pyclient-0.2.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
18
|
+
dump_things_pyclient-0.2.3.dist-info/entry_points.txt,sha256=U1QhQtk767G_OXdZwPdTXYbIPfcDU13Z2u1d6exX8uE,470
|
|
19
|
+
dump_things_pyclient-0.2.3.dist-info/top_level.txt,sha256=Asvruw-SyLoYhWis1CFOx89RGxpjXoTZVGoq4JSGt88,21
|
|
20
|
+
dump_things_pyclient-0.2.3.dist-info/RECORD,,
|
{dump_things_pyclient-0.2.2.dist-info → dump_things_pyclient-0.2.3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|