pyUSPTO 0.4.3__tar.gz → 0.4.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 (129) hide show
  1. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/workflows/publish-to-test-pypi.yml +1 -1
  2. {pyuspto-0.4.3 → pyuspto-0.4.4}/PKG-INFO +4 -3
  3. {pyuspto-0.4.3 → pyuspto-0.4.4}/README.md +2 -1
  4. {pyuspto-0.4.3 → pyuspto-0.4.4}/pyproject.toml +7 -2
  5. {pyuspto-0.4.3 → pyuspto-0.4.4}/requirements-dev.txt +3 -3
  6. {pyuspto-0.4.3 → pyuspto-0.4.4}/requirements.txt +1 -1
  7. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/patent_data.py +75 -1
  8. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO.egg-info/PKG-INFO +4 -3
  9. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_patent_data_clients.py +163 -3
  10. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/test_patent_data_integration.py +49 -0
  11. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  12. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  13. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/ISSUE_TEMPLATE/documentation-updates.yml +0 -0
  14. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  15. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/dependabot.yml +0 -0
  17. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/workflows/publish-docs.yaml +0 -0
  18. {pyuspto-0.4.3 → pyuspto-0.4.4}/.github/workflows/python-tests.yml +0 -0
  19. {pyuspto-0.4.3 → pyuspto-0.4.4}/.gitignore +0 -0
  20. {pyuspto-0.4.3 → pyuspto-0.4.4}/.readthedocs.yaml +0 -0
  21. {pyuspto-0.4.3 → pyuspto-0.4.4}/ADVANCED.md +0 -0
  22. {pyuspto-0.4.3 → pyuspto-0.4.4}/CHANGELOG.md +0 -0
  23. {pyuspto-0.4.3 → pyuspto-0.4.4}/CODE_OF_CONDUCT.md +0 -0
  24. {pyuspto-0.4.3 → pyuspto-0.4.4}/CONTRIBUTING.md +0 -0
  25. {pyuspto-0.4.3 → pyuspto-0.4.4}/LICENSE +0 -0
  26. {pyuspto-0.4.3 → pyuspto-0.4.4}/_version.pyi +0 -0
  27. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/Makefile +0 -0
  28. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/_static/.gitkeep +0 -0
  29. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/advanced.rst +0 -0
  30. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/bulk_data.rst +0 -0
  31. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/index.rst +0 -0
  32. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/patent_data.rst +0 -0
  33. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/petition_decisions.rst +0 -0
  34. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/ptab_appeals.rst +0 -0
  35. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/ptab_interferences.rst +0 -0
  36. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/clients/ptab_trials.rst +0 -0
  37. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/config/config.rst +0 -0
  38. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/config/http_config.rst +0 -0
  39. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/config/index.rst +0 -0
  40. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/exceptions.rst +0 -0
  41. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/index.rst +0 -0
  42. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/base.rst +0 -0
  43. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/bulk_data.rst +0 -0
  44. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/index.rst +0 -0
  45. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/patent_data.rst +0 -0
  46. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/petition_decisions.rst +0 -0
  47. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/ptab.rst +0 -0
  48. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/models/utils.rst +0 -0
  49. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/api/warnings.rst +0 -0
  50. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/conf.py +0 -0
  51. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/configuration.rst +0 -0
  52. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/development.rst +0 -0
  53. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/bulk_data.rst +0 -0
  54. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/ifw_example.rst +0 -0
  55. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/index.rst +0 -0
  56. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/patent_data.rst +0 -0
  57. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/petition_decisions.rst +0 -0
  58. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/ptab_appeals.rst +0 -0
  59. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/ptab_interferences.rst +0 -0
  60. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/examples/ptab_trials.rst +0 -0
  61. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/index.rst +0 -0
  62. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/installation.rst +0 -0
  63. {pyuspto-0.4.3 → pyuspto-0.4.4}/docs/source/quickstart.rst +0 -0
  64. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/bulk_data_example.py +0 -0
  65. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/error_handling_example.py +0 -0
  66. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/ifw_example.py +0 -0
  67. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/patent_data_example.py +0 -0
  68. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/petition_decisions_example.py +0 -0
  69. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/ptab_appeals_example.py +0 -0
  70. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/ptab_interferences_example.py +0 -0
  71. {pyuspto-0.4.3 → pyuspto-0.4.4}/examples/ptab_trials_example.py +0 -0
  72. {pyuspto-0.4.3 → pyuspto-0.4.4}/pyUSPTO.code-workspace +0 -0
  73. {pyuspto-0.4.3 → pyuspto-0.4.4}/setup.cfg +0 -0
  74. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/__init__.py +0 -0
  75. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/__init__.py +0 -0
  76. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/base.py +0 -0
  77. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/bulk_data.py +0 -0
  78. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/petition_decisions.py +0 -0
  79. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/ptab_appeals.py +0 -0
  80. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/ptab_interferences.py +0 -0
  81. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/clients/ptab_trials.py +0 -0
  82. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/config.py +0 -0
  83. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/exceptions.py +0 -0
  84. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/http_config.py +0 -0
  85. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/__init__.py +0 -0
  86. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/base.py +0 -0
  87. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/bulk_data.py +0 -0
  88. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/patent_data.py +0 -0
  89. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/petition_decisions.py +0 -0
  90. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/ptab.py +0 -0
  91. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/models/utils.py +0 -0
  92. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/py.typed +0 -0
  93. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/utils/__init__.py +0 -0
  94. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO/warnings.py +0 -0
  95. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO.egg-info/SOURCES.txt +0 -0
  96. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO.egg-info/dependency_links.txt +0 -0
  97. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO.egg-info/requires.txt +0 -0
  98. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO.egg-info/top_level.txt +0 -0
  99. {pyuspto-0.4.3 → pyuspto-0.4.4}/src/pyUSPTO.egg-info/zip-safe +0 -0
  100. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/__init__.py +0 -0
  101. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/__init__.py +0 -0
  102. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_base.py +0 -0
  103. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_bulk_data_clients.py +0 -0
  104. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_petition_decision_clients.py +0 -0
  105. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_ptab_appeals_client.py +0 -0
  106. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_ptab_interferences_client.py +0 -0
  107. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/clients/test_ptab_trials_client.py +0 -0
  108. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/conftest.py +0 -0
  109. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/__init__.py +0 -0
  110. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/conftest.py +0 -0
  111. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/test_bulk_data_integration.py +0 -0
  112. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/test_petition_decisions_integration.py +0 -0
  113. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/test_ptab_appeals_integration.py +0 -0
  114. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/test_ptab_interferences_integration.py +0 -0
  115. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/integration/test_ptab_trials_integration.py +0 -0
  116. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/models/__init__.py +0 -0
  117. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/models/test_bulk_data_models.py +0 -0
  118. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/models/test_patent_data_models.py +0 -0
  119. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/models/test_petition_decision_models.py +0 -0
  120. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/models/test_ptab_models.py +0 -0
  121. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/models/test_utils.py +0 -0
  122. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/test_config.py +0 -0
  123. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/test_edge_cases.py +0 -0
  124. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/test_exceptions.py +0 -0
  125. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/test_http_config.py +0 -0
  126. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/test_init.py +0 -0
  127. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/test_version.py +0 -0
  128. {pyuspto-0.4.3 → pyuspto-0.4.4}/tests/utils/__init__.py +0 -0
  129. {pyuspto-0.4.3 → pyuspto-0.4.4}/tox.ini +0 -0
@@ -12,7 +12,7 @@ on:
12
12
 
13
13
  jobs:
14
14
  build:
15
- name: Build distribution 📦
15
+ name: Build distribution
16
16
  runs-on: ubuntu-latest
17
17
 
18
18
  steps:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyUSPTO
3
- Version: 0.4.3
3
+ Version: 0.4.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
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
19
  Classifier: Operating System :: OS Independent
20
- Classifier: Development Status :: 4 - Beta
20
+ Classifier: Development Status :: 5 - Production/Stable
21
21
  Classifier: Environment :: Console
22
22
  Classifier: Intended Audience :: Developers
23
23
  Classifier: Intended Audience :: Education
@@ -65,12 +65,13 @@ Requires-Dist: pyUSPTO[docs]; extra == "dev"
65
65
  Requires-Dist: pyUSPTO[lint]; extra == "dev"
66
66
  Dynamic: license-file
67
67
 
68
- # pyUSPTO
68
+ # USPTO Open Data Portal Client Library
69
69
 
70
70
  [![PyPI version](https://badge.fury.io/py/pyUSPTO.svg)](https://badge.fury.io/py/pyUSPTO)
71
71
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
72
72
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
73
73
  [![Read the Docs](https://img.shields.io/readthedocs/pyuspto)](https://pyuspto.readthedocs.io/en/latest/)
74
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyuspto?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLUE&right_color=GREY&left_text=downloads)](https://pepy.tech/projects/pyuspto)
74
75
 
75
76
  A Python client library for interacting with the United Stated Patent and Trademark Office (USPTO) [Open Data Portal](https://data.uspto.gov/home) APIs.
76
77
 
@@ -1,9 +1,10 @@
1
- # pyUSPTO
1
+ # USPTO Open Data Portal Client Library
2
2
 
3
3
  [![PyPI version](https://badge.fury.io/py/pyUSPTO.svg)](https://badge.fury.io/py/pyUSPTO)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
6
6
  [![Read the Docs](https://img.shields.io/readthedocs/pyuspto)](https://pyuspto.readthedocs.io/en/latest/)
7
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyuspto?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLUE&right_color=GREY&left_text=downloads)](https://pepy.tech/projects/pyuspto)
7
8
 
8
9
  A Python client library for interacting with the United Stated Patent and Trademark Office (USPTO) [Open Data Portal](https://data.uspto.gov/home) APIs.
9
10
 
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.13",
21
21
  "Programming Language :: Python :: 3.14",
22
22
  "Operating System :: OS Independent",
23
- "Development Status :: 4 - Beta",
23
+ "Development Status :: 5 - Production/Stable",
24
24
  "Environment :: Console",
25
25
  "Intended Audience :: Developers",
26
26
  "Intended Audience :: Education",
@@ -51,7 +51,12 @@ dependencies = [
51
51
  dynamic = ["version"]
52
52
 
53
53
  [project.optional-dependencies]
54
- test = ["pytest>=9.0.2", "pytest-cov>=7.0.0", "pytest-mock>=3.15.1", "typing_extensions>=4.15.0"]
54
+ test = [
55
+ "pytest>=9.0.2",
56
+ "pytest-cov>=7.0.0",
57
+ "pytest-mock>=3.15.1",
58
+ "typing_extensions>=4.15.0",
59
+ ]
55
60
  docs = [
56
61
  "sphinx>=9.1.0",
57
62
  "sphinx-rtd-theme>=3.1.0",
@@ -14,7 +14,7 @@ babel==2.18.0
14
14
  # via sphinx
15
15
  certifi==2026.2.25
16
16
  # via requests
17
- charset-normalizer==3.4.4
17
+ charset-normalizer==3.4.5
18
18
  # via requests
19
19
  colorama==0.4.6
20
20
  # via
@@ -102,7 +102,7 @@ requests==2.32.5
102
102
  # sphinx-immaterial
103
103
  roman-numerals==4.1.0
104
104
  # via sphinx
105
- ruff==0.15.4
105
+ ruff==0.15.5
106
106
  # via pyuspto
107
107
  snowballstemmer==3.0.1
108
108
  # via sphinx
@@ -115,7 +115,7 @@ sphinx==9.1.0
115
115
  # sphinx-immaterial
116
116
  # sphinx-rtd-theme
117
117
  # sphinxcontrib-jquery
118
- sphinx-autodoc-typehints==3.9.6
118
+ sphinx-autodoc-typehints==3.9.7
119
119
  # via pyuspto
120
120
  sphinx-copybutton==0.5.2
121
121
  # via pyuspto
@@ -6,7 +6,7 @@
6
6
  #
7
7
  certifi==2026.2.25
8
8
  # via requests
9
- charset-normalizer==3.4.4
9
+ charset-normalizer==3.4.5
10
10
  # via requests
11
11
  idna==3.11
12
12
  # via requests
@@ -163,7 +163,7 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
163
163
  if not serial.isdigit():
164
164
  raise ValueError(f"Invalid PCT serial: {serial}. Must be numeric.")
165
165
 
166
- return f"PCT{country}{year}{serial}"
166
+ return f"PCT{country}{year}{serial.lstrip('0')}"
167
167
 
168
168
  # Strip whitespace and remove commas/spaces
169
169
  cleaned = raw.replace(",", "").replace(" ", "")
@@ -1039,6 +1039,80 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
1039
1039
  overwrite=overwrite,
1040
1040
  )
1041
1041
 
1042
+ def _resolve_by_search(self, **search_kwargs: Any) -> PatentFileWrapper | None:
1043
+ """Search for an application and return the first matching wrapper.
1044
+
1045
+ This is a shared helper for convenience methods that resolve
1046
+ non-application-number identifiers to a PatentFileWrapper.
1047
+
1048
+ Args:
1049
+ **search_kwargs: Keyword arguments passed to search_applications().
1050
+
1051
+ Returns:
1052
+ Optional[PatentFileWrapper]: The first matching wrapper, or None.
1053
+ """
1054
+ pdr = self.search_applications(**search_kwargs, limit=1)
1055
+ if pdr.patent_file_wrapper_data_bag:
1056
+ return pdr.patent_file_wrapper_data_bag[0]
1057
+ return None
1058
+
1059
+ def get_patent(self, patent_number: str) -> PatentFileWrapper | None:
1060
+ """Retrieve application metadata by patent number.
1061
+
1062
+ Searches the USPTO API for the given patent number and returns
1063
+ the corresponding PatentFileWrapper. This is a lightweight lookup
1064
+ that does not fetch the full document bag.
1065
+
1066
+ Args:
1067
+ patent_number (str): The USPTO patent number (e.g., "11000000").
1068
+
1069
+ Returns:
1070
+ Optional[PatentFileWrapper]: The matching patent file wrapper,
1071
+ or None if not found.
1072
+ """
1073
+ return self._resolve_by_search(patent_number_q=patent_number)
1074
+
1075
+ def get_publication(self, publication_number: str) -> PatentFileWrapper | None:
1076
+ """Retrieve application metadata by publication number.
1077
+
1078
+ Searches the USPTO API for the given pre-grant publication number
1079
+ and returns the corresponding PatentFileWrapper. This is a lightweight
1080
+ lookup that does not fetch the full document bag.
1081
+
1082
+ Args:
1083
+ publication_number (str): The USPTO publication number
1084
+ (e.g., "20230123456").
1085
+
1086
+ Returns:
1087
+ Optional[PatentFileWrapper]: The matching patent file wrapper,
1088
+ or None if not found.
1089
+ """
1090
+ return self._resolve_by_search(earliestPublicationNumber_q=publication_number)
1091
+
1092
+ def get_pct(self, pct_number: str) -> PatentFileWrapper | None:
1093
+ """Retrieve application metadata by PCT number.
1094
+
1095
+ Accepts both PCT application numbers and PCT publication numbers.
1096
+ The format is auto-detected:
1097
+
1098
+ - PCT application numbers (starting with "PCT") are resolved via
1099
+ direct lookup using get_application_by_number.
1100
+ - PCT publication numbers (e.g., "WO2024012345A1") are resolved
1101
+ via search.
1102
+
1103
+ Args:
1104
+ pct_number (str): A PCT application number (e.g.,
1105
+ "PCT/US2024/012345") or PCT publication number
1106
+ (e.g., "WO2024012345A1").
1107
+
1108
+ Returns:
1109
+ Optional[PatentFileWrapper]: The matching patent file wrapper,
1110
+ or None if not found.
1111
+ """
1112
+ if pct_number.strip().upper().startswith("PCT"):
1113
+ return self.get_application_by_number(application_number=pct_number)
1114
+ return self._resolve_by_search(pctPublicationNumber_q=pct_number)
1115
+
1042
1116
  def get_IFW_metadata(
1043
1117
  self,
1044
1118
  *,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyUSPTO
3
- Version: 0.4.3
3
+ Version: 0.4.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
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
19
  Classifier: Operating System :: OS Independent
20
- Classifier: Development Status :: 4 - Beta
20
+ Classifier: Development Status :: 5 - Production/Stable
21
21
  Classifier: Environment :: Console
22
22
  Classifier: Intended Audience :: Developers
23
23
  Classifier: Intended Audience :: Education
@@ -65,12 +65,13 @@ Requires-Dist: pyUSPTO[docs]; extra == "dev"
65
65
  Requires-Dist: pyUSPTO[lint]; extra == "dev"
66
66
  Dynamic: license-file
67
67
 
68
- # pyUSPTO
68
+ # USPTO Open Data Portal Client Library
69
69
 
70
70
  [![PyPI version](https://badge.fury.io/py/pyUSPTO.svg)](https://badge.fury.io/py/pyUSPTO)
71
71
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
72
72
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
73
73
  [![Read the Docs](https://img.shields.io/readthedocs/pyuspto)](https://pyuspto.readthedocs.io/en/latest/)
74
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyuspto?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLUE&right_color=GREY&left_text=downloads)](https://pepy.tech/projects/pyuspto)
74
75
 
75
76
  A Python client library for interacting with the United Stated Patent and Trademark Office (USPTO) [Open Data Portal](https://data.uspto.gov/home) APIs.
76
77
 
@@ -1334,7 +1334,7 @@ class TestGetIFW:
1334
1334
  # Should call get_application_by_number first
1335
1335
  assert mock_make_request.call_args_list[0] == call(
1336
1336
  method="GET",
1337
- endpoint="api/v1/patent/applications/PCTUS24012345",
1337
+ endpoint="api/v1/patent/applications/PCTUS2412345",
1338
1338
  response_class=PatentDataResponse,
1339
1339
  )
1340
1340
  assert result.application_number_text == mock_patent_file_wrapper.application_number_text
@@ -1348,7 +1348,7 @@ class TestGetIFW:
1348
1348
  """Test PCT application number sanitization with 2-digit year format (US24 vs US2024).
1349
1349
 
1350
1350
  Verifies that PCT numbers with short year format (PCT/US24/012345) are correctly
1351
- sanitized to PCTUS24012345 before making API request.
1351
+ sanitized to PCTUS2412345 before making API request.
1352
1352
 
1353
1353
  Note: This will trigger a data mismatch warning because the mock_patent_file_wrapper
1354
1354
  has application_number_text='12345678' but we're requesting a PCT number.
@@ -1369,7 +1369,7 @@ class TestGetIFW:
1369
1369
  # Should call get_application_by_number first
1370
1370
  assert mock_make_request.call_args_list[0] == call(
1371
1371
  method="GET",
1372
- endpoint="api/v1/patent/applications/PCTUS24012345",
1372
+ endpoint="api/v1/patent/applications/PCTUS2412345",
1373
1373
  response_class=PatentDataResponse,
1374
1374
  )
1375
1375
  assert result.application_number_text == mock_patent_file_wrapper.application_number_text
@@ -1550,6 +1550,166 @@ class TestGetIFW:
1550
1550
  assert isinstance(result.document_bag, DocumentBag)
1551
1551
 
1552
1552
 
1553
+ class TestGetPatent:
1554
+ """Tests for the get_patent convenience method."""
1555
+
1556
+ def test_get_patent_found(
1557
+ self,
1558
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1559
+ mock_patent_file_wrapper: PatentFileWrapper,
1560
+ ) -> None:
1561
+ """Test get_patent returns wrapper when patent is found."""
1562
+ client, mock_make_request = client_with_mocked_request
1563
+ mock_make_request.return_value = PatentDataResponse(
1564
+ count=1, patent_file_wrapper_data_bag=[mock_patent_file_wrapper]
1565
+ )
1566
+
1567
+ patent_num = "11000000"
1568
+ result = client.get_patent(patent_num)
1569
+
1570
+ assert mock_make_request.call_args == call(
1571
+ method="GET",
1572
+ endpoint="api/v1/patent/applications/search",
1573
+ params={
1574
+ "q": f"applicationMetaData.patentNumber:{patent_num}",
1575
+ "limit": 1,
1576
+ "offset": 0,
1577
+ },
1578
+ response_class=PatentDataResponse,
1579
+ )
1580
+ assert result is not None
1581
+ assert result.application_number_text == mock_patent_file_wrapper.application_number_text
1582
+
1583
+ def test_get_patent_not_found(
1584
+ self,
1585
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1586
+ ) -> None:
1587
+ """Test get_patent returns None when patent is not found."""
1588
+ client, mock_make_request = client_with_mocked_request
1589
+ mock_make_request.return_value = PatentDataResponse(
1590
+ count=0, patent_file_wrapper_data_bag=[]
1591
+ )
1592
+
1593
+ result = client.get_patent("nonexistent")
1594
+ assert result is None
1595
+
1596
+
1597
+ class TestGetPublication:
1598
+ """Tests for the get_publication convenience method."""
1599
+
1600
+ def test_get_publication_found(
1601
+ self,
1602
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1603
+ mock_patent_file_wrapper: PatentFileWrapper,
1604
+ ) -> None:
1605
+ """Test get_publication returns wrapper when publication is found."""
1606
+ client, mock_make_request = client_with_mocked_request
1607
+ mock_make_request.return_value = PatentDataResponse(
1608
+ count=1, patent_file_wrapper_data_bag=[mock_patent_file_wrapper]
1609
+ )
1610
+
1611
+ pub_num = "20230123456"
1612
+ result = client.get_publication(pub_num)
1613
+
1614
+ assert mock_make_request.call_args == call(
1615
+ method="GET",
1616
+ endpoint="api/v1/patent/applications/search",
1617
+ params={
1618
+ "q": f"applicationMetaData.earliestPublicationNumber:{pub_num}",
1619
+ "limit": 1,
1620
+ "offset": 0,
1621
+ },
1622
+ response_class=PatentDataResponse,
1623
+ )
1624
+ assert result is not None
1625
+ assert result.application_number_text == mock_patent_file_wrapper.application_number_text
1626
+
1627
+ def test_get_publication_not_found(
1628
+ self,
1629
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1630
+ ) -> None:
1631
+ """Test get_publication returns None when publication is not found."""
1632
+ client, mock_make_request = client_with_mocked_request
1633
+ mock_make_request.return_value = PatentDataResponse(
1634
+ count=0, patent_file_wrapper_data_bag=[]
1635
+ )
1636
+
1637
+ result = client.get_publication("nonexistent")
1638
+ assert result is None
1639
+
1640
+
1641
+ class TestGetPCT:
1642
+ """Tests for the get_pct convenience method."""
1643
+
1644
+ @pytest.fixture
1645
+ def mock_pct_file_wrapper(self) -> PatentFileWrapper:
1646
+ """Provides a mock PatentFileWrapper with a sanitized PCT application number."""
1647
+ return PatentFileWrapper(application_number_text="PCTUS2412345")
1648
+
1649
+ def test_get_pct_with_app_number(
1650
+ self,
1651
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1652
+ mock_pct_file_wrapper: PatentFileWrapper,
1653
+ ) -> None:
1654
+ """Test get_pct with PCT application number uses direct lookup."""
1655
+ client, mock_make_request = client_with_mocked_request
1656
+ mock_make_request.return_value = PatentDataResponse(
1657
+ count=1, patent_file_wrapper_data_bag=[mock_pct_file_wrapper]
1658
+ )
1659
+
1660
+ pct_app = "PCT/US2024/012345"
1661
+ result = client.get_pct(pct_app)
1662
+
1663
+ # Should call get_application_by_number (direct lookup)
1664
+ assert mock_make_request.call_args == call(
1665
+ method="GET",
1666
+ endpoint="api/v1/patent/applications/PCTUS2412345",
1667
+ response_class=PatentDataResponse,
1668
+ )
1669
+ assert result is not None
1670
+ assert result.application_number_text == mock_pct_file_wrapper.application_number_text
1671
+
1672
+ def test_get_pct_with_pub_number(
1673
+ self,
1674
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1675
+ mock_patent_file_wrapper: PatentFileWrapper,
1676
+ ) -> None:
1677
+ """Test get_pct with PCT publication number uses search."""
1678
+ client, mock_make_request = client_with_mocked_request
1679
+ mock_make_request.return_value = PatentDataResponse(
1680
+ count=1, patent_file_wrapper_data_bag=[mock_patent_file_wrapper]
1681
+ )
1682
+
1683
+ pct_pub = "WO2024012345A1"
1684
+ result = client.get_pct(pct_pub)
1685
+
1686
+ assert mock_make_request.call_args == call(
1687
+ method="GET",
1688
+ endpoint="api/v1/patent/applications/search",
1689
+ params={
1690
+ "q": f"applicationMetaData.pctPublicationNumber:{pct_pub}",
1691
+ "limit": 1,
1692
+ "offset": 0,
1693
+ },
1694
+ response_class=PatentDataResponse,
1695
+ )
1696
+ assert result is not None
1697
+ assert result.application_number_text == mock_patent_file_wrapper.application_number_text
1698
+
1699
+ def test_get_pct_not_found(
1700
+ self,
1701
+ client_with_mocked_request: tuple[PatentDataClient, MagicMock],
1702
+ ) -> None:
1703
+ """Test get_pct returns None when PCT number is not found."""
1704
+ client, mock_make_request = client_with_mocked_request
1705
+ mock_make_request.return_value = PatentDataResponse(
1706
+ count=0, patent_file_wrapper_data_bag=[]
1707
+ )
1708
+
1709
+ result = client.get_pct("WO9999999999")
1710
+ assert result is None
1711
+
1712
+
1553
1713
  class TestDownloadArchive:
1554
1714
  """Tests for downloading archive files."""
1555
1715
 
@@ -857,3 +857,52 @@ class TestPatentDataIntegration:
857
857
  )
858
858
  except Exception as e:
859
859
  pytest.fail(f"Unexpected exception for invalid app number: {e}")
860
+
861
+
862
+ class TestHelperMethodsIntegration:
863
+ """Integration tests for get_patent, get_publication, and get_pct helper methods."""
864
+
865
+ KNOWN_PATENT_NUMBER = "12565253"
866
+ KNOWN_PUBLICATION_NUMBER = "US20260054762A1"
867
+ KNOWN_PCT_APP_NUMBER = "PCT/US2025/047756"
868
+ KNOWN_PCT_PUB_NUMBER = "WO 2026/044302"
869
+
870
+ def test_get_patent(self, patent_data_client: PatentDataClient) -> None:
871
+ """Test get_patent returns a PatentFileWrapper for a known patent number."""
872
+ result = patent_data_client.get_patent(self.KNOWN_PATENT_NUMBER)
873
+
874
+ assert isinstance(result, PatentFileWrapper)
875
+ assert result.application_number_text == "19378371"
876
+ assert result.application_meta_data is not None
877
+ assert isinstance(result.application_meta_data, ApplicationMetaData)
878
+
879
+ def test_get_publication(self, patent_data_client: PatentDataClient) -> None:
880
+ """Test get_publication returns a PatentFileWrapper for a known publication number."""
881
+ result = patent_data_client.get_publication(self.KNOWN_PUBLICATION_NUMBER)
882
+
883
+ assert isinstance(result, PatentFileWrapper)
884
+ assert result.application_number_text == "19378371"
885
+ assert result.application_meta_data is not None
886
+ assert isinstance(result.application_meta_data, ApplicationMetaData)
887
+
888
+ def test_get_pct_with_app_number(
889
+ self, patent_data_client: PatentDataClient
890
+ ) -> None:
891
+ """Test get_pct with a PCT application number (direct lookup)."""
892
+ result = patent_data_client.get_pct(self.KNOWN_PCT_APP_NUMBER)
893
+
894
+ assert isinstance(result, PatentFileWrapper)
895
+ assert result.application_number_text == "PCTUS2547756"
896
+ assert result.application_meta_data is not None
897
+ assert result.application_meta_data.pct_publication_number == "WO2026044302"
898
+
899
+ def test_get_pct_with_pub_number(
900
+ self, patent_data_client: PatentDataClient
901
+ ) -> None:
902
+ """Test get_pct with a PCT publication number."""
903
+ result = patent_data_client.get_pct(self.KNOWN_PCT_PUB_NUMBER)
904
+
905
+ assert isinstance(result, PatentFileWrapper)
906
+ assert result.application_number_text == "PCTUS2547756"
907
+ assert result.application_meta_data is not None
908
+ assert result.application_meta_data.pct_publication_number == "WO2026044302"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes