pyzotero 1.6.1__py3-none-any.whl → 1.6.2__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.
_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.6.1'
16
- __version_tuple__ = version_tuple = (1, 6, 1)
15
+ __version__ = version = '1.6.2'
16
+ __version_tuple__ = version_tuple = (1, 6, 2)
pyzotero/zotero.py CHANGED
@@ -37,9 +37,9 @@ from urllib.parse import (
37
37
 
38
38
  import bibtexparser
39
39
  import feedparser
40
+ import httpx
40
41
  import pytz
41
- import requests
42
- from requests import Request
42
+ from httpx import Request
43
43
 
44
44
  import pyzotero as pz
45
45
 
@@ -97,11 +97,14 @@ def tcache(func):
97
97
  "GET",
98
98
  build_url(self.endpoint, query_string),
99
99
  params=params,
100
- ).prepare()
100
+ )
101
+ with httpx.Client() as client:
102
+ response = client.send(r)
103
+
101
104
  # now split up the URL
102
- result = urlparse(r.url)
105
+ result = urlparse(str(response.url))
103
106
  # construct cache key
104
- cachekey = result.path + "_" + result.query
107
+ cachekey = f"{result.path}_{result.query}"
105
108
  if self.templates.get(cachekey) and not self._updated(
106
109
  query_string, self.templates[cachekey], cachekey
107
110
  ):
@@ -132,7 +135,7 @@ def backoff_check(func):
132
135
  resp = func(self, *args, **kwargs)
133
136
  try:
134
137
  resp.raise_for_status()
135
- except requests.exceptions.HTTPError as exc:
138
+ except httpx.HTTPError as exc:
136
139
  error_handler(self, resp, exc)
137
140
  self.request = resp
138
141
  backoff = resp.headers.get("backoff") or resp.headers.get("retry-after")
@@ -166,8 +169,8 @@ def retrieve(func):
166
169
  self.links = self._extract_links()
167
170
  # determine content and format, based on url params
168
171
  content = (
169
- self.content.search(self.request.url)
170
- and self.content.search(self.request.url).group(0)
172
+ self.content.search(str(self.request.url))
173
+ and self.content.search(str(self.request.url)).group(0)
171
174
  or "bib"
172
175
  )
173
176
  # JSON by default
@@ -223,7 +226,7 @@ def retrieve(func):
223
226
  file = retrieved.content
224
227
  return file
225
228
  # check to see whether it's tag data
226
- if "tags" in self.request.url:
229
+ if "tags" in str(self.request.url):
227
230
  self.tag_data = False
228
231
  return self._tags_data(retrieved.json())
229
232
  if fmt == "atom":
@@ -277,6 +280,7 @@ class Zotero:
277
280
  locale="en-US",
278
281
  local=False,
279
282
  ):
283
+ self.client = None
280
284
  """Store Zotero credentials"""
281
285
  if not local:
282
286
  self.endpoint = "https://api.zotero.org"
@@ -300,6 +304,7 @@ class Zotero:
300
304
  self.tag_data = False
301
305
  self.request = None
302
306
  self.snapshot = False
307
+ self.client = httpx.Client(headers=self.default_headers())
303
308
  # these aren't valid item fields, so never send them to the server
304
309
  self.temp_keys = set(["key", "etag", "group_id", "updated"])
305
310
  # determine which processor to use for the parsed content
@@ -331,6 +336,11 @@ class Zotero:
331
336
  self.backoff = False
332
337
  self.backoff_duration = 0.0
333
338
 
339
+ def __del__(self):
340
+ # this isn't guaranteed to run, but that's OK
341
+ if c := self.client:
342
+ c.close()
343
+
334
344
  def _check_for_component(self, url, component):
335
345
  """Check a url path query fragment for a specific query parameter"""
336
346
  if parse_qs(url).get(component):
@@ -381,11 +391,11 @@ class Zotero:
381
391
  It's always OK to include these headers
382
392
  """
383
393
  _headers = {
384
- "User-Agent": "Pyzotero/%s" % pz.__version__,
385
- "Zotero-API-Version": "%s" % __api_version__,
394
+ "User-Agent": f"Pyzotero/{pz.__version__}",
395
+ "Zotero-API-Version": f"{__api_version__}",
386
396
  }
387
397
  if self.api_key:
388
- _headers["Authorization"] = "Bearer %s" % self.api_key
398
+ _headers["Authorization"] = f"Bearer {self.api_key}"
389
399
  return _headers
390
400
 
391
401
  def _cache(self, response, key):
@@ -442,13 +452,17 @@ class Zotero:
442
452
  params["locale"] = self.locale
443
453
  else:
444
454
  params = {"locale": self.locale}
445
- self.request = requests.get(
446
- url=full_url, headers=self.default_headers(), params=params
447
- )
455
+ # we now have to merge self.url_params (default params, and those supplied by the user)
456
+ if not params:
457
+ params = {}
458
+ if not self.url_params:
459
+ self.url_params = {}
460
+ merged_params = params | self.url_params
461
+ self.request = self.client.get(url=full_url, params=merged_params)
448
462
  self.request.encoding = "utf-8"
449
463
  try:
450
464
  self.request.raise_for_status()
451
- except requests.exceptions.HTTPError as exc:
465
+ except httpx.HTTPError as exc:
452
466
  error_handler(self, self.request, exc)
453
467
  backoff = self.request.headers.get("backoff") or self.request.headers.get(
454
468
  "retry-after"
@@ -465,7 +479,7 @@ class Zotero:
465
479
  try:
466
480
  for key, value in self.request.links.items():
467
481
  parsed = urlparse(value["url"])
468
- fragment = "{path}?{query}".format(path=parsed[2], query=parsed[4])
482
+ fragment = f"{parsed[2]}?{parsed[4]}"
469
483
  extracted[key] = fragment
470
484
  # add a 'self' link
471
485
  parsed = list(urlparse(self.self_link))
@@ -510,13 +524,12 @@ class Zotero:
510
524
  "%a, %d %b %Y %H:%M:%S %Z"
511
525
  )
512
526
  }
513
- headers.update(self.default_headers())
514
527
  # perform the request, and check whether the response returns 304
515
528
  self._check_backoff()
516
- req = requests.get(query, headers=headers)
529
+ req = self.client.get(query, headers=headers)
517
530
  try:
518
531
  req.raise_for_status()
519
- except requests.exceptions.HTTPError as exc:
532
+ except httpx.HTTPError as exc:
520
533
  error_handler(self, req, exc)
521
534
  backoff = self.request.headers.get("backoff") or self.request.headers.get(
522
535
  "retry-after"
@@ -550,7 +563,7 @@ class Zotero:
550
563
  # bib format can't have a limit
551
564
  if params.get("format") == "bib":
552
565
  del params["limit"]
553
- self.url_params = urlencode(params, doseq=True)
566
+ self.url_params = params
554
567
 
555
568
  def _build_query(self, query_string, no_params=False):
556
569
  """
@@ -560,12 +573,11 @@ class Zotero:
560
573
  try:
561
574
  query = quote(query_string.format(u=self.library_id, t=self.library_type))
562
575
  except KeyError as err:
563
- raise ze.ParamNotPassed("There's a request parameter missing: %s" % err)
576
+ raise ze.ParamNotPassed(f"There's a request parameter missing: {err}")
564
577
  # Add the URL parameters and the user key, if necessary
565
578
  if no_params is False:
566
579
  if not self.url_params:
567
580
  self.add_parameters()
568
- query = "%s?%s" % (query, self.url_params)
569
581
  return query
570
582
 
571
583
  @retrieve
@@ -638,9 +650,9 @@ class Zotero:
638
650
  For text documents, 'indexedChars' and 'totalChars' OR
639
651
  For PDFs, 'indexedPages' and 'totalPages'.
640
652
  """
641
- headers = self.default_headers()
653
+ headers = {}
642
654
  headers.update({"Content-Type": "application/json"})
643
- return requests.put(
655
+ return self.client.put(
644
656
  url=build_url(
645
657
  self.endpoint,
646
658
  "/{t}/{u}/items/{k}/fulltext".format(
@@ -648,7 +660,7 @@ class Zotero:
648
660
  ),
649
661
  ),
650
662
  headers=headers,
651
- data=json.dumps(payload),
663
+ content=json.dumps(payload),
652
664
  )
653
665
 
654
666
  def new_fulltext(self, since):
@@ -656,15 +668,18 @@ class Zotero:
656
668
  Retrieve list of full-text content items and versions which are newer
657
669
  than <since>
658
670
  """
659
- query_string = "/{t}/{u}/fulltext?since={version}".format(
660
- t=self.library_type, u=self.library_id, version=since
671
+ query_string = "/{t}/{u}/fulltext".format(
672
+ t=self.library_type, u=self.library_id
661
673
  )
662
- headers = self.default_headers()
674
+ headers = {}
675
+ params = {"since": since}
663
676
  self._check_backoff()
664
- resp = requests.get(build_url(self.endpoint, query_string), headers=headers)
677
+ resp = self.client.get(
678
+ build_url(self.endpoint, query_string), params=params, headers=headers
679
+ )
665
680
  try:
666
681
  resp.raise_for_status()
667
- except requests.exceptions.HTTPError as exc:
682
+ except httpx.HTTPError as exc:
668
683
  error_handler(self, resp, exc)
669
684
  backoff = self.request.headers.get("backoff") or self.request.headers.get(
670
685
  "retry-after"
@@ -984,17 +999,17 @@ class Zotero:
984
999
  def item_template(self, itemtype, linkmode=None):
985
1000
  """Get a template for a new item"""
986
1001
  # if we have a template and it hasn't been updated since we stored it
987
- template_name = "{}_{}_{}".format(*["item_template", itemtype, linkmode or ""])
988
- query_string = "/items/new?itemType={i}".format(i=itemtype)
1002
+ template_name = f"item_template_{itemtype}_{linkmode or ''}"
1003
+ params = {"itemType": itemtype}
1004
+ # Set linkMode parameter for API request if itemtype is attachment
1005
+ if itemtype == "attachment":
1006
+ params["linkMode"] = linkmode
1007
+ self.add_parameters(**params)
1008
+ query_string = "/items/new"
989
1009
  if self.templates.get(template_name) and not self._updated(
990
1010
  query_string, self.templates[template_name], template_name
991
1011
  ):
992
1012
  return copy.deepcopy(self.templates[template_name]["tmplt"])
993
-
994
- # Set linkMode parameter for API request if itemtype is attachment
995
- if itemtype == "attachment":
996
- query_string = "{}&linkMode={}".format(query_string, linkmode)
997
-
998
1013
  # otherwise perform a normal request and cache the response
999
1014
  retrieved = self._retrieve_data(query_string)
1000
1015
  return self._cache(retrieved, template_name)
@@ -1050,20 +1065,19 @@ class Zotero:
1050
1065
  self.savedsearch._validate(conditions)
1051
1066
  payload = [{"name": name, "conditions": conditions}]
1052
1067
  headers = {"Zotero-Write-Token": token()}
1053
- headers.update(self.default_headers())
1054
1068
  self._check_backoff()
1055
- req = requests.post(
1069
+ req = self.client.post(
1056
1070
  url=build_url(
1057
1071
  self.endpoint,
1058
1072
  "/{t}/{u}/searches".format(t=self.library_type, u=self.library_id),
1059
1073
  ),
1060
1074
  headers=headers,
1061
- data=json.dumps(payload),
1075
+ content=json.dumps(payload),
1062
1076
  )
1063
1077
  self.request = req
1064
1078
  try:
1065
1079
  req.raise_for_status()
1066
- except requests.exceptions.HTTPError as exc:
1080
+ except httpx.HTTPError as exc:
1067
1081
  error_handler(self, req, exc)
1068
1082
  backoff = self.request.headers.get("backoff") or self.request.headers.get(
1069
1083
  "retry-after"
@@ -1078,9 +1092,8 @@ class Zotero:
1078
1092
  unique search keys
1079
1093
  """
1080
1094
  headers = {"Zotero-Write-Token": token()}
1081
- headers.update(self.default_headers())
1082
1095
  self._check_backoff()
1083
- req = requests.delete(
1096
+ req = self.client.delete(
1084
1097
  url=build_url(
1085
1098
  self.endpoint,
1086
1099
  "/{t}/{u}/searches".format(t=self.library_type, u=self.library_id),
@@ -1091,7 +1104,7 @@ class Zotero:
1091
1104
  self.request = req
1092
1105
  try:
1093
1106
  req.raise_for_status()
1094
- except requests.exceptions.HTTPError as exc:
1107
+ except httpx.HTTPError as exc:
1095
1108
  error_handler(self, req, exc)
1096
1109
  backoff = self.request.headers.get("backoff") or self.request.headers.get(
1097
1110
  "retry-after"
@@ -1117,7 +1130,7 @@ class Zotero:
1117
1130
  except AssertionError:
1118
1131
  item["data"]["tags"] = list()
1119
1132
  for tag in tags:
1120
- item["data"]["tags"].append({"tag": "%s" % tag})
1133
+ item["data"]["tags"].append({"tag": f"{tag}"})
1121
1134
  # make sure everything's OK
1122
1135
  assert self.check_items([item])
1123
1136
  return self.update_item(item)
@@ -1134,9 +1147,11 @@ class Zotero:
1134
1147
  "GET",
1135
1148
  build_url(self.endpoint, query_string),
1136
1149
  params=params,
1137
- ).prepare()
1150
+ )
1151
+ with httpx.Client() as client:
1152
+ response = client.send(r)
1138
1153
  # now split up the URL
1139
- result = urlparse(r.url)
1154
+ result = urlparse(str(response.url))
1140
1155
  # construct cache key
1141
1156
  cachekey = result.path + "_" + result.query
1142
1157
  if self.templates.get(cachekey) and not self._updated(
@@ -1189,8 +1204,7 @@ class Zotero:
1189
1204
  difference = to_check.difference(template)
1190
1205
  if difference:
1191
1206
  raise ze.InvalidItemFields(
1192
- "Invalid keys present in item %s: %s"
1193
- % (pos + 1, " ".join(i for i in difference))
1207
+ f"Invalid keys present in item {pos + 1}: {' '.join(i for i in difference)}"
1194
1208
  )
1195
1209
  return items
1196
1210
 
@@ -1254,20 +1268,19 @@ class Zotero:
1254
1268
  if last_modified is not None:
1255
1269
  headers["If-Unmodified-Since-Version"] = str(last_modified)
1256
1270
  to_send = json.dumps([i for i in self._cleanup(*payload, allow=("key"))])
1257
- headers.update(self.default_headers())
1258
1271
  self._check_backoff()
1259
- req = requests.post(
1272
+ req = self.client.post(
1260
1273
  url=build_url(
1261
1274
  self.endpoint,
1262
1275
  "/{t}/{u}/items".format(t=self.library_type, u=self.library_id),
1263
1276
  ),
1264
- data=to_send,
1277
+ content=to_send,
1265
1278
  headers=dict(headers),
1266
1279
  )
1267
1280
  self.request = req
1268
1281
  try:
1269
1282
  req.raise_for_status()
1270
- except requests.exceptions.HTTPError as exc:
1283
+ except httpx.HTTPError as exc:
1271
1284
  error_handler(self, req, exc)
1272
1285
  resp = req.json()
1273
1286
  backoff = self.request.headers.get("backoff") or self.request.headers.get(
@@ -1282,24 +1295,23 @@ class Zotero:
1282
1295
  uheaders = {
1283
1296
  "If-Unmodified-Since-Version": req.headers["last-modified-version"]
1284
1297
  }
1285
- uheaders.update(self.default_headers())
1286
1298
  for value in resp["success"].values():
1287
1299
  payload = json.dumps({"parentItem": parentid})
1288
1300
  self._check_backoff()
1289
- presp = requests.patch(
1301
+ presp = self.client.patch(
1290
1302
  url=build_url(
1291
1303
  self.endpoint,
1292
1304
  "/{t}/{u}/items/{v}".format(
1293
1305
  t=self.library_type, u=self.library_id, v=value
1294
1306
  ),
1295
1307
  ),
1296
- data=payload,
1308
+ content=payload,
1297
1309
  headers=dict(uheaders),
1298
1310
  )
1299
1311
  self.request = presp
1300
1312
  try:
1301
1313
  presp.raise_for_status()
1302
- except requests.exceptions.HTTPError as exc:
1314
+ except httpx.HTTPError as exc:
1303
1315
  error_handler(self, presp, exc)
1304
1316
  backoff = presp.headers.get("backoff") or presp.headers.get(
1305
1317
  "retry-after"
@@ -1330,20 +1342,19 @@ class Zotero:
1330
1342
  headers = {"Zotero-Write-Token": token()}
1331
1343
  if last_modified is not None:
1332
1344
  headers["If-Unmodified-Since-Version"] = str(last_modified)
1333
- headers.update(self.default_headers())
1334
1345
  self._check_backoff()
1335
- req = requests.post(
1346
+ req = self.client.post(
1336
1347
  url=build_url(
1337
1348
  self.endpoint,
1338
1349
  "/{t}/{u}/collections".format(t=self.library_type, u=self.library_id),
1339
1350
  ),
1340
1351
  headers=headers,
1341
- data=json.dumps(payload),
1352
+ content=json.dumps(payload),
1342
1353
  )
1343
1354
  self.request = req
1344
1355
  try:
1345
1356
  req.raise_for_status()
1346
- except requests.exceptions.HTTPError as exc:
1357
+ except httpx.HTTPError as exc:
1347
1358
  error_handler(self, req, exc)
1348
1359
  backoff = req.headers.get("backoff") or req.headers.get("retry-after")
1349
1360
  if backoff:
@@ -1362,9 +1373,8 @@ class Zotero:
1362
1373
  modified = last_modified
1363
1374
  key = payload["key"]
1364
1375
  headers = {"If-Unmodified-Since-Version": str(modified)}
1365
- headers.update(self.default_headers())
1366
1376
  headers.update({"Content-Type": "application/json"})
1367
- return requests.put(
1377
+ return self.client.put(
1368
1378
  url=build_url(
1369
1379
  self.endpoint,
1370
1380
  "/{t}/{u}/collections/{c}".format(
@@ -1372,7 +1382,7 @@ class Zotero:
1372
1382
  ),
1373
1383
  ),
1374
1384
  headers=headers,
1375
- data=json.dumps(payload),
1385
+ content=json.dumps(payload),
1376
1386
  )
1377
1387
 
1378
1388
  def attachment_simple(self, files, parentid=None):
@@ -1422,8 +1432,7 @@ class Zotero:
1422
1432
  modified = last_modified
1423
1433
  ident = payload["key"]
1424
1434
  headers = {"If-Unmodified-Since-Version": str(modified)}
1425
- headers.update(self.default_headers())
1426
- return requests.patch(
1435
+ return self.client.patch(
1427
1436
  url=build_url(
1428
1437
  self.endpoint,
1429
1438
  "/{t}/{u}/items/{id}".format(
@@ -1431,7 +1440,7 @@ class Zotero:
1431
1440
  ),
1432
1441
  ),
1433
1442
  headers=headers,
1434
- data=json.dumps(to_send),
1443
+ content=json.dumps(to_send),
1435
1444
  )
1436
1445
 
1437
1446
  def update_items(self, payload):
@@ -1440,24 +1449,21 @@ class Zotero:
1440
1449
  Accepts one argument, a list of dicts containing Item data
1441
1450
  """
1442
1451
  to_send = [self.check_items([p])[0] for p in payload]
1443
- headers = {}
1444
- headers.update(self.default_headers())
1445
1452
  # the API only accepts 50 items at a time, so we have to split
1446
1453
  # anything longer
1447
1454
  for chunk in chunks(to_send, 50):
1448
1455
  self._check_backoff()
1449
- req = requests.post(
1456
+ req = self.client.post(
1450
1457
  url=build_url(
1451
1458
  self.endpoint,
1452
1459
  "/{t}/{u}/items/".format(t=self.library_type, u=self.library_id),
1453
1460
  ),
1454
- headers=headers,
1455
- data=json.dumps(chunk),
1461
+ content=json.dumps(chunk),
1456
1462
  )
1457
1463
  self.request = req
1458
1464
  try:
1459
1465
  req.raise_for_status()
1460
- except requests.exceptions.HTTPError as exc:
1466
+ except httpx.HTTPError as exc:
1461
1467
  error_handler(self, req, exc)
1462
1468
  backoff = req.headers.get("backoff") or req.headers.get("retry-after")
1463
1469
  if backoff:
@@ -1470,26 +1476,23 @@ class Zotero:
1470
1476
  Accepts one argument, a list of dicts containing Collection data
1471
1477
  """
1472
1478
  to_send = [self.check_items([p])[0] for p in payload]
1473
- headers = {}
1474
- headers.update(self.default_headers())
1475
1479
  # the API only accepts 50 items at a time, so we have to split
1476
1480
  # anything longer
1477
1481
  for chunk in chunks(to_send, 50):
1478
1482
  self._check_backoff()
1479
- req = requests.post(
1483
+ req = self.client.post(
1480
1484
  url=build_url(
1481
1485
  self.endpoint,
1482
1486
  "/{t}/{u}/collections/".format(
1483
1487
  t=self.library_type, u=self.library_id
1484
1488
  ),
1485
1489
  ),
1486
- headers=headers,
1487
- data=json.dumps(chunk),
1490
+ content=json.dumps(chunk),
1488
1491
  )
1489
1492
  self.request = req
1490
1493
  try:
1491
1494
  req.raise_for_status()
1492
- except requests.exceptions.HTTPError as exc:
1495
+ except httpx.HTTPError as exc:
1493
1496
  error_handler(self, req, exc)
1494
1497
  backoff = req.headers.get("backoff") or req.headers.get("retry-after")
1495
1498
  if backoff:
@@ -1508,15 +1511,14 @@ class Zotero:
1508
1511
  # add the collection data from the item
1509
1512
  modified_collections = payload["data"]["collections"] + [collection]
1510
1513
  headers = {"If-Unmodified-Since-Version": str(modified)}
1511
- headers.update(self.default_headers())
1512
- return requests.patch(
1514
+ return self.client.patch(
1513
1515
  url=build_url(
1514
1516
  self.endpoint,
1515
1517
  "/{t}/{u}/items/{i}".format(
1516
1518
  t=self.library_type, u=self.library_id, i=ident
1517
1519
  ),
1518
1520
  ),
1519
- data=json.dumps({"collections": modified_collections}),
1521
+ content=json.dumps({"collections": modified_collections}),
1520
1522
  headers=headers,
1521
1523
  )
1522
1524
 
@@ -1534,15 +1536,14 @@ class Zotero:
1534
1536
  c for c in payload["data"]["collections"] if c != collection
1535
1537
  ]
1536
1538
  headers = {"If-Unmodified-Since-Version": str(modified)}
1537
- headers.update(self.default_headers())
1538
- return requests.patch(
1539
+ return self.client.patch(
1539
1540
  url=build_url(
1540
1541
  self.endpoint,
1541
1542
  "/{t}/{u}/items/{i}".format(
1542
1543
  t=self.library_type, u=self.library_id, i=ident
1543
1544
  ),
1544
1545
  ),
1545
- data=json.dumps({"collections": modified_collections}),
1546
+ content=json.dumps({"collections": modified_collections}),
1546
1547
  headers=headers,
1547
1548
  )
1548
1549
 
@@ -1561,8 +1562,7 @@ class Zotero:
1561
1562
  headers = {
1562
1563
  "If-Unmodified-Since-Version": self.request.headers["last-modified-version"]
1563
1564
  }
1564
- headers.update(self.default_headers())
1565
- return requests.delete(
1565
+ return self.client.delete(
1566
1566
  url=build_url(
1567
1567
  self.endpoint,
1568
1568
  "/{t}/{u}/tags".format(t=self.library_type, u=self.library_id),
@@ -1603,8 +1603,7 @@ class Zotero:
1603
1603
  ),
1604
1604
  )
1605
1605
  headers = {"If-Unmodified-Since-Version": str(modified)}
1606
- headers.update(self.default_headers())
1607
- return requests.delete(url=url, params=params, headers=headers)
1606
+ return self.client.delete(url=url, params=params, headers=headers)
1608
1607
 
1609
1608
  @backoff_check
1610
1609
  def delete_collection(self, payload, last_modified=None):
@@ -1638,8 +1637,7 @@ class Zotero:
1638
1637
  ),
1639
1638
  )
1640
1639
  headers = {"If-Unmodified-Since-Version": str(modified)}
1641
- headers.update(self.default_headers())
1642
- return requests.delete(url=url, params=params, headers=headers)
1640
+ return self.client.delete(url=url, params=params, headers=headers)
1643
1641
 
1644
1642
 
1645
1643
  def error_handler(zot, req, exc=None):
@@ -1658,13 +1656,7 @@ def error_handler(zot, req, exc=None):
1658
1656
 
1659
1657
  def err_msg(req):
1660
1658
  """Return a nicely-formatted error message"""
1661
- return "\nCode: %s\nURL: %s\nMethod: %s\nResponse: %s" % (
1662
- req.status_code,
1663
- # error.msg,
1664
- req.url,
1665
- req.request.method,
1666
- req.text,
1667
- )
1659
+ return f"\nCode: {req.status_code}\nURL: {str(req.url)}\nMethod: {req.request.method}\nResponse: {req.text}"
1668
1660
 
1669
1661
  if error_codes.get(req.status_code):
1670
1662
  # check to see whether its 429
@@ -1833,12 +1825,11 @@ class SavedSearch:
1833
1825
  for condition in conditions:
1834
1826
  if set(condition.keys()) != allowed_keys:
1835
1827
  raise ze.ParamNotPassed(
1836
- "Keys must be all of: %s" % ", ".join(self.searchkeys)
1828
+ f"Keys must be all of: {', '.join(self.searchkeys)}"
1837
1829
  )
1838
1830
  if condition.get("operator") not in operators_set:
1839
1831
  raise ze.ParamNotPassed(
1840
- "You have specified an unknown operator: %s"
1841
- % condition.get("operator")
1832
+ f"You have specified an unknown operator: {condition.get('operator')}"
1842
1833
  )
1843
1834
  # dict keys of allowed operators for the current condition
1844
1835
  permitted_operators = self.conditions_operators.get(
@@ -1850,12 +1841,7 @@ class SavedSearch:
1850
1841
  )
1851
1842
  if condition.get("operator") not in permitted_operators_list:
1852
1843
  raise ze.ParamNotPassed(
1853
- "You may not use the '%s' operator when selecting the '%s' condition. \nAllowed operators: %s"
1854
- % (
1855
- condition.get("operator"),
1856
- condition.get("condition"),
1857
- ", ".join(list(permitted_operators_list)),
1858
- )
1844
+ f"You may not use the '{condition.get('operator')}' operator when selecting the '{condition.get('condition')}' condition. \nAllowed operators: {', '.join(list(permitted_operators_list))}"
1859
1845
  )
1860
1846
 
1861
1847
 
@@ -1893,14 +1879,12 @@ class Zupload:
1893
1879
  pass
1894
1880
  except IOError:
1895
1881
  raise ze.FileDoesNotExist(
1896
- "The file at %s couldn't be opened or found."
1897
- % str(self.basedir.joinpath(templt["filename"]))
1882
+ f"The file at {str(self.basedir.joinpath(templt['filename']))} couldn't be opened or found."
1898
1883
  )
1899
1884
  # no point in continuing if the file isn't a file
1900
1885
  else:
1901
1886
  raise ze.FileDoesNotExist(
1902
- "The file at %s couldn't be opened or found."
1903
- % str(self.basedir.joinpath(templt["filename"]))
1887
+ f"The file at {str(self.basedir.joinpath(templt['filename']))} couldn't be opened or found."
1904
1888
  )
1905
1889
 
1906
1890
  def _create_prelim(self):
@@ -1917,26 +1901,25 @@ class Zupload:
1917
1901
  liblevel = "/{t}/{u}/items"
1918
1902
  # Create one or more new attachments
1919
1903
  headers = {"Zotero-Write-Token": token(), "Content-Type": "application/json"}
1920
- headers.update(self.zinstance.default_headers())
1921
1904
  # If we have a Parent ID, add it as a parentItem
1922
1905
  if self.parentid:
1923
1906
  for child in self.payload:
1924
1907
  child["parentItem"] = self.parentid
1925
1908
  to_send = json.dumps(self.payload)
1926
1909
  self.zinstance._check_backoff()
1927
- req = requests.post(
1910
+ req = self.client.post(
1928
1911
  url=build_url(
1929
1912
  self.zinstance.endpoint,
1930
1913
  liblevel.format(
1931
1914
  t=self.zinstance.library_type, u=self.zinstance.library_id
1932
1915
  ),
1933
1916
  ),
1934
- data=to_send,
1917
+ content=to_send,
1935
1918
  headers=headers,
1936
1919
  )
1937
1920
  try:
1938
1921
  req.raise_for_status()
1939
- except requests.exceptions.HTTPError as exc:
1922
+ except httpx.HTTPError as exc:
1940
1923
  error_handler(self.zinstance, req, exc)
1941
1924
  backoff = req.headers.get("backoff") or req.headers.get("retry-after")
1942
1925
  if backoff:
@@ -1961,7 +1944,6 @@ class Zupload:
1961
1944
  else:
1962
1945
  # docs specify that for existing file we use this
1963
1946
  auth_headers["If-Match"] = md5
1964
- auth_headers.update(self.zinstance.default_headers())
1965
1947
  data = {
1966
1948
  "md5": digest.hexdigest(),
1967
1949
  "filename": os.path.basename(attachment),
@@ -1972,7 +1954,7 @@ class Zupload:
1972
1954
  "params": 1,
1973
1955
  }
1974
1956
  self.zinstance._check_backoff()
1975
- auth_req = requests.post(
1957
+ auth_req = self.zinstance.client.post(
1976
1958
  url=build_url(
1977
1959
  self.zinstance.endpoint,
1978
1960
  "/{t}/{u}/items/{i}/file".format(
@@ -1981,12 +1963,12 @@ class Zupload:
1981
1963
  i=reg_key,
1982
1964
  ),
1983
1965
  ),
1984
- data=data,
1966
+ content=data,
1985
1967
  headers=auth_headers,
1986
1968
  )
1987
1969
  try:
1988
1970
  auth_req.raise_for_status()
1989
- except requests.exceptions.HTTPError as exc:
1971
+ except httpx.HTTPError as exc:
1990
1972
  error_handler(self.zinstance, auth_req, exc)
1991
1973
  backoff = auth_req.headers.get("backoff") or auth_req.headers.get("retry-after")
1992
1974
  if backoff:
@@ -2009,16 +1991,16 @@ class Zupload:
2009
1991
  upload_pairs = tuple(upload_list)
2010
1992
  try:
2011
1993
  self.zinstance._check_backoff()
2012
- upload = requests.post(
1994
+ upload = self.client.post(
2013
1995
  url=authdata["url"],
2014
1996
  files=upload_pairs,
2015
- headers={"User-Agent": "Pyzotero/%s" % pz.__version__},
1997
+ headers={"User-Agent": f"Pyzotero/{pz.__version__}"},
2016
1998
  )
2017
- except requests.exceptions.ConnectionError:
1999
+ except httpx.ConnectionError:
2018
2000
  raise ze.UploadError("ConnectionError")
2019
2001
  try:
2020
2002
  upload.raise_for_status()
2021
- except requests.exceptions.HTTPError as exc:
2003
+ except httpx.HTTPError as exc:
2022
2004
  error_handler(self.zinstance, upload, exc)
2023
2005
  backoff = upload.headers.get("backoff") or upload.headers.get("retry-after")
2024
2006
  if backoff:
@@ -2034,10 +2016,9 @@ class Zupload:
2034
2016
  "Content-Type": "application/x-www-form-urlencoded",
2035
2017
  "If-None-Match": "*",
2036
2018
  }
2037
- reg_headers.update(self.zinstance.default_headers())
2038
2019
  reg_data = {"upload": authdata.get("uploadKey")}
2039
2020
  self.zinstance._check_backoff()
2040
- upload_reg = requests.post(
2021
+ upload_reg = self.zinstane.client.post(
2041
2022
  url=build_url(
2042
2023
  self.zinstance.endpoint,
2043
2024
  "/{t}/{u}/items/{i}/file".format(
@@ -2046,12 +2027,12 @@ class Zupload:
2046
2027
  i=reg_key,
2047
2028
  ),
2048
2029
  ),
2049
- data=reg_data,
2030
+ content=reg_data,
2050
2031
  headers=dict(reg_headers),
2051
2032
  )
2052
2033
  try:
2053
2034
  upload_reg.raise_for_status()
2054
- except requests.exceptions.HTTPError as exc:
2035
+ except httpx.HTTPError as exc:
2055
2036
  error_handler(self.zinstance, upload_reg, exc)
2056
2037
  backoff = upload_reg.headers.get("backoff") or upload_reg.headers.get(
2057
2038
  "retry-after"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyzotero
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: Python wrapper for the Zotero API
5
5
  Author-email: Stephan Hügel <urschrei@gmail.com>
6
6
  License: # Blue Oak Model License
@@ -61,12 +61,13 @@ License-File: LICENSE.md
61
61
  License-File: AUTHORS
62
62
  Requires-Dist: feedparser>=6.0.11
63
63
  Requires-Dist: pytz
64
- Requires-Dist: requests>=2.21.0
65
64
  Requires-Dist: bibtexparser
65
+ Requires-Dist: httpx>=0.28.1
66
66
  Provides-Extra: test
67
67
  Requires-Dist: pytest>=7.4.2; extra == "test"
68
68
  Requires-Dist: httpretty; extra == "test"
69
69
  Requires-Dist: python-dateutil; extra == "test"
70
+ Requires-Dist: ipython; extra == "test"
70
71
 
71
72
  [![Supported Python versions](https://img.shields.io/pypi/pyversions/Pyzotero.svg?style=flat)](https://pypi.python.org/pypi/Pyzotero/) [![Docs](https://readthedocs.org/projects/pyzotero/badge/?version=latest)](http://pyzotero.readthedocs.org/en/latest/?badge=latest) [![PyPI Version](https://img.shields.io/pypi/v/Pyzotero.svg)](https://pypi.python.org/pypi/Pyzotero) [![Anaconda-Server Badge](https://anaconda.org/conda-forge/pyzotero/badges/version.svg)](https://anaconda.org/conda-forge/pyzotero) [![Downloads](https://pepy.tech/badge/pyzotero)](https://pepy.tech/project/pyzotero)
72
73
 
@@ -86,7 +87,7 @@ Then:
86
87
 
87
88
  ``` python
88
89
  from pyzotero import zotero
89
- zot = zotero.Zotero(library_id, library_type, api_key)
90
+ zot = zotero.Zotero(library_id, library_type, api_key) # local=True for read access to local Zotero
90
91
  items = zot.top(limit=5)
91
92
  # we've retrieved the latest five top-level items in our library
92
93
  # we can print each item's item type and ID
@@ -109,7 +110,7 @@ Example:
109
110
  ``` bash
110
111
  git clone git://github.com/urschrei/pyzotero.git
111
112
  cd pyzotero
112
- git checkout dev
113
+ git checkout main
113
114
  pip install .
114
115
  ```
115
116
 
@@ -119,7 +120,7 @@ Run `pytest .` from the top-level directory.
119
120
 
120
121
  ## Issues
121
122
 
122
- The latest commits can be found on the [dev branch][9], although new features are currently rare. If you encounter an error, please open an issue.
123
+ The latest commits can be found on the [main branch][9], although new features are currently rare. If you encounter an error, please open an issue.
123
124
 
124
125
  ## Pull Requests
125
126
 
@@ -145,14 +146,14 @@ A sample citation (APA 6th edition) might look like:
145
146
 
146
147
  # License
147
148
 
148
- Pyzotero is licensed under the [Blue Oak Model Licence 1.0.0][8]. See [LICENCE.md](LICENCE.md) for details.
149
+ Pyzotero is licensed under the [Blue Oak Model Licence 1.0.0][8]. See [LICENSE.md](LICENSE.md) for details.
149
150
 
150
151
  [1]: https://www.zotero.org/support/dev/web_api/v3/start
151
152
  [2]: https://www.zotero.org/settings/keys/new
152
153
  [3]: http://pyzotero.readthedocs.org/en/latest/
153
154
  [7]: https://nose2.readthedocs.io/en/latest/
154
155
  [8]: https://opensource.org/license/blue-oak-model-license
155
- [9]: https://github.com/urschrei/pyzotero/tree/dev
156
+ [9]: https://github.com/urschrei/pyzotero/tree/main
156
157
  [10]: http://www.pip-installer.org/en/latest/index.html
157
158
  † This isn't strictly true: you only need an API key for personal libraries and non-public group libraries.
158
159
 
@@ -0,0 +1,10 @@
1
+ _version.py,sha256=ay9A4GSmtr3NioHirRgXvWfXtjwRjzXIO_WPuFobCoI,411
2
+ pyzotero/__init__.py,sha256=5QI4Jou9L-YJAf_oN9TgRXVKgt_Unc39oADo2Ch8bLI,243
3
+ pyzotero/zotero.py,sha256=jnfqgwbn2nMxUnUNG98dm--AlE6CaLyJCuOizXOvJz4,75376
4
+ pyzotero/zotero_errors.py,sha256=UPhAmf2K05cnoeIl2wjufWQedepg7vBKb-ShU0TdlL4,2582
5
+ pyzotero-1.6.2.dist-info/AUTHORS,sha256=ZMicxg7lRScOYbxzMPznlzMbmrFIUIHwg-NvljEMbRQ,110
6
+ pyzotero-1.6.2.dist-info/LICENSE.md,sha256=bhy1CPMj1zWffD9YifFmSeBzPylsrhb1qP8OCEx5Etw,1550
7
+ pyzotero-1.6.2.dist-info/METADATA,sha256=aFEP8H1c0A7W75pQO_qBh6HNNcqDmnlRZ47llubffwc,7289
8
+ pyzotero-1.6.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
9
+ pyzotero-1.6.2.dist-info/top_level.txt,sha256=BOPNkPk5VtNDCy_li7Xftx6k0zG8STGxh-KgckcxLEw,18
10
+ pyzotero-1.6.2.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- _version.py,sha256=edFVVa8HpVPfLqL2y6CKtViSqJREfmXxInA-HCy-134,411
2
- pyzotero/__init__.py,sha256=5QI4Jou9L-YJAf_oN9TgRXVKgt_Unc39oADo2Ch8bLI,243
3
- pyzotero/zotero.py,sha256=Qk3vuJxv4rbD_Q0sg_8nc8QXVAboJ9_eZWrLuJLkW_4,76313
4
- pyzotero/zotero_errors.py,sha256=UPhAmf2K05cnoeIl2wjufWQedepg7vBKb-ShU0TdlL4,2582
5
- pyzotero-1.6.1.dist-info/AUTHORS,sha256=ZMicxg7lRScOYbxzMPznlzMbmrFIUIHwg-NvljEMbRQ,110
6
- pyzotero-1.6.1.dist-info/LICENSE.md,sha256=bhy1CPMj1zWffD9YifFmSeBzPylsrhb1qP8OCEx5Etw,1550
7
- pyzotero-1.6.1.dist-info/METADATA,sha256=TCxweUVtZbjMDFDrFtZMDzKCFm4j8VMNkwzbJ2kJUZ0,7204
8
- pyzotero-1.6.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
9
- pyzotero-1.6.1.dist-info/top_level.txt,sha256=BOPNkPk5VtNDCy_li7Xftx6k0zG8STGxh-KgckcxLEw,18
10
- pyzotero-1.6.1.dist-info/RECORD,,