dump-things-pyclient 0.2.3__py3-none-any.whl → 0.2.6__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.
@@ -2,12 +2,24 @@ import json
2
2
  import logging
3
3
  import re
4
4
  import sys
5
+ from itertools import (
6
+ count,
7
+ chain,
8
+ )
9
+ from typing import (
10
+ cast,
11
+ Iterable,
12
+ )
5
13
 
6
14
  import rich_click as click
15
+ from rich import print as rprint
16
+ from rich.console import Console
17
+ from rich.progress import track
7
18
 
8
19
  from ...communicate import (
9
20
  HTTPError,
10
21
  curated_write_record,
22
+ get_session,
11
23
  incoming_delete_record,
12
24
  incoming_read_labels,
13
25
  incoming_read_records,
@@ -16,6 +28,7 @@ from ...communicate import (
16
28
 
17
29
  logger = logging.getLogger('auto-curate')
18
30
 
31
+ console = Console(file=sys.stderr)
19
32
  stl_info = False
20
33
 
21
34
 
@@ -32,17 +45,17 @@ stl_info = False
32
45
  @click.option(
33
46
  '--destination-service-url',
34
47
  metavar='DEST_SERVICE_URL',
35
- help='select a different dump-thing-service, i.e. not SERVICE_URL, as destination for auto-curated records',
48
+ help='select a different dump-thing-service, i.e. not SERVICE_URL, as destination for auto-curated records (the default is SERVICE_URL)',
36
49
  )
37
50
  @click.option(
38
51
  '--destination-collection',
39
52
  metavar='DEST_COLLECTION',
40
- help='select a different collection, i.e. not the COLLECTION of SERVICE_URL, as destination for auto-curated records',
53
+ help='select a different collection, i.e. not COLLECTION, as destination for auto-curated records',
41
54
  )
42
55
  @click.option(
43
56
  '--destination-token',
44
57
  metavar='DEST_TOKEN',
45
- help='if provided, this token will be used the authenticate again DEST_SERVICE_URL, otherwise the token for SERVICE_URL will be used',
58
+ help='if provided, this token will be used the authenticate against DEST_SERVICE_URL, which defaults to SERVICE_URL (the default is the token provided via --token)',
46
59
  )
47
60
  @click.option(
48
61
  '--pid', '-p',
@@ -114,7 +127,9 @@ def cli(
114
127
  dry_run,
115
128
  )
116
129
  except HTTPError as e:
117
- print(f'ERROR: {e}: {e.response.text}', file=sys.stderr, flush=True)
130
+ console.print(
131
+ f'[red]Error[/red]: {e}: {e.response.text}',
132
+ )
118
133
  return 1
119
134
 
120
135
 
@@ -135,14 +150,17 @@ def auto_curate(
135
150
  curator_token = obj
136
151
 
137
152
  if curator_token is None:
138
- print(
139
- f'ERROR: no token was provided (use --token or DTC_TOKEN environment variable)',
140
- file=sys.stderr,
141
- flush=True,
142
- )
153
+ console.print(f'[red]Error[/red]: no token was provided (use --token or DTC_TOKEN environment variable)')
143
154
  return 1
144
155
 
145
- click.echo(f'auto curate: {obj}')
156
+ if destination_collection is None:
157
+ destination_collection = collection
158
+
159
+ if destination_service_url is None:
160
+ destination_service_url = service_url
161
+
162
+ if destination_token is None:
163
+ destination_token = curator_token
146
164
 
147
165
  output = None
148
166
 
@@ -157,12 +175,14 @@ def auto_curate(
157
175
  if list_labels:
158
176
  output = []
159
177
 
160
- for label in incoming_read_labels(
161
- service_url=service_url,
162
- collection=collection,
163
- token=obj,
164
- ):
165
-
178
+ session = get_session()
179
+ all_labels = incoming_read_labels(
180
+ service_url=service_url,
181
+ collection=collection,
182
+ token=obj,
183
+ session=session,
184
+ )
185
+ for label in all_labels:
166
186
  if include and label not in include:
167
187
  logger.debug('ignoring non-included incoming label: %s', label)
168
188
  continue
@@ -178,13 +198,35 @@ def auto_curate(
178
198
  if list_records:
179
199
  output[label] = []
180
200
 
181
- for record, _, _, _, _ in incoming_read_records(
182
- service_url=service_url,
183
- collection=collection,
184
- label=label,
185
- token=obj,
186
- ):
201
+ # Get the total number of entries for the
202
+ record_source = incoming_read_records(
203
+ service_url=service_url,
204
+ collection=collection,
205
+ label=label,
206
+ token=obj,
207
+ session=session,
208
+ )
187
209
 
210
+ # Get the first entry to find the total number of records
211
+ try:
212
+ first_record, _, _, _, total = next(record_source)
213
+ except StopIteration:
214
+ console.print(f'no records in incoming area [green]{label}[/green], skipping it')
215
+ continue
216
+
217
+ # Get the first entry an all other entries
218
+ for index, (record, _, _, _, total) in track(
219
+ zip(
220
+ count(),
221
+ chain(
222
+ [(first_record, None, None, None, None)],
223
+ cast(Iterable, record_source),
224
+ ),
225
+ ),
226
+ description=f'processing [green]{label}[/green]',
227
+ total=total,
228
+ console=console,
229
+ ):
188
230
  if list_records:
189
231
  output[label].append(record)
190
232
  continue
@@ -204,43 +246,61 @@ def auto_curate(
204
246
  class_name = re.search('([_A-Za-z0-9]*$)', record['schema_type']).group(0)
205
247
  except (IndexError, KeyError):
206
248
  global stl_info
249
+ console.print(f'[yellow]Warning[/yellow]: ignoring record with pid [yellow]{record["pid"]}[/yellow] because `schema_type` attribute is missing.')
207
250
  if not stl_info:
208
- logger.warning(
209
- f"""Could not find `schema_type` attribute in record with
210
- pid {record['pid']}. Please ensure that `schema_type` is stored in
211
- the records or that the associated incoming area store has a backend
212
- with a "Schema Type Layer", i.e., "record_dir+stl" or
213
- "sqlite+stl".""",
251
+ console.print(
252
+ ' [yellow]Please ensure that `schema_type` is stored in the records. Note: '
253
+ 'if the incoming area store has a backend with a "Schema Type Layer", i.e., '
254
+ '"record_dir+stl" or "sqlite+stl", `schema_type` will not be stored on persistent '
255
+ 'storage and will not be returned when retrieving records from the incoming area. '
256
+ 'dump-things-service <= 5.4.0 circumvented the "Schema Type Layer", therefore they '
257
+ 'will return records without `schema_type` attributes on curator access to '
258
+ 'incoming areas or curated areas. Therefore it might be a good idea to NOT use a '
259
+ '"Schema Type Layer" in collections that shall be auto-curated, when using '
260
+ 'dump-things-service <= 5.4.0.[/yellow]',
214
261
  )
215
262
  stl_info = True
216
- else:
217
- logger.warning(f'ignoring record with pid {record["pid"]}, `schema_type` attribute is missing.')
218
263
  continue
219
264
 
220
265
  if dry_run:
221
- print(f'WRITE record "{record["pid"]}" of class "{class_name}" to "{destination_collection}@{destination_service_url}"')
222
- print(f'DELETE record "{record["pid"]}" from inbox "{label}" of "{collection}@{service_url}"')
266
+ console.print(f'WRITE record [green]"{record["pid"]}"[/green] of class "{class_name}" to collection "{destination_collection}" on "{destination_service_url}"')
267
+ console.print(f'DELETE record [green]"{record["pid"]}"[/green] from inbox "{label}" of collection "{collection}" on "{service_url}"')
223
268
  continue
224
269
 
225
270
  # Store record in destination collection
226
- curated_write_record(
227
- service_url=destination_service_url,
228
- collection=destination_collection,
229
- class_name=class_name,
230
- record=record,
231
- token=destination_token)
271
+ try:
272
+ curated_write_record(
273
+ service_url=destination_service_url,
274
+ collection=destination_collection,
275
+ class_name=class_name,
276
+ record=record,
277
+ token=destination_token,
278
+ session=session,
279
+ )
280
+ except HTTPError as e:
281
+ console.print(
282
+ f'[red]Error[/red]: writing record with pid {record["pid"]} failed: {e}: {e.response.text}',
283
+ )
284
+ return 1
232
285
 
233
286
  # Delete record from incoming area
234
- incoming_delete_record(
235
- service_url=service_url,
236
- collection=collection,
237
- label=label,
238
- pid=record['pid'],
239
- token=obj,
240
- )
287
+ try:
288
+ incoming_delete_record(
289
+ service_url=service_url,
290
+ collection=collection,
291
+ label=label,
292
+ pid=record['pid'],
293
+ token=curator_token,
294
+ session=session,
295
+ )
296
+ except HTTPError as e:
297
+ console.print(
298
+ f'[red]ERROR[/red]: deleting record with pid {record["pid"]} failed: {e}: {e.response.text}',
299
+ )
300
+ return 1
241
301
 
242
302
  if output is not None:
243
- print(json.dumps(output, ensure_ascii=False))
303
+ rprint(json.dumps(output, ensure_ascii=False))
244
304
 
245
305
  return 0
246
306
 
@@ -4,6 +4,7 @@ import rich_click as click
4
4
 
5
5
  from ...communicate import (
6
6
  HTTPError,
7
+ get_session,
7
8
  incoming_delete_record,
8
9
  incoming_read_records,
9
10
  )
@@ -71,11 +72,13 @@ def clean_incoming(
71
72
  click.echo('ERROR: token not provided', err=True)
72
73
  return 1
73
74
 
75
+ session = get_session()
74
76
  for record, _, _, _, _ in incoming_read_records(
75
77
  service_url=service_url,
76
78
  collection=collection,
77
79
  label=inbox_label,
78
80
  token=token,
81
+ session=session,
79
82
  ):
80
83
  if list_only:
81
84
  click.echo(json.dumps(record, ensure_ascii=False))
@@ -88,6 +91,6 @@ def clean_incoming(
88
91
  label=inbox_label,
89
92
  pid=record['pid'],
90
93
  token=token,
91
-
94
+ session=session,
92
95
  )
93
96
  return 0
@@ -4,9 +4,12 @@ import sys
4
4
  from functools import partial
5
5
 
6
6
  import rich_click as click
7
+ from rich.progress import track
8
+ from rich.console import Console
7
9
 
8
10
  from ...communicate import (
9
11
  HTTPError,
12
+ get_session,
10
13
  collection_delete_record,
11
14
  curated_delete_record,
12
15
  incoming_delete_record,
@@ -18,6 +21,8 @@ subcommand_name = 'delete-records'
18
21
 
19
22
  logger = logging.getLogger('delete-records')
20
23
 
24
+ console = Console(file=sys.stderr)
25
+
21
26
 
22
27
  @click.command(short_help='Delete records from a dump-things collection')
23
28
  @click.pass_obj
@@ -83,7 +88,7 @@ def cli(
83
88
  ignore_errors,
84
89
  )
85
90
  except HTTPError as e:
86
- click.echo(f'ERROR: {e}: {e.response.text}', err=True)
91
+ console.print(f'[red]Error[/red]: {e}: {e.response.text}')
87
92
  return 1
88
93
 
89
94
 
@@ -102,16 +107,15 @@ def delete_records(
102
107
  click.echo(f'WARNING: no token provided', err=True)
103
108
 
104
109
  if incoming and curated:
105
- click.echo(
106
- 'ERROR: -i/--incoming and -c/--curated are mutually exclusive',
107
- err=True,
108
- )
110
+ console.print('[red]Error[/red]: -i/--incoming and -c/--curated are mutually exclusive')
109
111
  return 1
110
112
 
113
+ session = get_session()
111
114
  kwargs = dict(
112
115
  service_url=service_url,
113
116
  collection=collection,
114
117
  token=token,
118
+ session=session,
115
119
  )
116
120
 
117
121
  if incoming == '-':
@@ -134,17 +138,18 @@ def delete_records(
134
138
  if not pids:
135
139
  pids = sys.stdin
136
140
 
137
- for pid in pids:
141
+ for pid in track(pids, console=console):
138
142
  try:
139
143
  operation(
140
144
  service_url=service_url,
141
145
  collection=collection,
142
146
  pid=pid.strip(),
143
147
  token=token,
148
+ session=session,
144
149
  )
145
150
  except HTTPError as e:
151
+ console.print(f'[red]Error[/red]: while deleting pid {pid}: {e}, {e.response.text}')
146
152
  if ignore_errors:
147
- click.echo(f'ERROR: while deleting pid {pid}: {e}', err=True)
148
153
  continue
149
- raise
154
+ return 1
150
155
  return 0
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import sys
2
3
  from collections import defaultdict
3
4
  from itertools import count
4
5
  from pathlib import Path
@@ -8,10 +9,13 @@ from typing import (
8
9
  )
9
10
 
10
11
  import rich_click as click
12
+ from rich.console import Console
13
+ from rich.progress import track
11
14
 
12
15
  from ...communicate import (
13
16
  HTTPError,
14
17
  curated_read_records,
18
+ get_session,
15
19
  incoming_read_labels,
16
20
  incoming_read_records,
17
21
  server,
@@ -20,6 +24,8 @@ from ...communicate import (
20
24
 
21
25
  subcommand_name = 'export'
22
26
 
27
+ console = Console(file=sys.stderr)
28
+
23
29
 
24
30
  @click.command(short_help='Export a collection to the file system')
25
31
  @click.pass_obj
@@ -76,9 +82,9 @@ def cli(
76
82
  ignore_errors,
77
83
  )
78
84
  except HTTPError as e:
79
- click.echo(f'ERROR: {e}: {e.response.text}', err=True)
85
+ console.print(f'[red]Error[/red]: {e}: {e.response.text}')
80
86
  except ValueError as e:
81
- click.echo(f'ERROR: {e}', err=True)
87
+ console.print(f'[red]Error[/red]: {e}')
82
88
  return 1
83
89
 
84
90
 
@@ -92,14 +98,15 @@ def export(
92
98
  token = obj
93
99
 
94
100
  if token is None:
95
- click.echo(f'ERROR: no token provided', err=True)
101
+ console.print(f'[red]Error[/red]: no token provided')
96
102
  return 1
97
103
 
98
- server_info = server(service_url)
104
+ session = get_session()
105
+ server_info = server(service_url, session=session)
99
106
  collection_info = ([c for c in server_info['collections'] if c['name'] == collection] or None)[0]
100
107
 
101
108
  if not collection_info:
102
- click.echo(f'ERROR: no collection {collection} on service', err=True)
109
+ console.print(f'[red]Error[/red]: no collection {collection} on service')
103
110
  return 1
104
111
 
105
112
  description = {
@@ -116,6 +123,7 @@ def export(
116
123
  curated_destination = destination / 'curated'
117
124
  curated_destination.mkdir()
118
125
 
126
+ console.print('Exporting records from curated area')
119
127
  _store_records(
120
128
  map(
121
129
  lambda x: x[0],
@@ -123,6 +131,7 @@ def export(
123
131
  service_url=service_url,
124
132
  collection=collection,
125
133
  token=token,
134
+ session=session,
126
135
  )
127
136
  ),
128
137
  curated_destination,
@@ -134,7 +143,9 @@ def export(
134
143
  service_url=service_url,
135
144
  collection=collection,
136
145
  token=token,
146
+ session=session,
137
147
  ):
148
+ console.print(f'Exporting records from incoming area: {label}')
138
149
  incoming_destination = destination / 'incoming' / label
139
150
  incoming_destination.mkdir(parents=True, exist_ok=False)
140
151
  _store_records(
@@ -145,6 +156,7 @@ def export(
145
156
  collection=collection,
146
157
  label=label,
147
158
  token=token,
159
+ session=session,
148
160
  )
149
161
  ),
150
162
  incoming_destination,
@@ -162,16 +174,13 @@ def _store_records(
162
174
  created_dirs = set()
163
175
  class_counters = defaultdict(count)
164
176
 
165
- for record in source:
177
+ for record in track(source, console=console):
166
178
  class_name = _de_prefix(record.get('schema_type', None))
167
179
  if class_name is None:
168
180
  if ignore_errors:
169
- click.echo(
170
- f'WARNING: no `schema_type` in record `{record["pid"]}`',
171
- err=True
172
- )
181
+ console.print(f'[red]Error[/red]: no `schema type` in record {record["pid"]}')
173
182
  continue
174
- msg = f'no `schema_type` in record `{record["pid"]}`'
183
+ msg = f'no `schema_type` in record {record["pid"]}'
175
184
  raise ValueError(msg)
176
185
 
177
186
  next_name_for_class = f'{next(class_counters[class_name]):09d}.json'
@@ -1,7 +1,9 @@
1
1
  import json
2
+ import sys
2
3
  from functools import partial
3
4
 
4
5
  import rich_click as click
6
+ from rich.console import Console
5
7
 
6
8
  from ...communicate import (
7
9
  HTTPError,
@@ -11,6 +13,7 @@ from ...communicate import (
11
13
  curated_read_records,
12
14
  curated_read_records_of_class,
13
15
  curated_read_record_with_pid,
16
+ get_session,
14
17
  incoming_read_labels,
15
18
  incoming_read_records,
16
19
  incoming_read_records_of_class,
@@ -20,6 +23,8 @@ from ...communicate import (
20
23
 
21
24
  subcommand_name = 'get-records'
22
25
 
26
+ console = Console(file=sys.stderr)
27
+
23
28
 
24
29
  @click.command(short_help='Get records from a dump-things collection')
25
30
  @click.pass_obj
@@ -48,7 +53,7 @@ subcommand_name = 'get-records'
48
53
  @click.option(
49
54
  '--incoming', '-i',
50
55
  metavar='LABEL',
51
- help='read from the collection\'s inbox with label LABEL, if LABEL is "-", return labels of all collection inboxes and exit',
56
+ help='read from the collection\'s inbox with label LABEL, if LABEL is "-", print labels of all collection inboxes and exit',
52
57
  )
53
58
  @click.option(
54
59
  '--curated', '-c',
@@ -144,7 +149,7 @@ def cli(
144
149
  pagination,
145
150
  )
146
151
  except HTTPError as e:
147
- click.echo(f'ERROR: {e}: {e.response.text}', err=True)
152
+ console.print(f'[red]Error[/red]: {e}: {e.response.text}')
148
153
  return 1
149
154
 
150
155
 
@@ -167,19 +172,18 @@ def get_records(
167
172
  token = obj
168
173
 
169
174
  if token is None:
170
- click.echo(f'WARNING: no token provided', err=True)
175
+ console.print(f'[yellow]Warning[/yellow]: no token provided')
171
176
 
172
177
  if incoming and curated:
173
- click.echo(
174
- 'ERROR: -i/--incoming and -c/--curated are mutually exclusive',
175
- err=True,
176
- )
178
+ console.print('[red]Error[/red]: -i/--incoming and -c/--curated are mutually exclusive')
177
179
  return 1
178
180
 
181
+ session = get_session()
179
182
  kwargs = dict(
180
183
  service_url=service_url,
181
184
  collection=collection,
182
185
  token=token,
186
+ session=session,
183
187
  )
184
188
 
185
189
  if incoming == '-':
@@ -187,7 +191,9 @@ def get_records(
187
191
  click.echo('\n'.join(
188
192
  map(
189
193
  partial(json.dumps, ensure_ascii=False),
190
- result)))
194
+ result
195
+ )
196
+ ))
191
197
  return 0
192
198
 
193
199
  elif pid:
@@ -251,8 +257,8 @@ def get_records(
251
257
 
252
258
  if pagination:
253
259
  for record in result:
254
- print(json.dumps(record, ensure_ascii=False))
260
+ click.echo(json.dumps(record, ensure_ascii=False))
255
261
  else:
256
262
  for record in result:
257
- print(json.dumps(record[0], ensure_ascii=False))
263
+ click.echo(json.dumps(record[0], ensure_ascii=False))
258
264
  return 0
@@ -0,0 +1,80 @@
1
+ import logging
2
+ import sys
3
+
4
+ import rich_click as click
5
+ from rich.console import Console
6
+
7
+ from ...communicate import (
8
+ HTTPError,
9
+ get_session,
10
+ maintenance as communicate_maintenance,
11
+ )
12
+
13
+
14
+ logger = logging.getLogger('maintenance')
15
+
16
+ console = Console(file=sys.stderr)
17
+
18
+ subcommand_name = 'maintenance'
19
+
20
+
21
+ @click.command(short_help='Activate or deactivate maintenance mode on a collection')
22
+ @click.pass_obj
23
+ @click.argument(
24
+ 'service_url',
25
+ metavar='SERVICE_URL',
26
+ )
27
+ @click.argument(
28
+ 'collection',
29
+ metavar='COLLECTION',
30
+ )
31
+ @click.argument(
32
+ 'active',
33
+ metavar='ACTIVE',
34
+ type=click.Choice(['On', 'Off'], case_sensitive=False),
35
+ )
36
+ def cli(
37
+ obj,
38
+ service_url: str,
39
+ collection: str,
40
+ active: bool,
41
+ ):
42
+ """Activate or deactivate maintenance mode on collection COLLECTION on the
43
+ service SERVICE_URL. The argument ACTIVE should be either `On` or `Off`
44
+ (case-insensitive).
45
+
46
+ A token with curator rights is required.
47
+
48
+ This command expects a server version >= 5.4.0"""
49
+ try:
50
+ return maintenance(
51
+ obj,
52
+ service_url,
53
+ collection,
54
+ active,
55
+ )
56
+ except HTTPError as e:
57
+ click.echo(f'ERROR: {e}: {e.response.text}', err=True)
58
+ return 1
59
+
60
+
61
+ def maintenance(
62
+ obj: str,
63
+ service_url: str,
64
+ collection: str,
65
+ active: bool,
66
+ ):
67
+ token = obj
68
+ if token is None:
69
+ console.print('[red]Error[/red]: no token provided')
70
+ return 1
71
+
72
+ session = get_session()
73
+ communicate_maintenance(
74
+ service_url=service_url,
75
+ collection=collection,
76
+ active=active,
77
+ token=token,
78
+ session=session,
79
+ )
80
+ return 0