pyzotero 1.7.2__py3-none-any.whl → 1.7.4__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.
- pyzotero/cli.py +160 -0
- {pyzotero-1.7.2.dist-info → pyzotero-1.7.4.dist-info}/METADATA +2 -2
- {pyzotero-1.7.2.dist-info → pyzotero-1.7.4.dist-info}/RECORD +5 -5
- {pyzotero-1.7.2.dist-info → pyzotero-1.7.4.dist-info}/WHEEL +0 -0
- {pyzotero-1.7.2.dist-info → pyzotero-1.7.4.dist-info}/entry_points.txt +0 -0
pyzotero/cli.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import sys
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
|
+
import httpx
|
|
7
8
|
|
|
8
9
|
from pyzotero import __version__, zotero
|
|
9
10
|
from pyzotero.zotero import chunks
|
|
@@ -14,6 +15,29 @@ def _get_zotero_client(locale="en-US"):
|
|
|
14
15
|
return zotero.Zotero(library_id="0", library_type="user", local=True, locale=locale)
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
def _normalize_doi(doi):
|
|
19
|
+
"""Normalise a DOI for case-insensitive matching.
|
|
20
|
+
|
|
21
|
+
Strips common prefixes (https://doi.org/, http://doi.org/, doi:) and converts to lowercase.
|
|
22
|
+
DOIs are case-insensitive per the DOI specification.
|
|
23
|
+
"""
|
|
24
|
+
if not doi:
|
|
25
|
+
return ""
|
|
26
|
+
|
|
27
|
+
# Strip whitespace
|
|
28
|
+
doi = doi.strip()
|
|
29
|
+
|
|
30
|
+
# Strip common prefixes
|
|
31
|
+
prefixes = ["https://doi.org/", "http://doi.org/", "doi:"]
|
|
32
|
+
for prefix in prefixes:
|
|
33
|
+
if doi.lower().startswith(prefix.lower()):
|
|
34
|
+
doi = doi[len(prefix) :]
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
# Convert to lowercase for case-insensitive matching
|
|
38
|
+
return doi.lower().strip()
|
|
39
|
+
|
|
40
|
+
|
|
17
41
|
@click.group()
|
|
18
42
|
@click.version_option(version=__version__, prog_name="pyzotero")
|
|
19
43
|
@click.option(
|
|
@@ -353,5 +377,141 @@ def itemtypes(ctx):
|
|
|
353
377
|
sys.exit(1)
|
|
354
378
|
|
|
355
379
|
|
|
380
|
+
@main.command()
|
|
381
|
+
@click.pass_context
|
|
382
|
+
def test(ctx):
|
|
383
|
+
"""Test connection to local Zotero instance.
|
|
384
|
+
|
|
385
|
+
This command checks whether Zotero is running and accepting local connections.
|
|
386
|
+
|
|
387
|
+
Examples:
|
|
388
|
+
pyzotero test
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
try:
|
|
392
|
+
locale = ctx.obj.get("locale", "en-US")
|
|
393
|
+
zot = _get_zotero_client(locale)
|
|
394
|
+
|
|
395
|
+
# Call settings() to test the connection
|
|
396
|
+
# This should return {} if Zotero is running and listening
|
|
397
|
+
result = zot.settings()
|
|
398
|
+
|
|
399
|
+
# If we get here, the connection succeeded
|
|
400
|
+
click.echo("✓ Connection successful: Zotero is running and listening locally.")
|
|
401
|
+
if result == {}:
|
|
402
|
+
click.echo(" Received expected empty settings response.")
|
|
403
|
+
else:
|
|
404
|
+
click.echo(f" Received response: {json.dumps(result)}")
|
|
405
|
+
|
|
406
|
+
except httpx.ConnectError:
|
|
407
|
+
click.echo(
|
|
408
|
+
"✗ Connection failed: Could not connect to Zotero.\n\n"
|
|
409
|
+
"Possible causes:\n"
|
|
410
|
+
" • Zotero might not be running\n"
|
|
411
|
+
" • Local connections might not be enabled\n\n"
|
|
412
|
+
"To enable local connections:\n"
|
|
413
|
+
" Zotero > Settings > Advanced > Allow other applications on this computer to communicate with Zotero",
|
|
414
|
+
err=True,
|
|
415
|
+
)
|
|
416
|
+
sys.exit(1)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
click.echo(f"Error: {e!s}", err=True)
|
|
419
|
+
sys.exit(1)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@main.command()
|
|
423
|
+
@click.argument("dois", nargs=-1)
|
|
424
|
+
@click.option(
|
|
425
|
+
"--json",
|
|
426
|
+
"output_json",
|
|
427
|
+
is_flag=True,
|
|
428
|
+
help="Output results as JSON",
|
|
429
|
+
)
|
|
430
|
+
@click.pass_context
|
|
431
|
+
def alldoi(ctx, dois, output_json): # noqa: PLR0912
|
|
432
|
+
"""Look up DOIs in the local Zotero library and return their Zotero IDs.
|
|
433
|
+
|
|
434
|
+
Accepts one or more DOIs as arguments and checks if they exist in the library.
|
|
435
|
+
DOI matching is case-insensitive and handles common prefixes (https://doi.org/, doi:).
|
|
436
|
+
|
|
437
|
+
If no DOIs are provided, shows "No items found" (text) or {} (JSON).
|
|
438
|
+
|
|
439
|
+
Examples:
|
|
440
|
+
pyzotero alldoi 10.1234/example
|
|
441
|
+
|
|
442
|
+
pyzotero alldoi 10.1234/abc https://doi.org/10.5678/def doi:10.9012/ghi
|
|
443
|
+
|
|
444
|
+
pyzotero alldoi 10.1234/example --json
|
|
445
|
+
|
|
446
|
+
"""
|
|
447
|
+
try:
|
|
448
|
+
locale = ctx.obj.get("locale", "en-US")
|
|
449
|
+
zot = _get_zotero_client(locale)
|
|
450
|
+
|
|
451
|
+
# Build a mapping of normalized DOIs to (original_doi, zotero_key)
|
|
452
|
+
click.echo("Building DOI index from library...", err=True)
|
|
453
|
+
doi_map = {}
|
|
454
|
+
|
|
455
|
+
# Get all items using everything() which handles pagination automatically
|
|
456
|
+
all_items = zot.everything(zot.items())
|
|
457
|
+
|
|
458
|
+
# Process all items
|
|
459
|
+
for item in all_items:
|
|
460
|
+
data = item.get("data", {})
|
|
461
|
+
item_doi = data.get("DOI", "")
|
|
462
|
+
|
|
463
|
+
if item_doi:
|
|
464
|
+
normalized_doi = _normalize_doi(item_doi)
|
|
465
|
+
item_key = data.get("key", "")
|
|
466
|
+
|
|
467
|
+
if normalized_doi and item_key:
|
|
468
|
+
# Store the original DOI from Zotero and the item key
|
|
469
|
+
doi_map[normalized_doi] = (item_doi, item_key)
|
|
470
|
+
|
|
471
|
+
click.echo(f"Indexed {len(doi_map)} items with DOIs", err=True)
|
|
472
|
+
|
|
473
|
+
# If no DOIs provided, return empty result
|
|
474
|
+
if not dois:
|
|
475
|
+
if output_json:
|
|
476
|
+
click.echo(json.dumps({}))
|
|
477
|
+
else:
|
|
478
|
+
click.echo("No items found")
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
# Look up each input DOI
|
|
482
|
+
found = []
|
|
483
|
+
not_found = []
|
|
484
|
+
|
|
485
|
+
for input_doi in dois:
|
|
486
|
+
normalized_input = _normalize_doi(input_doi)
|
|
487
|
+
|
|
488
|
+
if normalized_input in doi_map:
|
|
489
|
+
original_doi, zotero_key = doi_map[normalized_input]
|
|
490
|
+
found.append({"doi": original_doi, "key": zotero_key})
|
|
491
|
+
else:
|
|
492
|
+
not_found.append(input_doi)
|
|
493
|
+
|
|
494
|
+
# Output results
|
|
495
|
+
if output_json:
|
|
496
|
+
result = {"found": found, "not_found": not_found}
|
|
497
|
+
click.echo(json.dumps(result, indent=2))
|
|
498
|
+
else:
|
|
499
|
+
if found:
|
|
500
|
+
click.echo(f"\nFound {len(found)} items:\n")
|
|
501
|
+
for item in found:
|
|
502
|
+
click.echo(f" {item['doi']} → {item['key']}")
|
|
503
|
+
else:
|
|
504
|
+
click.echo("No items found")
|
|
505
|
+
|
|
506
|
+
if not_found:
|
|
507
|
+
click.echo(f"\nNot found ({len(not_found)}):")
|
|
508
|
+
for doi in not_found:
|
|
509
|
+
click.echo(f" {doi}")
|
|
510
|
+
|
|
511
|
+
except Exception as e:
|
|
512
|
+
click.echo(f"Error: {e!s}", err=True)
|
|
513
|
+
sys.exit(1)
|
|
514
|
+
|
|
515
|
+
|
|
356
516
|
if __name__ == "__main__":
|
|
357
517
|
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyzotero
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.4
|
|
4
4
|
Summary: Python wrapper for the Zotero API
|
|
5
5
|
Keywords: Zotero,DH
|
|
6
6
|
Author: Stephan Hügel
|
|
@@ -149,7 +149,7 @@ Use the `--json` flag to output structured JSON.
|
|
|
149
149
|
|
|
150
150
|
## Optional: Command-Line Interface
|
|
151
151
|
|
|
152
|
-
Pyzotero includes an optional command-line interface for searching and querying your local Zotero library.
|
|
152
|
+
Pyzotero includes an optional command-line interface for searching and querying your local Zotero library. As it uses the local API server introduced in Zotero 7, it requires "Allow other applications on this computer to communicate with Zotero" to be enabled in Zotero's Settings > Advanced.
|
|
153
153
|
|
|
154
154
|
### Installing the CLI
|
|
155
155
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
pyzotero/__init__.py,sha256=5QI4Jou9L-YJAf_oN9TgRXVKgt_Unc39oADo2Ch8bLI,243
|
|
2
|
-
pyzotero/cli.py,sha256=
|
|
2
|
+
pyzotero/cli.py,sha256=dsq4QFqs4vig7CpORQYWD96smM8Z00BbaVh8AsLjhmg,16243
|
|
3
3
|
pyzotero/filetransport.py,sha256=umLik1LLmrpgaNmyjvtBoqqcaMgIq79PYsTvN5vG-gY,5530
|
|
4
4
|
pyzotero/zotero.py,sha256=4qb7jLl1lNkDv3WpEPLW2L0SbleTtGYlQ6Rloz-hmN0,76497
|
|
5
5
|
pyzotero/zotero_errors.py,sha256=6obx9-pBO0z1bxt33vuzDluELvA5kSLCsfc-uGc3KNw,2660
|
|
6
|
-
pyzotero-1.7.
|
|
7
|
-
pyzotero-1.7.
|
|
8
|
-
pyzotero-1.7.
|
|
9
|
-
pyzotero-1.7.
|
|
6
|
+
pyzotero-1.7.4.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
7
|
+
pyzotero-1.7.4.dist-info/entry_points.txt,sha256=MzN7IMRj_oPNmDCsseYFPum3bHWE1gFxywhlbFbcn2k,48
|
|
8
|
+
pyzotero-1.7.4.dist-info/METADATA,sha256=CplfEb3lWWO75oglunBdtKSiytbEGgrbKSsDfEejOcU,9962
|
|
9
|
+
pyzotero-1.7.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|