pyUSPTO 0.4.3__tar.gz → 0.4.5__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.
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/workflows/publish-to-test-pypi.yml +1 -1
- {pyuspto-0.4.3 → pyuspto-0.4.5}/PKG-INFO +4 -3
- {pyuspto-0.4.3 → pyuspto-0.4.5}/README.md +2 -1
- {pyuspto-0.4.3 → pyuspto-0.4.5}/pyproject.toml +7 -2
- {pyuspto-0.4.3 → pyuspto-0.4.5}/requirements-dev.txt +3 -7
- {pyuspto-0.4.3 → pyuspto-0.4.5}/requirements.txt +1 -1
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/base.py +144 -64
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/bulk_data.py +4 -9
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/patent_data.py +104 -39
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/petition_decisions.py +13 -16
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/ptab_appeals.py +4 -6
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/ptab_interferences.py +4 -6
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/ptab_trials.py +10 -12
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/patent_data.py +9 -3
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/petition_decisions.py +6 -3
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO.egg-info/PKG-INFO +4 -3
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_base.py +87 -50
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_bulk_data_clients.py +3 -3
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_patent_data_clients.py +468 -261
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_petition_decision_clients.py +130 -112
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/test_patent_data_integration.py +64 -15
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/test_petition_decisions_integration.py +23 -23
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/test_ptab_appeals_integration.py +18 -14
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/ISSUE_TEMPLATE/documentation-updates.yml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/dependabot.yml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/workflows/publish-docs.yaml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.github/workflows/python-tests.yml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.gitignore +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/.readthedocs.yaml +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/ADVANCED.md +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/CHANGELOG.md +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/CODE_OF_CONDUCT.md +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/CONTRIBUTING.md +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/LICENSE +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/_version.pyi +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/Makefile +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/_static/.gitkeep +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/advanced.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/bulk_data.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/index.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/patent_data.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/petition_decisions.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/ptab_appeals.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/ptab_interferences.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/clients/ptab_trials.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/config/config.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/config/http_config.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/config/index.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/exceptions.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/index.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/base.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/bulk_data.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/index.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/patent_data.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/petition_decisions.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/ptab.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/models/utils.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/api/warnings.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/conf.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/configuration.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/development.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/bulk_data.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/ifw_example.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/index.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/patent_data.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/petition_decisions.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/ptab_appeals.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/ptab_interferences.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/examples/ptab_trials.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/index.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/installation.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/docs/source/quickstart.rst +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/bulk_data_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/error_handling_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/ifw_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/patent_data_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/petition_decisions_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/ptab_appeals_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/ptab_interferences_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/examples/ptab_trials_example.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/pyUSPTO.code-workspace +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/setup.cfg +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/clients/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/config.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/exceptions.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/http_config.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/base.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/bulk_data.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/ptab.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/models/utils.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/py.typed +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/utils/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO/warnings.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO.egg-info/SOURCES.txt +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO.egg-info/dependency_links.txt +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO.egg-info/requires.txt +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO.egg-info/top_level.txt +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/src/pyUSPTO.egg-info/zip-safe +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_ptab_appeals_client.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_ptab_interferences_client.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/clients/test_ptab_trials_client.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/conftest.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/conftest.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/test_bulk_data_integration.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/test_ptab_interferences_integration.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/integration/test_ptab_trials_integration.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/models/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/models/test_bulk_data_models.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/models/test_patent_data_models.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/models/test_petition_decision_models.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/models/test_ptab_models.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/models/test_utils.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/test_config.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/test_edge_cases.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/test_exceptions.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/test_http_config.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/test_init.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/test_version.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tests/utils/__init__.py +0 -0
- {pyuspto-0.4.3 → pyuspto-0.4.5}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyUSPTO
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
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 ::
|
|
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
|
-
#
|
|
68
|
+
# USPTO Open Data Portal Client Library
|
|
69
69
|
|
|
70
70
|
[](https://badge.fury.io/py/pyUSPTO)
|
|
71
71
|
[](https://opensource.org/licenses/MIT)
|
|
72
72
|
[](https://www.python.org/downloads/)
|
|
73
73
|
[](https://pyuspto.readthedocs.io/en/latest/)
|
|
74
|
+
[](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
|
-
#
|
|
1
|
+
# USPTO Open Data Portal Client Library
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/py/pyUSPTO)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.python.org/downloads/)
|
|
6
6
|
[](https://pyuspto.readthedocs.io/en/latest/)
|
|
7
|
+
[](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 ::
|
|
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 = [
|
|
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.
|
|
17
|
+
charset-normalizer==3.4.5
|
|
18
18
|
# via requests
|
|
19
19
|
colorama==0.4.6
|
|
20
20
|
# via
|
|
@@ -88,10 +88,6 @@ pytest-cov==7.0.0
|
|
|
88
88
|
# via pyuspto
|
|
89
89
|
pytest-mock==3.15.1
|
|
90
90
|
# via pyuspto
|
|
91
|
-
pyuspto @ file:///C:/Users/andrewp/Documents/GitHub/pyUSPTO
|
|
92
|
-
# via
|
|
93
|
-
# pyUSPTO (pyproject.toml)
|
|
94
|
-
# pyuspto
|
|
95
91
|
pyyaml==6.0.3
|
|
96
92
|
# via myst-parser
|
|
97
93
|
requests==2.32.5
|
|
@@ -102,7 +98,7 @@ requests==2.32.5
|
|
|
102
98
|
# sphinx-immaterial
|
|
103
99
|
roman-numerals==4.1.0
|
|
104
100
|
# via sphinx
|
|
105
|
-
ruff==0.15.
|
|
101
|
+
ruff==0.15.5
|
|
106
102
|
# via pyuspto
|
|
107
103
|
snowballstemmer==3.0.1
|
|
108
104
|
# via sphinx
|
|
@@ -115,7 +111,7 @@ sphinx==9.1.0
|
|
|
115
111
|
# sphinx-immaterial
|
|
116
112
|
# sphinx-rtd-theme
|
|
117
113
|
# sphinxcontrib-jquery
|
|
118
|
-
sphinx-autodoc-typehints==3.9.
|
|
114
|
+
sphinx-autodoc-typehints==3.9.8
|
|
119
115
|
# via pyuspto
|
|
120
116
|
sphinx-copybutton==0.5.2
|
|
121
117
|
# via pyuspto
|
|
@@ -14,6 +14,10 @@ from typing import (
|
|
|
14
14
|
runtime_checkable,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
+
try:
|
|
18
|
+
from typing import Self
|
|
19
|
+
except ImportError:
|
|
20
|
+
from typing_extensions import Self
|
|
17
21
|
import requests
|
|
18
22
|
|
|
19
23
|
from pyUSPTO.config import USPTOConfig
|
|
@@ -31,7 +35,7 @@ class FromDictProtocol(Protocol):
|
|
|
31
35
|
"""Protocol for classes that can be created from a dictionary."""
|
|
32
36
|
|
|
33
37
|
@classmethod
|
|
34
|
-
def from_dict(cls, data: dict[str, Any], include_raw_data: bool = False) ->
|
|
38
|
+
def from_dict(cls, data: dict[str, Any], include_raw_data: bool = False) -> Self:
|
|
35
39
|
"""Create an object from a dictionary."""
|
|
36
40
|
...
|
|
37
41
|
|
|
@@ -39,6 +43,9 @@ class FromDictProtocol(Protocol):
|
|
|
39
43
|
# Type variable for response classes
|
|
40
44
|
T = TypeVar("T", bound=FromDictProtocol)
|
|
41
45
|
|
|
46
|
+
# Type variable for response classes
|
|
47
|
+
M = TypeVar("M", bound=FromDictProtocol)
|
|
48
|
+
|
|
42
49
|
|
|
43
50
|
class BaseUSPTOClient(Generic[T]):
|
|
44
51
|
"""Base client class for USPTO API clients."""
|
|
@@ -166,49 +173,56 @@ class BaseUSPTOClient(Generic[T]):
|
|
|
166
173
|
),
|
|
167
174
|
) from json_err
|
|
168
175
|
|
|
169
|
-
def
|
|
176
|
+
def _build_url(
|
|
170
177
|
self,
|
|
171
|
-
method: str,
|
|
172
178
|
endpoint: str,
|
|
179
|
+
custom_url: str | None = None,
|
|
180
|
+
custom_base_url: str | None = None,
|
|
181
|
+
) -> str:
|
|
182
|
+
"""Build the request URL from endpoint or custom URL.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
endpoint: API endpoint path (without base URL)
|
|
186
|
+
custom_url: Optional full custom URL (overrides endpoint and base URL)
|
|
187
|
+
custom_base_url: Optional custom base URL instead of self.base_url
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
The resolved URL string.
|
|
191
|
+
"""
|
|
192
|
+
if custom_url:
|
|
193
|
+
return custom_url
|
|
194
|
+
base = custom_base_url if custom_base_url else self.base_url
|
|
195
|
+
return f"{base}/{endpoint.lstrip('/')}"
|
|
196
|
+
|
|
197
|
+
def _execute_request(
|
|
198
|
+
self,
|
|
199
|
+
method: str,
|
|
200
|
+
url: str,
|
|
173
201
|
params: dict[str, Any] | None = None,
|
|
174
202
|
json_data: dict[str, Any] | None = None,
|
|
175
203
|
stream: bool = False,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
custom_base_url: str | None = None,
|
|
179
|
-
) -> dict[str, Any] | T | requests.Response:
|
|
180
|
-
"""Make an HTTP request to the USPTO API.
|
|
204
|
+
) -> requests.Response:
|
|
205
|
+
"""Execute an HTTP request and return the raw Response.
|
|
181
206
|
|
|
182
|
-
|
|
183
|
-
|
|
207
|
+
Handles URL dispatch, error translation, and timeout/connection errors.
|
|
208
|
+
Callers are responsible for interpreting the response body.
|
|
184
209
|
|
|
185
210
|
Args:
|
|
186
211
|
method: HTTP method (GET or POST only)
|
|
187
|
-
|
|
212
|
+
url: Fully resolved request URL
|
|
188
213
|
params: Optional query parameters
|
|
189
214
|
json_data: Optional JSON body for POST requests
|
|
190
215
|
stream: Whether to stream the response
|
|
191
|
-
response_class: Class to use for parsing the response
|
|
192
|
-
custom_url: Optional full custom URL to use (overrides endpoint and base URL)
|
|
193
|
-
custom_base_url: Optional custom base URL to use instead of self.base_url
|
|
194
216
|
|
|
195
217
|
Returns:
|
|
196
|
-
|
|
197
|
-
- If stream=True: requests.Response object
|
|
198
|
-
- If response_class is provided: Instance of response_class
|
|
199
|
-
- Otherwise: Dict[str, Any] containing the JSON response
|
|
218
|
+
The raw requests.Response after raise_for_status().
|
|
200
219
|
|
|
201
220
|
Raises:
|
|
202
221
|
ValueError: If an unsupported HTTP method is provided
|
|
222
|
+
USPTOApiError: On HTTP errors from the API
|
|
223
|
+
USPTOTimeout: On request timeout
|
|
224
|
+
USPTOConnectionError: On connection failure
|
|
203
225
|
"""
|
|
204
|
-
url: str = ""
|
|
205
|
-
if custom_url:
|
|
206
|
-
url = custom_url
|
|
207
|
-
else:
|
|
208
|
-
base = custom_base_url if custom_base_url else self.base_url
|
|
209
|
-
url = f"{base}/{endpoint.lstrip('/')}"
|
|
210
|
-
|
|
211
|
-
# Get timeout from HTTP config
|
|
212
226
|
timeout = self.http_config.get_timeout_tuple()
|
|
213
227
|
|
|
214
228
|
try:
|
|
@@ -228,26 +242,10 @@ class BaseUSPTOClient(Generic[T]):
|
|
|
228
242
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
229
243
|
|
|
230
244
|
response.raise_for_status()
|
|
231
|
-
|
|
232
|
-
# Return the raw response for streaming requests
|
|
233
|
-
if stream:
|
|
234
|
-
return response
|
|
235
|
-
|
|
236
|
-
# Parse JSON response with error handling
|
|
237
|
-
json_data = self._parse_json_response(response, url)
|
|
238
|
-
|
|
239
|
-
# Parse the response based on the specified class
|
|
240
|
-
if response_class:
|
|
241
|
-
parsed_response: T = response_class.from_dict(
|
|
242
|
-
json_data, include_raw_data=self.config.include_raw_data
|
|
243
|
-
)
|
|
244
|
-
return parsed_response
|
|
245
|
-
|
|
246
|
-
# Return the raw JSON for other requests
|
|
247
|
-
return json_data
|
|
245
|
+
return response
|
|
248
246
|
|
|
249
247
|
except requests.exceptions.HTTPError as http_err:
|
|
250
|
-
client_operation_message = f"API request to '{url}' failed with HTTPError"
|
|
248
|
+
client_operation_message = f"API request to '{url}' failed with HTTPError"
|
|
251
249
|
|
|
252
250
|
# Include request body for POST debugging
|
|
253
251
|
if method.upper() == "POST" and json_data:
|
|
@@ -257,7 +255,6 @@ class BaseUSPTOClient(Generic[T]):
|
|
|
257
255
|
f"\nRequest body sent:\n{json.dumps(json_data, indent=2)}"
|
|
258
256
|
)
|
|
259
257
|
|
|
260
|
-
# Create APIErrorArgs directly from the HTTPError
|
|
261
258
|
current_error_args = APIErrorArgs.from_http_error(
|
|
262
259
|
http_error=http_err, client_operation_message=client_operation_message
|
|
263
260
|
)
|
|
@@ -266,7 +263,6 @@ class BaseUSPTOClient(Generic[T]):
|
|
|
266
263
|
raise api_exception_to_raise from http_err
|
|
267
264
|
|
|
268
265
|
except requests.exceptions.Timeout as timeout_err:
|
|
269
|
-
# Specific handling for timeout errors
|
|
270
266
|
raise USPTOTimeout(
|
|
271
267
|
message=f"Request to '{url}' timed out",
|
|
272
268
|
api_short_error="Timeout",
|
|
@@ -274,31 +270,120 @@ class BaseUSPTOClient(Generic[T]):
|
|
|
274
270
|
) from timeout_err
|
|
275
271
|
|
|
276
272
|
except requests.exceptions.ConnectionError as conn_err:
|
|
277
|
-
# Specific handling for connection errors (DNS, refused connection, etc.)
|
|
278
273
|
raise USPTOConnectionError(
|
|
279
274
|
message=f"Failed to connect to '{url}'",
|
|
280
275
|
api_short_error="Connection Error",
|
|
281
276
|
error_details=str(conn_err),
|
|
282
277
|
) from conn_err
|
|
283
278
|
|
|
284
|
-
except
|
|
285
|
-
|
|
286
|
-
) as req_err: # Catches other non-HTTP errors from requests
|
|
287
|
-
client_operation_message = (
|
|
288
|
-
f"API request to '{url}' failed" # 'url' is from _make_request scope
|
|
289
|
-
)
|
|
279
|
+
except requests.exceptions.RequestException as req_err:
|
|
280
|
+
client_operation_message = f"API request to '{url}' failed"
|
|
290
281
|
|
|
291
|
-
# Create APIErrorArgs from the generic RequestException
|
|
292
282
|
current_error_args = APIErrorArgs.from_request_exception(
|
|
293
283
|
request_exception=req_err,
|
|
294
|
-
client_operation_message=client_operation_message,
|
|
284
|
+
client_operation_message=client_operation_message,
|
|
295
285
|
)
|
|
296
286
|
|
|
297
|
-
api_exception_to_raise = get_api_exception(
|
|
298
|
-
current_error_args
|
|
299
|
-
) # Will default to USPTOApiError
|
|
287
|
+
api_exception_to_raise = get_api_exception(current_error_args)
|
|
300
288
|
raise api_exception_to_raise from req_err
|
|
301
289
|
|
|
290
|
+
def _stream_request(
|
|
291
|
+
self,
|
|
292
|
+
method: str,
|
|
293
|
+
endpoint: str,
|
|
294
|
+
params: dict[str, Any] | None = None,
|
|
295
|
+
json_data: dict[str, Any] | None = None,
|
|
296
|
+
custom_url: str | None = None,
|
|
297
|
+
custom_base_url: str | None = None,
|
|
298
|
+
) -> requests.Response:
|
|
299
|
+
"""Make a streaming HTTP request and return the raw Response.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
method: HTTP method (GET or POST only)
|
|
303
|
+
endpoint: API endpoint path (without base URL)
|
|
304
|
+
params: Optional query parameters
|
|
305
|
+
json_data: Optional JSON body for POST requests
|
|
306
|
+
custom_url: Optional full custom URL (overrides endpoint and base URL)
|
|
307
|
+
custom_base_url: Optional custom base URL instead of self.base_url
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Streaming requests.Response object.
|
|
311
|
+
"""
|
|
312
|
+
url = self._build_url(
|
|
313
|
+
endpoint, custom_url=custom_url, custom_base_url=custom_base_url
|
|
314
|
+
)
|
|
315
|
+
return self._execute_request(
|
|
316
|
+
method=method, url=url, params=params, json_data=json_data, stream=True
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def _get_model(
|
|
320
|
+
self,
|
|
321
|
+
method: str,
|
|
322
|
+
endpoint: str,
|
|
323
|
+
response_class: type[M],
|
|
324
|
+
params: dict[str, Any] | None = None,
|
|
325
|
+
json_data: dict[str, Any] | None = None,
|
|
326
|
+
custom_url: str | None = None,
|
|
327
|
+
custom_base_url: str | None = None,
|
|
328
|
+
) -> M:
|
|
329
|
+
"""Make an HTTP request and parse the response into a model.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
method: HTTP method (GET or POST only)
|
|
333
|
+
endpoint: API endpoint path (without base URL)
|
|
334
|
+
response_class: Class to use for parsing the response
|
|
335
|
+
params: Optional query parameters
|
|
336
|
+
json_data: Optional JSON body for POST requests
|
|
337
|
+
custom_url: Optional full custom URL (overrides endpoint and base URL)
|
|
338
|
+
custom_base_url: Optional custom base URL instead of self.base_url
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Instance of response_class parsed from the JSON response.
|
|
342
|
+
"""
|
|
343
|
+
url = self._build_url(
|
|
344
|
+
endpoint, custom_url=custom_url, custom_base_url=custom_base_url
|
|
345
|
+
)
|
|
346
|
+
response = self._execute_request(
|
|
347
|
+
method=method, url=url, params=params, json_data=json_data
|
|
348
|
+
)
|
|
349
|
+
data = self._parse_json_response(response, url)
|
|
350
|
+
|
|
351
|
+
ret = response_class.from_dict(
|
|
352
|
+
data, include_raw_data=self.config.include_raw_data
|
|
353
|
+
)
|
|
354
|
+
assert isinstance(ret, response_class)
|
|
355
|
+
return ret
|
|
356
|
+
|
|
357
|
+
def _get_json(
|
|
358
|
+
self,
|
|
359
|
+
method: str,
|
|
360
|
+
endpoint: str,
|
|
361
|
+
params: dict[str, Any] | None = None,
|
|
362
|
+
json_data: dict[str, Any] | None = None,
|
|
363
|
+
custom_url: str | None = None,
|
|
364
|
+
custom_base_url: str | None = None,
|
|
365
|
+
) -> dict[str, Any]:
|
|
366
|
+
"""Make an HTTP request and return the parsed JSON response.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
method: HTTP method (GET or POST only)
|
|
370
|
+
endpoint: API endpoint path (without base URL)
|
|
371
|
+
params: Optional query parameters
|
|
372
|
+
json_data: Optional JSON body for POST requests
|
|
373
|
+
custom_url: Optional full custom URL (overrides endpoint and base URL)
|
|
374
|
+
custom_base_url: Optional custom base URL instead of self.base_url
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Dict containing the JSON response.
|
|
378
|
+
"""
|
|
379
|
+
url = self._build_url(
|
|
380
|
+
endpoint, custom_url=custom_url, custom_base_url=custom_base_url
|
|
381
|
+
)
|
|
382
|
+
response = self._execute_request(
|
|
383
|
+
method=method, url=url, params=params, json_data=json_data
|
|
384
|
+
)
|
|
385
|
+
return self._parse_json_response(response, url)
|
|
386
|
+
|
|
302
387
|
def paginate_results(
|
|
303
388
|
self,
|
|
304
389
|
method_name: str,
|
|
@@ -785,19 +870,14 @@ class BaseUSPTOClient(Generic[T]):
|
|
|
785
870
|
Path to downloaded file
|
|
786
871
|
|
|
787
872
|
Raises:
|
|
788
|
-
TypeError: If response is not a valid Response object
|
|
789
873
|
FileExistsError: If file exists and overwrite is False
|
|
790
874
|
"""
|
|
791
|
-
response = self.
|
|
875
|
+
response = self._stream_request(
|
|
792
876
|
method="GET",
|
|
793
877
|
endpoint="",
|
|
794
|
-
stream=True,
|
|
795
878
|
custom_url=url,
|
|
796
879
|
)
|
|
797
880
|
|
|
798
|
-
if not isinstance(response, requests.Response):
|
|
799
|
-
raise TypeError(f"Expected Response, got {type(response)}")
|
|
800
|
-
|
|
801
881
|
return self._save_response_to_file(
|
|
802
882
|
response=response,
|
|
803
883
|
destination=destination,
|
|
@@ -109,13 +109,12 @@ class BulkDataClient(BaseUSPTOClient[BulkDataResponse]):
|
|
|
109
109
|
params["latest"] = str(latest).lower()
|
|
110
110
|
|
|
111
111
|
# Use response_class for clean parsing
|
|
112
|
-
response = self.
|
|
112
|
+
response = self._get_model(
|
|
113
113
|
method="GET",
|
|
114
114
|
endpoint=endpoint,
|
|
115
|
-
params=params if params else None,
|
|
116
115
|
response_class=BulkDataResponse,
|
|
116
|
+
params=params if params else None,
|
|
117
117
|
)
|
|
118
|
-
assert isinstance(response, BulkDataResponse)
|
|
119
118
|
|
|
120
119
|
# Extract the product from response
|
|
121
120
|
if response.bulk_data_product_bag:
|
|
@@ -251,13 +250,9 @@ class BulkDataClient(BaseUSPTOClient[BulkDataResponse]):
|
|
|
251
250
|
if fields is not None:
|
|
252
251
|
params["fields"] = ",".join(fields)
|
|
253
252
|
|
|
254
|
-
|
|
253
|
+
return self._get_model(
|
|
255
254
|
method="GET",
|
|
256
255
|
endpoint=self.ENDPOINTS["products_search"],
|
|
257
|
-
params=params,
|
|
258
256
|
response_class=BulkDataResponse,
|
|
257
|
+
params=params,
|
|
259
258
|
)
|
|
260
|
-
|
|
261
|
-
# Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse
|
|
262
|
-
assert isinstance(result, BulkDataResponse)
|
|
263
|
-
return result
|