pyzotero 1.5.28__tar.gz → 1.6.1__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.
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.github/pull_request_template.md +4 -4
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.github/workflows/tests.yml +1 -1
- {pyzotero-1.5.28/src/pyzotero.egg-info → pyzotero-1.6.1}/PKG-INFO +1 -1
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/_version.py +2 -2
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero/zotero.py +43 -10
- {pyzotero-1.5.28 → pyzotero-1.6.1/src/pyzotero.egg-info}/PKG-INFO +1 -1
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/test_zotero.py +15 -1
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.coveragerc +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.github/ISSUE_TEMPLATE/usage_question.md +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.github/dependabot.yml +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.gitignore +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/.readthedocs.yaml +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/AUTHORS +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/CITATION.cff +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/CONTRIBUTING.md +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/CONTRIBUTORS.md +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/LICENSE.md +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/README.md +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/__init__.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/doc/Makefile +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/doc/_templates/layout.html +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/doc/cat.png +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/doc/conf.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/doc/index.rst +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/dump_contributors.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/pyproject.toml +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/setup.cfg +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/setup.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero/__init__.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero/zotero_errors.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero.egg-info/SOURCES.txt +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero.egg-info/dependency_links.txt +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero.egg-info/requires.txt +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/src/pyzotero.egg-info/top_level.txt +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/__init__.py +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/attachments_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/citation_doc.xml +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/collection_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/collection_tags.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/collection_versions.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/collections_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/creation_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/groups_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/item_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/item_fields.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/item_file.pdf +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/item_template.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/item_types.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/item_versions.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/items_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/keys_doc.txt +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/tests/api_responses/tags_doc.json +0 -0
- {pyzotero-1.5.28 → pyzotero-1.6.1}/uv.lock +0 -0
|
@@ -2,11 +2,11 @@ Thanks for opening a PR. Please read the following:
|
|
|
2
2
|
|
|
3
3
|
- **Base your changes on the `dev` branch**
|
|
4
4
|
- If necessary, rebase against `dev` before opening a pull request
|
|
5
|
-
- Follow [PEP 8](http://www.python.org/dev/peps/pep-0008/). I
|
|
5
|
+
- Follow [PEP 8](http://www.python.org/dev/peps/pep-0008/). I run [`ruff`](https://docs.astral.sh/ruff/) against the codebase and perhaps you should, too
|
|
6
6
|
- Use spaces for indentation, and ensure that all methods have a proper docstring. **Please don't use Doctest**
|
|
7
7
|
- If at all possible, don't add dependencies
|
|
8
8
|
- If it's unavoidable, ensure that the dependency is maintained, and supported
|
|
9
|
-
- Ensure that you add your dependency to [
|
|
9
|
+
- Ensure that you add your dependency to [pyproject.toml](pyproject.toml)
|
|
10
10
|
- Run the tests, and ensure that they pass. If you're adding a feature, **you must add tests that exercise it**
|
|
11
11
|
- If your pull request is a feature, **document it**
|
|
12
12
|
- One feature per pull request
|
|
@@ -14,5 +14,5 @@ Thanks for opening a PR. Please read the following:
|
|
|
14
14
|
- If in doubt, comment your code.
|
|
15
15
|
|
|
16
16
|
## License of Contributed Code
|
|
17
|
-
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be
|
|
18
|
-
Please note that pull requests with licenses that are more restrictive than or otherwise incompatible with the
|
|
17
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be licensed under the Blua Oak Model License 1.0, without any additional terms or conditions.
|
|
18
|
+
Please note that pull requests with licenses that are more restrictive than or otherwise incompatible with the license will not be accepted.
|
|
@@ -24,8 +24,16 @@ import uuid
|
|
|
24
24
|
import zipfile
|
|
25
25
|
from collections import OrderedDict
|
|
26
26
|
from functools import wraps
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from urllib.parse import
|
|
27
|
+
from pathlib import Path, PurePosixPath
|
|
28
|
+
from urllib.parse import (
|
|
29
|
+
parse_qs,
|
|
30
|
+
parse_qsl,
|
|
31
|
+
quote,
|
|
32
|
+
unquote,
|
|
33
|
+
urlencode,
|
|
34
|
+
urlparse,
|
|
35
|
+
urlunparse,
|
|
36
|
+
)
|
|
29
37
|
|
|
30
38
|
import bibtexparser
|
|
31
39
|
import feedparser
|
|
@@ -272,8 +280,10 @@ class Zotero:
|
|
|
272
280
|
"""Store Zotero credentials"""
|
|
273
281
|
if not local:
|
|
274
282
|
self.endpoint = "https://api.zotero.org"
|
|
283
|
+
self.local = False
|
|
275
284
|
else:
|
|
276
285
|
self.endpoint = "http://localhost:23119/api"
|
|
286
|
+
self.local = True
|
|
277
287
|
if library_id and library_type:
|
|
278
288
|
self.library_id = library_id
|
|
279
289
|
# library_type determines whether query begins w. /users or /groups
|
|
@@ -321,6 +331,22 @@ class Zotero:
|
|
|
321
331
|
self.backoff = False
|
|
322
332
|
self.backoff_duration = 0.0
|
|
323
333
|
|
|
334
|
+
def _check_for_component(self, url, component):
|
|
335
|
+
"""Check a url path query fragment for a specific query parameter"""
|
|
336
|
+
if parse_qs(url).get(component):
|
|
337
|
+
return True
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
def _striplocal(self, url):
|
|
341
|
+
"""We need to remve the leading "/api" substring from urls if we're running in local mode"""
|
|
342
|
+
if self.local:
|
|
343
|
+
parsed = urlparse(url)
|
|
344
|
+
purepath = PurePosixPath(unquote(parsed.path))
|
|
345
|
+
newpath = "/".join(purepath.parts[2:])
|
|
346
|
+
replaced = parsed._replace(path="/" + newpath)
|
|
347
|
+
return urlunparse(replaced)
|
|
348
|
+
return url
|
|
349
|
+
|
|
324
350
|
def _set_backoff(self, duration):
|
|
325
351
|
"""
|
|
326
352
|
Set a backoff
|
|
@@ -406,10 +432,16 @@ class Zotero:
|
|
|
406
432
|
self.self_link = request
|
|
407
433
|
# ensure that we wait if there's an active backoff
|
|
408
434
|
self._check_backoff()
|
|
409
|
-
if
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
435
|
+
# don't set locale if the url already contains it
|
|
436
|
+
# we always add a locale if it's a "standalone" or first call
|
|
437
|
+
needs_locale = not self.links or not self._check_for_component(
|
|
438
|
+
self.links.get("next"), "locale"
|
|
439
|
+
)
|
|
440
|
+
if needs_locale:
|
|
441
|
+
if params:
|
|
442
|
+
params["locale"] = self.locale
|
|
443
|
+
else:
|
|
444
|
+
params = {"locale": self.locale}
|
|
413
445
|
self.request = requests.get(
|
|
414
446
|
url=full_url, headers=self.default_headers(), params=params
|
|
415
447
|
)
|
|
@@ -845,10 +877,11 @@ class Zotero:
|
|
|
845
877
|
@retrieve
|
|
846
878
|
def follow(self):
|
|
847
879
|
"""Return the result of the call to the URL in the 'Next' link"""
|
|
848
|
-
if self.links.get("next"):
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
return
|
|
880
|
+
if n := self.links.get("next"):
|
|
881
|
+
newurl = self._striplocal(n)
|
|
882
|
+
print(newurl)
|
|
883
|
+
return newurl
|
|
884
|
+
return
|
|
852
885
|
|
|
853
886
|
def iterfollow(self):
|
|
854
887
|
"""Generator for self.follow()"""
|
|
@@ -59,7 +59,7 @@ class ZoteroTests(unittest.TestCase):
|
|
|
59
59
|
content_type="application/json",
|
|
60
60
|
body=self.items_doc,
|
|
61
61
|
)
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
def testBuildUrlCorrectHandleEndpoint(self):
|
|
64
64
|
"""url should be concat correctly by build_url"""
|
|
65
65
|
url = z.build_url("http://localhost:23119/api", "/users/0")
|
|
@@ -84,6 +84,20 @@ class ZoteroTests(unittest.TestCase):
|
|
|
84
84
|
parse_qs("start=7&limit=100&format=json"), parse_qs(zot.url_params)
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
+
@httpretty.activate
|
|
88
|
+
def testLocale(self):
|
|
89
|
+
"""Should correctly add locale to request because it's an initial request"""
|
|
90
|
+
HTTPretty.register_uri(
|
|
91
|
+
HTTPretty.GET,
|
|
92
|
+
"https://api.zotero.org/users/myuserID/items",
|
|
93
|
+
content_type="application/json",
|
|
94
|
+
body=self.item_doc,
|
|
95
|
+
)
|
|
96
|
+
zot = z.Zotero("myuserID", "user", "myuserkey")
|
|
97
|
+
_ = zot.items()
|
|
98
|
+
req = zot.request
|
|
99
|
+
self.assertEqual(req.url.find("locale"), 66)
|
|
100
|
+
|
|
87
101
|
@httpretty.activate
|
|
88
102
|
def testRequestBuilderLimitNone(self):
|
|
89
103
|
"""Should skip limit = 100 param if limit is set to None"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|