pyzotero 1.7.3__py3-none-any.whl → 1.7.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.
- pyzotero/cli.py +117 -0
- pyzotero/zotero.py +4 -4
- {pyzotero-1.7.3.dist-info → pyzotero-1.7.5.dist-info}/METADATA +1 -1
- pyzotero-1.7.5.dist-info/RECORD +9 -0
- pyzotero-1.7.3.dist-info/RECORD +0 -9
- {pyzotero-1.7.3.dist-info → pyzotero-1.7.5.dist-info}/WHEEL +0 -0
- {pyzotero-1.7.3.dist-info → pyzotero-1.7.5.dist-info}/entry_points.txt +0 -0
pyzotero/cli.py
CHANGED
|
@@ -15,6 +15,29 @@ def _get_zotero_client(locale="en-US"):
|
|
|
15
15
|
return zotero.Zotero(library_id="0", library_type="user", local=True, locale=locale)
|
|
16
16
|
|
|
17
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
|
+
|
|
18
41
|
@click.group()
|
|
19
42
|
@click.version_option(version=__version__, prog_name="pyzotero")
|
|
20
43
|
@click.option(
|
|
@@ -396,5 +419,99 @@ def test(ctx):
|
|
|
396
419
|
sys.exit(1)
|
|
397
420
|
|
|
398
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
|
+
|
|
399
516
|
if __name__ == "__main__":
|
|
400
517
|
main()
|
pyzotero/zotero.py
CHANGED
|
@@ -796,12 +796,12 @@ class Zotero:
|
|
|
796
796
|
"""Dump a file attachment to disk, with optional filename and path"""
|
|
797
797
|
if not filename:
|
|
798
798
|
filename = self.item(itemkey)["data"]["filename"]
|
|
799
|
-
pth = Path(path) / filename if path else filename
|
|
799
|
+
pth = Path(path) / filename if path else Path(filename)
|
|
800
800
|
file = self.file(itemkey)
|
|
801
801
|
if self.snapshot:
|
|
802
802
|
self.snapshot = False
|
|
803
|
-
pth
|
|
804
|
-
with
|
|
803
|
+
pth = pth.parent / (pth.name + ".zip")
|
|
804
|
+
with pth.open("wb") as f:
|
|
805
805
|
f.write(file)
|
|
806
806
|
|
|
807
807
|
@retrieve
|
|
@@ -1492,7 +1492,7 @@ class Zotero:
|
|
|
1492
1492
|
|
|
1493
1493
|
@backoff_check
|
|
1494
1494
|
def addto_collection(self, collection, payload):
|
|
1495
|
-
"""Add
|
|
1495
|
+
"""Add item to a collection
|
|
1496
1496
|
Accepts two arguments:
|
|
1497
1497
|
The collection ID, and an item dict
|
|
1498
1498
|
"""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pyzotero/__init__.py,sha256=5QI4Jou9L-YJAf_oN9TgRXVKgt_Unc39oADo2Ch8bLI,243
|
|
2
|
+
pyzotero/cli.py,sha256=dsq4QFqs4vig7CpORQYWD96smM8Z00BbaVh8AsLjhmg,16243
|
|
3
|
+
pyzotero/filetransport.py,sha256=umLik1LLmrpgaNmyjvtBoqqcaMgIq79PYsTvN5vG-gY,5530
|
|
4
|
+
pyzotero/zotero.py,sha256=tXcSpUU1d72rVMCLiGqiRZHl4H2s3BcOLWYaK4lNbGs,76509
|
|
5
|
+
pyzotero/zotero_errors.py,sha256=6obx9-pBO0z1bxt33vuzDluELvA5kSLCsfc-uGc3KNw,2660
|
|
6
|
+
pyzotero-1.7.5.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
7
|
+
pyzotero-1.7.5.dist-info/entry_points.txt,sha256=MzN7IMRj_oPNmDCsseYFPum3bHWE1gFxywhlbFbcn2k,48
|
|
8
|
+
pyzotero-1.7.5.dist-info/METADATA,sha256=GeDQw1OkhpNH6iTVbQuSHIWjsNuEk_dlxa7KZtrVwm0,9962
|
|
9
|
+
pyzotero-1.7.5.dist-info/RECORD,,
|
pyzotero-1.7.3.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
pyzotero/__init__.py,sha256=5QI4Jou9L-YJAf_oN9TgRXVKgt_Unc39oADo2Ch8bLI,243
|
|
2
|
-
pyzotero/cli.py,sha256=t88ENb-7zydV1GFMf4Attj4wBYgM84eY2Ck5jSFO3Fc,12596
|
|
3
|
-
pyzotero/filetransport.py,sha256=umLik1LLmrpgaNmyjvtBoqqcaMgIq79PYsTvN5vG-gY,5530
|
|
4
|
-
pyzotero/zotero.py,sha256=4qb7jLl1lNkDv3WpEPLW2L0SbleTtGYlQ6Rloz-hmN0,76497
|
|
5
|
-
pyzotero/zotero_errors.py,sha256=6obx9-pBO0z1bxt33vuzDluELvA5kSLCsfc-uGc3KNw,2660
|
|
6
|
-
pyzotero-1.7.3.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
7
|
-
pyzotero-1.7.3.dist-info/entry_points.txt,sha256=MzN7IMRj_oPNmDCsseYFPum3bHWE1gFxywhlbFbcn2k,48
|
|
8
|
-
pyzotero-1.7.3.dist-info/METADATA,sha256=pCuO1_RfFP50-HU9P0PzyEvcL3Dpl5U8bFZMwIRNFXI,9962
|
|
9
|
-
pyzotero-1.7.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|