pyzotero 1.6.8__tar.gz → 1.6.10__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 (63) hide show
  1. pyzotero-1.6.10/.github/pull_request_template.md +23 -0
  2. {pyzotero-1.6.8 → pyzotero-1.6.10}/.github/workflows/tests.yml +43 -18
  3. {pyzotero-1.6.8 → pyzotero-1.6.10}/CONTRIBUTING.md +10 -9
  4. {pyzotero-1.6.8 → pyzotero-1.6.10}/CONTRIBUTORS.md +2 -0
  5. {pyzotero-1.6.8 → pyzotero-1.6.10}/PKG-INFO +5 -2
  6. {pyzotero-1.6.8 → pyzotero-1.6.10}/doc/conf.py +4 -5
  7. {pyzotero-1.6.8 → pyzotero-1.6.10}/doc/index.rst +6 -0
  8. pyzotero-1.6.10/dump_contributors.py +13 -0
  9. pyzotero-1.6.10/example/.gitignore +3 -0
  10. pyzotero-1.6.10/example/local_base_use.py +7 -0
  11. pyzotero-1.6.10/example/local_copy_pdf.py +63 -0
  12. pyzotero-1.6.10/example/local_get_item_detail.py +68 -0
  13. pyzotero-1.6.10/example/local_search_title.py +43 -0
  14. {pyzotero-1.6.8 → pyzotero-1.6.10}/pyproject.toml +46 -1
  15. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/_version.py +9 -4
  16. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero/filetransport.py +3 -3
  17. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero/zotero.py +317 -393
  18. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero/zotero_errors.py +17 -17
  19. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero.egg-info/PKG-INFO +5 -2
  20. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero.egg-info/SOURCES.txt +5 -0
  21. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero.egg-info/requires.txt +1 -0
  22. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/test_async.py +3 -0
  23. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/test_zotero.py +10 -9
  24. {pyzotero-1.6.8 → pyzotero-1.6.10}/uv.lock +89 -0
  25. pyzotero-1.6.8/.github/pull_request_template.md +0 -18
  26. pyzotero-1.6.8/dump_contributors.py +0 -14
  27. {pyzotero-1.6.8 → pyzotero-1.6.10}/.coveragerc +0 -0
  28. {pyzotero-1.6.8 → pyzotero-1.6.10}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {pyzotero-1.6.8 → pyzotero-1.6.10}/.github/ISSUE_TEMPLATE/usage_question.md +0 -0
  30. {pyzotero-1.6.8 → pyzotero-1.6.10}/.github/dependabot.yml +0 -0
  31. {pyzotero-1.6.8 → pyzotero-1.6.10}/.gitignore +0 -0
  32. {pyzotero-1.6.8 → pyzotero-1.6.10}/.readthedocs.yaml +0 -0
  33. {pyzotero-1.6.8 → pyzotero-1.6.10}/AUTHORS +0 -0
  34. {pyzotero-1.6.8 → pyzotero-1.6.10}/CITATION.cff +0 -0
  35. {pyzotero-1.6.8 → pyzotero-1.6.10}/LICENSE.md +0 -0
  36. {pyzotero-1.6.8 → pyzotero-1.6.10}/README.md +0 -0
  37. {pyzotero-1.6.8 → pyzotero-1.6.10}/__init__.py +0 -0
  38. {pyzotero-1.6.8 → pyzotero-1.6.10}/doc/Makefile +0 -0
  39. {pyzotero-1.6.8 → pyzotero-1.6.10}/doc/_templates/layout.html +0 -0
  40. {pyzotero-1.6.8 → pyzotero-1.6.10}/doc/cat.png +0 -0
  41. {pyzotero-1.6.8 → pyzotero-1.6.10}/setup.cfg +0 -0
  42. {pyzotero-1.6.8 → pyzotero-1.6.10}/setup.py +0 -0
  43. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero/__init__.py +0 -0
  44. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero.egg-info/dependency_links.txt +0 -0
  45. {pyzotero-1.6.8 → pyzotero-1.6.10}/src/pyzotero.egg-info/top_level.txt +0 -0
  46. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/__init__.py +0 -0
  47. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/attachments_doc.json +0 -0
  48. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/citation_doc.xml +0 -0
  49. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/collection_doc.json +0 -0
  50. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/collection_tags.json +0 -0
  51. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/collection_versions.json +0 -0
  52. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/collections_doc.json +0 -0
  53. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/creation_doc.json +0 -0
  54. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/groups_doc.json +0 -0
  55. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/item_doc.json +0 -0
  56. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/item_fields.json +0 -0
  57. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/item_file.pdf +0 -0
  58. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/item_template.json +0 -0
  59. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/item_types.json +0 -0
  60. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/item_versions.json +0 -0
  61. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/items_doc.json +0 -0
  62. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/keys_doc.txt +0 -0
  63. {pyzotero-1.6.8 → pyzotero-1.6.10}/tests/api_responses/tags_doc.json +0 -0
@@ -0,0 +1,23 @@
1
+ <!-- Thanks for opening a PR. Please read the following:
2
+
3
+ - **Base your changes on the `main` branch**
4
+ - If necessary, rebase against `main` before opening a pull request
5
+ - This codebase uses Ruff. PRs that reformat code will not be accepted.
6
+ - Ensure that all methods added have a proper docstring. **Please do not use Doctest**
7
+ - If at all possible, don't add dependencies
8
+ - If it is unavoidable, you must ensure that the dependency is maintained, and supported
9
+ - Ensure that you add your dependency to [pyproject.toml](pyproject.toml)
10
+ - Run the tests and ensure that they pass. If you are adding a feature **you must add tests that exercise it**
11
+ - If your pull request is a feature **document the feature**
12
+ - One feature per pull request
13
+ - [squash](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) your commits before opening a pull request unless it makes no sense to do so
14
+ - If in doubt, comment your code.
15
+
16
+ ## License of Contributed Code
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. -->
19
+
20
+ Description of changes:
21
+ Issue reference (if applicable):
22
+
23
+ - [ ] I have read [the CONTRIBUTING doc](CONTRIBUTING.md)
@@ -3,6 +3,19 @@ name: Build wheel, release and publish on new tag
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
+ lint:
7
+ name: run Ruff
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ with:
12
+ fetch-depth: 0 # Optional, use if you use setuptools_scm
13
+ submodules: false # Optional, use if you have submodules
14
+ name: Check out repo
15
+ - uses: astral-sh/ruff-action@v3
16
+ with:
17
+ args: "check --verbose"
18
+
6
19
  build:
7
20
  name: Build and test
8
21
  runs-on: ubuntu-latest
@@ -11,27 +24,25 @@ jobs:
11
24
  python-version: [3.9, 3.10.9, 3.11, 3.12, 3.13]
12
25
 
13
26
  steps:
14
- - uses: actions/checkout@v4
27
+ - name: check out repo
28
+ uses: actions/checkout@v4
15
29
  with:
16
30
  fetch-depth: 0 # Optional, use if you use setuptools_scm
17
31
  submodules: false # Optional, use if you have submodules
18
- name: Check out repo
19
- - name: Set up Python ${{ matrix.python-version }}
20
- uses: actions/setup-python@v5
32
+
33
+ - name: Install uv and set the python version
34
+ uses: astral-sh/setup-uv@v5
21
35
  with:
22
36
  python-version: ${{ matrix.python-version }}
23
- - name: Install dependencies
24
- run: |
25
- python -m pip install --upgrade pip
26
- pip install wheel
27
- pip install .[test] --use-pep517
28
- - name: Test with pytest
29
- run: |
30
- pytest
37
+
38
+ - name: Install the project and deps
39
+ run: uv sync --all-extras --dev
40
+
41
+ - name: Run tests
42
+ run: uv run pytest
43
+
31
44
  - name: Build wheel
32
- run: |
33
- python -m pip install --upgrade pip
34
- pip wheel . -w dist --no-deps --use-pep517
45
+ run: uv build --no-sources --wheel -o dist
35
46
 
36
47
  - uses: actions/upload-artifact@v4
37
48
  name: Upload wheel as artifact
@@ -49,17 +60,31 @@ jobs:
49
60
  fetch-depth: 0 # Optional, use if you use setuptools_scm
50
61
  submodules: true # Optional, use if you have submodules
51
62
 
63
+ - name: Install uv
64
+ uses: astral-sh/setup-uv@v5
65
+
52
66
  - name: Build SDist
53
- run: pipx run build --sdist
67
+ run: uv build --no-sources --sdist -o dist
54
68
 
55
69
  - uses: actions/upload-artifact@v4
56
70
  with:
57
71
  path: dist/*.tar.gz
58
72
 
73
+ status_check:
74
+ name: All Checks
75
+ needs: [lint, build]
76
+ runs-on: ubuntu-latest
77
+ if: always()
78
+ steps:
79
+ - name: Check status
80
+ if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
81
+ run: exit 1
82
+
83
+
59
84
  release_artifacts:
60
85
  if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
61
86
  name: Release repaired and tested wheels
62
- needs: build
87
+ needs: [status_check, make_sdist]
63
88
  runs-on: ubuntu-latest
64
89
  environment: release
65
90
  permissions:
@@ -85,6 +110,6 @@ jobs:
85
110
  token: ${{ secrets.GITHUB_TOKEN }}
86
111
 
87
112
  - name: PyPI Publish
88
- uses: pypa/gh-action-pypi-publish@v1.12.3
113
+ uses: pypa/gh-action-pypi-publish@release/v1
89
114
  with:
90
115
  packages-dir: ${{ steps.download.outputs.download-path }}
@@ -1,17 +1,18 @@
1
1
  # Contributing
2
2
  Contributions are welcome. Please bear the following in mind:
3
3
 
4
- - **Base your changes on the dev branch**
5
- - If necessary, rebase against `dev` before opening a pull request
6
- - Follow [PEP 8](http://www.python.org/dev/peps/pep-0008/). I currently periodically run [`Black`](https://black.readthedocs.io/en/stable/) against the codebase and perhaps you should, too
7
- - Use spaces for indentation, and ensure that all methods have a proper docstring. **Please don't use Doctest**
4
+ - **Base your changes on the `main` branch**
5
+ - If necessary, rebase against `main` before opening a pull request
6
+ - This codebase uses Ruff, and PRs are gated on `ruff check` passing. Run `ruff check` locally before opening a PR
7
+ - PRs that reformat code will not be accepted.
8
+ - Ensure that all methods added have a proper docstring. **Please do not use Doctest**
8
9
  - If at all possible, don't add dependencies
9
- - If it's unavoidable, ensure that the dependency is maintained, and supported
10
- - Ensure that you add your dependency to [setup.py](setup.py)
11
- - Run the tests, and ensure that they pass. If you're adding a feature, **you must add tests that exercise it**
12
- - If your pull request is a feature, **document it**
10
+ - If it is unavoidable, you must ensure that the dependency is maintained, and supported
11
+ - Ensure that you add your dependency to [pyproject.toml](pyproject.toml)
12
+ - Run the tests and ensure that they pass. If you are adding a feature **you must add tests that exercise it**
13
+ - If your pull request is a feature **document the feature**
13
14
  - One feature per pull request
14
- - If possible, [squash](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) your commits before opening a pull request, unless it makes no sense to do so
15
+ - [squash](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) your commits before opening a pull request unless it makes no sense to do so
15
16
  - If in doubt, comment your code.
16
17
 
17
18
  # License of Contributed Code
@@ -14,7 +14,9 @@
14
14
  | 1 | [christianbrodbeck](https://github.com/urschrei/pyzotero/commits?author=christianbrodbeck) |
15
15
  | 1 | [egh](https://github.com/urschrei/pyzotero/commits?author=egh) |
16
16
  | 1 | [fmagin](https://github.com/urschrei/pyzotero/commits?author=fmagin) |
17
+ | 1 | [pnb](https://github.com/urschrei/pyzotero/commits?author=pnb) |
17
18
  | 1 | [porduna](https://github.com/urschrei/pyzotero/commits?author=porduna) |
18
19
  | 1 | [stakats](https://github.com/urschrei/pyzotero/commits?author=stakats) |
19
20
  | 1 | [epistemery](https://github.com/urschrei/pyzotero/commits?author=epistemery) |
21
+ | 1 | [tnajdek](https://github.com/urschrei/pyzotero/commits?author=tnajdek) |
20
22
  | 1 | [jghauser](https://github.com/urschrei/pyzotero/commits?author=jghauser) |
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pyzotero
3
- Version: 1.6.8
3
+ Version: 1.6.10
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
@@ -41,6 +41,7 @@ License: # Blue Oak Model License
41
41
 
42
42
  Project-URL: Repository, https://github.com/urschrei/pyzotero
43
43
  Project-URL: Tracker, https://github.com/urschrei/pyzotero/issues
44
+ Project-URL: documentation, https://pyzotero.readthedocs.org
44
45
  Keywords: Zotero,DH
45
46
  Classifier: Programming Language :: Python
46
47
  Classifier: Programming Language :: Python :: 3.9
@@ -69,6 +70,8 @@ Requires-Dist: httpretty; extra == "test"
69
70
  Requires-Dist: python-dateutil; extra == "test"
70
71
  Requires-Dist: ipython; extra == "test"
71
72
  Requires-Dist: pytest-asyncio; extra == "test"
73
+ Requires-Dist: pytest-cov>=6.0.0; extra == "test"
74
+ Dynamic: license-file
72
75
 
73
76
  [![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)
74
77
 
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  #
3
2
  # Pyzotero documentation build configuration file, created by
4
3
  # sphinx-quickstart on Mon Jul 4 19:52:05 2011.
@@ -11,9 +10,9 @@
11
10
  # All configuration values have a default; values that are commented out
12
11
  # serve to show the default.
13
12
 
13
+ import datetime
14
14
  import os
15
15
  import sys
16
- from datetime import date
17
16
 
18
17
  sys.path.insert(1, "..")
19
18
  import pyzotero
@@ -26,7 +25,7 @@ if os.environ.get("READTHEDOCS", "") == "True":
26
25
  html_context["READTHEDOCS"] = True
27
26
 
28
27
  author = zot.__author__
29
- current_year = date.today().year
28
+ current_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
30
29
 
31
30
  html_context = {
32
31
  "display_github": True, # Integrate GitHub
@@ -63,7 +62,7 @@ master_doc = "index"
63
62
 
64
63
  # General information about the project.
65
64
  project = "Pyzotero"
66
- copyright = "2011 %s, %s" % (current_year, author)
65
+ copyright = f"2011 - {current_year}, {author}"
67
66
 
68
67
  # The version info for the project you're documenting, acts as replacement for
69
68
  # |version| and |release|, also used in various other places throughout the
@@ -240,7 +239,7 @@ man_pages = [("index", "pyzotero", "Pyzotero Documentation", [author], 1)]
240
239
  epub_title = "Pyzotero"
241
240
  epub_author = author
242
241
  epub_publisher = author
243
- epub_copyright = "2011 %s, %s" % (current_year, author)
242
+ epub_copyright = f"2011 - {current_year}, {author}"
244
243
 
245
244
  # The language of the text. It defaults to the language option
246
245
  # or en if the language is not set.
@@ -168,6 +168,12 @@ The following methods will retrieve either user or group items, depending on the
168
168
 
169
169
  :rtype: list of dicts
170
170
 
171
+ .. py:method:: Zotero.settings([since])
172
+
173
+ Returns synced Zotero user library settings such as feeds and PDF reading progress. Use the optional ``since`` parameter to specify the retrieval of changes since a known previous library version.
174
+
175
+ :rtype: list of dicts
176
+
171
177
  .. py:method:: Zotero.count_items()
172
178
 
173
179
  Returns a count of all items in a library / group
@@ -0,0 +1,13 @@
1
+ import httpx
2
+
3
+ url = "https://api.github.com/repos/urschrei/pyzotero/contributors"
4
+ result = httpx.get(url)
5
+ result.raise_for_status()
6
+ as_dict = [d for d in result.json() if not d["login"].lower().startswith("dependabot")]
7
+ # remove me from the list
8
+ as_dict.pop(0)
9
+ header = "# This is the list of people (as distinct from [AUTHORS](AUTHORS)) who have contributed code to Pyzotero.\n\n| **Commits** | **Contributor**<br/> |\n| --- |--- |\n"
10
+ template = "| {contributions} | [{login}](https://github.com/urschrei/pyzotero/commits?author={login}) |\n"
11
+ with open("CONTRIBUTORS.md", "w", encoding="utf-8") as f:
12
+ f.write(header)
13
+ f.writelines(template.format(**dct) for dct in as_dict)
@@ -0,0 +1,3 @@
1
+ *.pdf
2
+ *.ipynb
3
+ data/
@@ -0,0 +1,7 @@
1
+ from pyzotero import zotero
2
+ zot = zotero.Zotero(library_id='000000', library_type = 'user', local=True) # local=True for read access to local Zotero
3
+ items = zot.top(limit=5)
4
+ # we've retrieved the latest five top-level items in our library
5
+ # we can print each item's item type and ID
6
+ for item in items:
7
+ print(f"Item: {item['data']['itemType']} | Key: {item['data']['key']}")
@@ -0,0 +1,63 @@
1
+ from pyzotero import zotero
2
+ from pathlib import Path
3
+
4
+ def copy_specific_pdf(item_id, output_dir, new_name=None):
5
+ """
6
+ Copy a specific PDF attachment from Zotero library to specified folder using dump()
7
+
8
+ Args:
9
+ item_id (str): Zotero item ID
10
+ output_dir (str): Path to output directory
11
+ new_name (str, optional): New filename for the PDF. If None, uses original name
12
+
13
+ """
14
+ # Initialize Zotero client with local=True
15
+ zot = zotero.Zotero(library_id='000000', library_type='user', local=True)
16
+
17
+ # Create output directory if it doesn't exist
18
+ output_path = Path(output_dir)
19
+ output_path.mkdir(parents=True, exist_ok=True)
20
+
21
+ try:
22
+ # Get the item metadata first
23
+ item = zot.item(item_id)
24
+
25
+ try:
26
+ # Get original filename or use default
27
+ default_filename = f"{item_id}.pdf"
28
+ original_filename = item['data'].get('filename', default_filename)
29
+
30
+ # Use new_name if provided, otherwise use original filename
31
+ filename = new_name if new_name else original_filename
32
+
33
+ # Add .pdf extension if not present
34
+ if not filename.lower().endswith('.pdf'):
35
+ filename += '.pdf'
36
+
37
+ # Use dump() with explicit filename
38
+ full_path = zot.dump(
39
+ item_id,
40
+ filename=filename,
41
+ path=str(output_path)
42
+ )
43
+
44
+ print(f"\nSuccessfully copied file to: {full_path}")
45
+ print(f"Title: {item['data'].get('title', 'No title')}")
46
+
47
+ except Exception as e:
48
+ print(f"Error copying file: {e!s}")
49
+ print(f"Item data: {item['data']}") # Debug info
50
+
51
+ except Exception as e:
52
+ print(f"Error accessing Zotero item: {e!s}")
53
+
54
+ if __name__ == "__main__":
55
+ # Example usage with specific item ID
56
+ item_id = '8M9FYC2W'
57
+ data_dir = "./example/data/pdfs"
58
+
59
+ # Example 1: Copy with new name
60
+ copy_specific_pdf(item_id, data_dir, new_name="my_paper")
61
+
62
+ # Example 2: Copy with original filename
63
+ copy_specific_pdf(item_id, data_dir)
@@ -0,0 +1,68 @@
1
+ from pyzotero import zotero
2
+ from pprint import pprint
3
+
4
+ def get_item_detail(item_id):
5
+ """
6
+ Get detailed information about a specific Zotero item
7
+ Args:
8
+ item_id (str): Zotero item ID
9
+ """
10
+ # Initialize Zotero client with local=True
11
+ zot = zotero.Zotero(library_id='000000', library_type='user', local=True)
12
+
13
+ try:
14
+ # Get the item
15
+ item = zot.item(item_id)
16
+
17
+ # Print basic information
18
+ print("\nItem Details:")
19
+ print("-" * 50)
20
+ print(f"Item ID: {item['key']}")
21
+ print(f"Item Type: {item['data'].get('itemType', 'Not specified')}")
22
+ print(f"Title: {item['data'].get('title', 'No title')}")
23
+
24
+ # If it's an attachment, show parent item
25
+ if item['data'].get('parentItem'):
26
+ try:
27
+ parent = zot.item(item['data']['parentItem'])
28
+ print("\nParent Item:")
29
+ print(f"Parent ID: {parent['key']}")
30
+ print(f"Parent Title: {parent['data'].get('title', 'No title')}")
31
+ except Exception as e:
32
+ print(f"Error getting parent item: {e!s}")
33
+
34
+ # If it has child items (attachments), show them
35
+ children = zot.children(item_id)
36
+ if children:
37
+ print("\nChild Items:")
38
+ for child in children:
39
+ print(f"- {child['data'].get('title', 'No title')} "
40
+ f"(ID: {child['key']}, "
41
+ f"Type: {child['data'].get('itemType', 'Unknown')})")
42
+
43
+ # Show collections this item belongs to
44
+ collections = item['data'].get('collections', [])
45
+ if collections:
46
+ print("\nCollections:")
47
+ try:
48
+ for coll_id in collections:
49
+ coll = zot.collection(coll_id)
50
+ print(f"- {coll['data'].get('name', 'Unnamed')} (ID: {coll_id})")
51
+ except Exception as e:
52
+ print(f"Error retrieving collections: {e!s}")
53
+
54
+ # Show all metadata
55
+ print("\nFull Metadata:")
56
+ print("-" * 50)
57
+ pprint(item['data'])
58
+
59
+ except Exception as e:
60
+ print(f"Error getting item details: {e!s}")
61
+ return None
62
+ else:
63
+ return item
64
+
65
+ if __name__ == "__main__":
66
+ # Example usage with a specific item ID
67
+ item_id = 'K9V7JFXY' # Replace with your item ID
68
+ item_detail = get_item_detail(item_id)
@@ -0,0 +1,43 @@
1
+ from pyzotero import zotero
2
+
3
+ def search_by_title(title_query):
4
+ """
5
+ Search Zotero items by title
6
+ Args:
7
+ title_query (str): The title or part of title to search for
8
+ Returns:
9
+ list: List of matching items
10
+ """
11
+ # Initialize Zotero client with local=True
12
+ zot = zotero.Zotero(library_id='000000', library_type='user', local=True)
13
+
14
+ try:
15
+ # Search for items where title contains the query string
16
+ results = zot.items(q=title_query)
17
+
18
+ # Print results
19
+ print(f"\nFound {len(results)} items matching '{title_query}':")
20
+ for item in results:
21
+ # Get the item data
22
+ title = item['data'].get('title', 'No title')
23
+ item_type = item['data'].get('itemType', 'Unknown type')
24
+ date = item['data'].get('date', 'No date')
25
+ item_id = item['data'].get('key', 'No ID')
26
+
27
+ print("\n##########################")
28
+ print(f"ID: {item_id}")
29
+ print(f"Title: {title}")
30
+ print(f"Type: {item_type}")
31
+ print(f"Date: {date}")
32
+ print("##########################\n")
33
+
34
+ except Exception as e:
35
+ print(f"Error searching Zotero: {e!s}")
36
+ return []
37
+ else:
38
+ return results
39
+
40
+ if __name__ == "__main__":
41
+ # Example usage
42
+ search_query = "uORFs" # Change this to your search term
43
+ search_by_title(search_query)
@@ -30,6 +30,7 @@ classifiers = [
30
30
  [project.urls]
31
31
  Repository = "https://github.com/urschrei/pyzotero"
32
32
  Tracker = "https://github.com/urschrei/pyzotero/issues"
33
+ documentation = "https://pyzotero.readthedocs.org"
33
34
 
34
35
  [project.optional-dependencies]
35
36
  test = [
@@ -38,6 +39,7 @@ test = [
38
39
  "python-dateutil",
39
40
  "ipython",
40
41
  "pytest-asyncio",
42
+ "pytest-cov>=6.0.0",
41
43
  ]
42
44
 
43
45
  [tool.setuptools.dynamic]
@@ -53,7 +55,7 @@ requires = [
53
55
  [tool.pytest.ini_options]
54
56
  minversion = "6.2.2"
55
57
  addopts = [
56
- "--import-mode=importlib",
58
+ "--import-mode=importlib", "--cov=pyzotero", "--cov-report=xml",
57
59
  ]
58
60
  testpaths = [
59
61
  "tests",
@@ -63,3 +65,46 @@ asyncio_default_fixture_loop_scope = "function"
63
65
 
64
66
  [tool.setuptools_scm]
65
67
  write_to = "src/_version.py"
68
+
69
+ [tool.ruff]
70
+ exclude = [
71
+ ".bzr",
72
+ ".direnv",
73
+ ".eggs",
74
+ ".git",
75
+ ".git-rewrite",
76
+ ".hg",
77
+ ".ipynb_checkpoints",
78
+ ".mypy_cache",
79
+ ".nox",
80
+ ".pants.d",
81
+ ".pyenv",
82
+ ".pytest_cache",
83
+ ".pytype",
84
+ ".ruff_cache",
85
+ ".svn",
86
+ ".tox",
87
+ ".venv",
88
+ ".vscode",
89
+ "__pypackages__",
90
+ "_build",
91
+ "buck-out",
92
+ "build",
93
+ "dist",
94
+ "node_modules",
95
+ "site-packages",
96
+ "venv",
97
+ ]
98
+
99
+ [tool.ruff.lint]
100
+ select = ["F", "E", "W", "N", "D4", "UP", "PL", "TRY", "PERF", "FURB", "RUF", "S", "DTZ"]
101
+ ignore = ["ANN001", "ANN003", "ANN202", "ANN201", "DOC201", "E501", "PLR0904", "PLR0913", "PLR0917", "SLF001", "FIX002", "D400", "D415"]
102
+ fixable = ["ALL"]
103
+ unfixable = []
104
+
105
+ [tool.ruff.format]
106
+ # Like Black, use double quotes for strings.
107
+ quote-style = "double"
108
+
109
+ # Like Black, indent with spaces, rather than tabs.
110
+ indent-style = "space"
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '1.6.8'
16
- __version_tuple__ = version_tuple = (1, 6, 8)
20
+ __version__ = version = '1.6.10'
21
+ __version_tuple__ = version_tuple = (1, 6, 10)
@@ -32,7 +32,7 @@
32
32
 
33
33
  import asyncio
34
34
  from pathlib import Path
35
- from typing import Optional, Tuple
35
+ from typing import Optional
36
36
 
37
37
  import httpx
38
38
  from httpx import (
@@ -65,7 +65,7 @@ httpx.URL.is_absolute_url = property(is_absolute_url) # type: ignore
65
65
 
66
66
 
67
67
  class FileTransport(AsyncBaseTransport, BaseTransport):
68
- def _handle(self, request: Request) -> Tuple[Optional[int], httpx.Headers]:
68
+ def _handle(self, request: Request) -> tuple[Optional[int], httpx.Headers]:
69
69
  if request.url.host and request.url.host != "localhost":
70
70
  raise NotImplementedError("Only local paths are allowed")
71
71
  if request.method in {"PUT", "DELETE"}:
@@ -148,4 +148,4 @@ class AsyncClient(_AsyncClient):
148
148
  self._mounts.update({URLPattern(protocol): transport})
149
149
 
150
150
 
151
- __all__ = ["FileTransport", "AsyncClient", "Client"]
151
+ __all__ = ["AsyncClient", "Client", "FileTransport"]