pyUSPTO 0.5.3__tar.gz → 0.5.4__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 (169) hide show
  1. {pyuspto-0.5.3 → pyuspto-0.5.4}/.devcontainer/devcontainer.json +4 -1
  2. {pyuspto-0.5.3 → pyuspto-0.5.4}/.gitignore +1 -0
  3. {pyuspto-0.5.3 → pyuspto-0.5.4}/CHANGELOG.md +10 -0
  4. {pyuspto-0.5.3 → pyuspto-0.5.4}/PKG-INFO +2 -2
  5. {pyuspto-0.5.3 → pyuspto-0.5.4}/README.md +1 -1
  6. {pyuspto-0.5.3 → pyuspto-0.5.4}/requirements-dev.txt +7 -7
  7. {pyuspto-0.5.3 → pyuspto-0.5.4}/requirements.txt +3 -3
  8. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/patent_data.py +32 -13
  9. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/config.py +79 -50
  10. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/patent_data.py +5 -1
  11. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO.egg-info/PKG-INFO +2 -2
  12. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_base.py +9 -27
  13. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_oa_citations_clients.py +5 -14
  14. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_oa_rejections_clients.py +4 -4
  15. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_patent_data_clients.py +69 -8
  16. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_petition_decision_clients.py +1 -3
  17. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_oa_actions_integration.py +4 -12
  18. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_oa_citations_integration.py +4 -12
  19. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_oa_rejections_integration.py +3 -9
  20. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_patent_data_integration.py +6 -6
  21. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_petition_decisions_integration.py +9 -9
  22. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_ptab_appeals_integration.py +6 -6
  23. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_ptab_interferences_integration.py +13 -13
  24. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_ptab_trials_integration.py +12 -12
  25. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_bulk_data_models.py +12 -4
  26. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_enriched_citation_models.py +3 -9
  27. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_oa_actions_models.py +15 -3
  28. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_oa_citations_models.py +8 -20
  29. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_oa_rejections_models.py +2 -6
  30. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/test_edge_cases.py +1 -1
  31. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/test_init.py +3 -3
  32. {pyuspto-0.5.3 → pyuspto-0.5.4}/.devcontainer/devcontainer-lock.json +0 -0
  33. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  34. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  35. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/ISSUE_TEMPLATE/documentation-updates.yml +0 -0
  36. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  37. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  38. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/dependabot.yml +0 -0
  39. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/workflows/codeql.yml +0 -0
  40. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/workflows/publish-docs.yaml +0 -0
  41. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/workflows/publish-to-test-pypi.yml +0 -0
  42. {pyuspto-0.5.3 → pyuspto-0.5.4}/.github/workflows/python-tests.yml +0 -0
  43. {pyuspto-0.5.3 → pyuspto-0.5.4}/.readthedocs.yaml +0 -0
  44. {pyuspto-0.5.3 → pyuspto-0.5.4}/ADVANCED.md +0 -0
  45. {pyuspto-0.5.3 → pyuspto-0.5.4}/CODE_OF_CONDUCT.md +0 -0
  46. {pyuspto-0.5.3 → pyuspto-0.5.4}/CONTRIBUTING.md +0 -0
  47. {pyuspto-0.5.3 → pyuspto-0.5.4}/LICENSE +0 -0
  48. {pyuspto-0.5.3 → pyuspto-0.5.4}/_version.pyi +0 -0
  49. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/Makefile +0 -0
  50. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/_static/.gitkeep +0 -0
  51. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/about.rst +0 -0
  52. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/advanced.rst +0 -0
  53. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/bulk_data.rst +0 -0
  54. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/enriched_citations.rst +0 -0
  55. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/index.rst +0 -0
  56. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/oa_actions.rst +0 -0
  57. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/oa_citations.rst +0 -0
  58. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/oa_rejections.rst +0 -0
  59. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/patent_data.rst +0 -0
  60. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/petition_decisions.rst +0 -0
  61. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/ptab_appeals.rst +0 -0
  62. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/ptab_interferences.rst +0 -0
  63. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/clients/ptab_trials.rst +0 -0
  64. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/config/config.rst +0 -0
  65. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/config/http_config.rst +0 -0
  66. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/config/index.rst +0 -0
  67. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/exceptions.rst +0 -0
  68. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/index.rst +0 -0
  69. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/base.rst +0 -0
  70. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/bulk_data.rst +0 -0
  71. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/enriched_citations.rst +0 -0
  72. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/index.rst +0 -0
  73. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/oa_actions.rst +0 -0
  74. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/oa_citations.rst +0 -0
  75. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/oa_rejections.rst +0 -0
  76. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/patent_data.rst +0 -0
  77. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/petition_decisions.rst +0 -0
  78. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/ptab.rst +0 -0
  79. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/models/utils.rst +0 -0
  80. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/api/warnings.rst +0 -0
  81. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/conf.py +0 -0
  82. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/configuration.rst +0 -0
  83. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/development.rst +0 -0
  84. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/bulk_data.rst +0 -0
  85. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/enriched_citations.rst +0 -0
  86. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/ifw_example.rst +0 -0
  87. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/index.rst +0 -0
  88. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/oa_actions.rst +0 -0
  89. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/oa_citations.rst +0 -0
  90. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/oa_rejections.rst +0 -0
  91. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/patent_data.rst +0 -0
  92. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/petition_decisions.rst +0 -0
  93. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/ptab_appeals.rst +0 -0
  94. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/ptab_interferences.rst +0 -0
  95. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/examples/ptab_trials.rst +0 -0
  96. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/index.rst +0 -0
  97. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/installation.rst +0 -0
  98. {pyuspto-0.5.3 → pyuspto-0.5.4}/docs/source/quickstart.rst +0 -0
  99. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/bulk_data_example.py +0 -0
  100. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/enriched_citations_example.py +0 -0
  101. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/error_handling_example.py +0 -0
  102. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/ifw_example.py +0 -0
  103. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/oa_actions_example.py +0 -0
  104. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/oa_citations_example.py +0 -0
  105. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/oa_rejections_example.py +0 -0
  106. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/patent_data_example.py +0 -0
  107. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/petition_decisions_example.py +0 -0
  108. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/ptab_appeals_example.py +0 -0
  109. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/ptab_interferences_example.py +0 -0
  110. {pyuspto-0.5.3 → pyuspto-0.5.4}/examples/ptab_trials_example.py +0 -0
  111. {pyuspto-0.5.3 → pyuspto-0.5.4}/pyUSPTO.code-workspace +0 -0
  112. {pyuspto-0.5.3 → pyuspto-0.5.4}/pyproject.toml +0 -0
  113. {pyuspto-0.5.3 → pyuspto-0.5.4}/setup.cfg +0 -0
  114. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/__init__.py +0 -0
  115. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/__init__.py +0 -0
  116. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/base.py +0 -0
  117. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/bulk_data.py +0 -0
  118. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/enriched_citations.py +0 -0
  119. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/oa_actions.py +0 -0
  120. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/oa_citations.py +0 -0
  121. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/oa_rejections.py +0 -0
  122. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/petition_decisions.py +0 -0
  123. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/ptab_appeals.py +0 -0
  124. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/ptab_interferences.py +0 -0
  125. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/clients/ptab_trials.py +0 -0
  126. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/exceptions.py +0 -0
  127. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/http_config.py +0 -0
  128. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/__init__.py +0 -0
  129. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/base.py +0 -0
  130. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/bulk_data.py +0 -0
  131. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/enriched_citations.py +0 -0
  132. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/oa_actions.py +0 -0
  133. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/oa_citations.py +0 -0
  134. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/oa_rejections.py +0 -0
  135. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/petition_decisions.py +0 -0
  136. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/ptab.py +0 -0
  137. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/models/utils.py +0 -0
  138. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/py.typed +0 -0
  139. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/utils/__init__.py +0 -0
  140. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO/warnings.py +0 -0
  141. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO.egg-info/SOURCES.txt +0 -0
  142. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO.egg-info/dependency_links.txt +0 -0
  143. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO.egg-info/requires.txt +0 -0
  144. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO.egg-info/top_level.txt +0 -0
  145. {pyuspto-0.5.3 → pyuspto-0.5.4}/src/pyUSPTO.egg-info/zip-safe +0 -0
  146. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/__init__.py +0 -0
  147. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/__init__.py +0 -0
  148. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_bulk_data_clients.py +0 -0
  149. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_enriched_citation_clients.py +0 -0
  150. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_oa_actions_clients.py +0 -0
  151. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_ptab_appeals_client.py +0 -0
  152. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_ptab_interferences_client.py +0 -0
  153. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/clients/test_ptab_trials_client.py +0 -0
  154. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/conftest.py +0 -0
  155. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/__init__.py +0 -0
  156. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/conftest.py +0 -0
  157. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_bulk_data_integration.py +0 -0
  158. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/integration/test_enriched_citations_integration.py +0 -0
  159. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/__init__.py +0 -0
  160. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_patent_data_models.py +0 -0
  161. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_petition_decision_models.py +0 -0
  162. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_ptab_models.py +0 -0
  163. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/models/test_utils.py +0 -0
  164. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/test_config.py +0 -0
  165. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/test_exceptions.py +0 -0
  166. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/test_http_config.py +0 -0
  167. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/test_version.py +0 -0
  168. {pyuspto-0.5.3 → pyuspto-0.5.4}/tests/utils/__init__.py +0 -0
  169. {pyuspto-0.5.3 → pyuspto-0.5.4}/tox.ini +0 -0
@@ -3,7 +3,10 @@
3
3
  "build": { "dockerfile": "Dockerfile" },
4
4
  "features": {
5
5
  "ghcr.io/devcontainers/features/git:1": {},
6
- "ghcr.io/devcontainers/features/node:2": {}
6
+ "ghcr.io/devcontainers/features/node:2": {},
7
+ "ghcr.io/devcontainers/features/powershell:2.0.2": {
8
+ "version": "latest"
9
+ }
7
10
  },
8
11
  "mounts": [
9
12
  "source=pyuspto-claude-home,target=/home/vscode/.claude,type=volume"
@@ -23,3 +23,4 @@ run_integration_tests.py
23
23
  CLAUDE.md
24
24
  docs/build/
25
25
  .devcontainer/Dockerfile
26
+ .claude/skills/
@@ -5,6 +5,16 @@ All notable changes to the pyUSPTO package will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Changed
11
+
12
+ - **Breaking**: `sanitize_application_number` now emits the standardized 15-character PCT format (`PCT` + 2-char country + 4-digit year + 6-digit zero-padded serial, e.g. `PCTUS2024012345`) per USPTO ODP Release 3.6 (2026-04-10). Previously emitted a non-standard 12-character form (e.g. `PCTUS2412345`). Affects every endpoint that takes a PCT application number, including `get_pct`, `get_application_by_number`, and `get_IFW_metadata(PCT_app_number=...)`. 2-digit year inputs are expanded via a sliding window (YY≥78 → 19YY, else 20YY). Legacy 12-character PCT strings are no longer accepted as input.
13
+
14
+ ### Deprecated
15
+
16
+ - `Address.country_or_state_code` — USPTO ODP Release 3.6 removed `countryOrStateCode` from the Patent Assignment API response; the location data now lives in `geographicRegionCode` (already exposed as `Address.geographic_region_code`). The attribute remains for backward compatibility and will be `None` for responses from updated endpoints.
17
+
8
18
  ## [0.4.5] - 2026-03-11
9
19
 
10
20
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyUSPTO
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: A Modern Python client for accessing the United States Patent and Trademark Office (USPTO) Open Data Portal (ODP) APIs.
5
5
  Author-email: Andrew Piechocki <apiechocki@dunlapcodding.com>
6
6
  License-Expression: MIT
@@ -201,7 +201,7 @@ if app.application_meta_data:
201
201
  wrapper = client.get_IFW_metadata(application_number="18/045,436")
202
202
  wrapper = client.get_IFW_metadata(patent_number="11,234,567")
203
203
  wrapper = client.get_IFW_metadata(publication_number="2023/0012345")
204
- wrapper = client.get_IFW_metadata(PCT_app_number="PCT/US24/12345")
204
+ wrapper = client.get_IFW_metadata(PCT_app_number="PCT/US2024/012345")
205
205
 
206
206
  # Look up USPTO status codes
207
207
  status_codes = client.get_status_codes()
@@ -134,7 +134,7 @@ if app.application_meta_data:
134
134
  wrapper = client.get_IFW_metadata(application_number="18/045,436")
135
135
  wrapper = client.get_IFW_metadata(patent_number="11,234,567")
136
136
  wrapper = client.get_IFW_metadata(publication_number="2023/0012345")
137
- wrapper = client.get_IFW_metadata(PCT_app_number="PCT/US24/12345")
137
+ wrapper = client.get_IFW_metadata(PCT_app_number="PCT/US2024/012345")
138
138
 
139
139
  # Look up USPTO status codes
140
140
  status_codes = client.get_status_codes()
@@ -8,7 +8,7 @@ alabaster==1.0.0
8
8
  # via sphinx
9
9
  babel==2.18.0
10
10
  # via sphinx
11
- certifi==2026.4.22
11
+ certifi==2026.5.20
12
12
  # via requests
13
13
  charset-normalizer==3.4.7
14
14
  # via requests
@@ -16,14 +16,14 @@ colorama==0.4.6
16
16
  # via
17
17
  # pytest
18
18
  # sphinx
19
- coverage==7.14.0
19
+ coverage==7.14.1
20
20
  # via pytest-cov
21
21
  docutils==0.22.4
22
22
  # via
23
23
  # myst-parser
24
24
  # sphinx
25
25
  # sphinx-rtd-theme
26
- idna==3.15
26
+ idna==3.16
27
27
  # via requests
28
28
  imagesize==2.0.0
29
29
  # via sphinx
@@ -76,16 +76,16 @@ pytest-mock==3.15.1
76
76
  # via pyuspto
77
77
  pyyaml==6.0.3
78
78
  # via myst-parser
79
- requests==2.34.0
79
+ requests==2.34.2
80
80
  # via
81
81
  # pyUSPTO (pyproject.toml)
82
82
  # pyuspto
83
83
  # sphinx
84
84
  roman-numerals==4.1.0
85
85
  # via sphinx
86
- ruff==0.15.12
86
+ ruff==0.15.14
87
87
  # via pyuspto
88
- snowballstemmer==3.0.1
88
+ snowballstemmer==3.1.0
89
89
  # via sphinx
90
90
  sphinx==9.1.0
91
91
  # via
@@ -115,7 +115,7 @@ sphinxcontrib-qthelp==2.0.0
115
115
  # via sphinx
116
116
  sphinxcontrib-serializinghtml==2.0.0
117
117
  # via sphinx
118
- types-requests==2.33.0.20260513
118
+ types-requests==2.33.0.20260518
119
119
  # via pyuspto
120
120
  typing-extensions==4.15.0
121
121
  # via
@@ -4,13 +4,13 @@
4
4
  #
5
5
  # pip-compile --output-file=requirements.txt --strip-extras pyproject.toml
6
6
  #
7
- certifi==2026.4.22
7
+ certifi==2026.5.20
8
8
  # via requests
9
9
  charset-normalizer==3.4.7
10
10
  # via requests
11
- idna==3.15
11
+ idna==3.16
12
12
  # via requests
13
- requests==2.34.0
13
+ requests==2.34.2
14
14
  # via pyUSPTO (pyproject.toml)
15
15
  tzdata==2026.2
16
16
  # via pyUSPTO (pyproject.toml)
@@ -93,7 +93,8 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
93
93
  Application numbers are either:
94
94
  - 8 digits (e.g., "16123456")
95
95
  - Series code format: 2 digits + "/" + 6 digits (e.g., "08/123456")
96
- - PCT format: "PCT/US2024/012345" → "PCTUS2412345"
96
+ - PCT format: "PCT/US2024/012345" → "PCTUS2024012345"
97
+ (standardized 15-character form per USPTO ODP Release 3.6, 2026-04-10)
97
98
 
98
99
  This method removes common separators (commas, spaces) while preserving
99
100
  the "/" in series code format.
@@ -103,7 +104,8 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
103
104
  spaces, or other formatting.
104
105
 
105
106
  Returns:
106
- str: Sanitized application number (either "NNNNNNNN" or "NN/NNNNNN").
107
+ str: Sanitized application number ("NNNNNNNN", "NN/NNNNNN", or
108
+ "PCT" + 2-char country + 4-digit year + 6-digit serial).
107
109
 
108
110
  Raises:
109
111
  ValueError: If the format is invalid.
@@ -117,19 +119,28 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
117
119
  "08/123456"
118
120
  >>> client.sanitize_application_number("08/123,456")
119
121
  "08/123456"
122
+ >>> client.sanitize_application_number("PCT/US2024/012345")
123
+ "PCTUS2024012345"
120
124
  """
121
125
  if not input_number or not input_number.strip():
122
126
  raise ValueError("Application number cannot be empty")
123
127
 
124
128
  raw = input_number.strip()
125
129
 
126
- # --- NEW: Handle PCT formats ---
127
- # Example: "PCT/US2024/012345" -> "PCTUS2412345"
130
+ # Handle PCT formats. Output is the standardized 15-char form:
131
+ # "PCT" + 2-char country + 4-digit year + 6-digit zero-padded serial.
132
+ # Example: "PCT/US2024/012345" -> "PCTUS2024012345"
128
133
  if raw.startswith("PCT"):
129
134
  parts = raw.split("/")
130
135
  if len(parts) == 1:
131
- # Already sanitized (e.g. "PCTUS0812705"), return as-is
132
- return raw
136
+ # No slashes — only accept input that is already in the 15-char form.
137
+ body = raw[3:]
138
+ if len(body) == 12 and body[:2].isalpha() and body[2:].isdigit():
139
+ return raw
140
+ raise ValueError(
141
+ f"Invalid PCT application format: {input_number}. "
142
+ "Expected PCT/CCYYYY/NNNNNN or PCTCCYYYYNNNNNN."
143
+ )
133
144
  if len(parts) != 3:
134
145
  raise ValueError(
135
146
  f"Invalid PCT application format: {input_number}. "
@@ -140,6 +151,10 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
140
151
 
141
152
  # country_year can be "US2024" or "US24"
142
153
  country = country_year[:2]
154
+ if len(country) != 2 or not country.isalpha():
155
+ raise ValueError(
156
+ f"Invalid PCT country code in: {country_year}. Expected 2 letters."
157
+ )
143
158
 
144
159
  year_part = country_year[2:]
145
160
  if not year_part.isdigit():
@@ -147,24 +162,28 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
147
162
  f"Invalid PCT year in: {country_year}. Must be digits."
148
163
  )
149
164
 
150
- # Normalize:
151
- # "2024" -> "24"
152
- # "24" -> "24"
165
+ # Normalize to 4-digit year. 2-digit input uses a sliding window
166
+ # tied to the PCT system's start year (1978): YY>=78 -> 19YY, else 20YY.
153
167
  if len(year_part) == 4:
154
- year = year_part[-2:]
155
- elif len(year_part) == 2:
156
168
  year = year_part
169
+ elif len(year_part) == 2:
170
+ yy = int(year_part)
171
+ year = f"19{year_part}" if yy >= 78 else f"20{year_part}"
157
172
  else:
158
173
  raise ValueError(
159
174
  f"Invalid PCT year length in: {country_year}. "
160
175
  "Expected CCYYYY or CCYY."
161
176
  )
162
177
 
163
- # Serial must be digits only
178
+ # Serial must be digits only, and fit in 6 digits.
164
179
  if not serial.isdigit():
165
180
  raise ValueError(f"Invalid PCT serial: {serial}. Must be numeric.")
181
+ if len(serial) > 6:
182
+ raise ValueError(
183
+ f"Invalid PCT serial: {serial}. Must be at most 6 digits."
184
+ )
166
185
 
167
- return f"PCT{country}{year}{serial.lstrip('0')}"
186
+ return f"PCT{country}{year}{serial.zfill(6)}"
168
187
 
169
188
  # Strip whitespace and remove commas/spaces
170
189
  cleaned = raw.replace(",", "").replace(" ", "")
@@ -5,7 +5,7 @@ including API keys, base URLs, and HTTP transport settings.
5
5
  """
6
6
 
7
7
  import os
8
- from typing import TYPE_CHECKING
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  import requests
@@ -15,6 +15,36 @@ from pyUSPTO.http_config import HTTPConfig
15
15
  DEFAULT_BASE_URL = "https://api.uspto.gov"
16
16
 
17
17
 
18
+ def _env_values() -> dict[str, Any]:
19
+ """Build a dict of config values from environment variables (or defaults)."""
20
+ return {
21
+ "api_key": os.environ.get("USPTO_API_KEY") or None,
22
+ "bulk_data_base_url": os.environ.get(
23
+ "USPTO_BULK_DATA_BASE_URL", DEFAULT_BASE_URL
24
+ ),
25
+ "patent_data_base_url": os.environ.get(
26
+ "USPTO_PATENT_DATA_BASE_URL", DEFAULT_BASE_URL
27
+ ),
28
+ "petition_decisions_base_url": os.environ.get(
29
+ "USPTO_PETITION_DECISIONS_BASE_URL", DEFAULT_BASE_URL
30
+ ),
31
+ "ptab_base_url": os.environ.get("USPTO_PTAB_BASE_URL", DEFAULT_BASE_URL),
32
+ "enriched_citations_base_url": os.environ.get(
33
+ "USPTO_ENRICHED_CITATIONS_BASE_URL", DEFAULT_BASE_URL
34
+ ),
35
+ "oa_actions_base_url": os.environ.get(
36
+ "USPTO_OA_ACTIONS_BASE_URL", DEFAULT_BASE_URL
37
+ ),
38
+ "oa_rejections_base_url": os.environ.get(
39
+ "USPTO_OA_REJECTIONS_BASE_URL", DEFAULT_BASE_URL
40
+ ),
41
+ "oa_citations_base_url": os.environ.get(
42
+ "USPTO_OA_CITATIONS_BASE_URL", DEFAULT_BASE_URL
43
+ ),
44
+ "http_config": HTTPConfig.from_env(),
45
+ }
46
+
47
+
18
48
  class USPTOConfig:
19
49
  """Configuration for USPTO API clients.
20
50
 
@@ -25,19 +55,22 @@ class USPTOConfig:
25
55
  def __init__(
26
56
  self,
27
57
  api_key: str | None = None,
28
- bulk_data_base_url: str = DEFAULT_BASE_URL,
29
- patent_data_base_url: str = DEFAULT_BASE_URL,
30
- petition_decisions_base_url: str = DEFAULT_BASE_URL,
31
- ptab_base_url: str = DEFAULT_BASE_URL,
32
- enriched_citations_base_url: str = DEFAULT_BASE_URL,
33
- oa_actions_base_url: str = DEFAULT_BASE_URL,
34
- oa_rejections_base_url: str = DEFAULT_BASE_URL,
35
- oa_citations_base_url: str = DEFAULT_BASE_URL,
58
+ bulk_data_base_url: str | None = None,
59
+ patent_data_base_url: str | None = None,
60
+ petition_decisions_base_url: str | None = None,
61
+ ptab_base_url: str | None = None,
62
+ enriched_citations_base_url: str | None = None,
63
+ oa_actions_base_url: str | None = None,
64
+ oa_rejections_base_url: str | None = None,
65
+ oa_citations_base_url: str | None = None,
36
66
  http_config: HTTPConfig | None = None,
37
67
  include_raw_data: bool = False,
38
68
  ):
39
69
  """Initialize the USPTOConfig.
40
70
 
71
+ Each field falls back to its environment variable (or the package
72
+ default) when the caller does not pass a value.
73
+
41
74
  Args:
42
75
  api_key: API key for authentication, defaults to USPTO_API_KEY environment variable
43
76
  bulk_data_base_url: Base URL for the Bulk Data API
@@ -51,21 +84,43 @@ class USPTOConfig:
51
84
  http_config: Optional HTTPConfig for request handling (uses defaults if None)
52
85
  include_raw_data: If True, store raw JSON in response objects for debugging (default: False)
53
86
  """
54
- # Use environment variable only if api_key is None, not if it's an empty string
55
- self.api_key = (
56
- api_key if api_key is not None else os.environ.get("USPTO_API_KEY")
87
+ env = _env_values()
88
+
89
+ self.api_key = api_key if api_key else env["api_key"]
90
+ self.bulk_data_base_url = (
91
+ bulk_data_base_url if bulk_data_base_url else env["bulk_data_base_url"]
92
+ )
93
+ self.patent_data_base_url = (
94
+ patent_data_base_url
95
+ if patent_data_base_url
96
+ else env["patent_data_base_url"]
97
+ )
98
+ self.petition_decisions_base_url = (
99
+ petition_decisions_base_url
100
+ if petition_decisions_base_url
101
+ else env["petition_decisions_base_url"]
57
102
  )
58
- self.bulk_data_base_url = bulk_data_base_url
59
- self.patent_data_base_url = patent_data_base_url
60
- self.petition_decisions_base_url = petition_decisions_base_url
61
- self.ptab_base_url = ptab_base_url
62
- self.enriched_citations_base_url = enriched_citations_base_url
63
- self.oa_actions_base_url = oa_actions_base_url
64
- self.oa_rejections_base_url = oa_rejections_base_url
65
- self.oa_citations_base_url = oa_citations_base_url
66
-
67
- # Use provided HTTPConfig or create default
68
- self.http_config = http_config if http_config is not None else HTTPConfig()
103
+ self.ptab_base_url = ptab_base_url if ptab_base_url else env["ptab_base_url"]
104
+ self.enriched_citations_base_url = (
105
+ enriched_citations_base_url
106
+ if enriched_citations_base_url
107
+ else env["enriched_citations_base_url"]
108
+ )
109
+ self.oa_actions_base_url = (
110
+ oa_actions_base_url if oa_actions_base_url else env["oa_actions_base_url"]
111
+ )
112
+ self.oa_rejections_base_url = (
113
+ oa_rejections_base_url
114
+ if oa_rejections_base_url
115
+ else env["oa_rejections_base_url"]
116
+ )
117
+ self.oa_citations_base_url = (
118
+ oa_citations_base_url
119
+ if oa_citations_base_url
120
+ else env["oa_citations_base_url"]
121
+ )
122
+
123
+ self.http_config = http_config if http_config else env["http_config"]
69
124
 
70
125
  # Control whether to include raw JSON data in response objects
71
126
  self.include_raw_data = include_raw_data
@@ -80,33 +135,7 @@ class USPTOConfig:
80
135
  Returns:
81
136
  USPTOConfig instance with values from environment
82
137
  """
83
- return cls(
84
- api_key=os.environ.get("USPTO_API_KEY"),
85
- bulk_data_base_url=os.environ.get(
86
- "USPTO_BULK_DATA_BASE_URL", DEFAULT_BASE_URL
87
- ),
88
- patent_data_base_url=os.environ.get(
89
- "USPTO_PATENT_DATA_BASE_URL", DEFAULT_BASE_URL
90
- ),
91
- petition_decisions_base_url=os.environ.get(
92
- "USPTO_PETITION_DECISIONS_BASE_URL", DEFAULT_BASE_URL
93
- ),
94
- ptab_base_url=os.environ.get("USPTO_PTAB_BASE_URL", DEFAULT_BASE_URL),
95
- enriched_citations_base_url=os.environ.get(
96
- "USPTO_ENRICHED_CITATIONS_BASE_URL", DEFAULT_BASE_URL
97
- ),
98
- oa_actions_base_url=os.environ.get(
99
- "USPTO_OA_ACTIONS_BASE_URL", DEFAULT_BASE_URL
100
- ),
101
- oa_rejections_base_url=os.environ.get(
102
- "USPTO_OA_REJECTIONS_BASE_URL", DEFAULT_BASE_URL
103
- ),
104
- oa_citations_base_url=os.environ.get(
105
- "USPTO_OA_CITATIONS_BASE_URL", DEFAULT_BASE_URL
106
- ),
107
- # Also read HTTP config from environment
108
- http_config=HTTPConfig.from_env(),
109
- )
138
+ return cls(**_env_values())
110
139
 
111
140
  @property
112
141
  def session(self) -> "requests.Session":
@@ -480,7 +480,11 @@ class Address:
480
480
  country_name: Full name of the country (e.g., "United States").
481
481
  postal_address_category: Category of the address (e.g., "MAILING_ADDRESS").
482
482
  correspondent_name_text: Name of the correspondent at this address.
483
- country_or_state_code: Country or state code.
483
+ country_or_state_code: Country or state code. Deprecated: USPTO ODP
484
+ Release 3.6 (2026-04-10) removed this field from the Patent
485
+ Assignment API response. Read ``geographic_region_code`` instead;
486
+ this attribute will be ``None`` for responses from updated
487
+ endpoints.
484
488
  ict_state_code: International code for the state/region (USPTO format).
485
489
  ict_country_code: International code for the country (USPTO format).
486
490
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyUSPTO
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: A Modern Python client for accessing the United States Patent and Trademark Office (USPTO) Open Data Portal (ODP) APIs.
5
5
  Author-email: Andrew Piechocki <apiechocki@dunlapcodding.com>
6
6
  License-Expression: MIT
@@ -201,7 +201,7 @@ if app.application_meta_data:
201
201
  wrapper = client.get_IFW_metadata(application_number="18/045,436")
202
202
  wrapper = client.get_IFW_metadata(patent_number="11,234,567")
203
203
  wrapper = client.get_IFW_metadata(publication_number="2023/0012345")
204
- wrapper = client.get_IFW_metadata(PCT_app_number="PCT/US24/12345")
204
+ wrapper = client.get_IFW_metadata(PCT_app_number="PCT/US2024/012345")
205
205
 
206
206
  # Look up USPTO status codes
207
207
  status_codes = client.get_status_codes()
@@ -1133,9 +1133,7 @@ class TestPaginateSolrResults:
1133
1133
 
1134
1134
  received_bodies: list[dict[str, Any]] = []
1135
1135
 
1136
- def mock_search(
1137
- post_body: dict[str, Any] | None = None, **kwargs: Any
1138
- ) -> Any:
1136
+ def mock_search(post_body: dict[str, Any] | None = None, **kwargs: Any) -> Any:
1139
1137
  if post_body:
1140
1138
  received_bodies.append(post_body.copy())
1141
1139
  start = post_body["start"] if post_body else 0
@@ -1183,9 +1181,7 @@ class TestPaginateSolrResults:
1183
1181
  test_client = TestClient(base_url="https://api.test.com")
1184
1182
  test_client.config._session = mock_session
1185
1183
 
1186
- with patch.object(
1187
- test_client, "search", wraps=test_client.search
1188
- ) as spy:
1184
+ with patch.object(test_client, "search", wraps=test_client.search) as spy:
1189
1185
  results = list(
1190
1186
  test_client.paginate_solr_results(
1191
1187
  method_name="search",
@@ -1198,9 +1194,7 @@ class TestPaginateSolrResults:
1198
1194
  assert results == ["doc1"]
1199
1195
  spy.assert_called_once_with(query="test", start=0, rows=10)
1200
1196
 
1201
- def test_paginate_solr_results_custom_rows(
1202
- self, mock_session: MagicMock
1203
- ) -> None:
1197
+ def test_paginate_solr_results_custom_rows(self, mock_session: MagicMock) -> None:
1204
1198
  """Test that rows is extracted from post_body and used as page size."""
1205
1199
  response = MagicMock()
1206
1200
  response.count = 1
@@ -1208,9 +1202,7 @@ class TestPaginateSolrResults:
1208
1202
 
1209
1203
  received_bodies: list[dict[str, Any]] = []
1210
1204
 
1211
- def mock_search(
1212
- post_body: dict[str, Any] | None = None, **kwargs: Any
1213
- ) -> Any:
1205
+ def mock_search(post_body: dict[str, Any] | None = None, **kwargs: Any) -> Any:
1214
1206
  if post_body:
1215
1207
  received_bodies.append(post_body.copy())
1216
1208
  return response
@@ -1231,9 +1223,7 @@ class TestPaginateSolrResults:
1231
1223
  assert received_bodies[0]["rows"] == 100
1232
1224
  assert received_bodies[0]["start"] == 0
1233
1225
 
1234
- def test_paginate_solr_results_default_rows(
1235
- self, mock_session: MagicMock
1236
- ) -> None:
1226
+ def test_paginate_solr_results_default_rows(self, mock_session: MagicMock) -> None:
1237
1227
  """Test that rows defaults to 25 when not specified."""
1238
1228
  response = MagicMock()
1239
1229
  response.count = 1
@@ -1241,9 +1231,7 @@ class TestPaginateSolrResults:
1241
1231
 
1242
1232
  received_bodies: list[dict[str, Any]] = []
1243
1233
 
1244
- def mock_search(
1245
- post_body: dict[str, Any] | None = None, **kwargs: Any
1246
- ) -> Any:
1234
+ def mock_search(post_body: dict[str, Any] | None = None, **kwargs: Any) -> Any:
1247
1235
  if post_body:
1248
1236
  received_bodies.append(post_body.copy())
1249
1237
  return response
@@ -1271,9 +1259,7 @@ class TestPaginateSolrResults:
1271
1259
 
1272
1260
  post_body = {"criteria": "test", "start": 10, "rows": 50}
1273
1261
 
1274
- with pytest.raises(
1275
- ValueError, match="Cannot specify 'start' in post_body"
1276
- ):
1262
+ with pytest.raises(ValueError, match="Cannot specify 'start' in post_body"):
1277
1263
  list(
1278
1264
  client.paginate_solr_results(
1279
1265
  method_name="search",
@@ -1289,9 +1275,7 @@ class TestPaginateSolrResults:
1289
1275
  client: BaseUSPTOClient[Any] = BaseUSPTOClient(base_url="https://api.test.com")
1290
1276
  client.config._session = mock_session
1291
1277
 
1292
- with pytest.raises(
1293
- ValueError, match="Cannot specify 'start' in kwargs"
1294
- ):
1278
+ with pytest.raises(ValueError, match="Cannot specify 'start' in kwargs"):
1295
1279
  list(
1296
1280
  client.paginate_solr_results(
1297
1281
  method_name="search",
@@ -1353,9 +1337,7 @@ class TestPaginateSolrResults:
1353
1337
 
1354
1338
  received_bodies: list[dict[str, Any]] = []
1355
1339
 
1356
- def mock_search(
1357
- post_body: dict[str, Any] | None = None, **kwargs: Any
1358
- ) -> Any:
1340
+ def mock_search(post_body: dict[str, Any] | None = None, **kwargs: Any) -> Any:
1359
1341
  if post_body:
1360
1342
  received_bodies.append(post_body.copy())
1361
1343
  return response
@@ -182,8 +182,7 @@ class TestOACitationsClientSearch:
182
182
 
183
183
  call_kwargs = mock_get_model.call_args.kwargs
184
184
  assert (
185
- call_kwargs["json_data"]["criteria"]
186
- == "patentApplicationNumber:17519936"
185
+ call_kwargs["json_data"]["criteria"] == "patentApplicationNumber:17519936"
187
186
  )
188
187
 
189
188
  def test_legal_section_code_q(
@@ -313,9 +312,7 @@ class TestOACitationsClientSearch:
313
312
  client.search(create_date_time_from_q="2025-01-01")
314
313
 
315
314
  call_kwargs = mock_get_model.call_args.kwargs
316
- assert (
317
- call_kwargs["json_data"]["criteria"] == "createDateTime:>=2025-01-01"
318
- )
315
+ assert call_kwargs["json_data"]["criteria"] == "createDateTime:>=2025-01-01"
319
316
 
320
317
  def test_create_date_time_to_only(
321
318
  self,
@@ -328,9 +325,7 @@ class TestOACitationsClientSearch:
328
325
  client.search(create_date_time_to_q="2025-06-30")
329
326
 
330
327
  call_kwargs = mock_get_model.call_args.kwargs
331
- assert (
332
- call_kwargs["json_data"]["criteria"] == "createDateTime:<=2025-06-30"
333
- )
328
+ assert call_kwargs["json_data"]["criteria"] == "createDateTime:<=2025-06-30"
334
329
 
335
330
  def test_combined_convenience_params(
336
331
  self,
@@ -457,9 +452,7 @@ class TestOACitationsClientPaginate:
457
452
  rows=10,
458
453
  )
459
454
 
460
- def test_passes_post_body(
461
- self, oa_citations_client: OACitationsClient
462
- ) -> None:
455
+ def test_passes_post_body(self, oa_citations_client: OACitationsClient) -> None:
463
456
  with patch.object(
464
457
  oa_citations_client, "paginate_solr_results", autospec=True
465
458
  ) as mock_paginate:
@@ -479,9 +472,7 @@ class TestOACitationsClientPaginate:
479
472
  mock_record: OACitationRecord,
480
473
  mock_response_with_data: OACitationsResponse,
481
474
  ) -> None:
482
- with patch.object(
483
- oa_citations_client, "search", autospec=True
484
- ) as mock_search:
475
+ with patch.object(oa_citations_client, "search", autospec=True) as mock_search:
485
476
  # Two pages: first has data, second is empty to stop pagination
486
477
  mock_search.side_effect = [
487
478
  mock_response_with_data,
@@ -399,7 +399,9 @@ class TestOARejectionsClientPaginate:
399
399
  oa_rejections_client, "paginate_solr_results", autospec=True
400
400
  ) as mock_paginate:
401
401
  mock_paginate.return_value = iter([])
402
- oa_rejections_client.paginate(legacy_document_code_identifier_q="CTNF", rows=10)
402
+ oa_rejections_client.paginate(
403
+ legacy_document_code_identifier_q="CTNF", rows=10
404
+ )
403
405
 
404
406
  mock_paginate.assert_called_once_with(
405
407
  method_name="search",
@@ -429,9 +431,7 @@ class TestOARejectionsClientPaginate:
429
431
  mock_record: OARejectionsRecord,
430
432
  mock_response_with_data: OARejectionsResponse,
431
433
  ) -> None:
432
- with patch.object(
433
- oa_rejections_client, "search", autospec=True
434
- ) as mock_search:
434
+ with patch.object(oa_rejections_client, "search", autospec=True) as mock_search:
435
435
  mock_search.side_effect = [
436
436
  mock_response_with_data,
437
437
  OARejectionsResponse(num_found=0, start=0, docs=[]),