From 6dc36e3976fb2ed37bfc62006884820dfbb27cc0 Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:38:25 -0500 Subject: [PATCH 1/8] chore: update CHANGELOG --- CHANGELOG.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab588a..a2c17ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,119 @@ All notable changes to the pyUSPTO package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.0] - TBD +## [0.4.5] - 2026-03-11 + +### Changed + +- **Refactor**: Split `_make_request` into three typed request methods: + - `_get_model` — returns parsed model `T` + - `_get_json` — returns `dict[str, Any]` + - `_stream_request` — returns `requests.Response` +- Removed all `assert isinstance` runtime checks and `# type: ignore` annotations at 28 call sites +- `DocumentBag`, `StatusCodeSearchResponse`, and `PetitionDecisionDownloadResponse` now conform to `FromDictProtocol` (`include_raw_data` parameter added to `from_dict`) + +### Fixed + +- PTAB appeals integration test query (`Appeal` → `REGULAR` for `applicationTypeCategory`) +- Read the Docs build: updated Python from 3.10 to 3.14 to satisfy `myst-parser>=3.11` requirement + +## [0.4.4] - 2026-03-08 + +### Added + +- `get_patent(patent_number)` — lookup `PatentFileWrapper` by granted patent number +- `get_publication(publication_number)` — lookup by publication number +- `get_pct(pct_number)` — lookup by PCT application or publication number (auto-detected) + +### Fixed + +- `sanitize_application_number` now strips leading zeros from PCT serial numbers + +## [0.4.3] - 2026-03-05 + +### Added + +- `get_IFW` method for bulk downloading all documents in an application's IFW +- `IFWResult` model with document map and optional ZIP output +- `get_IFW_metadata` now populates `document_bag` on the returned `PatentFileWrapper` +- Example: searching CPC codes (#102) + +### Fixed + +- Auto-quote `classification_q` values containing spaces or slashes (#101) +- Missing `typing_extensions` dependency +- CI refactor to exclude dev requirements from install + +## [0.4.2] - 2026-02-26 + +### Fixed + +- Quote multi-word values in query convenience parameters + +## [0.4.1] - 2026-02-25 + +### Added + +- Download path validation and zip-bomb protection +- Sanitize download filenames to prevent path traversal +- Skip symlinks during archive extraction +- Session lifecycle and extraction safety documentation + +### Changed + +- Enable retries for POST requests +- Remove unused `utils.http` module and `ALLOWED_METHODS` +- Enforce keyword-only arguments in `get_IFW_metadata` +- Optimize tox deps and enable parallel + +## [0.4.0] - 2026-02-23 + +### Changed + +- **Refactor**: Centralize session management in `USPTOConfig` +- `FileData.file_date` changed to `datetime` type + +### Fixed + +- Prevent path traversal in archive extraction +- Fix #79 + +## [0.3.4] - 2026-01-11 + +### Added + +- JSON parsing error handling +- Pagination validation +- Documentation for HTTP method restrictions and `include_raw_data` flag + +### Changed + +- Aligned backoff factor default + +## [0.3.3] - 2026-01-08 + +### Changed + +- Bulk data client refactor (#40) + +## [0.3.2] - 2025-12-31 + +### Changed + +- Refactor downloads (#35) +- Configurable download chunk size +- Session sharing across clients + +## [0.3.1] - 2025-12-15 + +### Added + +- `paginate_decisions` POST support +- Configurable download chunk size +- Session sharing across clients +- Bulk data endpoint updates + +## [0.3.0] - 2025-12-09 ### Added From ce157dc08db1f743d7a52b6fcf25a15f5d3b2a60 Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:41:12 -0500 Subject: [PATCH 2/8] chore: remove unused DecisionTypeCodes --- src/pyUSPTO/models/petition_decisions.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pyUSPTO/models/petition_decisions.py b/src/pyUSPTO/models/petition_decisions.py index f60c78a..ef2737a 100644 --- a/src/pyUSPTO/models/petition_decisions.py +++ b/src/pyUSPTO/models/petition_decisions.py @@ -28,9 +28,7 @@ class DecisionTypeCode(Enum): Hopefully the USPTO will give access to others in the future. """ - # GRANTED = "GRANTED" DENIED = "DENIED" - # DISMISSED = "DISMISSED" C = DENIED @classmethod @@ -38,12 +36,8 @@ def _missing_(cls, value: Any) -> "DecisionTypeCode": """Handle case-insensitive lookup and common aliases.""" if isinstance(value, str): val_upper = value.upper() - # if val_upper == "GRANTED": - # return cls.GRANTED if val_upper in ("DENIED", "C"): return cls.DENIED - # if val_upper == "DISMISSED": - # return cls.DISMISSED raise ValueError(f"{value!r} is not a valid {cls.__name__}") From 59579dfaf4f9ab4099397791a6c3a7a7a5e53203 Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:43:24 -0500 Subject: [PATCH 3/8] chore: remove documented PTAB fields that are not in the API --- src/pyUSPTO/models/ptab.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/pyUSPTO/models/ptab.py b/src/pyUSPTO/models/ptab.py index 20ddab7..bb24cdb 100644 --- a/src/pyUSPTO/models/ptab.py +++ b/src/pyUSPTO/models/ptab.py @@ -286,7 +286,6 @@ class PTABTrialProceeding: Attributes: trial_number: Trial number (e.g., "IPR2023-00123"). - trial_record_identifier: UUID identifier for the trial record. last_modified_date_time: Last modification timestamp. trial_meta_data: Trial metadata. patent_owner_data: Patent owner information. @@ -297,7 +296,6 @@ class PTABTrialProceeding: """ trial_number: str | None = None - # trial_record_identifier: Optional[str] = None # Removed: Documented but not in API. last_modified_date_time: datetime | None = None trial_meta_data: TrialMetaData | None = None patent_owner_data: PatentOwnerData | None = None @@ -345,7 +343,6 @@ def from_dict( return cls( trial_number=data.get("trialNumber"), - # trial_record_identifier=data.get("trialRecordIdentifier"), last_modified_date_time=parse_to_datetime_utc( data.get("lastModifiedDateTime") ), @@ -367,9 +364,6 @@ def to_dict(self) -> dict[str, Any]: if self.trial_number is not None: result["trialNumber"] = self.trial_number - # Removed: Documented but not in API. - # if self.trial_record_identifier is not None: - # result["trialRecordIdentifier"] = self.trial_record_identifier if self.last_modified_date_time is not None: result["lastModifiedDateTime"] = serialize_datetime_as_naive( self.last_modified_date_time @@ -489,8 +483,6 @@ class TrialDocumentData: document_type_description_text: str | None = None file_download_uri: str | None = None filing_party_category: str | None = None - # mime_type_identifier: Optional[str] = None # Removed: Documented but not in API. - # document_status: Optional[str] = None # Removed: Documented but not in API. @classmethod def from_dict( @@ -519,8 +511,6 @@ def from_dict( document_type_description_text=data.get("documentTypeDescriptionText"), file_download_uri=file_download_uri, filing_party_category=data.get("filingPartyCategory"), - # mime_type_identifier=data.get("mimeTypeIdentifier"), - # document_status=data.get("documentStatus"), ) def to_dict(self) -> dict[str, Any]: @@ -553,11 +543,6 @@ def to_dict(self) -> dict[str, Any]: result["fileDownloadURI"] = self.file_download_uri # Uppercase URI if self.filing_party_category is not None: result["filingPartyCategory"] = self.filing_party_category - # Removed: Documented but not in API. - # if self.mime_type_identifier is not None: - # result["mimeTypeIdentifier"] = self.mime_type_identifier - # if self.document_status is not None: - # result["documentStatus"] = self.document_status return result From 748bf2fdefb9db3d299d4ab6f0280d4c7c38e5ab Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:51:05 -0500 Subject: [PATCH 4/8] chore: fix tautological assertions and dead conditionals --- src/pyUSPTO/models/patent_data.py | 14 ++++---------- tests/models/test_petition_decision_models.py | 18 ++++++++---------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/pyUSPTO/models/patent_data.py b/src/pyUSPTO/models/patent_data.py index 2d242a0..e30c905 100644 --- a/src/pyUSPTO/models/patent_data.py +++ b/src/pyUSPTO/models/patent_data.py @@ -1983,18 +1983,12 @@ def to_dict(self) -> dict[str, Any]: d["publicationDateBag"] = [ serialize_date(dt) for dt in self.publication_date_bag if dt ] - if ( - "publication_date_bag" in d - and "publication_date_bag" != "publicationDateBag" - ): + if "publication_date_bag" in d: d.pop("publication_date_bag") d["firstInventorToFileIndicator"] = serialize_bool_to_yn( self.first_inventor_to_file_indicator ) - if ( - "first_inventor_to_file_indicator" in d - and "first_inventor_to_file_indicator" != "firstInventorToFileIndicator" - ): + if "first_inventor_to_file_indicator" in d: d.pop("first_inventor_to_file_indicator") if self.entity_status_data: d["entityStatusData"] = self.entity_status_data.to_dict() @@ -2002,10 +1996,10 @@ def to_dict(self) -> dict[str, Any]: d.pop("entityStatusData", None) d.pop("entity_status_data", None) d["applicantBag"] = [a.to_dict() for a in self.applicant_bag] - if "applicant_bag" in d and "applicant_bag" != "applicantBag": + if "applicant_bag" in d: d.pop("applicant_bag") d["inventorBag"] = [i.to_dict() for i in self.inventor_bag] - if "inventor_bag" in d and "inventor_bag" != "inventorBag": + if "inventor_bag" in d: d.pop("inventor_bag") if "class_field" in d: d["class"] = d.pop("class_field") diff --git a/tests/models/test_petition_decision_models.py b/tests/models/test_petition_decision_models.py index 310ad1d..3dcc36d 100644 --- a/tests/models/test_petition_decision_models.py +++ b/tests/models/test_petition_decision_models.py @@ -497,10 +497,8 @@ class TestDecisionTypeCodeEnum: def test_enum_values(self) -> None: """Test enum has expected values.""" - assert DecisionTypeCode.DENIED == DecisionTypeCode.DENIED - assert DecisionTypeCode.C == DecisionTypeCode.C - # assert DecisionTypeCode.DISMISSED == DecisionTypeCode.DISMISSED - # assert DecisionTypeCode.GRANTED == DecisionTypeCode.GRANTED + assert DecisionTypeCode("DENIED") == DecisionTypeCode.DENIED + assert DecisionTypeCode.C == DecisionTypeCode.DENIED def test_missing_case_insensitive(self) -> None: """Test _missing_ handles case-insensitive lookup.""" @@ -508,10 +506,6 @@ def test_missing_case_insensitive(self) -> None: assert DecisionTypeCode("C") == DecisionTypeCode.DENIED with pytest.raises(ValueError): DecisionTypeCode("not_a_real_code") - # assert DecisionTypeCode("DISMISSED") == DecisionTypeCode.DISMISSED - # assert DecisionTypeCode("granted") == DecisionTypeCode.GRANTED - # assert DecisionTypeCode("GRANTED") == DecisionTypeCode.GRANTED - # assert DecisionTypeCode("dismissed") == DecisionTypeCode.DISMISSED class TestDocumentDirectionCategoryEnum: @@ -519,8 +513,12 @@ class TestDocumentDirectionCategoryEnum: def test_enum_values(self) -> None: """Test enum has expected values.""" - assert DocumentDirectionCategory.INCOMING == DocumentDirectionCategory.INCOMING - assert DocumentDirectionCategory.OUTGOING == DocumentDirectionCategory.OUTGOING + assert ( + DocumentDirectionCategory("INCOMING") == DocumentDirectionCategory.INCOMING + ) + assert ( + DocumentDirectionCategory("OUTGOING") == DocumentDirectionCategory.OUTGOING + ) def test_missing_case_insensitive(self) -> None: """Test _missing_ handles case-insensitive lookup.""" From c688f8882898e9796a0359f04430ad9ac0601644 Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:01:07 -0500 Subject: [PATCH 5/8] chore: fix tautological assertions and unused variables in tests --- tests/clients/test_base.py | 6 +-- tests/clients/test_patent_data_clients.py | 60 ++++++++--------------- 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/tests/clients/test_base.py b/tests/clients/test_base.py index cfa8338..634ab77 100644 --- a/tests/clients/test_base.py +++ b/tests/clients/test_base.py @@ -1782,7 +1782,7 @@ def test_max_size_enforcement(self, tmp_path: Any) -> None: client._extract_archive(archive_path, extract_to=extract_to, max_size=500) # Should succeed with max_size=2000 - result = client._extract_archive( + client._extract_archive( archive_path, extract_to=extract_to, max_size=2000 ) assert (extract_to / "large_file.txt").exists() @@ -1823,7 +1823,7 @@ def test_tar_with_directories(self, tmp_path: Any) -> None: tar.add(temp_dir, arcname="testdir") extract_to = tmp_path / "extracted" - result = client._extract_archive(archive_path, extract_to=extract_to) + client._extract_archive(archive_path, extract_to=extract_to) # Directory entries are skipped, but files are extracted assert (extract_to / "testdir" / "file.txt").exists() @@ -1843,7 +1843,7 @@ def test_zip_with_directories(self, tmp_path: Any) -> None: zip_ref.writestr("testdir/file.txt", "content") extract_to = tmp_path / "extracted" - result = client._extract_archive(archive_path, extract_to=extract_to) + client._extract_archive(archive_path, extract_to=extract_to) # Directory entries are skipped, but files are extracted assert (extract_to / "testdir" / "file.txt").exists() diff --git a/tests/clients/test_patent_data_clients.py b/tests/clients/test_patent_data_clients.py index dcf80cd..6fe2b84 100644 --- a/tests/clients/test_patent_data_clients.py +++ b/tests/clients/test_patent_data_clients.py @@ -2654,7 +2654,6 @@ def test_api_error_handling( client.search_applications(query="test") - class TestApplicationNumberSanitization: """Tests for application number sanitization and validation.""" @@ -3246,57 +3245,38 @@ def test_to_csv_with_multiple_wrappers( ) meta2_dict = mock_application_meta_data.to_dict() - if meta2_dict: - meta2_dict["inventionTitle"] = "Another Test Invention" - meta2_dict["filingDate"] = "2021-02-02" - meta2_dict["firstInventorName"] = "Jane Inventor" - # Ensure other fields needed for CSV are present if they were None in original mock_app_meta - meta2_dict.setdefault("applicationTypeLabelName", "Design") - meta2_dict.setdefault("publicationCategoryBag", ["S1"]) - meta2_dict.setdefault("applicationStatusDescriptionText", "Allowed") - meta2_dict.setdefault("applicationStatusDate", "2023-10-10") - - wrapper2_meta = ApplicationMetaData.from_dict(meta2_dict) - wrapper2 = PatentFileWrapper( - application_number_text="APP002", application_meta_data=wrapper2_meta - ) - response = PatentDataResponse( - count=2, patent_file_wrapper_data_bag=[wrapper1, wrapper2] - ) - else: - wrapper2_meta = ApplicationMetaData( - invention_title="Fallback Title", - first_inventor_name="Fallback Inventor", - filing_date=date(2021, 2, 2), - application_type_label_name="Utility", - publication_category_bag=["A1"], - application_status_description_text="Status", - application_status_date=date(2021, 2, 3), - ) - wrapper2 = PatentFileWrapper( - application_number_text="APP002", application_meta_data=wrapper2_meta - ) - response = PatentDataResponse( - count=1, patent_file_wrapper_data_bag=[wrapper1] - ) # fallback to 1 if dict was None + meta2_dict["inventionTitle"] = "Another Test Invention" + meta2_dict["filingDate"] = "2021-02-02" + meta2_dict["firstInventorName"] = "Jane Inventor" + meta2_dict.setdefault("applicationTypeLabelName", "Design") + meta2_dict.setdefault("publicationCategoryBag", ["S1"]) + meta2_dict.setdefault("applicationStatusDescriptionText", "Allowed") + meta2_dict.setdefault("applicationStatusDate", "2023-10-10") + + wrapper2_meta = ApplicationMetaData.from_dict(meta2_dict) + wrapper2 = PatentFileWrapper( + application_number_text="APP002", application_meta_data=wrapper2_meta + ) + response = PatentDataResponse( + count=2, patent_file_wrapper_data_bag=[wrapper1, wrapper2] + ) csv_string = response.to_csv() reader = csv.reader(io.StringIO(csv_string)) next(reader) data_rows = list(reader) - assert len(data_rows) == response.count + assert len(data_rows) == 2 assert data_rows[0][0] == wrapper1_meta.invention_title assert data_rows[0][1] == "APP001" assert data_rows[0][2] == serialize_date(wrapper1_meta.filing_date) assert data_rows[0][7] == wrapper1_meta.first_inventor_name - if response.count > 1: - assert data_rows[1][0] == wrapper2_meta.invention_title - assert data_rows[1][1] == "APP002" - assert data_rows[1][2] == serialize_date(wrapper2_meta.filing_date) - assert data_rows[1][7] == wrapper2_meta.first_inventor_name + assert data_rows[1][0] == wrapper2_meta.invention_title + assert data_rows[1][1] == "APP002" + assert data_rows[1][2] == serialize_date(wrapper2_meta.filing_date) + assert data_rows[1][7] == wrapper2_meta.first_inventor_name class TestGetIFWDownload: From 3c492630e9423acf8645b4300af5b8e4037f965b Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:11:22 -0500 Subject: [PATCH 6/8] chore: standardize example download paths to DEST_PATH --- examples/bulk_data_example.py | 6 ++++-- examples/ifw_example.py | 29 +++++++++++++++++++------- examples/patent_data_example.py | 14 ++++++------- examples/petition_decisions_example.py | 6 ++++-- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/examples/bulk_data_example.py b/examples/bulk_data_example.py index ca05569..3e44fbf 100644 --- a/examples/bulk_data_example.py +++ b/examples/bulk_data_example.py @@ -10,6 +10,8 @@ from pyUSPTO.config import USPTOConfig from pyUSPTO.models.bulk_data import FileData +DEST_PATH = "./notes/download-example" + def format_size(size_bytes: int | float) -> str: """Format a size in bytes to a human-readable string (KB, MB, GB, etc.). @@ -155,7 +157,7 @@ def format_size(size_bytes: int | float) -> str: # Download with extraction (default behavior for archives) downloaded_path = client.download_file( file_data=min_file, - destination="./downloads", + destination=DEST_PATH, overwrite=True, extract=True, # Auto-extract if it's a tar.gz or zip ) @@ -176,7 +178,7 @@ def format_size(size_bytes: int | float) -> str: # Download without extraction downloaded_path = client.download_file( file_data=min_file, - destination="./downloads", + destination=DEST_PATH, overwrite=True, extract=False, # Keep archive compressed ) diff --git a/examples/ifw_example.py b/examples/ifw_example.py index 8e51643..660f91f 100644 --- a/examples/ifw_example.py +++ b/examples/ifw_example.py @@ -37,6 +37,8 @@ config = USPTOConfig(api_key=api_key) client = PatentDataClient(config=config) +DEST_PATH = "./notes/download-example" + print("\nBeginning API requests with configured client:") @@ -81,12 +83,20 @@ print("\nGet IFW + download all prosecution docs as a ZIP archive -->") -ifw_result = client.get_IFW(application_number=application_number, destination="./download-example", overwrite=True) +ifw_result = client.get_IFW( + application_number=application_number, + destination=DEST_PATH, + overwrite=True, +) if ifw_result: - print(f"Title: {ifw_result.wrapper.application_meta_data.invention_title if ifw_result.wrapper.application_meta_data else 'N/A'}") + print( + f"Title: {ifw_result.wrapper.application_meta_data.invention_title if ifw_result.wrapper.application_meta_data else 'N/A'}" + ) print(f"Output: {ifw_result.output_path}") doc_bag = ifw_result.wrapper.document_bag or [] - print(f"Documents downloaded: {len(ifw_result.downloaded_documents)} of {len(doc_bag)}") + print( + f"Documents downloaded: {len(ifw_result.downloaded_documents)} of {len(doc_bag)}" + ) for doc in doc_bag: if doc.document_identifier: filename = ifw_result.downloaded_documents.get(doc.document_identifier) @@ -94,7 +104,12 @@ print(f" {doc.document_code} [{doc.document_identifier}] {status}") print("\nGet IFW + download all prosecution docs as a directory (no ZIP) -->") -ifw_dir_result = client.get_IFW(application_number=application_number, destination="./download-example", overwrite=True, as_zip=False) +ifw_dir_result = client.get_IFW( + application_number=application_number, + destination=DEST_PATH, + overwrite=True, + as_zip=False, +) if ifw_dir_result: print(f"Output directory: {ifw_dir_result.output_path}") @@ -103,9 +118,8 @@ if app_no_ifw and app_no_ifw.pgpub_document_meta_data: pgpub_archive = app_no_ifw.pgpub_document_meta_data print(json.dumps(pgpub_archive.to_dict(), indent=2)) - download_path = "./download-example" file_path = client.download_archive( - printed_metadata=pgpub_archive, destination=download_path, overwrite=True + printed_metadata=pgpub_archive, destination=DEST_PATH, overwrite=True ) print(f"-Downloaded document to: {file_path}") @@ -113,8 +127,7 @@ if app_no_ifw and app_no_ifw.grant_document_meta_data: grant_archive = app_no_ifw.grant_document_meta_data print(json.dumps(grant_archive.to_dict(), indent=2)) - download_path = "./download-example" file_path = client.download_archive( - printed_metadata=grant_archive, destination=download_path, overwrite=True + printed_metadata=grant_archive, destination=DEST_PATH, overwrite=True ) print(f"-Downloaded document to: {file_path}") diff --git a/examples/patent_data_example.py b/examples/patent_data_example.py index 5809925..88f3b14 100644 --- a/examples/patent_data_example.py +++ b/examples/patent_data_example.py @@ -23,7 +23,7 @@ config = USPTOConfig(api_key=api_key) client = PatentDataClient(config=config) -DEST_PATH = "./download-example" +DEST_PATH = "./notes/download-example" print("\nBeginning API requests with configured client:") @@ -78,9 +78,11 @@ print("\nGenerating CSV for the current response (first few rows shown):") csv_data = response.to_csv() # You could save this csv_data to a file: - # with open("patent_search_results.csv", "w", newline="", encoding="utf-8") as f: - # f.write(csv_data) - # print("\nFull CSV data saved to patent_search_results.csv (example).") + csv_path = os.path.join(DEST_PATH, "patent_search_results.csv") + os.makedirs(DEST_PATH, exist_ok=True) + with open(csv_path, "w", newline="", encoding="utf-8") as f: + f.write(csv_data) + print(f"\nFull CSV data saved to {csv_path}.") except Exception as e: @@ -308,9 +310,7 @@ # to the application, so each result may have multiple codes. try: print("\nSearching by CPC classification code 'H10D 64/667'...") - cpc_response = client.search_applications( - classification_q="H10D 64/667", limit=3 - ) + cpc_response = client.search_applications(classification_q="H10D 64/667", limit=3) print(f"Found {cpc_response.count} applications with CPC code H10D 64/667.") for patent_wrapper in cpc_response.patent_file_wrapper_data_bag: app_meta = patent_wrapper.application_meta_data diff --git a/examples/petition_decisions_example.py b/examples/petition_decisions_example.py index f22ffdb..7a424ce 100644 --- a/examples/petition_decisions_example.py +++ b/examples/petition_decisions_example.py @@ -24,7 +24,7 @@ config = USPTOConfig(api_key=api_key) client = FinalPetitionDecisionsClient(config=config) -DEST_PATH = "./download-example" +DEST_PATH = "./notes/download-example" print("\nBeginning API requests with configured client:") @@ -234,7 +234,9 @@ d = client.get_decision_by_id( decision.petition_decision_record_identifier, include_documents=True ) - print(f"Getting docs for patent: {d.invention_title} with id: {d.petition_decision_record_identifier}") # type: ignore + print( + f"Getting docs for patent: {d.invention_title} with id: {d.petition_decision_record_identifier}" + ) # type: ignore if d and d.document_bag: for doc in d.document_bag: if doc.download_option_bag and len(doc.download_option_bag) > 0: From eed5bb37b574bd7257e4ef3801f2d56c4dd45daa Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 09:57:26 -0500 Subject: [PATCH 7/8] chore: Update examples; unify style --- examples/bulk_data_example.py | 153 +++---- examples/error_handling_example.py | 56 +-- examples/ifw_example.py | 88 ++-- examples/patent_data_example.py | 555 ++++++++++++------------- examples/petition_decisions_example.py | 483 +++++++++------------ examples/ptab_appeals_example.py | 384 +++++++---------- examples/ptab_interferences_example.py | 442 +++++++++----------- examples/ptab_trials_example.py | 312 ++++++-------- src/pyUSPTO/__init__.py | 13 +- 9 files changed, 1064 insertions(+), 1422 deletions(-) diff --git a/examples/bulk_data_example.py b/examples/bulk_data_example.py index 3e44fbf..bf28635 100644 --- a/examples/bulk_data_example.py +++ b/examples/bulk_data_example.py @@ -1,27 +1,18 @@ -"""Example usage of pyUSPTO for the BulkDataClient. +"""Example usage of pyUSPTO for bulk data products. -This example demonstrates how to use the BulkDataClient to interact with the USPTO Bulk Data API. -It shows how to search for products, retrieve product details, and download files. +Demonstrates the BulkDataClient for searching products, listing files, +and downloading bulk data archives. """ import os -from pyUSPTO.clients import BulkDataClient -from pyUSPTO.config import USPTOConfig -from pyUSPTO.models.bulk_data import FileData +from pyUSPTO import BulkDataClient, FileData, USPTOConfig DEST_PATH = "./notes/download-example" def format_size(size_bytes: int | float) -> str: - """Format a size in bytes to a human-readable string (KB, MB, GB, etc.). - - Args: - size_bytes: The size in bytes to format - - Returns: - A human-readable string representation of the size - """ + """Format a size in bytes to a human-readable string (KB, MB, GB, etc.).""" if size_bytes == 0: return "0 B" @@ -31,39 +22,22 @@ def format_size(size_bytes: int | float) -> str: size_bytes /= 1024 i += 1 - # Round to 2 decimal places return f"{size_bytes:.2f} {size_names[i]}" -# ============================================================================ -# Client Initialization Methods -# ============================================================================ - -# Method 1: Initialize with USPTOConfig object -print("\nMethod 1: Initialize with USPTOConfig") -config = USPTOConfig(api_key="YOUR_API_KEY_HERE") +# --- Client Initialization --- +api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") +if api_key == "YOUR_API_KEY_HERE": + raise ValueError( + "API key is not set. Set the USPTO_API_KEY environment variable." + ) +config = USPTOConfig(api_key=api_key) client = BulkDataClient(config=config) -# Method 2: Initialize from environment variables (recommended) -print("\nMethod 2: Initialize from environment variables") -os.environ["USPTO_API_KEY"] = "YOUR_API_KEY_HERE" # Set this outside your script -config_from_env = USPTOConfig.from_env() -client = BulkDataClient(config=config_from_env) - -print("\n" + "=" * 60) -print("Beginning API requests with configured client") -print("=" * 60) - - -# ============================================================================ -# Example 1: Search for Products -# ============================================================================ +print("-" * 40) +print("Example 1: Search for products") +print("-" * 40) -print("\n--- Example 1: Search for Products ---") -# The Bulk Data API supports full-text search via the query parameter -# Field-specific queries (e.g., "productIdentifier:value") are not supported - -# Search for patent-related products response = client.search_products(query="patent", limit=5) print(f"Found {response.count} products matching 'patent'") @@ -74,30 +48,22 @@ def format_size(size_bytes: int | float) -> str: print(f" Total files: {product.product_file_total_quantity}") print(f" Total size: {format_size(product.product_total_file_size)}") +print("-" * 40) +print("Example 2: Paginate through products") +print("-" * 40) -# ============================================================================ -# Example 2: Paginate Through All Products -# ============================================================================ - -print("\n--- Example 2: Paginate Through Products ---") -# Use pagination to iterate through all matching products - +max_items = 20 count = 0 for product in client.paginate_products(query="trademark", limit=10): count += 1 print(f" {count}. {product.product_title_text} ({product.product_identifier})") - if count >= 20: # Limit output for example - print(" ... (stopping after 20 products)") + if count >= max_items: + print(f" ... (stopping at {max_items} products)") break - -# ============================================================================ -# Example 3: Get Product Details by ID -# ============================================================================ - -print("\n--- Example 3: Get Product by ID ---") -# Retrieve a specific product by its identifier -# Use include_files=True to get file listing +print("-" * 40) +print("Example 3: Get product by ID") +print("-" * 40) product_id = "PTGRXML" # Patent Grant Full-Text Data (No Images) - XML product = client.get_product_by_id(product_id, include_files=True, latest=True) @@ -109,13 +75,9 @@ def format_size(size_bytes: int | float) -> str: print(f"Categories: {product.product_dataset_category_array_text}") print(f"Date range: {product.product_from_date} to {product.product_to_date}") - -# ============================================================================ -# Example 4: List Files for a Product -# ============================================================================ - -print("\n--- Example 4: List Files for a Product ---") -# Get product with files and display file details +print("-" * 40) +print("Example 4: List files for a product") +print("-" * 40) if product.product_file_bag and product.product_file_bag.file_data_bag: print(f"Found {len(product.product_file_bag.file_data_bag)} file(s):") @@ -132,13 +94,9 @@ def format_size(size_bytes: int | float) -> str: else: print("No files found for this product") - -# ============================================================================ -# Example 5: Download a File -# ============================================================================ - -print("\n--- Example 5: Download a File ---") -# Download a file from the product +print("-" * 40) +print("Example 5: Download a file (with extraction)") +print("-" * 40) min_file: FileData | None = None last_bytes: float = float("inf") @@ -153,40 +111,23 @@ def format_size(size_bytes: int | float) -> str: print(f"Downloading smallest file: {min_file.file_name}") print(f"Size: {format_size(min_file.file_size)}") - try: - # Download with extraction (default behavior for archives) - downloaded_path = client.download_file( - file_data=min_file, - destination=DEST_PATH, - overwrite=True, - extract=True, # Auto-extract if it's a tar.gz or zip - ) - print(f"SUCCESS: Downloaded to {downloaded_path}") - except Exception as e: - print(f"ERROR: {e}") - - -# ============================================================================ -# Example 6: Download Without Extraction -# ============================================================================ + downloaded_path = client.download_file( + file_data=min_file, + destination=DEST_PATH, + overwrite=True, + extract=True, + ) + print(f"Downloaded to {downloaded_path}") -print("\n--- Example 6: Download Without Extraction ---") -# Download archive file without extracting +print("-" * 40) +print("Example 6: Download without extraction") +print("-" * 40) if product.product_file_bag and product.product_file_bag.file_data_bag and min_file: - try: - # Download without extraction - downloaded_path = client.download_file( - file_data=min_file, - destination=DEST_PATH, - overwrite=True, - extract=False, # Keep archive compressed - ) - print(f"SUCCESS: Archive saved to {downloaded_path}") - except Exception as e: - print(f"ERROR: {e}") - - -print("\n" + "=" * 60) -print("Examples complete!") -print("=" * 60) + downloaded_path = client.download_file( + file_data=min_file, + destination=DEST_PATH, + overwrite=True, + extract=False, + ) + print(f"Archive saved to {downloaded_path}") diff --git a/examples/error_handling_example.py b/examples/error_handling_example.py index 2b75aff..74ec771 100644 --- a/examples/error_handling_example.py +++ b/examples/error_handling_example.py @@ -1,64 +1,68 @@ -"""Error handling example for pyUSPTO. +"""Example usage of pyUSPTO for error handling patterns. -This example demonstrates how to handle common errors when using the USPTO API clients. +Demonstrates how to handle common errors when using the USPTO API clients. """ import os import time -from pyUSPTO.clients import PatentDataClient -from pyUSPTO.config import USPTOConfig -from pyUSPTO.exceptions import ( +from pyUSPTO import ( + HTTPConfig, + PatentDataClient, USPTOApiAuthError, USPTOApiNotFoundError, USPTOApiRateLimitError, + USPTOConfig, USPTOConnectionError, USPTOTimeout, ) -from pyUSPTO.http_config import HTTPConfig -# Initialize client +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") config = USPTOConfig(api_key=api_key) client = PatentDataClient(config=config) -# Example 1: Handle authentication errors +print("-" * 40) print("Example 1: Authentication errors") +print("-" * 40) + try: - # This will fail with invalid API key bad_client = PatentDataClient(config=config) bad_client.search_applications(limit=1) except USPTOApiAuthError as e: print(f"Authentication failed: {e}") print("Check your API key and try again.") -# Example 2: Handle not found errors -print("\nExample 2: Not found errors") +print("-" * 40) +print("Example 2: Not found errors") +print("-" * 40) + try: - # Try to get a non-existent application client.get_application_by_number("99999999") except USPTOApiNotFoundError as e: print(f"Resource not found: {e}") print("The application number may be invalid or not in the system.") -# Example 3: Handle rate limiting with retry -print("\nExample 3: Rate limiting") +print("-" * 40) +print("Example 3: Rate limiting") +print("-" * 40) + try: - # If you hit rate limits, the API returns 429 results = client.search_applications(limit=100) print(f"Retrieved {results.count} results") except USPTOApiRateLimitError as e: print(f"Rate limit exceeded: {e}") print("Waiting 60 seconds before retry...") time.sleep(60) - # Retry the request results = client.search_applications(limit=100) print(f"Retry successful: {results.count} results") -# Example 4: Handle timeouts with custom configuration -print("\nExample 4: Timeout handling") +print("-" * 40) +print("Example 4: Timeout handling") +print("-" * 40) + # Configure shorter timeout for demonstration -http_config = HTTPConfig(timeout=1) # Very short timeout +http_config = HTTPConfig(timeout=1) config = USPTOConfig(api_key=api_key, http_config=http_config) timeout_client = PatentDataClient(config=config) @@ -68,17 +72,20 @@ print(f"Request timed out: {e}") print("Consider increasing timeout or checking network connection.") -# Example 5: Handle connection errors -print("\nExample 5: Connection errors") +print("-" * 40) +print("Example 5: Connection errors") +print("-" * 40) + try: - # This might fail due to network issues client.search_applications(limit=10) except USPTOConnectionError as e: print(f"Connection error: {e}") print("Check your network connection and try again.") -# Example 6: Catch-all for unexpected errors -print("\nExample 6: General error handling") +print("-" * 40) +print("Example 6: General error handling") +print("-" * 40) + try: results = client.search_applications(patent_number_q="10000000", limit=5) if results.count > 0: @@ -94,5 +101,4 @@ except USPTOConnectionError: print("Connection failed - check network") except Exception as e: - # Catch any other unexpected errors print(f"Unexpected error: {e}") diff --git a/examples/ifw_example.py b/examples/ifw_example.py index 660f91f..8a72b5b 100644 --- a/examples/ifw_example.py +++ b/examples/ifw_example.py @@ -1,88 +1,68 @@ -"""Example usage of pyUSPTO for IFW data. - -This example demonstrates how to use the PatentDataClient to retrieve Image File -Wrapper (IFW) data from the USPTO Patent Data API. It covers: - -- get_IFW_metadata(): retrieve a PatentFileWrapper (with populated document_bag) - using any of the five supported identifiers: - - application_number - - patent_number - - publication_number - - PCT_app_number - - PCT_pub_number - -- get_IFW(): retrieve metadata AND bulk-download all prosecution history documents - (PDF preferred, DOCX fallback; XML and formatless docs are skipped). Returns an - IFWResult with: - - wrapper: the PatentFileWrapper - - output_path: path to the ZIP archive (as_zip=True, default) or output directory - - downloaded_documents: dict mapping document_identifier -> filename, allowing - each Document in document_bag to be linked to its downloaded file - -- download_archive() / download_publication(): download the pgpub or grant XML - archive from PrintedMetaData. +"""Example usage of pyUSPTO for Image File Wrapper (IFW) retrieval. + +Demonstrates the PatentDataClient for retrieving IFW metadata via multiple identifier +types, bulk-downloading prosecution documents, and downloading publication archives. """ import json import os -from pyUSPTO.clients.patent_data import PatentDataClient -from pyUSPTO.config import USPTOConfig +from pyUSPTO import PatentDataClient, USPTOConfig + +DEST_PATH = "./notes/download-example" +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( - "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable." + "API key is not set. Set the USPTO_API_KEY environment variable." ) config = USPTOConfig(api_key=api_key) client = PatentDataClient(config=config) -DEST_PATH = "./notes/download-example" - - -print("\nBeginning API requests with configured client:") +print("-" * 40) +print("Example 1: Get IFW metadata by identifier type") +print("-" * 40) -print("\nGet IFW Based on Application Number ->") +# Application number application_number = "14412875" app_no_ifw = client.get_IFW_metadata(application_number=application_number) if app_no_ifw and app_no_ifw.application_meta_data: print(f"Title: {app_no_ifw.application_meta_data.invention_title}") - print(f" - IFW Found based on App No: {application_number}") + print(f" IFW found via application number: {application_number}") - -print("\nGet IFW Based on Patent Number ->") +# Patent number patent_number = "10765880" pat_no_ifw = client.get_IFW_metadata(patent_number=patent_number) if pat_no_ifw and pat_no_ifw.application_meta_data: print(f"Title: {pat_no_ifw.application_meta_data.invention_title}") - print(f" - IFW Found based on Pat No: {patent_number}") - + print(f" IFW found via patent number: {patent_number}") -print("\nGet IFW Based on Publication Number ->") +# Publication number publication_number = "*20150157873*" pub_no_ifw = client.get_IFW_metadata(publication_number=publication_number) if pub_no_ifw and pub_no_ifw.application_meta_data: print(f"Title: {pub_no_ifw.application_meta_data.invention_title}") - print(f" - IFW Found based on Pub No: {publication_number}") - + print(f" IFW found via publication number: {publication_number}") -print("\nGet IFW Based on PCT App Number ->") +# PCT application number PCT_app_number = "PCT/US2008/12705" pct_app_no_ifw = client.get_IFW_metadata(PCT_app_number=PCT_app_number) if pct_app_no_ifw and pct_app_no_ifw.application_meta_data: print(f"Title: {pct_app_no_ifw.application_meta_data.invention_title}") - print(f" - IFW Found based on PCT App No: {PCT_app_number}") + print(f" IFW found via PCT application number: {PCT_app_number}") - -print("\nGet IFW Based on PCT Pub Number ->") +# PCT publication number PCT_pub_number = "*2009064413*" pct_pub_no_ifw = client.get_IFW_metadata(PCT_pub_number=PCT_pub_number) if pct_pub_no_ifw and pct_pub_no_ifw.application_meta_data: print(f"Title: {pct_pub_no_ifw.application_meta_data.invention_title}") - print(f" - IFW Found based on PCT Pub No: {PCT_pub_number}") + print(f" IFW found via PCT publication number: {PCT_pub_number}") +print("-" * 40) +print("Example 2: Download IFW as ZIP archive") +print("-" * 40) -print("\nGet IFW + download all prosecution docs as a ZIP archive -->") ifw_result = client.get_IFW( application_number=application_number, destination=DEST_PATH, @@ -103,7 +83,10 @@ status = f"-> {filename}" if filename else "(skipped)" print(f" {doc.document_code} [{doc.document_identifier}] {status}") -print("\nGet IFW + download all prosecution docs as a directory (no ZIP) -->") +print("-" * 40) +print("Example 3: Download IFW as directory") +print("-" * 40) + ifw_dir_result = client.get_IFW( application_number=application_number, destination=DEST_PATH, @@ -113,21 +96,26 @@ if ifw_dir_result: print(f"Output directory: {ifw_dir_result.output_path}") +print("-" * 40) +print("Example 4: Download publication XML") +print("-" * 40) -print("\nNow let's download the Patent Publication Text -->") if app_no_ifw and app_no_ifw.pgpub_document_meta_data: pgpub_archive = app_no_ifw.pgpub_document_meta_data print(json.dumps(pgpub_archive.to_dict(), indent=2)) file_path = client.download_archive( printed_metadata=pgpub_archive, destination=DEST_PATH, overwrite=True ) - print(f"-Downloaded document to: {file_path}") + print(f"Downloaded publication XML to: {file_path}") + +print("-" * 40) +print("Example 5: Download grant XML") +print("-" * 40) -print("\nNow let's download the Patent Grant Text -->") if app_no_ifw and app_no_ifw.grant_document_meta_data: grant_archive = app_no_ifw.grant_document_meta_data print(json.dumps(grant_archive.to_dict(), indent=2)) file_path = client.download_archive( printed_metadata=grant_archive, destination=DEST_PATH, overwrite=True ) - print(f"-Downloaded document to: {file_path}") + print(f"Downloaded grant XML to: {file_path}") diff --git a/examples/patent_data_example.py b/examples/patent_data_example.py index 88f3b14..bfcb9b2 100644 --- a/examples/patent_data_example.py +++ b/examples/patent_data_example.py @@ -1,337 +1,310 @@ -"""Example usage of the pyUSPTO module for patent data. +"""Example usage of pyUSPTO for patent application search and retrieval. -This example demonstrates how to use the PatentDataClient to interact with the USPTO Patent Data API. -It shows how to retrieve patent applications, search for patents by various criteria, and access -detailed patent information including inventors, applicants, assignments, and more. +Demonstrates the PatentDataClient for searching applications, retrieving metadata, +downloading documents, and exporting CSV. """ import json import os -from pyUSPTO.clients.patent_data import PatentDataClient -from pyUSPTO.config import USPTOConfig -from pyUSPTO.models.patent_data import ApplicationContinuityData +from pyUSPTO import ApplicationContinuityData, PatentDataClient, USPTOConfig -# --- Initialization --- -# Initialize the client with API key from ENV Var. -print("Initialize with config") +DEST_PATH = "./notes/download-example" + +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( - "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable." + "API key is not set. Set the USPTO_API_KEY environment variable." ) config = USPTOConfig(api_key=api_key) client = PatentDataClient(config=config) -DEST_PATH = "./notes/download-example" +print("-" * 40) +print("Example 1: Default search") +print("-" * 40) + +response = client.search_applications(limit=5) +print( + f"Found {response.count} total patent applications matching default/broad criteria." +) +print( + f"Displaying first {len(response.patent_file_wrapper_data_bag)} applications from response:" +) + +for patent_wrapper in response.patent_file_wrapper_data_bag: + app_meta = patent_wrapper.application_meta_data + if app_meta: + print(f"\n Application: {patent_wrapper.application_number_text}") + print(f" Title: {app_meta.invention_title}") + print(f" Status: {app_meta.application_status_description_text}") + print(f" Filing Date: {app_meta.filing_date}") + + if app_meta.patent_number: + print(f" Patent Number: {app_meta.patent_number}") + print(f" Grant Date: {app_meta.grant_date}") + + if app_meta.inventor_bag: + print(" Inventors:") + for inventor in app_meta.inventor_bag: + name_parts = [ + part + for part in [inventor.first_name, inventor.last_name] + if part + ] + print(f" - {' '.join(name_parts).strip()}") + if inventor.correspondence_address_bag: + address = inventor.correspondence_address_bag[0] + if address.city_name and address.geographic_region_code: + print( + f" ({address.city_name}, {address.geographic_region_code})" + ) + + if app_meta.applicant_bag: + print(" Applicants:") + for applicant in app_meta.applicant_bag: + print(f" - {applicant.applicant_name_text}") + +# Export results to CSV +if response.count > 0: + print("\nGenerating CSV for the current response:") + csv_data = response.to_csv() + csv_path = os.path.join(DEST_PATH, "patent_search_results.csv") + os.makedirs(DEST_PATH, exist_ok=True) + with open(csv_path, "w", newline="", encoding="utf-8") as f: + f.write(csv_data) + print(f"Full CSV data saved to {csv_path}.") + +print("-" * 40) +print("Example 2: Search by inventor name") +print("-" * 40) + +inventor_search_response = client.search_applications( + inventor_name_q="Smith", limit=2 +) +print( + f"Found {inventor_search_response.count} patents with 'Smith' as inventor (showing up to 2)." +) +for patent_wrapper in inventor_search_response.patent_file_wrapper_data_bag: + if patent_wrapper.application_meta_data: + print( + f" - App No: {patent_wrapper.application_number_text}, Title: {patent_wrapper.application_meta_data.invention_title}" + ) -print("\nBeginning API requests with configured client:") +print("-" * 40) +print("Example 3: Search by filing date range") +print("-" * 40) + +date_search_response = client.search_applications( + filing_date_from_q="2020-01-01", filing_date_to_q="2020-12-31", limit=2 +) +print( + f"Found {date_search_response.count} patents filed in 2020 (showing up to 2)." +) +for patent_wrapper in date_search_response.patent_file_wrapper_data_bag: + if patent_wrapper.application_meta_data: + print( + f" - App No: {patent_wrapper.application_number_text}, Filing Date: {patent_wrapper.application_meta_data.filing_date}" + ) -# Get some patent applications (default is 25) -try: - print("\nAttempting to get some patent applications (default search)...") - # Calling with no specific query, relying on API defaults or client defaults (e.g., limit) - response = client.search_applications(limit=5) # Example: get 5 results - print( - f"Found {response.count} total patent applications matching default/broad criteria." - ) - print( - f"Displaying first {len(response.patent_file_wrapper_data_bag)} applications from response:" - ) +print("-" * 40) +print("Example 4: Get application by number") +print("-" * 40) - for patent_wrapper in response.patent_file_wrapper_data_bag: - app_meta = patent_wrapper.application_meta_data - if app_meta: - print(f"\n Application: {patent_wrapper.application_number_text}") - print(f" Title: {app_meta.invention_title}") - print(f" Status: {app_meta.application_status_description_text}") - print(f" Filing Date: {app_meta.filing_date}") - - if app_meta.patent_number: - print(f" Patent Number: {app_meta.patent_number}") - print(f" Grant Date: {app_meta.grant_date}") - - if app_meta.inventor_bag: - print(" Inventors:") - for inventor in app_meta.inventor_bag: - name_parts = [ - part - for part in [inventor.first_name, inventor.last_name] - if part - ] - print(f" - {' '.join(name_parts).strip()}") - if inventor.correspondence_address_bag: - address = inventor.correspondence_address_bag[0] - if address.city_name and address.geographic_region_code: - print( - f" ({address.city_name}, {address.geographic_region_code})" - ) - - if app_meta.applicant_bag: - print(" Applicants:") - for applicant in app_meta.applicant_bag: - print(f" - {applicant.applicant_name_text}") - print("-" * 20) - - # Example of using the to_csv method from PatentDataResponse - if response.count > 0: - print("\nGenerating CSV for the current response (first few rows shown):") - csv_data = response.to_csv() - # You could save this csv_data to a file: - csv_path = os.path.join(DEST_PATH, "patent_search_results.csv") - os.makedirs(DEST_PATH, exist_ok=True) - with open(csv_path, "w", newline="", encoding="utf-8") as f: - f.write(csv_data) - print(f"\nFull CSV data saved to {csv_path}.") - - -except Exception as e: - print(f"Error getting patent applications: {e}") - -# Search for patents by inventor name using convenience _q parameter -try: - print("\nSearching for patents with 'Smith' as inventor...") - # Changed from search_patents to search_applications with inventor_name_q - inventor_search_response = client.search_applications( - inventor_name_q="Smith", limit=2 - ) +app_no_to_fetch = "18045436" +print(f"Retrieving patent application: {app_no_to_fetch}") +patent_wrapper_detail = client.get_application_by_number( + application_number=app_no_to_fetch +) +if patent_wrapper_detail: print( - f"Found {inventor_search_response.count} patents with 'Smith' as inventor (showing up to 2)." + f"Successfully retrieved: {patent_wrapper_detail.application_number_text}" ) - for patent_wrapper in inventor_search_response.patent_file_wrapper_data_bag: - if patent_wrapper.application_meta_data: - print( - f" - App No: {patent_wrapper.application_number_text}, Title: {patent_wrapper.application_meta_data.invention_title}" - ) -except Exception as e: - print(f"Error searching by inventor: {e}") - + if patent_wrapper_detail.application_meta_data: + print( + f"Title: {patent_wrapper_detail.application_meta_data.invention_title}" + ) -# Search for patents filed in a date range using convenience _q parameters -try: - print("\nSearching for patents filed in 2020...") - date_search_response = client.search_applications( - filing_date_from_q="2020-01-01", filing_date_to_q="2020-12-31", limit=2 - ) - print( - f"Found {date_search_response.count} patents filed in 2020 (showing up to 2)." - ) - for patent_wrapper in date_search_response.patent_file_wrapper_data_bag: - if patent_wrapper.application_meta_data: - print( - f" - App No: {patent_wrapper.application_number_text}, Filing Date: {patent_wrapper.application_meta_data.filing_date}" - ) -except Exception as e: - print(f"Error searching by date range: {e}") - -# Get a specific patent by application number -app_no_to_fetch = "18045436" # Known application number, ensure it's valid -try: - print(f"\nAttempting to retrieve patent application: {app_no_to_fetch}") - patent_wrapper_detail = client.get_application_by_number( + print("\nRetrieving document information...") + documents_bag = client.get_application_documents( application_number=app_no_to_fetch ) - if patent_wrapper_detail: + print(f"Found {len(documents_bag)} documents for application {app_no_to_fetch}") + + if documents_bag.documents: + document_to_download = documents_bag.documents[0] + print("\nFirst document details:") + print(f" Document ID: {document_to_download.document_identifier}") print( - f"Successfully retrieved: {patent_wrapper_detail.application_number_text}" + f" Document Type: {document_to_download.document_code} - {document_to_download.document_code_description_text}" ) - if patent_wrapper_detail.application_meta_data: + print(f" Date: {document_to_download.official_date}") + print(f" Direction: {document_to_download.direction_category}") + + if ( + document_to_download.document_formats + and document_to_download.document_identifier + ): + print("\nDownloading first PDF document...") + print(json.dumps(document_to_download.to_dict(), indent=2)) + downloaded_path = client.download_document( + document=document_to_download, + format="PDF", + destination=DEST_PATH, + overwrite=True, + ) + print(f"Downloaded document to: {downloaded_path}") + else: print( - f"Title: {patent_wrapper_detail.application_meta_data.invention_title}" + "No downloadable formats available for the first document or document identifier missing." ) + else: + print("No documents listed for this application.") + + # Download publication XML (grant or pgpub) + print("\nChecking for publication files (grant/pgpub XML)...") + if patent_wrapper_detail.grant_document_meta_data: + grant_metadata = patent_wrapper_detail.grant_document_meta_data + print(f"Grant document available: {grant_metadata.xml_file_name}") + print(f" Product: {grant_metadata.product_identifier}") + print(f" Created: {grant_metadata.file_create_date_time}") + + print("\nDownloading grant XML...") + grant_path = client.download_publication( + printed_metadata=grant_metadata, + destination=DEST_PATH, + overwrite=True, + ) + print(f"Downloaded grant XML to: {grant_path}") - print("\nRetrieving document information...") - documents_bag = client.get_application_documents( - application_number=app_no_to_fetch + if patent_wrapper_detail.pgpub_document_meta_data: + pgpub_metadata = patent_wrapper_detail.pgpub_document_meta_data + print(f"\nPre-grant publication available: {pgpub_metadata.xml_file_name}") + + pgpub_path = client.download_publication( + printed_metadata=pgpub_metadata, + file_name="my_pgpub.xml", + destination=DEST_PATH, + overwrite=True, ) - print(f"Found {len(documents_bag)} documents for application {app_no_to_fetch}") + print(f"Downloaded pgpub XML to: {pgpub_path}") - if documents_bag.documents: - document_to_download = documents_bag.documents[0] # Example: first document - print("\nFirst document details:") - print(f" Document ID: {document_to_download.document_identifier}") - print( - f" Document Type: {document_to_download.document_code} - {document_to_download.document_code_description_text}" - ) - print(f" Date: {document_to_download.official_date}") - print(f" Direction: {document_to_download.direction_category}") - - if ( - document_to_download.document_formats - and document_to_download.document_identifier - ): - print("\nAttempting to download first PDF document...") - print(json.dumps(document_to_download.to_dict(), indent=2)) - downloaded_path = client.download_document( - document=document_to_download, - format="PDF", - destination=DEST_PATH, - overwrite=True, - ) - print(f"Downloaded document to: {downloaded_path}") - else: + if patent_wrapper_detail.assignment_bag: + print("\nAssignments:") + for assignment in patent_wrapper_detail.assignment_bag: + for assignee in assignment.assignee_bag: print( - "No downloadable formats available for the first document or document identifier missing." + f" - {assignee.assignee_name_text} (Recorded: {assignment.assignment_recorded_date})" ) - else: - print("No documents listed for this application.") - - # Example: Download publication XML (grant or pgpub) - print("\nChecking for publication files (grant/pgpub XML)...") - if patent_wrapper_detail.grant_document_meta_data: - grant_metadata = patent_wrapper_detail.grant_document_meta_data - print(f"Grant document available: {grant_metadata.xml_file_name}") - print(f" Product: {grant_metadata.product_identifier}") - print(f" Created: {grant_metadata.file_create_date_time}") - - # Download grant XML to downloads folder with auto-generated filename - print("\nDownloading grant XML...") - grant_path = client.download_publication( - printed_metadata=grant_metadata, - destination=DEST_PATH, - overwrite=True, - ) - print(f"Downloaded grant XML to: {grant_path}") - - if patent_wrapper_detail.pgpub_document_meta_data: - pgpub_metadata = patent_wrapper_detail.pgpub_document_meta_data - print(f"\nPre-grant publication available: {pgpub_metadata.xml_file_name}") - - # Download with custom filename - pgpub_path = client.download_publication( - printed_metadata=pgpub_metadata, - file_name="my_pgpub.xml", - destination=DEST_PATH, - overwrite=True, - ) - print(f"Downloaded pgpub XML to: {pgpub_path}") - - if patent_wrapper_detail.assignment_bag: - print("\nAssignments:") - for assignment in patent_wrapper_detail.assignment_bag: - for assignee in assignment.assignee_bag: - print( - f" - {assignee.assignee_name_text} (Recorded: {assignment.assignment_recorded_date})" - ) - print(f" Conveyance: {assignment.conveyance_text}") - else: - print(f"Could not retrieve details for application {app_no_to_fetch}") + print(f" Conveyance: {assignment.conveyance_text}") +else: + print(f"Could not retrieve details for application {app_no_to_fetch}") -except Exception as e: - print(f"Error retrieving or processing patent application {app_no_to_fetch}: {e}") +print("-" * 40) +print("Example 5: Search by patent number") +print("-" * 40) -# Search for a specific patent by patent number (using search_applications) target_patent_number = "10000000" -try: - print(f"\nSearching for patent US {target_patent_number} B2...") - patent_search_response = client.search_applications( - patent_number_q=target_patent_number, limit=1 - ) - +print(f"Searching for patent US {target_patent_number} B2...") +patent_search_response = client.search_applications( + patent_number_q=target_patent_number, limit=1 +) + +if ( + patent_search_response.count > 0 + and patent_search_response.patent_file_wrapper_data_bag +): + found_patent_wrapper = patent_search_response.patent_file_wrapper_data_bag[0] if ( - patent_search_response.count > 0 - and patent_search_response.patent_file_wrapper_data_bag + found_patent_wrapper.application_meta_data + and found_patent_wrapper.application_meta_data.patent_number ): - found_patent_wrapper = patent_search_response.patent_file_wrapper_data_bag[0] - if ( - found_patent_wrapper.application_meta_data - and found_patent_wrapper.application_meta_data.patent_number - ): - print( - f"Retrieved patent: US {found_patent_wrapper.application_meta_data.patent_number}" - ) - else: - print( - f"Retrieved patent application: {found_patent_wrapper.application_number_text}" - ) - - if found_patent_wrapper.patent_term_adjustment_data: - pta = found_patent_wrapper.patent_term_adjustment_data - print(f"Patent Term Adjustment: {pta.adjustment_total_quantity} days") - if pta.a_delay_quantity is not None: - print(f" A Delay: {pta.a_delay_quantity} days") - if pta.b_delay_quantity is not None: - print(f" B Delay: {pta.b_delay_quantity} days") - if pta.c_delay_quantity is not None: - print(f" C Delay: {pta.c_delay_quantity} days") - if pta.applicant_day_delay_quantity is not None: - print(f" Applicant Delay: {pta.applicant_day_delay_quantity} days") - - # Example of getting continuity data (assuming it's part of the wrapper) - continuity_data = ApplicationContinuityData.from_wrapper( - wrapper=found_patent_wrapper + print( + f"Retrieved patent: US {found_patent_wrapper.application_meta_data.patent_number}" ) - if continuity_data.parent_continuity_bag: - print("\nParent Applications:") - for p_continuity in continuity_data.parent_continuity_bag: - print(f" - App No: {p_continuity.parent_application_number_text}") - print( - f" Type: {p_continuity.claim_parentage_type_code_description_text}" - ) - print(f" Filing Date: {p_continuity.parent_application_filing_date}") - - if continuity_data.child_continuity_bag: - print("\nChild Applications:") - for c_continuity in continuity_data.child_continuity_bag: - print(f" - App No: {c_continuity.child_application_number_text}") - print( - f" Type: {c_continuity.claim_parentage_type_code_description_text}" - ) - print(f" Filing Date: {c_continuity.child_application_filing_date}") else: - print(f"No patents found with patent number: {target_patent_number}") - -except Exception as e: - print(f"Error retrieving patent by number {target_patent_number}: {e}") - -# Example of POST search for applications -try: - print("\nAttempting POST search for applications with 'AI' in title...") - post_search_body = { - "q": "applicationMetaData.inventionTitle:AI", - "pagination": {"offset": 0, "limit": 2}, - } - post_response = client.search_applications(post_body=post_search_body) - print( - f"Found {post_response.count} applications via POST search (showing up to 2)." + print( + f"Retrieved patent application: {found_patent_wrapper.application_number_text}" + ) + + if found_patent_wrapper.patent_term_adjustment_data: + pta = found_patent_wrapper.patent_term_adjustment_data + print(f"Patent Term Adjustment: {pta.adjustment_total_quantity} days") + if pta.a_delay_quantity is not None: + print(f" A Delay: {pta.a_delay_quantity} days") + if pta.b_delay_quantity is not None: + print(f" B Delay: {pta.b_delay_quantity} days") + if pta.c_delay_quantity is not None: + print(f" C Delay: {pta.c_delay_quantity} days") + if pta.applicant_day_delay_quantity is not None: + print(f" Applicant Delay: {pta.applicant_day_delay_quantity} days") + + continuity_data = ApplicationContinuityData.from_wrapper( + wrapper=found_patent_wrapper ) - for patent_wrapper in post_response.patent_file_wrapper_data_bag: - if patent_wrapper.application_meta_data: + if continuity_data.parent_continuity_bag: + print("\nParent Applications:") + for p_continuity in continuity_data.parent_continuity_bag: + print(f" - App No: {p_continuity.parent_application_number_text}") print( - f" - App No: {patent_wrapper.application_number_text}, Title: {patent_wrapper.application_meta_data.invention_title}" + f" Type: {p_continuity.claim_parentage_type_code_description_text}" ) -except Exception as e: - print(f"Error with POST search: {e}") + print(f" Filing Date: {p_continuity.parent_application_filing_date}") - -# Search by CPC classification code -# CPC codes containing spaces or slashes are automatically quoted for the Lucene query. -# cpc_classification_bag on ApplicationMetaData is a list[str] of all CPC codes assigned -# to the application, so each result may have multiple codes. -try: - print("\nSearching by CPC classification code 'H10D 64/667'...") - cpc_response = client.search_applications(classification_q="H10D 64/667", limit=3) - print(f"Found {cpc_response.count} applications with CPC code H10D 64/667.") - for patent_wrapper in cpc_response.patent_file_wrapper_data_bag: - app_meta = patent_wrapper.application_meta_data - if app_meta: + if continuity_data.child_continuity_bag: + print("\nChild Applications:") + for c_continuity in continuity_data.child_continuity_bag: + print(f" - App No: {c_continuity.child_application_number_text}") print( - f" - App No: {patent_wrapper.application_number_text}, Title: {app_meta.invention_title}" + f" Type: {c_continuity.claim_parentage_type_code_description_text}" ) - if app_meta.cpc_classification_bag: - print(f" CPC codes: {', '.join(app_meta.cpc_classification_bag)}") -except Exception as e: - print(f"Error searching by CPC classification: {e}") + print(f" Filing Date: {c_continuity.child_application_filing_date}") +else: + print(f"No patents found with patent number: {target_patent_number}") + +print("-" * 40) +print("Example 6: POST search") +print("-" * 40) + +print("POST search for applications with 'AI' in title...") +post_search_body = { + "q": "applicationMetaData.inventionTitle:AI", + "pagination": {"offset": 0, "limit": 2}, +} +post_response = client.search_applications(post_body=post_search_body) +print( + f"Found {post_response.count} applications via POST search (showing up to 2)." +) +for patent_wrapper in post_response.patent_file_wrapper_data_bag: + if patent_wrapper.application_meta_data: + print( + f" - App No: {patent_wrapper.application_number_text}, Title: {patent_wrapper.application_meta_data.invention_title}" + ) +print("-" * 40) +print("Example 7: Search by CPC classification") +print("-" * 40) -# Example of getting status codes -try: - print("\nGetting first 5 status codes...") - status_code_response = client.get_status_codes(params={"limit": 5}) - print( - f"Retrieved {len(status_code_response.status_code_bag)} status codes (out of {status_code_response.count} total)." - ) - for code_obj in status_code_response.status_code_bag: - print(f" - Code: {code_obj.code}, Description: {code_obj.description}") -except Exception as e: - print(f"Error getting status codes: {e}") +# CPC codes containing spaces or slashes are automatically quoted for the Lucene query. +print("Searching by CPC classification code 'H10D 64/667'...") +cpc_response = client.search_applications(classification_q="H10D 64/667", limit=3) +print(f"Found {cpc_response.count} applications with CPC code H10D 64/667.") +for patent_wrapper in cpc_response.patent_file_wrapper_data_bag: + app_meta = patent_wrapper.application_meta_data + if app_meta: + print( + f" - App No: {patent_wrapper.application_number_text}, Title: {app_meta.invention_title}" + ) + if app_meta.cpc_classification_bag: + print(f" CPC codes: {', '.join(app_meta.cpc_classification_bag)}") + +print("-" * 40) +print("Example 8: Get status codes") +print("-" * 40) + +status_code_response = client.get_status_codes(params={"limit": 5}) +print( + f"Retrieved {len(status_code_response.status_code_bag)} status codes (out of {status_code_response.count} total)." +) +for code_obj in status_code_response.status_code_bag: + print(f" - Code: {code_obj.code}, Description: {code_obj.description}") diff --git a/examples/petition_decisions_example.py b/examples/petition_decisions_example.py index 7a424ce..cf8499a 100644 --- a/examples/petition_decisions_example.py +++ b/examples/petition_decisions_example.py @@ -1,304 +1,237 @@ -"""Example usage of the pyUSPTO module for Final Petition Decisions. +"""Example usage of pyUSPTO for Final Petition Decisions. -This example demonstrates how to use the FinalPetitionDecisionsClient to interact with the -USPTO Final Petition Decisions API. It shows how to search for petition decisions, retrieve -specific decisions by ID, download decision data, and access detailed information about -petitions and their associated documents. +Demonstrates the FinalPetitionDecisionsClient for searching decisions, +downloading decision data, and paginating results. """ import json import os -from pyUSPTO.clients import FinalPetitionDecisionsClient -from pyUSPTO.config import USPTOConfig -from pyUSPTO.models.petition_decisions import PetitionDecisionDownloadResponse +from pyUSPTO import FinalPetitionDecisionsClient, PetitionDecisionDownloadResponse, USPTOConfig -# --- Initialization --- -# Initialize the client with direct API key -print("Initialize with direct API key") +DEST_PATH = "./notes/download-example" + +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( - "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable." + "API key is not set. Set the USPTO_API_KEY environment variable." ) config = USPTOConfig(api_key=api_key) client = FinalPetitionDecisionsClient(config=config) -DEST_PATH = "./notes/download-example" - -print("\nBeginning API requests with configured client:") - -# Basic search for petition decisions -try: - print("\n" + "=" * 60) - print("Example 1: Basic Search for Petition Decisions") - print("=" * 60) +print("-" * 40) +print("Example 1: Basic search") +print("-" * 40) - response = client.search_decisions(limit=5) - print(f"Found {response.count} total petition decisions.") - print(f"Displaying first {len(response.petition_decision_data_bag)} decisions:") +response = client.search_decisions(limit=5) +print(f"Found {response.count} total petition decisions.") +print(f"Displaying first {len(response.petition_decision_data_bag)} decisions:") - for decision in response.petition_decision_data_bag: - print(f"\n Decision ID: {decision.petition_decision_record_identifier}") - print(f" Application Number: {decision.application_number_text}") - print(f" Decision Type: {decision.decision_type_code}") - print(f" Decision Date: {decision.decision_date}") - print(f" Technology Center: {decision.technology_center}") - - if decision.first_applicant_name: - print(f" Applicant: {decision.first_applicant_name}") - - if decision.patent_number: - print(f" Patent Number: {decision.patent_number}") +for decision in response.petition_decision_data_bag: + print(f"\n Decision ID: {decision.petition_decision_record_identifier}") + print(f" Application Number: {decision.application_number_text}") + print(f" Decision Type: {decision.decision_type_code}") + print(f" Decision Date: {decision.decision_date}") + print(f" Technology Center: {decision.technology_center}") - if decision.inventor_bag: - print(f" Inventors ({len(decision.inventor_bag)}):") - for inventor in decision.inventor_bag[:3]: # Show first 3 - print(f" - {inventor}") + if decision.first_applicant_name: + print(f" Applicant: {decision.first_applicant_name}") - if decision.document_bag: - print(f" Documents: {len(decision.document_bag)}") + if decision.patent_number: + print(f" Patent Number: {decision.patent_number}") - print("-" * 40) + if decision.inventor_bag: + print(f" Inventors ({len(decision.inventor_bag)}):") + for inventor in decision.inventor_bag[:3]: + print(f" - {inventor}") -except Exception as e: - print(f"Error in basic search: {e}") + if decision.document_bag: + print(f" Documents: {len(decision.document_bag)}") -# Search with query parameter -try: - print("\n" + "=" * 60) - print("Example 2: Search with Custom Query") - print("=" * 60) +print("-" * 40) +print("Example 2: Search with custom query") +print("-" * 40) - # Search for decisions mentioning specific terms - response = client.search_decisions(query="decisionTypeCode:C", limit=3) - print(f"Found {response.count} decisions with C type.") - print(f"Showing {len(response.petition_decision_data_bag)} results:") +response = client.search_decisions(query="decisionTypeCode:C", limit=3) +print(f"Found {response.count} decisions with C type.") +print(f"Showing {len(response.petition_decision_data_bag)} results:") - for decision in response.petition_decision_data_bag: - print( - f" - {decision.petition_decision_record_identifier}: {decision.decision_type_code}" - ) - -except Exception as e: - print(f"Error searching with query: {e}") - -# Search using convenience parameters -try: - print("\n" + "=" * 60) - print("Example 3: Search Using Convenience Parameters") - print("=" * 60) - - # Search by application number (if you have a specific one) - print("\nSearching by date range...") - response = client.search_decisions( - decision_date_from_q="2023-01-01", decision_date_to_q="2023-12-31", limit=5 - ) - print(f"Found {response.count} decisions from 2023.") - - # Search by technology center - print("\nSearching by technology center...") - response = client.search_decisions(technology_center_q="2600", limit=3) - print(f"Found {response.count} decisions from Technology Center 2600.") - -except Exception as e: - print(f"Error with convenience parameters: {e}") - -# Get a specific decision by ID -try: - print("\n" + "=" * 60) - print("Example 4: Get Specific Decision by ID") - print("=" * 60) - - # First, get a decision ID from search results - response = client.search_decisions(limit=1) - if response.count > 0: - decision_id = response.petition_decision_data_bag[ - 0 - ].petition_decision_record_identifier - if decision_id: - print(f"Retrieving decision: {decision_id}") - decision = client.get_decision_by_id(decision_id) - if decision: - print("\nDecision Details:") - print(f" ID: {decision.petition_decision_record_identifier}") - print(f" Application: {decision.application_number_text}") - print(f" Patent: {decision.patent_number}") - print(f" Decision Type: {decision.decision_type_code}") - print(f" Decision Date: {decision.decision_date}") - print(f" Technology Center: {decision.technology_center}") - print(f" Group Art Unit: {decision.group_art_unit_number}") - - if decision.rule_bag: - print(f"\n Rules Cited ({len(decision.rule_bag)}):") - for rule in decision.rule_bag[:5]: # Show first 5 - print(f" - {rule}") - - if decision.statute_bag: - print(f"\n Statutes Cited ({len(decision.statute_bag)}):") - for statute in decision.statute_bag[:5]: # Show first 5 - print(f" - {statute}") - - if decision.document_bag: - print(f"\n Associated Documents ({len(decision.document_bag)}):") - for doc in decision.document_bag[:3]: # Show first 3 - print(f" - Doc ID: {doc.document_identifier}") - print(f" Date: {doc.official_date}") - print(f" Doc. Code: {doc.document_code_description_text}") - print(f" Direction: {doc.direction_category}") - if doc.download_option_bag: - print( - f" Download Options: {len(doc.download_option_bag)}" - ) - for mime in doc.download_option_bag: - print(f" >Mime Type: {mime.mime_type_identifier}") - print(f" >>Pages: {mime.page_total_quantity}") - -except Exception as e: - print(f"Error retrieving decision by ID: {e}") - -# Download petition decisions data -try: - print("\n" + "=" * 60) - print("Example 5: Download Petition Decisions Data") - print("=" * 60) - - # Download as JSON (returns response object) - print("\nDownloading decisions as JSON...") - response = client.download_decisions( - format="json", decision_date_from_q="2023-01-01", limit=5, overwrite=True - ) - if isinstance(response, PetitionDecisionDownloadResponse): - print( - f"Downloaded JSON with {len(response.petition_decision_data)} decision records" - ) - print(json.dumps(response.to_dict(), indent=2)) - - # Download as CSV (automatically saves to file) - print("\nDownloading decisions as CSV...") - csv_path = client.download_decisions( - format="csv", - decision_date_from_q="2023-01-01", - limit=10, - destination=DEST_PATH, - overwrite=True, +for decision in response.petition_decision_data_bag: + print( + f" - {decision.petition_decision_record_identifier}: {decision.decision_type_code}" ) - print(f"Downloaded CSV to: {csv_path}") - -except Exception as e: - print(f"Error downloading decisions: {e}") - -# Pagination example -try: - print("\n" + "=" * 60) - print("Example 6: Paginating Through Results") - print("=" * 60) - - page_size = 10 - max_pages = 3 # Limit to 3 pages for example +print("-" * 40) +print("Example 3: Search with convenience parameters") +print("-" * 40) + +# Search by date range +print("\nSearching by date range...") +response = client.search_decisions( + decision_date_from_q="2023-01-01", decision_date_to_q="2023-12-31", limit=5 +) +print(f"Found {response.count} decisions from 2023.") + +# Search by technology center +print("\nSearching by technology center...") +response = client.search_decisions(technology_center_q="2600", limit=3) +print(f"Found {response.count} decisions from Technology Center 2600.") + +print("-" * 40) +print("Example 4: Get specific decision by ID") +print("-" * 40) + +# Get a decision ID from search results +response = client.search_decisions(limit=1) +if response.count > 0: + decision_id = response.petition_decision_data_bag[ + 0 + ].petition_decision_record_identifier + if decision_id: + print(f"Retrieving decision: {decision_id}") + decision = client.get_decision_by_id(decision_id) + if decision: + print("\nDecision Details:") + print(f" ID: {decision.petition_decision_record_identifier}") + print(f" Application: {decision.application_number_text}") + print(f" Patent: {decision.patent_number}") + print(f" Decision Type: {decision.decision_type_code}") + print(f" Decision Date: {decision.decision_date}") + print(f" Technology Center: {decision.technology_center}") + print(f" Group Art Unit: {decision.group_art_unit_number}") + + if decision.rule_bag: + print(f"\n Rules Cited ({len(decision.rule_bag)}):") + for rule in decision.rule_bag[:5]: + print(f" - {rule}") + + if decision.statute_bag: + print(f"\n Statutes Cited ({len(decision.statute_bag)}):") + for statute in decision.statute_bag[:5]: + print(f" - {statute}") + + if decision.document_bag: + print(f"\n Associated Documents ({len(decision.document_bag)}):") + for doc in decision.document_bag[:3]: + print(f" - Doc ID: {doc.document_identifier}") + print(f" Date: {doc.official_date}") + print(f" Doc. Code: {doc.document_code_description_text}") + print(f" Direction: {doc.direction_category}") + if doc.download_option_bag: + print( + f" Download Options: {len(doc.download_option_bag)}" + ) + for mime in doc.download_option_bag: + print(f" Mime Type: {mime.mime_type_identifier}") + print(f" Pages: {mime.page_total_quantity}") + +print("-" * 40) +print("Example 5: Download petition decisions data") +print("-" * 40) + +# Download as JSON (returns response object) +print("\nDownloading decisions as JSON...") +response = client.download_decisions( + format="json", decision_date_from_q="2023-01-01", limit=5, overwrite=True +) +if isinstance(response, PetitionDecisionDownloadResponse): print( - f"Paginating through results ({page_size} per page, max {max_pages} pages)..." + f"Downloaded JSON with {len(response.petition_decision_data)} decision records" ) - - total_decisions = 0 - - for decision in client.paginate_decisions( - limit=page_size, query="decisionDate:[2023-01-01 TO 2023-12-31]" - ): - total_decisions += 1 - - if total_decisions % page_size == 0: - print(f" Retrieved {total_decisions} decisions so far...") - - if total_decisions >= (page_size * max_pages): - print(f" (Stopping after {max_pages} pages for example)") - break - - print(f"\nTotal decisions retrieved: {total_decisions}") - -except Exception as e: - print(f"Error during pagination: {e}") - -# Download a petition document -try: - print("\n" + "=" * 60) - print("Example 7: Download Petition Decision Document") - print("=" * 60) - - # Find a decision with downloadable documents - response = client.search_decisions(limit=20) - - document_found = False + print(json.dumps(response.to_dict(), indent=2)) + +# Download as CSV (automatically saves to file) +print("\nDownloading decisions as CSV...") +csv_path = client.download_decisions( + format="csv", + decision_date_from_q="2023-01-01", + limit=10, + destination=DEST_PATH, + overwrite=True, +) +print(f"Downloaded CSV to: {csv_path}") + +print("-" * 40) +print("Example 6: Paginate through results") +print("-" * 40) + +max_items = 30 +count = 0 +for decision in client.paginate_decisions( + limit=10, query="decisionDate:[2023-01-01 TO 2023-12-31]" +): + count += 1 + if count >= max_items: + print(f" ... (stopping at {max_items} items)") + break + +print(f"Retrieved {count} decisions via pagination") + +print("-" * 40) +print("Example 7: Download petition document") +print("-" * 40) + +# Find a decision with downloadable documents +response = client.search_decisions(limit=20) + +document_found = False +for decision in response.petition_decision_data_bag: + d = client.get_decision_by_id( + decision.petition_decision_record_identifier, include_documents=True + ) + print( + f"Getting docs for patent: {d.invention_title} with id: {d.petition_decision_record_identifier}" + ) # type: ignore + if d and d.document_bag: + for doc in d.document_bag: + if doc.download_option_bag and len(doc.download_option_bag) > 0: + download_option = doc.download_option_bag[0] + + print("Found downloadable document:") + print(f" Document ID: {doc.document_identifier}") + print(f" MIME Type: {download_option.mime_type_identifier}") + print(f" Pages: {download_option.page_total_quantity}") + print(f" URL: {download_option.download_url}") + + print("\nDownloading document...") + file_path = client.download_petition_document( + download_option=download_option, + destination=DEST_PATH, + ) + print(f"Downloaded to: {file_path}") + + document_found = True + break + + if document_found: + break + +if not document_found: + print("No downloadable documents found in the first 20 results") + +print("-" * 40) +print("Example 8: Advanced search with multiple criteria") +print("-" * 40) + +response = client.search_decisions( + application_number_q="16*", + decision_date_from_q="2020-01-01", + technology_center_q="2600", + limit=10, +) + +print("Search criteria:") +print(" - Application numbers starting with '16'") +print(" - Decision date from 2020-01-01") +print(" - Technology Center 2600") +print(f"\nFound {response.count} matching decisions") + +if response.count > 0: + print(f"Showing first {len(response.petition_decision_data_bag)} results:") for decision in response.petition_decision_data_bag: - d = client.get_decision_by_id( - decision.petition_decision_record_identifier, include_documents=True - ) print( - f"Getting docs for patent: {d.invention_title} with id: {d.petition_decision_record_identifier}" - ) # type: ignore - if d and d.document_bag: - for doc in d.document_bag: - if doc.download_option_bag and len(doc.download_option_bag) > 0: - download_option = doc.download_option_bag[0] - - print("Found downloadable document:") - print(f" Document ID: {doc.document_identifier}") - print(f" MIME Type: {download_option.mime_type_identifier}") - print(f" Pages: {download_option.page_total_quantity}") - print(f" URL: {download_option.download_url}") - - print("\nDownloading document...") - file_path = client.download_petition_document( - download_option=download_option, - destination=DEST_PATH, - ) - print(f"Downloaded to: {file_path}") - - document_found = True - break - - if document_found: - break - - if not document_found: - print("No downloadable documents found in the first 20 results") - -except Exception as e: - print(f"Error downloading document: {e}") - -# Advanced search example -try: - print("\n" + "=" * 60) - print("Example 8: Advanced Search with Multiple Criteria") - print("=" * 60) - - # Search with multiple parameters - response = client.search_decisions( - application_number_q="16*", # Applications starting with 16 - decision_date_from_q="2020-01-01", - technology_center_q="2600", - limit=10, - ) - - print("Search criteria:") - print(" - Application numbers starting with '16'") - print(" - Decision date from 2020-01-01") - print(" - Technology Center 2600") - print(f"\nFound {response.count} matching decisions") - - if response.count > 0: - print(f"Showing first {len(response.petition_decision_data_bag)} results:") - for decision in response.petition_decision_data_bag: - print( - f" - App: {decision.application_number_text}, " - f"TC: {decision.technology_center}, " - f"Date: {decision.decision_date}" - ) - -except Exception as e: - print(f"Error in advanced search: {e}") - -print("\n" + "=" * 60) -print("Examples completed!") -print("=" * 60) + f" - App: {decision.application_number_text}, " + f"TC: {decision.technology_center}, " + f"Date: {decision.decision_date}" + ) diff --git a/examples/ptab_appeals_example.py b/examples/ptab_appeals_example.py index 9c2a31f..4298ada 100644 --- a/examples/ptab_appeals_example.py +++ b/examples/ptab_appeals_example.py @@ -1,261 +1,179 @@ -"""Example usage of the pyUSPTO module for PTAB Appeals API. +"""Example usage of pyUSPTO for PTAB ex parte appeals. -This example demonstrates how to use the PTABAppealsClient to interact with the USPTO PTAB -(Patent Trial and Appeal Board) Appeals API. It shows how to search for ex parte appeal -decisions using various search criteria. - -PTAB Appeals include ex parte appeals from patent application examinations to the Board. +Demonstrates the PTABAppealsClient for searching appeal decisions +by technology center, decision type, and application number. """ import os -from pyUSPTO import PTABAppealsClient -from pyUSPTO.config import USPTOConfig +from pyUSPTO import PTABAppealsClient, USPTOConfig -# --- Initialization --- -# Initialize the client with direct API key -print("Initialize with direct API key") +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( - "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable." + "API key is not set. Set the USPTO_API_KEY environment variable." ) config = USPTOConfig(api_key=api_key) client = PTABAppealsClient(config=config) - -print("\nBeginning PTAB Appeals API requests with configured client:") -# ============================================================================= -# 1. Search Appeal Decisions by Technology Center -# ============================================================================= - -print("\n" + "=" * 80) -print("1. Searching for appeal decisions by technology center") -print("=" * 80) - -try: - # Search for decisions from Technology Center 3600 (Business Methods/Software) - response = client.search_decisions( - technology_center_number_q="3600", - decision_date_from_q="2023-01-01", - decision_date_to_q="2023-12-31", - limit=5, - ) - - print(f"\nFound {response.count} appeal decisions from TC 3600 in 2023") - print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") - - for decision in response.patent_appeal_data_bag: - print(f"\n Appeal Number: {decision.appeal_number}") - - if decision.appeal_meta_data: - meta = decision.appeal_meta_data - print(f" Application Type: {meta.application_type_category}") - print(f" Filing Date: {meta.appeal_filing_date}") - - if decision.appellant_data: - appellant = decision.appellant_data - print(f" Application Number: {appellant.application_number_text}") - print(f" Technology Center: {appellant.technology_center_number}") - - if appellant.inventor_name: - print(f" Inventor: {appellant.inventor_name}") - - if decision.decision_data: - dec = decision.decision_data - print(f" Decision Type: {dec.decision_type_category}") - print(f" Decision Date: {dec.decision_issue_date}") - -except Exception as e: - print(f"Error searching appeal decisions: {e}") - -# ============================================================================= -# 2. Search by Decision Type -# ============================================================================= - -print("\n" + "=" * 80) -print("2. Searching for 'Affirmed' decisions") -print("=" * 80) - -try: - # Search for decisions where the examiner was affirmed - response = client.search_decisions( - decision_type_category_q="Decision", - decision_date_from_q="2024-01-01", - limit=5, +print("-" * 40) +print("Example 1: Search by technology center") +print("-" * 40) + +# Search for decisions from Technology Center 3600 (Business Methods/Software) +response = client.search_decisions( + technology_center_number_q="3600", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + limit=5, +) + +print(f"\nFound {response.count} appeal decisions from TC 3600 in 2023") +print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") + +for decision in response.patent_appeal_data_bag: + print(f"\n Appeal Number: {decision.appeal_number}") + + if decision.appeal_meta_data: + meta = decision.appeal_meta_data + print(f" Application Type: {meta.application_type_category}") + print(f" Filing Date: {meta.appeal_filing_date}") + + if decision.appellant_data: + appellant = decision.appellant_data + print(f" Application Number: {appellant.application_number_text}") + print(f" Technology Center: {appellant.technology_center_number}") + + if appellant.inventor_name: + print(f" Inventor: {appellant.inventor_name}") + + if decision.decision_data: + dec = decision.decision_data + print(f" Decision Type: {dec.decision_type_category}") + print(f" Decision Date: {dec.decision_issue_date}") + +print("-" * 40) +print("Example 2: Search by decision type") +print("-" * 40) + +response = client.search_decisions( + decision_type_category_q="Decision", + decision_date_from_q="2024-01-01", + limit=5, +) + +print(f"\nFound {response.count} 'Decision's since 2024") +print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") + +for decision in response.patent_appeal_data_bag: + print(f"\n Appeal Number: {decision.appeal_number}") + + if decision.appellant_data: + print(f" Application: {decision.appellant_data.application_number_text}") + print(f" Inventor: {decision.appellant_data.inventor_name or 'N/A'}") + + if decision.decision_data: + print(f" Decision: {decision.decision_data.decision_type_category}") + print(f" Outcome: {decision.decision_data.appeal_outcome_category}") + print(f" Date: {decision.decision_data.decision_issue_date}") + +print("-" * 40) +print("Example 3: Search by application number") +print("-" * 40) + +# Search for decisions related to applications starting with "15" +response = client.search_decisions( + application_number_text_q="15*", + decision_date_from_q="2023-01-01", + limit=3, +) + +print(f"\nFound {response.count} decisions for applications starting with '15/'") +print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") + +for decision in response.patent_appeal_data_bag: + print(f"\n Appeal Number: {decision.appeal_number}") + + if decision.appellant_data: + print(f" Application: {decision.appellant_data.application_number_text}") + print(f" TC Number: {decision.appellant_data.technology_center_number}") + + if decision.document_data: + doc = decision.document_data + print(f" Document Name: {doc.document_name}") + if doc.file_download_uri: + print(f" Download URL: {doc.file_download_uri}") + +print("-" * 40) +print("Example 4: Paginate through decisions") +print("-" * 40) + +max_items = 10 +count = 0 +for decision in client.paginate_decisions( + decision_date_from_q="2024-01-01", + limit=5, +): + count += 1 + decision_type = ( + decision.decision_data.decision_type_category + if decision.decision_data + else "N/A" ) + print(f" {count}. {decision.appeal_number} - {decision_type}") - print(f"\nFound {response.count} 'Decision's since 2024") - print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") - - for decision in response.patent_appeal_data_bag: - print(f"\n Appeal Number: {decision.appeal_number}") - - if decision.appellant_data: - print(f" Application: {decision.appellant_data.application_number_text}") - print(f" Inventor: {decision.appellant_data.inventor_name or 'N/A'}") + if count >= max_items: + print(f" ... (stopping at {max_items} items)") + break - if decision.decision_data: - print(f" Decision: {decision.decision_data.decision_type_category}") - print(f" Outcome: {decision.decision_data.appeal_outcome_category}") - print(f" Date: {decision.decision_data.decision_issue_date}") +print(f"Retrieved {count} decisions via pagination") -except Exception as e: - print(f"Error searching by decision type: {e}") +print("-" * 40) +print("Example 5: Advanced search with multiple criteria") +print("-" * 40) -# ============================================================================= -# 3. Search by Application Number -# ============================================================================= +response = client.search_decisions( + technology_center_number_q="2100", + decision_type_category_q="Decision", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + sort="decisionData.decisionIssueDate desc", + limit=3, +) -print("\n" + "=" * 80) -print("3. Searching for decisions by application number pattern") -print("=" * 80) +print(f"\nFound {response.count} Decisions from TC 2100 (Electronics) in 2023") +print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") -try: - # Search for decisions related to applications starting with "15" - response = client.search_decisions( - application_number_text_q="15*", - decision_date_from_q="2023-01-01", - limit=3, - ) - - print(f"\nFound {response.count} decisions for applications starting with '15/'") - print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") - - for decision in response.patent_appeal_data_bag: - print(f"\n Appeal Number: {decision.appeal_number}") - - if decision.appellant_data: - print(f" Application: {decision.appellant_data.application_number_text}") - print(f" TC Number: {decision.appellant_data.technology_center_number}") - - if decision.document_data: - doc = decision.document_data - print(f" Document Name: {doc.document_name}") - if doc.file_download_uri: - print(f" Download URL: {doc.file_download_uri}") - -except Exception as e: - print(f"Error searching by application number: {e}") - -# ============================================================================= -# 4. Pagination Example -# ============================================================================= - -print("\n" + "=" * 80) -print("4. Paginating through appeal decisions") -print("=" * 80) - -try: - print("\nIterating through first 10 appeal decisions from 2024...") - count = 0 - for decision in client.paginate_decisions( - decision_date_from_q="2024-01-01", - limit=5, # Fetch 5 per page - ): - count += 1 - decision_type = ( - decision.decision_data.decision_type_category - if decision.decision_data - else "N/A" - ) - print(f"{count}. {decision.appeal_number} - {decision_type}") - - if count >= 10: # Stop after 10 results for this example - break - - print(f"\nDisplayed {count} decisions using pagination") - -except Exception as e: - print(f"Error paginating decisions: {e}") - -# ============================================================================= -# 5. Advanced Search with Multiple Criteria -# ============================================================================= - -print("\n" + "=" * 80) -print("5. Advanced search with multiple criteria") -print("=" * 80) - -try: - # Search with multiple convenience parameters - response = client.search_decisions( - technology_center_number_q="2100", # Electronics - decision_type_category_q="Decision", - decision_date_from_q="2023-01-01", - decision_date_to_q="2023-12-31", - sort="decisionData.decisionIssueDate desc", - limit=3, - ) +for decision in response.patent_appeal_data_bag: + print(f"\n Appeal Number: {decision.appeal_number}") - print(f"\nFound {response.count} Decisions from TC 2100 (Electronics) in 2023") - print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") + if decision.appellant_data: + print(f" Application: {decision.appellant_data.application_number_text}") - for decision in response.patent_appeal_data_bag: - print(f"\n Appeal Number: {decision.appeal_number}") + if decision.decision_data: + print(f" Decision: {decision.decision_data.decision_type_category}") + print(f" Date: {decision.decision_data.decision_issue_date}") - if decision.appellant_data: - print(f" Application: {decision.appellant_data.application_number_text}") +print("-" * 40) +print("Example 6: Direct query string") +print("-" * 40) - if decision.decision_data: - print(f" Decision: {decision.decision_data.decision_type_category}") - print(f" Date: {decision.decision_data.decision_issue_date}") +# Use a direct query string for more complex searches +response = client.search_decisions( + query="appellantData.technologyCenterNumber:3600 AND decisionData.appealOutcomeCategory:(Affirmed OR Reversed)", + limit=10, +) -except Exception as e: - print(f"Error with advanced search: {e}") - -# ============================================================================= -# 6. Direct Query String Example -# ============================================================================= - -print("\n" + "=" * 80) -print("6. Using direct query string for complex searches") -print("=" * 80) - -try: - # Use a direct query string for more complex searches - response = client.search_decisions( - query="appellantData.technologyCenterNumber:3600 AND decisionData.appealOutcomeCategory:(Affirmed OR Reversed)", - limit=10, - ) - - print(f"\nFound {response.count} Affirmed/Reversed decisions from TC 3600") - print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") - - for decision in response.patent_appeal_data_bag: - print(f"\n Appeal Number: {decision.appeal_number}") - print(f" >App. Number: {decision.appellant_data.application_number_text}") # type: ignore - if decision.decision_data: - print(f" >Decision: {decision.decision_data.decision_type_category}") - print(f" >Outcome: {decision.decision_data.appeal_outcome_category}") - -except Exception as e: - print(f"Error with direct query: {e}") - -# ============================================================================= -# 7. Error Handling Example -# ============================================================================= - -print("\n" + "=" * 80) -print("7. Error handling demonstration") -print("=" * 80) - -try: - # Attempt a search that might return no results - print("\nAttempting search with unlikely parameters...") - response = client.search_decisions( - appeal_number_q="INVALID-APPEAL-NUMBER", - limit=1, - ) +print(f"\nFound {response.count} Affirmed/Reversed decisions from TC 3600") +print(f"Displaying first {len(response.patent_appeal_data_bag)} results:") - if response.count == 0: - print("No results found for the given search criteria") - else: - print(f"Found {response.count} results") +for decision in response.patent_appeal_data_bag: + print(f"\n Appeal Number: {decision.appeal_number}") -except Exception as e: - print(f"Expected error occurred: {type(e).__name__}: {e}") + if decision.appellant_data: + print(f" Application: {decision.appellant_data.application_number_text}") -print("\n" + "=" * 80) -print("PTAB Appeals API example completed successfully!") -print("=" * 80) + if decision.decision_data: + print(f" Decision: {decision.decision_data.decision_type_category}") + print(f" Outcome: {decision.decision_data.appeal_outcome_category}") diff --git a/examples/ptab_interferences_example.py b/examples/ptab_interferences_example.py index 9f90892..5fd97a2 100644 --- a/examples/ptab_interferences_example.py +++ b/examples/ptab_interferences_example.py @@ -1,289 +1,217 @@ -"""Example usage of the pyUSPTO module for PTAB Interferences API. +"""Example usage of pyUSPTO for PTAB interferences. -This example demonstrates how to use the PTABInterferencesClient to interact with the USPTO PTAB -(Patent Trial and Appeal Board) Interferences API. It shows how to search for interference -decisions using various search criteria. - -PTAB Interferences are proceedings to determine priority of invention when two or more parties -claim the same patentable invention. +Demonstrates the PTABInterferencesClient for searching interference decisions +by outcome, party name, and application number. """ import os -from pyUSPTO import PTABInterferencesClient -from pyUSPTO.config import USPTOConfig +from pyUSPTO import PTABInterferencesClient, USPTOConfig -# --- Initialization --- -# Initialize the client with direct API key -print("Initialize with direct API key") +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( - "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable." + "API key is not set. Set the USPTO_API_KEY environment variable." ) config = USPTOConfig(api_key=api_key) client = PTABInterferencesClient(config=config) -print("\nBeginning PTAB Interferences API requests with configured client:") - -# ============================================================================= -# 1. Search Interference Decisions -# ============================================================================= - -print("\n" + "=" * 80) -print("1. Searching for interference decisions") -print("=" * 80) - -try: - # Search for recent interference decisions - response = client.search_decisions( - decision_date_from_q="2023-01-01", - limit=5, - ) - - print(f"\nFound {response.count} interference decisions since 2023") - print(f"Displaying first {len(response.patent_interference_data_bag)} results:") - - for decision in response.patent_interference_data_bag: - print(f"\n Interference Number: {decision.interference_number}") - - if decision.interference_meta_data: - meta = decision.interference_meta_data - print(f" Style Name: {meta.interference_style_name}") - print(f" Last Modified: {meta.interference_last_modified_date}") - - if decision.senior_party_data: - senior = decision.senior_party_data - print(f" Senior Party: {senior.patent_owner_name}") - if senior.patent_number: - print(f" Senior Patent: {senior.patent_number}") - - if decision.junior_party_data: - junior = decision.junior_party_data - print(f" Junior Party: {junior.patent_owner_name}") - if junior.publication_number: - print(f" Junior Publication: {junior.publication_number}") - - if decision.document_data: - doc = decision.document_data - print(f" Outcome: {doc.interference_outcome_category}") - print(f" Decision Type: {doc.decision_type_category}") - -except Exception as e: - print(f"Error searching interference decisions: {e}") - -# ============================================================================= -# 2. Search by Interference Outcome -# ============================================================================= - -print("\n" + "=" * 80) -print("2. Searching for decisions by outcome") -print("=" * 80) - -try: - # Search for decisions with specific outcomes - response = client.search_decisions( - interference_outcome_category_q="Final Decision", - decision_date_from_q="2012-01-01", - limit=3, - ) - - print(f"\nFound {response.count} final decisions since 2012") - print(f"Displaying first {len(response.patent_interference_data_bag)} results:") - - for decision in response.patent_interference_data_bag: - print(f"\n Interference Number: {decision.interference_number}") - - if decision.senior_party_data: - print(f" Senior Party: {decision.senior_party_data.patent_owner_name}") - print( - f" Senior Application: {decision.senior_party_data.application_number_text}" - ) - - if decision.junior_party_data: - print(f" Junior Party: {decision.junior_party_data.patent_owner_name}") - - if decision.document_data: - print(f" Outcome: {decision.document_data.interference_outcome_category}") - print(f" Decision Date: {decision.document_data.decision_issue_date}") - -except Exception as e: - print(f"Error searching by outcome: {e}") - -# ============================================================================= -# 3. Search by Party Name -# ============================================================================= - -print("\n" + "=" * 80) -print("3. Searching for decisions by party name") -print("=" * 80) - -try: - # Search for decisions involving a specific senior party - response = client.search_decisions( - senior_party_name_q="*Corp*", # Any company with "Corp" in the name - limit=3, - ) - - print(f"\nFound {response.count} decisions with 'Corp' in senior party name") - print(f"Displaying first {len(response.patent_interference_data_bag)} results:") - - for decision in response.patent_interference_data_bag: - print(f"\n Interference Number: {decision.interference_number}") - - if decision.senior_party_data: - senior = decision.senior_party_data - print(f" Senior Party: {senior.patent_owner_name}") - if senior.counsel_name: - print(f" Senior Counsel: {senior.counsel_name}") - - if decision.junior_party_data: - junior = decision.junior_party_data - print(f" Junior Party: {junior.patent_owner_name}") - if junior.counsel_name: - print(f" Junior Counsel: {junior.counsel_name}") - -except Exception as e: - print(f"Error searching by party name: {e}") - -# ============================================================================= -# 4. Search by Application Numbers -# ============================================================================= - -print("\n" + "=" * 80) -print("4. Searching for decisions by application numbers") -print("=" * 80) - -try: - # Search for decisions involving specific application numbers - response = client.search_decisions( - senior_party_application_number_q="12*", # Applications starting with 12/ - limit=3, - ) - - print( - f"\nFound {response.count} decisions with senior applications starting with '12'" - ) - print(f"Displaying first {len(response.patent_interference_data_bag)} results:") - - for decision in response.patent_interference_data_bag: - print(f"\n Interference Number: {decision.interference_number}") - - if decision.senior_party_data: - print( - f" Senior Application: {decision.senior_party_data.application_number_text}" - ) - - if decision.junior_party_data: - print( - f" Junior Publication: {decision.junior_party_data.publication_number}" - ) - - if decision.document_data: - print(f" Decision Type: {decision.document_data.decision_type_category}") - -except Exception as e: - print(f"Error searching by application numbers: {e}") - -# ============================================================================= -# 5. Pagination Example -# ============================================================================= - -print("\n" + "=" * 80) -print("5. Paginating through interference decisions") -print("=" * 80) - -try: - print("\nIterating through first 5 interference decisions from 2023...") - count = 0 - for decision in client.paginate_decisions( - decision_date_from_q="2023-01-01", - limit=3, # Fetch 3 per page - ): - count += 1 - outcome = ( - decision.document_data.interference_outcome_category - if decision.document_data - else "N/A" +print("-" * 40) +print("Example 1: Search interference decisions") +print("-" * 40) + +# Search for recent interference decisions +response = client.search_decisions( + decision_date_from_q="2023-01-01", + limit=5, +) + +print(f"\nFound {response.count} interference decisions since 2023") +print(f"Displaying first {len(response.patent_interference_data_bag)} results:") + +for decision in response.patent_interference_data_bag: + print(f"\n Interference Number: {decision.interference_number}") + + if decision.interference_meta_data: + meta = decision.interference_meta_data + print(f" Style Name: {meta.interference_style_name}") + print(f" Last Modified: {meta.interference_last_modified_date}") + + if decision.senior_party_data: + senior = decision.senior_party_data + print(f" Senior Party: {senior.patent_owner_name}") + if senior.patent_number: + print(f" Senior Patent: {senior.patent_number}") + + if decision.junior_party_data: + junior = decision.junior_party_data + print(f" Junior Party: {junior.patent_owner_name}") + if junior.publication_number: + print(f" Junior Publication: {junior.publication_number}") + + if decision.document_data: + doc = decision.document_data + print(f" Outcome: {doc.interference_outcome_category}") + print(f" Decision Type: {doc.decision_type_category}") + +print("-" * 40) +print("Example 2: Search by outcome") +print("-" * 40) + +response = client.search_decisions( + interference_outcome_category_q="Final Decision", + decision_date_from_q="2012-01-01", + limit=3, +) + +print(f"\nFound {response.count} final decisions since 2012") +print(f"Displaying first {len(response.patent_interference_data_bag)} results:") + +for decision in response.patent_interference_data_bag: + print(f"\n Interference Number: {decision.interference_number}") + + if decision.senior_party_data: + print(f" Senior Party: {decision.senior_party_data.patent_owner_name}") + print( + f" Senior Application: {decision.senior_party_data.application_number_text}" ) - print(f"{count}. {decision.interference_number} - {outcome}") - - if count >= 5: # Stop after 5 results for this example - break - - print(f"\nDisplayed {count} decisions using pagination") -except Exception as e: - print(f"Error paginating decisions: {e}") - -# ============================================================================= -# 6. Advanced Search with Multiple Criteria -# ============================================================================= + if decision.junior_party_data: + print(f" Junior Party: {decision.junior_party_data.patent_owner_name}") + + if decision.document_data: + print(f" Outcome: {decision.document_data.interference_outcome_category}") + print(f" Decision Date: {decision.document_data.decision_issue_date}") + +print("-" * 40) +print("Example 3: Search by party name") +print("-" * 40) + +# Search for decisions involving a specific senior party +response = client.search_decisions( + senior_party_name_q="*Corp*", + limit=3, +) + +print(f"\nFound {response.count} decisions with 'Corp' in senior party name") +print(f"Displaying first {len(response.patent_interference_data_bag)} results:") + +for decision in response.patent_interference_data_bag: + print(f"\n Interference Number: {decision.interference_number}") + + if decision.senior_party_data: + senior = decision.senior_party_data + print(f" Senior Party: {senior.patent_owner_name}") + if senior.counsel_name: + print(f" Senior Counsel: {senior.counsel_name}") + + if decision.junior_party_data: + junior = decision.junior_party_data + print(f" Junior Party: {junior.patent_owner_name}") + if junior.counsel_name: + print(f" Junior Counsel: {junior.counsel_name}") + +print("-" * 40) +print("Example 4: Search by application number") +print("-" * 40) + +response = client.search_decisions( + senior_party_application_number_q="12*", + limit=3, +) + +print( + f"\nFound {response.count} decisions with senior applications starting with '12'" +) +print(f"Displaying first {len(response.patent_interference_data_bag)} results:") + +for decision in response.patent_interference_data_bag: + print(f"\n Interference Number: {decision.interference_number}") + + if decision.senior_party_data: + print( + f" Senior Application: {decision.senior_party_data.application_number_text}" + ) -print("\n" + "=" * 80) -print("6. Advanced search with multiple criteria") -print("=" * 80) + if decision.junior_party_data: + print( + f" Junior Publication: {decision.junior_party_data.publication_number}" + ) -try: - # Search with multiple convenience parameters - response = client.search_decisions( - decision_type_category_q="Decision", - decision_date_from_q="2020-01-01", - decision_date_to_q="2023-12-31", - sort="documentData.decisionIssueDate desc", - limit=3, + if decision.document_data: + print(f" Decision Type: {decision.document_data.decision_type_category}") + +print("-" * 40) +print("Example 5: Paginate through decisions") +print("-" * 40) + +max_items = 5 +count = 0 +for decision in client.paginate_decisions( + decision_date_from_q="2023-01-01", + limit=3, +): + count += 1 + outcome = ( + decision.document_data.interference_outcome_category + if decision.document_data + else "N/A" ) + print(f" {count}. {decision.interference_number} - {outcome}") - print(f"\nFound {response.count} Decisions between 2020-2023") - print(f"Displaying first {len(response.patent_interference_data_bag)} results:") + if count >= max_items: + print(f" ... (stopping at {max_items} items)") + break - for decision in response.patent_interference_data_bag: - print(f"\n Interference Number: {decision.interference_number}") +print(f"Retrieved {count} decisions via pagination") - if decision.interference_meta_data: - print(f" Style: {decision.interference_meta_data.interference_style_name}") +print("-" * 40) +print("Example 6: Advanced search with multiple criteria") +print("-" * 40) - if decision.document_data: - print(f" Decision Type: {decision.document_data.decision_type_category}") - print(f" Decision Date: {decision.document_data.decision_issue_date}") - print(f" Outcome: {decision.document_data.interference_outcome_category}") +response = client.search_decisions( + decision_type_category_q="Decision", + decision_date_from_q="2020-01-01", + decision_date_to_q="2023-12-31", + sort="documentData.decisionIssueDate desc", + limit=3, +) - # Show additional parties if present - if decision.additional_party_data_bag: - print(f" Additional Parties: {len(decision.additional_party_data_bag)}") - for party in decision.additional_party_data_bag: - print(f" - {party.additional_party_name}") +print(f"\nFound {response.count} Decisions between 2020-2023") +print(f"Displaying first {len(response.patent_interference_data_bag)} results:") -except Exception as e: - print(f"Error with advanced search: {e}") +for decision in response.patent_interference_data_bag: + print(f"\n Interference Number: {decision.interference_number}") -# ============================================================================= -# 7. Direct Query String Example -# ============================================================================= + if decision.interference_meta_data: + print(f" Style: {decision.interference_meta_data.interference_style_name}") -print("\n" + "=" * 80) -print("7. Using direct query string for complex searches") -print("=" * 80) + if decision.document_data: + print(f" Decision Type: {decision.document_data.decision_type_category}") + print(f" Decision Date: {decision.document_data.decision_issue_date}") + print(f" Outcome: {decision.document_data.interference_outcome_category}") -try: - # Use a direct query string for more complex searches - response = client.search_decisions( - query='documentData.interferenceOutcomeCategory:"Final Decision"', - limit=3, - ) + if decision.additional_party_data_bag: + print(f" Additional Parties: {len(decision.additional_party_data_bag)}") + for party in decision.additional_party_data_bag: + print(f" - {party.additional_party_name}") - print(f"\nFound {response.count} final decisions.") - print(f"Displaying first {len(response.patent_interference_data_bag)} results:") +print("-" * 40) +print("Example 7: Direct query string") +print("-" * 40) - for decision in response.patent_interference_data_bag: - print(f"\n Interference Number: {decision.interference_number}") +# Use a direct query string for more complex searches +response = client.search_decisions( + query='documentData.interferenceOutcomeCategory:"Final Decision"', + limit=3, +) - if decision.document_data: - print(f" Outcome: {decision.document_data.interference_outcome_category}") +print(f"\nFound {response.count} final decisions.") +print(f"Displaying first {len(response.patent_interference_data_bag)} results:") -except Exception as e: - print(f"Error with direct query: {e}") +for decision in response.patent_interference_data_bag: + print(f"\n Interference Number: {decision.interference_number}") -print("\n" + "=" * 80) -print("PTAB Interferences API example completed successfully!") -print("=" * 80) + if decision.document_data: + print(f" Outcome: {decision.document_data.interference_outcome_category}") diff --git a/examples/ptab_trials_example.py b/examples/ptab_trials_example.py index dd21e84..786b946 100644 --- a/examples/ptab_trials_example.py +++ b/examples/ptab_trials_example.py @@ -1,197 +1,141 @@ -"""Example usage of the pyUSPTO module for PTAB Trials API. +"""Example usage of pyUSPTO for PTAB trial proceedings. -This example demonstrates how to use the PTABTrialsClient to interact with the USPTO PTAB -(Patent Trial and Appeal Board) Trials API. It shows how to search for trial proceedings, -documents, and decisions using various search criteria. - -PTAB Trials include: -- IPR (Inter Partes Review) -- PGR (Post-Grant Review) -- CBM (Covered Business Method) -- DER (Derivation) proceedings +Demonstrates the PTABTrialsClient for searching proceedings, documents, +and decisions across IPR, PGR, CBM, and DER trial types. """ import os -from pyUSPTO import PTABTrialsClient -from pyUSPTO.config import USPTOConfig +from pyUSPTO import PTABTrialsClient, USPTOConfig -# --- Initialization --- -# Initialize the client with direct API key -print("Initialize with direct API key") +# --- Client Initialization --- api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( - "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable." + "API key is not set. Set the USPTO_API_KEY environment variable." ) - config = USPTOConfig(api_key=api_key) client = PTABTrialsClient(config=config) -print("\nBeginning PTAB Trials API requests with configured client:") - -# ============================================================================= -# 1. Search Trial Proceedings -# ============================================================================= - -print("\n" + "=" * 80) -print("1. Searching for IPR trial proceedings") -print("=" * 80) - -try: - # Search for IPR proceedings filed in 2023 - response = client.search_proceedings( - trial_type_code_q="IPR", - petition_filing_date_from_q="2023-01-01", - petition_filing_date_to_q="2023-12-31", - limit=5, - ) - - print(f"\nFound {response.count} IPR proceedings filed in 2023") - print(f"Displaying first {len(response.patent_trial_proceeding_data_bag)} results:") - - for proceeding in response.patent_trial_proceeding_data_bag: - print(f"\n Trial Number: {proceeding.trial_number}") - - if proceeding.trial_meta_data: - meta = proceeding.trial_meta_data - print(f" Trial Type: {meta.trial_type_code}") - print(f" Status: {meta.trial_status_category}") - print(f" Filing Date: {meta.petition_filing_date}") - - if proceeding.patent_owner_data: - print(f" Patent Owner: {proceeding.patent_owner_data.patent_owner_name}") - print(f" Patent Number: {proceeding.patent_owner_data.patent_number}") - - if proceeding.regular_petitioner_data: - print( - f" Petitioner: {proceeding.regular_petitioner_data.real_party_in_interest_name}" - ) - -except Exception as e: - print(f"Error searching proceedings: {e}") - -# ============================================================================= -# 2. Search Trial Documents -# ============================================================================= - -print("\n" + "=" * 80) -print("2. Searching for trial documents") -print("=" * 80) - -try: - # Search for documents in a specific trial - # Using the new convenience parameters for petitioner and patent owner - response = client.search_documents( - trial_number_q="IPR2025-01319", - limit=10, - ) - - print(f"\nFound {response.count} documents") - print(f"Displaying first {len(response.patent_trial_document_data_bag)} results:") - - for item in response.patent_trial_document_data_bag: - print(f"\n Trial Number: {item.trial_number}") - - if item.document_data: - doc = item.document_data - print(f" Document Type: {doc.document_type_description_text}") - print(f" Filing Date: {doc.document_filing_date}") - - if doc.file_download_uri: - print(f" Download URL: {doc.file_download_uri}") - -except Exception as e: - print(f"Error searching documents: {e}") - -# ============================================================================= -# 3. Search Trial Decisions with New Convenience Parameters -# ============================================================================= - -print("\n" + "=" * 80) -print("3. Searching for trial decisions with new parameters") -print("=" * 80) - -try: - # Using all the new convenience parameters - response = client.search_decisions( - trial_type_code_q="IPR", - decision_type_category_q="Decision", - patent_owner_name_q="*", - trial_status_category_q="Terminated", - decision_date_from_q="2023-01-01", - limit=5, - ) - - print(f"\nFound {response.count} Decisions in IPR proceedings") - print(f"Displaying first {len(response.patent_trial_document_data_bag)} results:") - - for item in response.patent_trial_document_data_bag: - print(f"\n Trial Number: {item.trial_number}") - - if item.trial_meta_data: - print(f" Trial Type: {item.trial_meta_data.trial_type_code}") - print(f" Status: {item.trial_meta_data.trial_status_category}") - - if item.decision_data: - decision = item.decision_data - print(f" Decision Type: {decision.decision_type_category}") - print(f" Decision Date: {decision.decision_issue_date}") - -except Exception as e: - print(f"Error searching decisions: {e}") - -# ============================================================================= -# 4. Pagination Example -# ============================================================================= - -print("\n" + "=" * 80) -print("4. Paginating through proceedings") -print("=" * 80) - -try: - print("\nIterating through first 10 IPR proceedings from 2024...") - count = 0 - for proceeding in client.paginate_proceedings( - trial_type_code_q="IPR", - petition_filing_date_from_q="2024-01-01", - limit=5, # Fetch 5 per page - ): - count += 1 - print(f"{count}. {proceeding.trial_number}") - - if count >= 10: # Stop after 10 results for this example - break - - print(f"\nDisplayed {count} proceedings using pagination") - -except Exception as e: - print(f"Error paginating proceedings: {e}") - -# ============================================================================= -# 5. Advanced Query with Additional Parameters -# ============================================================================= - -print("\n" + "=" * 80) -print("5. Advanced search with additional query parameters") -print("=" * 80) - -try: - # Search using additional_query_params for custom filters - response = client.search_proceedings( - trial_type_code_q="PGR", - trial_status_category_q="Terminated", - sort="trialMetaData.petitionFilingDate desc", - fields="trialNumber,lastModifiedDateTime", - limit=3, - ) - - print(f"\nFound {response.count} Instituted PGR proceedings") - print(f"Displaying first {len(response.patent_trial_proceeding_data_bag)} results:") - - for proceeding in response.patent_trial_proceeding_data_bag: - print(f"\n Trial Number: {proceeding.trial_number}") - print(f" Last Modified: {proceeding.last_modified_date_time}") - -except Exception as e: - print(f"Error with advanced search: {e}") +print("-" * 40) +print("Example 1: Search trial proceedings") +print("-" * 40) + +# Search for IPR proceedings filed in 2023 +response = client.search_proceedings( + trial_type_code_q="IPR", + petition_filing_date_from_q="2023-01-01", + petition_filing_date_to_q="2023-12-31", + limit=5, +) + +print(f"\nFound {response.count} IPR proceedings filed in 2023") +print(f"Displaying first {len(response.patent_trial_proceeding_data_bag)} results:") + +for proceeding in response.patent_trial_proceeding_data_bag: + print(f"\n Trial Number: {proceeding.trial_number}") + + if proceeding.trial_meta_data: + meta = proceeding.trial_meta_data + print(f" Trial Type: {meta.trial_type_code}") + print(f" Status: {meta.trial_status_category}") + print(f" Filing Date: {meta.petition_filing_date}") + + if proceeding.patent_owner_data: + print(f" Patent Owner: {proceeding.patent_owner_data.patent_owner_name}") + print(f" Patent Number: {proceeding.patent_owner_data.patent_number}") + + if proceeding.regular_petitioner_data: + print( + f" Petitioner: {proceeding.regular_petitioner_data.real_party_in_interest_name}" + ) + +print("-" * 40) +print("Example 2: Search trial documents") +print("-" * 40) + +response = client.search_documents( + trial_number_q="IPR2025-01319", + limit=10, +) + +print(f"\nFound {response.count} documents") +print(f"Displaying first {len(response.patent_trial_document_data_bag)} results:") + +for item in response.patent_trial_document_data_bag: + print(f"\n Trial Number: {item.trial_number}") + + if item.document_data: + doc = item.document_data + print(f" Document Type: {doc.document_type_description_text}") + print(f" Filing Date: {doc.document_filing_date}") + + if doc.file_download_uri: + print(f" Download URL: {doc.file_download_uri}") + +print("-" * 40) +print("Example 3: Search trial decisions") +print("-" * 40) + +response = client.search_decisions( + trial_type_code_q="IPR", + decision_type_category_q="Decision", + patent_owner_name_q="*", + trial_status_category_q="Terminated", + decision_date_from_q="2023-01-01", + limit=5, +) + +print(f"\nFound {response.count} Decisions in IPR proceedings") +print(f"Displaying first {len(response.patent_trial_document_data_bag)} results:") + +for item in response.patent_trial_document_data_bag: + print(f"\n Trial Number: {item.trial_number}") + + if item.trial_meta_data: + print(f" Trial Type: {item.trial_meta_data.trial_type_code}") + print(f" Status: {item.trial_meta_data.trial_status_category}") + + if item.decision_data: + decision = item.decision_data + print(f" Decision Type: {decision.decision_type_category}") + print(f" Decision Date: {decision.decision_issue_date}") + +print("-" * 40) +print("Example 4: Paginate through proceedings") +print("-" * 40) + +max_items = 10 +count = 0 +for proceeding in client.paginate_proceedings( + trial_type_code_q="IPR", + petition_filing_date_from_q="2024-01-01", + limit=5, +): + count += 1 + print(f" {count}. {proceeding.trial_number}") + + if count >= max_items: + print(f" ... (stopping at {max_items} items)") + break + +print(f"Retrieved {count} proceedings via pagination") + +print("-" * 40) +print("Example 5: Advanced search with sort and fields") +print("-" * 40) + +response = client.search_proceedings( + trial_type_code_q="PGR", + trial_status_category_q="Terminated", + sort="trialMetaData.petitionFilingDate desc", + fields="trialNumber,lastModifiedDateTime", + limit=3, +) + +print(f"\nFound {response.count} Terminated PGR proceedings") +print(f"Displaying first {len(response.patent_trial_proceeding_data_bag)} results:") + +for proceeding in response.patent_trial_proceeding_data_bag: + print(f"\n Trial Number: {proceeding.trial_number}") + print(f" Last Modified: {proceeding.last_modified_date_time}") diff --git a/src/pyUSPTO/__init__.py b/src/pyUSPTO/__init__.py index ab45d6a..065374d 100644 --- a/src/pyUSPTO/__init__.py +++ b/src/pyUSPTO/__init__.py @@ -24,6 +24,8 @@ USPTOApiError, USPTOApiNotFoundError, USPTOApiRateLimitError, + USPTOConnectionError, + USPTOTimeout, ) from pyUSPTO.http_config import HTTPConfig @@ -34,10 +36,15 @@ FileData, ProductFileBag, ) -from pyUSPTO.models.patent_data import PatentDataResponse, PatentFileWrapper +from pyUSPTO.models.patent_data import ( + ApplicationContinuityData, + PatentDataResponse, + PatentFileWrapper, +) from pyUSPTO.models.petition_decisions import ( PetitionDecision, PetitionDecisionDocument, + PetitionDecisionDownloadResponse, PetitionDecisionResponse, ) from pyUSPTO.models.ptab import ( @@ -64,6 +71,8 @@ "USPTOApiRateLimitError", "USPTOApiNotFoundError", "FormatNotAvailableError", + "USPTOConnectionError", + "USPTOTimeout", "USPTOConfig", "HTTPConfig", # Warning classes @@ -81,6 +90,7 @@ "FileData", # Patent Data API "PatentDataClient", + "ApplicationContinuityData", "PatentDataResponse", "PatentFileWrapper", # Final Petition Decisions API @@ -88,6 +98,7 @@ "PetitionDecisionResponse", "PetitionDecision", "PetitionDecisionDocument", + "PetitionDecisionDownloadResponse", # PTAB API "PTABTrialsClient", "PTABAppealsClient", From 7cc6d9e6beb4555b89803f3268da86178171b199 Mon Sep 17 00:00:00 2001 From: Andrew <3300522+dpieski@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:12:47 -0500 Subject: [PATCH 8/8] chore: update version --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 72d4b5b..23780cb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -55,7 +55,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node`