From ea41b7a263e2021652c03041a77afd3131f0a865 Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Fri, 27 Feb 2026 12:31:39 -0500 Subject: [PATCH 1/2] add lock and update workflows --- .github/workflows/ci.yml | 31 ++- CHANGELOG.md | 2 +- pyproject.toml | 19 +- tind_client/fetch.py | 4 +- uv.lock | 452 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 483 insertions(+), 25 deletions(-) create mode 100644 uv.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84e2fc2..5682677 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,36 +1,33 @@ name: CI -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - +on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.13"] + python-version: + - "3.13" + - "3.14" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v7 with: + version: "0.10.7" python-version: ${{ matrix.python-version }} + enable-cache: true - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install ".[test,lint]" + - name: Sync dependencies + run: uv sync --locked --all-extras --dev - name: Lint with pylint - run: pylint tind_client/ + run: uv run pylint tind_client tests - name: Type-check with mypy - run: mypy tind_client/ + run: uv run mypy tind_client tests - name: Run tests - run: pytest + run: uv run pytest diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c9dcc..22a9776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implemented TINDClient to wrap API interactions ### Changed -- N/A +- updates to workflow actions and pyproject.toml ### Deprecated - N/A diff --git a/pyproject.toml b/pyproject.toml index 92030aa..278fc74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,10 +44,19 @@ testpaths = ["tests"] [tool.mypy] python_version = "3.13" -strict = true +warn_unused_configs = true +warn_redundant_casts = true +warn_return_any = true +strict_equality = true +check_untyped_defs = true +disallow_subclassing_any = true +# We can't enable disallow_untyped_calls because of PyMARC. +disallow_untyped_calls = false +disallow_incomplete_defs = true +disallow_untyped_defs = true -[tool.pylint.main] -py-version = "3.13" +[tool.pydoclint] +allow-init-docstring = true +skip-checking-raises = true +style = "sphinx" -[tool.pylint.messages_control] -disable = ["C0114"] # missing-module-docstring — already covered by file-level docstrings diff --git a/tind_client/fetch.py b/tind_client/fetch.py index d453b46..a317417 100644 --- a/tind_client/fetch.py +++ b/tind_client/fetch.py @@ -35,7 +35,7 @@ def fetch_metadata(record: str, *, api_key: str, api_url: str) -> Record: if status == 404 or len(response.strip()) == 0: raise RecordNotFoundError(f"Record {record} not found in TIND.") - records: list[Record] = parse_xml_to_array(StringIO(response)) # type: ignore[no-untyped-call] + records: list[Record] = parse_xml_to_array(StringIO(response)) # When the record does not match any records, we may receive a zero-length array of records. # Additionally, if the XML is malformed, the parser function may return multiple records. # We need to ensure that exactly one record is parsed out of the TIND API response. @@ -232,7 +232,7 @@ def search( records = list(collection) if collection is not None else [] if result_format == "pymarc": - recs = recs + parse_xml_to_array(StringIO(response)) # type: ignore[no-untyped-call] + recs = recs + parse_xml_to_array(StringIO(response)) else: for record in records: recs.append(E.tostring(record, encoding="unicode")) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1f47fc6 --- /dev/null +++ b/uv.lock @@ -0,0 +1,452 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "astroid" +version = "4.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/63/0adf26577da5eff6eb7a177876c1cfa213856be9926a000f65c4add9692b/astroid-4.0.4.tar.gz", hash = "sha256:986fed8bcf79fb82c78b18a53352a0b287a73817d6dbcfba3162da36667c49a0", size = 406358, upload-time = "2026-02-07T23:35:07.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/cf/1c5f42b110e57bc5502eb80dbc3b03d256926062519224835ef08134f1f9/astroid-4.0.4-py3-none-any.whl", hash = "sha256:52f39653876c7dec3e3afd4c2696920e05c83832b9737afc21928f2d2eb7a753", size = 276445, upload-time = "2026-02-07T23:35:05.344Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "docstring-parser-fork" +version = "0.0.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/bf/27f9cab2f0cd1d17a4420572088bbc19f36d726fbcf165edf226a8926dbc/docstring_parser_fork-0.0.14.tar.gz", hash = "sha256:a2743a63d8d36c09650594f7b4ab5b2758fee8629dcf794d1b221b23179baa5c", size = 34551, upload-time = "2025-09-07T17:27:38.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl", hash = "sha256:4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af", size = 43063, upload-time = "2025-09-07T17:27:37.012Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isort" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/e3/e72b0b3a85f24cf5fc2cd8e92b996592798f896024c5cdf3709232e6e377/isort-8.0.0.tar.gz", hash = "sha256:fddea59202f231e170e52e71e3510b99c373b6e571b55d9c7b31b679c0fed47c", size = 769482, upload-time = "2026-02-19T16:31:59.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/ea/cf3aad99dd12c026e2d6835d559efb6fc50ccfd5b46d42d5fec2608b116a/isort-8.0.0-py3-none-any.whl", hash = "sha256:184916a933041c7cf718787f7e52064f3c06272aff69a5cb4dc46497bd8911d9", size = 89715, upload-time = "2026-02-19T16:31:57.745Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydoclint" +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docstring-parser-fork" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/58/bb3c7edd2bd06b07d9bbb494e52e2d2d6d53405b166aac531b1a27cef972/pydoclint-0.8.3.tar.gz", hash = "sha256:0c69c0ed92c6f6b5aec2a14371bbd9e0a77980da8e2fa0f708092aa06b3b20b3", size = 186619, upload-time = "2025-11-26T06:53:43.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl", hash = "sha256:5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164", size = 79075, upload-time = "2025-11-26T06:53:42.129Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pylint" +version = "4.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/b6/74d9a8a68b8067efce8d07707fe6a236324ee1e7808d2eb3646ec8517c7d/pylint-4.0.5.tar.gz", hash = "sha256:8cd6a618df75deb013bd7eb98327a95f02a6fb839205a6bbf5456ef96afb317c", size = 1572474, upload-time = "2026-02-20T09:07:33.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/6f/9ac2548e290764781f9e7e2aaf0685b086379dabfb29ca38536985471eaf/pylint-4.0.5-py3-none-any.whl", hash = "sha256:00f51c9b14a3b3ae08cff6b2cdd43f28165c78b165b628692e428fb1f8dc2cf2", size = 536694, upload-time = "2026-02-20T09:07:31.028Z" }, +] + +[[package]] +name = "pymarc" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/28/8cd999db1c4c6d7e855e7596bc17294829e95e818473afbb655cfa167dd0/pymarc-5.3.1.tar.gz", hash = "sha256:d289233d7298aeae45d976081710391cf21e754f73928132508b8adfe8ea0b49", size = 235395, upload-time = "2025-06-18T13:57:51.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/ba/058498db9945b4f0b1bbf9d2e3095d3c4c21934946953056b1a0751cc5a1/pymarc-5.3.1-py3-none-any.whl", hash = "sha256:3fcc09f9d51ef27c40f8d215e3219d0f3eb29716a0767e689c4923e38182ad73", size = 160259, upload-time = "2025-06-18T13:57:46.869Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-tind-client" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pymarc" }, + { name = "requests" }, +] + +[package.optional-dependencies] +debug = [ + { name = "debugpy" }, +] +dev = [ + { name = "debugpy" }, + { name = "mypy" }, + { name = "pydoclint" }, + { name = "pylint" }, + { name = "pytest" }, + { name = "requests-mock" }, + { name = "types-requests" }, +] +lint = [ + { name = "mypy" }, + { name = "pydoclint" }, + { name = "pylint" }, + { name = "types-requests" }, +] +test = [ + { name = "pytest" }, + { name = "requests-mock" }, +] + +[package.metadata] +requires-dist = [ + { name = "debugpy", marker = "extra == 'debug'", specifier = ">=1.8" }, + { name = "mypy", marker = "extra == 'lint'", specifier = ">=1.14" }, + { name = "pydoclint", marker = "extra == 'lint'", specifier = ">=0.5" }, + { name = "pylint", marker = "extra == 'lint'", specifier = ">=3.3" }, + { name = "pymarc", specifier = ">=5.2" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3" }, + { name = "python-tind-client", extras = ["test", "lint", "debug"], marker = "extra == 'dev'" }, + { name = "requests", specifier = ">=2.32" }, + { name = "requests-mock", marker = "extra == 'test'", specifier = ">=1.12" }, + { name = "types-requests", marker = "extra == 'lint'", specifier = ">=2.32" }, +] +provides-extras = ["test", "lint", "debug", "dev"] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-mock" +version = "1.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/32/587625f91f9a0a3d84688bf9cfc4b2480a7e8ec327cefd0ff2ac891fd2cf/requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401", size = 60901, upload-time = "2024-03-29T03:54:29.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/ec/889fbc557727da0c34a33850950310240f2040f3b1955175fdb2b36a8910/requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", size = 27695, upload-time = "2024-03-29T03:54:27.64Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20260107" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] From 0d2dd37be66dc020c0cd0de953cd00e74caebe98 Mon Sep 17 00:00:00 2001 From: Jason Raitz Date: Fri, 27 Feb 2026 17:52:53 -0500 Subject: [PATCH 2/2] refactor - roll fetch.py into client.py - removed fetch.py - remove direct api access - defaults for client init including default tind api key & url environment fetching - added basic .flake8 style settings - updated readme - updated tests --- .flake8 | 4 + README.md | 42 ++++--- tests/test_fetch.py | 48 +++----- tind_client/__init__.py | 21 +--- tind_client/client.py | 212 +++++++++++++++++++++++++++-------- tind_client/fetch.py | 243 ---------------------------------------- 6 files changed, 206 insertions(+), 364 deletions(-) create mode 100644 .flake8 delete mode 100644 tind_client/fetch.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..8d59d33 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 100 +# Equivalent to allow-init-docstring, which we set on pydoclint. +extend-ignore = DOC301,F401 diff --git a/README.md b/README.md index 82d715d..c504a2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-tind-client -Python library for interacting with the [TIND ILS](https://tind.io) API. +Python library for interacting with the [TIND DA](https://tind.io) API. ## Requirements @@ -23,11 +23,11 @@ pip install "python-tind-client[debug]" # debugpy ## Configuration -Create a `TINDClient` with explicit configuration values: +Create a `TINDClient` with optional configuration values: -- `api_key` (required): Your TIND API token -- `api_url` (required): Base URL of the TIND instance (e.g. `https://tind.example.edu`) -- `default_storage_dir` (optional): Default output directory for downloaded files +- `api_key` (optional): Your TIND API token. Falls back to the `TIND_API_KEY` environment variable. +- `api_url` (optional): Base URL of the TIND instance (e.g. `https://tind.example.edu`). Falls back to the `TIND_API_URL` environment variable. +- `default_storage_dir` (optional): Default output directory for downloaded files. Defaults to `./tmp`. ## Usage @@ -44,39 +44,35 @@ client = TINDClient( ### Fetch pyMARC metadata for a record ```python -record = client.fetch_metadata("12345") +record = client.fetch_metadata("116262") print(record["245"]["a"]) # title ``` ### Fetch file metadata for a record ```python -metadata = client.fetch_file_metadata("12345") -print(metadata[0]) # first file metadata dict -print(metadata[0]["url"]) # file download URL +metadata = client.fetch_file_metadata("116262") +print(metadata[0]) # first file metadata dict +print(metadata[0]["url"]) # file download URL ``` ### Download a file ```python # use metadata from previous example -path_to_download = client.fetch_file(metadata[0].url) +path_to_download = client.fetch_file(metadata[0]["url"]) ``` -## Functional fetch API - -The functions in `tind_client.fetch` are available for direct use and now accept -explicit credentials instead of a client object. - +### Search for records ```python -from tind_client.fetch import fetch_metadata +# return a list of record IDs matching a query +ids = client.fetch_ids_search("collection:'Disabled Students Program Photos'") -record = fetch_metadata( - "12345", - api_key="your-token", - api_url="https://tind.example.edu", -) -``` +# return PyMARC records matching a query +records = client.fetch_search_metadata("collection:'Disabled Students Program Photos'") -For most use cases, prefer `TINDClient` methods as the primary interface. +# return raw XML or PyMARC records from a paginated search +xml_results = client.search("collection:'Disabled Students Program Photos'", result_format="xml") +pymarc_results = client.search("collection:'Disabled Students Program Photos'", result_format="pymarc") +``` ## Running tests diff --git a/tests/test_fetch.py b/tests/test_fetch.py index 17027b9..4655ad0 100644 --- a/tests/test_fetch.py +++ b/tests/test_fetch.py @@ -1,5 +1,5 @@ """ -Tests for tind_client.fetch. +Tests for TINDClient methods (fetch operations). """ import json @@ -7,7 +7,7 @@ import pytest import requests_mock as req_mock # noqa: F401 — activates the requests_mock fixture -from tind_client import TINDClient, fetch +from tind_client import TINDClient from tind_client.errors import RecordNotFoundError, TINDError BASE_URL = "https://tind.example.edu" @@ -28,9 +28,7 @@ def test_fetch_metadata_success( requests_mock.get( f"{BASE_URL}/record/12345/", text=sample_marc_xml, status_code=200 ) - record = fetch.fetch_metadata( - "12345", api_key=client.api_key, api_url=client.api_url - ) + record = client.fetch_metadata("12345") assert record["245"]["a"] == "Sample Title" @@ -38,7 +36,7 @@ def test_fetch_metadata_404(requests_mock: req_mock.Mocker, client: TINDClient) """fetch_metadata raises RecordNotFoundError on HTTP 404.""" requests_mock.get(f"{BASE_URL}/record/99999/", text="", status_code=404) with pytest.raises(RecordNotFoundError): - fetch.fetch_metadata("99999", api_key=client.api_key, api_url=client.api_url) + client.fetch_metadata("99999") def test_fetch_metadata_empty_body( @@ -47,7 +45,7 @@ def test_fetch_metadata_empty_body( """fetch_metadata raises RecordNotFoundError when the response body is empty.""" requests_mock.get(f"{BASE_URL}/record/11111/", text=" ", status_code=200) with pytest.raises(RecordNotFoundError): - fetch.fetch_metadata("11111", api_key=client.api_key, api_url=client.api_url) + client.fetch_metadata("11111") # --------------------------------------------------------------------------- @@ -58,7 +56,7 @@ def test_fetch_metadata_empty_body( def test_fetch_file_invalid_url(client: TINDClient) -> None: """fetch_file raises ValueError for non-TIND download URLs.""" with pytest.raises(ValueError): - fetch.fetch_file("https://not-a-tind-url.com/file.pdf", api_key=client.api_key) + client.fetch_file("https://not-a-tind-url.com/file.pdf") def test_fetch_file_success( @@ -74,7 +72,7 @@ def test_fetch_file_success( status_code=200, headers={"Content-Disposition": 'attachment; filename="document.pdf"'}, ) - path = fetch.fetch_file(url, api_key=client.api_key, output_dir=str(tmp_path)) + path = client.fetch_file(url, output_dir=str(tmp_path)) assert path.endswith("document.pdf") @@ -87,7 +85,7 @@ def test_fetch_file_not_found( url = f"{BASE_URL}/files/missing/download" requests_mock.get(url, status_code=404) with pytest.raises(RecordNotFoundError): - fetch.fetch_file(url, api_key=client.api_key, output_dir=str(tmp_path)) + client.fetch_file(url, output_dir=str(tmp_path)) # --------------------------------------------------------------------------- @@ -105,9 +103,7 @@ def test_fetch_file_metadata_success( text=json.dumps(payload), status_code=200, ) - result = fetch.fetch_file_metadata( - "12345", api_key=client.api_key, api_url=client.api_url - ) + result = client.fetch_file_metadata("12345") assert result[0]["name"] == "file.pdf" @@ -121,9 +117,7 @@ def test_fetch_file_metadata_error( status_code=404, ) with pytest.raises(TINDError): - fetch.fetch_file_metadata( - "12345", api_key=client.api_key, api_url=client.api_url - ) + client.fetch_file_metadata("12345") # --------------------------------------------------------------------------- @@ -140,9 +134,7 @@ def test_fetch_ids_search_success( text=json.dumps({"hits": ["1", "2", "3"]}), status_code=200, ) - ids = fetch.fetch_ids_search( - "title:python", api_key=client.api_key, api_url=client.api_url - ) + ids = client.fetch_ids_search("title:python") assert ids == ["1", "2", "3"] @@ -156,9 +148,7 @@ def test_fetch_ids_search_error( status_code=400, ) with pytest.raises(TINDError): - fetch.fetch_ids_search( - "title:python", api_key=client.api_key, api_url=client.api_url - ) + client.fetch_ids_search("title:python") # --------------------------------------------------------------------------- @@ -169,12 +159,7 @@ def test_fetch_ids_search_error( def test_search_invalid_format(client: TINDClient) -> None: """search raises ValueError for unsupported result_format values.""" with pytest.raises(ValueError, match="Unexpected result format"): - fetch.search( - "title:test", - api_key=client.api_key, - api_url=client.api_url, - result_format="csv", - ) + client.search("title:test", result_format="csv") def test_search_returns_xml( @@ -191,12 +176,7 @@ def test_search_returns_xml( requests_mock.get(f"{BASE_URL}/search", text=wrapped, status_code=200) - results = fetch.search( - "title:sample", - api_key=client.api_key, - api_url=client.api_url, - result_format="xml", - ) + results = client.search("title:sample", result_format="xml") assert isinstance(results, list) assert len(results) >= 1 assert requests_mock.call_count == 1 diff --git a/tind_client/__init__.py b/tind_client/__init__.py index 71b8fcb..a369af6 100644 --- a/tind_client/__init__.py +++ b/tind_client/__init__.py @@ -1,34 +1,15 @@ """ -python-tind-client — Python library for interacting with the TIND ILS API. +python-tind-client — Python library for interacting with the TIND DA API. """ __copyright__ = "© 2026 The Regents of the University of California. MIT license." from .client import TINDClient -from .api import tind_download, tind_get from .errors import AuthorizationError, RecordNotFoundError, TINDError -from .fetch import ( - fetch_file, - fetch_file_metadata, - fetch_ids_search, - fetch_marc_by_ids, - fetch_metadata, - fetch_search_metadata, - search, -) __all__ = [ "TINDClient", - "tind_get", - "tind_download", "AuthorizationError", "RecordNotFoundError", "TINDError", - "fetch_metadata", - "fetch_file", - "fetch_file_metadata", - "fetch_ids_search", - "fetch_marc_by_ids", - "fetch_search_metadata", - "search", ] diff --git a/tind_client/client.py b/tind_client/client.py index 6228f20..6026b36 100644 --- a/tind_client/client.py +++ b/tind_client/client.py @@ -1,24 +1,23 @@ """ -TINDClient — the main entry point for interacting with the TIND ILS API. +TINDClient — the main entry point for interacting with the TIND DA API. """ +import json +import os +import re +from io import StringIO from typing import Any +import xml.etree.ElementTree as E + from pymarc import Record +from pymarc.marcxml import parse_xml_to_array from .api import tind_get, tind_download -from .fetch import ( - fetch_metadata, - fetch_file, - fetch_file_metadata, - fetch_ids_search, - fetch_marc_by_ids, - fetch_search_metadata, - search, -) +from .errors import RecordNotFoundError, TINDError class TINDClient: - """Client for interacting with a TIND ILS instance. + """Client for interacting with a TIND DA instance. :param str api_key: Your TIND API token. :param str api_url: Base URL of the TIND instance, e.g. ``https://tind.example.edu``. @@ -27,58 +26,183 @@ class TINDClient: """ def __init__( - self, api_key: str, api_url: str, default_storage_dir: str = "" + self, + api_key: str = os.environ.get("TIND_API_KEY", ""), + api_url: str = os.environ.get("TIND_API_URL", ""), + default_storage_dir: str = "./tmp", ) -> None: self.api_key = api_key self.api_url = api_url self.default_storage_dir = default_storage_dir - def _tind_get( - self, endpoint: str, params: dict[str, str] | None = None - ) -> tuple[int, str]: - """See :func:`tind_client.api.tind_get`.""" - return tind_get( - endpoint, api_key=self.api_key, api_url=self.api_url, params=params + def fetch_metadata(self, record: str) -> Record: + """Fetch the MARC XML metadata for a given record. + + :param str record: The record ID for which to fetch metadata. + :raises AuthorizationError: When the TIND API key is invalid. + :raises RecordNotFoundError: When the record ID is invalid or not found. + :returns Record: A PyMARC MARC record of the requested record. + """ + status, response = tind_get( + f"record/{record}/", + api_key=self.api_key, + api_url=self.api_url, + params={"of": "xm"}, ) + if status == 404 or len(response.strip()) == 0: + raise RecordNotFoundError(f"Record {record} not found in TIND.") - def _tind_download(self, url: str, output_dir: str) -> tuple[int, str]: - """See :func:`tind_client.api.tind_download`.""" - return tind_download(url, output_dir, api_key=self.api_key) + records: list[Record] = parse_xml_to_array(StringIO(response)) + # When the record does not match any records, we may receive a zero-length array of + # records. Additionally, if the XML is malformed, the parser function may return + # multiple records. We need to ensure that exactly one record is parsed. + if len(records) != 1: + raise RecordNotFoundError( + f"Record {record} did not match exactly one record in TIND." + ) - def fetch_metadata(self, record: str) -> Record: - """See :func:`tind_client.fetch.fetch_metadata`.""" - return fetch_metadata(record, api_key=self.api_key, api_url=self.api_url) + return records[0] def fetch_file(self, file_url: str, output_dir: str = "") -> str: - """See :func:`tind_client.fetch.fetch_file`.""" - return fetch_file( - file_url, - api_key=self.api_key, - output_dir=output_dir, - default_storage_dir=self.default_storage_dir, + """Download a file from TIND and save it locally. + + :param str file_url: The TIND file download URL. + :param str output_dir: Directory in which to save the file. + Falls back to ``default_storage_dir`` when empty. + :raises AuthorizationError: When the TIND API key is invalid or the file is restricted. + :raises ValueError: When ``file_url`` is not a valid TIND file download URL. + :raises RecordNotFoundError: When the file is invalid or not found. + :returns str: The full path to the locally saved file. + """ + if not re.match(r"^http.*/download(/)?(\?version=\d+)?$", file_url): + raise ValueError("URL is not a valid TIND file download URL.") + + output_target = output_dir or self.default_storage_dir + (status, saved_to) = tind_download( + file_url, output_dir=output_target, api_key=self.api_key ) + if status != 200: + raise RecordNotFoundError("Referenced file could not be downloaded.") + + return saved_to + def fetch_file_metadata(self, record: str) -> list[dict[str, Any]]: - """See :func:`tind_client.fetch.fetch_file_metadata`.""" - return fetch_file_metadata(record, api_key=self.api_key, api_url=self.api_url) + """Fetch file metadata for a given TIND record. + + :param str record: The record ID in TIND to fetch file metadata for. + :raises AuthorizationError: When the TIND API key is invalid. + :raises TINDError: For any response other than 200. + :returns list: A list of file metadata dicts for the given record. + """ + status, files = tind_get( + f"record/{record}/files", api_key=self.api_key, api_url=self.api_url + ) + + if status != 200: + raise TINDError.from_json(status, files) + + return json.loads(files) # type: ignore[no-any-return] def fetch_ids_search(self, query: str) -> list[str]: - """See :func:`tind_client.fetch.fetch_ids_search`.""" - return fetch_ids_search(query, api_key=self.api_key, api_url=self.api_url) + """Return a list of TIND record IDs matching a search query. + + :param str query: The query string to search for in TIND. + :returns list[str]: A list of TIND record IDs. + """ + status, rec_ids = tind_get( + "search", api_key=self.api_key, api_url=self.api_url, params={"p": query} + ) + + if status != 200: + raise TINDError.from_json(status, rec_ids) + + j = json.loads(rec_ids) + hits = j.get("hits", []) if isinstance(j, dict) else [] + if not isinstance(hits, list): + return [] + return [str(item) for item in hits] def fetch_marc_by_ids(self, ids: list[str]) -> list[Record]: - """See :func:`tind_client.fetch.fetch_marc_by_ids`.""" - return fetch_marc_by_ids(ids, api_key=self.api_key, api_url=self.api_url) + """Fetch MARC records for a list of TIND record IDs. + + :param list[str] ids: The TIND record IDs to fetch. + :returns list[Record]: A list of PyMARC records. + """ + return [self.fetch_metadata(item) for item in ids] def fetch_search_metadata(self, query: str) -> list[Record]: - """See :func:`tind_client.fetch.fetch_search_metadata`.""" - return fetch_search_metadata(query, api_key=self.api_key, api_url=self.api_url) + """Return PyMARC records matching a search query. + + :param str query: The TIND search query. + :returns list[Record]: A list of PyMARC records that match the given query. + """ + ids = self.fetch_ids_search(query) + return self.fetch_marc_by_ids(ids) def search(self, query: str, result_format: str = "xml") -> list[Any]: - """See :func:`tind_client.fetch.search`.""" - return search( - query, - result_format=result_format, - api_key=self.api_key, - api_url=self.api_url, + """Search TIND and return results as XML strings or PyMARC records. + + :param str query: A TIND search string. + :param str result_format: ``'xml'`` for XML strings, ``'pymarc'`` for PyMARC records. + :raises ValueError: When ``result_format`` is neither ``'xml'`` nor ``'pymarc'``. + :returns list: Records as XML strings or PyMARC Record objects. + """ + if result_format not in ("xml", "pymarc"): + raise ValueError( + f"Unexpected result format: {result_format} is neither 'xml' nor 'pymarc'" + ) + + recs: list[Any] = [] + search_id = None + + while True: + response = self._search_request(query, search_id=search_id) + xml, search_id = self._retrieve_xml_search_id(response) + + collection = xml.find("{http://www.loc.gov/MARC21/slim}collection") + records = list(collection) if collection is not None else [] + + if result_format == "pymarc": + recs = recs + parse_xml_to_array(StringIO(response)) + else: + for record in records: + recs.append(E.tostring(record, encoding="unicode")) + + if not search_id or not records: + break + + return recs + + def _search_request(self, query: str, *, search_id: str | None = None) -> str: + """Retrieve a page of MARC data records. + + :param str query: The TIND search query. + :param str|None search_id: The search_id for pagination. + :returns str: A page of MARC records in XML format. + """ + params: dict[str, str] = {"format": "xml", "p": query} + if search_id: + params["search_id"] = search_id + + status, response = tind_get( + "search", api_key=self.api_key, api_url=self.api_url, params=params ) + + if status != 200: + raise TINDError(f"Status {status} while retrieving TIND record") + + return response + + def _retrieve_xml_search_id(self, response: str) -> tuple[E.Element, str]: + """Parse a TIND search response and extract the pagination search_id. + + :param str response: The string returned from the TIND search call. + :returns: A parsable XML element and the search ID for the next page. + :rtype: tuple[xml.etree.ElementTree.Element, str] + """ + E.register_namespace("", "http://www.loc.gov/MARC21/slim") + xml = E.fromstring(response) + search_id = xml.findtext("search_id", default="") + + return xml, search_id diff --git a/tind_client/fetch.py b/tind_client/fetch.py deleted file mode 100644 index a317417..0000000 --- a/tind_client/fetch.py +++ /dev/null @@ -1,243 +0,0 @@ -""" -Provides routines to fetch information from the TIND API. -""" - -import json -import re -from io import StringIO -from typing import Any - -import xml.etree.ElementTree as E -from pymarc.marcxml import parse_xml_to_array -from pymarc import Record - -from .errors import RecordNotFoundError, TINDError -from .api import tind_get, tind_download - - -def fetch_metadata(record: str, *, api_key: str, api_url: str) -> Record: - """Fetch the MARC XML metadata for a given record. - - :param str record: The record ID for which to fetch metadata. - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :raises AuthorizationError: When the TIND API key is invalid. - :raises RecordNotFoundError: When the record ID is invalid or not found. - :returns Record: A PyMARC MARC record of the requested record. - """ - - status, response = tind_get( - f"record/{record}/", - api_key=api_key, - api_url=api_url, - params={"of": "xm"}, - ) - if status == 404 or len(response.strip()) == 0: - raise RecordNotFoundError(f"Record {record} not found in TIND.") - - records: list[Record] = parse_xml_to_array(StringIO(response)) - # When the record does not match any records, we may receive a zero-length array of records. - # Additionally, if the XML is malformed, the parser function may return multiple records. - # We need to ensure that exactly one record is parsed out of the TIND API response. - if len(records) != 1: - raise RecordNotFoundError( - f"Record {record} did not match exactly one record in TIND." - ) - - return records[0] - - -def fetch_file( - file_url: str, - *, - api_key: str, - output_dir: str = "", - default_storage_dir: str = "", -) -> str: - """Fetch the given file from TIND. - - :param str file_url: The URL to the file to download from TIND. - This must be a TIND file download URL. - :param str api_key: The TIND API token. - :param str output_dir: The output directory for this download. - :param str default_storage_dir: Fallback output directory when output_dir is empty. - :raises AuthorizationError: When the TIND API key is invalid, or the file is restricted. - :raises ValueError: When the URL is not a valid TIND file download URL. - :raises RecordNotFoundError: When the file is invalid or not found. - :returns str: The full path to the file successfully downloaded to the output directory. - """ - if not re.match(r"^http.*/download(/)?(\?version=\d+)?$", file_url): - raise ValueError("URL is not a valid TIND file download URL.") - - output_target = output_dir or default_storage_dir - (status, saved_to) = tind_download( - file_url, output_dir=output_target, api_key=api_key - ) - - if status != 200: - raise RecordNotFoundError("Referenced file could not be downloaded.") - - return saved_to - - -def fetch_file_metadata( - record: str, *, api_key: str, api_url: str -) -> list[dict[str, Any]]: - """Fetch file metadata for a given TIND record. - - :param str record: The record ID in TIND to fetch file metadata for. - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :raises AuthorizationError: When the TIND API key is invalid. - :raises TINDError: For any response other than 200. - :returns list: A list of file metadata for a given TIND record. - """ - - status, files = tind_get(f"record/{record}/files", api_key=api_key, api_url=api_url) - - if status != 200: - raise TINDError.from_json(status, files) - - return json.loads(files) # type: ignore[no-any-return] - - -def fetch_ids_search(query: str, *, api_key: str, api_url: str) -> list[str]: - """Returns a list of TIND record IDs for a given search query. - - :param str query: The query string to search for in TIND. - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :returns list[str]: A list of TIND record IDs. - """ - status, rec_ids = tind_get( - "search", api_key=api_key, api_url=api_url, params={"p": query} - ) - - if status != 200: - raise TINDError.from_json(status, rec_ids) - - j = json.loads(rec_ids) - hits = j.get("hits", []) if isinstance(j, dict) else [] - if not isinstance(hits, list): - return [] - return [str(item) for item in hits] - - -def fetch_marc_by_ids(ids: list[str], *, api_key: str, api_url: str) -> list[Record]: - """Fetch MARC records from a list of TIND record IDs. - - :param list ids: The TIND record IDs to fetch. - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :returns list[Record]: A list of PyMARC records. - """ - records = [] - for item in ids: - m = fetch_metadata(item, api_key=api_key, api_url=api_url) - records.append(m) - - return records - - -def fetch_search_metadata(query: str, *, api_key: str, api_url: str) -> list[Record]: - """Returns PyMARC records that match a given search. - - :param str query: The TIND search query. - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :returns list[Record]: A list of PyMARC records that match the given query. - """ - ids = fetch_ids_search(query, api_key=api_key, api_url=api_url) - - return fetch_marc_by_ids(ids, api_key=api_key, api_url=api_url) - - -def _search_request( - query: str, *, api_key: str, api_url: str, search_id: str | None = None -) -> str: - """Retrieve a page of MARC data records. - - :param str query: The TIND search query. - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :param str|None search_id: The search_id for each page of TIND results for pagination. - :returns str: A page of MARC records in XML format. - """ - if search_id: - status, response = tind_get( - "search", - api_key=api_key, - api_url=api_url, - params={"format": "xml", "p": query, "search_id": search_id}, - ) - else: - status, response = tind_get( - "search", - api_key=api_key, - api_url=api_url, - params={"format": "xml", "p": query}, - ) - - if status != 200: - raise TINDError(f"Status {status} while retrieving TIND record") - - return response - - -def _retrieve_xml_search_id(response: str) -> tuple[E.Element, str]: - """Creates a parsable XML and retrieves search_id from the TIND result set for pagination. - - :param str response: The string returned from the Tind search call. - :returns: A Search ID and a parsable XML document. - :rtype: tuple[xml.etree.ElementTree.Element, str] - """ - E.register_namespace("", "http://www.loc.gov/MARC21/slim") - xml = E.fromstring(response) - search_id = xml.findtext("search_id", default="") - - return xml, search_id - - -def search( - query: str, *, api_key: str, api_url: str, result_format: str = "xml" -) -> list[Any]: - """Searches TIND and retrieves a list of either XML or PyMARC. - - :param str query: A Tind search string - :param str api_key: The TIND API token. - :param str api_url: Base URL of the TIND instance. - :param str result_format: ``xml`` for XML string, ``pymarc`` for list of pymarc records. - :returns list[Any]: a list of records as either xml strings or pymarc records - """ - - if result_format not in ("xml", "pymarc"): - raise ValueError( - f"Unexpected result format: {result_format} is neither 'xml' nor 'pymarc'" - ) - - recs: list[Any] = [] - search_id = None - - while True: - if search_id: - response = _search_request( - query, api_key=api_key, api_url=api_url, search_id=search_id - ) - else: - response = _search_request(query, api_key=api_key, api_url=api_url) - - xml, search_id = _retrieve_xml_search_id(response) - - collection = xml.find("{http://www.loc.gov/MARC21/slim}collection") - records = list(collection) if collection is not None else [] - - if result_format == "pymarc": - recs = recs + parse_xml_to_array(StringIO(response)) - else: - for record in records: - recs.append(E.tostring(record, encoding="unicode")) - - if not search_id or not records: - break - - return recs