pyUSPTO 0.4.1__tar.gz → 0.4.3__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.1 → pyuspto-0.4.3}/.github/workflows/publish-docs.yaml +1 -1
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/workflows/publish-to-test-pypi.yml +3 -3
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/workflows/python-tests.yml +3 -4
- {pyuspto-0.4.1 → pyuspto-0.4.3}/CONTRIBUTING.md +2 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/PKG-INFO +20 -13
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/ifw_example.py +40 -2
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/patent_data_example.py +22 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/pyproject.toml +9 -16
- {pyuspto-0.4.1 → pyuspto-0.4.3}/requirements-dev.txt +31 -21
- {pyuspto-0.4.1 → pyuspto-0.4.3}/requirements.txt +1 -1
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/patent_data.py +202 -24
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/petition_decisions.py +35 -7
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/patent_data.py +21 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO.egg-info/PKG-INFO +20 -13
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO.egg-info/requires.txt +18 -8
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_patent_data_clients.py +423 -50
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_petition_decision_clients.py +6 -6
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tox.ini +2 -4
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/ISSUE_TEMPLATE/documentation-updates.yml +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.github/dependabot.yml +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.gitignore +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/.readthedocs.yaml +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/ADVANCED.md +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/CHANGELOG.md +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/CODE_OF_CONDUCT.md +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/LICENSE +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/README.md +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/_version.pyi +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/Makefile +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/_static/.gitkeep +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/advanced.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/bulk_data.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/index.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/patent_data.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/petition_decisions.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/ptab_appeals.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/ptab_interferences.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/clients/ptab_trials.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/config/config.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/config/http_config.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/config/index.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/exceptions.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/index.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/base.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/bulk_data.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/index.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/patent_data.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/petition_decisions.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/ptab.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/models/utils.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/api/warnings.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/conf.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/configuration.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/development.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/bulk_data.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/ifw_example.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/index.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/patent_data.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/petition_decisions.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/ptab_appeals.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/ptab_interferences.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/examples/ptab_trials.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/index.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/installation.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/docs/source/quickstart.rst +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/bulk_data_example.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/error_handling_example.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/petition_decisions_example.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/ptab_appeals_example.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/ptab_interferences_example.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/examples/ptab_trials_example.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/pyUSPTO.code-workspace +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/setup.cfg +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/base.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/bulk_data.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/ptab_appeals.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/ptab_interferences.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/clients/ptab_trials.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/config.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/exceptions.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/http_config.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/base.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/bulk_data.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/petition_decisions.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/ptab.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/models/utils.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/py.typed +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/utils/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO/warnings.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO.egg-info/SOURCES.txt +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO.egg-info/dependency_links.txt +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO.egg-info/top_level.txt +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/src/pyUSPTO.egg-info/zip-safe +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_base.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_bulk_data_clients.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_ptab_appeals_client.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_ptab_interferences_client.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/clients/test_ptab_trials_client.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/conftest.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/conftest.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/test_bulk_data_integration.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/test_patent_data_integration.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/test_petition_decisions_integration.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/test_ptab_appeals_integration.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/test_ptab_interferences_integration.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/integration/test_ptab_trials_integration.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/models/__init__.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/models/test_bulk_data_models.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/models/test_patent_data_models.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/models/test_petition_decision_models.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/models/test_ptab_models.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/models/test_utils.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/test_config.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/test_edge_cases.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/test_exceptions.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/test_http_config.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/test_init.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/test_version.py +0 -0
- {pyuspto-0.4.1 → pyuspto-0.4.3}/tests/utils/__init__.py +0 -0
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
- name: Build a binary wheel and a source tarball
|
|
33
33
|
run: python3 -m build
|
|
34
34
|
- name: Store the distribution packages
|
|
35
|
-
uses: actions/upload-artifact@
|
|
35
|
+
uses: actions/upload-artifact@v7
|
|
36
36
|
with:
|
|
37
37
|
name: python-package-distributions
|
|
38
38
|
path: dist/
|
|
@@ -53,7 +53,7 @@ jobs:
|
|
|
53
53
|
|
|
54
54
|
steps:
|
|
55
55
|
- name: Download all the dists
|
|
56
|
-
uses: actions/download-artifact@
|
|
56
|
+
uses: actions/download-artifact@v8
|
|
57
57
|
with:
|
|
58
58
|
name: python-package-distributions
|
|
59
59
|
path: dist/
|
|
@@ -76,7 +76,7 @@ jobs:
|
|
|
76
76
|
|
|
77
77
|
steps:
|
|
78
78
|
- name: Download all the dists
|
|
79
|
-
uses: actions/download-artifact@
|
|
79
|
+
uses: actions/download-artifact@v8
|
|
80
80
|
with:
|
|
81
81
|
name: python-package-distributions
|
|
82
82
|
path: dist/
|
|
@@ -25,8 +25,7 @@ jobs:
|
|
|
25
25
|
run: |
|
|
26
26
|
python -m pip install --upgrade pip
|
|
27
27
|
pip install -r requirements.txt
|
|
28
|
-
pip install -
|
|
29
|
-
pip install -e .
|
|
28
|
+
pip install -e ".[test]"
|
|
30
29
|
|
|
31
30
|
- name: Run tests
|
|
32
31
|
run: |
|
|
@@ -46,12 +45,12 @@ jobs:
|
|
|
46
45
|
- name: Set up Python
|
|
47
46
|
uses: actions/setup-python@v6
|
|
48
47
|
with:
|
|
49
|
-
python-version: "3.
|
|
48
|
+
python-version: "3.14"
|
|
50
49
|
|
|
51
50
|
- name: Install dependencies
|
|
52
51
|
run: |
|
|
53
52
|
python -m pip install --upgrade pip
|
|
54
|
-
pip install
|
|
53
|
+
pip install -e ".[lint]"
|
|
55
54
|
|
|
56
55
|
- name: Lint with ruff
|
|
57
56
|
run: |
|
|
@@ -22,6 +22,8 @@ cd pyUSPTO
|
|
|
22
22
|
|
|
23
23
|
### Set Up Development Environment
|
|
24
24
|
|
|
25
|
+
> **Note:** Python 3.11+ is required for the development environment. Some dev dependencies (e.g. `myst-parser`) do not support Python 3.10. The package itself supports Python 3.10+.
|
|
26
|
+
|
|
25
27
|
```bash
|
|
26
28
|
# Create and activate a virtual environment
|
|
27
29
|
python -m venv venv
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyUSPTO
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
@@ -43,19 +43,26 @@ License-File: LICENSE
|
|
|
43
43
|
Requires-Dist: requests>=2.32.5
|
|
44
44
|
Requires-Dist: typing-extensions>=4.15.0; python_version < "3.11"
|
|
45
45
|
Requires-Dist: tzdata>=2025.3
|
|
46
|
+
Provides-Extra: test
|
|
47
|
+
Requires-Dist: pytest>=9.0.2; extra == "test"
|
|
48
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == "test"
|
|
49
|
+
Requires-Dist: pytest-mock>=3.15.1; extra == "test"
|
|
50
|
+
Requires-Dist: typing_extensions>=4.15.0; extra == "test"
|
|
51
|
+
Provides-Extra: docs
|
|
52
|
+
Requires-Dist: sphinx>=9.1.0; extra == "docs"
|
|
53
|
+
Requires-Dist: sphinx-rtd-theme>=3.1.0; extra == "docs"
|
|
54
|
+
Requires-Dist: sphinx_immaterial>=0.13.9; extra == "docs"
|
|
55
|
+
Requires-Dist: sphinx-autodoc-typehints>=3.9.5; extra == "docs"
|
|
56
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
|
|
57
|
+
Requires-Dist: myst-parser>=5.0.0; extra == "docs"
|
|
58
|
+
Provides-Extra: lint
|
|
59
|
+
Requires-Dist: mypy>=1.19.0; extra == "lint"
|
|
60
|
+
Requires-Dist: types-requests>=2.32.4; extra == "lint"
|
|
61
|
+
Requires-Dist: ruff>=0.15.0; extra == "lint"
|
|
46
62
|
Provides-Extra: dev
|
|
47
|
-
Requires-Dist:
|
|
48
|
-
Requires-Dist:
|
|
49
|
-
Requires-Dist:
|
|
50
|
-
Requires-Dist: sphinx==8.1.3; extra == "dev"
|
|
51
|
-
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "dev"
|
|
52
|
-
Requires-Dist: sphinx_immaterial>=0.13.8; extra == "dev"
|
|
53
|
-
Requires-Dist: sphinx-autodoc-typehints==3.0.1; extra == "dev"
|
|
54
|
-
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "dev"
|
|
55
|
-
Requires-Dist: myst-parser>=4.0.1; extra == "dev"
|
|
56
|
-
Requires-Dist: mypy>=1.19.0; extra == "dev"
|
|
57
|
-
Requires-Dist: types-requests>=2.32.4; extra == "dev"
|
|
58
|
-
Requires-Dist: ruff>=0.15.0; extra == "dev"
|
|
63
|
+
Requires-Dist: pyUSPTO[test]; extra == "dev"
|
|
64
|
+
Requires-Dist: pyUSPTO[docs]; extra == "dev"
|
|
65
|
+
Requires-Dist: pyUSPTO[lint]; extra == "dev"
|
|
59
66
|
Dynamic: license-file
|
|
60
67
|
|
|
61
68
|
# pyUSPTO
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
"""Example usage of pyUSPTO for IFW data.
|
|
2
2
|
|
|
3
|
-
This example demonstrates how to use the PatentDataClient to
|
|
4
|
-
|
|
3
|
+
This example demonstrates how to use the PatentDataClient to retrieve Image File
|
|
4
|
+
Wrapper (IFW) data from the USPTO Patent Data API. It covers:
|
|
5
|
+
|
|
6
|
+
- get_IFW_metadata(): retrieve a PatentFileWrapper (with populated document_bag)
|
|
7
|
+
using any of the five supported identifiers:
|
|
8
|
+
- application_number
|
|
9
|
+
- patent_number
|
|
10
|
+
- publication_number
|
|
11
|
+
- PCT_app_number
|
|
12
|
+
- PCT_pub_number
|
|
13
|
+
|
|
14
|
+
- get_IFW(): retrieve metadata AND bulk-download all prosecution history documents
|
|
15
|
+
(PDF preferred, DOCX fallback; XML and formatless docs are skipped). Returns an
|
|
16
|
+
IFWResult with:
|
|
17
|
+
- wrapper: the PatentFileWrapper
|
|
18
|
+
- output_path: path to the ZIP archive (as_zip=True, default) or output directory
|
|
19
|
+
- downloaded_documents: dict mapping document_identifier -> filename, allowing
|
|
20
|
+
each Document in document_bag to be linked to its downloaded file
|
|
21
|
+
|
|
22
|
+
- download_archive() / download_publication(): download the pgpub or grant XML
|
|
23
|
+
archive from PrintedMetaData.
|
|
5
24
|
"""
|
|
6
25
|
|
|
7
26
|
import json
|
|
@@ -61,6 +80,25 @@ if pct_pub_no_ifw and pct_pub_no_ifw.application_meta_data:
|
|
|
61
80
|
print(f" - IFW Found based on PCT Pub No: {PCT_pub_number}")
|
|
62
81
|
|
|
63
82
|
|
|
83
|
+
print("\nGet IFW + download all prosecution docs as a ZIP archive -->")
|
|
84
|
+
ifw_result = client.get_IFW(application_number=application_number, destination="./download-example", overwrite=True)
|
|
85
|
+
if ifw_result:
|
|
86
|
+
print(f"Title: {ifw_result.wrapper.application_meta_data.invention_title if ifw_result.wrapper.application_meta_data else 'N/A'}")
|
|
87
|
+
print(f"Output: {ifw_result.output_path}")
|
|
88
|
+
doc_bag = ifw_result.wrapper.document_bag or []
|
|
89
|
+
print(f"Documents downloaded: {len(ifw_result.downloaded_documents)} of {len(doc_bag)}")
|
|
90
|
+
for doc in doc_bag:
|
|
91
|
+
if doc.document_identifier:
|
|
92
|
+
filename = ifw_result.downloaded_documents.get(doc.document_identifier)
|
|
93
|
+
status = f"-> {filename}" if filename else "(skipped)"
|
|
94
|
+
print(f" {doc.document_code} [{doc.document_identifier}] {status}")
|
|
95
|
+
|
|
96
|
+
print("\nGet IFW + download all prosecution docs as a directory (no ZIP) -->")
|
|
97
|
+
ifw_dir_result = client.get_IFW(application_number=application_number, destination="./download-example", overwrite=True, as_zip=False)
|
|
98
|
+
if ifw_dir_result:
|
|
99
|
+
print(f"Output directory: {ifw_dir_result.output_path}")
|
|
100
|
+
|
|
101
|
+
|
|
64
102
|
print("\nNow let's download the Patent Publication Text -->")
|
|
65
103
|
if app_no_ifw and app_no_ifw.pgpub_document_meta_data:
|
|
66
104
|
pgpub_archive = app_no_ifw.pgpub_document_meta_data
|
|
@@ -302,6 +302,28 @@ except Exception as e:
|
|
|
302
302
|
print(f"Error with POST search: {e}")
|
|
303
303
|
|
|
304
304
|
|
|
305
|
+
# Search by CPC classification code
|
|
306
|
+
# CPC codes containing spaces or slashes are automatically quoted for the Lucene query.
|
|
307
|
+
# cpc_classification_bag on ApplicationMetaData is a list[str] of all CPC codes assigned
|
|
308
|
+
# to the application, so each result may have multiple codes.
|
|
309
|
+
try:
|
|
310
|
+
print("\nSearching by CPC classification code 'H10D 64/667'...")
|
|
311
|
+
cpc_response = client.search_applications(
|
|
312
|
+
classification_q="H10D 64/667", limit=3
|
|
313
|
+
)
|
|
314
|
+
print(f"Found {cpc_response.count} applications with CPC code H10D 64/667.")
|
|
315
|
+
for patent_wrapper in cpc_response.patent_file_wrapper_data_bag:
|
|
316
|
+
app_meta = patent_wrapper.application_meta_data
|
|
317
|
+
if app_meta:
|
|
318
|
+
print(
|
|
319
|
+
f" - App No: {patent_wrapper.application_number_text}, Title: {app_meta.invention_title}"
|
|
320
|
+
)
|
|
321
|
+
if app_meta.cpc_classification_bag:
|
|
322
|
+
print(f" CPC codes: {', '.join(app_meta.cpc_classification_bag)}")
|
|
323
|
+
except Exception as e:
|
|
324
|
+
print(f"Error searching by CPC classification: {e}")
|
|
325
|
+
|
|
326
|
+
|
|
305
327
|
# Example of getting status codes
|
|
306
328
|
try:
|
|
307
329
|
print("\nGetting first 5 status codes...")
|
|
@@ -51,24 +51,17 @@ dependencies = [
|
|
|
51
51
|
dynamic = ["version"]
|
|
52
52
|
|
|
53
53
|
[project.optional-dependencies]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
"sphinx==8.1.3",
|
|
61
|
-
"sphinx-rtd-theme>=3.0.0",
|
|
62
|
-
"sphinx_immaterial>=0.13.8",
|
|
63
|
-
"sphinx-autodoc-typehints==3.0.1",
|
|
54
|
+
test = ["pytest>=9.0.2", "pytest-cov>=7.0.0", "pytest-mock>=3.15.1", "typing_extensions>=4.15.0"]
|
|
55
|
+
docs = [
|
|
56
|
+
"sphinx>=9.1.0",
|
|
57
|
+
"sphinx-rtd-theme>=3.1.0",
|
|
58
|
+
"sphinx_immaterial>=0.13.9",
|
|
59
|
+
"sphinx-autodoc-typehints>=3.9.5",
|
|
64
60
|
"sphinx-copybutton>=0.5.2",
|
|
65
|
-
"myst-parser>=
|
|
66
|
-
# Type checking
|
|
67
|
-
"mypy>=1.19.0",
|
|
68
|
-
"types-requests>=2.32.4",
|
|
69
|
-
# Code quality and formatting
|
|
70
|
-
"ruff>=0.15.0",
|
|
61
|
+
"myst-parser>=5.0.0",
|
|
71
62
|
]
|
|
63
|
+
lint = ["mypy>=1.19.0", "types-requests>=2.32.4", "ruff>=0.15.0"]
|
|
64
|
+
dev = ["pyUSPTO[test]", "pyUSPTO[docs]", "pyUSPTO[lint]"]
|
|
72
65
|
|
|
73
66
|
[project.urls]
|
|
74
67
|
GitHub = "https://github.com/DunlapCoddingPC/pyUSPTO"
|
|
@@ -12,7 +12,7 @@ appdirs==1.4.4
|
|
|
12
12
|
# via sphinx-immaterial
|
|
13
13
|
babel==2.18.0
|
|
14
14
|
# via sphinx
|
|
15
|
-
certifi==2026.
|
|
15
|
+
certifi==2026.2.25
|
|
16
16
|
# via requests
|
|
17
17
|
charset-normalizer==3.4.4
|
|
18
18
|
# via requests
|
|
@@ -22,14 +22,14 @@ colorama==0.4.6
|
|
|
22
22
|
# sphinx
|
|
23
23
|
coverage==7.13.4
|
|
24
24
|
# via pytest-cov
|
|
25
|
-
docutils==0.
|
|
25
|
+
docutils==0.22.4
|
|
26
26
|
# via
|
|
27
27
|
# myst-parser
|
|
28
28
|
# sphinx
|
|
29
29
|
# sphinx-rtd-theme
|
|
30
30
|
idna==3.11
|
|
31
31
|
# via requests
|
|
32
|
-
imagesize==
|
|
32
|
+
imagesize==2.0.0
|
|
33
33
|
# via sphinx
|
|
34
34
|
iniconfig==2.3.0
|
|
35
35
|
# via pytest
|
|
@@ -39,7 +39,7 @@ jinja2==3.1.6
|
|
|
39
39
|
# sphinx
|
|
40
40
|
librt==0.8.1
|
|
41
41
|
# via mypy
|
|
42
|
-
markdown-it-py==
|
|
42
|
+
markdown-it-py==4.0.0
|
|
43
43
|
# via
|
|
44
44
|
# mdit-py-plugins
|
|
45
45
|
# myst-parser
|
|
@@ -52,11 +52,11 @@ mdit-py-plugins==0.5.0
|
|
|
52
52
|
mdurl==0.1.2
|
|
53
53
|
# via markdown-it-py
|
|
54
54
|
mypy==1.19.1
|
|
55
|
-
# via
|
|
55
|
+
# via pyuspto
|
|
56
56
|
mypy-extensions==1.1.0
|
|
57
57
|
# via mypy
|
|
58
|
-
myst-parser==
|
|
59
|
-
# via
|
|
58
|
+
myst-parser==5.0.0
|
|
59
|
+
# via pyuspto
|
|
60
60
|
packaging==26.0
|
|
61
61
|
# via
|
|
62
62
|
# pytest
|
|
@@ -81,41 +81,48 @@ pygments==2.19.2
|
|
|
81
81
|
# sphinx
|
|
82
82
|
pytest==9.0.2
|
|
83
83
|
# via
|
|
84
|
-
# pyUSPTO (pyproject.toml)
|
|
85
84
|
# pytest-cov
|
|
86
85
|
# pytest-mock
|
|
86
|
+
# pyuspto
|
|
87
87
|
pytest-cov==7.0.0
|
|
88
|
-
# via
|
|
88
|
+
# via pyuspto
|
|
89
89
|
pytest-mock==3.15.1
|
|
90
|
-
# via
|
|
90
|
+
# via pyuspto
|
|
91
|
+
pyuspto @ file:///C:/Users/andrewp/Documents/GitHub/pyUSPTO
|
|
92
|
+
# via
|
|
93
|
+
# pyUSPTO (pyproject.toml)
|
|
94
|
+
# pyuspto
|
|
91
95
|
pyyaml==6.0.3
|
|
92
96
|
# via myst-parser
|
|
93
97
|
requests==2.32.5
|
|
94
98
|
# via
|
|
95
99
|
# pyUSPTO (pyproject.toml)
|
|
100
|
+
# pyuspto
|
|
96
101
|
# sphinx
|
|
97
102
|
# sphinx-immaterial
|
|
98
|
-
|
|
99
|
-
# via
|
|
103
|
+
roman-numerals==4.1.0
|
|
104
|
+
# via sphinx
|
|
105
|
+
ruff==0.15.4
|
|
106
|
+
# via pyuspto
|
|
100
107
|
snowballstemmer==3.0.1
|
|
101
108
|
# via sphinx
|
|
102
|
-
sphinx==
|
|
109
|
+
sphinx==9.1.0
|
|
103
110
|
# via
|
|
104
111
|
# myst-parser
|
|
105
|
-
#
|
|
112
|
+
# pyuspto
|
|
106
113
|
# sphinx-autodoc-typehints
|
|
107
114
|
# sphinx-copybutton
|
|
108
115
|
# sphinx-immaterial
|
|
109
116
|
# sphinx-rtd-theme
|
|
110
117
|
# sphinxcontrib-jquery
|
|
111
|
-
sphinx-autodoc-typehints==3.
|
|
112
|
-
# via
|
|
118
|
+
sphinx-autodoc-typehints==3.9.6
|
|
119
|
+
# via pyuspto
|
|
113
120
|
sphinx-copybutton==0.5.2
|
|
114
|
-
# via
|
|
121
|
+
# via pyuspto
|
|
115
122
|
sphinx-immaterial==0.13.9
|
|
116
|
-
# via
|
|
123
|
+
# via pyuspto
|
|
117
124
|
sphinx-rtd-theme==3.1.0
|
|
118
|
-
# via
|
|
125
|
+
# via pyuspto
|
|
119
126
|
sphinxcontrib-applehelp==2.0.0
|
|
120
127
|
# via sphinx
|
|
121
128
|
sphinxcontrib-devhelp==2.0.0
|
|
@@ -131,19 +138,22 @@ sphinxcontrib-qthelp==2.0.0
|
|
|
131
138
|
sphinxcontrib-serializinghtml==2.0.0
|
|
132
139
|
# via sphinx
|
|
133
140
|
types-requests==2.32.4.20260107
|
|
134
|
-
# via
|
|
141
|
+
# via pyuspto
|
|
135
142
|
typing-extensions==4.15.0
|
|
136
143
|
# via
|
|
137
144
|
# mypy
|
|
138
145
|
# pydantic
|
|
139
146
|
# pydantic-core
|
|
140
147
|
# pydantic-extra-types
|
|
148
|
+
# pyuspto
|
|
141
149
|
# sphinx-immaterial
|
|
142
150
|
# typing-inspection
|
|
143
151
|
typing-inspection==0.4.2
|
|
144
152
|
# via pydantic
|
|
145
153
|
tzdata==2025.3
|
|
146
|
-
# via
|
|
154
|
+
# via
|
|
155
|
+
# pyUSPTO (pyproject.toml)
|
|
156
|
+
# pyuspto
|
|
147
157
|
urllib3==2.6.3
|
|
148
158
|
# via
|
|
149
159
|
# requests
|
|
@@ -4,7 +4,11 @@ This module provides a client for interacting with the USPTO Patent Data API.
|
|
|
4
4
|
It allows you to search for and retrieve patent application data.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import dataclasses
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
7
10
|
import warnings
|
|
11
|
+
import zipfile
|
|
8
12
|
from collections.abc import Iterator
|
|
9
13
|
from typing import Any
|
|
10
14
|
|
|
@@ -20,6 +24,7 @@ from pyUSPTO.models.patent_data import (
|
|
|
20
24
|
DocumentMimeType,
|
|
21
25
|
EventData,
|
|
22
26
|
ForeignPriority,
|
|
27
|
+
IFWResult,
|
|
23
28
|
PatentDataResponse,
|
|
24
29
|
PatentFileWrapper,
|
|
25
30
|
PatentTermAdjustmentData,
|
|
@@ -121,6 +126,9 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
|
|
|
121
126
|
# Example: "PCT/US2024/012345" -> "PCTUS2412345"
|
|
122
127
|
if raw.startswith("PCT"):
|
|
123
128
|
parts = raw.split("/")
|
|
129
|
+
if len(parts) == 1:
|
|
130
|
+
# Already sanitized (e.g. "PCTUS0812705"), return as-is
|
|
131
|
+
return raw
|
|
124
132
|
if len(parts) != 3:
|
|
125
133
|
raise ValueError(
|
|
126
134
|
f"Invalid PCT application format: {input_number}. "
|
|
@@ -275,21 +283,35 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
|
|
|
275
283
|
f"applicationMetaData.patentNumber:{patent_number_q}"
|
|
276
284
|
)
|
|
277
285
|
if inventor_name_q:
|
|
286
|
+
v = (
|
|
287
|
+
f'"{inventor_name_q}"'
|
|
288
|
+
if " " in inventor_name_q
|
|
289
|
+
else inventor_name_q
|
|
290
|
+
)
|
|
278
291
|
q_parts.append(
|
|
279
|
-
f"applicationMetaData.inventorBag.inventorNameText:{
|
|
292
|
+
f"applicationMetaData.inventorBag.inventorNameText:{v}"
|
|
280
293
|
)
|
|
281
294
|
if applicant_name_q:
|
|
282
|
-
|
|
283
|
-
f"
|
|
295
|
+
v = (
|
|
296
|
+
f'"{applicant_name_q}"'
|
|
297
|
+
if " " in applicant_name_q
|
|
298
|
+
else applicant_name_q
|
|
284
299
|
)
|
|
300
|
+
q_parts.append(f"applicationMetaData.firstApplicantName:{v}")
|
|
285
301
|
if assignee_name_q:
|
|
286
|
-
|
|
287
|
-
f"
|
|
302
|
+
v = (
|
|
303
|
+
f'"{assignee_name_q}"'
|
|
304
|
+
if " " in assignee_name_q
|
|
305
|
+
else assignee_name_q
|
|
288
306
|
)
|
|
307
|
+
q_parts.append(f"assignmentBag.assigneeBag.assigneeNameText:{v}")
|
|
289
308
|
if classification_q:
|
|
290
|
-
|
|
291
|
-
f"
|
|
309
|
+
v = (
|
|
310
|
+
f'"{classification_q}"'
|
|
311
|
+
if any(c in classification_q for c in [" ", "/"])
|
|
312
|
+
else classification_q
|
|
292
313
|
)
|
|
314
|
+
q_parts.append(f"applicationMetaData.cpcClassificationBag:{v}")
|
|
293
315
|
if earliestPublicationNumber_q:
|
|
294
316
|
q_parts.append(
|
|
295
317
|
f"applicationMetaData.earliestPublicationNumber:{earliestPublicationNumber_q}"
|
|
@@ -405,21 +427,35 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
|
|
|
405
427
|
f"applicationMetaData.patentNumber:{patent_number_q}"
|
|
406
428
|
)
|
|
407
429
|
if inventor_name_q:
|
|
430
|
+
v = (
|
|
431
|
+
f'"{inventor_name_q}"'
|
|
432
|
+
if " " in inventor_name_q
|
|
433
|
+
else inventor_name_q
|
|
434
|
+
)
|
|
408
435
|
q_parts.append(
|
|
409
|
-
f"applicationMetaData.inventorBag.inventorNameText:{
|
|
436
|
+
f"applicationMetaData.inventorBag.inventorNameText:{v}"
|
|
410
437
|
)
|
|
411
438
|
if applicant_name_q:
|
|
412
|
-
|
|
413
|
-
f"
|
|
439
|
+
v = (
|
|
440
|
+
f'"{applicant_name_q}"'
|
|
441
|
+
if " " in applicant_name_q
|
|
442
|
+
else applicant_name_q
|
|
414
443
|
)
|
|
444
|
+
q_parts.append(f"applicationMetaData.firstApplicantName:{v}")
|
|
415
445
|
if assignee_name_q:
|
|
416
|
-
|
|
417
|
-
f"
|
|
446
|
+
v = (
|
|
447
|
+
f'"{assignee_name_q}"'
|
|
448
|
+
if " " in assignee_name_q
|
|
449
|
+
else assignee_name_q
|
|
418
450
|
)
|
|
451
|
+
q_parts.append(f"assignmentBag.assigneeBag.assigneeNameText:{v}")
|
|
419
452
|
if classification_q:
|
|
420
|
-
|
|
421
|
-
f"
|
|
453
|
+
v = (
|
|
454
|
+
f'"{classification_q}"'
|
|
455
|
+
if any(c in classification_q for c in [" ", "/"])
|
|
456
|
+
else classification_q
|
|
422
457
|
)
|
|
458
|
+
q_parts.append(f"applicationMetaData.cpcClassificationBag:{v}")
|
|
423
459
|
|
|
424
460
|
if filing_date_from_q and filing_date_to_q:
|
|
425
461
|
q_parts.append(
|
|
@@ -1036,27 +1072,169 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
|
|
|
1036
1072
|
comprehensive data if found using one of the identifiers,
|
|
1037
1073
|
otherwise None.
|
|
1038
1074
|
"""
|
|
1075
|
+
wrapper = None
|
|
1039
1076
|
if application_number:
|
|
1040
|
-
|
|
1041
|
-
|
|
1077
|
+
wrapper = self.get_application_by_number(
|
|
1078
|
+
application_number=application_number
|
|
1079
|
+
)
|
|
1080
|
+
elif patent_number:
|
|
1042
1081
|
pdr = self.search_applications(patent_number_q=patent_number, limit=1)
|
|
1043
1082
|
if pdr.patent_file_wrapper_data_bag:
|
|
1044
|
-
|
|
1045
|
-
|
|
1083
|
+
wrapper = pdr.patent_file_wrapper_data_bag[0]
|
|
1084
|
+
elif publication_number:
|
|
1046
1085
|
pdr = self.search_applications(
|
|
1047
1086
|
earliestPublicationNumber_q=publication_number, limit=1
|
|
1048
1087
|
)
|
|
1049
1088
|
if pdr.patent_file_wrapper_data_bag:
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1089
|
+
wrapper = pdr.patent_file_wrapper_data_bag[0]
|
|
1090
|
+
elif PCT_app_number:
|
|
1091
|
+
wrapper = self.get_application_by_number(application_number=PCT_app_number)
|
|
1092
|
+
elif PCT_pub_number:
|
|
1054
1093
|
pdr = self.search_applications(
|
|
1055
1094
|
pctPublicationNumber_q=PCT_pub_number, limit=1
|
|
1056
1095
|
)
|
|
1057
1096
|
if pdr.patent_file_wrapper_data_bag:
|
|
1058
|
-
|
|
1059
|
-
|
|
1097
|
+
wrapper = pdr.patent_file_wrapper_data_bag[0]
|
|
1098
|
+
if wrapper is None:
|
|
1099
|
+
return None
|
|
1100
|
+
doc_bag = self.get_application_documents(wrapper.application_number_text)
|
|
1101
|
+
return dataclasses.replace(wrapper, document_bag=doc_bag)
|
|
1102
|
+
|
|
1103
|
+
def get_IFW(
|
|
1104
|
+
self,
|
|
1105
|
+
*,
|
|
1106
|
+
application_number: str | None = None,
|
|
1107
|
+
publication_number: str | None = None,
|
|
1108
|
+
patent_number: str | None = None,
|
|
1109
|
+
PCT_app_number: str | None = None,
|
|
1110
|
+
PCT_pub_number: str | None = None,
|
|
1111
|
+
destination: str | None = None,
|
|
1112
|
+
overwrite: bool = False,
|
|
1113
|
+
as_zip: bool = True,
|
|
1114
|
+
) -> IFWResult | None:
|
|
1115
|
+
"""Retrieve IFW metadata and download all prosecution documents.
|
|
1116
|
+
|
|
1117
|
+
Combines `get_IFW_metadata` with a bulk download of all available prosecution
|
|
1118
|
+
history documents (PDF preferred, DOCX fallback). Documents with no downloadable
|
|
1119
|
+
format (e.g., NPL references) are silently skipped. A warning is issued only
|
|
1120
|
+
if a document has a download URL but the download itself fails.
|
|
1121
|
+
|
|
1122
|
+
Args:
|
|
1123
|
+
application_number: USPTO application number (e.g., "16123456").
|
|
1124
|
+
publication_number: USPTO pre-grant publication number.
|
|
1125
|
+
patent_number: USPTO patent number.
|
|
1126
|
+
PCT_app_number: PCT application number.
|
|
1127
|
+
PCT_pub_number: PCT publication number.
|
|
1128
|
+
destination: Directory for output. Defaults to current directory.
|
|
1129
|
+
overwrite: Whether to overwrite an existing output. Default False.
|
|
1130
|
+
as_zip: If True (default), package all downloads into a ZIP archive
|
|
1131
|
+
at ``{destination}/{app_no}_ifw.zip``. If False, download files
|
|
1132
|
+
directly into ``{destination}/{app_no}_ifw/``.
|
|
1133
|
+
|
|
1134
|
+
Returns:
|
|
1135
|
+
IFWResult with the PatentFileWrapper, the output path, and a mapping
|
|
1136
|
+
of document_identifier to filename for each downloaded document.
|
|
1137
|
+
Returns None if no application was found.
|
|
1138
|
+
|
|
1139
|
+
Raises:
|
|
1140
|
+
FileExistsError: If the output path already exists and overwrite=False.
|
|
1141
|
+
"""
|
|
1142
|
+
wrapper = self.get_IFW_metadata(
|
|
1143
|
+
application_number=application_number,
|
|
1144
|
+
publication_number=publication_number,
|
|
1145
|
+
patent_number=patent_number,
|
|
1146
|
+
PCT_app_number=PCT_app_number,
|
|
1147
|
+
PCT_pub_number=PCT_pub_number,
|
|
1148
|
+
)
|
|
1149
|
+
if wrapper is None:
|
|
1150
|
+
return None
|
|
1151
|
+
|
|
1152
|
+
dest_dir = destination or "."
|
|
1153
|
+
app_no = wrapper.application_number_text or "unknown"
|
|
1154
|
+
downloaded_documents: dict[str, str] = {}
|
|
1155
|
+
|
|
1156
|
+
if as_zip:
|
|
1157
|
+
output_path = os.path.join(dest_dir, f"{app_no}_ifw.zip")
|
|
1158
|
+
if os.path.exists(output_path) and not overwrite:
|
|
1159
|
+
raise FileExistsError(
|
|
1160
|
+
f"ZIP archive already exists: {output_path}. Use overwrite=True to replace."
|
|
1161
|
+
)
|
|
1162
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
1163
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
1164
|
+
with zipfile.ZipFile(
|
|
1165
|
+
output_path, "w", compression=zipfile.ZIP_DEFLATED
|
|
1166
|
+
) as zf:
|
|
1167
|
+
for doc in wrapper.document_bag or []:
|
|
1168
|
+
if not doc.document_identifier:
|
|
1169
|
+
continue
|
|
1170
|
+
fmt_obj = next(
|
|
1171
|
+
(
|
|
1172
|
+
f
|
|
1173
|
+
for f in doc.document_formats
|
|
1174
|
+
if f.mime_type_identifier in ("PDF", "MS_WORD")
|
|
1175
|
+
and f.download_url
|
|
1176
|
+
),
|
|
1177
|
+
None,
|
|
1178
|
+
)
|
|
1179
|
+
if fmt_obj is None or not fmt_obj.download_url:
|
|
1180
|
+
continue
|
|
1181
|
+
try:
|
|
1182
|
+
downloaded = self._download_and_extract(
|
|
1183
|
+
url=fmt_obj.download_url,
|
|
1184
|
+
destination=tmp_dir,
|
|
1185
|
+
overwrite=True,
|
|
1186
|
+
)
|
|
1187
|
+
arcname = os.path.basename(downloaded)
|
|
1188
|
+
zf.write(downloaded, arcname=arcname)
|
|
1189
|
+
downloaded_documents[doc.document_identifier] = arcname
|
|
1190
|
+
except Exception as exc:
|
|
1191
|
+
warnings.warn(
|
|
1192
|
+
f"Failed to download document {doc.document_identifier} "
|
|
1193
|
+
f"({doc.document_code}): {exc}",
|
|
1194
|
+
stacklevel=2,
|
|
1195
|
+
)
|
|
1196
|
+
else:
|
|
1197
|
+
output_path = os.path.join(dest_dir, f"{app_no}_ifw")
|
|
1198
|
+
if os.path.exists(output_path) and not overwrite:
|
|
1199
|
+
raise FileExistsError(
|
|
1200
|
+
f"Output directory already exists: {output_path}. Use overwrite=True to replace."
|
|
1201
|
+
)
|
|
1202
|
+
os.makedirs(output_path, exist_ok=True)
|
|
1203
|
+
for doc in wrapper.document_bag or []:
|
|
1204
|
+
if not doc.document_identifier:
|
|
1205
|
+
continue
|
|
1206
|
+
fmt_obj = next(
|
|
1207
|
+
(
|
|
1208
|
+
f
|
|
1209
|
+
for f in doc.document_formats
|
|
1210
|
+
if f.mime_type_identifier in ("PDF", "MS_WORD")
|
|
1211
|
+
and f.download_url
|
|
1212
|
+
),
|
|
1213
|
+
None,
|
|
1214
|
+
)
|
|
1215
|
+
if fmt_obj is None or not fmt_obj.download_url:
|
|
1216
|
+
continue
|
|
1217
|
+
try:
|
|
1218
|
+
downloaded = self._download_and_extract(
|
|
1219
|
+
url=fmt_obj.download_url,
|
|
1220
|
+
destination=output_path,
|
|
1221
|
+
overwrite=overwrite,
|
|
1222
|
+
)
|
|
1223
|
+
downloaded_documents[doc.document_identifier] = os.path.basename(
|
|
1224
|
+
downloaded
|
|
1225
|
+
)
|
|
1226
|
+
except Exception as exc:
|
|
1227
|
+
warnings.warn(
|
|
1228
|
+
f"Failed to download document {doc.document_identifier} "
|
|
1229
|
+
f"({doc.document_code}): {exc}",
|
|
1230
|
+
stacklevel=2,
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
return IFWResult(
|
|
1234
|
+
wrapper=wrapper,
|
|
1235
|
+
output_path=os.path.abspath(output_path),
|
|
1236
|
+
downloaded_documents=downloaded_documents,
|
|
1237
|
+
)
|
|
1060
1238
|
|
|
1061
1239
|
def download_archive(
|
|
1062
1240
|
self,
|