pyzotero 1.6.15__tar.gz → 1.6.16__tar.gz

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.
Files changed (33) hide show
  1. {pyzotero-1.6.15 → pyzotero-1.6.16}/PKG-INFO +10 -8
  2. {pyzotero-1.6.15 → pyzotero-1.6.16}/README.md +9 -7
  3. {pyzotero-1.6.15 → pyzotero-1.6.16}/doc/index.rst +1 -1
  4. {pyzotero-1.6.15 → pyzotero-1.6.16}/pyproject.toml +1 -1
  5. {pyzotero-1.6.15 → pyzotero-1.6.16}/src/pyzotero/zotero.py +14 -5
  6. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/test_zotero.py +33 -10
  7. {pyzotero-1.6.15 → pyzotero-1.6.16}/LICENSE.md +0 -0
  8. {pyzotero-1.6.15 → pyzotero-1.6.16}/doc/Makefile +0 -0
  9. {pyzotero-1.6.15 → pyzotero-1.6.16}/doc/_templates/layout.html +0 -0
  10. {pyzotero-1.6.15 → pyzotero-1.6.16}/doc/cat.png +0 -0
  11. {pyzotero-1.6.15 → pyzotero-1.6.16}/doc/conf.py +0 -0
  12. {pyzotero-1.6.15 → pyzotero-1.6.16}/src/pyzotero/__init__.py +0 -0
  13. {pyzotero-1.6.15 → pyzotero-1.6.16}/src/pyzotero/filetransport.py +0 -0
  14. {pyzotero-1.6.15 → pyzotero-1.6.16}/src/pyzotero/zotero_errors.py +0 -0
  15. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/__init__.py +0 -0
  16. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/attachments_doc.json +0 -0
  17. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/citation_doc.xml +0 -0
  18. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/collection_doc.json +0 -0
  19. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/collection_tags.json +0 -0
  20. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/collection_versions.json +0 -0
  21. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/collections_doc.json +0 -0
  22. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/creation_doc.json +0 -0
  23. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/groups_doc.json +0 -0
  24. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/item_doc.json +0 -0
  25. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/item_fields.json +0 -0
  26. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/item_file.pdf +0 -0
  27. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/item_template.json +0 -0
  28. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/item_types.json +0 -0
  29. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/item_versions.json +0 -0
  30. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/items_doc.json +0 -0
  31. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/keys_doc.txt +0 -0
  32. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/api_responses/tags_doc.json +0 -0
  33. {pyzotero-1.6.15 → pyzotero-1.6.16}/tests/test_async.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyzotero
3
- Version: 1.6.15
3
+ Version: 1.6.16
4
4
  Summary: Python wrapper for the Zotero API
5
5
  Keywords: Zotero,DH
6
6
  Author: Stephan Hügel
@@ -86,7 +86,7 @@ items = zot.top(limit=5)
86
86
  # we've retrieved the latest five top-level items in our library
87
87
  # we can print each item's item type and ID
88
88
  for item in items:
89
- print('Item: %s | Key: %s' % (item['data']['itemType'], item['data']['key']))
89
+ print(f"Item: {item['data']['itemType']} | Key: {item['data']['key']}")
90
90
  ```
91
91
 
92
92
  # Documentation
@@ -95,8 +95,9 @@ Full documentation of available Pyzotero methods, code examples, and sample outp
95
95
 
96
96
  # Installation
97
97
 
98
- * Using [pip][10]: `pip install pyzotero` (it's available as a wheel, and is tested on Python 3.7 and up)
99
- * Using Anaconda:`conda config --add channels conda-forge && conda install pyzotero`
98
+ * Using [uv][11]: `uv add pyzotero`
99
+ * Using [pip][10]: `pip install pyzotero`
100
+ * Using Anaconda:`conda install conda-forge::pyzotero`
100
101
  * From a local clone, if you wish to install Pyzotero from a specific branch:
101
102
 
102
103
  Example:
@@ -105,12 +106,13 @@ Example:
105
106
  git clone git://github.com/urschrei/pyzotero.git
106
107
  cd pyzotero
107
108
  git checkout main
108
- pip install .
109
+ # specify --dev if you're planning on running tests
110
+ uv sync
109
111
  ```
110
112
 
111
113
  ## Testing
112
114
 
113
- Run `pytest .` from the top-level directory.
115
+ Run `pytest .` from the top-level directory. This requires the `dev` dependency group to be installed: `uv sync --dev` / `pip install --group dev`
114
116
 
115
117
  ## Issues
116
118
 
@@ -118,14 +120,13 @@ The latest commits can be found on the [main branch][9], although new features a
118
120
 
119
121
  ## Pull Requests
120
122
 
121
- Pull requests are welcomed. Please read the [contribution guidelines](CONTRIBUTING.md). In particular, please **base your PR on the `dev` branch**.
123
+ Pull requests are welcomed. Please read the [contribution guidelines](CONTRIBUTING.md). In particular, please **base your PR on the `main` branch**.
122
124
 
123
125
  ## Versioning
124
126
 
125
127
  As of v1.0.0, Pyzotero is versioned according to [Semver](http://semver.org); version increments are performed as follows:
126
128
 
127
129
 
128
-
129
130
  1. MAJOR version will increment with incompatible API changes,
130
131
  2. MINOR version will increment when functionality is added in a backwards-compatible manner, and
131
132
  3. PATCH version will increment with backwards-compatible bug fixes.
@@ -149,5 +150,6 @@ Pyzotero is licensed under the [Blue Oak Model Licence 1.0.0][8]. See [LICENSE.m
149
150
  [8]: https://opensource.org/license/blue-oak-model-license
150
151
  [9]: https://github.com/urschrei/pyzotero/tree/main
151
152
  [10]: http://www.pip-installer.org/en/latest/index.html
153
+ [11]: https://docs.astral.sh/uv
152
154
  † This isn't strictly true: you only need an API key for personal libraries and non-public group libraries.
153
155
 
@@ -21,7 +21,7 @@ items = zot.top(limit=5)
21
21
  # we've retrieved the latest five top-level items in our library
22
22
  # we can print each item's item type and ID
23
23
  for item in items:
24
- print('Item: %s | Key: %s' % (item['data']['itemType'], item['data']['key']))
24
+ print(f"Item: {item['data']['itemType']} | Key: {item['data']['key']}")
25
25
  ```
26
26
 
27
27
  # Documentation
@@ -30,8 +30,9 @@ Full documentation of available Pyzotero methods, code examples, and sample outp
30
30
 
31
31
  # Installation
32
32
 
33
- * Using [pip][10]: `pip install pyzotero` (it's available as a wheel, and is tested on Python 3.7 and up)
34
- * Using Anaconda:`conda config --add channels conda-forge && conda install pyzotero`
33
+ * Using [uv][11]: `uv add pyzotero`
34
+ * Using [pip][10]: `pip install pyzotero`
35
+ * Using Anaconda:`conda install conda-forge::pyzotero`
35
36
  * From a local clone, if you wish to install Pyzotero from a specific branch:
36
37
 
37
38
  Example:
@@ -40,12 +41,13 @@ Example:
40
41
  git clone git://github.com/urschrei/pyzotero.git
41
42
  cd pyzotero
42
43
  git checkout main
43
- pip install .
44
+ # specify --dev if you're planning on running tests
45
+ uv sync
44
46
  ```
45
47
 
46
48
  ## Testing
47
49
 
48
- Run `pytest .` from the top-level directory.
50
+ Run `pytest .` from the top-level directory. This requires the `dev` dependency group to be installed: `uv sync --dev` / `pip install --group dev`
49
51
 
50
52
  ## Issues
51
53
 
@@ -53,14 +55,13 @@ The latest commits can be found on the [main branch][9], although new features a
53
55
 
54
56
  ## Pull Requests
55
57
 
56
- Pull requests are welcomed. Please read the [contribution guidelines](CONTRIBUTING.md). In particular, please **base your PR on the `dev` branch**.
58
+ Pull requests are welcomed. Please read the [contribution guidelines](CONTRIBUTING.md). In particular, please **base your PR on the `main` branch**.
57
59
 
58
60
  ## Versioning
59
61
 
60
62
  As of v1.0.0, Pyzotero is versioned according to [Semver](http://semver.org); version increments are performed as follows:
61
63
 
62
64
 
63
-
64
65
  1. MAJOR version will increment with incompatible API changes,
65
66
  2. MINOR version will increment when functionality is added in a backwards-compatible manner, and
66
67
  3. PATCH version will increment with backwards-compatible bug fixes.
@@ -84,5 +85,6 @@ Pyzotero is licensed under the [Blue Oak Model Licence 1.0.0][8]. See [LICENSE.m
84
85
  [8]: https://opensource.org/license/blue-oak-model-license
85
86
  [9]: https://github.com/urschrei/pyzotero/tree/main
86
87
  [10]: http://www.pip-installer.org/en/latest/index.html
88
+ [11]: https://docs.astral.sh/uv
87
89
  † This isn't strictly true: you only need an API key for personal libraries and non-public group libraries.
88
90
 
@@ -37,7 +37,7 @@ Getting started (short version)
37
37
  # we've retrieved the latest five top-level items in our library
38
38
  # we can print each item's item type and ID
39
39
  for item in items:
40
- print('Item Type: %s | Key: %s' % (item['data']['itemType'], item['data']['key']))
40
+ print(f"Item: {item['data']['itemType']} | Key: {item['data']['key']}")
41
41
 
42
42
  Refer to the :ref:`read` and :ref:`write`.
43
43
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyzotero"
3
- version = "1.6.15"
3
+ version = "1.6.16"
4
4
  description = "Python wrapper for the Zotero API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -46,6 +46,7 @@ timeout = 30
46
46
  NOT_MODIFIED = 304
47
47
  ONE_HOUR = 3600
48
48
  DEFAULT_NUM_ITEMS = 50
49
+ DEFAULT_ITEM_LIMIT = 100
49
50
  TOO_MANY_REQUESTS = 429
50
51
 
51
52
 
@@ -470,7 +471,7 @@ class Zotero:
470
471
  params = {}
471
472
  if not self.url_params:
472
473
  self.url_params = {}
473
- merged_params = {**params, **self.url_params}
474
+ merged_params = {**self.url_params, **params}
474
475
  # our incoming url might be from the "links" dict, in which case it will contain url parameters.
475
476
  # Unfortunately, httpx doesn't like to merge query paramaters in the url string and passed params
476
477
  # so we strip the url params, combining them with our existing url_params
@@ -580,7 +581,13 @@ class Zotero:
580
581
 
581
582
  Also ensure that only valid format/content combinations are requested
582
583
  """
583
- self.url_params = None
584
+ # Preserve constructor-level parameters (like locale) while allowing method-level overrides
585
+ if self.url_params is None:
586
+ self.url_params = {}
587
+
588
+ # Store existing params to preserve things like locale
589
+ preserved_params = self.url_params.copy()
590
+
584
591
  # we want JSON by default
585
592
  if not params.get("format"):
586
593
  params["format"] = "json"
@@ -589,7 +596,7 @@ class Zotero:
589
596
  params["format"] = "atom"
590
597
  # TODO: rewrite format=atom, content=json request
591
598
  if "limit" not in params or params.get("limit") == 0:
592
- params["limit"] = 100
599
+ params["limit"] = DEFAULT_ITEM_LIMIT
593
600
  # Need ability to request arbitrary number of results for version
594
601
  # response
595
602
  # -1 value is hack that works with current version
@@ -597,8 +604,10 @@ class Zotero:
597
604
  del params["limit"]
598
605
  # bib format can't have a limit
599
606
  if params.get("format") == "bib":
600
- del params["limit"]
601
- self.url_params = params
607
+ params.pop("limit", None)
608
+
609
+ # Merge preserved params with new params (new params override existing ones)
610
+ self.url_params = {**preserved_params, **params}
602
611
 
603
612
  def _build_query(self, query_string, no_params=False):
604
613
  """Set request parameters. Will always add the user ID if it hasn't
@@ -21,9 +21,10 @@ from httpretty import HTTPretty
21
21
 
22
22
  try:
23
23
  from pyzotero.pyzotero import zotero as z
24
+ from pyzotero.pyzotero.zotero import DEFAULT_ITEM_LIMIT
24
25
  except ModuleNotFoundError:
25
26
  from pyzotero import zotero as z
26
-
27
+ from pyzotero.zotero import DEFAULT_ITEM_LIMIT
27
28
  from urllib.parse import urlencode
28
29
 
29
30
 
@@ -88,7 +89,7 @@ class ZoteroTests(unittest.TestCase):
88
89
  zot = z.Zotero("myuserID", "user", "myuserkey")
89
90
  zot.add_parameters(limit=0, start=7)
90
91
  self.assertEqual(
91
- parse_qs("start=7&limit=100&format=json"),
92
+ parse_qs(f"start=7&limit={DEFAULT_ITEM_LIMIT}&format=json"),
92
93
  parse_qs(urlencode(zot.url_params, doseq=True)),
93
94
  )
94
95
 
@@ -104,7 +105,26 @@ class ZoteroTests(unittest.TestCase):
104
105
  zot = z.Zotero("myuserID", "user", "myuserkey")
105
106
  _ = zot.items()
106
107
  req = zot.request
107
- self.assertEqual(str(req.url).find("locale"), 44)
108
+ self.assertIn("locale=en-US", str(req.url))
109
+
110
+ @httpretty.activate
111
+ def testLocalePreservedWithMethodParams(self):
112
+ """Should preserve locale when methods provide their own parameters"""
113
+ HTTPretty.register_uri(
114
+ HTTPretty.GET,
115
+ "https://api.zotero.org/users/myuserID/items/top",
116
+ content_type="application/json",
117
+ body=self.items_doc,
118
+ )
119
+ # Test with non-default locale
120
+ zot = z.Zotero("myuserID", "user", "myuserkey", locale="de-DE")
121
+ # Call top() with limit which internally adds parameters
122
+ _ = zot.top(limit=1)
123
+ req = zot.request
124
+ # Check that locale is preserved in the URL
125
+ self.assertIn("locale=de-DE", str(req.url))
126
+ # Also verify the method parameter is present
127
+ self.assertIn("limit=1", str(req.url))
108
128
 
109
129
  @httpretty.activate
110
130
  def testRequestBuilderLimitNone(self):
@@ -368,9 +388,13 @@ class ZoteroTests(unittest.TestCase):
368
388
  body=self.tags_doc,
369
389
  )
370
390
  _ = zot.tags(limit=1)
371
- self.assertEqual(
372
- "https://api.zotero.org/users/myuserID/tags?locale=en-US&limit=1&format=json",
373
- zot.request.url,
391
+ # Check that all expected parameters are present
392
+ url_str = str(zot.request.url)
393
+ self.assertIn("locale=en-US", url_str)
394
+ self.assertIn("limit=1", url_str)
395
+ self.assertIn("format=json", url_str)
396
+ self.assertTrue(
397
+ url_str.startswith("https://api.zotero.org/users/myuserID/tags?")
374
398
  )
375
399
 
376
400
  @httpretty.activate
@@ -408,15 +432,14 @@ class ZoteroTests(unittest.TestCase):
408
432
  self.assertEqual("smart_cities", groups_data[0]["data"]["name"])
409
433
 
410
434
  def testParamsReset(self):
411
- """Should successfully reset URL parameters after a query string
412
- is built
413
- """
435
+ """Should preserve existing URL parameters when add_parameters is called multiple times"""
414
436
  zot = z.Zotero("myuserID", "user", "myuserkey")
415
437
  zot.add_parameters(start=5, limit=10)
416
438
  zot._build_query("/whatever")
417
439
  zot.add_parameters(start=2)
440
+ # Should get default limit=100 since no limit specified in second call
418
441
  self.assertEqual(
419
- parse_qs("start=2&format=json&limit=100"),
442
+ parse_qs(f"start=2&format=json&limit={DEFAULT_ITEM_LIMIT}"),
420
443
  parse_qs(urlencode(zot.url_params, doseq=True)),
421
444
  )
422
445
 
File without changes
File without changes
File without changes
File without changes
File without changes