dump-things-pyclient 0.2.3__py3-none-any.whl → 0.2.5__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,8 +2,19 @@ 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,
@@ -16,6 +27,7 @@ from ...communicate import (
16
27
 
17
28
  logger = logging.getLogger('auto-curate')
18
29
 
30
+ console = Console(file=sys.stderr)
19
31
  stl_info = False
20
32
 
21
33
 
@@ -32,17 +44,17 @@ stl_info = False
32
44
  @click.option(
33
45
  '--destination-service-url',
34
46
  metavar='DEST_SERVICE_URL',
35
- help='select a different dump-thing-service, i.e. not SERVICE_URL, as destination for auto-curated records',
47
+ help='select a different dump-thing-service, i.e. not SERVICE_URL, as destination for auto-curated records (the default is SERVICE_URL)',
36
48
  )
37
49
  @click.option(
38
50
  '--destination-collection',
39
51
  metavar='DEST_COLLECTION',
40
- help='select a different collection, i.e. not the COLLECTION of SERVICE_URL, as destination for auto-curated records',
52
+ help='select a different collection, i.e. not COLLECTION, as destination for auto-curated records',
41
53
  )
42
54
  @click.option(
43
55
  '--destination-token',
44
56
  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',
57
+ 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
58
  )
47
59
  @click.option(
48
60
  '--pid', '-p',
@@ -114,7 +126,11 @@ def cli(
114
126
  dry_run,
115
127
  )
116
128
  except HTTPError as e:
117
- print(f'ERROR: {e}: {e.response.text}', file=sys.stderr, flush=True)
129
+ rprint(
130
+ f'[red]Error[/red]: {e}: {e.response.text}',
131
+ file=sys.stderr,
132
+ flush=True,
133
+ )
118
134
  return 1
119
135
 
120
136
 
@@ -135,14 +151,14 @@ def auto_curate(
135
151
  curator_token = obj
136
152
 
137
153
  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
- )
154
+ console.print(f'[red]Error[/red]: no token was provided (use --token or DTC_TOKEN environment variable)')
143
155
  return 1
144
156
 
145
- click.echo(f'auto curate: {obj}')
157
+ if destination_service_url is None:
158
+ destination_service_url = service_url
159
+
160
+ if destination_token is None:
161
+ destination_token = curator_token
146
162
 
147
163
  output = None
148
164
 
@@ -157,12 +173,12 @@ def auto_curate(
157
173
  if list_labels:
158
174
  output = []
159
175
 
160
- for label in incoming_read_labels(
161
- service_url=service_url,
162
- collection=collection,
163
- token=obj,
164
- ):
165
-
176
+ all_labels = incoming_read_labels(
177
+ service_url=service_url,
178
+ collection=collection,
179
+ token=obj,
180
+ )
181
+ for label in all_labels:
166
182
  if include and label not in include:
167
183
  logger.debug('ignoring non-included incoming label: %s', label)
168
184
  continue
@@ -178,13 +194,34 @@ def auto_curate(
178
194
  if list_records:
179
195
  output[label] = []
180
196
 
181
- for record, _, _, _, _ in incoming_read_records(
182
- service_url=service_url,
183
- collection=collection,
184
- label=label,
185
- token=obj,
186
- ):
197
+ # Get the total number of entries for the
198
+ record_source = incoming_read_records(
199
+ service_url=service_url,
200
+ collection=collection,
201
+ label=label,
202
+ token=obj,
203
+ )
187
204
 
205
+ # Get the first entry to find the total number of records
206
+ try:
207
+ first_record, _, _, _, total = next(record_source)
208
+ except StopIteration:
209
+ console.print(f'no records in incoming area [green]{label}[/green], skipping it')
210
+ continue
211
+
212
+ # Get the first entry an all other entries
213
+ for index, (record, _, _, _, total) in track(
214
+ zip(
215
+ count(),
216
+ chain(
217
+ [(first_record, None, None, None, None)],
218
+ cast(Iterable, record_source),
219
+ ),
220
+ ),
221
+ description=f'processing [green]{label}[/green]',
222
+ total=total,
223
+ console=console,
224
+ ):
188
225
  if list_records:
189
226
  output[label].append(record)
190
227
  continue
@@ -204,43 +241,53 @@ def auto_curate(
204
241
  class_name = re.search('([_A-Za-z0-9]*$)', record['schema_type']).group(0)
205
242
  except (IndexError, KeyError):
206
243
  global stl_info
244
+ console.print(f'[yellow]Warning[/yellow]: ignoring record with pid {record["pid"]} because `schema_type` attribute is missing.')
207
245
  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".""",
246
+ console.print(
247
+ ' Please ensure that `schema_type` is stored in the records '
248
+ 'or that the associated incoming area store has a backend with a '
249
+ '"Schema Type Layer", i.e., "record_dir+stl" or "sqlite+stl"."',
214
250
  )
215
251
  stl_info = True
216
- else:
217
- logger.warning(f'ignoring record with pid {record["pid"]}, `schema_type` attribute is missing.')
218
252
  continue
219
253
 
220
254
  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}"')
255
+ console.print(f'WRITE record [green]"{record["pid"]}"[/green] of class "{class_name}" to "{destination_collection}@{destination_service_url}"')
256
+ console.print(f'DELETE record [green]"{record["pid"]}"[/green] from inbox "{label}" of "{collection}@{service_url}"')
223
257
  continue
224
258
 
225
259
  # 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)
260
+ try:
261
+ curated_write_record(
262
+ service_url=destination_service_url,
263
+ collection=destination_collection,
264
+ class_name=class_name,
265
+ record=record,
266
+ token=destination_token,
267
+ )
268
+ except HTTPError as e:
269
+ console.print(
270
+ f'[red]Error[/red]: writing record with pid {record["pid"]} failed: {e}: {e.response.text}',
271
+ )
272
+ raise
232
273
 
233
274
  # 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
- )
275
+ try:
276
+ incoming_delete_record(
277
+ service_url=service_url,
278
+ collection=collection,
279
+ label=label,
280
+ pid=record['pid'],
281
+ token=curator_token,
282
+ )
283
+ except HTTPError as e:
284
+ console.print(
285
+ f'[red]ERROR[/red]: deleting record with pid {record["pid"]} failed: {e}: {e.response.text}',
286
+ )
287
+ raise
241
288
 
242
289
  if output is not None:
243
- print(json.dumps(output, ensure_ascii=False))
290
+ rprint(json.dumps(output, ensure_ascii=False))
244
291
 
245
292
  return 0
246
293
 
@@ -0,0 +1,73 @@
1
+ import logging
2
+
3
+ import rich_click as click
4
+
5
+ from ...communicate import (
6
+ HTTPError,
7
+ maintenance as communicate_maintenance,
8
+ )
9
+
10
+
11
+ logger = logging.getLogger('maintenance')
12
+
13
+ subcommand_name = 'maintenance'
14
+
15
+
16
+ @click.command(short_help='Activate or deactivate maintenance mode on a collection')
17
+ @click.pass_obj
18
+ @click.argument(
19
+ 'service_url',
20
+ metavar='SERVICE_URL',
21
+ )
22
+ @click.argument(
23
+ 'collection',
24
+ metavar='COLLECTION',
25
+ )
26
+ @click.argument(
27
+ 'active',
28
+ metavar='ACTIVE',
29
+ type=click.Choice(['On', 'Off'], case_sensitive=False),
30
+ )
31
+ def cli(
32
+ obj,
33
+ service_url: str,
34
+ collection: str,
35
+ active: bool,
36
+ ):
37
+ """Activate or deactivate maintenance mode on collection COLLECTION on the
38
+ service SERVICE_URL. The argument ACTIVE should be either `On` or `Off`
39
+ (case-insensitive).
40
+
41
+ A token with curator rights is required.
42
+
43
+ This command expects a server version >= 5.4.0"""
44
+ try:
45
+ return maintenance(
46
+ obj,
47
+ service_url,
48
+ collection,
49
+ active,
50
+ )
51
+ except HTTPError as e:
52
+ click.echo(f'ERROR: {e}: {e.response.text}', err=True)
53
+ return 1
54
+
55
+
56
+ def maintenance(
57
+ obj: str,
58
+ service_url: str,
59
+ collection: str,
60
+ active: bool,
61
+ ):
62
+ token = obj
63
+ if token is None:
64
+ click.echo('ERROR: no token provided', err=True)
65
+ return 1
66
+
67
+ communicate_maintenance(
68
+ service_url=service_url,
69
+ collection=collection,
70
+ active=active,
71
+ token=token,
72
+ )
73
+ return 0
@@ -37,6 +37,8 @@ __all__ = [
37
37
  'incoming_read_records_of_class',
38
38
  'incoming_read_record_with_pid',
39
39
  'incoming_write_record',
40
+ 'maintenance',
41
+ 'server',
40
42
  ]
41
43
 
42
44
 
@@ -694,6 +696,33 @@ def server(
694
696
  return _do_request(requests.get, url=url, token=None, params=None)
695
697
 
696
698
 
699
+ def maintenance(
700
+ service_url: str,
701
+ collection: str,
702
+ active: bool,
703
+ token: str,
704
+ ) -> None:
705
+ """Activate or deactivate maintenance mode of a collection
706
+
707
+ :param service_url: the base URL of the service, i.e., the URL up to
708
+ `/<collection>/...`, `/maintenance`, or `/server`
709
+ :param collection: the name of the collection
710
+ :param active: whether maintenance mode should be active (`True`) or
711
+ non-active (`False`).
712
+ :param token: a token to authenticate against the endpoint, the token
713
+ must have curator-rights for the collection
714
+ """
715
+ url = (
716
+ (f'{service_url[:-1]}' if service_url.endswith('/') else service_url)
717
+ + '/maintenance'
718
+ )
719
+ _post_to_url(
720
+ url=url,
721
+ token=token,
722
+ json={'collection': collection, 'active': active}
723
+ )
724
+
725
+
697
726
  def _get_from_url(url: str,
698
727
  token: str | None,
699
728
  params: dict[str, str] | None = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dump-things-pyclient
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: A client library and CLI commands for dump-things-services
5
5
  Author-email: Christian Mönch <christian.moench@web.de>
6
6
  Requires-Python: >=3.11
@@ -1,20 +1,21 @@
1
1
  dump_things_pyclient/__init__.py,sha256=cn-U3TRIalN6aYHp1cMBRkQm1x98XBwquLFbgFEIf_Q,113
2
- dump_things_pyclient/communicate.py,sha256=R1lifyuP5Cm2oQdyaWxTsQAbiMBU9NxYA5-9yU4M3qA,31008
2
+ dump_things_pyclient/communicate.py,sha256=OSSrPRmeURiW1tOuBXecBzUgC_Qeb8TBs05l6QyoNF8,31904
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
6
6
  dump_things_pyclient/commands/redirect.py,sha256=kl8pGj8khjxk4lhk8AJLfgtCIm5PtjeMAl0J6K5FB7M,264
7
7
  dump_things_pyclient/commands/dtc_plugins/__init__.py,sha256=0YLByLiofhHkhJcDCkokldcCw3Jj0rsKJinRX4tt3Hc,514
8
- dump_things_pyclient/commands/dtc_plugins/auto_curate.py,sha256=3_SHXPQCXmY6GqTMTNVkKh5vvshfiZpMGFY0gvJxRbo,7411
8
+ dump_things_pyclient/commands/dtc_plugins/auto_curate.py,sha256=mDYHkXVS_2Nrcqd90nC9LIBCQnzYfRdj66fmZweyX_M,9094
9
9
  dump_things_pyclient/commands/dtc_plugins/clean_incoming.py,sha256=slk3xn1-DgMl88WZqgyemyscwof97TMXt3rley4mU1w,2086
10
10
  dump_things_pyclient/commands/dtc_plugins/delete_record.py,sha256=TVXyGetob8d75zuSBGuRKjtQPAcRqqjw-CcwYYHSC28,3626
11
11
  dump_things_pyclient/commands/dtc_plugins/export.py,sha256=FF1DmSmIA3GIhosUrTrXy_qyypMSJUalxdLJ844XhIE,4850
12
12
  dump_things_pyclient/commands/dtc_plugins/get_records.py,sha256=00W-agALqO_qFhjq14MI9N6TnJCgscMioy-Ll1ClrB4,7451
13
13
  dump_things_pyclient/commands/dtc_plugins/list_incoming.py,sha256=tmM0Qs4MVwMMLyERsWCxWGTM90rSNOShLpHH32wObd8,1959
14
+ dump_things_pyclient/commands/dtc_plugins/maintenance.py,sha256=WkVVxDVd04Mi-cRIdAGSwn-7aP2bDlLjaCzt-H4_sJU,1553
14
15
  dump_things_pyclient/commands/dtc_plugins/post_records.py,sha256=s3j9THe-RszKxyIISkQZRCTKplWWLlomHbS5dyRlep0,2908
15
16
  dump_things_pyclient/commands/dtc_plugins/read_pages.py,sha256=hpw7vtG7joIMrNqEqZFCwzbQFd3ATzv7iyySsX8nKWk,3385
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,,
17
+ dump_things_pyclient-0.2.5.dist-info/METADATA,sha256=Bnwt2hpG4eycg9gnso1oi_HWhNjLePPbgxpeZ6kTrNc,999
18
+ dump_things_pyclient-0.2.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
19
+ dump_things_pyclient-0.2.5.dist-info/entry_points.txt,sha256=U1QhQtk767G_OXdZwPdTXYbIPfcDU13Z2u1d6exX8uE,470
20
+ dump_things_pyclient-0.2.5.dist-info/top_level.txt,sha256=Asvruw-SyLoYhWis1CFOx89RGxpjXoTZVGoq4JSGt88,21
21
+ dump_things_pyclient-0.2.5.dist-info/RECORD,,