diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 1ea6ee8..70ab75c 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -51,19 +51,15 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 black isort mypy + pip install ruff mypy - - name: Lint with flake8 + - name: Lint with ruff run: | - flake8 src/pyUSPTO --count --select=E9,F63,F7,F82 --show-source --statistics + ruff check src/ - - name: Check formatting with black + - name: Check formatting with ruff run: | - black --check src/ - - - name: Check imports with isort - run: | - isort --check-only --profile black src/ + ruff format --check src/ - name: Type check with mypy run: | diff --git a/.gitignore b/.gitignore index 72e5097..a219980 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ dist/ .claude/settings.local.json .tox/ Python/ +run_integration_tests.py +.plan.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6dad3..cab588a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ 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 + +### Added + +- **PTAB API 3.0 Support**: New clients for PTAB trials, appeals, and interferences + - `PTABTrialsClient` - Search trial proceedings, documents, and decisions + - `PTABAppealsClient` - Search ex parte appeal decisions + - `PTABInterferencesClient` - Search interference decisions +- New data models in `pyUSPTO.models.ptab` for PTAB responses: + - `PTABTrialProceeding`, `PTABAppealDecision`, `PTABInterferenceDecision` + - Supporting models for party data, metadata, and decision information +- Configuration support for PTAB base URL in `USPTOConfig` +- Comprehensive examples for all three PTAB clients (`examples/ptab_*.py`) +- Additional convenience parameters for `PTABTrialsClient` search methods: + - `search_documents()`: petitioner name, inventor, patent details, real party in interest + - `search_decisions()`: trial type, patent/application numbers, status, party information, document category + +### Changed + +- Enhanced `PTABTrialsClient.search_documents()` with convenience parameters for petitioner, inventor, patent details +- Enhanced `PTABTrialsClient.search_decisions()` with convenience parameters for trial type, status, and party information + ## [0.2.2] ### Added diff --git a/README.md b/README.md index 867ebfb..1c93681 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # pyUSPTO + [![PyPI version](https://badge.fury.io/py/pyUSPTO.svg)](https://badge.fury.io/py/pyUSPTO) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) @@ -6,64 +7,253 @@ A Python client library for interacting with the United Stated Patent and Trademark Office (USPTO) [Open Data Portal](https://data.uspto.gov/home) APIs. -This package provides clients for interacting with the USPTO Bulk Data API, the USPTO Patent Data API, and the USPTO Final Petition Decisions API. +This package provides clients for interacting with the USPTO Bulk Data API, Patent Data API, Final Petition Decisions API, and PTAB (Patent Trial and Appeal Board) APIs. > [!IMPORTANT] > The USPTO is in the process of moving their API. This package is only concerned with the new API. The [old API](https://developer.uspto.gov/) will be retired at the end of 2025. ## Quick Start -### Installation - **Requirements**: Python ≥3.10 ```bash pip install pyUSPTO ``` -Or install from source: +> [!IMPORTANT] +> You must have an API key for the [USPTO Open Data Portal API](https://data.uspto.gov/myodp/landing). -```bash -git clone https://github.com/DunlapCoddingPC/pyUSPTO.git -cd pyUSPTO -pip install -e . -``` +```python +from pyUSPTO import PatentDataClient +# Initialize with your API key +client = PatentDataClient(api_key="your_api_key_here") -### Configuration Options +# Search for patent applications +results = client.search_applications(inventor_name_q="Smith", limit=10) +print(f"Found {results.count} applications") +``` -> [!IMPORTANT] -> You must have an API key for the [USPTO Open Data Portal API](https://data.uspto.gov/myodp/landing). +## Configuration + +All clients can be configured using one of three methods: -There are multiple ways to configure the USPTO API clients: +### Method 1: Direct API Key Initialization +> [!NOTE] +> This method is convenient for quick scripts but not recommended for production use. Consider using environment variables instead. ```python -from pyUSPTO import PatentDataClient, FinalPetitionDecisionsClient +from pyUSPTO import ( + BulkDataClient, + PatentDataClient, + FinalPetitionDecisionsClient, + PTABTrialsClient, + PTABAppealsClient, + PTABInterferencesClient +) -# Method 1: Direct API key initialization patent_client = PatentDataClient(api_key="your_api_key_here") +bulk_client = BulkDataClient(api_key="your_api_key_here") petition_client = FinalPetitionDecisionsClient(api_key="your_api_key_here") +trials_client = PTABTrialsClient(api_key="your_api_key_here") +appeals_client = PTABAppealsClient(api_key="your_api_key_here") +interferences_client = PTABInterferencesClient(api_key="your_api_key_here") +``` + +### Method 2: Using USPTOConfig + +```python +from pyUSPTO import ( + BulkDataClient, + PatentDataClient, + FinalPetitionDecisionsClient, + PTABTrialsClient, + PTABAppealsClient, + PTABInterferencesClient +) -# Method 2: Using USPTOConfig with explicit parameters from pyUSPTO.config import USPTOConfig -config = USPTOConfig( - api_key="your_api_key_here", - bulk_data_base_url="https://api.uspto.gov", - patent_data_base_url="https://api.uspto.gov", - petition_decisions_base_url="https://api.uspto.gov" + +config = USPTOConfig(api_key="your_api_key_here") + +patent_client = PatentDataClient(config=config) +bulk_client = BulkDataClient(config=config) +petition_client = FinalPetitionDecisionsClient(config=config) +trials_client = PTABTrialsClient(config=config) +appeals_client = PTABAppealsClient(config=config) +interferences_client = PTABInterferencesClient(config=config) +``` + +### Method 3: Environment Variables (Recommended) + +Set the environment variable in your shell: + +```bash +export USPTO_API_KEY="your_api_key_here" +``` + +Then use it in your Python code: + +```python +from pyUSPTO import ( + BulkDataClient, + PatentDataClient, + FinalPetitionDecisionsClient, + PTABTrialsClient, + PTABAppealsClient, + PTABInterferencesClient ) +from pyUSPTO.config import USPTOConfig + +# Load configuration from environment +config = USPTOConfig.from_env() + patent_client = PatentDataClient(config=config) +bulk_client = BulkDataClient(config=config) petition_client = FinalPetitionDecisionsClient(config=config) +trials_client = PTABTrialsClient(config=config) +appeals_client = PTABAppealsClient(config=config) +interferences_client = PTABInterferencesClient(config=config) +``` + +## API Usage Examples + +### Patent Data API + +```python +# Search for applications by inventor name +inventor_search = patent_client.search_applications(inventor_name_q="Smith") +print(f"Found {inventor_search.count} applications with 'Smith' as inventor") +# > Found 104926 applications with 'Smith' as inventor. +``` + +### Final Petition Decisions API + +```python +# Search for petition decisions by date range +decisions = petition_client.search_decisions( + decision_date_from_q="2023-01-01", + limit=10 +) +print(f"Found {decisions.count} petition decisions since 2023") + +# Get a specific decision by ID +decision = petition_client.get_decision_by_id("decision_id_here") +print(f"Decision Type: {decision.decision_type_code}") +print(f"Application: {decision.application_number_text}") +``` + +### PTAB (Patent Trial and Appeal Board) APIs + +The package provides three clients for accessing PTAB data: + +#### PTAB Trials API + +```python +from pyUSPTO import PTABTrialsClient + +# Initialize client +trials_client = PTABTrialsClient(api_key="your_api_key_here") + +# Search for IPR trial proceedings +proceedings = trials_client.search_proceedings( + trial_type_code_q="IPR", + trial_status_category_q="Instituted", + petition_filing_date_from_q="2023-01-01", + limit=10 +) +print(f"Found {proceedings.count} instituted IPR proceedings") + +# Search for trial documents with new convenience parameters +documents = trials_client.search_documents( + trial_number_q="IPR2023-00001", + petitioner_party_name_q="Acme Corp", + patent_owner_name_q="XYZ Inc", + limit=5 +) + +# Search for trial decisions +decisions = trials_client.search_decisions( + trial_type_code_q="IPR", + decision_type_category_q="Final Written Decision", + patent_number_q="US1234567", + decision_date_from_q="2023-01-01" +) + +# Paginate through proceedings +for proceeding in trials_client.paginate_proceedings(trial_type_code_q="IPR", limit=25): + print(f"Trial: {proceeding.trial_number}") +``` + +#### PTAB Appeals API + +```python +from pyUSPTO import PTABAppealsClient -# Method 3: Using environment variables (recommended for production) -import os -os.environ["USPTO_API_KEY"] = "your_api_key_here" -config_from_env = USPTOConfig.from_env() -patent_client = PatentDataClient(config=config_from_env) -petition_client = FinalPetitionDecisionsClient(config=config_from_env) +# Initialize client +appeals_client = PTABAppealsClient(api_key="your_api_key_here") + +# Search for appeal decisions by technology center +decisions = appeals_client.search_decisions( + technology_center_number_q="3600", + decision_type_category_q="Affirmed", + decision_date_from_q="2023-01-01", + limit=10 +) +print(f"Found {decisions.count} affirmed decisions from TC 3600") + +# Search by application number +decisions = appeals_client.search_decisions( + application_number_text_q="15/123456", + limit=5 +) + +# Paginate through decisions +for decision in appeals_client.paginate_decisions( + technology_center_number_q="2100", + limit=25 +): + print(f"Appeal: {decision.appeal_number}") ``` +#### PTAB Interferences API + +```python +from pyUSPTO import PTABInterferencesClient + +# Initialize client +interferences_client = PTABInterferencesClient(api_key="your_api_key_here") + +# Search for interference decisions by outcome +decisions = interferences_client.search_decisions( + interference_outcome_category_q="Priority to Senior Party", + decision_date_from_q="2022-01-01", + limit=10 +) +print(f"Found {decisions.count} decisions awarding priority to senior party") + +# Search by party name +decisions = interferences_client.search_decisions( + senior_party_name_q="Example Corp", + junior_party_name_q="Test Inc", + limit=5 +) + +# Paginate through decisions +for decision in interferences_client.paginate_decisions( + decision_type_category_q="Final Decision", + limit=25 +): + print(f"Interference: {decision.interference_number}") +``` + +## Documentation + +Full documentation may be found on [Read the Docs](https://pyuspto.readthedocs.io/). + +## Advanced Topics + ### Advanced HTTP Configuration Control timeout behavior, retry logic, and connection pooling using `HTTPConfig`: @@ -126,36 +316,11 @@ patent_client = PatentDataClient(config=patent_config) petition_client = FinalPetitionDecisionsClient(config=petition_config) ``` -### Patent Data API - -```python -# Search for applications by inventor name -inventor_search = patent_client.search_applications(inventor_name_q="Smith") -print(f"Found {inventor_search.count} applications with 'Smith' as inventor") -# > Found 104926 applications with 'Smith' as inventor. -``` - -### Final Petition Decisions API - -```python -# Search for petition decisions by date range -decisions = petition_client.search_decisions( - decision_date_from_q="2023-01-01", - limit=10 -) -print(f"Found {decisions.count} petition decisions since 2023") - -# Get a specific decision by ID -decision = petition_client.get_decision_by_id("decision_id_here") -print(f"Decision Type: {decision.decision_type_code}") -print(f"Application: {decision.application_number_text}") -``` - -## Warning Control +### Warning Control The library uses Python's standard `warnings` module to report data parsing issues. This allows you to control how warnings are handled based on your needs. -### Warning Categories +**Warning Categories** All warnings inherit from `USPTODataWarning`: @@ -164,7 +329,7 @@ All warnings inherit from `USPTODataWarning`: - `USPTOTimezoneWarning`: Timezone-related issues - `USPTOEnumParseWarning`: Enum value parsing failures -### Controlling Warnings +**Controlling Warnings** ```python import warnings @@ -194,22 +359,9 @@ warnings.filterwarnings('always', category=USPTODataWarning) The library's permissive parsing philosophy returns `None` for fields that cannot be parsed, allowing you to retrieve partial data even when some fields have issues. Warnings inform you when this happens without stopping execution. -## Features +## Data Models -- Access to USPTO Bulk Data API, Patent Data API, and Final Petition Decisions API -- Search for patent applications using various filters -- Search and retrieve petition decisions with detailed information -- Download files, documents, and petition decision documents from the APIs -- Pagination support for large result sets -- Full type annotations and comprehensive test coverage - -## Documentation - -Full documentation may be found on [Read the Docs](https://pyuspto.readthedocs.io/). - -### Data Models - -The library uses Python dataclasses to represent API responses. All data models include type annotations for attributes and methods, making them fully compatible with static type checkers. +The library uses Python dataclasses to represent API responses. All data models include type annotations for attributes and methods, making them fully compatible with static type checkers. #### Bulk Data API @@ -239,6 +391,31 @@ The library uses Python dataclasses to represent API responses. All data models - `DecisionTypeCode`: Enum for petition decision types - `DocumentDirectionCategory`: Enum for document direction categories +#### PTAB Trials API + +- `PTABTrialProceedingResponse`: Top-level response from the API +- `PTABTrialProceeding`: Information about a PTAB trial proceeding (IPR, PGR, CBM, DER) +- `PTABTrialDocument`: Document associated with a trial proceeding +- `PTABTrialDecision`: Decision information for a trial proceeding +- `RegularPetitionerData`, `RespondentData`, `DerivationPetitionerData`: Party data for different trial types +- `PTABTrialMetaData`: Trial metadata and status information + +#### PTAB Appeals API + +- `PTABAppealResponse`: Top-level response from the API +- `PTABAppealDecision`: Ex parte appeal decision information +- `AppellantData`: Appellant information and application details +- `PTABAppealMetaData`: Appeal metadata and filing information +- `PTABAppealDocumentData`: Document and decision details + +#### PTAB Interferences API + +- `PTABInterferenceResponse`: Top-level response from the API +- `PTABInterferenceDecision`: Interference proceeding decision information +- `SeniorPartyData`, `JuniorPartyData`, `AdditionalPartyData`: Party data classes +- `PTABInterferenceMetaData`: Interference metadata and status information +- `PTABInterferenceDocumentData`: Document and outcome details + ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo index 4c27933..26e37b3 100644 --- a/docs/build/html/.buildinfo +++ b/docs/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 41a512eb26efab7f3a442ee890fc7d4c +config: c43a1a90993d7b5542e6aff794c219e2 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/.doctrees/api/clients.doctree b/docs/build/html/.doctrees/api/clients.doctree index 788a88b..75ed98f 100644 Binary files a/docs/build/html/.doctrees/api/clients.doctree and b/docs/build/html/.doctrees/api/clients.doctree differ diff --git a/docs/build/html/.doctrees/api/config.doctree b/docs/build/html/.doctrees/api/config.doctree index a44ac64..f9f064c 100644 Binary files a/docs/build/html/.doctrees/api/config.doctree and b/docs/build/html/.doctrees/api/config.doctree differ diff --git a/docs/build/html/.doctrees/api/exceptions.doctree b/docs/build/html/.doctrees/api/exceptions.doctree index 8406a62..1aee399 100644 Binary files a/docs/build/html/.doctrees/api/exceptions.doctree and b/docs/build/html/.doctrees/api/exceptions.doctree differ diff --git a/docs/build/html/.doctrees/api/index.doctree b/docs/build/html/.doctrees/api/index.doctree index b4d61c9..c6da02c 100644 Binary files a/docs/build/html/.doctrees/api/index.doctree and b/docs/build/html/.doctrees/api/index.doctree differ diff --git a/docs/build/html/.doctrees/api/models.doctree b/docs/build/html/.doctrees/api/models.doctree index f7a7e17..53ca05f 100644 Binary files a/docs/build/html/.doctrees/api/models.doctree and b/docs/build/html/.doctrees/api/models.doctree differ diff --git a/docs/build/html/.doctrees/api/warnings.doctree b/docs/build/html/.doctrees/api/warnings.doctree new file mode 100644 index 0000000..a1550f5 Binary files /dev/null and b/docs/build/html/.doctrees/api/warnings.doctree differ diff --git a/docs/build/html/.doctrees/development.doctree b/docs/build/html/.doctrees/development.doctree index 8977260..74403b2 100644 Binary files a/docs/build/html/.doctrees/development.doctree and b/docs/build/html/.doctrees/development.doctree differ diff --git a/docs/build/html/.doctrees/environment.pickle b/docs/build/html/.doctrees/environment.pickle index 887d8d4..a7778b4 100644 Binary files a/docs/build/html/.doctrees/environment.pickle and b/docs/build/html/.doctrees/environment.pickle differ diff --git a/docs/build/html/.doctrees/examples/bulk_data.doctree b/docs/build/html/.doctrees/examples/bulk_data.doctree index 622021a..e821a0c 100644 Binary files a/docs/build/html/.doctrees/examples/bulk_data.doctree and b/docs/build/html/.doctrees/examples/bulk_data.doctree differ diff --git a/docs/build/html/.doctrees/examples/ifw_example.doctree b/docs/build/html/.doctrees/examples/ifw_example.doctree new file mode 100644 index 0000000..befdc27 Binary files /dev/null and b/docs/build/html/.doctrees/examples/ifw_example.doctree differ diff --git a/docs/build/html/.doctrees/examples/index.doctree b/docs/build/html/.doctrees/examples/index.doctree index 4522011..287181f 100644 Binary files a/docs/build/html/.doctrees/examples/index.doctree and b/docs/build/html/.doctrees/examples/index.doctree differ diff --git a/docs/build/html/.doctrees/examples/patent_data.doctree b/docs/build/html/.doctrees/examples/patent_data.doctree index d994ac9..00a19a6 100644 Binary files a/docs/build/html/.doctrees/examples/patent_data.doctree and b/docs/build/html/.doctrees/examples/patent_data.doctree differ diff --git a/docs/build/html/.doctrees/examples/petition_decisions.doctree b/docs/build/html/.doctrees/examples/petition_decisions.doctree new file mode 100644 index 0000000..da53a91 Binary files /dev/null and b/docs/build/html/.doctrees/examples/petition_decisions.doctree differ diff --git a/docs/build/html/.doctrees/examples/ptab_appeals.doctree b/docs/build/html/.doctrees/examples/ptab_appeals.doctree new file mode 100644 index 0000000..cd1ee50 Binary files /dev/null and b/docs/build/html/.doctrees/examples/ptab_appeals.doctree differ diff --git a/docs/build/html/.doctrees/examples/ptab_interferences.doctree b/docs/build/html/.doctrees/examples/ptab_interferences.doctree new file mode 100644 index 0000000..84b97a6 Binary files /dev/null and b/docs/build/html/.doctrees/examples/ptab_interferences.doctree differ diff --git a/docs/build/html/.doctrees/examples/ptab_trials.doctree b/docs/build/html/.doctrees/examples/ptab_trials.doctree new file mode 100644 index 0000000..66e2138 Binary files /dev/null and b/docs/build/html/.doctrees/examples/ptab_trials.doctree differ diff --git a/docs/build/html/.doctrees/index.doctree b/docs/build/html/.doctrees/index.doctree index 84e3817..cafb9f9 100644 Binary files a/docs/build/html/.doctrees/index.doctree and b/docs/build/html/.doctrees/index.doctree differ diff --git a/docs/build/html/.doctrees/installation.doctree b/docs/build/html/.doctrees/installation.doctree index 93a52fe..0725aca 100644 Binary files a/docs/build/html/.doctrees/installation.doctree and b/docs/build/html/.doctrees/installation.doctree differ diff --git a/docs/build/html/.doctrees/quickstart.doctree b/docs/build/html/.doctrees/quickstart.doctree index 0a4d996..aac4464 100644 Binary files a/docs/build/html/.doctrees/quickstart.doctree and b/docs/build/html/.doctrees/quickstart.doctree differ diff --git a/docs/build/html/_modules/index.html b/docs/build/html/_modules/index.html index abd67e7..436801c 100644 --- a/docs/build/html/_modules/index.html +++ b/docs/build/html/_modules/index.html @@ -5,7 +5,7 @@ - Overview: module code — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Overview: module code — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -77,10 +77,18 @@

All modules for which code is available

@@ -90,7 +98,7 @@

All modules for which code is available


-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/clients/bulk_data.html b/docs/build/html/_modules/pyUSPTO/clients/bulk_data.html index 09fd258..8e92649 100644 --- a/docs/build/html/_modules/pyUSPTO/clients/bulk_data.html +++ b/docs/build/html/_modules/pyUSPTO/clients/bulk_data.html @@ -5,7 +5,7 @@ - pyUSPTO.clients.bulk_data — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + pyUSPTO.clients.bulk_data — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -84,17 +84,12 @@

Source code for pyUSPTO.clients.bulk_data

 """
 
 import os
-from typing import Any, Dict, Generator, Iterator, List, Optional
+from typing import Any, Dict, Iterator, List, Optional
 from urllib.parse import urlparse
 
-from pyUSPTO.base import BaseUSPTOClient
+from pyUSPTO.clients.base import BaseUSPTOClient
 from pyUSPTO.config import USPTOConfig
-from pyUSPTO.exceptions import USPTOApiError
-from pyUSPTO.models.bulk_data import (
-    BulkDataProduct,
-    BulkDataResponse,
-    FileData,
-)
+from pyUSPTO.models.bulk_data import BulkDataProduct, BulkDataResponse, FileData
 
 
 
@@ -136,7 +131,7 @@

Source code for pyUSPTO.clients.bulk_data

         # Use provided base_url or get from config
         base_url = base_url or self.config.bulk_data_base_url
 
-        super().__init__(api_key=api_key, base_url=base_url)
+ super().__init__(api_key=api_key, base_url=base_url, config=self.config)
@@ -413,7 +408,7 @@

Source code for pyUSPTO.clients.bulk_data

   
-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/clients/patent_data.html b/docs/build/html/_modules/pyUSPTO/clients/patent_data.html index 31e0356..3745f7e 100644 --- a/docs/build/html/_modules/pyUSPTO/clients/patent_data.html +++ b/docs/build/html/_modules/pyUSPTO/clients/patent_data.html @@ -5,7 +5,7 @@ - pyUSPTO.clients.patent_data — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + pyUSPTO.clients.patent_data — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -83,18 +83,33 @@

Source code for pyUSPTO.clients.patent_data

 It allows you to search for and retrieve patent application data.
 """
 
-import os
-import re
-from typing import Any, Dict, Iterator, List, Optional, Union
-from urllib.parse import urlparse
+import warnings
+from pathlib import Path
+from typing import Any, Dict, Iterator, List, Optional
+from urllib.parse import urljoin, urlparse
 
-from pyUSPTO.base import BaseUSPTOClient
+from pyUSPTO.clients.base import BaseUSPTOClient
 from pyUSPTO.config import USPTOConfig
-from pyUSPTO.exceptions import USPTOApiError
 from pyUSPTO.models.patent_data import (
+    ApplicationContinuityData,
+    ApplicationMetaData,
+    Assignment,
+    ChildContinuity,
+    DocumentBag,
+    DocumentFormat,
+    EventData,
+    ForeignPriority,
+    ParentContinuity,
     PatentDataResponse,
     PatentFileWrapper,
+    PatentTermAdjustmentData,
+    PrintedMetaData,
+    PrintedPublication,
+    RecordAttorney,
+    StatusCodeCollection,
+    StatusCodeSearchResponse,
 )
+from pyUSPTO.warnings import USPTODataMismatchWarning
 
 
 
@@ -102,684 +117,1182 @@

Source code for pyUSPTO.clients.patent_data

 class PatentDataClient(BaseUSPTOClient[PatentDataResponse]):
     """Client for interacting with the USPTO Patent Data API."""
 
-    # Centralized endpoint configuration
     ENDPOINTS = {
-        # Application endpoints
-        "applications_search": "applications/search",
-        "applications_search_download": "applications/search/download",
-        "application_by_number": "applications/{application_number}",
-        "application_metadata": "applications/{application_number}/meta-data",
-        "application_adjustment": "applications/{application_number}/adjustment",
-        "application_assignment": "applications/{application_number}/assignment",
-        "application_attorney": "applications/{application_number}/attorney",
-        "application_continuity": "applications/{application_number}/continuity",
-        "application_foreign_priority": "applications/{application_number}/foreign-priority",
-        "application_transactions": "applications/{application_number}/transactions",
-        "application_documents": "applications/{application_number}/documents",
-        "application_associated_documents": "applications/{application_number}/associated-documents",
-        # Document download endpoint (different base URL)
-        "download_document": "download/applications/{application_number}/{document_id}",
-        # Status code endpoints
-        "status_codes": "status-codes",
+        "search_applications": "api/v1/patent/applications/search",
+        "get_search_results": "api/v1/patent/applications/search/download",
+        "get_application_by_number": "api/v1/patent/applications/{application_number}",
+        "get_application_metadata": "api/v1/patent/applications/{application_number}/meta-data",
+        "get_application_adjustment": "api/v1/patent/applications/{application_number}/adjustment",
+        "get_application_assignment": "api/v1/patent/applications/{application_number}/assignment",
+        "get_application_attorney": "api/v1/patent/applications/{application_number}/attorney",
+        "get_application_continuity": "api/v1/patent/applications/{application_number}/continuity",
+        "get_application_foreign_priority": "api/v1/patent/applications/{application_number}/foreign-priority",
+        "get_application_transactions": "api/v1/patent/applications/{application_number}/transactions",
+        "get_application_documents": "api/v1/patent/applications/{application_number}/documents",
+        "get_application_associated_documents": "api/v1/patent/applications/{application_number}/associated-documents",
+        "download_application_document": "api/v1/download/applications/{application_number}/{document_id}",
+        "status_codes": "api/v1/patent/status-codes",
     }
 
-
-[docs] def __init__( self, api_key: Optional[str] = None, base_url: Optional[str] = None, config: Optional[USPTOConfig] = None, ): - """ - Initialize the PatentDataClient. + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.patent_data_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + ) + +
+[docs] + def sanitize_application_number(self, input_number: str) -> str: + """Sanitize and validate a USPTO application number. + + Application numbers are either: + - 8 digits (e.g., "16123456") + - Series code format: 2 digits + "/" + 6 digits (e.g., "08/123456") + - PCT format: "PCT/US2024/012345" → "PCTUS2412345" + + This method removes common separators (commas, spaces) while preserving + the "/" in series code format. Args: - api_key: Optional API key for authentication - base_url: The base URL of the API, defaults to config.patent_data_base_url or "https://api.uspto.gov/api/v1/patent" - config: Optional USPTOConfig instance + input_number: Raw application number input. May include commas, + spaces, or other formatting. + + Returns: + str: Sanitized application number (either "NNNNNNNN" or "NN/NNNNNN"). + + Raises: + ValueError: If the format is invalid. + + Examples: + >>> client.sanitize_application_number("16123456") + "16123456" + >>> client.sanitize_application_number("16,123,456") + "16123456" + >>> client.sanitize_application_number("08/123456") + "08/123456" + >>> client.sanitize_application_number("08/123,456") + "08/123456" """ - # Use config if provided, otherwise create default config - self.config = config or USPTOConfig(api_key=api_key) + if not input_number or not input_number.strip(): + raise ValueError("Application number cannot be empty") - # Use provided API key or get from config - api_key = api_key or self.config.api_key + raw = input_number.strip() - # Use provided base_url or get from config - base_url = base_url or self.config.patent_data_base_url + # --- NEW: Handle PCT formats --- + # Example: "PCT/US2024/012345" -> "PCTUS2412345" + if raw.startswith("PCT"): + parts = raw.split("/") + if len(parts) != 3: + raise ValueError( + f"Invalid PCT application format: {input_number}. " + "Expected PCT/CCYYYY/NNNNNN" + ) - super().__init__(api_key=api_key, base_url=base_url)
+ _, country_year, serial = parts + # country_year can be "US2024" or "US24" + country = country_year[:2] -
-[docs] - def get_patent_applications( - self, params: Optional[Dict[str, Any]] = None - ) -> PatentDataResponse: - """ - Get a list of patent applications using the search endpoint. + year_part = country_year[2:] + if not year_part.isdigit(): + raise ValueError( + f"Invalid PCT year in: {country_year}. Must be digits." + ) - Args: - params: Optional query parameters including: - - q: Search query string - - sort: Field to sort by followed by sort order - - offset: Position in dataset to start from - - limit: Number of results to return - - facets: List of fields to facet upon - - fields: Fields to include in response - - filters: Field filters - - rangeFilters: Range filters + # Normalize: + # "2024" -> "24" + # "24" -> "24" + if len(year_part) == 4: + year = year_part[-2:] + elif len(year_part) == 2: + year = year_part + else: + raise ValueError( + f"Invalid PCT year length in: {country_year}. " + "Expected CCYYYY or CCYY." + ) - Returns: - PatentDataResponse object containing the API response - """ - result = self._make_request( - method="GET", - endpoint=self.ENDPOINTS["applications_search"], - params=params, - response_class=PatentDataResponse, - ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ # Serial must be digits only + if not serial.isdigit(): + raise ValueError(f"Invalid PCT serial: {serial}. Must be numeric.") + return f"PCT{country}{year}{serial}" -
-[docs] - def search_patent_applications_post( - self, search_request: Dict[str, Any] - ) -> PatentDataResponse: - """ - Search patent applications using POST method with JSON payload. + # Strip whitespace and remove commas/spaces + cleaned = raw.replace(",", "").replace(" ", "") - Args: - search_request: JSON payload with search parameters including: - - q: Search query string - - filters: Array of filter objects - - rangeFilters: Array of range filter objects - - sort: Array of sort objects - - fields: Array of field names to include - - pagination: Pagination object - - facets: Array of facet field names + # Check if this is series code format (NN/NNNNNN) + if "/" in cleaned: + parts = cleaned.split("/") + if len(parts) != 2: + raise ValueError( + f"Invalid application number format: {input_number}. " + "Expected format: NNNNNNNN or NN/NNNNNN" + ) - Returns: - PatentDataResponse object containing the API response - """ - result = self._make_request( - method="POST", - endpoint=self.ENDPOINTS["applications_search"], - json_data=search_request, - response_class=PatentDataResponse, - ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ series, serial = parts + if not series.isdigit() or not serial.isdigit(): + raise ValueError( + f"Invalid application number format: {input_number}. " + "Series and serial must be numeric." + ) + if len(series) != 2 or len(serial) != 6: + raise ValueError( + f"Invalid application number format: {input_number}. " + "Expected series code format: NN/NNNNNN (2 digits / 6 digits)" + ) -
-[docs] - def download_patent_applications( - self, params: Optional[Dict[str, Any]] = None, format: str = "json" - ) -> PatentDataResponse: - """ - Download patent data with specified format. + return cleaned - Args: - params: Optional query parameters - format: Download format (json or csv) + # Standard 8-digit format + if not cleaned.isdigit(): + raise ValueError( + f"Invalid application number format: {input_number}. " + "Must contain only digits." + ) - Returns: - PatentDataResponse object containing the API response + if len(cleaned) != 8: + raise ValueError( + f"Invalid application number format: {input_number}. " + "Expected 8 digits." + ) + + return cleaned
+ + + def _get_wrapper_from_response( + self, + response_data: PatentDataResponse, + application_number_for_validation: Optional[str] = None, + ) -> Optional[PatentFileWrapper]: + """Helper to extract a single PatentFileWrapper, optionally validating the app number.""" + if not response_data or not response_data.patent_file_wrapper_data_bag: + return None + + wrapper = response_data.patent_file_wrapper_data_bag[0] + + if ( + application_number_for_validation + and wrapper.application_number_text + != self.sanitize_application_number(application_number_for_validation) + ): + warnings.warn( + f"API returned application number '{wrapper.application_number_text}' " + f"but requested '{application_number_for_validation}'. " + f"This may indicate an API data inconsistency.", + USPTODataMismatchWarning, + stacklevel=2, + ) + return wrapper + +
+[docs] + def search_applications( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + application_number_q: Optional[str] = None, + patent_number_q: Optional[str] = None, + inventor_name_q: Optional[str] = None, + applicant_name_q: Optional[str] = None, + assignee_name_q: Optional[str] = None, + filing_date_from_q: Optional[str] = None, + filing_date_to_q: Optional[str] = None, + grant_date_from_q: Optional[str] = None, + grant_date_to_q: Optional[str] = None, + classification_q: Optional[str] = None, + earliestPublicationNumber_q: Optional[str] = None, + pctPublicationNumber_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PatentDataResponse: + """ + Searches for patent applications. + Can perform a GET request based on OpenAPI query parameters or a POST request if post_body is specified. """ - # Add format parameter if not already in params - if params is None: - params = {} - if "format" not in params: - params["format"] = format - - result = self._make_request( - method="GET", - endpoint=self.ENDPOINTS["applications_search_download"], - params=params, - response_class=PatentDataResponse, - ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse + endpoint = self.ENDPOINTS["search_applications"] + + if post_body is not None: + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + response_class=PatentDataResponse, + ) + else: + params: Dict[str, Any] = {} + final_q = query + + if final_q is None: + q_parts = [] + if application_number_q: + q_parts.append(f"applicationNumberText:{application_number_q}") + if patent_number_q: + q_parts.append( + f"applicationMetaData.patentNumber:{patent_number_q}" + ) + if inventor_name_q: + q_parts.append( + f"applicationMetaData.inventorBag.inventorNameText:{inventor_name_q}" + ) + if applicant_name_q: + q_parts.append( + f"applicationMetaData.firstApplicantName:{applicant_name_q}" + ) + if assignee_name_q: + q_parts.append( + f"assignmentBag.assigneeBag.assigneeNameText:{assignee_name_q}" + ) + if classification_q: + q_parts.append( + f"applicationMetaData.cpcClassificationBag:{classification_q}" + ) + if earliestPublicationNumber_q: + q_parts.append( + f"applicationMetaData.earliestPublicationNumber:{earliestPublicationNumber_q}" + ) + if pctPublicationNumber_q: + q_parts.append( + f"applicationMetaData.pctPublicationNumber:{pctPublicationNumber_q}" + ) + if filing_date_from_q and filing_date_to_q: + q_parts.append( + f"applicationMetaData.filingDate:[{filing_date_from_q} TO {filing_date_to_q}]" + ) + elif filing_date_from_q: + q_parts.append( + f"applicationMetaData.filingDate:>={filing_date_from_q}" + ) + elif filing_date_to_q: + q_parts.append( + f"applicationMetaData.filingDate:<={filing_date_to_q}" + ) + + if grant_date_from_q and grant_date_to_q: + q_parts.append( + f"applicationMetaData.grantDate:[{grant_date_from_q} TO {grant_date_to_q}]" + ) + elif grant_date_from_q: + q_parts.append( + f"applicationMetaData.grantDate:>={grant_date_from_q}" + ) + elif grant_date_to_q: + q_parts.append(f"applicationMetaData.grantDate:<={grant_date_to_q}") + + if q_parts: + final_q = " AND ".join(q_parts) + + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets is not None: + params["facets"] = facets + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=PatentDataResponse, + ) assert isinstance(result, PatentDataResponse) return result
-
-[docs] - def download_patent_applications_post( - self, download_request: Dict[str, Any] - ) -> PatentDataResponse: +
+[docs] + def get_search_results( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + fields_param: Optional[str] = None, + filters_param: Optional[str] = None, + range_filters_param: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + application_number_q: Optional[str] = None, + patent_number_q: Optional[str] = None, + inventor_name_q: Optional[str] = None, + applicant_name_q: Optional[str] = None, + assignee_name_q: Optional[str] = None, + filing_date_from_q: Optional[str] = None, + filing_date_to_q: Optional[str] = None, + grant_date_from_q: Optional[str] = None, + grant_date_to_q: Optional[str] = None, + classification_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> list[ApplicationMetaData]: """ - Download patent data using POST method with JSON payload. + Fetches a dataset of patent applications based on search criteria, always requesting JSON format. + For GET, parameters align with OpenAPI for /api/v1/patent/applications/search/download. + For POST, post_body should conform to PatentDownloadRequest schema. + """ + endpoint = self.ENDPOINTS["get_search_results"] - Args: - download_request: JSON payload with download parameters including format + if post_body is not None: + if "format" not in post_body: + post_body["format"] = "json" - Returns: - PatentDataResponse object containing the API response - """ - result = self._make_request( - method="POST", - endpoint=self.ENDPOINTS["applications_search_download"], - json_data=download_request, - response_class=PatentDataResponse, - ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + ) + else: + params: Dict[str, Any] = {} + final_q = query + + if final_q is None: + q_parts = [] + if application_number_q: + q_parts.append(f"applicationNumberText:{application_number_q}") + if patent_number_q: + q_parts.append( + f"applicationMetaData.patentNumber:{patent_number_q}" + ) + if inventor_name_q: + q_parts.append( + f"applicationMetaData.inventorBag.inventorNameText:{inventor_name_q}" + ) + if applicant_name_q: + q_parts.append( + f"applicationMetaData.firstApplicantName:{applicant_name_q}" + ) + if assignee_name_q: + q_parts.append( + f"assignmentBag.assigneeBag.assigneeNameText:{assignee_name_q}" + ) + if classification_q: + q_parts.append( + f"applicationMetaData.cpcClassificationBag:{classification_q}" + ) + + if filing_date_from_q and filing_date_to_q: + q_parts.append( + f"applicationMetaData.filingDate:[{filing_date_from_q} TO {filing_date_to_q}]" + ) + elif filing_date_from_q: + q_parts.append( + f"applicationMetaData.filingDate:>={filing_date_from_q}" + ) + elif filing_date_to_q: + q_parts.append( + f"applicationMetaData.filingDate:<={filing_date_to_q}" + ) + + if grant_date_from_q and grant_date_to_q: + q_parts.append( + f"applicationMetaData.grantDate:[{grant_date_from_q} TO {grant_date_to_q}]" + ) + elif grant_date_from_q: + q_parts.append( + f"applicationMetaData.grantDate:>={grant_date_from_q}" + ) + elif grant_date_to_q: + q_parts.append(f"applicationMetaData.grantDate:<={grant_date_to_q}") + + if q_parts: + final_q = " AND ".join(q_parts) + + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if fields_param is not None: + params["fields"] = fields_param + if filters_param is not None: + params["filters"] = filters_param + if range_filters_param is not None: + params["rangeFilters"] = range_filters_param + + params["format"] = "json" + + if additional_query_params: + params.update(additional_query_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + ) + assert isinstance(result, Dict) + amd_list = [ + ApplicationMetaData.from_dict(item["applicationMetaData"]) + for item in result["patentdata"] + ] + return amd_list
-
-[docs] - def get_patent_by_application_number( +
+[docs] + def get_application_by_number( self, application_number: str - ) -> PatentFileWrapper: - """ - Get a specific patent by application number. + ) -> Optional[PatentFileWrapper]: + """Retrieves the full details for a specific patent application by its number. + + This method fetches comprehensive information for a single patent application + identified by its unique application number. Args: - application_number: The application number + application_number (str): The USPTO application number for the patent + application (e.g., "16123456" or "18/915,708"). The application + number will be automatically sanitized to remove commas and spaces. Returns: - PatentFileWrapper object containing the patent data + Optional[PatentFileWrapper]: A `PatentFileWrapper` object representing + the complete file wrapper for the application if found. This object + contains all data sections related to the application, such as + metadata, addresses, assignments, attorney/agent data, continuity + data, PTA/PTE data, transactions, and associated documents. + Returns None if the application cannot be found or if the response + does not contain the expected data. """ - endpoint = self.ENDPOINTS["application_by_number"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_by_number"].format( + application_number=self.sanitize_application_number(application_number) ) - data = self._make_request(method="GET", endpoint=endpoint) - - # Handling different response formats - if isinstance(data, dict): - if "patentFileWrapperDataBag" in data: - for wrapper in data["patentFileWrapperDataBag"]: - if wrapper.get("applicationNumberText") == application_number: - return PatentFileWrapper.from_dict(wrapper) - raise ValueError( - f"Patent with application number {application_number} not found in response" - ) - else: - # If response doesn't contain patentFileWrapperDataBag, assume it's a direct PatentFileWrapper - return PatentFileWrapper.from_dict(data) - elif isinstance(data, PatentFileWrapper): - return data - else: - raise TypeError(f"Unexpected response type: {type(data)}")
+ response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse + ) + assert isinstance(response_data, PatentDataResponse) + return self._get_wrapper_from_response( + response_data=response_data, + application_number_for_validation=application_number, + )
[docs] - def get_application_metadata(self, application_number: str) -> PatentDataResponse: - """ - Get metadata for a specific patent application. + def get_application_metadata( + self, application_number: str + ) -> Optional[ApplicationMetaData]: + """Retrieves key metadata for a specific patent application. + + This method fetches the `ApplicationMetaData` component from the full + patent file wrapper. The metadata includes a wide range of information + such as application status, important dates (filing, grant, publication), + applicant and inventor details, classification data, and other core + identifying information for the application. Args: - application_number: The application number + application_number (str): The USPTO application number for which + metadata is being requested (e.g., "16123456" or "18/915,708"). + The application number will be automatically sanitized. Returns: - PatentDataResponse object containing the application metadata + Optional[ApplicationMetaData]: An `ApplicationMetaData` object + containing the core details of the patent application if found. + Returns None if the application cannot be found or if metadata + is not available in the response. """ - endpoint = self.ENDPOINTS["application_metadata"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_metadata"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return wrapper.application_meta_data if wrapper else None
[docs] - def get_application_adjustment(self, application_number: str) -> PatentDataResponse: - """ - Get patent term adjustment data for an application. + def get_application_adjustment( + self, application_number: str + ) -> Optional[PatentTermAdjustmentData]: + """Retrieves patent term adjustment (PTA) data for a specific application. + + This method fetches the `PatentTermAdjustmentData` component from the + full patent file wrapper. This data includes details on various delay + quantities (e.g., A, B, C delays, applicant delays), the total + calculated adjustment, and a history of PTA events that influenced the + term. Args: - application_number: The application number + application_number (str): The USPTO application number for which PTA + data is being requested (e.g., "16123456"). Returns: - PatentDataResponse object containing the adjustment data + Optional[PatentTermAdjustmentData]: A `PatentTermAdjustmentData` + object containing the PTA details if the application is found + and has such data. Returns None if the application cannot be + found or if PTA data is not available in the response. """ - endpoint = self.ENDPOINTS["application_adjustment"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_adjustment"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return wrapper.patent_term_adjustment_data if wrapper else None
[docs] - def get_application_assignment(self, application_number: str) -> PatentDataResponse: - """ - Get assignment data for an application. + def get_application_assignment( + self, application_number: str + ) -> Optional[List[Assignment]]: + """Retrieves a list of patent assignments for a specific application. + + This method fetches the `assignment_bag` from the patent file wrapper, + which contains a list of `Assignment` objects. Each `Assignment` object + details an assignment including information such as reel and frame numbers, + recording dates, conveyance text, and details about the assignors and assignees. Args: - application_number: The application number + application_number (str): The USPTO application number for which + assignment data is being requested (e.g., "16123456"). Returns: - PatentDataResponse object containing the assignment data + Optional[List[Assignment]]: A list of `Assignment` objects, each + representing a recorded assignment for the application. Returns + None if the application cannot be found, or if no assignment + data is available in the response. An empty list may be + returned if the application is found but has no recorded + assignments. """ - endpoint = self.ENDPOINTS["application_assignment"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_assignment"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return wrapper.assignment_bag if wrapper else None
[docs] - def get_application_attorney(self, application_number: str) -> PatentDataResponse: - """ - Get attorney/agent data for an application. + def get_application_attorney( + self, application_number: str + ) -> Optional[RecordAttorney]: + """Retrieves data for the attorney(s) of record for a specific application. + + This method fetches the `RecordAttorney` object associated with the + patent application. This object contains details about the attorney(s) + of record, including customer number correspondence data, power of attorney + information, and a list of listed attorneys. Args: - application_number: The application number + application_number (str): The USPTO application number for which + attorney data is being requested (e.g., "16123456"). Returns: - PatentDataResponse object containing the attorney data + Optional[RecordAttorney]: A `RecordAttorney` object with details + about the attorney(s) of record if the application is found + and such data exists. Returns None if the application cannot + be found or if no attorney data is available in the response. """ - endpoint = self.ENDPOINTS["application_attorney"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_attorney"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return wrapper.record_attorney if wrapper else None
[docs] - def get_application_continuity(self, application_number: str) -> PatentDataResponse: - """ - Get continuity data for an application. + def get_application_continuity( + self, application_number: str + ) -> Optional[ApplicationContinuityData]: + """Retrieves continuity data (parent/child applications) for a specific application. + + This method fetches the lineage of the specified application, returning an + `ApplicationContinuityData` object. This object consolidates lists of + `ParentContinuity` (applications to which the current one claims priority) + and `ChildContinuity` (applications claiming priority to the current one) + objects, each detailing the related application's key identifiers and status. Args: - application_number: The application number + application_number (str): The USPTO application number for which + continuity data is being requested (e.g., "16123456"). Returns: - PatentDataResponse object containing the continuity data + Optional[ApplicationContinuityData]: An `ApplicationContinuityData` + object containing lists of parent and child continuity relationships. + Returns None if the application cannot be found or if the underlying + data to construct continuity is not available. The lists within + the returned object may be empty if no parent or child continuity + links exist. """ - endpoint = self.ENDPOINTS["application_continuity"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_continuity"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return ApplicationContinuityData.from_wrapper(wrapper) if wrapper else None
[docs] def get_application_foreign_priority( self, application_number: str - ) -> PatentDataResponse: - """ - Get foreign priority data for an application. + ) -> Optional[List[ForeignPriority]]: + """Retrieves a list of foreign priority claims for a specific application. + + This method fetches the `foreign_priority_bag` from the patent file + wrapper. This bag contains a list of `ForeignPriority` objects, each + representing a claim to a foreign patent application's priority date. + Details include the IP office name, filing date, and application number + of the foreign priority application. Args: - application_number: The application number + application_number (str): The USPTO application number for which + foreign priority data is being requested (e.g., "16123456"). Returns: - PatentDataResponse object containing the foreign priority data + Optional[List[ForeignPriority]]: A list of `ForeignPriority` objects, + each detailing a claimed foreign priority. Returns None if the + application cannot be found or if no foreign priority data is + available. An empty list may be returned if the application + is found but has no foreign priority claims. """ - endpoint = self.ENDPOINTS["application_foreign_priority"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_foreign_priority"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return wrapper.foreign_priority_bag if wrapper else None
[docs] def get_application_transactions( self, application_number: str - ) -> PatentDataResponse: - """ - Get transaction data for an application. + ) -> Optional[List[EventData]]: + """Retrieves the transaction history (events) for a specific application. + + This method fetches the `event_data_bag` from the patent file wrapper. + This bag contains a list of `EventData` objects, each representing a + single recorded event in the prosecution history of the patent application. + Events include details like an event code, a textual description, and + the date the event was recorded. Args: - application_number: The application number + application_number (str): The USPTO application number for which + transaction history is being requested (e.g., "16123456"). Returns: - PatentDataResponse object containing the transaction data + Optional[List[EventData]]: A list of `EventData` objects, each + detailing a transaction or event in the application's history. + Returns None if the application cannot be found or if no + transaction data is available. An empty list may be returned if + the application is found but has no recorded transaction events. """ - endpoint = self.ENDPOINTS["application_transactions"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_transactions"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return wrapper.event_data_bag if wrapper else None
[docs] - def get_application_documents(self, application_number: str) -> PatentDataResponse: - """ - Get document details for an application. + def get_application_documents( + self, + application_number: str, + document_codes: Optional[List[str]] = None, + official_date_from: Optional[str] = None, + official_date_to: Optional[str] = None, + ) -> DocumentBag: + """Retrieves metadata for documents associated with a specific application. + + This method fetches a collection of document metadata related to the given + patent application. The result is a `DocumentBag` object, which is an + iterable collection of `Document` instances. Each `Document` object + contains metadata such as its identifier, official date, document code + and description, direction (incoming/outgoing), and available download + formats. Args: - application_number: The application number + application_number (str): The USPTO application number for which + document metadata is being requested (e.g., "16123456"). + document_codes (Optional[List[str]]): Filter by specific document type + codes. If provided, only documents with these codes will be returned. + Examples: ['ABST', 'CLM', 'SPEC', 'DRWD']. + official_date_from (Optional[str]): Filter documents from this date + (inclusive). Date format: YYYY-MM-DD (e.g., "2020-01-15"). + official_date_to (Optional[str]): Filter documents to this date + (inclusive). Date format: YYYY-MM-DD (e.g., "2023-12-31"). Returns: - PatentDataResponse object containing document details + DocumentBag: A `DocumentBag` object containing metadata for all + publicly available documents associated with the application + that match the provided filters. The bag will be empty if no + documents are found or if the API response indicates no documents. + It does not return None for "not found" cases; an empty collection + is returned instead. """ - endpoint = self.ENDPOINTS["application_documents"].format( - application_number=application_number + endpoint = self.ENDPOINTS["get_application_documents"].format( + application_number=self.sanitize_application_number(application_number) ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, + + params = {} + if document_codes: + params["documentCodes"] = ",".join(document_codes) + if official_date_from: + params["officialDateFrom"] = official_date_from + if official_date_to: + params["officialDateTo"] = official_date_to + + result_dict = self._make_request( + method="GET", endpoint=endpoint, params=params if params else None ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ assert isinstance(result_dict, dict) + return DocumentBag.from_dict(result_dict)
[docs] def get_application_associated_documents( self, application_number: str - ) -> PatentDataResponse: - """ - Get associated documents metadata for an application. + ) -> Optional[PrintedPublication]: + """Retrieves metadata for Pre-Grant Publication and Grant documents. - Args: - application_number: The application number - - Returns: - PatentDataResponse object containing the associated documents metadata - """ - endpoint = self.ENDPOINTS["application_associated_documents"].format( - application_number=application_number - ) - result = self._make_request( - method="GET", - endpoint=endpoint, - response_class=PatentDataResponse, - ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
- - -
-[docs] - def download_application_document( - self, application_number: str, document_id: str, destination: str - ) -> str: - """ - Download a document for a patent application. + This method fetches metadata specifically for published documents associated + with the patent application, such as Pre-Grant Publications (PGPUBs) + and granted patent documents. It does not retrieve the prosecution + history documents (see `get_application_documents` for that). + The result is a `PrintedPublication` object, which holds + `PrintedMetaData` including file URIs and names. Download with download_archive. Args: - application_number: The application number - document_id: The document identifier - destination: Directory where the file should be saved + application_number (str): The USPTO application number for which + associated PGPUB/Grant document metadata is being requested + (e.g., "16123456"). Returns: - Path to the downloaded file + Optional[PrintedPublication]: A `PrintedPublication` object + containing `PrintedMetaData` for the Pre-Grant Publication + and/or the Grant document, if available. Returns None if the + application cannot be found or if no such associated document + metadata is available. The fields within the returned object + (`pgpub_document_meta_data`, `grant_document_meta_data`) + may themselves be None if a particular type of document + (e.g., PGPUB) does not exist for the application. """ - # This endpoint is at a different base URL level - base_url_parts = self.base_url.split("/patent") - base_url_root = base_url_parts[0] - endpoint = self.ENDPOINTS["download_document"].format( - application_number=application_number, document_id=document_id + endpoint = self.ENDPOINTS["get_application_associated_documents"].format( + application_number=self.sanitize_application_number(application_number) ) - - # Get the response with streaming enabled - response = self._make_request( - method="GET", endpoint=endpoint, stream=True, custom_base_url=base_url_root + response_data = self._make_request( + method="GET", endpoint=endpoint, response_class=PatentDataResponse ) + assert isinstance(response_data, PatentDataResponse) + wrapper = self._get_wrapper_from_response(response_data, application_number) + return PrintedPublication.from_wrapper(wrapper) if wrapper else None
- # Ensure we have a Response object with iter_content - import requests - if not isinstance(response, requests.Response): - raise TypeError("Expected a Response object for streaming download") +
+[docs] + def paginate_applications(self, **kwargs: Any) -> Iterator[PatentFileWrapper]: + """Provides an iterator to easily paginate through patent application search results. - if not os.path.exists(destination): - os.makedirs(destination) + This method simplifies the process of fetching all patent applications + that match a given search query by automatically handling pagination. + It internally calls the `search_applications` method for GET requests, + batching results and yielding them one by one. - # Get filename from Content-Disposition header if available - content_disposition = response.headers.get("Content-Disposition") - if content_disposition and "filename=" in content_disposition: - filename_match = re.search(r'filename="(.+?)"', content_disposition) - if filename_match: - filename = filename_match.group(1) - else: - filename = document_id - else: - filename = document_id - - file_path = os.path.join(destination, filename) - - with open(file=file_path, mode="wb") as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - - return file_path
- - -
-[docs] - def paginate_patents(self, **kwargs: Any) -> Iterator[PatentFileWrapper]: - """ - Paginate through all patents matching the search criteria. + All keyword arguments provided to this method (`**kwargs`) are passed + directly to the `search_applications` method to define the search + criteria. See the `search_applications` method for more details + on available parameters. + The `offset` and `limit` parameters are managed by the pagination logic; + setting them directly in `kwargs` might lead to unexpected behavior. Args: - **kwargs: Keyword arguments to pass to search_patents + **kwargs (Any): Keyword arguments to be passed to the + `search_applications` method for constructing the search query. + These define the criteria for the patent applications to be + retrieved. Do not include `post_body`. + + Returns: + Iterator[PatentFileWrapper]: An iterator that yields `PatentFileWrapper` + objects, allowing iteration over all matching patent applications + across multiple pages of results. - Yields: - PatentFileWrapper objects + Raises: + ValueError: If `post_body` is included in `kwargs`, as this + method only supports GET request parameters for pagination. """ + if "post_body" in kwargs: + raise ValueError( + "paginate_applications uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + return self.paginate_results( - method_name="get_patent_applications", + method_name="search_applications", response_container_attr="patent_file_wrapper_data_bag", **kwargs, )
-
-[docs] - def get_patent_status_codes( +
+[docs] + def get_status_codes( self, params: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """ - Get patent application status codes and descriptions. + ) -> StatusCodeSearchResponse: + """Retrieves USPTO patent application status codes and their descriptions. + + This method fetches a list of defined USPTO patent application status codes + (e.g., codes for "Pending," "Abandoned," "Issued") using a GET request. + The request can be customized with query parameters to filter or paginate + the results if supported by the API endpoint. Args: - params: Optional query parameters including: - - q: Search query string - - offset: Position in dataset to start from - - limit: Number of results to return + params (Optional[Dict[str, Any]], optional): A dictionary of query + parameters to be sent with the GET request. These parameters can + be used to filter or control the output of the status codes + list. Defaults to None, which typically retrieves all available + status codes or the API's default set. Returns: - Dictionary containing status codes and descriptions + StatusCodeSearchResponse: An object containing a count of matching + status codes, a `StatusCodeCollection` of the `StatusCode` + objects (code and description), and a request identifier. """ - result = self._make_request( + result_dict = self._make_request( method="GET", endpoint=self.ENDPOINTS["status_codes"], params=params ) - assert isinstance(result, dict) - return result
+ assert isinstance(result_dict, dict) + return StatusCodeSearchResponse.from_dict(result_dict)
-
-[docs] - def search_patent_status_codes_post( +
+[docs] + def search_status_codes( self, search_request: Dict[str, Any] - ) -> Dict[str, Any]: - """ - Search patent status codes using POST method with JSON payload. + ) -> StatusCodeSearchResponse: + """Searches USPTO patent application status codes using POST criteria. + + Performs targeted searches for USPTO patent application status codes + (e.g., for "Pending," "Abandoned," "Issued") by sending a POST request + with a JSON body containing the `search_request` criteria. This method + is suited for more complex queries than the GET-based `get_status_codes`. Args: - search_request: JSON payload with search parameters + search_request (Dict[str, Any]): A dictionary with search criteria, + sent as the JSON POST body. The structure must conform to USPTO + API requirements for this endpoint (e.g., for searching by code + or description keywords). Returns: - Dictionary containing status codes and descriptions + StatusCodeSearchResponse: An object containing a count of matching + status codes, a `StatusCodeCollection` of the `StatusCode` + objects (code and description), and a request identifier. """ - result = self._make_request( + result_dict = self._make_request( method="POST", endpoint=self.ENDPOINTS["status_codes"], json_data=search_request, ) - assert isinstance(result, dict) - return result
+ assert isinstance(result_dict, dict) + return StatusCodeSearchResponse.from_dict(result_dict)
-
-[docs] - def search_patents( +
+[docs] + def download_document( + self, + document_format: DocumentFormat, + file_name: Optional[str] = None, + destination_path: Optional[str] = None, + overwrite: bool = False, + stream: bool = True, + ) -> str: + """Downloads a document in the specified format. + + Args: + document_format: DocumentFormat object containing download URL and metadata + file_name: Optional filename. If not provided, extracted from URL + destination_path: Optional path - can be a directory OR a complete file path + overwrite: Whether to overwrite existing files. Default False + stream: Whether to stream the download. Default True for large files + + Returns: + str: Path to the downloaded file + + Raises: + ValueError: If document_format has no download URL + FileExistsError: If file exists and overwrite=False + """ + # Validate we have a download URL + if document_format.download_url is None: + raise ValueError("DocumentFormat must have a download_url") + + # Get filename - either provided or extract from URL + if file_name is None: + url_filename = document_format.download_url.split("/")[-1] + if "." in url_filename: + file_name = url_filename + else: + extension = ( + document_format.mime_type_identifier.lower() + if document_format.mime_type_identifier + else "pdf" + ) + file_name = f"document.{extension}" + + # Determine final file path + if destination_path is None: + final_file_path = Path(file_name) + else: + # destination_path is ALWAYS treated as a directory path + destination_dir = Path(destination_path) + destination_dir.mkdir(parents=True, exist_ok=True) + final_file_path = destination_dir / file_name + + # Download the file (overwrite check handled by base class) + return self._download_file( + url=document_format.download_url, + file_path=final_file_path.as_posix(), + overwrite=overwrite, + )
+ + +
+[docs] + def get_IFW_metadata( self, - query: Optional[str] = None, application_number: Optional[str] = None, + publication_number: Optional[str] = None, patent_number: Optional[str] = None, - inventor_name: Optional[str] = None, - applicant_name: Optional[str] = None, - assignee_name: Optional[str] = None, - filing_date_from: Optional[str] = None, - filing_date_to: Optional[str] = None, - grant_date_from: Optional[str] = None, - grant_date_to: Optional[str] = None, - classification: Optional[str] = None, - limit: Optional[int] = 25, - offset: Optional[int] = 0, - ) -> PatentDataResponse: - """ - Search for patents with various filters. + PCT_app_number: Optional[str] = None, + PCT_pub_number: Optional[str] = None, + ) -> Optional[PatentFileWrapper]: + """Retrieves complete patent file wrapper data using common identifiers. + + This utility fetches the `PatentFileWrapper`, which contains comprehensive + IFW metadata, application details, and more. Provide only one + identifier if possible. If multiple are given, they are processed in the + order listed in the arguments, and the first successful match is returned. Args: - query: Search text in all fields - application_number: Filter by application number - patent_number: Filter by patent number - inventor_name: Filter by inventor name - applicant_name: Filter by applicant name - assignee_name: Filter by assignee name - filing_date_from: Filter by filing date from (YYYY-MM-DD) - filing_date_to: Filter by filing date to (YYYY-MM-DD) - grant_date_from: Filter by grant date from (YYYY-MM-DD) - grant_date_to: Filter by grant date to (YYYY-MM-DD) - classification: Filter by CPC classification - limit: Number of results to return (default 25) - offset: Position in dataset to start from (default 0) + application_number (Optional[str], optional): USPTO application number + (e.g., "16123456"). Checked first (direct lookup). + patent_number (Optional[str], optional): USPTO patent number + (e.g., "11000000"). Checked second (uses search). + publication_number (Optional[str], optional): USPTO pre-grant + publication number (e.g., "20230123456"). Checked third (uses search). + PCT_app_number (Optional[str], optional): PCT application number. + Checked fourth (direct lookup, treated as USPTO app#). + PCT_pub_number (Optional[str], optional): PCT publication number + (e.g., "2023012345"). Checked fifth (uses search). Returns: - PatentDataResponse object containing matching patents + Optional[PatentFileWrapper]: A `PatentFileWrapper` object with + comprehensive data if found using one of the identifiers, + otherwise None. """ - # Build the query string - q_parts = [] if application_number: - q_parts.append(f"applicationNumberText:{application_number}") - + return self.get_application_by_number(application_number=application_number) if patent_number: - q_parts.append(f"applicationMetaData.patentNumber:{patent_number}") - - if inventor_name: - q_parts.append( - f"applicationMetaData.inventorBag.inventorNameText:{inventor_name}" + pdr = self.search_applications(patent_number_q=patent_number, limit=1) + if pdr.patent_file_wrapper_data_bag: + return pdr.patent_file_wrapper_data_bag[0] + if publication_number: + pdr = self.search_applications( + earliestPublicationNumber_q=publication_number, limit=1 ) - - if applicant_name: - q_parts.append(f"applicationMetaData.firstApplicantName:{applicant_name}") - - if assignee_name: - q_parts.append( - f"assignmentBag.assigneeBag.assigneeNameText:{assignee_name}" + if pdr.patent_file_wrapper_data_bag: + return pdr.patent_file_wrapper_data_bag[0] + if PCT_app_number: + return self.get_application_by_number(application_number=PCT_app_number) + if PCT_pub_number: + pdr = self.search_applications( + pctPublicationNumber_q=PCT_pub_number, limit=1 ) + if pdr.patent_file_wrapper_data_bag: + return pdr.patent_file_wrapper_data_bag[0] + return None
- if classification: - q_parts.append(f"applicationMetaData.cpcClassificationBag:{classification}") - # Add date range filters - range_filters = [] +
+[docs] + def download_archive( + self, + printed_metadata: PrintedMetaData, + file_name: Optional[str] = None, + destination_path: Optional[str] = None, + overwrite: bool = False, + ) -> str: + """Downloads Printed Metadata (XML data). These are XML files of the patent as printed. - if filing_date_from and filing_date_to: - range_filters.append( - f"applicationMetaData.filingDate:[{filing_date_from} TO {filing_date_to}]" - ) - elif filing_date_from: - range_filters.append(f"applicationMetaData.filingDate:>={filing_date_from}") - elif filing_date_to: - range_filters.append(f"applicationMetaData.filingDate:<={filing_date_to}") - - if grant_date_from and grant_date_to: - range_filters.append( - f"applicationMetaData.grantDate:[{grant_date_from} TO {grant_date_to}]" - ) - elif grant_date_from: - range_filters.append(f"applicationMetaData.grantDate:>={grant_date_from}") - elif grant_date_to: - range_filters.append(f"applicationMetaData.grantDate:<={grant_date_to}") + Note: + See also `download_publication()` for a clearer method name with identical functionality. + + Args: + printed_metadata: ArchiveMetaData object containing download URL and metadata + file_name: Optional filename. If not provided, uses xml_file_name from metadata + destination_path: Optional directory path to save the file + overwrite: Whether to overwrite existing files. Default False - # Combine all query parts - if query: - q_parts.append(query) + Returns: + str: Path to the downloaded file - q_parts.extend(range_filters) + Raises: + ValueError: If printed_metadata has no download URL + FileExistsError: If file exists and overwrite=False + """ + # Validate we have a download URL + if printed_metadata.file_location_uri is None: + raise ValueError("PrintedMetaData must have a file_location_uri") + + # Get filename - either provided or from metadata + if file_name is None: + if printed_metadata.xml_file_name: + file_name = printed_metadata.xml_file_name + else: + # Fallback: extract from URL + url_filename = printed_metadata.file_location_uri.split("/")[-1] + if "." in url_filename: + file_name = url_filename + else: + # Last resort: use product identifier + product_id = printed_metadata.product_identifier or "patent_text" + file_name = f"{product_id}.xml" + + # Determine final file path + if destination_path is None: + final_file_path = Path(file_name) + else: + destination_dir = Path(destination_path) + destination_dir.mkdir(parents=True, exist_ok=True) + final_file_path = destination_dir / file_name + + # Check for existing file + if final_file_path.exists() and overwrite is False: + raise FileExistsError( + f"File already exists: {final_file_path}. Use overwrite=True to replace." + ) - # Build the final query string - q = " AND ".join(q_parts) if q_parts else None + # Download the Printed Metadata + return self._download_file( + url=printed_metadata.file_location_uri, file_path=final_file_path.as_posix() + )
- # Set up parameters - params = {} - if offset is not None: - params["offset"] = str(offset) +
+[docs] + def download_publication( + self, + printed_metadata: PrintedMetaData, + file_name: Optional[str] = None, + destination_path: Optional[str] = None, + overwrite: bool = False, + ) -> str: + """Download a publication XML file (grant or pre-grant publication). - if limit is not None: - params["limit"] = str(limit) + This method downloads publication XML files from PrintedMetaData objects, + such as grant documents or pre-grant publications (pgpub). The filename + is automatically extracted from the metadata if not provided. - if q: - params["q"] = q + Args: + printed_metadata: PrintedMetaData object containing the publication + download URL and filename information. Typically obtained from + `get_application_associated_documents()` or from PatentFileWrapper's + `grant_document_meta_data` or `pg_publication_document_meta_data`. + file_name: Optional custom filename. If not provided, uses the + `xml_file_name` from the metadata (e.g., "18915708_12307527.xml"). + destination_path: Optional directory path where the file should be saved. + If not provided, saves to the current directory. The directory will + be created if it doesn't exist. + overwrite: Whether to overwrite an existing file at the destination. + Default is False, which raises FileExistsError if file exists. - # Use the applications_search endpoint from ENDPOINTS - result = self._make_request( - method="GET", - endpoint=self.ENDPOINTS["applications_search"], - params=params, - response_class=PatentDataResponse, - ) - # Since we specified response_class=BulkDataResponse, the result should be a BulkDataResponse - assert isinstance(result, PatentDataResponse) - return result
+ Returns: + str: Absolute path to the downloaded publication file. + + Raises: + ValueError: If printed_metadata has no file_location_uri (download URL). + FileExistsError: If the file already exists and overwrite=False. + + Examples: + Download grant XML to a specific directory (auto-filename): + + >>> response = client.get_application_by_number("18/915,708") + >>> ifw = response + >>> grant_metadata = ifw.grant_document_meta_data + >>> path = client.download_publication(grant_metadata, destination_path="./downloads") + >>> print(path) + './downloads/18915708_12307527.xml' + + Download pgpub XML with custom filename: + + >>> pgpub_metadata = ifw.pg_publication_document_meta_data + >>> path = client.download_publication( + ... pgpub_metadata, + ... file_name="my_publication.xml", + ... destination_path="./downloads" + ... ) + >>> print(path) + './downloads/my_publication.xml' + + Download to current directory: + + >>> path = client.download_publication(grant_metadata) + >>> print(path) + './18915708_12307527.xml' + """ + return self.download_archive( + printed_metadata=printed_metadata, + file_name=file_name, + destination_path=destination_path, + overwrite=overwrite, + )
@@ -791,7 +1304,7 @@

Source code for pyUSPTO.clients.patent_data

   
-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/clients/petition_decisions.html b/docs/build/html/_modules/pyUSPTO/clients/petition_decisions.html new file mode 100644 index 0000000..87aaefb --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/clients/petition_decisions.html @@ -0,0 +1,737 @@ + + + + + + + + pyUSPTO.clients.petition_decisions — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyUSPTO.clients.petition_decisions

+"""
+clients.petition_decisions - Client for USPTO Final Petition Decisions API
+
+This module provides a client for interacting with the USPTO Final Petition
+Decisions API. It allows you to search for and retrieve final agency petition
+decisions in publicly available patent applications and patents filed in 2001 or later.
+"""
+
+import warnings
+from pathlib import Path
+from typing import Any, Dict, Iterator, List, Optional, Union
+
+import requests
+
+from pyUSPTO.clients.base import BaseUSPTOClient
+from pyUSPTO.config import USPTOConfig
+from pyUSPTO.models.petition_decisions import (
+    DocumentDownloadOption,
+    PetitionDecision,
+    PetitionDecisionDownloadResponse,
+    PetitionDecisionResponse,
+)
+from pyUSPTO.warnings import USPTODataMismatchWarning
+
+
+
+[docs] +class FinalPetitionDecisionsClient(BaseUSPTOClient[PetitionDecisionResponse]): + """Client for interacting with the USPTO Final Petition Decisions API. + + This client provides methods to search for petition decisions, retrieve specific + decisions by ID, download decision data, and download associated documents. + + Final petition decisions data are incrementally added to the USPTO Open Data Portal + on a monthly basis starting with data from 2022 and later. + """ + + ENDPOINTS = { + "search_decisions": "api/v1/petition/decisions/search", + "get_decision_by_id": "api/v1/petition/decisions/{petitionDecisionRecordIdentifier}", + "download_decisions": "api/v1/petition/decisions/search/download", + } + +
+[docs] + def __init__( + self, + api_key: Optional[str] = None, + base_url: Optional[str] = None, + config: Optional[USPTOConfig] = None, + ): + """Initialize the FinalPetitionDecisionsClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url + or self.config.petition_decisions_base_url + or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + )
+ + + def _get_decision_from_response( + self, + response_data: PetitionDecisionResponse, + petition_decision_record_identifier_for_validation: Optional[str] = None, + ) -> Optional[PetitionDecision]: + """Helper to extract a single PetitionDecision from response. + + Args: + response_data: The API response containing petition decisions. + petition_decision_record_identifier_for_validation: Optional identifier + to validate against the returned decision. + + Returns: + Optional[PetitionDecision]: The first petition decision if found, None otherwise. + """ + if not response_data or not response_data.petition_decision_data_bag: + return None + + decision = response_data.petition_decision_data_bag[0] + + if ( + petition_decision_record_identifier_for_validation + and decision.petition_decision_record_identifier + != petition_decision_record_identifier_for_validation + ): + warnings.warn( + f"API returned decision identifier '{decision.petition_decision_record_identifier}' " + f"but requested '{petition_decision_record_identifier_for_validation}'. " + f"This may indicate an API data inconsistency.", + USPTODataMismatchWarning, + stacklevel=2, + ) + return decision + +
+[docs] + def search_decisions( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + # Convenience query parameters + application_number_q: Optional[str] = None, + patent_number_q: Optional[str] = None, + inventor_name_q: Optional[str] = None, + applicant_name_q: Optional[str] = None, + invention_title_q: Optional[str] = None, + decision_type_code_q: Optional[str] = None, + decision_date_from_q: Optional[str] = None, + decision_date_to_q: Optional[str] = None, + petition_mail_date_from_q: Optional[str] = None, + petition_mail_date_to_q: Optional[str] = None, + technology_center_q: Optional[str] = None, + final_deciding_office_name_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PetitionDecisionResponse: + """Searches for final petition decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + application_number_q: Filter by application number. + patent_number_q: Filter by patent number. + inventor_name_q: Filter by inventor name. + applicant_name_q: Filter by applicant name. + invention_title_q: Filter by invention title. + decision_type_code_q: Filter by decision type code. + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + petition_mail_date_from_q: Filter petition mail dates from (YYYY-MM-DD). + petition_mail_date_to_q: Filter petition mail dates to (YYYY-MM-DD). + technology_center_q: Filter by technology center. + final_deciding_office_name_q: Filter by deciding office name. + additional_query_params: Additional custom query parameters. + + Returns: + PetitionDecisionResponse: Response containing matching petition decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="applicationNumberText:17765301") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... applicant_name_q="ACME Corp", + ... decision_date_from_q="2022-01-01", + ... limit=50 + ... ) + + # Search with POST body + >>> response = client.search_decisions( + ... post_body={"q": "technologyCenter:1700", "limit": 100} + ... ) + """ + endpoint = self.ENDPOINTS["search_decisions"] + + if post_body is not None: + # POST request path + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + response_class=PetitionDecisionResponse, + ) + else: + # GET request path + params: Dict[str, Any] = {} + final_q = query + + # Build query from convenience parameters + if final_q is None: + q_parts = [] + if application_number_q: + q_parts.append(f"applicationNumberText:{application_number_q}") + if patent_number_q: + q_parts.append(f"patentNumber:{patent_number_q}") + if inventor_name_q: + q_parts.append(f"inventorBag:{inventor_name_q}") + if applicant_name_q: + q_parts.append(f"firstApplicantName:{applicant_name_q}") + if invention_title_q: + q_parts.append(f"inventionTitle:{invention_title_q}") + if decision_type_code_q: + q_parts.append(f"decisionTypeCode:{decision_type_code_q}") + if technology_center_q: + q_parts.append(f"technologyCenter:{technology_center_q}") + if final_deciding_office_name_q: + q_parts.append( + f"finalDecidingOfficeName:{final_deciding_office_name_q}" + ) + + # Handle decision date range + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"decisionDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append(f"decisionDate:>={decision_date_from_q}") + elif decision_date_to_q: + q_parts.append(f"decisionDate:<={decision_date_to_q}") + + # Handle petition mail date range + if petition_mail_date_from_q and petition_mail_date_to_q: + q_parts.append( + f"petitionMailDate:[{petition_mail_date_from_q} TO {petition_mail_date_to_q}]" + ) + elif petition_mail_date_from_q: + q_parts.append(f"petitionMailDate:>={petition_mail_date_from_q}") + elif petition_mail_date_to_q: + q_parts.append(f"petitionMailDate:<={petition_mail_date_to_q}") + + if q_parts: + final_q = " AND ".join(q_parts) + + # Add parameters + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets is not None: + params["facets"] = facets + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=PetitionDecisionResponse, + ) + + assert isinstance(result, PetitionDecisionResponse) + return result
+ + +
+[docs] + def get_decision_by_id( + self, + petition_decision_record_identifier: str, + include_documents: Optional[bool] = None, + ) -> Optional[PetitionDecision]: + """Retrieves a specific petition decision by its record identifier. + + Args: + petition_decision_record_identifier: The unique identifier for the petition + decision record (UUID format). + include_documents: Whether to include associated documents in the response. + If True, adds includeDocuments=true query parameter. + + Returns: + Optional[PetitionDecision]: The petition decision if found, None otherwise. + + Examples: + # Get decision without documents + >>> decision = client.get_decision_by_id( + ... "9f1a4a2b-eee1-58ec-a3aa-167c4075aed4" + ... ) + + # Get decision with documents + >>> decision = client.get_decision_by_id( + ... "34044333-4b40-515f-a684-2515325c57c5", + ... include_documents=True + ... ) + """ + endpoint = self.ENDPOINTS["get_decision_by_id"].format( + petitionDecisionRecordIdentifier=petition_decision_record_identifier + ) + + params = {} + if include_documents is not None: + params["includeDocuments"] = str(include_documents).lower() + + response_data = self._make_request( + method="GET", + endpoint=endpoint, + params=params if params else None, + response_class=PetitionDecisionResponse, + ) + assert isinstance(response_data, PetitionDecisionResponse) + return self._get_decision_from_response( + response_data=response_data, + petition_decision_record_identifier_for_validation=petition_decision_record_identifier, + )
+ + +
+[docs] + def download_decisions( + self, + format: str = "json", + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + # Convenience query parameters + application_number_q: Optional[str] = None, + patent_number_q: Optional[str] = None, + inventor_name_q: Optional[str] = None, + applicant_name_q: Optional[str] = None, + decision_date_from_q: Optional[str] = None, + decision_date_to_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + # File save options (for CSV format) + file_name: Optional[str] = None, + destination_path: Optional[str] = None, + overwrite: bool = False, + ) -> Union[PetitionDecisionDownloadResponse, requests.Response, str]: + """Downloads petition decisions data in the specified format. + + This endpoint is designed for bulk downloads of petition decisions data. + It supports JSON and CSV formats. + + Args: + format: Download format, either "json" or "csv". Defaults to "json". + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + application_number_q: Filter by application number. + patent_number_q: Filter by patent number. + inventor_name_q: Filter by inventor name. + applicant_name_q: Filter by applicant name. + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + additional_query_params: Additional custom query parameters. + file_name: Optional filename for CSV downloads. Defaults to "petition_decisions.csv". + destination_path: Optional directory path to save CSV file. If None, returns Response. + overwrite: Whether to overwrite existing files. Default False. + + Returns: + Union[PetitionDecisionDownloadResponse, requests.Response, str]: + - If format="json": Returns PetitionDecisionDownloadResponse + - If format="csv" and destination_path is None: Returns streaming Response + - If format="csv" and destination_path is set: Returns str path to saved file + + Raises: + FileExistsError: If CSV file exists and overwrite=False + + Examples: + # Download as JSON + >>> download = client.download_decisions( + ... format="json", + ... technology_center_q="1700", + ... limit=1000 + ... ) + >>> for decision in download.petition_decision_data: + ... print(decision.application_number_text) + + # Download CSV and save to file + >>> file_path = client.download_decisions( + ... format="csv", + ... decision_date_from_q="2023-01-01", + ... destination_path="./downloads" + ... ) + >>> print(f"Saved to: {file_path}") + + # Download CSV as streaming response (advanced usage) + >>> response = client.download_decisions(format="csv") + >>> with open("decisions.csv", "wb") as f: + ... for chunk in response.iter_content(chunk_size=8192): + ... f.write(chunk) + """ + endpoint = self.ENDPOINTS["download_decisions"] + + params: Dict[str, Any] = {"format": format} + final_q = query + + # Build query from convenience parameters + if final_q is None: + q_parts = [] + if application_number_q: + q_parts.append(f"applicationNumberText:{application_number_q}") + if patent_number_q: + q_parts.append(f"patentNumber:{patent_number_q}") + if inventor_name_q: + q_parts.append(f"inventorBag:{inventor_name_q}") + if applicant_name_q: + q_parts.append(f"firstApplicantName:{applicant_name_q}") + + # Handle decision date range + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"decisionDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append(f"decisionDate:>={decision_date_from_q}") + elif decision_date_to_q: + q_parts.append(f"decisionDate:<={decision_date_to_q}") + + if q_parts: + final_q = " AND ".join(q_parts) + + # Add parameters + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + + if format.lower() == "json": + # For JSON, parse the response + result_dict = self._make_request( + method="GET", endpoint=endpoint, params=params + ) + assert isinstance(result_dict, dict) + return PetitionDecisionDownloadResponse.from_dict(result_dict) + else: + # For CSV or other formats, get streaming response + result = self._make_request( + method="GET", endpoint=endpoint, params=params, stream=True + ) + assert isinstance(result, requests.Response) + + if destination_path is not None: + # Save to file using the base class helper + from pathlib import Path + + # Determine filename + if file_name is None: + file_name = "petition_decisions.csv" + + # Build full file path + destination_dir = Path(destination_path) + destination_dir.mkdir(parents=True, exist_ok=True) + final_file_path = destination_dir / file_name + + # Save streaming response to file (overwrite check handled by base class) + return self._save_response_to_file( + response=result, file_path=str(final_file_path), overwrite=overwrite + ) + else: + # Return streaming response for manual handling + return result
+ + +
+[docs] + def paginate_decisions(self, **kwargs: Any) -> Iterator[PetitionDecision]: + """Provides an iterator to paginate through petition decision search results. + + This method simplifies fetching all petition decisions matching a search query + by automatically handling pagination. It internally calls the search_decisions + method for GET requests, batching results and yielding them one by one. + + All keyword arguments are passed directly to search_decisions to define the + search criteria. The offset and limit parameters are managed by the pagination + logic; setting them directly in kwargs might lead to unexpected behavior. + + Args: + **kwargs: Keyword arguments passed to search_decisions for constructing + the search query. Do not include post_body. + + Returns: + Iterator[PetitionDecision]: An iterator yielding PetitionDecision objects, + allowing iteration over all matching petition decisions across multiple + pages of results. + + Raises: + ValueError: If post_body is included in kwargs, as this method only + supports GET request parameters for pagination. + + Examples: + # Paginate through all decisions for a technology center + >>> for decision in client.paginate_decisions(technology_center_q="1700"): + ... print(f"{decision.application_number_text}: {decision.decision_type_code}") + + # Paginate with date range + >>> for decision in client.paginate_decisions( + ... decision_date_from_q="2023-01-01", + ... decision_date_to_q="2023-12-31" + ... ): + ... process_decision(decision) + """ + if "post_body" in kwargs: + raise ValueError( + "paginate_decisions uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_decisions", + response_container_attr="petition_decision_data_bag", + **kwargs, + )
+ + +
+[docs] + def download_petition_document( + self, + download_option: DocumentDownloadOption, + file_name: Optional[str] = None, + destination_path: Optional[str] = None, + overwrite: bool = False, + ) -> str: + """Downloads a petition decision document in the specified format. + + Args: + download_option: DocumentDownloadOption object containing the download + URL and metadata. + file_name: Optional filename for the downloaded file. If not provided, + it will be extracted from the URL or generated based on the MIME type. + destination_path: Optional directory path where the file should be saved. + If not provided, saves to the current directory. + overwrite: Whether to overwrite an existing file. Defaults to False. + + Returns: + str: The absolute path to the downloaded file. + + Raises: + ValueError: If download_option has no download URL. + FileExistsError: If the file exists and overwrite=False. + + Examples: + # Download first document from a decision + >>> decision = client.get_decision_by_id( + ... "34044333-4b40-515f-a684-2515325c57c5", + ... include_documents=True + ... ) + >>> if decision.document_bag: + ... doc = decision.document_bag[0] + ... if doc.download_option_bag: + ... # Download PDF version + ... pdf_option = next( + ... opt for opt in doc.download_option_bag + ... if opt.mime_type_identifier == "PDF" + ... ) + ... path = client.download_petition_document( + ... pdf_option, + ... destination_path="./downloads" + ... ) + ... print(f"Downloaded to: {path}") + """ + if download_option.download_url is None: + raise ValueError("DocumentDownloadOption must have a download_url") + + # Determine filename + if file_name is None: + url_filename = download_option.download_url.split("/")[-1] + if "." in url_filename: + file_name = url_filename + else: + # Generate filename from MIME type + extension = ( + download_option.mime_type_identifier.lower() + if download_option.mime_type_identifier + else "pdf" + ) + file_name = f"document.{extension}" + + # Determine final file path + if destination_path is None: + final_file_path = Path(file_name) + else: + destination_dir = Path(destination_path) + destination_dir.mkdir(parents=True, exist_ok=True) + final_file_path = destination_dir / file_name + + # Check for existing file + if final_file_path.exists() and overwrite is False: + raise FileExistsError( + f"File already exists: {final_file_path}. Use overwrite=True to replace." + ) + + # Download the file + return self._download_file( + url=download_option.download_url, file_path=final_file_path.as_posix() + )
+
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/clients/ptab_appeals.html b/docs/build/html/_modules/pyUSPTO/clients/ptab_appeals.html new file mode 100644 index 0000000..ac4d6fc --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/clients/ptab_appeals.html @@ -0,0 +1,363 @@ + + + + + + + + pyUSPTO.clients.ptab_appeals — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyUSPTO.clients.ptab_appeals

+"""
+clients.ptab_appeals - Client for USPTO PTAB Appeals API
+
+This module provides a client for interacting with the USPTO PTAB (Patent Trial
+and Appeal Board) Appeals API. It allows you to search for ex parte appeal decisions.
+"""
+
+from typing import Any, Dict, Iterator, Optional
+
+from pyUSPTO.clients.base import BaseUSPTOClient
+from pyUSPTO.config import USPTOConfig
+from pyUSPTO.models.ptab import PTABAppealDecision, PTABAppealResponse
+
+
+
+[docs] +class PTABAppealsClient(BaseUSPTOClient[PTABAppealResponse]): + """Client for interacting with the USPTO PTAB Appeals API. + + This client provides methods to search for ex parte appeal decisions from the + Patent Trial and Appeal Board. + + Appeals data includes decisions on patent application appeals from the examiner + to the PTAB. + """ + + ENDPOINTS = { + "search_decisions": "api/v1/patent/appeals/decisions/search", + } + +
+[docs] + def __init__( + self, + api_key: Optional[str] = None, + base_url: Optional[str] = None, + config: Optional[USPTOConfig] = None, + ): + """Initialize the PTABAppealsClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.ptab_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + )
+ + +
+[docs] + def search_decisions( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + # Convenience query parameters + appeal_number_q: Optional[str] = None, + application_number_text_q: Optional[str] = None, + appellant_name_q: Optional[str] = None, + requestor_name_q: Optional[str] = None, + decision_type_category_q: Optional[str] = None, + decision_date_from_q: Optional[str] = None, + decision_date_to_q: Optional[str] = None, + technology_center_number_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PTABAppealResponse: + """Searches for PTAB appeal decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + appeal_number_q: Filter by appeal number. + application_number_text_q: Filter by application number. + appellant_name_q: Filter by appellant name. + requestor_name_q: Filter by requestor name. + decision_type_category_q: Filter by decision type category. + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + technology_center_number_q: Filter by technology center number. + additional_query_params: Additional custom query parameters. + + Returns: + PTABAppealResponse: Response containing matching appeal decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="appealNumber:2023-001234") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... technology_center_number_q="3600", + ... decision_date_from_q="2023-01-01", + ... limit=50 + ... ) + + # Search with POST body + >>> response = client.search_decisions( + ... post_body={"q": "decisionTypeCategory:Affirmed", "limit": 100} + ... ) + """ + endpoint = self.ENDPOINTS["search_decisions"] + + if post_body is not None: + # POST request path + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + response_class=PTABAppealResponse, + ) + else: + # GET request path + params: Dict[str, Any] = {} + final_q = query + + # Build query from convenience parameters + if final_q is None: + q_parts = [] + if appeal_number_q: + q_parts.append(f"appealNumber:{appeal_number_q}") + if application_number_text_q: + q_parts.append(f"applicationNumberText:{application_number_text_q}") + if appellant_name_q: + q_parts.append(f"appellantName:{appellant_name_q}") + if requestor_name_q: + q_parts.append(f"requestorName:{requestor_name_q}") + if decision_type_category_q: + q_parts.append(f"decisionTypeCategory:{decision_type_category_q}") + if technology_center_number_q: + q_parts.append( + f"technologyCenterNumber:{technology_center_number_q}" + ) + + # Handle decision date range + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"decisionDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append(f"decisionDate:>={decision_date_from_q}") + elif decision_date_to_q: + q_parts.append(f"decisionDate:<={decision_date_to_q}") + + if q_parts: + final_q = " AND ".join(q_parts) + + # Add parameters + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets is not None: + params["facets"] = facets + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=PTABAppealResponse, + ) + + assert isinstance(result, PTABAppealResponse) + return result
+ + +
+[docs] + def paginate_decisions(self, **kwargs: Any) -> Iterator[PTABAppealDecision]: + """Provides an iterator to paginate through appeal decision search results. + + This method simplifies fetching all appeal decisions matching a search query + by automatically handling pagination. It internally calls the search_decisions + method for GET requests, batching results and yielding them one by one. + + All keyword arguments are passed directly to search_decisions to define the + search criteria. The offset and limit parameters are managed by the pagination + logic; setting them directly in kwargs might lead to unexpected behavior. + + Args: + **kwargs: Keyword arguments passed to search_decisions for constructing + the search query. Do not include post_body. + + Returns: + Iterator[PTABAppealDecision]: An iterator yielding PTABAppealDecision objects, + allowing iteration over all matching decisions across multiple pages of results. + + Raises: + ValueError: If post_body is included in kwargs, as this method only + supports GET request parameters for pagination. + + Examples: + # Paginate through all decisions for a technology center + >>> for decision in client.paginate_decisions(technology_center_number_q="3600"): + ... print(f"{decision.appeal_meta_data.appeal_number}: " + ... f"{decision.decision_data.decision_type_category}") + + # Paginate with date range + >>> for decision in client.paginate_decisions( + ... decision_date_from_q="2023-01-01", + ... decision_date_to_q="2023-12-31" + ... ): + ... process_decision(decision) + """ + if "post_body" in kwargs: + raise ValueError( + "paginate_decisions uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_decisions", + response_container_attr="patent_appeal_data_bag", + **kwargs, + )
+
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/clients/ptab_interferences.html b/docs/build/html/_modules/pyUSPTO/clients/ptab_interferences.html new file mode 100644 index 0000000..9ba2c9b --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/clients/ptab_interferences.html @@ -0,0 +1,389 @@ + + + + + + + + pyUSPTO.clients.ptab_interferences — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyUSPTO.clients.ptab_interferences

+"""
+clients.ptab_interferences - Client for USPTO PTAB Interferences API
+
+This module provides a client for interacting with the USPTO PTAB (Patent Trial
+and Appeal Board) Interferences API. It allows you to search for patent interference decisions.
+"""
+
+from typing import Any, Dict, Iterator, Optional
+
+from pyUSPTO.clients.base import BaseUSPTOClient
+from pyUSPTO.config import USPTOConfig
+from pyUSPTO.models.ptab import PTABInterferenceDecision, PTABInterferenceResponse
+
+
+
+[docs] +class PTABInterferencesClient(BaseUSPTOClient[PTABInterferenceResponse]): + """Client for interacting with the USPTO PTAB Interferences API. + + This client provides methods to search for patent interference decisions from the + Patent Trial and Appeal Board. + + Interference proceedings are used to determine priority of invention when two or + more parties claim the same patentable invention. + """ + + ENDPOINTS = { + "search_decisions": "api/v1/patent/interferences/decisions/search", + } + +
+[docs] + def __init__( + self, + api_key: Optional[str] = None, + base_url: Optional[str] = None, + config: Optional[USPTOConfig] = None, + ): + """Initialize the PTABInterferencesClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.ptab_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + )
+ + +
+[docs] + def search_decisions( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + # Convenience query parameters + interference_number_q: Optional[str] = None, + senior_party_application_number_q: Optional[str] = None, + junior_party_application_number_q: Optional[str] = None, + senior_party_name_q: Optional[str] = None, + junior_party_name_q: Optional[str] = None, + real_party_in_interest_q: Optional[str] = None, + interference_outcome_category_q: Optional[str] = None, + decision_type_category_q: Optional[str] = None, + decision_date_from_q: Optional[str] = None, + decision_date_to_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PTABInterferenceResponse: + """Searches for PTAB interference decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + interference_number_q: Filter by interference number. + senior_party_application_number_q: Filter by senior party application number. + junior_party_application_number_q: Filter by junior party application number. + senior_party_name_q: Filter by senior party name. + junior_party_name_q: Filter by junior party name. + real_party_in_interest_q: Filter by Real Party in Interest. + interference_outcome_category_q: Filter by interference outcome category. + decision_type_category_q: Filter by decision type category. + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + additional_query_params: Additional custom query parameters. + + Returns: + PTABInterferenceResponse: Response containing matching interference decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="interferenceNumber:106123") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... interference_outcome_category_q="Priority to Senior Party", + ... decision_date_from_q="2020-01-01", + ... limit=50 + ... ) + + # Search with POST body + >>> response = client.search_decisions( + ... post_body={"q": "decisionTypeCategory:Final Decision", "limit": 100} + ... ) + """ + endpoint = self.ENDPOINTS["search_decisions"] + + if post_body is not None: + # POST request path + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + response_class=PTABInterferenceResponse, + ) + else: + # GET request path + params: Dict[str, Any] = {} + final_q = query + + # Build query from convenience parameters + if final_q is None: + q_parts = [] + if interference_number_q: + q_parts.append(f"interferenceNumber:{interference_number_q}") + if senior_party_application_number_q: + q_parts.append( + f"seniorPartyData.applicationNumberText:{senior_party_application_number_q}" + ) + if junior_party_application_number_q: + q_parts.append( + f"juniorPartyData.applicationNumberText:{junior_party_application_number_q}" + ) + if senior_party_name_q: + q_parts.append( + f'seniorPartyData.patentOwnerName:"{senior_party_name_q}" OR seniorPartyData.inventorName:"{senior_party_name_q}" OR seniorPartyData.realPartyInInterestName:"{senior_party_name_q}"' + ) + if junior_party_name_q: + q_parts.append( + f'juniorPartyData.patentOwnerName:"{junior_party_name_q}" OR juniorPartyData.inventorName:"{junior_party_name_q}" OR juniorPartyData.realPartyInInterestName:"{junior_party_name_q}"' + ) + if real_party_in_interest_q: + q_parts.append( + f'seniorPartyData.realPartyInInterestName:"{real_party_in_interest_q}" OR juniorPartyData.realPartyInInterestName:"{real_party_in_interest_q}"' + ) + + if interference_outcome_category_q: + q_parts.append( + f'documentData.interferenceOutcomeCategory:"{interference_outcome_category_q}"' + ) + if decision_type_category_q: + q_parts.append( + f'documentData.decisionTypeCategory:"{decision_type_category_q}"' + ) + + # Handle decision date range + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"documentData.decisionIssueDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append( + f"documentData.decisionIssueDate:>={decision_date_from_q}" + ) + elif decision_date_to_q: + q_parts.append( + f"documentData.decisionIssueDate:<={decision_date_to_q}" + ) + + if q_parts: + final_q = " AND ".join(q_parts) + + # Add parameters + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets is not None: + params["facets"] = facets + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=PTABInterferenceResponse, + ) + + assert isinstance(result, PTABInterferenceResponse) + return result
+ + +
+[docs] + def paginate_decisions(self, **kwargs: Any) -> Iterator[PTABInterferenceDecision]: + """Provides an iterator to paginate through interference decision search results. + + This method simplifies fetching all interference decisions matching a search query + by automatically handling pagination. It internally calls the search_decisions + method for GET requests, batching results and yielding them one by one. + + All keyword arguments are passed directly to search_decisions to define the + search criteria. The offset and limit parameters are managed by the pagination + logic; setting them directly in kwargs might lead to unexpected behavior. + + Args: + **kwargs: Keyword arguments passed to search_decisions for constructing + the search query. Do not include post_body. + + Returns: + Iterator[PTABInterferenceDecision]: An iterator yielding PTABInterferenceDecision + objects, allowing iteration over all matching decisions across multiple pages + of results. + + Raises: + ValueError: If post_body is included in kwargs, as this method only + supports GET request parameters for pagination. + + Examples: + # Paginate through all interference decisions + >>> for decision in client.paginate_decisions(): + ... print(f"{decision.interference_meta_data.interference_number}: " + ... f"{decision.document_data.interference_outcome_category}") + + # Paginate with date range + >>> for decision in client.paginate_decisions( + ... decision_date_from_q="2020-01-01", + ... decision_date_to_q="2023-12-31" + ... ): + ... process_decision(decision) + """ + if "post_body" in kwargs: + raise ValueError( + "paginate_decisions uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_decisions", + response_container_attr="patent_interference_data_bag", + **kwargs, + )
+
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/clients/ptab_trials.html b/docs/build/html/_modules/pyUSPTO/clients/ptab_trials.html new file mode 100644 index 0000000..a7d573d --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/clients/ptab_trials.html @@ -0,0 +1,618 @@ + + + + + + + + pyUSPTO.clients.ptab_trials — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyUSPTO.clients.ptab_trials

+"""
+clients.ptab_trials - Client for USPTO PTAB Trials API
+
+This module provides a client for interacting with the USPTO PTAB (Patent Trial
+and Appeal Board) Trials API. It allows you to search for trial proceedings,
+documents, and decisions.
+"""
+
+from typing import Any, Dict, Iterator, List, Optional, Union
+
+from pyUSPTO.clients.base import BaseUSPTOClient
+from pyUSPTO.config import USPTOConfig
+from pyUSPTO.models.ptab import (
+    PTABTrialDocumentResponse,
+    PTABTrialProceeding,
+    PTABTrialProceedingResponse,
+)
+
+
+
+[docs] +class PTABTrialsClient( + BaseUSPTOClient[Union[PTABTrialProceedingResponse, PTABTrialDocumentResponse]] +): + """Client for interacting with the USPTO PTAB Trials API. + + This client provides methods to search for trial proceedings, trial documents, + and trial decisions from the Patent Trial and Appeal Board. + + Trial proceedings data includes IPR (Inter Partes Review), PGR (Post-Grant Review), + CBM (Covered Business Method), and DER (Derivation) proceedings. + """ + + ENDPOINTS = { + "search_proceedings": "api/v1/patent/trials/proceedings/search", + "search_documents": "api/v1/patent/trials/documents/search", + "search_decisions": "api/v1/patent/trials/decisions/search", + } + +
+[docs] + def __init__( + self, + api_key: Optional[str] = None, + base_url: Optional[str] = None, + config: Optional[USPTOConfig] = None, + ): + """Initialize the PTABTrialsClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.ptab_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + )
+ + + def _perform_search( + self, + endpoint_key: str, + response_class: Any, + query: Optional[str], + query_parts: List[str], + post_body: Optional[Dict[str, Any]], + sort: Optional[str], + offset: Optional[int], + limit: Optional[int], + facets: Optional[str], + fields: Optional[str], + filters: Optional[str], + range_filters: Optional[str], + additional_params: Optional[Dict[str, Any]], + ) -> Union[PTABTrialProceedingResponse, PTABTrialDocumentResponse]: + """Internal helper to execute search requests (GET or POST).""" + endpoint = self.ENDPOINTS[endpoint_key] + + # Handle POST request + if post_body is not None: + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_params, + response_class=response_class, + ) + return result # type: ignore + + # Handle GET request + params: Dict[str, Any] = {} + final_q = query + + # Combine specific convenience query parts if no direct query is provided + if final_q is None and query_parts: + final_q = " AND ".join(query_parts) + + if final_q: + params["q"] = final_q + if sort: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets: + params["facets"] = facets + if fields: + params["fields"] = fields + if filters: + params["filters"] = filters + if range_filters: + params["rangeFilters"] = range_filters + + if additional_params: + params.update(additional_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=response_class, + ) + return result # type: ignore + +
+[docs] + def search_proceedings( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + # Convenience query parameters + trial_number_q: Optional[str] = None, + patent_owner_name_q: Optional[str] = None, + petitioner_real_party_in_interest_name_q: Optional[str] = None, + respondent_name_q: Optional[str] = None, + trial_type_code_q: Optional[str] = None, + trial_status_category_q: Optional[str] = None, + petition_filing_date_from_q: Optional[str] = None, + petition_filing_date_to_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PTABTrialProceedingResponse: + """Searches for PTAB trial proceedings. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + trial_number_q: Filter by trial number (e.g., "IPR2023-00001"). + patent_owner_name_q: Filter by patent owner name. + petitioner_real_party_in_interest_name_q: Filter by petitioner real party in interest. + respondent_name_q: Filter by respondent name. + trial_type_code_q: Filter by trial type code (e.g., "IPR", "PGR", "CBM", "DER"). + trial_status_category_q: Filter by trial status category. + petition_filing_date_from_q: Filter proceedings from this date (YYYY-MM-DD). + petition_filing_date_to_q: Filter proceedings to this date (YYYY-MM-DD). + additional_query_params: Additional custom query parameters. + + Returns: + PTABTrialProceedingResponse: Response containing matching trial proceedings. + + Examples: + # Search with direct query + >>> response = client.search_proceedings(query="trialNumber:IPR2023-00001") + + # Search with convenience parameters + >>> response = client.search_proceedings( + ... trial_type_code_q="IPR", + ... petition_filing_date_from_q="2023-01-01", + ... limit=50 + ... ) + """ + q_parts = [] + if trial_number_q: + q_parts.append(f"trialNumber:{trial_number_q}") + if patent_owner_name_q: + q_parts.append(f'patentOwnerData.patentOwnerName:"{patent_owner_name_q}"') + if petitioner_real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{petitioner_real_party_in_interest_name_q}"' + ) + if respondent_name_q: + q_parts.append(f'respondentData.patentOwnerName:"{respondent_name_q}"') + if trial_type_code_q: + q_parts.append(f"trialMetaData.trialTypeCode:{trial_type_code_q}") + if trial_status_category_q: + q_parts.append( + f'trialMetaData.trialStatusCategory:"{trial_status_category_q}"' + ) + + if petition_filing_date_from_q and petition_filing_date_to_q: + q_parts.append( + f"trialMetaData.petitionFilingDate:[{petition_filing_date_from_q} TO {petition_filing_date_to_q}]" + ) + elif petition_filing_date_from_q: + q_parts.append( + f"trialMetaData.petitionFilingDate:>={petition_filing_date_from_q}" + ) + elif petition_filing_date_to_q: + q_parts.append( + f"trialMetaData.petitionFilingDate:<={petition_filing_date_to_q}" + ) + + return self._perform_search( + endpoint_key="search_proceedings", + response_class=PTABTrialProceedingResponse, + query=query, + query_parts=q_parts, + post_body=post_body, + sort=sort, + offset=offset, + limit=limit, + facets=facets, + fields=fields, + filters=filters, + range_filters=range_filters, + additional_params=additional_query_params, + ) # type: ignore
+ + +
+[docs] + def search_documents( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + # Convenience query parameters + trial_number_q: Optional[str] = None, + document_category_q: Optional[str] = None, + document_type_name_q: Optional[str] = None, + filing_date_from_q: Optional[str] = None, + filing_date_to_q: Optional[str] = None, + petitioner_real_party_in_interest_name_q: Optional[str] = None, + inventor_name_q: Optional[str] = None, + real_party_in_interest_name_q: Optional[str] = None, + patent_number_q: Optional[str] = None, + patent_owner_name_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PTABTrialDocumentResponse: + """Searches for PTAB trial documents. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + trial_number_q: Filter by trial number. + document_category_q: Filter by document category (e.g., "Petition") DOCUMENTED BUT NOT IN API. + document_type_name_q: Filter by document type name (description). + filing_date_from_q: Filter documents from this date (YYYY-MM-DD). + filing_date_to_q: Filter documents to this date (YYYY-MM-DD). + petitioner_real_party_in_interest_name_q: Filter by petitioner real party in interest. + inventor_name_q: Filter by inventor name. + real_party_in_interest_name_q: Filter by real party in interest (generic). + patent_number_q: Filter by patent number. + patent_owner_name_q: Filter by patent owner name. + additional_query_params: Additional custom query parameters. + + Returns: + PTABTrialDocumentResponse: Response containing matching trial documents. + + Examples: + # Search with direct query + >>> response = client.search_documents(query="trialNumber:IPR2023-00001") + + # Search with convenience parameters + >>> response = client.search_documents( + ... document_category_q="Paper", + ... filing_date_from_q="2023-01-01", + ... limit=50 + ... ) + """ + q_parts = [] + if trial_number_q: + q_parts.append(f"trialNumber:{trial_number_q}") + if document_category_q: + q_parts.append(f'documentData.documentCategory:"{document_category_q}"') + if document_type_name_q: + q_parts.append( + f'documentData.documentTypeDescriptionText:"{document_type_name_q}"' + ) + if petitioner_real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{petitioner_real_party_in_interest_name_q}"' + ) + if inventor_name_q: + q_parts.append(f'patentOwnerData.inventorName:"{inventor_name_q}"') + if real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{real_party_in_interest_name_q}"' + ) + if patent_number_q: + q_parts.append(f"patentOwnerData.patentNumber:{patent_number_q}") + if patent_owner_name_q: + q_parts.append(f'patentOwnerData.patentOwnerName:"{patent_owner_name_q}"') + + if filing_date_from_q and filing_date_to_q: + q_parts.append( + f"documentData.documentFilingDate:[{filing_date_from_q} TO {filing_date_to_q}]" + ) + elif filing_date_from_q: + q_parts.append(f"documentData.documentFilingDate:>={filing_date_from_q}") + elif filing_date_to_q: + q_parts.append(f"documentData.documentFilingDate:<={filing_date_to_q}") + + return self._perform_search( + endpoint_key="search_documents", + response_class=PTABTrialDocumentResponse, + query=query, + query_parts=q_parts, + post_body=post_body, + sort=sort, + offset=offset, + limit=limit, + facets=facets, + fields=fields, + filters=filters, + range_filters=range_filters, + additional_params=additional_query_params, + ) # type: ignore
+ + +
+[docs] + def search_decisions( + self, + query: Optional[str] = None, + sort: Optional[str] = None, + offset: Optional[int] = 0, + limit: Optional[int] = 25, + facets: Optional[str] = None, + fields: Optional[str] = None, + filters: Optional[str] = None, + range_filters: Optional[str] = None, + post_body: Optional[Dict[str, Any]] = None, + # Convenience query parameters + trial_number_q: Optional[str] = None, + decision_type_category_q: Optional[str] = None, + document_type_description_q: Optional[str] = None, + decision_date_from_q: Optional[str] = None, + decision_date_to_q: Optional[str] = None, + trial_type_code_q: Optional[str] = None, + patent_number_q: Optional[str] = None, + application_number_q: Optional[str] = None, + patent_owner_name_q: Optional[str] = None, + trial_status_category_q: Optional[str] = None, + real_party_in_interest_name_q: Optional[str] = None, + document_category_q: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, + ) -> PTABTrialDocumentResponse: + """Searches for PTAB trial decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + trial_number_q: Filter by trial number. + decision_type_category_q: Filter by decision type category. + document_type_description_q: Filter by "*[description]*". + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + trial_type_code_q: Filter by trial type code (e.g., "IPR", "PGR", "CBM", "DER"). + patent_number_q: Filter by patent number. + application_number_q: Filter by application number. + patent_owner_name_q: Filter by patent owner name. + trial_status_category_q: Filter by trial status category. + real_party_in_interest_name_q: Filter by real party in interest name. + document_category_q: Filter by document category. + additional_query_params: Additional custom query parameters. + + Returns: + PTABTrialDocumentResponse: Response containing matching trial decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="trialNumber:IPR2023-00001") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... decision_type_category_q="Final Written Decision", + ... decision_date_from_q="2023-01-01", + ... limit=50 + ... ) + """ + q_parts = [] + if trial_number_q: + q_parts.append(f"trialNumber:{trial_number_q}") + if decision_type_category_q: + q_parts.append( + f'decisionData.decisionTypeCategory:"{decision_type_category_q}"' + ) + if document_type_description_q: + q_parts.append( + f'documentData.documentTypeDescriptionText:"*{document_type_description_q}*"' + ) + if trial_type_code_q: + q_parts.append(f"trialMetaData.trialTypeCode:{trial_type_code_q}") + if patent_number_q: + q_parts.append(f"patentOwnerData.patentNumber:{patent_number_q}") + if application_number_q: + q_parts.append( + f"patentOwnerData.applicationNumberText:{application_number_q}" + ) + if patent_owner_name_q: + q_parts.append(f'patentOwnerData.patentOwnerName:"{patent_owner_name_q}"') + if trial_status_category_q: + q_parts.append( + f'trialMetaData.trialStatusCategory:"{trial_status_category_q}"' + ) + if real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{real_party_in_interest_name_q}"' + ) + if document_category_q: + q_parts.append(f'documentData.documentCategory:"{document_category_q}"') + + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"decisionData.decisionIssueDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append(f"decisionData.decisionIssueDate:>={decision_date_from_q}") + elif decision_date_to_q: + q_parts.append(f"decisionData.decisionIssueDate:<={decision_date_to_q}") + + return self._perform_search( + endpoint_key="search_decisions", + response_class=PTABTrialDocumentResponse, + query=query, + query_parts=q_parts, + post_body=post_body, + sort=sort, + offset=offset, + limit=limit, + facets=facets, + fields=fields, + filters=filters, + range_filters=range_filters, + additional_params=additional_query_params, + ) # type: ignore
+ + +
+[docs] + def paginate_proceedings(self, **kwargs: Any) -> Iterator[PTABTrialProceeding]: + """Provides an iterator to paginate through trial proceeding search results.""" + if "post_body" in kwargs: + raise ValueError( + "paginate_proceedings uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_proceedings", + response_container_attr="patent_trial_proceeding_data_bag", + **kwargs, + )
+
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/config.html b/docs/build/html/_modules/pyUSPTO/config.html index 819a396..8c9b251 100644 --- a/docs/build/html/_modules/pyUSPTO/config.html +++ b/docs/build/html/_modules/pyUSPTO/config.html @@ -5,7 +5,7 @@ - pyUSPTO.config — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + pyUSPTO.config — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -79,60 +79,89 @@

Source code for pyUSPTO.config

 """
 config - Configuration management for USPTO API clients
 
-This module provides configuration management for USPTO API clients.
+This module provides configuration management for USPTO API clients,
+including API keys, base URLs, and HTTP transport settings.
 """
 
 import os
 from typing import Optional
 
+from pyUSPTO.http_config import HTTPConfig
+
 
 
[docs] class USPTOConfig: - """Configuration for USPTO API clients.""" + """Configuration for USPTO API clients. + + Manages API-level configuration (keys, URLs) and optionally + accepts HTTP transport configuration via HTTPConfig. + """
[docs] def __init__( self, api_key: Optional[str] = None, - bulk_data_base_url: str = "https://api.uspto.gov/api/v1/datasets", - patent_data_base_url: str = "https://api.uspto.gov/api/v1/patent", + bulk_data_base_url: str = "https://api.uspto.gov", + patent_data_base_url: str = "https://api.uspto.gov", + petition_decisions_base_url: str = "https://api.uspto.gov", + ptab_base_url: str = "https://api.uspto.gov", + http_config: Optional[HTTPConfig] = None, + include_raw_data: bool = False, ): - """ - Initialize the USPTOConfig. + """Initialize the USPTOConfig. Args: api_key: API key for authentication, defaults to USPTO_API_KEY environment variable bulk_data_base_url: Base URL for the Bulk Data API patent_data_base_url: Base URL for the Patent Data API + petition_decisions_base_url: Base URL for the Final Petition Decisions API + ptab_base_url: Base URL for the PTAB (Patent Trial and Appeal Board) API + http_config: Optional HTTPConfig for request handling (uses defaults if None) + include_raw_data: If True, store raw JSON in response objects for debugging (default: False) """ # Use environment variable only if api_key is None, not if it's an empty string self.api_key = ( api_key if api_key is not None else os.environ.get("USPTO_API_KEY") ) self.bulk_data_base_url = bulk_data_base_url - self.patent_data_base_url = patent_data_base_url
+ self.patent_data_base_url = patent_data_base_url + self.petition_decisions_base_url = petition_decisions_base_url + self.ptab_base_url = ptab_base_url + + # Use provided HTTPConfig or create default + self.http_config = http_config if http_config is not None else HTTPConfig() + + # Control whether to include raw JSON data in response objects + self.include_raw_data = include_raw_data
[docs] @classmethod def from_env(cls) -> "USPTOConfig": - """ - Create a USPTOConfig from environment variables. + """Create a USPTOConfig from environment variables. Returns: - USPTOConfig instance + USPTOConfig instance with values from environment """ return cls( api_key=os.environ.get("USPTO_API_KEY"), bulk_data_base_url=os.environ.get( - "USPTO_BULK_DATA_BASE_URL", "https://api.uspto.gov/api/v1/datasets" + "USPTO_BULK_DATA_BASE_URL", "https://api.uspto.gov" ), patent_data_base_url=os.environ.get( - "USPTO_PATENT_DATA_BASE_URL", "https://api.uspto.gov/api/v1/patent" + "USPTO_PATENT_DATA_BASE_URL", "https://api.uspto.gov" + ), + petition_decisions_base_url=os.environ.get( + "USPTO_PETITION_DECISIONS_BASE_URL", "https://api.uspto.gov" + ), + ptab_base_url=os.environ.get( + "USPTO_PTAB_BASE_URL", "https://api.uspto.gov" ), + # Also read HTTP config from environment + http_config=HTTPConfig.from_env(), )
@@ -145,7 +174,7 @@

Source code for pyUSPTO.config

   
-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/exceptions.html b/docs/build/html/_modules/pyUSPTO/exceptions.html index 071202b..587cf1e 100644 --- a/docs/build/html/_modules/pyUSPTO/exceptions.html +++ b/docs/build/html/_modules/pyUSPTO/exceptions.html @@ -5,7 +5,7 @@ - pyUSPTO.exceptions — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + pyUSPTO.exceptions — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -79,27 +79,112 @@

Source code for pyUSPTO.exceptions

 """
 exceptions - Exception classes for USPTO API clients
 
-This module provides exception classes for USPTO API clients.
+This module provides exception classes for USPTO API errors that correspond to
+the various response types from the USPTO API. It also includes helper
+structures and functions for creating these exceptions.
 """
 
-from typing import Optional
+from dataclasses import asdict, dataclass
+from typing import TYPE_CHECKING, Optional, Type, Union
 
+# To avoid circular imports if requests is type-hinted directly,
+# use TYPE_CHECKING guard or a string literal for the type hint.
+if TYPE_CHECKING:
+    import requests  # requests.exceptions.HTTPError
 
+
+# --- Exception Classes (largely unchanged) ---
 
[docs] class USPTOApiError(Exception): - """Base exception for USPTO API errors.""" - - def __init__(self, message: str, status_code: Optional[int] = None): + """Base exception for USPTO API errors. + This is the parent class for all USPTO API-specific exceptions. It includes + information about the status code, API's short error message, detailed error + information, and request identifier from the API response. + """ + + DEFAULT_UNKNOWN_MESSAGE = "UNK USPTO API ERROR" + +
+[docs] + def __init__( + self, + message: str, # Primary client-facing message for the exception context + status_code: Optional[int] = None, + api_short_error: Optional[ + str + ] = None, # From API 'error' or 'message' (for 413) field + error_details: Optional[ + Union[str, dict] + ] = None, # From API 'errorDetails' or 'detailedMessage' field + request_identifier: Optional[str] = None, + ): + """ + Initializes the USPTOApiError. + Args: + message: The primary message for the exception (often client-generated context). + status_code: The HTTP status code from the API response (e.g., 400, 403). + api_short_error: The short error description from the API (e.g., "Bad Request", "Forbidden"). + error_details: The detailed error message or structure from the API. + request_identifier: The request identifier from the API response, if available. + """ + effective_message = message if message else self.DEFAULT_UNKNOWN_MESSAGE + super().__init__(effective_message) self.status_code = status_code - super().__init__(message)
+ self.api_short_error = api_short_error + self.error_details = error_details + self.request_identifier = request_identifier
+ + + @property + def message(self) -> str: + """ + Provides direct access to the primary exception message. + This refers to the first argument passed to the exception, + which is conventionally the main human-readable message. + """ + return str(object=self.args[0]) + +
+[docs] + def __str__(self) -> str: + """Provides a more informative string representation of the error.""" + parts = [super().__str__()] + + if self.status_code: + parts.append(f"HTTP Status: {self.status_code}") + + if self.api_short_error: + parts.append(f"API Error: {self.api_short_error}") + + if self.error_details: + details_str = str(self.error_details) + parts.append(f"Details: {details_str}") + + if self.request_identifier: + parts.append(f"Request ID: {self.request_identifier}") + + if len(parts) > 1: + return " - ".join(filter(None, parts)) + else: + return parts[0]
+
+ + + +
+[docs] +class USPTOApiBadRequestError(USPTOApiError): + """Bad Request error (HTTP 400).""" + + pass
[docs] class USPTOApiAuthError(USPTOApiError): - """Authentication error.""" + """Authentication/Authorization error (HTTP 401/403).""" pass
@@ -108,7 +193,7 @@

Source code for pyUSPTO.exceptions

 
[docs] class USPTOApiRateLimitError(USPTOApiError): - """Rate limit exceeded.""" + """Rate limit exceeded error (HTTP 429).""" pass
@@ -117,10 +202,187 @@

Source code for pyUSPTO.exceptions

 
[docs] class USPTOApiNotFoundError(USPTOApiError): - """Resource not found.""" + """Resource not found error (HTTP 404).""" + + pass
+ + + +
+[docs] +class USPTOApiPayloadTooLargeError(USPTOApiError): + """Payload Too Large error (HTTP 413).""" + + pass
+ + + +
+[docs] +class USPTOApiServerError(USPTOApiError): + """Internal Server Error (HTTP 500 series).""" pass
+ + +
+[docs] +class USPTOConnectionError(USPTOApiError): + """Network-level connection error (DNS failure, refused connection, etc.).""" + + pass
+ + + +
+[docs] +class USPTOTimeout(USPTOApiError): + """Request to USPTO API timed out.""" + + pass
+ + + +# --- Helper Structures and Functions --- + + +
+[docs] +@dataclass +class APIErrorArgs: + """Data structure to hold arguments for API exception constructors.""" + + message: str + status_code: Optional[int] = None + api_short_error: Optional[str] = None + error_details: Optional[Union[str, dict]] = None + request_identifier: Optional[str] = None + +
+[docs] + @classmethod + def from_http_error( + cls, + http_error: "requests.exceptions.HTTPError", # String literal for type hint + client_operation_message: str, + ) -> "APIErrorArgs": + """ + Creates an APIErrorArgs instance by parsing a requests.exceptions.HTTPError. + + Args: + http_error: The HTTPError object from the requests library. + client_operation_message: A message describing the client operation that failed. + + Returns: + An instance of APIErrorArgs populated with details from the HTTPError. + """ + status_code = http_error.response.status_code + + api_short_error_from_response = None + error_details_from_response = None + request_identifier_from_response = None + + try: + error_data = http_error.response.json() + if status_code == 413: + api_short_error_from_response = error_data.get("message") + error_details_from_response = error_data.get("detailedMessage") + else: + api_short_error_from_response = error_data.get("error") + error_details_from_response = error_data.get("errorDetails") + request_identifier_from_response = error_data.get("requestIdentifier") + except ValueError: # If response.json() fails (e.g., not JSON) + pass # Values remain None + + # Fallback for api_short_error if not found in JSON response + if not api_short_error_from_response and http_error.response.reason: + api_short_error_from_response = http_error.response.reason + + # Fallback for error_details if not found in JSON and response text is available + if not error_details_from_response and http_error.response.text: + # Avoid setting very long HTML pages as error_details if JSON parsing failed + if ( + "content-type" in http_error.response.headers + and "application/json" + not in http_error.response.headers.get("content-type", "").lower() + ): + if len(http_error.response.text) > 500: # Heuristic for "too long" + error_details_from_response = f"Non-JSON error response (status {status_code}). Check response text." + else: + error_details_from_response = http_error.response.text + elif ( + http_error.response.text + ): # If it might have been JSON but parsing failed + error_details_from_response = http_error.response.text + + return cls( + message=client_operation_message, + status_code=status_code, + api_short_error=api_short_error_from_response, + error_details=error_details_from_response, + request_identifier=request_identifier_from_response, + )
+ + +
+[docs] + @classmethod + def from_request_exception( + cls, + request_exception: "requests.exceptions.RequestException", # String for type hint + client_operation_message: Optional[str] = None, + ) -> "APIErrorArgs": + """ + Creates an APIErrorArgs instance from a generic requests.exceptions.RequestException + (e.g., ConnectionError, Timeout) that is not an HTTPError. + """ + message_prefix = client_operation_message or "API request failed" + return cls( + message=f"{message_prefix} due to a network or request issue: {str(request_exception)}" + # status_code, api_short_error, etc., will be None + )
+
+ + + +
+[docs] +def get_api_exception(error_args: APIErrorArgs) -> USPTOApiError: + """ + Determines and instantiates the appropriate USPTOApiError subclass + based on the status code in error_args. + + Args: + error_args: An instance of APIErrorArgs containing all necessary + information to construct the exception. + + Returns: + An instance of a USPTOApiError subclass. + """ + status_code = error_args.status_code + exception_class: Type[USPTOApiError] + + match status_code: + case 400: + exception_class = USPTOApiBadRequestError + case 401 | 403: + exception_class = USPTOApiAuthError + case 404: + exception_class = USPTOApiNotFoundError + case 413: + exception_class = USPTOApiPayloadTooLargeError + case 429: + exception_class = USPTOApiRateLimitError + case _ if status_code is not None and status_code >= 500: + exception_class = USPTOApiServerError + case ( + _ + ): # Default for other errors or if status_code is None (e.g. network error) + exception_class = USPTOApiError + + return exception_class(**asdict(error_args))
+
@@ -130,7 +392,7 @@

Source code for pyUSPTO.exceptions

   
-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/http_config.html b/docs/build/html/_modules/pyUSPTO/http_config.html new file mode 100644 index 0000000..d1a802c --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/http_config.html @@ -0,0 +1,196 @@ + + + + + + + + pyUSPTO.http_config — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyUSPTO.http_config

+"""
+http_config - HTTP client configuration for USPTO API requests
+
+This module provides configuration for HTTP transport-level settings including
+timeouts, retries, connection pooling, and custom headers.
+"""
+
+import os
+from dataclasses import dataclass, field
+from typing import Dict, List, Optional
+
+
+
+[docs] +@dataclass +class HTTPConfig: + """HTTP client configuration for request handling. + + This class separates transport-level HTTP concerns from API-level + configuration, allowing fine-grained control over request behavior. + + Attributes: + timeout: Read timeout in seconds for requests (default: 30.0) + connect_timeout: Connection establishment timeout in seconds (default: 10.0) + max_retries: Maximum number of retry attempts (default: 3) + backoff_factor: Exponential backoff multiplier for retries (default: 1.0) + retry_status_codes: HTTP status codes that trigger retries + pool_connections: Number of connection pools to cache (default: 10) + pool_maxsize: Maximum number of connections per pool (default: 10) + custom_headers: Additional headers to include in all requests + """ + + # Timeout configuration + timeout: Optional[float] = 30.0 + connect_timeout: Optional[float] = 10.0 + + # Retry configuration + max_retries: int = 3 + backoff_factor: float = 1.0 + retry_status_codes: List[int] = field( + default_factory=lambda: [429, 500, 502, 503, 504] + ) + + # Connection pooling + pool_connections: int = 10 + pool_maxsize: int = 10 + + # Custom headers (User-Agent, tracking, etc.) + custom_headers: Optional[Dict[str, str]] = None + +
+[docs] + @classmethod + def from_env(cls) -> "HTTPConfig": + """Create HTTPConfig from environment variables. + + Environment variables: + USPTO_REQUEST_TIMEOUT: Request timeout in seconds + USPTO_CONNECT_TIMEOUT: Connection timeout in seconds + USPTO_MAX_RETRIES: Maximum retry attempts + USPTO_BACKOFF_FACTOR: Retry backoff factor + USPTO_POOL_CONNECTIONS: Connection pool size + USPTO_POOL_MAXSIZE: Max connections per pool + + Returns: + HTTPConfig instance with values from environment or defaults + """ + return cls( + timeout=float(os.environ.get("USPTO_REQUEST_TIMEOUT", "30.0")), + connect_timeout=float(os.environ.get("USPTO_CONNECT_TIMEOUT", "10.0")), + max_retries=int(os.environ.get("USPTO_MAX_RETRIES", "3")), + backoff_factor=float(os.environ.get("USPTO_BACKOFF_FACTOR", "1.0")), + pool_connections=int(os.environ.get("USPTO_POOL_CONNECTIONS", "10")), + pool_maxsize=int(os.environ.get("USPTO_POOL_MAXSIZE", "10")), + )
+ + +
+[docs] + def get_timeout_tuple(self) -> tuple[Optional[float], Optional[float]]: + """Get timeout as tuple for requests library. + + Returns: + Tuple of (connect_timeout, read_timeout) for requests + """ + return (self.connect_timeout, self.timeout)
+
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/models/bulk_data.html b/docs/build/html/_modules/pyUSPTO/models/bulk_data.html index 3b6b714..5b321ae 100644 --- a/docs/build/html/_modules/pyUSPTO/models/bulk_data.html +++ b/docs/build/html/_modules/pyUSPTO/models/bulk_data.html @@ -5,7 +5,7 @@ - pyUSPTO.models.bulk_data — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + pyUSPTO.models.bulk_data — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -82,7 +82,8 @@

Source code for pyUSPTO.models.bulk_data

 This module provides data models for the USPTO Open Data Portal (ODP) Bulk Data API.
 """
 
-from dataclasses import dataclass
+import json
+from dataclasses import dataclass, field
 from typing import Any, Dict, List, Optional
 
 
@@ -201,22 +202,40 @@ 

Source code for pyUSPTO.models.bulk_data

 [docs]
 @dataclass
 class BulkDataResponse:
-    """Top-level response from the bulk data API."""
+    """Top-level response from the bulk data API.
+
+    Attributes:
+        count: The number of bulk data products in the response.
+        bulk_data_product_bag: List of bulk data products.
+        raw_data: Optional raw JSON data from the API response (for debugging).
+    """
 
     count: int
     bulk_data_product_bag: List[BulkDataProduct]
+    raw_data: Optional[str] = field(default=None, compare=False, repr=False)
 
 
[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "BulkDataResponse": - """Create a BulkDataResponse object from a dictionary.""" + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "BulkDataResponse": + """Create a BulkDataResponse object from a dictionary. + + Args: + data: Dictionary containing API response data. + include_raw_data: If True, store the raw JSON for debugging. + + Returns: + BulkDataResponse: An instance of BulkDataResponse. + """ return cls( count=data.get("count", 0), bulk_data_product_bag=[ BulkDataProduct.from_dict(product) for product in data.get("bulkDataProductBag", []) ], + raw_data=json.dumps(data) if include_raw_data else None, )
@@ -247,7 +266,7 @@

Source code for pyUSPTO.models.bulk_data

   
-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/models/patent_data.html b/docs/build/html/_modules/pyUSPTO/models/patent_data.html index 3e74975..de7fd30 100644 --- a/docs/build/html/_modules/pyUSPTO/models/patent_data.html +++ b/docs/build/html/_modules/pyUSPTO/models/patent_data.html @@ -5,7 +5,7 @@ - pyUSPTO.models.patent_data — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + pyUSPTO.models.patent_data — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -79,63 +79,427 @@

Source code for pyUSPTO.models.patent_data

 """
 models.patent_data - Data models for USPTO patent data API
 
-This module provides data models for the USPTO Patent Data API.
+This module provides Pydantic-style data models, primarily using frozen
+dataclasses, for representing responses from the USPTO Patent Data API.
+It aims to offer more Pythonic representations (e.g., Enums, native
+date/datetime objects) of the API's JSON data. Models cover aspects like
+application metadata, party information (applicants, inventors, attorneys),
+document details, continuity, assignments, and more.
 """
 
-from dataclasses import dataclass, field
-from typing import Any, Dict, List, Optional
+import csv
+import io
+import json
+import warnings
+from dataclasses import asdict, dataclass, field
+from datetime import date, datetime
+from enum import Enum
+from typing import Any, Dict, Iterator, List, Optional, Union
+
+# Import utility functions from models.utils module
+from pyUSPTO.models.utils import (
+    ASSUMED_NAIVE_TIMEZONE,
+    ASSUMED_NAIVE_TIMEZONE_STR,
+    parse_to_date,
+    parse_to_datetime_utc,
+    parse_yn_to_bool,
+    serialize_bool_to_yn,
+    serialize_date,
+    serialize_datetime_as_iso,
+    serialize_datetime_as_naive,
+    to_camel_case,
+)
+from pyUSPTO.warnings import USPTOEnumParseWarning
+
+
+# --- Enums for Categorical Data ---
+
+[docs] +class DirectionCategory(Enum): + """Represents the direction of a document relative to the USPTO (e.g., INCOMING, OUTGOING).""" + + INCOMING = "INCOMING" + OUTGOING = "OUTGOING"
+ + + +
+[docs] +class ActiveIndicator(Enum): + """Represents an active or inactive status, often used for practitioners or entities. + + This Enum is designed to flexibly parse common string representations of + active/inactive or true/false states (e.g., "Y", "N", "true", "false", "Active") + into standardized Enum members. + """ + + YES = "Y" + NO = "N" + TRUE = "true" + FALSE = "false" + ACTIVE = "Active" + @classmethod + def _missing_(cls, value: Any) -> "ActiveIndicator": + if isinstance(value, str): + val_upper = value.upper() + if val_upper == "Y": + return cls.YES + if val_upper == "N": + return cls.NO + if val_upper == "TRUE": + return cls.TRUE + if val_upper == "FALSE": + return cls.FALSE + if val_upper == "ACTIVE": + return cls.ACTIVE + return super()._missing_(value=value) # type: ignore[no-any-return]
+ + + +# --- Data Models --- +
+[docs] +@dataclass(frozen=True) +class DocumentFormat: + """Represents an available download format for a specific document. + + Attributes: + mime_type_identifier: The MIME type of the downloadable file (e.g., "PDF"). + download_url: The URL from which the document format can be downloaded. + page_total_quantity: The total number of pages in this document format. + """ + + mime_type_identifier: Optional[str] = None + download_url: Optional[str] = None + page_total_quantity: Optional[int] = None + + def __str__(self) -> str: + return ( + f"{self.mime_type_identifier} format with {self.page_total_quantity} pages" + ) + + def __repr__(self) -> str: + return f"DocumentFormat(mime_type={self.mime_type_identifier}, pages={self.page_total_quantity})" + +
+[docs] + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "DocumentFormat": + """Creates a `DocumentFormat` instance from a dictionary representation. -
-[docs] -@dataclass -class PatentDataResponse: - """Top-level response from the patent data API.""" + This factory method is typically used to construct `DocumentFormat` + objects from data parsed from an API JSON response. It maps + dictionary keys (expected in camelCase) to the class attributes. - count: int - patent_file_wrapper_data_bag: List["PatentFileWrapper"] + Args: + data (Dict[str, Any]): A dictionary containing the data for a + `DocumentFormat`. Expected keys from the API are + "mimeTypeIdentifier", "downloadUrl", and "pageTotalQuantity". -
-[docs] + Returns: + DocumentFormat: An instance of `DocumentFormat` initialized with + data from the input dictionary. + """ + + return cls( + mime_type_identifier=data.get("mimeTypeIdentifier"), + download_url=data.get("downloadUrl"), + page_total_quantity=data.get("pageTotalQuantity"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `DocumentFormat` instance to a dictionary. + + This method serializes the `DocumentFormat` object into a dictionary, + mapping the instance's attributes to camelCase keys. This is typically + useful for generating JSON representations compatible with API expectations. + + Returns: + Dict[str, Any]: A dictionary representation of the `DocumentFormat` + instance with keys "mimeTypeIdentifier", "downloadUrl", and + "pageTotalQuantity". + """ + + return { + "mimeTypeIdentifier": self.mime_type_identifier, + "downloadUrl": self.download_url, + "pageTotalQuantity": self.page_total_quantity, + }
+
+ + + +
+[docs] +@dataclass(frozen=True) +class Document: + """Represents a single document associated with a patent application. + + This includes metadata such as its identifier, official date, code, description, + direction (incoming/outgoing), and available download formats. + + Attributes: + application_number_text: The application number this document belongs to. + official_date: The official date of the document. + document_identifier: A unique identifier for this document. + document_code: A code representing the type of document. + document_code_description_text: A textual description of the document code. + direction_category: The direction of the document (e.g., INCOMING, OUTGOING). + document_formats: A list of available download formats for this document. + """ + + application_number_text: Optional[str] = None + official_date: Optional[datetime] = None + document_identifier: Optional[str] = None + document_code: Optional[str] = None + document_code_description_text: Optional[str] = None + direction_category: Optional[DirectionCategory] = None + document_formats: List[DocumentFormat] = field(default_factory=list) + + def __str__(self) -> str: + date_str = ( + self.official_date.strftime("%Y-%m-%d") if self.official_date else "No date" + ) + return f"Document {self.document_identifier} ({self.document_code}): {self.document_code_description_text} - {date_str}" + + def __repr__(self) -> str: + return f"Document(id={self.document_identifier}, code={self.document_code}, date={self.official_date.strftime('%Y-%m-%d') if self.official_date else 'None'})" + +
+[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PatentDataResponse": - """Create a PatentDataResponse object from a dictionary.""" + def from_dict(cls, data: Dict[str, Any]) -> "Document": + """Creates a `Document` instance from a dictionary representation. + + Maps API JSON keys (camelCase) to class attributes, parsing nested + objects like `DocumentFormat` and `DirectionCategory`. + + Args: + data (Dict[str, Any]): A dictionary containing document data, + typically from an API response. + + Returns: + Document: An instance of `Document`. + """ + + dl_formats = [ + DocumentFormat.from_dict(f) + for f in data.get("downloadOptionBag", []) + if isinstance(f, dict) + ] + dir_val = data.get("documentDirectionCategory") + dir_cat = None + if dir_val: + try: + dir_cat = DirectionCategory(dir_val) + except ValueError: + warnings.warn( + f"Unknown document direction category '{dir_val}'", + category=USPTOEnumParseWarning, + stacklevel=2, + ) return cls( - count=data.get("count", 0), - patent_file_wrapper_data_bag=[ - PatentFileWrapper.from_dict(data=wrapper) - for wrapper in data.get("patentFileWrapperDataBag", []) - ], + application_number_text=data.get("applicationNumberText"), + official_date=parse_to_datetime_utc(data.get("officialDate")), + document_identifier=data.get("documentIdentifier"), + document_code=data.get("documentCode"), + document_code_description_text=data.get("documentCodeDescriptionText"), + direction_category=dir_cat, + document_formats=dl_formats, )
-
-[docs] +
+[docs] def to_dict(self) -> Dict[str, Any]: - """Convert the PatentDataResponse object to a dictionary.""" + """Converts the `Document` instance to a dictionary for API compatibility. + + Serializes attributes to camelCase keys and handles nested objects. + Omits keys with None values or empty lists. + + Returns: + Dict[str, Any]: A dictionary representation of the `Document`. + """ + + d = { + "applicationNumberText": self.application_number_text, + "officialDate": ( + serialize_datetime_as_iso(self.official_date) + if self.official_date + else None + ), + "documentIdentifier": self.document_identifier, + "documentCode": self.document_code, + "documentCodeDescriptionText": self.document_code_description_text, + "documentDirectionCategory": ( + self.direction_category.value if self.direction_category else None + ), + "downloadOptionBag": [df.to_dict() for df in self.document_formats], + } return { - "count": self.count, - "patentFileWrapperDataBag": [ - # If PatentFileWrapper had a to_dict method, we would use it here - # For now, we'll just return a basic representation - { - "applicationNumberText": wrapper.application_number_text, - # Add other fields as needed - } - for wrapper in self.patent_file_wrapper_data_bag - ], - # Add other fields that might be in the API response but not in our model - "documentBag": [], # Empty placeholder for document bag + k: v + for k, v in d.items() + if v is not None and (not isinstance(v, list) or v) }
+
+[docs] +class DocumentBag: + """A collection of Document objects associated with a patent application. + + Provides iterable access and standard collection methods like `len` and `getitem`. + This class is immutable by convention after initialization. + + Attributes: + documents (tuple[Document, ...]): An immutable tuple of `Document` objects. + """ + +
+[docs] + def __init__(self, documents: List[Document]): + """Initializes the DocumentBag with a list of documents. + + Args: + documents (List[Document]): A list of `Document` instances. + """ + self._documents = tuple(documents)
+ + + @property + def documents(self) -> tuple[Document, ...]: + """Provides access to the tuple of documents.""" + return self._documents + + def __iter__(self) -> Iterator[Document]: + return iter(self._documents) + + def __len__(self) -> int: + return len(self._documents) + + def __getitem__(self, index: int) -> Document: + return self._documents[index] + +
+[docs] + def __str__(self) -> str: + """Returns a string representation showing document count and summary. + + Returns: + str: Human-readable summary of the DocumentBag. + """ + count = len(self._documents) + if count == 0: + return "DocumentBag(0 documents)" + + # Count unique document codes + doc_codes: Dict[str, int] = {} + for doc in self._documents: + code = doc.document_code or "Unknown" + doc_codes[code] = doc_codes.get(code, 0) + 1 + + # Format summary + if count == 1: + code = self._documents[0].document_code or "Unknown" + return f"DocumentBag(1 document: {code})" + + # Show top 3 most common document codes + sorted_codes = sorted(doc_codes.items(), key=lambda x: x[1], reverse=True) + top_codes = sorted_codes[:3] + code_summary = ", ".join(f"{code} ({cnt})" for code, cnt in top_codes) + + if len(sorted_codes) > 3: + remaining = len(sorted_codes) - 3 + return f"DocumentBag({count} documents: {code_summary}, +{remaining} more types)" + else: + return f"DocumentBag({count} documents: {code_summary})"
+ + +
+[docs] + def __repr__(self) -> str: + """Returns a detailed string representation for debugging. + + Returns: + str: Detailed representation of the DocumentBag. + """ + return f"DocumentBag(documents={self._documents!r})"
+ + +
+[docs] + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "DocumentBag": + """Creates a `DocumentBag` instance from a dictionary representation. + + Expects a dictionary with a "documentBag" key containing a list of + document data dictionaries. + + Args: + data (Dict[str, Any]): A dictionary, typically from an API response, + containing the document bag. + + Returns: + DocumentBag: An instance of `DocumentBag`. + """ + docs_data = data.get("documentBag", []) + docs = ( + [Document.from_dict(dd) for dd in docs_data if isinstance(dd, dict)] + if isinstance(docs_data, list) + else [] + ) + return cls(documents=docs)
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `DocumentBag` instance to a dictionary. + + Serializes the collection into a dictionary with a "documentBag" key, + containing a list of `Document` dictionaries. + + Returns: + Dict[str, Any]: A dictionary representation of the `DocumentBag`. + """ + return {"documentBag": [doc.to_dict() for doc in self._documents]}
+
+ + +
[docs] -@dataclass +@dataclass(frozen=True) class Address: - """Represents an address in the patent data API.""" + """Represents a postal address with fields for street, city, region, country, and postal code. + + It can be used for various entities like applicants, inventors, or correspondence. + + Attributes: + name_line_one_text: First line of the name (e.g., company name). + name_line_two_text: Second line of the name. + address_line_one_text: First line of the street address. + address_line_two_text: Second line of the street address. + address_line_three_text: Third line of the street address. + address_line_four_text: Fourth line of the street address. + geographic_region_name: Name of the geographic region (e.g., state, province). + geographic_region_code: Code for the geographic region. + postal_code: Postal or ZIP code. + city_name: Name of the city. + country_code: Two-letter country code (e.g., "US"). + country_name: Full name of the country (e.g., "United States"). + postal_address_category: Category of the address (e.g., "MAILING_ADDRESS"). + correspondent_name_text: Name of the correspondent at this address. + country_or_state_code: Country or state code. + ict_state_code: International code for the state/region (USPTO format). + ict_country_code: International code for the country (USPTO format). + """ name_line_one_text: Optional[str] = None name_line_two_text: Optional[str] = None @@ -151,12 +515,24 @@

Source code for pyUSPTO.models.patent_data

     country_name: Optional[str] = None
     postal_address_category: Optional[str] = None
     correspondent_name_text: Optional[str] = None
+    country_or_state_code: Optional[str] = None
+    ict_state_code: Optional[str] = None
+    ict_country_code: Optional[str] = None
 
 
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Address": - """Create an Address object from a dictionary.""" + """Creates an `Address` instance from a dictionary representation. + + Maps camelCase keys from API data to class attributes. + + Args: + data (Dict[str, Any]): Dictionary containing address data. + + Returns: + Address: An instance of `Address`. + """ return cls( name_line_one_text=data.get("nameLineOneText"), name_line_two_text=data.get("nameLineTwoText"), @@ -172,16 +548,56 @@

Source code for pyUSPTO.models.patent_data

             country_name=data.get("countryName"),
             postal_address_category=data.get("postalAddressCategory"),
             correspondent_name_text=data.get("correspondentNameText"),
+            country_or_state_code=data.get("countryOrStateCode"),
+            ict_state_code=data.get("ictStateCode"),
+            ict_country_code=data.get("ictCountryCode"),
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Address` instance to a dictionary with camelCase keys. + + Returns: + Dict[str, Any]: A dictionary representation of the address. + """ + _dict = { + "nameLineOneText": self.name_line_one_text, + "nameLineTwoText": self.name_line_two_text, + "addressLineOneText": self.address_line_one_text, + "addressLineTwoText": self.address_line_two_text, + "addressLineThreeText": self.address_line_three_text, + "addressLineFourText": self.address_line_four_text, + "geographicRegionName": self.geographic_region_name, + "geographicRegionCode": self.geographic_region_code, + "postalCode": self.postal_code, + "cityName": self.city_name, + "countryCode": self.country_code, + "countryName": self.country_name, + "postalAddressCategory": self.postal_address_category, + "correspondentNameText": self.correspondent_name_text, + "countryOrStateCode": self.country_or_state_code, + "ictStateCode": self.ict_state_code, + "ictCountryCode": self.ict_country_code, + } + # Filter out None values to match API behavior + return {k: v for k, v in _dict.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class Telecommunication: - """Represents telecommunication information.""" + """Represents telecommunication details, such as phone or fax numbers. + + Attributes: + telecommunication_number: The main number (e.g., phone number). + extension_number: Any extension associated with the number. + telecom_type_code: A code indicating the type of telecommunication (e.g., "TEL", "FAX"). + """ telecommunication_number: Optional[str] = None extension_number: Optional[str] = None @@ -191,21 +607,57 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "Telecommunication":
-        """Create a Telecommunication object from a dictionary."""
+        """Creates a `Telecommunication` instance from a dictionary.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with telecommunication data.
+
+        Returns:
+            Telecommunication: An instance of `Telecommunication`.
+        """
         return cls(
             telecommunication_number=data.get("telecommunicationNumber"),
             extension_number=data.get("extensionNumber"),
             telecom_type_code=data.get("telecomTypeCode"),
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Telecommunication` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + _dict = { + "telecommunicationNumber": self.telecommunication_number, + "extensionNumber": self.extension_number, + "telecomTypeCode": self.telecom_type_code, + } + # Filter out None values to match API behavior + return {k: v for k, v in _dict.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class Person: - """Base class for person-related data.""" + """A base data class representing a person with common name and country attributes. + + This class is typically inherited by more specific types like Applicant, Inventor, or Attorney. + + Attributes: + first_name: The first name of the person. + middle_name: The middle name or initial of the person. + last_name: The last name or surname of the person. + name_prefix: A prefix for the name (e.g., "Dr.", "Mr."). + name_suffix: A suffix for the name (e.g., "Jr.", "PhD"). + preferred_name: The person's preferred name, if different. + country_code: The country code associated with the person (e.g., citizenship). + """ first_name: Optional[str] = None middle_name: Optional[str] = None @@ -215,29 +667,45 @@

Source code for pyUSPTO.models.patent_data

     preferred_name: Optional[str] = None
     country_code: Optional[str] = None
 
-
-[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Person": - """Create a Person object from a dictionary.""" - return cls( - first_name=data.get("firstName"), - middle_name=data.get("middleName"), - last_name=data.get("lastName"), - name_prefix=data.get("namePrefix"), - name_suffix=data.get("nameSuffix"), - preferred_name=data.get("preferredName"), - country_code=data.get("countryCode"), - )
+ def _extract_person_fields(cls, data: Dict[str, Any]) -> Dict[str, Any]: + return { + "first_name": data.get("firstName"), + "middle_name": data.get("middleName"), + "last_name": data.get("lastName"), + "name_prefix": data.get("namePrefix"), + "name_suffix": data.get("nameSuffix"), + "preferred_name": data.get("preferredName"), + "country_code": data.get("countryCode"), + } + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Person` instance to a dictionary with camelCase keys. + + Omits attributes that are None. + + Returns: + Dict[str, Any]: A dictionary representation of the person. + """ + return {to_camel_case(k): v for k, v in asdict(self).items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class Applicant(Person): - """Represents an applicant in the patent data.""" + """Represents an applicant for a patent, inheriting from Person. + + Includes applicant-specific name text and a list of correspondence addresses. + + Attributes: + applicant_name_text: The full name of the applicant as a single string. + correspondence_address_bag: A list of `Address` objects for the applicant. + """ applicant_name_text: Optional[str] = None correspondence_address_bag: List[Address] = field(default_factory=list) @@ -246,35 +714,70 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "Applicant":
-        """Create an Applicant object from a dictionary."""
-        person = Person.from_dict(data=data)
-        addresses = []
-        if "correspondenceAddressBag" in data:
-            addresses = [
-                Address.from_dict(data=addr)
-                for addr in data.get("correspondenceAddressBag", [])
-            ]
-
+        """Creates an `Applicant` instance from a dictionary.
+
+        Inherits person fields and adds applicant-specific fields.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with applicant data.
+
+        Returns:
+            Applicant: An instance of `Applicant`.
+        """
+        pf = Person._extract_person_fields(data)
+        addrs = [
+            Address.from_dict(a)
+            for a in data.get("correspondenceAddressBag", [])
+            if isinstance(a, dict)
+        ]
         return cls(
-            first_name=person.first_name,
-            middle_name=person.middle_name,
-            last_name=person.last_name,
-            name_prefix=person.name_prefix,
-            name_suffix=person.name_suffix,
-            preferred_name=person.preferred_name,
-            country_code=person.country_code,
+            **pf,
             applicant_name_text=data.get("applicantNameText"),
-            correspondence_address_bag=addresses,
+            correspondence_address_bag=addrs,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Applicant` instance to a dictionary. + + Includes inherited person fields and applicant-specific fields, + using camelCase keys and omitting None values or empty lists. + + Returns: + Dict[str, Any]: Dictionary representation of the applicant. + """ + d = super().to_dict() + d.update( + { + "applicantNameText": self.applicant_name_text, + "correspondenceAddressBag": [ + a.to_dict() for a in self.correspondence_address_bag + ], + } + ) + return { + k: v + for k, v in d.items() + if v is not None and (not isinstance(v, list) or v) + }
[docs] -@dataclass +@dataclass(frozen=True) class Inventor(Person): - """Represents an inventor in the patent data.""" + """Represents an inventor for a patent application, inheriting from Person. + + Includes inventor-specific name text and a list of correspondence addresses. + + Attributes: + inventor_name_text: The full name of the inventor as a single string. + correspondence_address_bag: A list of `Address` objects for the inventor. + """ inventor_name_text: Optional[str] = None correspondence_address_bag: List[Address] = field(default_factory=list) @@ -283,35 +786,73 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "Inventor":
-        """Create an Inventor object from a dictionary."""
-        person = Person.from_dict(data=data)
-        addresses = []
-        if "correspondenceAddressBag" in data:
-            addresses = [
-                Address.from_dict(data=addr)
-                for addr in data.get("correspondenceAddressBag", [])
-            ]
-
+        """Creates an `Inventor` instance from a dictionary.
+
+        Inherits person fields and adds inventor-specific fields.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with inventor data.
+
+        Returns:
+            Inventor: An instance of `Inventor`.
+        """
+        pf = Person._extract_person_fields(data)
+        addrs = [
+            Address.from_dict(a)
+            for a in data.get("correspondenceAddressBag", [])
+            if isinstance(a, dict)
+        ]
         return cls(
-            first_name=person.first_name,
-            middle_name=person.middle_name,
-            last_name=person.last_name,
-            name_prefix=person.name_prefix,
-            name_suffix=person.name_suffix,
-            preferred_name=person.preferred_name,
-            country_code=person.country_code,
+            **pf,
             inventor_name_text=data.get("inventorNameText"),
-            correspondence_address_bag=addresses,
+            correspondence_address_bag=addrs,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Inventor` instance to a dictionary. + + Includes inherited person fields and inventor-specific fields, + using camelCase keys and omitting None values or empty lists. + + Returns: + Dict[str, Any]: Dictionary representation of the inventor. + """ + d = super().to_dict() + d.update( + { + "inventorNameText": self.inventor_name_text, + "correspondenceAddressBag": [ + a.to_dict() for a in self.correspondence_address_bag + ], + } + ) + return { + k: v + for k, v in d.items() + if v is not None and (not isinstance(v, list) or v) + }
[docs] -@dataclass +@dataclass(frozen=True) class Attorney(Person): - """Represents an attorney in the patent data.""" + """Represents an attorney or agent associated with a patent application, inheriting from Person. + + Includes registration number, active status, practitioner category, addresses, and telecommunication details. + + Attributes: + registration_number: The attorney's USPTO registration number. + active_indicator: Indicates if the attorney is currently active (e.g., "Y", "N"). + registered_practitioner_category: Category of the practitioner (e.g., "ATTORNEY", "AGENT"). + attorney_address_bag: List of `Address` objects for the attorney. + telecommunication_address_bag: List of `Telecommunication` objects for the attorney. + """ registration_number: Optional[str] = None active_indicator: Optional[str] = None @@ -323,45 +864,79 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "Attorney":
-        """Create an Attorney object from a dictionary."""
-        person = Person.from_dict(data=data)
-        addresses = []
-        if "attorneyAddressBag" in data:
-            addresses = [
-                Address.from_dict(data=addr)
-                for addr in data.get("attorneyAddressBag", [])
-            ]
-
-        telecom_addresses = []
-        if "telecommunicationAddressBag" in data:
-            telecom_addresses = [
-                Telecommunication.from_dict(data=telecom)
-                for telecom in data.get("telecommunicationAddressBag", [])
-            ]
-
+        """Creates an `Attorney` instance from a dictionary.
+
+        Inherits person fields and adds attorney-specific details.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with attorney data.
+
+        Returns:
+            Attorney: An instance of `Attorney`.
+        """
+        pf = Person._extract_person_fields(data)
+        addrs = [
+            Address.from_dict(a)
+            for a in data.get("attorneyAddressBag", [])
+            if isinstance(a, dict)
+        ]
+        telecoms = [
+            Telecommunication.from_dict(t)
+            for t in data.get("telecommunicationAddressBag", [])
+            if isinstance(t, dict)
+        ]
         return cls(
-            first_name=person.first_name,
-            middle_name=person.middle_name,
-            last_name=person.last_name,
-            name_prefix=person.name_prefix,
-            name_suffix=person.name_suffix,
-            preferred_name=person.preferred_name,
-            country_code=person.country_code,
+            **pf,
             registration_number=data.get("registrationNumber"),
             active_indicator=data.get("activeIndicator"),
             registered_practitioner_category=data.get("registeredPractitionerCategory"),
-            attorney_address_bag=addresses,
-            telecommunication_address_bag=telecom_addresses,
+            attorney_address_bag=addrs,
+            telecommunication_address_bag=telecoms,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Attorney` instance to a dictionary. + + Includes inherited person fields and attorney-specific fields, + using camelCase keys and omitting None values or empty lists. + + Returns: + Dict[str, Any]: Dictionary representation of the attorney. + """ + d = super().to_dict() + d.update( + { + "registrationNumber": self.registration_number, + "activeIndicator": self.active_indicator, + "registeredPractitionerCategory": self.registered_practitioner_category, + "attorneyAddressBag": [a.to_dict() for a in self.attorney_address_bag], + "telecommunicationAddressBag": [ + t.to_dict() for t in self.telecommunication_address_bag + ], + } + ) + return { + k: v + for k, v in d.items() + if v is not None and (not isinstance(v, list) or v) + }
[docs] -@dataclass +@dataclass(frozen=True) class EntityStatus: - """Represents entity status data.""" + """Represents the entity status of an applicant (e.g., small entity status). + + Attributes: + small_entity_status_indicator: Boolean indicating if the applicant qualifies for small entity status. + business_entity_status_category: String category of the business entity status (e.g., "Undiscounted"). + """ small_entity_status_indicator: Optional[bool] = None business_entity_status_category: Optional[str] = None @@ -370,20 +945,50 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "EntityStatus":
-        """Create an EntityStatus object from a dictionary."""
+        """Creates an `EntityStatus` instance from a dictionary.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with entity status data.
+
+        Returns:
+            EntityStatus: An instance of `EntityStatus`.
+        """
         return cls(
             small_entity_status_indicator=data.get("smallEntityStatusIndicator"),
             business_entity_status_category=data.get("businessEntityStatusCategory"),
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `EntityStatus` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + return { + "smallEntityStatusIndicator": self.small_entity_status_indicator, + "businessEntityStatusCategory": self.business_entity_status_category, + }
[docs] -@dataclass +@dataclass(frozen=True) class CustomerNumberCorrespondence: - """Represents customer number correspondence data.""" + """Represents correspondence data associated with a USPTO customer number. + + Includes patron identifier, organization name, power of attorney addresses, and telecommunication details. + + Attributes: + patron_identifier: The USPTO customer number. + organization_standard_name: The name of the organization associated with the customer number. + power_of_attorney_address_bag: List of `Address` objects for power of attorney. + telecommunication_address_bag: List of `Telecommunication` objects. + """ patron_identifier: Optional[int] = None organization_standard_name: Optional[str] = None @@ -394,46 +999,76 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "CustomerNumberCorrespondence":
-        """Create a CustomerNumberCorrespondence object from a dictionary."""
-        addresses = []
-        if "powerOfAttorneyAddressBag" in data:
-            power_of_attorney_bag = data.get("powerOfAttorneyAddressBag", [])
-            # Ensure we only process dictionary objects
-            addresses = [
-                Address.from_dict(data=addr)
-                for addr in power_of_attorney_bag
-                if isinstance(addr, dict)
-            ]
-
-        telecom_addresses = []
-        if "telecommunicationAddressBag" in data:
-            telecom_bag = data.get("telecommunicationAddressBag", [])
-            # Ensure we only process dictionary objects
-            telecom_addresses = [
-                Telecommunication.from_dict(data=telecom)
-                for telecom in telecom_bag
-                if isinstance(telecom, dict)
-            ]
-
+        """Creates a `CustomerNumberCorrespondence` instance from a dictionary.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with customer number correspondence data.
+
+        Returns:
+            CustomerNumberCorrespondence: An instance of `CustomerNumberCorrespondence`.
+        """
+        addrs = [
+            Address.from_dict(a)
+            for a in data.get("powerOfAttorneyAddressBag", [])
+            if isinstance(a, dict)
+        ]
+        telecoms = [
+            Telecommunication.from_dict(t)
+            for t in data.get("telecommunicationAddressBag", [])
+            if isinstance(t, dict)
+        ]
         return cls(
             patron_identifier=data.get("patronIdentifier"),
             organization_standard_name=data.get("organizationStandardName"),
-            power_of_attorney_address_bag=addresses,
-            telecommunication_address_bag=telecom_addresses,
+            power_of_attorney_address_bag=addrs,
+            telecommunication_address_bag=telecoms,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `CustomerNumberCorrespondence` instance to a dictionary. + + Omits keys with None values or empty lists. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + d = { + "patronIdentifier": self.patron_identifier, + "organizationStandardName": self.organization_standard_name, + "powerOfAttorneyAddressBag": [ + a.to_dict() for a in self.power_of_attorney_address_bag + ], + "telecommunicationAddressBag": [ + t.to_dict() for t in self.telecommunication_address_bag + ], + } + return { + k: v + for k, v in d.items() + if v is not None and (not isinstance(v, list) or v) + }
[docs] -@dataclass +@dataclass(frozen=True) class RecordAttorney: - """Represents record attorney data.""" + """Represents information about the attorney(s) of record for a patent application. - customer_number_correspondence_data: List[CustomerNumberCorrespondence] = field( - default_factory=list - ) + Contains customer number correspondence data, power of attorney information, and listed attorneys. + + Attributes: + customer_number_correspondence_data: `CustomerNumberCorrespondence` object with customer number details. + power_of_attorney_bag: List of `Attorney` objects named in a power of attorney. + attorney_bag: List of `Attorney` objects listed as attorneys of record. + """ + + customer_number_correspondence_data: Optional[CustomerNumberCorrespondence] = None power_of_attorney_bag: List[Attorney] = field(default_factory=list) attorney_bag: List[Attorney] = field(default_factory=list) @@ -441,67 +1076,119 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "RecordAttorney":
-        """Create a RecordAttorney object from a dictionary."""
-        customer_correspondence = []
-        if "customerNumberCorrespondenceData" in data:
-            correspondence_data = data.get("customerNumberCorrespondenceData", [])
-            # Ensure we only process dictionary objects
-            customer_correspondence = [
-                CustomerNumberCorrespondence.from_dict(corr)
-                for corr in correspondence_data
-                if isinstance(corr, dict)
-            ]
-
-        power_attorneys = []
-        if "powerOfAttorneyBag" in data:
-            power_attorneys = [
-                Attorney.from_dict(data=attorney)
-                for attorney in data.get("powerOfAttorneyBag", [])
-            ]
-
-        attorneys = []
-        if "attorneyBag" in data:
-            attorneys = [
-                Attorney.from_dict(data=attorney)
-                for attorney in data.get("attorneyBag", [])
-            ]
-
+        """Creates a `RecordAttorney` instance from a dictionary.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with record attorney data.
+
+        Returns:
+            RecordAttorney: An instance of `RecordAttorney`.
+        """
+        cust_corr_data = data.get("customerNumberCorrespondenceData")
+        cust_corr = (
+            CustomerNumberCorrespondence.from_dict(cust_corr_data)
+            if isinstance(cust_corr_data, dict)
+            else None
+        )
+        poa_bag = [
+            Attorney.from_dict(a)
+            for a in data.get("powerOfAttorneyBag", [])
+            if isinstance(a, dict)
+        ]
+        att_bag = [
+            Attorney.from_dict(a)
+            for a in data.get("attorneyBag", [])
+            if isinstance(a, dict)
+        ]
         return cls(
-            customer_number_correspondence_data=customer_correspondence,
-            power_of_attorney_bag=power_attorneys,
-            attorney_bag=attorneys,
+            customer_number_correspondence_data=cust_corr,
+            power_of_attorney_bag=poa_bag,
+            attorney_bag=att_bag,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `RecordAttorney` instance to a dictionary. + + Omits keys with None values. Includes empty lists to match API behavior. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + d = { + "customerNumberCorrespondenceData": ( + self.customer_number_correspondence_data.to_dict() + if self.customer_number_correspondence_data + else None + ), + "powerOfAttorneyBag": [p.to_dict() for p in self.power_of_attorney_bag], + "attorneyBag": [a.to_dict() for a in self.attorney_bag], + } + return {k: v for k, v in d.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class Assignor: - """Represents an assignor in an assignment.""" + """Represents an assignor in a patent assignment. + + Attributes: + assignor_name: The name of the assigning party. + execution_date: The date the assignment was executed. + """ assignor_name: Optional[str] = None - execution_date: Optional[str] = None + execution_date: Optional[date] = None
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Assignor": - """Create an Assignor object from a dictionary.""" + """Creates an `Assignor` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with assignor data. + + Returns: + Assignor: An instance of `Assignor`. + """ return cls( assignor_name=data.get("assignorName"), - execution_date=data.get("executionDate"), + execution_date=parse_to_date(data.get("executionDate")), )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Assignor` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + return { + "assignorName": self.assignor_name, + "executionDate": serialize_date(self.execution_date), + }
[docs] -@dataclass +@dataclass(frozen=True) class Assignee: - """Represents an assignee in an assignment.""" + """Represents an assignee in a patent assignment. + + Attributes: + assignee_name_text: The name of the party receiving the assignment. + assignee_address: The `Address` of the assignee. + """ assignee_name_text: Optional[str] = None assignee_address: Optional[Address] = None @@ -510,130 +1197,314 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "Assignee":
-        """Create an Assignee object from a dictionary."""
-        address = None
-        if "assigneeAddress" in data and data.get("assigneeAddress") is not None:
-            address = Address.from_dict(data=data.get("assigneeAddress", {}))
+        """Creates an `Assignee` instance from a dictionary.
 
+        Args:
+            data (Dict[str, Any]): Dictionary with assignee data.
+
+        Returns:
+            Assignee: An instance of `Assignee`.
+        """
+        addr_data = data.get("assigneeAddress")
+        addr = Address.from_dict(addr_data) if isinstance(addr_data, dict) else None
         return cls(
-            assignee_name_text=data.get("assigneeNameText"), assignee_address=address
+            assignee_name_text=data.get("assigneeNameText"), assignee_address=addr
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Assignee` instance to a dictionary. + + Omits keys with None values. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + d = { + "assigneeNameText": self.assignee_name_text, + "assigneeAddress": ( + self.assignee_address.to_dict() if self.assignee_address else None + ), + } + return {k: v for k, v in d.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class Assignment: - """Represents an assignment in the patent data.""" + """Represents a patent assignment, detailing the transfer of rights. + + Includes information about the reel and frame, document location, dates, conveyance text, + and bags of assignors, assignees, correspondence address, and domestic representative. + + Attributes: + reel_number: Reel number for the assignment record. + frame_number: Frame number for the assignment record. + reel_and_frame_number: Combined reel and frame number. + page_total_quantity: Total number of pages in the assignment document. + assignment_document_location_uri: URI for the assignment document. + assignment_received_date: Date the assignment was received by USPTO. + assignment_recorded_date: Date the assignment was recorded by USPTO. + assignment_mailed_date: Date the assignment notification was mailed. + conveyance_text: Text describing the nature of the conveyance. + image_available_status_code: Code to indicate the availability of the image. + attorney_docket_number: Attorney docket number for the assignment. + assignor_bag: List of `Assignor` objects. + assignee_bag: List of `Assignee` objects. + correspondence_address: `Address` object for correspondence (single object). + domestic_representative: `Address` object for the domestic representative. + """ reel_number: Optional[int] = None frame_number: Optional[int] = None reel_and_frame_number: Optional[str] = None + page_total_quantity: Optional[int] = None assignment_document_location_uri: Optional[str] = None - assignment_received_date: Optional[str] = None - assignment_recorded_date: Optional[str] = None - assignment_mailed_date: Optional[str] = None + assignment_received_date: Optional[date] = None + assignment_recorded_date: Optional[date] = None + assignment_mailed_date: Optional[date] = None conveyance_text: Optional[str] = None + image_available_status_code: Optional[bool] = None + attorney_docket_number: Optional[str] = None assignor_bag: List[Assignor] = field(default_factory=list) assignee_bag: List[Assignee] = field(default_factory=list) - correspondence_address_bag: List[Address] = field(default_factory=list) + correspondence_address: Optional[Address] = None + domestic_representative: Optional[Address] = None
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Assignment": - """Create an Assignment object from a dictionary.""" - assignors = [] - if "assignorBag" in data: - assignors = [ - Assignor.from_dict(data=assignor) - for assignor in data.get("assignorBag", []) - ] - - assignees = [] - if "assigneeBag" in data: - assignees = [ - Assignee.from_dict(data=assignee) - for assignee in data.get("assigneeBag", []) - ] - - addresses = [] - if "correspondenceAddressBag" in data: - addresses = [ - Address.from_dict(data=addr) - for addr in data.get("correspondenceAddressBag", []) - ] + """Creates an `Assignment` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with assignment data. + + Returns: + Assignment: An instance of `Assignment`. + """ + assignors = [ + Assignor.from_dict(a) + for a in data.get("assignorBag", []) + if isinstance(a, dict) + ] + assignees = [ + Assignee.from_dict(a) + for a in data.get("assigneeBag", []) + if isinstance(a, dict) + ] + + # Parse correspondence address (single object, not bag) + corr_addr_data = data.get("correspondenceAddress") + corr_addr = ( + Address.from_dict(corr_addr_data) + if isinstance(corr_addr_data, dict) + else None + ) + + # Parse domestic representative + dom_rep_data = data.get("domesticRepresentative") + dom_rep = ( + Address.from_dict(dom_rep_data) if isinstance(dom_rep_data, dict) else None + ) return cls( reel_number=data.get("reelNumber"), frame_number=data.get("frameNumber"), reel_and_frame_number=data.get("reelAndFrameNumber"), + page_total_quantity=data.get("pageTotalQuantity"), assignment_document_location_uri=data.get("assignmentDocumentLocationURI"), - assignment_received_date=data.get("assignmentReceivedDate"), - assignment_recorded_date=data.get("assignmentRecordedDate"), - assignment_mailed_date=data.get("assignmentMailedDate"), + assignment_received_date=parse_to_date(data.get("assignmentReceivedDate")), + assignment_recorded_date=parse_to_date(data.get("assignmentRecordedDate")), + assignment_mailed_date=parse_to_date(data.get("assignmentMailedDate")), conveyance_text=data.get("conveyanceText"), + image_available_status_code=data.get("imageAvailableStatusCode"), + attorney_docket_number=data.get("attorneyDocketNumber"), assignor_bag=assignors, assignee_bag=assignees, - correspondence_address_bag=addresses, + correspondence_address=corr_addr, + domestic_representative=dom_rep, )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Assignment` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + _dict = { + "reelNumber": self.reel_number, + "frameNumber": self.frame_number, + "reelAndFrameNumber": self.reel_and_frame_number, + "pageTotalQuantity": self.page_total_quantity, + "assignmentDocumentLocationURI": self.assignment_document_location_uri, + "assignmentReceivedDate": serialize_date(self.assignment_received_date), + "assignmentRecordedDate": serialize_date(self.assignment_recorded_date), + "assignmentMailedDate": serialize_date(self.assignment_mailed_date), + "conveyanceText": self.conveyance_text, + "imageAvailableStatusCode": self.image_available_status_code, + "attorneyDocketNumber": self.attorney_docket_number, + "assignorBag": [a.to_dict() for a in self.assignor_bag], + "assigneeBag": [a.to_dict() for a in self.assignee_bag], + "correspondenceAddress": ( + self.correspondence_address.to_dict() + if self.correspondence_address + else None + ), + "domesticRepresentative": ( + self.domestic_representative.to_dict() + if self.domestic_representative + else None + ), + } + # Filter out None values to match API behavior + return {k: v for k, v in _dict.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class ForeignPriority: - """Represents foreign priority information.""" + """Represents a foreign priority claim for a patent application. + + Attributes: + ip_office_name: The name of the intellectual property office of the priority application. + filing_date: The filing date of the priority application. + application_number_text: The application number of the priority application. + """ ip_office_name: Optional[str] = None - filing_date: Optional[str] = None + filing_date: Optional[date] = None application_number_text: Optional[str] = None
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "ForeignPriority": - """Create a ForeignPriority object from a dictionary.""" + """Creates a `ForeignPriority` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with foreign priority data. + + Returns: + ForeignPriority: An instance of `ForeignPriority`. + """ return cls( ip_office_name=data.get("ipOfficeName"), - filing_date=data.get("filingDate"), + filing_date=parse_to_date(data.get("filingDate")), application_number_text=data.get("applicationNumberText"), )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `ForeignPriority` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + return { + "ipOfficeName": self.ip_office_name, + "filingDate": serialize_date(self.filing_date), + "applicationNumberText": self.application_number_text, + }
[docs] -@dataclass +@dataclass(frozen=True) class Continuity: - """Base class for continuity information.""" + """Base class representing continuity data for a patent application. + + This includes details about the application's relationship to other applications (parent/child), + its filing status under AIA (America Invents Act), and key identifiers. + + Attributes: + first_inventor_to_file_indicator: Boolean indicating if the application is under First-Inventor-to-File provisions. + application_number_text: The application number of the related (parent or child) application. + filing_date: The filing date of the related application. + status_code: The status code of the related application. + status_description_text: The status description of the related application. + patent_number: The patent number if the related application is granted. + claim_parentage_type_code: Code indicating the type of continuity claim (e.g., "CON", "DIV"). + claim_parentage_type_code_description_text: Description of the continuity claim type. + """ first_inventor_to_file_indicator: Optional[bool] = None application_number_text: Optional[str] = None - filing_date: Optional[str] = None + filing_date: Optional[date] = None status_code: Optional[int] = None status_description_text: Optional[str] = None patent_number: Optional[str] = None claim_parentage_type_code: Optional[str] = None - claim_parentage_type_code_description_text: Optional[str] = None
+ claim_parentage_type_code_description_text: Optional[str] = None + + @property + def is_aia(self) -> Optional[bool]: + """Returns True if the application is AIA, False if pre-AIA, None if unknown.""" + return self.first_inventor_to_file_indicator + + @property + def is_pre_aia(self) -> Optional[bool]: + """Returns True if the application is pre-AIA, False if AIA, None if unknown.""" + if self.first_inventor_to_file_indicator is None: + return None + return not self.first_inventor_to_file_indicator + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `Continuity` instance to a dictionary. + + Omits attributes that are None and property-derived fields. + Keys are converted to camelCase. + + Returns: + Dict[str, Any]: A dictionary representation of the continuity data. + """ + return { + to_camel_case(k): v + for k, v in asdict(self).items() + if v is not None and not k.startswith("is_") + }
+
[docs] -@dataclass +@dataclass(frozen=True) class ParentContinuity(Continuity): - """Represents parent continuity information.""" + """Represents a parent application in a patent application's continuity chain. + + Inherits from Continuity and adds specific fields for parent application details. + + Attributes: + parent_application_status_code: Status code of the parent application. + parent_patent_number: Patent number of the parent application, if granted. + parent_application_status_description_text: Status description of the parent application. + parent_application_filing_date: Filing date of the parent application. + parent_application_number_text: Application number of the parent application. + child_application_number_text: Application number of the child (current) application. + """ parent_application_status_code: Optional[int] = None parent_patent_number: Optional[str] = None parent_application_status_description_text: Optional[str] = None - parent_application_filing_date: Optional[str] = None + parent_application_filing_date: Optional[date] = None parent_application_number_text: Optional[str] = None child_application_number_text: Optional[str] = None @@ -641,7 +1512,15 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "ParentContinuity":
-        """Create a ParentContinuity object from a dictionary."""
+        """Creates a `ParentContinuity` instance from a dictionary.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with parent continuity data.
+
+        Returns:
+            ParentContinuity: An instance of `ParentContinuity`.
+        """
+        p_filing_date = parse_to_date(data.get("parentApplicationFilingDate"))
         return cls(
             first_inventor_to_file_indicator=data.get("firstInventorToFileIndicator"),
             parent_application_status_code=data.get("parentApplicationStatusCode"),
@@ -649,42 +1528,87 @@ 

Source code for pyUSPTO.models.patent_data

             parent_application_status_description_text=data.get(
                 "parentApplicationStatusDescriptionText"
             ),
-            parent_application_filing_date=data.get("parentApplicationFilingDate"),
+            parent_application_filing_date=p_filing_date,
             parent_application_number_text=data.get("parentApplicationNumberText"),
             child_application_number_text=data.get("childApplicationNumberText"),
             claim_parentage_type_code=data.get("claimParentageTypeCode"),
             claim_parentage_type_code_description_text=data.get(
                 "claimParentageTypeCodeDescriptionText"
             ),
-            # Map parent-specific fields to base class fields
+            application_number_text=data.get("parentApplicationNumberText"),
+            filing_date=p_filing_date,
             status_code=data.get("parentApplicationStatusCode"),
             status_description_text=data.get("parentApplicationStatusDescriptionText"),
-            filing_date=data.get("parentApplicationFilingDate"),
-            application_number_text=data.get("parentApplicationNumberText"),
             patent_number=data.get("parentPatentNumber"),
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `ParentContinuity` instance to a dictionary. + + Maps attributes to specific camelCase keys expected by the API for parent continuity. + Filters out None values to match the API response structure. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + _dict = { + "firstInventorToFileIndicator": self.first_inventor_to_file_indicator, + "parentApplicationStatusCode": self.parent_application_status_code, + "parentPatentNumber": self.parent_patent_number, + "parentApplicationStatusDescriptionText": self.parent_application_status_description_text, + "parentApplicationFilingDate": serialize_date( + self.parent_application_filing_date + ), + "parentApplicationNumberText": self.parent_application_number_text, + "childApplicationNumberText": self.child_application_number_text, + "claimParentageTypeCode": self.claim_parentage_type_code, + "claimParentageTypeCodeDescriptionText": self.claim_parentage_type_code_description_text, + } + return {k: v for k, v in _dict.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class ChildContinuity(Continuity): - """Represents child continuity information.""" + """Represents a child application in a patent application's continuity chain. + + Inherits from Continuity and adds specific fields for child application details. + + Attributes: + child_application_status_code: Status code of the child application. + parent_application_number_text: Application number of the parent (current) application. + child_application_number_text: Application number of the child application. + child_application_status_description_text: Status description of the child application. + child_application_filing_date: Filing date of the child application. + child_patent_number: Patent number of the child application, if granted. + """ child_application_status_code: Optional[int] = None parent_application_number_text: Optional[str] = None child_application_number_text: Optional[str] = None child_application_status_description_text: Optional[str] = None - child_application_filing_date: Optional[str] = None + child_application_filing_date: Optional[date] = None child_patent_number: Optional[str] = None
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "ChildContinuity": - """Create a ChildContinuity object from a dictionary.""" + """Creates a `ChildContinuity` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with child continuity data. + + Returns: + ChildContinuity: An instance of `ChildContinuity`. + """ + c_filing_date = parse_to_date(data.get("childApplicationFilingDate")) return cls( first_inventor_to_file_indicator=data.get("firstInventorToFileIndicator"), child_application_status_code=data.get("childApplicationStatusCode"), @@ -693,73 +1617,163 @@

Source code for pyUSPTO.models.patent_data

             child_application_status_description_text=data.get(
                 "childApplicationStatusDescriptionText"
             ),
-            child_application_filing_date=data.get("childApplicationFilingDate"),
+            child_application_filing_date=c_filing_date,
             child_patent_number=data.get("childPatentNumber"),
             claim_parentage_type_code=data.get("claimParentageTypeCode"),
             claim_parentage_type_code_description_text=data.get(
                 "claimParentageTypeCodeDescriptionText"
             ),
-            # Map child-specific fields to base class fields
+            application_number_text=data.get("childApplicationNumberText"),
+            filing_date=c_filing_date,
             status_code=data.get("childApplicationStatusCode"),
             status_description_text=data.get("childApplicationStatusDescriptionText"),
-            filing_date=data.get("childApplicationFilingDate"),
-            application_number_text=data.get("childApplicationNumberText"),
             patent_number=data.get("childPatentNumber"),
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `ChildContinuity` instance to a dictionary. + + Maps attributes to specific camelCase keys expected by the API for child continuity. + Filters out None values to match the API response structure. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + _dict = { + "childApplicationStatusCode": self.child_application_status_code, + "parentApplicationNumberText": self.parent_application_number_text, + "childApplicationNumberText": self.child_application_number_text, + "childApplicationStatusDescriptionText": self.child_application_status_description_text, + "childApplicationFilingDate": serialize_date( + self.child_application_filing_date + ), + "firstInventorToFileIndicator": self.first_inventor_to_file_indicator, + "childPatentNumber": self.child_patent_number, + "claimParentageTypeCode": self.claim_parentage_type_code, + "claimParentageTypeCodeDescriptionText": self.claim_parentage_type_code_description_text, + } + return {k: v for k, v in _dict.items() if v is not None}
[docs] -@dataclass +@dataclass(frozen=True) class PatentTermAdjustmentHistoryData: - """Represents patent term adjustment history data.""" + """Represents a single entry in the patent term adjustment (PTA) history for an application. + + Details specific events, dates, and day quantities affecting the patent term. + + Attributes: + event_date: Date of the PTA event. + applicant_day_delay_quantity: Number of days of delay attributable to the applicant for this event. + event_description_text: Textual description of the PTA event. + event_sequence_number: Sequence number of this event in the PTA history. + originating_event_sequence_number: Sequence number of an event that originated this event. + pta_pte_code: Code indicating if the event relates to PTA or Patent Term Extension (PTE). + ip_office_day_delay_quantity: Number of days of IP office delay used in adjustment calculation for this event. + """ - event_date: Optional[str] = None + event_date: Optional[date] = None applicant_day_delay_quantity: Optional[float] = None event_description_text: Optional[str] = None event_sequence_number: Optional[float] = None - ip_office_day_delay_quantity: Optional[float] = None originating_event_sequence_number: Optional[float] = None pta_pte_code: Optional[str] = None + ip_office_day_delay_quantity: Optional[float] = None
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentHistoryData": - """Create a PatentTermAdjustmentHistoryData object from a dictionary.""" + """Creates a `PatentTermAdjustmentHistoryData` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with PTA history event data. + + Returns: + PatentTermAdjustmentHistoryData: An instance of `PatentTermAdjustmentHistoryData`. + """ return cls( - event_date=data.get("eventDate"), + event_date=parse_to_date(data.get("eventDate")), applicant_day_delay_quantity=data.get("applicantDayDelayQuantity"), event_description_text=data.get("eventDescriptionText"), event_sequence_number=data.get("eventSequenceNumber"), - ip_office_day_delay_quantity=data.get("ipOfficeDayDelayQuantity"), originating_event_sequence_number=data.get( "originatingEventSequenceNumber" ), pta_pte_code=data.get("ptaPTECode"), + ip_office_day_delay_quantity=data.get("ipOfficeDayDelayQuantity"), )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `PatentTermAdjustmentHistoryData` instance to a dictionary. + + Omits keys with None values. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + final_dict: Dict[str, Any] = {} + if self.event_date is not None: + final_dict["eventDate"] = serialize_date(self.event_date) + if self.applicant_day_delay_quantity is not None: + final_dict["applicantDayDelayQuantity"] = self.applicant_day_delay_quantity + if self.event_description_text is not None: + final_dict["eventDescriptionText"] = self.event_description_text + if self.event_sequence_number is not None: + final_dict["eventSequenceNumber"] = self.event_sequence_number + if self.originating_event_sequence_number is not None: + final_dict["originatingEventSequenceNumber"] = ( + self.originating_event_sequence_number + ) + if self.pta_pte_code is not None: + final_dict["ptaPTECode"] = self.pta_pte_code + if self.ip_office_day_delay_quantity is not None: + final_dict["ipOfficeDayDelayQuantity"] = self.ip_office_day_delay_quantity + return final_dict
[docs] -@dataclass +@dataclass(frozen=True) class PatentTermAdjustmentData: - """Represents patent term adjustment data.""" + """Represents the overall patent term adjustment (PTA) data for an application. + + Includes various delay quantities (A, B, C, applicant, IP office), total adjustment, + and a history of PTA events. + + Attributes: + a_delay_quantity: Number of days of 'A' delay. + adjustment_total_quantity: Total calculated PTA in days. + applicant_day_delay_quantity: Total days of delay attributable to the applicant. + b_delay_quantity: Number of days of 'B' delay. + c_delay_quantity: Number of days of 'C' delay. + non_overlapping_day_quantity: Number of non-overlapping delay days. + overlapping_day_quantity: Number of overlapping delay days. + non_overlapping_day_delay_quantity: Number of non-overlapping delay days specifically for delay calculation. + ip_office_adjustment_delay_quantity: Days of IP office delay used in adjustment calculation. + patent_term_adjustment_history_data_bag: List of `PatentTermAdjustmentHistoryData` events. + """ a_delay_quantity: Optional[float] = None adjustment_total_quantity: Optional[float] = None applicant_day_delay_quantity: Optional[float] = None b_delay_quantity: Optional[float] = None c_delay_quantity: Optional[float] = None - filing_date: Optional[str] = None - grant_date: Optional[str] = None non_overlapping_day_quantity: Optional[float] = None overlapping_day_quantity: Optional[float] = None - ip_office_day_delay_quantity: Optional[float] = None + non_overlapping_day_delay_quantity: Optional[float] = None + ip_office_adjustment_delay_quantity: Optional[float] = None patent_term_adjustment_history_data_bag: List[PatentTermAdjustmentHistoryData] = ( field(default_factory=list) ) @@ -768,104 +1782,246 @@

Source code for pyUSPTO.models.patent_data

 [docs]
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentData":
-        """Create a PatentTermAdjustmentData object from a dictionary."""
-        history_data = []
-        if "patentTermAdjustmentHistoryDataBag" in data:
-            history_data = [
-                PatentTermAdjustmentHistoryData.from_dict(history)
-                for history in data.get("patentTermAdjustmentHistoryDataBag", [])
-            ]
-
+        """Creates a `PatentTermAdjustmentData` instance from a dictionary.
+
+        Args:
+            data (Dict[str, Any]): Dictionary with PTA data.
+
+        Returns:
+            PatentTermAdjustmentData: An instance of `PatentTermAdjustmentData`.
+        """
+        history = [
+            PatentTermAdjustmentHistoryData.from_dict(h)
+            for h in data.get("patentTermAdjustmentHistoryDataBag", [])
+            if isinstance(h, dict)
+        ]
         return cls(
             a_delay_quantity=data.get("aDelayQuantity"),
             adjustment_total_quantity=data.get("adjustmentTotalQuantity"),
             applicant_day_delay_quantity=data.get("applicantDayDelayQuantity"),
             b_delay_quantity=data.get("bDelayQuantity"),
             c_delay_quantity=data.get("cDelayQuantity"),
-            filing_date=data.get("filingDate"),
-            grant_date=data.get("grantDate"),
             non_overlapping_day_quantity=data.get("nonOverlappingDayQuantity"),
             overlapping_day_quantity=data.get("overlappingDayQuantity"),
-            ip_office_day_delay_quantity=data.get("ipOfficeDayDelayQuantity"),
-            patent_term_adjustment_history_data_bag=history_data,
+            non_overlapping_day_delay_quantity=data.get(
+                "nonOverlappingDayDelayQuantity"
+            ),
+            ip_office_adjustment_delay_quantity=data.get(
+                "ipOfficeAdjustmentDelayQuantity"
+            ),
+            patent_term_adjustment_history_data_bag=history,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `PatentTermAdjustmentData` instance to a dictionary. + + Omits keys with None values or empty lists, and converts field names to camelCase. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + d = asdict(self) + d["patentTermAdjustmentHistoryDataBag"] = [ + h.to_dict() for h in self.patent_term_adjustment_history_data_bag + ] + return { + to_camel_case(k): v + for k, v in d.items() + if v is not None and (not isinstance(v, list) or v) + }
-
-[docs] -@dataclass -class Event: - """Represents an event in the patent data.""" +
+[docs] +@dataclass(frozen=True) +class EventData: + """Represents a single event in the transaction history of a patent application. + + Attributes: + event_code: A code identifying the type of event. + event_description_text: A textual description of the event. + event_date: The date the event was recorded. + """ event_code: Optional[str] = None event_description_text: Optional[str] = None - event_date: Optional[str] = None + event_date: Optional[date] = None -
-[docs] +
+[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Event": - """Create an Event object from a dictionary.""" + def from_dict(cls, data: Dict[str, Any]) -> "EventData": + """Creates an `EventData` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with event data. + + Returns: + EventData: An instance of `EventData`. + """ return cls( event_code=data.get("eventCode"), event_description_text=data.get("eventDescriptionText"), - event_date=data.get("eventDate"), + event_date=parse_to_date(data.get("eventDate")), )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `EventData` instance to a dictionary. + + Omits keys with None values and converts field names to camelCase. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + d = asdict(self) + d["eventDate"] = serialize_date(self.event_date) + return {to_camel_case(k): v for k, v in d.items() if v is not None}
-
-[docs] -@dataclass -class DocumentMetaData: - """Represents document metadata.""" +
+[docs] +@dataclass(frozen=True) +class PrintedMetaData: + """Represents metadata for a specific archive file, such as a PGPUB or Grant XML file. + + Attributes: + zip_file_name: The name of the ZIP archive. + product_identifier: An identifier for the data product (e.g., "APPXML", "PTGRXML"). + file_location_uri: The URI where the document file can be accessed. + file_create_date_time: The creation timestamp of the document file (UTC). + xml_file_name: The name of the XML file within the ZIP archive. + """ zip_file_name: Optional[str] = None product_identifier: Optional[str] = None file_location_uri: Optional[str] = None - file_create_date_time: Optional[str] = None + file_create_date_time: Optional[datetime] = None xml_file_name: Optional[str] = None -
-[docs] +
+[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "DocumentMetaData": - """Create a DocumentMetaData object from a dictionary.""" + def from_dict(cls, data: Dict[str, Any]) -> "PrintedMetaData": + """Creates a `PrintedMetaData` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with printed metadata. + + Returns: + PrintedMetaData: An instance of `PrintedMetaData`. + """ return cls( zip_file_name=data.get("zipFileName"), product_identifier=data.get("productIdentifier"), file_location_uri=data.get("fileLocationURI"), - file_create_date_time=data.get("fileCreateDateTime"), + file_create_date_time=parse_to_datetime_utc(data.get("fileCreateDateTime")), xml_file_name=data.get("xmlFileName"), )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `PrintedMetaData` instance to a dictionary. + + Omits keys with None values. Serializes datetime to ISO format with 'Z'. + + Returns: + Dict[str, Any]: Dictionary representation with camelCase keys. + """ + final_dict: Dict[str, Any] = {} + if self.zip_file_name is not None: + final_dict["zipFileName"] = self.zip_file_name + if self.product_identifier is not None: + final_dict["productIdentifier"] = self.product_identifier + if self.file_location_uri is not None: + final_dict["fileLocationURI"] = self.file_location_uri + if self.file_create_date_time is not None: + final_dict["fileCreateDateTime"] = serialize_datetime_as_naive( + self.file_create_date_time + ) + if self.xml_file_name is not None: + final_dict["xmlFileName"] = self.xml_file_name + return final_dict
[docs] -@dataclass +@dataclass(frozen=True) class ApplicationMetaData: - """Represents application metadata.""" + """Represents the metadata associated with a patent application. + + This class holds a wide range of information including application status, + dates (filing, grant, publication), applicant and inventor details, + classification data, and other identifying information. + + Attributes: + national_stage_indicator: Indicates if the application is a national stage entry. + entity_status_data: `EntityStatus` object detailing applicant's entity status. + publication_date_bag: List of publication dates. + publication_sequence_number_bag: List of publication sequence numbers. + publication_category_bag: List of publication categories. + docket_number: Applicant's or attorney's docket number. + first_inventor_to_file_indicator: Boolean indicating if under First-Inventor-to-File. + first_applicant_name: Name of the first listed applicant. + first_inventor_name: Name of the first listed inventor. + application_confirmation_number: USPTO confirmation number for the application. + application_status_date: Date the current application status was set. + application_status_description_text: Textual description of the current application status. + filing_date: Official filing date of the application. + effective_filing_date: Effective filing date, considering priority claims. + grant_date: Date the patent was granted, if applicable. + group_art_unit_number: USPTO Group Art Unit number. + application_type_code: Code for the application type. + application_type_label_name: Label for the application type (e.g., "Utility"). + application_type_category: Category of the application type. + invention_title: Title of the invention. + patent_number: USPTO patent number, if granted. + application_status_code: Numeric code for the application status. + earliest_publication_number: Number of the earliest pre-grant publication. + earliest_publication_date: Date of the earliest pre-grant publication. + pct_publication_number: PCT publication number, if applicable. + pct_publication_date: PCT publication date, if applicable. + international_registration_publication_date: Date of international registration publication. + international_registration_number: International registration number. + examiner_name_text: Name of the patent examiner. + class_field: USPC main classification. (Named `class_field` to avoid keyword clash). + subclass: USPC subclass. + uspc_symbol_text: Full USPC classification symbol. + customer_number: USPTO customer number associated with the application. + cpc_classification_bag: List of CPC classification symbols. + applicant_bag: List of `Applicant` objects. + inventor_bag: List of `Inventor` objects. + raw_data: Raw JSON string of the data used to create this instance (for debugging). + """ national_stage_indicator: Optional[bool] = None entity_status_data: Optional[EntityStatus] = None - publication_date_bag: List[str] = field(default_factory=list) + publication_date_bag: List[date] = field(default_factory=list) publication_sequence_number_bag: List[str] = field(default_factory=list) publication_category_bag: List[str] = field(default_factory=list) docket_number: Optional[str] = None - first_inventor_to_file_indicator: Optional[str] = None + first_inventor_to_file_indicator: Optional[bool] = None first_applicant_name: Optional[str] = None first_inventor_name: Optional[str] = None application_confirmation_number: Optional[int] = None - application_status_date: Optional[str] = None + application_status_date: Optional[date] = None application_status_description_text: Optional[str] = None - filing_date: Optional[str] = None - effective_filing_date: Optional[str] = None - grant_date: Optional[str] = None + filing_date: Optional[date] = None + effective_filing_date: Optional[date] = None + grant_date: Optional[date] = None group_art_unit_number: Optional[str] = None application_type_code: Optional[str] = None application_type_label_name: Optional[str] = None @@ -874,65 +2030,91 @@

Source code for pyUSPTO.models.patent_data

     patent_number: Optional[str] = None
     application_status_code: Optional[int] = None
     earliest_publication_number: Optional[str] = None
-    earliest_publication_date: Optional[str] = None
+    earliest_publication_date: Optional[date] = None
     pct_publication_number: Optional[str] = None
-    pct_publication_date: Optional[str] = None
-    international_registration_publication_date: Optional[str] = None
+    pct_publication_date: Optional[date] = None
+    international_registration_publication_date: Optional[date] = None
     international_registration_number: Optional[str] = None
     examiner_name_text: Optional[str] = None
-    class_field: Optional[str] = None  # 'class' is a reserved keyword
+    class_field: Optional[str] = None
     subclass: Optional[str] = None
     uspc_symbol_text: Optional[str] = None
     customer_number: Optional[int] = None
     cpc_classification_bag: List[str] = field(default_factory=list)
     applicant_bag: List[Applicant] = field(default_factory=list)
     inventor_bag: List[Inventor] = field(default_factory=list)
+    raw_data: Optional[str] = field(default=None, compare=False)
+
+    @property
+    def is_aia(self) -> Optional[bool]:
+        """Returns True if the application is AIA, False if pre-AIA, None if unknown."""
+        return self.first_inventor_to_file_indicator
+
+    @property
+    def is_pre_aia(self) -> Optional[bool]:
+        """Returns True if the application is pre-AIA, False if AIA, None if unknown."""
+        if self.first_inventor_to_file_indicator is None:
+            return None
+        return not self.first_inventor_to_file_indicator
 
 
[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ApplicationMetaData": - """Create an ApplicationMetaData object from a dictionary.""" - entity_status = None - if "entityStatusData" in data and data.get("entityStatusData") is not None: - entity_status = EntityStatus.from_dict( - data=data.get("entityStatusData", {}) - ) - - applicants = [] - if "applicantBag" in data: - applicants = [ - Applicant.from_dict(data=applicant) - for applicant in data.get("applicantBag", []) - ] - - inventors = [] - if "inventorBag" in data: - inventors = [ - Inventor.from_dict(data=inventor) - for inventor in data.get("inventorBag", []) - ] - + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "ApplicationMetaData": + """Creates an `ApplicationMetaData` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with application metadata. + include_raw_data (bool): If True, store the raw JSON for debugging. + + Returns: + ApplicationMetaData: An instance of `ApplicationMetaData`. + """ + entity = ( + EntityStatus.from_dict(data["entityStatusData"]) + if isinstance(data.get("entityStatusData"), dict) + else None + ) + app_bag = [ + Applicant.from_dict(a) + for a in data.get("applicantBag", []) + if isinstance(a, dict) + ] + inv_bag = [ + Inventor.from_dict(i) + for i in data.get("inventorBag", []) + if isinstance(i, dict) + ] + pub_dates_str = data.get("publicationDateBag", []) + pub_dates = ( + [parse_to_date(d) for d in pub_dates_str if isinstance(d, str)] + if isinstance(pub_dates_str, list) + else [] + ) + fitf_indicator_str = data.get("firstInventorToFileIndicator") + fitf_indicator_bool = parse_yn_to_bool(fitf_indicator_str) return cls( national_stage_indicator=data.get("nationalStageIndicator"), - entity_status_data=entity_status, - publication_date_bag=data.get("publicationDateBag", []), + entity_status_data=entity, + publication_date_bag=[d for d in pub_dates if d is not None], publication_sequence_number_bag=data.get( "publicationSequenceNumberBag", [] ), publication_category_bag=data.get("publicationCategoryBag", []), docket_number=data.get("docketNumber"), - first_inventor_to_file_indicator=data.get("firstInventorToFileIndicator"), + first_inventor_to_file_indicator=fitf_indicator_bool, first_applicant_name=data.get("firstApplicantName"), first_inventor_name=data.get("firstInventorName"), application_confirmation_number=data.get("applicationConfirmationNumber"), - application_status_date=data.get("applicationStatusDate"), + application_status_date=parse_to_date(data.get("applicationStatusDate")), application_status_description_text=data.get( "applicationStatusDescriptionText" ), - filing_date=data.get("filingDate"), - effective_filing_date=data.get("effectiveFilingDate"), - grant_date=data.get("grantDate"), + filing_date=parse_to_date(data.get("filingDate")), + effective_filing_date=parse_to_date(data.get("effectiveFilingDate")), + grant_date=parse_to_date(data.get("grantDate")), group_art_unit_number=data.get("groupArtUnitNumber"), application_type_code=data.get("applicationTypeCode"), application_type_label_name=data.get("applicationTypeLabelName"), @@ -941,33 +2123,134 @@

Source code for pyUSPTO.models.patent_data

             patent_number=data.get("patentNumber"),
             application_status_code=data.get("applicationStatusCode"),
             earliest_publication_number=data.get("earliestPublicationNumber"),
-            earliest_publication_date=data.get("earliestPublicationDate"),
+            earliest_publication_date=parse_to_date(
+                data.get("earliestPublicationDate")
+            ),
             pct_publication_number=data.get("pctPublicationNumber"),
-            pct_publication_date=data.get("pctPublicationDate"),
-            international_registration_publication_date=data.get(
-                "internationalRegistrationPublicationDate"
+            pct_publication_date=parse_to_date(data.get("pctPublicationDate")),
+            international_registration_publication_date=parse_to_date(
+                data.get("internationalRegistrationPublicationDate")
             ),
             international_registration_number=data.get(
                 "internationalRegistrationNumber"
             ),
             examiner_name_text=data.get("examinerNameText"),
-            class_field=data.get("class"),  # Renamed due to reserved keyword
+            class_field=data.get("class"),
             subclass=data.get("subclass"),
             uspc_symbol_text=data.get("uspcSymbolText"),
             customer_number=data.get("customerNumber"),
             cpc_classification_bag=data.get("cpcClassificationBag", []),
-            applicant_bag=applicants,
-            inventor_bag=inventors,
+            applicant_bag=app_bag,
+            inventor_bag=inv_bag,
+            raw_data=json.dumps(data) if include_raw_data else None,
         )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `ApplicationMetaData` instance to a dictionary. + + Serializes attributes to camelCase keys suitable for API interaction or storage. + Omits keys with None values or empty lists. Handles date and boolean serialization. + + Returns: + Dict[str, Any]: Dictionary representation of the application metadata. + """ + d = asdict(self) + d.pop("raw_data", None) + d.pop("is_aia", None) + d.pop("is_pre_aia", None) + date_fields = [ + "application_status_date", + "filing_date", + "effective_filing_date", + "grant_date", + "earliest_publication_date", + "pct_publication_date", + "international_registration_publication_date", + ] + for field_name in date_fields: + camel_key = to_camel_case(field_name) + d[camel_key] = serialize_date(getattr(self, field_name, None)) + if field_name != camel_key: + d.pop(field_name, None) + 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" + ): + 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" + ): + d.pop("first_inventor_to_file_indicator") + if self.entity_status_data: + d["entityStatusData"] = self.entity_status_data.to_dict() + else: + 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": + d.pop("applicant_bag") + d["inventorBag"] = [i.to_dict() for i in self.inventor_bag] + if "inventor_bag" in d and "inventor_bag" != "inventorBag": + d.pop("inventor_bag") + if "class_field" in d: + d["class"] = d.pop("class_field") + if "class" in d and d["class"] is None: + d.pop("class", None) + final_data = {} + for key_snake, v_obj in d.items(): + k_camel = to_camel_case(snake_str=key_snake) + if key_snake in [ + "firstInventorToFileIndicator", + "publicationDateBag", + "entityStatusData", + "applicantBag", + "inventorBag", + ] or any(to_camel_case(df) == key_snake for df in date_fields): + k_camel = key_snake + if v_obj is not None: + if isinstance(v_obj, list) and not v_obj: + continue + final_data[k_camel] = v_obj + return final_data
[docs] -@dataclass +@dataclass(frozen=True) class PatentFileWrapper: - """Represents a patent file wrapper.""" + """Represents the complete file wrapper for a single patent application. + + This is a top-level object containing all data sections related to an application, + such as metadata, addresses, assignments, attorney information, continuity data, + PTA data, transaction events, and associated document metadata. + + Attributes: + application_number_text: The primary application number. + application_meta_data: Comprehensive `ApplicationMetaData`. + correspondence_address_bag: List of `Address` objects for correspondence. + assignment_bag: List of `Assignment` records. + record_attorney: Information about the `RecordAttorney`. + foreign_priority_bag: List of `ForeignPriority` claims. + parent_continuity_bag: List of `ParentContinuity` records. + child_continuity_bag: List of `ChildContinuity` records. + patent_term_adjustment_data: `PatentTermAdjustmentData` details. + event_data_bag: List of `EventData` (transaction history). + pgpub_document_meta_data: `PrintedMetaData` for Pre-Grant Publication. + grant_document_meta_data: `PrintedMetaData` for the granted patent. + last_ingestion_date_time: Timestamp of when this data was last ingested by the API (UTC). + """ application_number_text: Optional[str] = None application_meta_data: Optional[ApplicationMetaData] = None @@ -978,112 +2261,634 @@

Source code for pyUSPTO.models.patent_data

     parent_continuity_bag: List[ParentContinuity] = field(default_factory=list)
     child_continuity_bag: List[ChildContinuity] = field(default_factory=list)
     patent_term_adjustment_data: Optional[PatentTermAdjustmentData] = None
-    event_data_bag: List[Event] = field(default_factory=list)
-    pgpub_document_meta_data: Optional[DocumentMetaData] = None
-    grant_document_meta_data: Optional[DocumentMetaData] = None
-    last_ingestion_date_time: Optional[str] = None
+    event_data_bag: List[EventData] = field(default_factory=list)
+    pgpub_document_meta_data: Optional[PrintedMetaData] = None
+    grant_document_meta_data: Optional[PrintedMetaData] = None
+    last_ingestion_date_time: Optional[datetime] = None
 
 
[docs] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PatentFileWrapper": - """Create a PatentFileWrapper object from a dictionary.""" - application_meta = None - if ( - "applicationMetaData" in data - and data.get("applicationMetaData") is not None - ): - application_meta = ApplicationMetaData.from_dict( - data=data.get("applicationMetaData", {}) - ) + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PatentFileWrapper": + """Creates a `PatentFileWrapper` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with patent file wrapper data. + include_raw_data (bool): If True, store the raw JSON for debugging. + + Returns: + PatentFileWrapper: An instance of `PatentFileWrapper`. + """ + amd_json = data.get("applicationMetaData") + amd = ( + ApplicationMetaData.from_dict(amd_json, include_raw_data=include_raw_data) + if isinstance(amd_json, dict) + else None + ) + corr_addrs = [ + Address.from_dict(a) + for a in data.get("correspondenceAddressBag", []) + if isinstance(a, dict) + ] + assigns = [ + Assignment.from_dict(a) + for a in data.get("assignmentBag", []) + if isinstance(a, dict) + ] + rec_att_json = data.get("recordAttorney") + rec_att = ( + RecordAttorney.from_dict(rec_att_json) + if isinstance(rec_att_json, dict) + else None + ) + f_pris = [ + ForeignPriority.from_dict(fp) + for fp in data.get("foreignPriorityBag", []) + if isinstance(fp, dict) + ] + p_conts = [ + ParentContinuity.from_dict(pc) + for pc in data.get("parentContinuityBag", []) + if isinstance(pc, dict) + ] + c_conts = [ + ChildContinuity.from_dict(cc) + for cc in data.get("childContinuityBag", []) + if isinstance(cc, dict) + ] + pta_json = data.get("patentTermAdjustmentData") + pta = ( + PatentTermAdjustmentData.from_dict(pta_json) + if isinstance(pta_json, dict) + else None + ) + evts = [ + EventData.from_dict(e) + for e in data.get("eventDataBag", []) + if isinstance(e, dict) + ] + pgpub_json = data.get("pgpubDocumentMetaData") + pgpub = ( + PrintedMetaData.from_dict(pgpub_json) + if isinstance(pgpub_json, dict) + else None + ) + grant_json = data.get("grantDocumentMetaData") + grant = ( + PrintedMetaData.from_dict(grant_json) + if isinstance(grant_json, dict) + else None + ) + return cls( + application_number_text=data.get("applicationNumberText"), + application_meta_data=amd, + correspondence_address_bag=corr_addrs, + assignment_bag=assigns, + record_attorney=rec_att, + foreign_priority_bag=f_pris, + parent_continuity_bag=p_conts, + child_continuity_bag=c_conts, + patent_term_adjustment_data=pta, + event_data_bag=evts, + pgpub_document_meta_data=pgpub, + grant_document_meta_data=grant, + last_ingestion_date_time=parse_to_datetime_utc( + data.get("lastIngestionDateTime") + ), + )
- addresses = [] - if "correspondenceAddressBag" in data: - addresses = [ - Address.from_dict(data=addr) - for addr in data.get("correspondenceAddressBag", []) - ] - assignments = [] - if "assignmentBag" in data: - assignments = [ - Assignment.from_dict(data=assignment) - for assignment in data.get("assignmentBag", []) - ] +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `PatentFileWrapper` instance to a dictionary. + + Omits keys with None values or empty lists. Serializes nested objects. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + _dict = { + "applicationNumberText": self.application_number_text, + "applicationMetaData": ( + self.application_meta_data.to_dict() + if self.application_meta_data + else None + ), + "correspondenceAddressBag": [ + a.to_dict() for a in self.correspondence_address_bag + ], + "assignmentBag": [a.to_dict() for a in self.assignment_bag], + "recordAttorney": ( + self.record_attorney.to_dict() if self.record_attorney else None + ), + "foreignPriorityBag": [fp.to_dict() for fp in self.foreign_priority_bag], + "parentContinuityBag": [pc.to_dict() for pc in self.parent_continuity_bag], + "childContinuityBag": [cc.to_dict() for cc in self.child_continuity_bag], + "patentTermAdjustmentData": ( + self.patent_term_adjustment_data.to_dict() + if self.patent_term_adjustment_data + else None + ), + "eventDataBag": [e.to_dict() for e in self.event_data_bag], + "pgpubDocumentMetaData": ( + self.pgpub_document_meta_data.to_dict() + if self.pgpub_document_meta_data + else None + ), + "grantDocumentMetaData": ( + self.grant_document_meta_data.to_dict() + if self.grant_document_meta_data + else None + ), + "lastIngestionDateTime": ( + serialize_datetime_as_naive(self.last_ingestion_date_time) + if self.last_ingestion_date_time + else None + ), + } + return { + k: v + for k, v in _dict.items() + if v is not None and (not isinstance(v, list) or v) + }
+
- record_atty = None - if "recordAttorney" in data and data.get("recordAttorney") is not None: - record_atty = RecordAttorney.from_dict(data=data.get("recordAttorney", {})) - foreign_priorities = [] - if "foreignPriorityBag" in data: - foreign_priorities = [ - ForeignPriority.from_dict(data=priority) - for priority in data.get("foreignPriorityBag", []) - ] - parent_continuities = [] - if "parentContinuityBag" in data: - parent_continuities = [ - ParentContinuity.from_dict(data=continuity) - for continuity in data.get("parentContinuityBag", []) - ] +
+[docs] +@dataclass(frozen=True) +class PatentDataResponse: + """Represents the overall response from a patent data API request. - child_continuities = [] - if "childContinuityBag" in data: - child_continuities = [ - ChildContinuity.from_dict(data=continuity) - for continuity in data.get("childContinuityBag", []) - ] + It typically includes a count of the results and a list of PatentFileWrapper objects, + each containing detailed data for a patent application. - patent_term = None - if ( - "patentTermAdjustmentData" in data - and data.get("patentTermAdjustmentData") is not None - ): - patent_term = PatentTermAdjustmentData.from_dict( - data=data.get("patentTermAdjustmentData", {}) + Attributes: + count: The total number of patent applications found matching the query. + patent_file_wrapper_data_bag: A list of `PatentFileWrapper` objects. + request_identifier: An identifier for the API request, if provided. + raw_data: Optional raw JSON data from the API response (for debugging). + """ + + count: int + patent_file_wrapper_data_bag: List[PatentFileWrapper] = field(default_factory=list) + request_identifier: Optional[str] = None + raw_data: Optional[str] = field(default=None, compare=False, repr=False) + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PatentDataResponse": + """Creates a `PatentDataResponse` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with API response data. + include_raw_data (bool): If True, store the raw JSON for debugging. + + Returns: + PatentDataResponse: An instance of `PatentDataResponse`. + """ + wrappers = [ + PatentFileWrapper.from_dict(w, include_raw_data=include_raw_data) + for w in data.get("patentFileWrapperDataBag", []) + if isinstance(w, dict) + ] + return cls( + count=data.get("count", 0), + patent_file_wrapper_data_bag=wrappers, + request_identifier=data.get("requestIdentifier"), + raw_data=json.dumps(data) if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `PatentDataResponse` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + _dict = { + "count": self.count, + "patentFileWrapperDataBag": [ + w.to_dict() for w in self.patent_file_wrapper_data_bag + ], + "requestIdentifier": self.request_identifier, + } + return { + k: v + for k, v in _dict.items() + if v is not None and (not isinstance(v, list) or v) + }
+ + +
+[docs] + def to_csv(self) -> str: + """Converts the patent data in this response to a CSV formatted string. + + The CSV will contain key metadata fields for each application, + such as invention title, application number, filing date, status, etc. + + Returns: + str: A string containing the data in CSV format. + """ + headers = [ + "inventionTitle", + "applicationNumberText", + "filingDate", + "applicationTypeLabelName", + "publicationCategoryBag", + "applicationStatusDescriptionText", + "applicationStatusDate", + "firstInventorName", + ] + + output = io.StringIO() + writer = csv.writer(output) + + writer.writerow(headers) + + if not self.patent_file_wrapper_data_bag: + return output.getvalue() + + for wrapper in self.patent_file_wrapper_data_bag: + if not wrapper.application_meta_data: + continue + + meta = wrapper.application_meta_data + + pub_category_str = ( + "|".join(meta.publication_category_bag) + if meta.publication_category_bag + else "" ) - events = [] - if "eventDataBag" in data: - events = [ - Event.from_dict(data=event) for event in data.get("eventDataBag", []) + row_data = [ + meta.invention_title or "", + wrapper.application_number_text or "", + serialize_date(meta.filing_date) or "", + meta.application_type_label_name or "", + pub_category_str, + meta.application_status_description_text or "", + serialize_date(meta.application_status_date) or "", + meta.first_inventor_name or "", ] + writer.writerow(row_data) - pgpub_meta = None - if ( - "pgpubDocumentMetaData" in data - and data.get("pgpubDocumentMetaData") is not None - ): - pgpub_meta = DocumentMetaData.from_dict( - data=data.get("pgpubDocumentMetaData", {}) - ) + return output.getvalue()
+
- grant_meta = None - if ( - "grantDocumentMetaData" in data - and data.get("grantDocumentMetaData") is not None - ): - grant_meta = DocumentMetaData.from_dict( - data=data.get("grantDocumentMetaData", {}) + + +
+[docs] +@dataclass(frozen=True) +class StatusCode: + """Represents a USPTO application status code and its textual description. + + Attributes: + code: The numeric status code. + description: The textual description of the status code. + """ + + code: Optional[int] = None + description: Optional[str] = None + +
+[docs] + def __str__(self) -> str: + """Returns a user-friendly string representation of the status code.""" + return f"{self.code}: {self.description}"
+ + +
+[docs] + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "StatusCode": + """Creates a `StatusCode` instance from a dictionary. + + Handles two possible key sets from the API for status information. + + Args: + data (Dict[str, Any]): Dictionary with status code data. + + Returns: + StatusCode: An instance of `StatusCode`. + """ + if "code" in data: + return cls( + code=data.get("code"), + description=data.get("description"), ) + else: + return cls( + code=data.get("applicationStatusCode"), + description=data.get("applicationStatusDescriptionText"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `StatusCode` instance to a dictionary. + + Uses keys "applicationStatusCode" and "applicationStatusDescriptionText" + for consistency with some API response parts. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + return { + "applicationStatusCode": self.code, + "applicationStatusDescriptionText": self.description, + }
+
+ + + +
+[docs] +class StatusCodeCollection: + """A collection of StatusCode objects. + + Provides iterable access and helper methods to find or filter status codes. + This class is immutable by convention after initialization. + + Attributes: + status_codes (tuple[StatusCode, ...]): An immutable tuple of `StatusCode` objects. + """ + +
+[docs] + def __init__(self, status_codes: List[StatusCode]): + """Initializes the StatusCodeCollection with a list of status codes. + + Args: + status_codes (List[StatusCode]): A list of `StatusCode` instances. + """ + self._status_codes: tuple[StatusCode, ...] = tuple(status_codes)
+ + + def __iter__(self) -> Iterator[StatusCode]: + return iter(self._status_codes) + + def __len__(self) -> int: + return len(self._status_codes) + + def __getitem__(self, index: int) -> StatusCode: + return self._status_codes[index] + + def __str__(self) -> str: + return f"StatusCodeCollection with {len(self)} status codes." + + def __repr__(self) -> str: + if not self._status_codes: + return "StatusCodeCollection(empty)" + + if len(self._status_codes) <= 3: + codes = ", ".join(str(s.code) for s in self._status_codes) + return f"StatusCodeCollection({len(self)} status codes: {codes})" + else: + first_codes = ", ".join(str(s.code) for s in self._status_codes[:3]) + return f"StatusCodeCollection({len(self)} status codes: {first_codes}, ...)" + +
+[docs] + def find_by_code(self, code_to_find: int) -> Optional[StatusCode]: + """Finds a status code by its numeric code. + + Args: + code_to_find (int): The numeric status code to search for. + + Returns: + Optional[StatusCode]: The `StatusCode` object if found, otherwise None. + """ + for status in self._status_codes: + if status.code == code_to_find: + return status + return None
+ + +
+[docs] + def search_by_description(self, text: str) -> "StatusCodeCollection": + """Searches for status codes by a case-insensitive text match in their description. + + Args: + text (str): The text to search for within status code descriptions. + + Returns: + StatusCodeCollection: A new collection containing matching status codes. + """ + matching = [ + s + for s in self._status_codes + if s.description and text.lower() in s.description.lower() + ] + return StatusCodeCollection(status_codes=matching)
+ + +
+[docs] + def to_dict(self) -> List[Dict[str, Any]]: + """Converts the collection of status codes to a list of dictionaries. + + Returns: + List[Dict[str, Any]]: A list where each item is the dictionary + representation of a `StatusCode`. + """ + return [sc.to_dict() for sc in self._status_codes]
+
+ + + +
+[docs] +@dataclass(frozen=True) +class StatusCodeSearchResponse: + """Represents the response from a search query for patent application status codes. + + Attributes: + count: The total number of status codes found matching the query. + status_code_bag: A `StatusCodeCollection` of the `StatusCode` objects returned. + request_identifier: An identifier for the API request, if provided. + """ + + count: int + status_code_bag: StatusCodeCollection + request_identifier: Optional[str] = None +
+[docs] + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "StatusCodeSearchResponse": + """Creates a `StatusCodeSearchResponse` instance from a dictionary. + + Args: + data (Dict[str, Any]): Dictionary with API response data for status codes. + + Returns: + StatusCodeSearchResponse: An instance of `StatusCodeSearchResponse`. + """ + codes_json = data.get("statusCodeBag", []) + parsed_codes = ( + [StatusCode.from_dict(cd) for cd in codes_json if isinstance(cd, dict)] + if isinstance(codes_json, list) + else [] + ) + collection = StatusCodeCollection(parsed_codes) return cls( - application_number_text=data.get("applicationNumberText"), - application_meta_data=application_meta, - correspondence_address_bag=addresses, - assignment_bag=assignments, - record_attorney=record_atty, - foreign_priority_bag=foreign_priorities, - parent_continuity_bag=parent_continuities, - child_continuity_bag=child_continuities, - patent_term_adjustment_data=patent_term, - event_data_bag=events, - pgpub_document_meta_data=pgpub_meta, - grant_document_meta_data=grant_meta, - last_ingestion_date_time=data.get("lastIngestionDateTime"), + count=data.get("count", 0), + status_code_bag=collection, + request_identifier=data.get("requestIdentifier"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `StatusCodeSearchResponse` instance to a dictionary. + + Omits keys with None values or empty lists. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + _dict = { + "count": self.count, + "statusCodeBag": self.status_code_bag.to_dict(), + "requestIdentifier": self.request_identifier, + } + return { + k: v + for k, v in _dict.items() + if v is not None and (not isinstance(v, list) or v) + }
+
+ + + +
+[docs] +@dataclass(frozen=True) +class ApplicationContinuityData: + """Holds parent and child continuity application data for a specific patent application. + + This class consolidates lists of ParentContinuity and ChildContinuity objects, + representing the lineage of an application. + + Attributes: + parent_continuity_bag: List of `ParentContinuity` objects. + child_continuity_bag: List of `ChildContinuity` objects. + """ + + parent_continuity_bag: List[ParentContinuity] = field(default_factory=list) + child_continuity_bag: List[ChildContinuity] = field(default_factory=list) + +
+[docs] + @classmethod + def from_wrapper(cls, wrapper: PatentFileWrapper) -> "ApplicationContinuityData": + """Creates an `ApplicationContinuityData` instance from a `PatentFileWrapper`. + + Extracts parent and child continuity bags from the wrapper. + + Args: + wrapper (PatentFileWrapper): The patent file wrapper containing continuity data. + + Returns: + ApplicationContinuityData: An instance of `ApplicationContinuityData`. + """ + return cls( + parent_continuity_bag=wrapper.parent_continuity_bag, + child_continuity_bag=wrapper.child_continuity_bag, )
+ + +
+[docs] + def to_dict( + self, + ) -> Dict[str, Any]: + """Converts the `ApplicationContinuityData` instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary representation with "parentContinuityBag" + and "childContinuityBag" keys. + """ + return { + "parentContinuityBag": [pc.to_dict() for pc in self.parent_continuity_bag], + "childContinuityBag": [cc.to_dict() for cc in self.child_continuity_bag], + }
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PrintedPublication: + """Holds metadata for associated documents like Pre-Grant Publications (PGPUB) + and Grant documents for a specific patent application. + + Attributes: + pgpub_document_meta_data: `PrintedMetaData` for the Pre-Grant Publication, if any. + grant_document_meta_data: `PrintedMetaData` for the Grant document, if any. + """ + + pgpub_document_meta_data: Optional[PrintedMetaData] = None + grant_document_meta_data: Optional[PrintedMetaData] = None + +
+[docs] + @classmethod + def from_wrapper(cls, wrapper: PatentFileWrapper) -> "PrintedPublication": + """Creates a `PrintedPublication` instance from a `PatentFileWrapper`. + + Extracts PGPUB and Grant document metadata from the wrapper. + + Args: + wrapper (PatentFileWrapper): The patent file wrapper. + + Returns: + PrintedPublication: An instance of `PrintedPublication`. + """ + return cls( + pgpub_document_meta_data=wrapper.pgpub_document_meta_data, + grant_document_meta_data=wrapper.grant_document_meta_data, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the `PrintedPublication` instance to a dictionary. + + Omits keys if their corresponding metadata is None. + + Returns: + Dict[str, Any]: Dictionary representation. + """ + return { + "pgpubDocumentMetaData": ( + self.pgpub_document_meta_data.to_dict() + if self.pgpub_document_meta_data + else None + ), + "grantDocumentMetaData": ( + self.grant_document_meta_data.to_dict() + if self.grant_document_meta_data + else None + ), + }
@@ -1095,7 +2900,7 @@

Source code for pyUSPTO.models.patent_data

   
-

© Copyright 2025, Andrew Piechocki.

+

© Copyright 2025, Dunlap Codding, P.C..

Built with Sphinx using a diff --git a/docs/build/html/_modules/pyUSPTO/models/ptab.html b/docs/build/html/_modules/pyUSPTO/models/ptab.html new file mode 100644 index 0000000..91019c0 --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/models/ptab.html @@ -0,0 +1,1985 @@ + + + + + + + + pyUSPTO.models.ptab — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyUSPTO.models.ptab

+"""
+models.ptab - Data models for USPTO PTAB (Patent Trial and Appeal Board) APIs
+
+This module provides data models, primarily using frozen dataclasses, for
+representing responses from the USPTO PTAB APIs. These models cover:
+- Patent trial proceedings (IPR, PGR, CBM, DER)
+- Trial documents and decisions
+- Appeal decisions
+- Interference decisions
+"""
+
+import json
+from dataclasses import asdict, dataclass, field
+from datetime import date, datetime
+from typing import Any, Dict, List, Optional
+
+try:
+    from typing import Self
+except ImportError:
+    from typing_extensions import Self
+
+# Import parsing utilities from models utils module
+from pyUSPTO.models.utils import (
+    parse_to_date,
+    parse_to_datetime_utc,
+    serialize_date,
+    serialize_datetime_as_iso,
+    serialize_datetime_as_naive,
+    to_camel_case,
+)
+
+
+@dataclass(frozen=True)
+class PartyData:
+    """Base class for all party data models across PTAB endpoints.
+
+    Attributes:
+        application_number_text: Application number.
+        counsel_name: Name of counsel.
+        grant_date: Patent grant date.
+        group_art_unit_number: Art unit number.
+        inventor_name: Name of inventor.
+        patent_number: Patent number.
+        technology_center_number: Technology center number.
+        real_party_in_interest_name: Real party in interest name.
+        patent_owner_name: Patent owner name.
+        publication_date: Publication date (if applicable).
+        publication_number: Publication number (if applicable).
+    """
+
+    application_number_text: Optional[str] = None
+    counsel_name: Optional[str] = None
+    grant_date: Optional[date] = None
+    group_art_unit_number: Optional[str] = None
+    inventor_name: Optional[str] = None
+    real_party_in_interest_name: Optional[str] = None
+    patent_number: Optional[str] = None
+    patent_owner_name: Optional[str] = None
+    technology_center_number: Optional[str] = None
+    publication_date: Optional[date] = None
+    publication_number: Optional[str] = None
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any], include_raw_data: bool = False) -> Self:
+        """Creates a PartyData instance from a dictionary.
+
+        Args:
+            data: Dictionary containing party data from API response.
+            include_raw_data: Ignored for this model.
+
+        Returns:
+            PartyData: A populated PartyData instance.
+        """
+        return cls(
+            application_number_text=data.get("applicationNumberText"),
+            counsel_name=data.get("counselName"),
+            grant_date=parse_to_date(data.get("grantDate")),
+            group_art_unit_number=data.get("groupArtUnitNumber"),
+            inventor_name=data.get("inventorName"),
+            real_party_in_interest_name=data.get("realPartyInInterestName"),
+            patent_number=data.get("patentNumber"),
+            patent_owner_name=data.get("patentOwnerName"),
+            technology_center_number=data.get("technologyCenterNumber"),
+            publication_date=parse_to_date(data.get("publicationDate")),
+            publication_number=data.get("publicationNumber"),
+        )
+
+    def to_dict(self) -> Dict[str, Any]:
+        """Converts the PartyData instance to a dictionary.
+
+        Returns:
+            Dict[str, Any]: Dictionary with camelCase keys and None values filtered.
+        """
+        result: Dict[str, Any] = {}
+        for k, v in asdict(self).items():
+            if v is not None:
+                if isinstance(v, date):
+                    result[to_camel_case(k)] = serialize_date(v)
+                else:
+                    result[to_camel_case(k)] = v
+        return result
+
+
+# ============================================================================
+# TRIAL PROCEEDINGS MODELS
+# ============================================================================
+
+
+
+[docs] +@dataclass(frozen=True) +class TrialMetaData: + """Trial metadata including status, dates, and download URI. + + Attributes: + petition_filing_date: Date the petition was filed. + accorded_filing_date: The filing date accorded to the petition. + trial_last_modified_date_time: Last modification timestamp. + trial_last_modified_date: Last modification date. + trial_status_category: Status of the trial (e.g., "Institution Denied", "Instituted"). + trial_type_code: Type of trial (IPR, PGR, CBM, DER). + file_download_uri: URI to download ZIP of all trial documents. + termination_date: Date the trial was terminated. + latest_decision_date: Date of the most recent decision. + institution_decision_date: Date of the institution decision. + """ + + petition_filing_date: Optional[date] = None + accorded_filing_date: Optional[date] = None + trial_last_modified_date_time: Optional[datetime] = None + trial_last_modified_date: Optional[date] = None + trial_status_category: Optional[str] = None + trial_type_code: Optional[str] = None + file_download_uri: Optional[str] = None + termination_date: Optional[date] = None + latest_decision_date: Optional[date] = None + institution_decision_date: Optional[date] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "TrialMetaData": + """Creates a TrialMetaData instance from a dictionary. + + Args: + data: Dictionary containing trial metadata from API response. + include_raw_data: Ignored for this model (no raw_data field). + + Returns: + TrialMetaData: An instance of TrialMetaData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + petition_filing_date=parse_to_date(data.get("petitionFilingDate")), + accorded_filing_date=parse_to_date(data.get("accordedFilingDate")), + trial_last_modified_date_time=parse_to_datetime_utc( + data.get("trialLastModifiedDateTime") + ), + trial_last_modified_date=parse_to_date(data.get("trialLastModifiedDate")), + trial_status_category=data.get("trialStatusCategory"), + trial_type_code=data.get("trialTypeCode"), + file_download_uri=file_download_uri, + termination_date=parse_to_date(data.get("terminationDate")), + latest_decision_date=parse_to_date(data.get("latestDecisionDate")), + institution_decision_date=parse_to_date( + data.get("institutionDecisionDate") + ), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the TrialMetaData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.petition_filing_date is not None: + result["petitionFilingDate"] = serialize_date(self.petition_filing_date) + if self.accorded_filing_date is not None: + result["accordedFilingDate"] = serialize_date(self.accorded_filing_date) + if self.trial_last_modified_date_time is not None: + result["trialLastModifiedDateTime"] = serialize_datetime_as_naive( + self.trial_last_modified_date_time + ) + if self.trial_last_modified_date is not None: + result["trialLastModifiedDate"] = serialize_date( + self.trial_last_modified_date + ) + if self.trial_status_category is not None: + result["trialStatusCategory"] = self.trial_status_category + if self.trial_type_code is not None: + result["trialTypeCode"] = self.trial_type_code + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + if self.termination_date is not None: + result["terminationDate"] = serialize_date(self.termination_date) + if self.latest_decision_date is not None: + result["latestDecisionDate"] = serialize_date(self.latest_decision_date) + if self.institution_decision_date is not None: + result["institutionDecisionDate"] = serialize_date( + self.institution_decision_date + ) + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PatentOwnerData(PartyData): + """Party data for a patent owner in PTAB trial proceedings. + + Inherits all attributes from PartyData. Used in IPR, PGR, CBM, + and DER proceedings to represent the patent holder. + """ + + pass
+ + + +
+[docs] +@dataclass(frozen=True) +class RegularPetitionerData: + """Regular petitioner information. + + Attributes: + counsel_name: Name of counsel. + real_party_in_interest_name: Real party in interest name. + """ + + counsel_name: Optional[str] = None + real_party_in_interest_name: Optional[str] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "RegularPetitionerData": + """Creates a RegularPetitionerData instance from a dictionary. + + Args: + data: Dictionary containing petitioner data from API response. + include_raw_data: Ignored for this model. + + Returns: + RegularPetitionerData: An instance of RegularPetitionerData. + """ + return cls( + counsel_name=data.get("counselName"), + real_party_in_interest_name=data.get("realPartyInInterestName"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the RegularPetitionerData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.counsel_name is not None: + result["counselName"] = self.counsel_name + if self.real_party_in_interest_name is not None: + result["realPartyInInterestName"] = self.real_party_in_interest_name + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class RespondentData(PartyData): + """Respondent party data in derivation proceedings. + + Inherits all attributes from PartyData. Used in DER proceedings + to represent the responding party. + """ + + pass
+ + + +
+[docs] +@dataclass(frozen=True) +class DerivationPetitionerData(PartyData): + """Derivation petitioner data in derivation proceedings. + + Inherits all attributes from PartyData. Used in DER proceedings + to represent the petitioning party claiming derivation. + """ + + pass
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABTrialProceeding: + """Individual PTAB trial proceeding record. + + 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. + regular_petitioner_data: Regular petitioner information. + respondent_data: Respondent information. + derivation_petitioner_data: Derivation petitioner information. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + trial_number: Optional[str] = None + # trial_record_identifier: Optional[str] = None # Removed: Documented but not in API. + last_modified_date_time: Optional[datetime] = None + trial_meta_data: Optional[TrialMetaData] = None + patent_owner_data: Optional[PatentOwnerData] = None + regular_petitioner_data: Optional[RegularPetitionerData] = None + respondent_data: Optional[RespondentData] = None + derivation_petitioner_data: Optional[DerivationPetitionerData] = None + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialProceeding": + """Creates a PTABTrialProceeding instance from a dictionary. + + Args: + data: Dictionary containing trial proceeding data from API response. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABTrialProceeding: An instance of PTABTrialProceeding. + """ + # Parse nested objects + trial_meta = data.get("trialMetaData") + trial_meta_data = TrialMetaData.from_dict(trial_meta) if trial_meta else None + + patent_owner = data.get("patentOwnerData") + patent_owner_data = ( + PatentOwnerData.from_dict(patent_owner) if patent_owner else None + ) + + reg_petitioner = data.get("regularPetitionerData") + regular_petitioner_data = ( + RegularPetitionerData.from_dict(reg_petitioner) if reg_petitioner else None + ) + + respondent = data.get("respondentData") + respondent_data = RespondentData.from_dict(respondent) if respondent else None + + deriv_petitioner = data.get("derivationPetitionerData") + derivation_petitioner_data = ( + DerivationPetitionerData.from_dict(deriv_petitioner) + if deriv_petitioner + else None + ) + + return cls( + trial_number=data.get("trialNumber"), + # trial_record_identifier=data.get("trialRecordIdentifier"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + trial_meta_data=trial_meta_data, + patent_owner_data=patent_owner_data, + regular_petitioner_data=regular_petitioner_data, + respondent_data=respondent_data, + derivation_petitioner_data=derivation_petitioner_data, + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABTrialProceeding instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: 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 + ) + if self.trial_meta_data is not None: + result["trialMetaData"] = self.trial_meta_data.to_dict() + if self.patent_owner_data is not None: + result["patentOwnerData"] = self.patent_owner_data.to_dict() + if self.regular_petitioner_data is not None: + result["regularPetitionerData"] = self.regular_petitioner_data.to_dict() + if self.respondent_data is not None: + result["respondentData"] = self.respondent_data.to_dict() + if self.derivation_petitioner_data is not None: + result["derivationPetitionerData"] = ( + self.derivation_petitioner_data.to_dict() + ) + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABTrialProceedingResponse: + """Response container for PTAB trial proceedings search. + + Attributes: + count: Total number of matching results. + request_identifier: UUID for the API request. + patent_trial_proceeding_data_bag: List of trial proceedings. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + count: int = 0 + request_identifier: Optional[str] = None + patent_trial_proceeding_data_bag: List[PTABTrialProceeding] = field( + default_factory=list + ) + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialProceedingResponse": + """Creates a PTABTrialProceedingResponse instance from a dictionary. + + Args: + data: Dictionary containing response data from API. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABTrialProceedingResponse: An instance of PTABTrialProceedingResponse. + """ + proceedings_data = data.get("patentTrialProceedingDataBag", []) + proceedings = [ + PTABTrialProceeding.from_dict(item, include_raw_data=include_raw_data) + for item in proceedings_data + ] + + return cls( + count=data.get("count", 0), + request_identifier=data.get("requestIdentifier"), + patent_trial_proceeding_data_bag=proceedings, + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABTrialProceedingResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_trial_proceeding_data_bag is not None + and len(self.patent_trial_proceeding_data_bag) > 0 + ): + result["patentTrialProceedingDataBag"] = [ + proceeding.to_dict() + for proceeding in self.patent_trial_proceeding_data_bag + ] + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class TrialDocumentData: + """Metadata for a document in a PTAB trial. + + Attributes: + document_category: Category of the document. + document_filing_date: Filing date. + document_identifier: Unique ID. + document_name: Filename. + document_number: Document number in the proceeding. + document_size_quantity: Size in bytes. + document_ocr_text: OCR text content. + document_title_text: Title of the document. + document_type_description_text: Description of document type. + file_download_uri: URL to download the file. + filing_party_category: Who filed (e.g., "Petitioner"). + mime_type_identifier: MIME type (e.g., "application/pdf"). + document_status: Public status. + """ + + # document_category: Optional[str] = None # Removed: Documented but not in API. + document_filing_date: Optional[date] = None + document_identifier: Optional[str] = None + document_name: Optional[str] = None + document_number: Optional[str] = None + document_size_quantity: Optional[int] = None + document_ocr_text: Optional[str] = None + document_title_text: Optional[str] = None + document_type_description_text: Optional[str] = None + file_download_uri: Optional[str] = None + filing_party_category: Optional[str] = None + # mime_type_identifier: Optional[str] = None # Removed: Documented but not in API. + # document_status: Optional[str] = None # Removed: Documented but not in API. + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "TrialDocumentData": + """Creates a TrialDocumentData instance from a dictionary. + + Args: + data: Dictionary containing document data from API response. + include_raw_data: Ignored for this model. + + Returns: + TrialDocumentData: An instance of TrialDocumentData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + # document_category=data.get("documentCategory"), # Removed: Documented but not in API. + document_filing_date=parse_to_date(data.get("documentFilingDate")), + document_identifier=data.get("documentIdentifier"), + document_name=data.get("documentName"), + document_number=data.get("documentNumber"), + document_size_quantity=data.get("documentSizeQuantity"), + document_ocr_text=data.get("documentOCRText"), + document_title_text=data.get("documentTitleText"), + 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"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the TrialDocumentData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + # Removed: Documented but not in API. + # if self.document_category is not None: + # result["documentCategory"] = self.document_category + if self.document_filing_date is not None: + result["documentFilingDate"] = serialize_date(self.document_filing_date) + if self.document_identifier is not None: + result["documentIdentifier"] = self.document_identifier + if self.document_name is not None: + result["documentName"] = self.document_name + if self.document_number is not None: + result["documentNumber"] = self.document_number + if self.document_size_quantity is not None: + result["documentSizeQuantity"] = self.document_size_quantity + if self.document_ocr_text is not None: + result["documentOCRText"] = self.document_ocr_text # Uppercase OCR + if self.document_title_text is not None: + result["documentTitleText"] = self.document_title_text + if self.document_type_description_text is not None: + result["documentTypeDescriptionText"] = self.document_type_description_text + if self.file_download_uri is not None: + 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
+
+ + + +
+[docs] +@dataclass(frozen=True) +class TrialDecisionData: + """Metadata for a decision in a PTAB trial. + + Attributes: + statute_and_rule_bag: List of applicable statutes and rules. + decision_issue_date: Date issued. + decision_type_category: Type of decision (e.g. "Final Written Decision"). + issue_type_bag: List of issues addressed. + trial_outcome_category: Outcome (e.g., "Denied"). + """ + + statute_and_rule_bag: List[str] = field(default_factory=list) + decision_issue_date: Optional[date] = None + decision_type_category: Optional[str] = None + issue_type_bag: List[str] = field(default_factory=list) + trial_outcome_category: Optional[str] = None + +
+[docs] + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "TrialDecisionData": + return cls( + statute_and_rule_bag=data.get("statuteAndRuleBag", []), + decision_issue_date=parse_to_date(data.get("decisionIssueDate")), + decision_type_category=data.get("decisionTypeCategory"), + issue_type_bag=data.get("issueTypeBag", []), + trial_outcome_category=data.get("trialOutcomeCategory"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the TrialDecisionData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.statute_and_rule_bag is not None and len(self.statute_and_rule_bag) > 0: + result["statuteAndRuleBag"] = self.statute_and_rule_bag + if self.decision_issue_date is not None: + result["decisionIssueDate"] = serialize_date(self.decision_issue_date) + if self.decision_type_category is not None: + result["decisionTypeCategory"] = self.decision_type_category + if self.issue_type_bag is not None and len(self.issue_type_bag) > 0: + result["issueTypeBag"] = self.issue_type_bag + if self.trial_outcome_category is not None: + result["trialOutcomeCategory"] = self.trial_outcome_category + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABTrialDocument: + """Individual trial document or decision record from PTAB document/decision search APIs. + + Used by search_documents() and search_decisions() endpoints. Contains document-specific + metadata (documentData) or decision information (decisionData), plus trial context. + Differs from PTABTrialProceeding which represents the entire proceeding rather than + individual documents within it. + + Attributes: + trial_document_category: Category (Document or Decision). + last_modified_date_time: Last modification timestamp. + trial_number: Trial number (e.g., "IPR2023-00123"). + trial_type_code: Type of trial (IPR, PGR, CBM, DER). + trial_meta_data: Trial metadata. + patent_owner_data: Patent owner information. + regular_petitioner_data: Regular petitioner information. + respondent_data: Respondent information. + derivation_petitioner_data: Derivation petitioner information. + document_data: Document metadata (if document). + decision_data: Decision information (if decision). + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + trial_document_category: Optional[str] = None + last_modified_date_time: Optional[datetime] = None + trial_number: Optional[str] = None + trial_type_code: Optional[str] = None + trial_meta_data: Optional[TrialMetaData] = None + patent_owner_data: Optional[PatentOwnerData] = None + regular_petitioner_data: Optional[RegularPetitionerData] = None + respondent_data: Optional[RespondentData] = None + derivation_petitioner_data: Optional[DerivationPetitionerData] = None + document_data: Optional[TrialDocumentData] = None + decision_data: Optional[TrialDecisionData] = None + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialDocument": + trial_meta = data.get("trialMetaData") + patent_owner = data.get("patentOwnerData") + reg_petitioner = data.get("regularPetitionerData") + respondent = data.get("respondentData") + deriv_petitioner = data.get("derivationPetitionerData") + doc_data = data.get("documentData") + dec_data = data.get("decisionData") + + return cls( + trial_document_category=data.get("trialDocumentCategory"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + trial_number=data.get("trialNumber"), + trial_meta_data=( + TrialMetaData.from_dict(trial_meta) if trial_meta else None + ), + patent_owner_data=( + PatentOwnerData.from_dict(patent_owner) if patent_owner else None + ), + regular_petitioner_data=( + RegularPetitionerData.from_dict(reg_petitioner) + if reg_petitioner + else None + ), + respondent_data=( + RespondentData.from_dict(respondent) if respondent else None + ), + derivation_petitioner_data=( + DerivationPetitionerData.from_dict(deriv_petitioner) + if deriv_petitioner + else None + ), + document_data=(TrialDocumentData.from_dict(doc_data) if doc_data else None), + decision_data=(TrialDecisionData.from_dict(dec_data) if dec_data else None), + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABTrialDocument instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.trial_document_category is not None: + result["trialDocumentCategory"] = self.trial_document_category + if self.last_modified_date_time is not None: + result["lastModifiedDateTime"] = serialize_datetime_as_naive( + self.last_modified_date_time + ) + if self.trial_number is not None: + result["trialNumber"] = self.trial_number + if self.trial_meta_data is not None: + result["trialMetaData"] = self.trial_meta_data.to_dict() + if self.patent_owner_data is not None: + result["patentOwnerData"] = self.patent_owner_data.to_dict() + if self.regular_petitioner_data is not None: + result["regularPetitionerData"] = self.regular_petitioner_data.to_dict() + if self.respondent_data is not None: + result["respondentData"] = self.respondent_data.to_dict() + if self.derivation_petitioner_data is not None: + result["derivationPetitionerData"] = ( + self.derivation_petitioner_data.to_dict() + ) + if self.document_data is not None: + result["documentData"] = self.document_data.to_dict() + if self.decision_data is not None: + result["decisionData"] = self.decision_data.to_dict() + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABTrialDocumentResponse: + """Response container for PTAB trial documents/decisions search.""" + + count: int = 0 + request_identifier: Optional[str] = None + patent_trial_document_data_bag: List[PTABTrialDocument] = field( + default_factory=list + ) + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialDocumentResponse": + docs_data = data.get("patentTrialDocumentDataBag", []) + docs = [ + PTABTrialDocument.from_dict(item, include_raw_data=include_raw_data) + for item in docs_data + ] + return cls( + count=data.get("count", 0), + patent_trial_document_data_bag=docs, + request_identifier=data.get("requestIdentifier"), + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABTrialDocumentResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_trial_document_data_bag is not None + and len(self.patent_trial_document_data_bag) > 0 + ): + result["patentTrialDocumentDataBag"] = [ + doc.to_dict() for doc in self.patent_trial_document_data_bag + ] + + return result
+
+ + + +# ============================================================================ +# APPEAL DECISIONS MODELS +# ============================================================================ + + +
+[docs] +@dataclass(frozen=True) +class AppealMetaData: + """Appeal metadata. + + Attributes: + appeal_filing_date: Date the appeal was filed. + appeal_last_modified_date: Last modification date. + appeal_last_modified_date_time: Last modification timestamp. + application_type_category: Type of application. + docket_notice_mailed_date: Date the docket notice was mailed. + file_download_uri: URI to download ZIP of appeal documents. + """ + + appeal_filing_date: Optional[date] = None + appeal_last_modified_date: Optional[date] = None + appeal_last_modified_date_time: Optional[datetime] = None + application_type_category: Optional[str] = None + docket_notice_mailed_date: Optional[date] = None + file_download_uri: Optional[str] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "AppealMetaData": + """Creates an AppealMetaData instance from a dictionary. + + Args: + data: Dictionary containing appeal metadata from API response. + include_raw_data: Ignored for this model. + + Returns: + AppealMetaData: An instance of AppealMetaData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + appeal_filing_date=parse_to_date(data.get("appealFilingDate")), + appeal_last_modified_date=parse_to_date(data.get("appealLastModifiedDate")), + appeal_last_modified_date_time=parse_to_datetime_utc( + data.get("appealLastModifiedDateTime") + ), + application_type_category=data.get("applicationTypeCategory"), + docket_notice_mailed_date=parse_to_date(data.get("docketNoticeMailedDate")), + file_download_uri=file_download_uri, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the AppealMetaData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.appeal_filing_date is not None: + result["appealFilingDate"] = serialize_date(self.appeal_filing_date) + if self.appeal_last_modified_date is not None: + result["appealLastModifiedDate"] = serialize_date( + self.appeal_last_modified_date + ) + if self.appeal_last_modified_date_time is not None: + result["appealLastModifiedDateTime"] = serialize_datetime_as_naive( + self.appeal_last_modified_date_time + ) + if self.application_type_category is not None: + result["applicationTypeCategory"] = self.application_type_category + if self.docket_notice_mailed_date is not None: + result["docketNoticeMailedDate"] = serialize_date( + self.docket_notice_mailed_date + ) + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class AppellantData(PartyData): + """Appellant party data in PTAB appeals. + + Inherits all attributes from PartyData. Used in appeal proceedings + to represent the party appealing an examiner decision. + """ + + pass
+ + + +
+[docs] +@dataclass(frozen=True) +class RequestorData: + """Third party requestor information. + + Attributes: + third_party_name: Name of the third party. + """ + + third_party_name: Optional[str] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "RequestorData": + """Creates a RequestorData instance from a dictionary. + + Args: + data: Dictionary containing requestor data from API response. + include_raw_data: Ignored for this model. + + Returns: + RequestorData: An instance of RequestorData. + """ + return cls( + third_party_name=data.get("thirdPartyName"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the RequestorData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + for k, v in asdict(self).items(): + if v is not None: + result[to_camel_case(k)] = v + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class AppealDocumentData: + """Appeal document metadata. + + Attributes: + document_filing_date: Date the document was filed. + document_identifier: Unique identifier for the document. + document_name: Name of the document. + document_size_quantity: Size of the document in bytes. + document_ocr_text: Full OCR text of the document. + document_type_description_text: Description of the document type. + file_download_uri: URI to download the document. + """ + + document_filing_date: Optional[date] = None + document_identifier: Optional[str] = None + document_name: Optional[str] = None + document_size_quantity: Optional[int] = None + document_ocr_text: Optional[str] = None + document_type_description_text: Optional[str] = None + file_download_uri: Optional[str] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "AppealDocumentData": + """Creates an AppealDocumentData instance from a dictionary. + + Args: + data: Dictionary containing document data from API response. + include_raw_data: Ignored for this model. + + Returns: + AppealDocumentData: An instance of AppealDocumentData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + doc_type = data.get("documentTypeDescriptionText") or data.get( + "documentTypeCategory" + ) + + return cls( + document_filing_date=parse_to_date(data.get("documentFilingDate")), + document_identifier=data.get("documentIdentifier"), + document_name=data.get("documentName"), + document_size_quantity=data.get("documentSizeQuantity"), + document_ocr_text=data.get("documentOCRText"), + document_type_description_text=doc_type, + file_download_uri=file_download_uri, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the AppealDocumentData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.document_filing_date is not None: + result["documentFilingDate"] = serialize_date(self.document_filing_date) + if self.document_identifier is not None: + result["documentIdentifier"] = self.document_identifier + if self.document_name is not None: + result["documentName"] = self.document_name + if self.document_size_quantity is not None: + result["documentSizeQuantity"] = self.document_size_quantity + if self.document_ocr_text is not None: + result["documentOCRText"] = self.document_ocr_text + if self.document_type_description_text is not None: + result["documentTypeDescriptionText"] = self.document_type_description_text + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class DecisionData: + """Appeal decision information. + + Attributes: + appeal_outcome_category: Outcome of the appeal. + statute_and_rule_bag: List of applicable statutes and rules. + decision_issue_date: Date the decision was issued. + decision_type_category: Type of decision. + issue_type_bag: List of issue types. + """ + + appeal_outcome_category: Optional[str] = None + statute_and_rule_bag: List[str] = field(default_factory=list) + decision_issue_date: Optional[date] = None + decision_type_category: Optional[str] = None + issue_type_bag: List[str] = field(default_factory=list) + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "DecisionData": + """Creates a DecisionData instance from a dictionary. + + Args: + data: Dictionary containing decision data from API response. + include_raw_data: Ignored for this model. + + Returns: + DecisionData: An instance of DecisionData. + """ + return cls( + appeal_outcome_category=data.get("appealOutcomeCategory"), + statute_and_rule_bag=data.get("statuteAndRuleBag", []), + decision_issue_date=parse_to_date(data.get("decisionIssueDate")), + decision_type_category=data.get("decisionTypeCategory"), + issue_type_bag=data.get("issueTypeBag", []), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the DecisionData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + for k, v in asdict(self).items(): + if v is not None: + if isinstance(v, date): + result[to_camel_case(k)] = serialize_date(v) + elif isinstance(v, list) and len(v) == 0: + # Skip empty lists + continue + else: + result[to_camel_case(k)] = v + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABAppealDecision: + """Individual PTAB appeal decision record. + + Attributes: + appeal_number: Appeal number. + last_modified_date_time: Last modification timestamp. + appeal_document_category: Document category. + appeal_meta_data: Appeal metadata. + appellant_data: Appellant information. + requestor_data: Third party requestor information. + document_data: Document metadata. + decision_data: Decision information. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + appeal_number: Optional[str] = None + last_modified_date_time: Optional[datetime] = None + appeal_document_category: Optional[str] = None + appeal_meta_data: Optional[AppealMetaData] = None + appellant_data: Optional[AppellantData] = None + requestor_data: Optional[RequestorData] = None + document_data: Optional[AppealDocumentData] = None + decision_data: Optional[DecisionData] = None + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABAppealDecision": + """Creates a PTABAppealDecision instance from a dictionary. + + Args: + data: Dictionary containing appeal decision data from API response. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABAppealDecision: An instance of PTABAppealDecision. + """ + # Parse nested objects + appeal_meta = data.get("appealMetaData") + appeal_meta_data = ( + AppealMetaData.from_dict(appeal_meta) if appeal_meta else None + ) + + # Handle potential typo 'appelantData' vs 'appellantData' + appellant = data.get("appellantData") or data.get("appelantData") + appellant_data = AppellantData.from_dict(appellant) if appellant else None + + requestor = data.get("requestorData") + requestor_data = RequestorData.from_dict(requestor) if requestor else None + + document = data.get("documentData") + document_data = AppealDocumentData.from_dict(document) if document else None + + decision = data.get("decisionData") + decision_data = DecisionData.from_dict(decision) if decision else None + + return cls( + appeal_number=data.get("appealNumber"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + appeal_document_category=data.get("appealDocumentCategory"), + appeal_meta_data=appeal_meta_data, + appellant_data=appellant_data, + requestor_data=requestor_data, + document_data=document_data, + decision_data=decision_data, + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABAppealDecision instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + # Manually process each field to preserve nested objects + if self.appeal_number is not None: + result["appealNumber"] = self.appeal_number + if self.last_modified_date_time is not None: + result["lastModifiedDateTime"] = serialize_datetime_as_naive( + self.last_modified_date_time + ) + if self.appeal_document_category is not None: + result["appealDocumentCategory"] = self.appeal_document_category + if self.appeal_meta_data is not None: + result["appealMetaData"] = self.appeal_meta_data.to_dict() + if self.appellant_data is not None: + result["appellantData"] = self.appellant_data.to_dict() + if self.requestor_data is not None: + result["requestorData"] = self.requestor_data.to_dict() + if self.document_data is not None: + result["documentData"] = self.document_data.to_dict() + if self.decision_data is not None: + result["decisionData"] = self.decision_data.to_dict() + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABAppealResponse: + """Response container for PTAB appeals search. + + Attributes: + count: Total number of matching results. + request_identifier: UUID for the API request. + patent_appeal_data_bag: List of appeal decisions. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + count: int = 0 + request_identifier: Optional[str] = None + patent_appeal_data_bag: List[PTABAppealDecision] = field(default_factory=list) + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABAppealResponse": + """Creates a PTABAppealResponse instance from a dictionary. + + Args: + data: Dictionary containing response data from API. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABAppealResponse: An instance of PTABAppealResponse. + """ + appeals_data = data.get("patentAppealDataBag", []) + appeals = [ + PTABAppealDecision.from_dict(item, include_raw_data=include_raw_data) + for item in appeals_data + ] + + return cls( + count=data.get("count", 0), + request_identifier=data.get("requestIdentifier"), + patent_appeal_data_bag=appeals, + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABAppealResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + # Manually process each field + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_appeal_data_bag is not None + and len(self.patent_appeal_data_bag) > 0 + ): + result["patentAppealDataBag"] = [ + decision.to_dict() for decision in self.patent_appeal_data_bag + ] + + return result
+
+ + + +# ============================================================================ +# INTERFERENCE DECISIONS MODELS +# ============================================================================ + + +
+[docs] +@dataclass(frozen=True) +class InterferenceMetaData: + """Interference metadata. + + Attributes: + interference_style_name: Style name of the interference. + interference_last_modified_date: Last modification date. + interference_last_modified_date_time: Last modification datetime. + declaration_date: Declaration date. + file_download_uri: URI to download ZIP of interference documents. + """ + + interference_style_name: Optional[str] = None + interference_last_modified_date: Optional[date] = None + interference_last_modified_date_time: Optional[datetime] = None + declaration_date: Optional[date] = None + file_download_uri: Optional[str] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "InterferenceMetaData": + """Creates an InterferenceMetaData instance from a dictionary. + + Args: + data: Dictionary containing interference metadata from API response. + include_raw_data: Ignored for this model. + + Returns: + InterferenceMetaData: An instance of InterferenceMetaData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + interference_style_name=data.get("interferenceStyleName"), + interference_last_modified_date=parse_to_date( + data.get("interferenceLastModifiedDate") + ), + interference_last_modified_date_time=parse_to_datetime_utc( + data.get("interferenceLastModifiedDateTime") + ), + declaration_date=parse_to_date(data.get("declarationDate")), + file_download_uri=file_download_uri, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the InterferenceMetaData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.interference_style_name is not None: + result["interferenceStyleName"] = self.interference_style_name + if self.interference_last_modified_date is not None: + result["interferenceLastModifiedDate"] = serialize_date( + self.interference_last_modified_date + ) + if self.interference_last_modified_date_time is not None: + result["interferenceLastModifiedDateTime"] = serialize_datetime_as_naive( + self.interference_last_modified_date_time + ) + if self.declaration_date is not None: + result["declarationDate"] = serialize_date(self.declaration_date) + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class SeniorPartyData(PartyData): + """Senior party information in PTAB interference proceedings. + + Inherits all attributes from PartyData. Represents the party with + the earlier effective filing date in an interference. + """ + + pass
+ + + +
+[docs] +@dataclass(frozen=True) +class JuniorPartyData(PartyData): + """Junior party information in PTAB interference proceedings. + + Inherits all attributes from PartyData. Represents the party with + the later effective filing date in an interference. + """ + + pass
+ + + +
+[docs] +@dataclass(frozen=True) +class AdditionalPartyData: + """Additional party information in an interference. + + Attributes: + application_number_text: Application number. + inventor_name: Name of inventor. + patent_number: Patent number. + additional_party_name: Name of additional party. + """ + + application_number_text: Optional[str] = None + inventor_name: Optional[str] = None + patent_number: Optional[str] = None + additional_party_name: Optional[str] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "AdditionalPartyData": + """Creates an AdditionalPartyData instance from a dictionary. + + Args: + data: Dictionary containing additional party data from API response. + include_raw_data: Ignored for this model. + + Returns: + AdditionalPartyData: An instance of AdditionalPartyData. + """ + return cls( + application_number_text=data.get("applicationNumberText"), + inventor_name=data.get("inventorName"), + patent_number=data.get("patentNumber"), + additional_party_name=data.get("additionalPartyName"), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the AdditionalPartyData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.application_number_text is not None: + result["applicationNumberText"] = self.application_number_text + if self.inventor_name is not None: + result["inventorName"] = self.inventor_name + if self.patent_number is not None: + result["patentNumber"] = self.patent_number + if self.additional_party_name is not None: + result["additionalPartyName"] = self.additional_party_name + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class InterferenceDocumentData: + """Interference document metadata. + + Attributes: + document_identifier: Unique identifier for the document. + document_name: Name of the document. + document_size_quantity: Size of the document in bytes. + document_ocr_text: Full OCR text of the document. + document_title_text: Title of the document. + interference_outcome_category: Outcome of the interference. + document_filing_date: Date the document was filed. + decision_issue_date: Date the decision was issued. + decision_type_category: Type of decision. + file_download_uri: URI to download the document. + statute_and_rule_bag: List of applicable statutes and rules. + issue_type_bag: List of issues addressed. + """ + + document_identifier: Optional[str] = None + document_name: Optional[str] = None + document_size_quantity: Optional[int] = None + document_ocr_text: Optional[str] = None + document_title_text: Optional[str] = None + interference_outcome_category: Optional[str] = None + document_filing_date: Optional[date] = None + decision_issue_date: Optional[date] = None + decision_type_category: Optional[str] = None + file_download_uri: Optional[str] = None + statute_and_rule_bag: List[str] = field(default_factory=list) + issue_type_bag: List[str] = field(default_factory=list) + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "InterferenceDocumentData": + """Creates an InterferenceDocumentData instance from a dictionary. + + Args: + data: Dictionary containing document data from API response. + include_raw_data: Ignored for this model. + + Returns: + InterferenceDocumentData: An instance of InterferenceDocumentData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + + return cls( + document_identifier=data.get("documentIdentifier"), + document_name=data.get("documentName"), + document_size_quantity=data.get("documentSizeQuantity"), + document_ocr_text=data.get("documentOCRText"), + document_title_text=data.get("documentTitleText"), + interference_outcome_category=data.get("interferenceOutcomeCategory"), + document_filing_date=parse_to_date(data.get("documentFilingDate")), + decision_issue_date=parse_to_date(data.get("decisionIssueDate")), + decision_type_category=data.get("decisionTypeCategory"), + file_download_uri=file_download_uri, + statute_and_rule_bag=data.get("statuteAndRuleBag", []), + issue_type_bag=data.get("issueTypeBag", []), + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the InterferenceDocumentData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + if self.document_identifier is not None: + result["documentIdentifier"] = self.document_identifier + if self.document_name is not None: + result["documentName"] = self.document_name + if self.document_size_quantity is not None: + result["documentSizeQuantity"] = self.document_size_quantity + if self.document_ocr_text is not None: + result["documentOCRText"] = self.document_ocr_text + if self.document_title_text is not None: + result["documentTitleText"] = self.document_title_text + if self.interference_outcome_category is not None: + result["interferenceOutcomeCategory"] = self.interference_outcome_category + if self.document_filing_date is not None: + result["documentFilingDate"] = serialize_date(self.document_filing_date) + if self.decision_issue_date is not None: + result["decisionIssueDate"] = serialize_date(self.decision_issue_date) + if self.decision_type_category is not None: + result["decisionTypeCategory"] = self.decision_type_category + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + if self.statute_and_rule_bag is not None and len(self.statute_and_rule_bag) > 0: + result["statuteAndRuleBag"] = self.statute_and_rule_bag + if self.issue_type_bag is not None and len(self.issue_type_bag) > 0: + result["issueTypeBag"] = self.issue_type_bag + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABInterferenceDecision: + """Individual PTAB interference decision record. + + Attributes: + interference_number: Interference number. + last_modified_date_time: Last modification timestamp. + interference_meta_data: Interference metadata. + senior_party_data: Senior party information. + junior_party_data: Junior party information. + additional_party_data_bag: List of additional parties. + document_data: Document metadata. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + interference_number: Optional[str] = None + last_modified_date_time: Optional[datetime] = None + interference_meta_data: Optional[InterferenceMetaData] = None + senior_party_data: Optional[SeniorPartyData] = None + junior_party_data: Optional[JuniorPartyData] = None + additional_party_data_bag: List[AdditionalPartyData] = field(default_factory=list) + document_data: Optional[InterferenceDocumentData] = None + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABInterferenceDecision": + """Creates a PTABInterferenceDecision instance from a dictionary. + + Args: + data: Dictionary containing interference decision data from API response. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABInterferenceDecision: An instance of PTABInterferenceDecision. + """ + # Parse nested objects + interference_meta = data.get("interferenceMetaData") + interference_meta_data = ( + InterferenceMetaData.from_dict(interference_meta) + if interference_meta + else None + ) + + senior_party = data.get("seniorPartyData") + senior_party_data = ( + SeniorPartyData.from_dict(senior_party) if senior_party else None + ) + + junior_party = data.get("juniorPartyData") + junior_party_data = ( + JuniorPartyData.from_dict(junior_party) if junior_party else None + ) + + additional_parties_data = data.get("additionalPartyDataBag", []) + additional_party_data_bag = [ + AdditionalPartyData.from_dict(item) for item in additional_parties_data + ] + + # Handle alias: documentData vs decisionDocumentData + document = data.get("documentData") or data.get("decisionDocumentData") + document_data = ( + InterferenceDocumentData.from_dict(document) if document else None + ) + + return cls( + interference_number=data.get("interferenceNumber"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + interference_meta_data=interference_meta_data, + senior_party_data=senior_party_data, + junior_party_data=junior_party_data, + additional_party_data_bag=additional_party_data_bag, + document_data=document_data, + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABInterferenceDecision instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + # Manually process each field to preserve nested objects + if self.interference_number is not None: + result["interferenceNumber"] = self.interference_number + if self.last_modified_date_time is not None: + result["lastModifiedDateTime"] = serialize_datetime_as_naive( + self.last_modified_date_time + ) + if self.interference_meta_data is not None: + result["interferenceMetaData"] = self.interference_meta_data.to_dict() + if self.senior_party_data is not None: + result["seniorPartyData"] = self.senior_party_data.to_dict() + if self.junior_party_data is not None: + result["juniorPartyData"] = self.junior_party_data.to_dict() + if ( + self.additional_party_data_bag is not None + and len(self.additional_party_data_bag) > 0 + ): + result["additionalPartyDataBag"] = [ + party.to_dict() for party in self.additional_party_data_bag + ] + if self.document_data is not None: + result["documentData"] = self.document_data.to_dict() + + return result
+
+ + + +
+[docs] +@dataclass(frozen=True) +class PTABInterferenceResponse: + """Response container for PTAB interferences search. + + Attributes: + count: Total number of matching results. + request_identifier: UUID for the API request. + patent_interference_data_bag: List of interference decisions. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + count: int = 0 + request_identifier: Optional[str] = None + patent_interference_data_bag: List[PTABInterferenceDecision] = field( + default_factory=list + ) + raw_data: Optional[Dict[str, Any]] = None + +
+[docs] + @classmethod + def from_dict( + cls, data: Dict[str, Any], include_raw_data: bool = False + ) -> "PTABInterferenceResponse": + """Creates a PTABInterferenceResponse instance from a dictionary. + + Args: + data: Dictionary containing response data from API. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABInterferenceResponse: An instance of PTABInterferenceResponse. + """ + interferences_data = data.get("patentInterferenceDataBag", []) + interferences = [ + PTABInterferenceDecision.from_dict(item, include_raw_data=include_raw_data) + for item in interferences_data + ] + + return cls( + count=data.get("count", 0), + request_identifier=data.get("requestIdentifier"), + patent_interference_data_bag=interferences, + raw_data=data if include_raw_data else None, + )
+ + +
+[docs] + def to_dict(self) -> Dict[str, Any]: + """Converts the PTABInterferenceResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: Dict[str, Any] = {} + + # Manually process each field + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_interference_data_bag is not None + and len(self.patent_interference_data_bag) > 0 + ): + result["patentInterferenceDataBag"] = [ + decision.to_dict() for decision in self.patent_interference_data_bag + ] + + return result
+
+ + + +# ============================================================================ +# EXPORTS +# ============================================================================ + +__all__ = [ + # Trial Proceedings Models + "TrialMetaData", + "PatentOwnerData", + "RegularPetitionerData", + "RespondentData", + "DerivationPetitionerData", + "PTABTrialProceeding", + "PTABTrialProceedingResponse", + # Trial Documents/Decisions Models + "TrialDocumentData", + "TrialDecisionData", + "PTABTrialDocument", + "PTABTrialDocumentResponse", + # Appeal Decisions Models + "AppealMetaData", + "AppellantData", + "RequestorData", + "AppealDocumentData", + "DecisionData", + "PTABAppealDecision", + "PTABAppealResponse", + # Interference Decisions Models + "InterferenceMetaData", + "SeniorPartyData", + "JuniorPartyData", + "AdditionalPartyData", + "InterferenceDocumentData", + "PTABInterferenceDecision", + "PTABInterferenceResponse", +] +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/models/utils.html b/docs/build/html/_modules/pyUSPTO/models/utils.html new file mode 100644 index 0000000..6a7b427 --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/models/utils.html @@ -0,0 +1,376 @@ + + + + + + + + pyUSPTO.models.utils — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyUSPTO.models.utils

+"""
+models.utils - Utility functions for USPTO data models
+
+This module provides utility functions for parsing, serializing, and converting
+data used across USPTO API data models. These utilities handle date/datetime
+conversions, boolean string representations, and string transformations.
+"""
+
+import warnings
+from datetime import date, datetime, timezone, tzinfo
+from typing import Optional
+from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
+
+from pyUSPTO.warnings import (
+    USPTOBooleanParseWarning,
+    USPTODateParseWarning,
+    USPTOTimezoneWarning,
+)
+
+# --- Timezone and Parsing Utilities ---
+ASSUMED_NAIVE_TIMEZONE_STR = "America/New_York"
+try:
+    ASSUMED_NAIVE_TIMEZONE: Optional[tzinfo] = ZoneInfo(ASSUMED_NAIVE_TIMEZONE_STR)
+except ZoneInfoNotFoundError:
+    warnings.warn(
+        f"Timezone '{ASSUMED_NAIVE_TIMEZONE_STR}' not found. "
+        f"Naive datetimes will be treated as UTC.",
+        category=USPTOTimezoneWarning,
+        stacklevel=1,
+    )
+    ASSUMED_NAIVE_TIMEZONE = timezone.utc
+
+
+
+[docs] +def parse_to_date(date_str: Optional[str], fmt: str = "%Y-%m-%d") -> Optional[date]: + """Parses a string representation of a date into a date object. + + Args: + date_str (Optional[str]): The string to parse as a date. + fmt (str, optional): The expected strptime format string for parsing + the date. Defaults to "%Y-%m-%d". + + Returns: + Optional[date]: A date object if parsing is successful and `date_str` + is not None. Returns None if `date_str` is None or if parsing fails. + + Warns: + USPTODateParseWarning: If the date string cannot be parsed. + """ + + if not date_str: + return None + try: + return datetime.strptime(date_str, fmt).date() + except ValueError: + warnings.warn( + f"Could not parse date string '{date_str}' with format '{fmt}'", + category=USPTODateParseWarning, + stacklevel=2, + ) + return None
+ + + +
+[docs] +def parse_to_datetime_utc(datetime_str: Optional[str]) -> Optional[datetime]: + """Parses a string representation of a datetime into a UTC datetime object. + + Attempts to parse ISO format strings. If the input string contains timezone + information, it's used. If the string is a naive datetime (no timezone), + it's assumed to be in the `ASSUMED_NAIVE_TIMEZONE_STR` (e.g., "America/New_York") + and then converted to UTC. + + Args: + datetime_str (Optional[str]): The string to parse as a datetime. + Supports ISO 8601 format, including those ending with "Z". + + Returns: + Optional[datetime]: A timezone-aware datetime object in UTC if parsing + is successful and `datetime_str` is not None. Returns None if + `datetime_str` is None or if parsing/conversion fails. + + Warns: + USPTODateParseWarning: If the datetime string cannot be parsed. + USPTOTimezoneWarning: If timezone localization fails. + """ + + if not datetime_str: + return None + dt_obj: Optional[datetime] = None + parsed_successfully = False + if isinstance(datetime_str, str): + try: + if datetime_str.endswith("Z"): + dt_obj = datetime.fromisoformat(datetime_str.replace("Z", "+00:00")) + # Normalize offsets like -0500 → -05:00 for Python <3.11 + elif ( + len(datetime_str) > 5 + and (datetime_str[-5] in "+-") + and datetime_str[-3] != ":" + ): + datetime_str = ( + datetime_str[:-5] + datetime_str[-5:-2] + ":" + datetime_str[-2:] + ) + dt_obj = datetime.fromisoformat(datetime_str) + else: + dt_obj = datetime.fromisoformat(datetime_str) + parsed_successfully = True + except ValueError: + pass + + if not parsed_successfully or dt_obj is None: + warnings.warn( + f"Could not parse datetime string '{datetime_str}' with any known format", + category=USPTODateParseWarning, + stacklevel=2, + ) + return None + if dt_obj.tzinfo is None or dt_obj.tzinfo.utcoffset(dt_obj) is None: + if ASSUMED_NAIVE_TIMEZONE: + try: + aware_dt = dt_obj.replace(tzinfo=ASSUMED_NAIVE_TIMEZONE) + return aware_dt.astimezone(timezone.utc) + except Exception as e: + warnings.warn( + f"Error localizing naive datetime '{datetime_str}': {e}", + category=USPTOTimezoneWarning, + stacklevel=2, + ) + if ASSUMED_NAIVE_TIMEZONE == timezone.utc: + return dt_obj.replace(tzinfo=timezone.utc) + return None + else: + return dt_obj.astimezone(timezone.utc)
+ + + +
+[docs] +def serialize_date(d: Optional[date]) -> Optional[str]: + """Serializes a date object into an ISO 8601 string (YYYY-MM-DD). + + Args: + d (Optional[date]): The date object to serialize. + + Returns: + Optional[str]: The date as an ISO 8601 formatted string, or None + if the input is None. + """ + return d.isoformat() if d else None
+ + + +
+[docs] +def serialize_datetime_as_iso(dt: Optional[datetime]) -> Optional[str]: + """Serializes a datetime object to a local-timezone ISO 8601 string. + + If the input datetime object is timezone-aware, it is converted to the + assumed local timezone defined by `ASSUMED_NAIVE_TIMEZONE`. + If it is naive (lacks timezone information), it is first assigned that + assumed local timezone. + + The resulting datetime is formatted as: + YYYY-MM-DDTHH:MM:SS.000±HHMM + (e.g., "2024-12-10T00:00:00.000-0500") + + Args: + dt (Optional[datetime]): The datetime object to serialize. + Can be naive or timezone-aware. + + Returns: + Optional[str]: The datetime formatted in the assumed local timezone, + or None if the input `dt` is None. + """ + if not dt: + return None + + if dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None: + dt = dt.replace(tzinfo=ASSUMED_NAIVE_TIMEZONE) + + dt_local = dt.astimezone(ASSUMED_NAIVE_TIMEZONE) + return dt_local.strftime("%Y-%m-%dT%H:%M:%S.000%z")
+ + + +
+[docs] +def serialize_datetime_as_naive(dt: datetime) -> str: + if dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None: + dt = dt.astimezone(ASSUMED_NAIVE_TIMEZONE).replace(tzinfo=None) + else: + dt = dt.replace(tzinfo=ASSUMED_NAIVE_TIMEZONE).replace(tzinfo=None) + return dt.isoformat()
+ + + +
+[docs] +def parse_yn_to_bool(value: Optional[str]) -> Optional[bool]: + """Converts a 'Y'/'N' (case-insensitive) string to a boolean. + + Args: + value (Optional[str]): The string value to convert. Expected to be + 'Y', 'y', 'N', or 'n'. + + Returns: + Optional[bool]: True if `value` is 'Y' or 'y', False if `value` is + 'N' or 'n'. Returns None if `value` is None or any other string. + + Warns: + USPTOBooleanParseWarning: If the value is not 'Y' or 'N'. + """ + + if value is None: + return None + if value == "": + return None + if value.upper() == "Y": + return True + if value.upper() == "N": + return False + warnings.warn( + f"Unexpected value for Y/N boolean string: '{value}'. Treating as None.", + category=USPTOBooleanParseWarning, + stacklevel=2, + ) + return None
+ + + +
+[docs] +def serialize_bool_to_yn(value: Optional[bool]) -> Optional[str]: + """Converts a boolean value to its 'Y'/'N' string representation. + + Args: + value (Optional[bool]): The boolean value to convert. + + Returns: + Optional[str]: "Y" if `value` is True, "N" if `value` is False. + Returns None if `value` is None. + """ + + if value is None: + return None + return "Y" if value else "N"
+ + + +
+[docs] +def to_camel_case(snake_str: str) -> str: + """Converts a snake_case string to lowerCamelCase. + + For example, "example_snake_string" becomes "exampleSnakeString". + + Args: + snake_str (str): The input string in snake_case. + + Returns: + str: The converted string in lowerCamelCase. + """ + parts = snake_str.split("_") + return parts[0] + "".join(x.title() for x in parts[1:])
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_modules/pyUSPTO/warnings.html b/docs/build/html/_modules/pyUSPTO/warnings.html new file mode 100644 index 0000000..c659d20 --- /dev/null +++ b/docs/build/html/_modules/pyUSPTO/warnings.html @@ -0,0 +1,205 @@ + + + + + + + + pyUSPTO.warnings — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyUSPTO.warnings

+"""
+warnings - Warning classes for pyUSPTO data parsing issues
+
+This module defines custom warning categories for different types of
+data parsing issues encountered when working with USPTO API responses.
+These warnings follow Python's standard warning framework and can be
+controlled using warnings.filterwarnings().
+
+Example:
+    # Suppress all pyUSPTO data warnings
+    import warnings
+    from pyUSPTO.warnings import USPTODataWarning
+    warnings.filterwarnings('ignore', category=USPTODataWarning)
+
+    # Turn specific warnings into errors (strict mode)
+    warnings.filterwarnings('error', category=USPTODateParseWarning)
+"""
+
+
+
+[docs] +class USPTODataWarning(UserWarning): + """Base warning class for USPTO data parsing issues. + + All pyUSPTO data-related warnings inherit from this class, + allowing users to filter all data warnings at once. + """ + + pass
+ + + +
+[docs] +class USPTODateParseWarning(USPTODataWarning): + """Warning for date/datetime string parsing failures. + + Raised when a date or datetime string from the API cannot be + parsed into a Python date/datetime object. The field will be + set to None. + """ + + pass
+ + + +
+[docs] +class USPTOBooleanParseWarning(USPTODataWarning): + """Warning for Y/N boolean string parsing failures. + + Raised when a string that should be 'Y' or 'N' has an unexpected + value. The field will be set to None. + """ + + pass
+ + + +
+[docs] +class USPTOTimezoneWarning(USPTODataWarning): + """Warning for timezone-related issues. + + Raised when timezone data is not available or timezone conversion + fails. Falls back to UTC timezone. + """ + + pass
+ + + +
+[docs] +class USPTOEnumParseWarning(USPTODataWarning): + """Warning for enum value parsing failures. + + Raised when an API response contains a value that doesn't match + any defined enum member. The field will be set to None. + """ + + pass
+ + + +
+[docs] +class USPTODataMismatchWarning(USPTODataWarning): + """Warning for data validation mismatches. + + Raised when the API returns data that doesn't match the requested + identifier (e.g., requesting application 12345678 but receiving 87654321). + This indicates a potential API inconsistency or data integrity issue. + """ + + pass
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2025, Dunlap Codding, P.C..

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/build/html/_sources/api/clients.rst.txt b/docs/build/html/_sources/api/clients.rst.txt index 66d240e..3c555b9 100644 --- a/docs/build/html/_sources/api/clients.rst.txt +++ b/docs/build/html/_sources/api/clients.rst.txt @@ -10,3 +10,23 @@ Clients :members: :undoc-members: :show-inheritance: + +.. automodule:: pyUSPTO.clients.petition_decisions + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.clients.ptab_appeals + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.clients.ptab_interferences + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.clients.ptab_trials + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/build/html/_sources/api/config.rst.txt b/docs/build/html/_sources/api/config.rst.txt index 9a901f6..91dc506 100644 --- a/docs/build/html/_sources/api/config.rst.txt +++ b/docs/build/html/_sources/api/config.rst.txt @@ -1,7 +1,12 @@ Configuration -============ +============= .. automodule:: pyUSPTO.config :members: :undoc-members: :show-inheritance: + +.. automodule:: pyUSPTO.http_config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/api/index.rst.txt b/docs/build/html/_sources/api/index.rst.txt index d1947e9..3137977 100644 --- a/docs/build/html/_sources/api/index.rst.txt +++ b/docs/build/html/_sources/api/index.rst.txt @@ -1,5 +1,5 @@ API Reference -============ +============= .. toctree:: :maxdepth: 2 @@ -8,3 +8,4 @@ API Reference models config exceptions + warnings diff --git a/docs/build/html/_sources/api/models.rst.txt b/docs/build/html/_sources/api/models.rst.txt index ff0a4aa..55b2c67 100644 --- a/docs/build/html/_sources/api/models.rst.txt +++ b/docs/build/html/_sources/api/models.rst.txt @@ -10,3 +10,18 @@ Models :members: :undoc-members: :show-inheritance: + +.. automodule:: pyUSPTO.models.petition_data + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.models.ptab + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.models.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/api/warnings.rst.txt b/docs/build/html/_sources/api/warnings.rst.txt new file mode 100644 index 0000000..293a606 --- /dev/null +++ b/docs/build/html/_sources/api/warnings.rst.txt @@ -0,0 +1,7 @@ +Warnings +======== + +.. automodule:: pyUSPTO.warnings + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/build/html/_sources/development.rst.txt b/docs/build/html/_sources/development.rst.txt index 2e1a9e6..65f2ce1 100644 --- a/docs/build/html/_sources/development.rst.txt +++ b/docs/build/html/_sources/development.rst.txt @@ -1,5 +1,5 @@ Development -========== +=========== Contributing ----------- diff --git a/docs/build/html/_sources/examples/bulk_data.rst.txt b/docs/build/html/_sources/examples/bulk_data.rst.txt index 695e7b2..a4547ab 100644 --- a/docs/build/html/_sources/examples/bulk_data.rst.txt +++ b/docs/build/html/_sources/examples/bulk_data.rst.txt @@ -1,5 +1,5 @@ Bulk Data Examples -================= +================== .. literalinclude:: ../../examples/bulk_data_example.py :language: python diff --git a/docs/build/html/_sources/examples/ifw_example.rst.txt b/docs/build/html/_sources/examples/ifw_example.rst.txt new file mode 100644 index 0000000..d228b48 --- /dev/null +++ b/docs/build/html/_sources/examples/ifw_example.rst.txt @@ -0,0 +1,6 @@ +Image File Wrapper Example +========================== + +.. literalinclude:: ../../../examples/ifw_example.py + :language: python + :linenos: diff --git a/docs/build/html/_sources/examples/index.rst.txt b/docs/build/html/_sources/examples/index.rst.txt index 0b0a3f0..029a8c4 100644 --- a/docs/build/html/_sources/examples/index.rst.txt +++ b/docs/build/html/_sources/examples/index.rst.txt @@ -6,3 +6,8 @@ Examples bulk_data patent_data + ifw_example + petition_decisions + ptab_appeals + ptab_interferences + ptab_trials diff --git a/docs/build/html/_sources/examples/patent_data.rst.txt b/docs/build/html/_sources/examples/patent_data.rst.txt index 946ec55..31a7253 100644 --- a/docs/build/html/_sources/examples/patent_data.rst.txt +++ b/docs/build/html/_sources/examples/patent_data.rst.txt @@ -1,6 +1,6 @@ Patent Data Examples -================== +==================== -.. literalinclude:: ../../examples/patent_data_example.py +.. literalinclude:: ../../../examples/patent_data_example.py :language: python :linenos: diff --git a/docs/build/html/_sources/examples/petition_decisions.rst.txt b/docs/build/html/_sources/examples/petition_decisions.rst.txt new file mode 100644 index 0000000..06bf5b8 --- /dev/null +++ b/docs/build/html/_sources/examples/petition_decisions.rst.txt @@ -0,0 +1,6 @@ +Petition Decisions Example +========================== + +.. literalinclude:: ../../../examples/petition_decisions_example.py + :language: python + :linenos: diff --git a/docs/build/html/_sources/examples/ptab_appeals.rst.txt b/docs/build/html/_sources/examples/ptab_appeals.rst.txt new file mode 100644 index 0000000..7bd1173 --- /dev/null +++ b/docs/build/html/_sources/examples/ptab_appeals.rst.txt @@ -0,0 +1,6 @@ +PTAB Appeals Example +==================== + +.. literalinclude:: ../../../examples/ptab_appeals_example.py + :language: python + :linenos: diff --git a/docs/build/html/_sources/examples/ptab_interferences.rst.txt b/docs/build/html/_sources/examples/ptab_interferences.rst.txt new file mode 100644 index 0000000..2c61060 --- /dev/null +++ b/docs/build/html/_sources/examples/ptab_interferences.rst.txt @@ -0,0 +1,6 @@ +PTAB Interferences Example +========================== + +.. literalinclude:: ../../../examples/ptab_interferences_example.py + :language: python + :linenos: diff --git a/docs/build/html/_sources/examples/ptab_trials.rst.txt b/docs/build/html/_sources/examples/ptab_trials.rst.txt new file mode 100644 index 0000000..24336ec --- /dev/null +++ b/docs/build/html/_sources/examples/ptab_trials.rst.txt @@ -0,0 +1,6 @@ +PTAB Trials Example +=================== + +.. literalinclude:: ../../../examples/ptab_trials_example.py + :language: python + :linenos: diff --git a/docs/build/html/_sources/installation.rst.txt b/docs/build/html/_sources/installation.rst.txt index 08b42c7..8580156 100644 --- a/docs/build/html/_sources/installation.rst.txt +++ b/docs/build/html/_sources/installation.rst.txt @@ -1,5 +1,5 @@ Installation -=========== +============ .. code-block:: bash diff --git a/docs/build/html/_sources/quickstart.rst.txt b/docs/build/html/_sources/quickstart.rst.txt index 3aba801..63c860c 100644 --- a/docs/build/html/_sources/quickstart.rst.txt +++ b/docs/build/html/_sources/quickstart.rst.txt @@ -1,5 +1,5 @@ Quick Start -========== +=========== This guide will help you get started with the pyUSPTO library. diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js index 6078937..5cf2cf3 100644 --- a/docs/build/html/_static/documentation_options.js +++ b/docs/build/html/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.1.4.dev0+ga92fa00.d20250320', + VERSION: '0.2.3.dev1+g2c5d89b38.d20251125', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/build/html/api/clients.html b/docs/build/html/api/clients.html index 84f77b7..42088d5 100644 --- a/docs/build/html/api/clients.html +++ b/docs/build/html/api/clients.html @@ -6,7 +6,7 @@ - Clients — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Clients — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -63,26 +63,59 @@
  • PatentDataClient +
  • +
  • FinalPetitionDecisionsClient +
  • +
  • PTABAppealsClient +
  • +
  • PTABInterferencesClient +
  • +
  • PTABTrialsClient
  • @@ -90,6 +123,7 @@
  • Models
  • Configuration
  • Exceptions
  • +
  • Warnings
  • Examples
  • @@ -143,9 +177,9 @@
    Parameters:
    @@ -159,11 +193,11 @@
    Parameters:
    • file_data (FileData) – FileData object containing file information

    • -
    • destination (str) – Directory where the file should be saved

    • +
    • destination (str) – Directory where the file should be saved

    Return type:
    -

    str

    +

    str

    Returns:

    Path to the downloaded file

    @@ -178,13 +212,13 @@
    Parameters:
      -
    • product_id (str) – The product identifier

    • -
    • file_data_from_date (Optional[str]) – Filter files by data from date (YYYY-MM-DD)

    • -
    • file_data_to_date (Optional[str]) – Filter files by data to date (YYYY-MM-DD)

    • -
    • offset (Optional[int]) – Number of product file records to skip

    • -
    • limit (Optional[int]) – Number of product file records to collect

    • -
    • include_files (Optional[bool]) – Whether to include product files in the response

    • -
    • latest (Optional[bool]) – Whether to return only the latest product file

    • +
    • product_id (str) – The product identifier

    • +
    • file_data_from_date (str | None) – Filter files by data from date (YYYY-MM-DD)

    • +
    • file_data_to_date (str | None) – Filter files by data to date (YYYY-MM-DD)

    • +
    • offset (int | None) – Number of product file records to skip

    • +
    • limit (int | None) – Number of product file records to collect

    • +
    • include_files (bool | None) – Whether to include product files in the response

    • +
    • latest (bool | None) – Whether to return only the latest product file

    Return type:
    @@ -203,7 +237,7 @@

    This method is deprecated. Use search_products instead.

    Parameters:
    -

    params (Optional[Dict[str, Any]]) – Optional query parameters

    +

    params (Dict[str, Any] | None) – Optional query parameters

    Return type:

    BulkDataResponse

    @@ -220,13 +254,13 @@

    Paginate through all products matching the search criteria.

    Parameters:
    -

    **kwargs (Any) – Keyword arguments to pass to search_products

    +

    **kwargs (Any) – Keyword arguments to pass to search_products

    Yields:

    BulkDataProduct objects

    Return type:
    -

    Iterator[BulkDataProduct]

    +

    Iterator[BulkDataProduct]

    @@ -238,21 +272,21 @@
    Parameters:
      -
    • query (Optional[str]) – Search text

    • -
    • product_title (Optional[str]) – Filter by product title

    • -
    • product_description (Optional[str]) – Filter by product description

    • -
    • product_short_name (Optional[str]) – Filter by product identifier (short name)

    • -
    • from_date (Optional[str]) – Filter products with data from this date (YYYY-MM-DD)

    • -
    • to_date (Optional[str]) – Filter products with data until this date (YYYY-MM-DD)

    • -
    • categories (Optional[List[str]]) – Filter by dataset categories

    • -
    • labels (Optional[List[str]]) – Filter by product labels

    • -
    • datasets (Optional[List[str]]) – Filter by datasets

    • -
    • file_types (Optional[List[str]]) – Filter by file types

    • -
    • offset (Optional[int]) – Number of product records to skip

    • -
    • limit (Optional[int]) – Number of product records to collect

    • -
    • include_files (Optional[bool]) – Whether to include product files in the response

    • -
    • latest (Optional[bool]) – Whether to return only the latest product file for each product

    • -
    • facets (Optional[bool]) – Whether to enable facets in the response

    • +
    • query (str | None) – Search text

    • +
    • product_title (str | None) – Filter by product title

    • +
    • product_description (str | None) – Filter by product description

    • +
    • product_short_name (str | None) – Filter by product identifier (short name)

    • +
    • from_date (str | None) – Filter products with data from this date (YYYY-MM-DD)

    • +
    • to_date (str | None) – Filter products with data until this date (YYYY-MM-DD)

    • +
    • categories (List[str] | None) – Filter by dataset categories

    • +
    • labels (List[str] | None) – Filter by product labels

    • +
    • datasets (List[str] | None) – Filter by datasets

    • +
    • file_types (List[str] | None) – Filter by file types

    • +
    • offset (int | None) – Number of product records to skip

    • +
    • limit (int | None) – Number of product records to collect

    • +
    • include_files (bool | None) – Whether to include product files in the response

    • +
    • latest (bool | None) – Whether to return only the latest product file for each product

    • +
    • facets (bool | None) – Whether to enable facets in the response

    Return type:
    @@ -276,78 +310,170 @@

    Client for interacting with the USPTO Patent Data API.

    -ENDPOINTS = {'application_adjustment': 'applications/{application_number}/adjustment', 'application_assignment': 'applications/{application_number}/assignment', 'application_associated_documents': 'applications/{application_number}/associated-documents', 'application_attorney': 'applications/{application_number}/attorney', 'application_by_number': 'applications/{application_number}', 'application_continuity': 'applications/{application_number}/continuity', 'application_documents': 'applications/{application_number}/documents', 'application_foreign_priority': 'applications/{application_number}/foreign-priority', 'application_metadata': 'applications/{application_number}/meta-data', 'application_transactions': 'applications/{application_number}/transactions', 'applications_search': 'applications/search', 'applications_search_download': 'applications/search/download', 'download_document': 'download/applications/{application_number}/{document_id}', 'status_codes': 'status-codes'}
    +ENDPOINTS = {'download_application_document': 'api/v1/download/applications/{application_number}/{document_id}', 'get_application_adjustment': 'api/v1/patent/applications/{application_number}/adjustment', 'get_application_assignment': 'api/v1/patent/applications/{application_number}/assignment', 'get_application_associated_documents': 'api/v1/patent/applications/{application_number}/associated-documents', 'get_application_attorney': 'api/v1/patent/applications/{application_number}/attorney', 'get_application_by_number': 'api/v1/patent/applications/{application_number}', 'get_application_continuity': 'api/v1/patent/applications/{application_number}/continuity', 'get_application_documents': 'api/v1/patent/applications/{application_number}/documents', 'get_application_foreign_priority': 'api/v1/patent/applications/{application_number}/foreign-priority', 'get_application_metadata': 'api/v1/patent/applications/{application_number}/meta-data', 'get_application_transactions': 'api/v1/patent/applications/{application_number}/transactions', 'get_search_results': 'api/v1/patent/applications/search/download', 'search_applications': 'api/v1/patent/applications/search', 'status_codes': 'api/v1/patent/status-codes'}
    -
    -__init__(api_key=None, base_url=None, config=None)[source]
    -

    Initialize the PatentDataClient.

    +
    +download_archive(printed_metadata, file_name=None, destination_path=None, overwrite=False)[source]
    +

    Downloads Printed Metadata (XML data). These are XML files of the patent as printed.

    +
    +

    Note

    +

    See also download_publication() for a clearer method name with identical functionality.

    +
    Parameters:
      -
    • api_key (Optional[str]) – Optional API key for authentication

    • -
    • base_url (Optional[str]) – The base URL of the API, defaults to config.patent_data_base_url or “https://api.uspto.gov/api/v1/patent”

    • -
    • config (Optional[USPTOConfig]) – Optional USPTOConfig instance

    • +
    • printed_metadata (PrintedMetaData) – ArchiveMetaData object containing download URL and metadata

    • +
    • file_name (str | None) – Optional filename. If not provided, uses xml_file_name from metadata

    • +
    • destination_path (str | None) – Optional directory path to save the file

    • +
    • overwrite (bool) – Whether to overwrite existing files. Default False

    • +
    +
    +
    Returns:
    +

    Path to the downloaded file

    +
    +
    Return type:
    +

    str

    +
    +
    Raises:
    +
    -
    -download_application_document(application_number, document_id, destination)[source]
    -

    Download a document for a patent application.

    +
    +download_document(document_format, file_name=None, destination_path=None, overwrite=False, stream=True)[source]
    +

    Downloads a document in the specified format.

    Parameters:
      -
    • application_number (str) – The application number

    • -
    • document_id (str) – The document identifier

    • -
    • destination (str) – Directory where the file should be saved

    • +
    • document_format (DocumentFormat) – DocumentFormat object containing download URL and metadata

    • +
    • file_name (str | None) – Optional filename. If not provided, extracted from URL

    • +
    • destination_path (str | None) – Optional path - can be a directory OR a complete file path

    • +
    • overwrite (bool) – Whether to overwrite existing files. Default False

    • +
    • stream (bool) – Whether to stream the download. Default True for large files

    -
    Return type:
    -

    str

    +
    Returns:
    +

    Path to the downloaded file

    -
    Returns:
    -

    Path to the downloaded file

    +
    Return type:
    +

    str

    +
    +
    Raises:
    +
    -
    -download_patent_applications(params=None, format='json')[source]
    -

    Download patent data with specified format.

    +
    +download_publication(printed_metadata, file_name=None, destination_path=None, overwrite=False)[source]
    +

    Download a publication XML file (grant or pre-grant publication).

    +

    This method downloads publication XML files from PrintedMetaData objects, +such as grant documents or pre-grant publications (pgpub). The filename +is automatically extracted from the metadata if not provided.

    Parameters:
      -
    • params (Optional[Dict[str, Any]]) – Optional query parameters

    • -
    • format (str) – Download format (json or csv)

    • +
    • printed_metadata (PrintedMetaData) – PrintedMetaData object containing the publication +download URL and filename information. Typically obtained from +get_application_associated_documents() or from PatentFileWrapper’s +grant_document_meta_data or pg_publication_document_meta_data.

    • +
    • file_name (str | None) – Optional custom filename. If not provided, uses the +xml_file_name from the metadata (e.g., “18915708_12307527.xml”).

    • +
    • destination_path (str | None) – Optional directory path where the file should be saved. +If not provided, saves to the current directory. The directory will +be created if it doesn’t exist.

    • +
    • overwrite (bool) – Whether to overwrite an existing file at the destination. +Default is False, which raises FileExistsError if file exists.

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    Absolute path to the downloaded publication file.

    -
    Returns:
    -

    PatentDataResponse object containing the API response

    +
    Return type:
    +

    str

    +
    +
    Raises:
    +
      +
    • ValueError – If printed_metadata has no file_location_uri (download URL).

    • +
    • FileExistsError – If the file already exists and overwrite=False.

    • +
    +
    +

    Examples

    +

    Download grant XML to a specific directory (auto-filename):

    +
    >>> response = client.get_application_by_number("18/915,708")
    +>>> ifw = response
    +>>> grant_metadata = ifw.grant_document_meta_data
    +>>> path = client.download_publication(grant_metadata, destination_path="./downloads")
    +>>> print(path)
    +'./downloads/18915708_12307527.xml'
    +
    +
    +

    Download pgpub XML with custom filename:

    +
    >>> pgpub_metadata = ifw.pg_publication_document_meta_data
    +>>> path = client.download_publication(
    +...     pgpub_metadata,
    +...     file_name="my_publication.xml",
    +...     destination_path="./downloads"
    +... )
    +>>> print(path)
    +'./downloads/my_publication.xml'
    +
    +
    +

    Download to current directory:

    +
    >>> path = client.download_publication(grant_metadata)
    +>>> print(path)
    +'./18915708_12307527.xml'
    +
    +
    +
    -
    -download_patent_applications_post(download_request)[source]
    -

    Download patent data using POST method with JSON payload.

    +
    +get_IFW_metadata(application_number=None, publication_number=None, patent_number=None, PCT_app_number=None, PCT_pub_number=None)[source]
    +

    Retrieves complete patent file wrapper data using common identifiers.

    +

    This utility fetches the PatentFileWrapper, which contains comprehensive +IFW metadata, application details, and more. Provide only one +identifier if possible. If multiple are given, they are processed in the +order listed in the arguments, and the first successful match is returned.

    Parameters:
    -

    download_request (Dict[str, Any]) – JSON payload with download parameters including format

    +
      +
    • application_number (Optional[str], optional) – USPTO application number +(e.g., “16123456”). Checked first (direct lookup).

    • +
    • patent_number (Optional[str], optional) – USPTO patent number +(e.g., “11000000”). Checked second (uses search).

    • +
    • publication_number (Optional[str], optional) – USPTO pre-grant +publication number (e.g., “20230123456”). Checked third (uses search).

    • +
    • PCT_app_number (Optional[str], optional) – PCT application number. +Checked fourth (direct lookup, treated as USPTO app#).

    • +
    • PCT_pub_number (Optional[str], optional) – PCT publication number +(e.g., “2023012345”). Checked fifth (uses search).

    • +
    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A PatentFileWrapper object with

    comprehensive data if found using one of the identifiers, +otherwise None.

    -
    Returns:
    -

    PatentDataResponse object containing the API response

    +
    +

    +
    +
    Return type:
    +

    Optional[PatentFileWrapper]

    @@ -355,16 +481,28 @@
    get_application_adjustment(application_number)[source]
    -

    Get patent term adjustment data for an application.

    +

    Retrieves patent term adjustment (PTA) data for a specific application.

    +

    This method fetches the PatentTermAdjustmentData component from the +full patent file wrapper. This data includes details on various delay +quantities (e.g., A, B, C delays, applicant delays), the total +calculated adjustment, and a history of PTA events that influenced the +term.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which PTA +data is being requested (e.g., “16123456”).

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A PatentTermAdjustmentData

    object containing the PTA details if the application is found +and has such data. Returns None if the application cannot be +found or if PTA data is not available in the response.

    -
    Returns:
    -

    PatentDataResponse object containing the adjustment data

    +
    +

    +
    +
    Return type:
    +

    Optional[PatentTermAdjustmentData]

    @@ -372,16 +510,29 @@
    get_application_assignment(application_number)[source]
    -

    Get assignment data for an application.

    +

    Retrieves a list of patent assignments for a specific application.

    +

    This method fetches the assignment_bag from the patent file wrapper, +which contains a list of Assignment objects. Each Assignment object +details an assignment including information such as reel and frame numbers, +recording dates, conveyance text, and details about the assignors and assignees.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +assignment data is being requested (e.g., “16123456”).

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A list of Assignment objects, each

    representing a recorded assignment for the application. Returns +None if the application cannot be found, or if no assignment +data is available in the response. An empty list may be +returned if the application is found but has no recorded +assignments.

    -
    Returns:
    -

    PatentDataResponse object containing the assignment data

    +
    +

    +
    +
    Return type:
    +

    Optional[List[Assignment]]

    @@ -389,16 +540,34 @@
    get_application_associated_documents(application_number)[source]
    -

    Get associated documents metadata for an application.

    +

    Retrieves metadata for Pre-Grant Publication and Grant documents.

    +

    This method fetches metadata specifically for published documents associated +with the patent application, such as Pre-Grant Publications (PGPUBs) +and granted patent documents. It does not retrieve the prosecution +history documents (see get_application_documents for that). +The result is a PrintedPublication object, which holds +PrintedMetaData including file URIs and names. Download with download_archive.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +associated PGPUB/Grant document metadata is being requested +(e.g., “16123456”).

    +
    +
    Returns:
    +

    +
    A PrintedPublication object

    containing PrintedMetaData for the Pre-Grant Publication +and/or the Grant document, if available. Returns None if the +application cannot be found or if no such associated document +metadata is available. The fields within the returned object +(pgpub_document_meta_data, grant_document_meta_data) +may themselves be None if a particular type of document +(e.g., PGPUB) does not exist for the application.

    -
    Return type:
    -

    PatentDataResponse

    +
    +

    -
    Returns:
    -

    PatentDataResponse object containing the associated documents metadata

    +
    Return type:
    +

    Optional[PrintedPublication]

    @@ -406,16 +575,57 @@
    get_application_attorney(application_number)[source]
    -

    Get attorney/agent data for an application.

    +

    Retrieves data for the attorney(s) of record for a specific application.

    +

    This method fetches the RecordAttorney object associated with the +patent application. This object contains details about the attorney(s) +of record, including customer number correspondence data, power of attorney +information, and a list of listed attorneys.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +attorney data is being requested (e.g., “16123456”).

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A RecordAttorney object with details

    about the attorney(s) of record if the application is found +and such data exists. Returns None if the application cannot +be found or if no attorney data is available in the response.

    -
    Returns:
    -

    PatentDataResponse object containing the attorney data

    +
    +

    +
    +
    Return type:
    +

    Optional[RecordAttorney]

    +
    +
    +
    + +
    +
    +get_application_by_number(application_number)[source]
    +

    Retrieves the full details for a specific patent application by its number.

    +

    This method fetches comprehensive information for a single patent application +identified by its unique application number.

    +
    +
    Parameters:
    +

    application_number (str) – The USPTO application number for the patent +application (e.g., “16123456” or “18/915,708”). The application +number will be automatically sanitized to remove commas and spaces.

    +
    +
    Returns:
    +

    +
    A PatentFileWrapper object representing

    the complete file wrapper for the application if found. This object +contains all data sections related to the application, such as +metadata, addresses, assignments, attorney/agent data, continuity +data, PTA/PTE data, transactions, and associated documents. +Returns None if the application cannot be found or if the response +does not contain the expected data.

    +
    +
    +

    +
    +
    Return type:
    +

    Optional[PatentFileWrapper]

    @@ -423,33 +633,71 @@
    get_application_continuity(application_number)[source]
    -

    Get continuity data for an application.

    +

    Retrieves continuity data (parent/child applications) for a specific application.

    +

    This method fetches the lineage of the specified application, returning an +ApplicationContinuityData object. This object consolidates lists of +ParentContinuity (applications to which the current one claims priority) +and ChildContinuity (applications claiming priority to the current one) +objects, each detailing the related application’s key identifiers and status.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +continuity data is being requested (e.g., “16123456”).

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    An ApplicationContinuityData

    object containing lists of parent and child continuity relationships. +Returns None if the application cannot be found or if the underlying +data to construct continuity is not available. The lists within +the returned object may be empty if no parent or child continuity +links exist.

    -
    Returns:
    -

    PatentDataResponse object containing the continuity data

    +
    +

    +
    +
    Return type:
    +

    Optional[ApplicationContinuityData]

    -get_application_documents(application_number)[source]
    -

    Get document details for an application.

    +get_application_documents(application_number, document_codes=None, official_date_from=None, official_date_to=None)[source] +

    Retrieves metadata for documents associated with a specific application.

    +

    This method fetches a collection of document metadata related to the given +patent application. The result is a DocumentBag object, which is an +iterable collection of Document instances. Each Document object +contains metadata such as its identifier, official date, document code +and description, direction (incoming/outgoing), and available download +formats.

    Parameters:
    -

    application_number (str) – The application number

    +
      +
    • application_number (str) – The USPTO application number for which +document metadata is being requested (e.g., “16123456”).

    • +
    • document_codes (Optional[List[str]]) – Filter by specific document type +codes. If provided, only documents with these codes will be returned. +Examples: [‘ABST’, ‘CLM’, ‘SPEC’, ‘DRWD’].

    • +
    • official_date_from (Optional[str]) – Filter documents from this date +(inclusive). Date format: YYYY-MM-DD (e.g., “2020-01-15”).

    • +
    • official_date_to (Optional[str]) – Filter documents to this date +(inclusive). Date format: YYYY-MM-DD (e.g., “2023-12-31”).

    • +
    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A DocumentBag object containing metadata for all

    publicly available documents associated with the application +that match the provided filters. The bag will be empty if no +documents are found or if the API response indicates no documents. +It does not return None for “not found” cases; an empty collection +is returned instead.

    -
    Returns:
    -

    PatentDataResponse object containing document details

    +
    +

    +
    +
    Return type:
    +

    DocumentBag

    @@ -457,16 +705,29 @@
    get_application_foreign_priority(application_number)[source]
    -

    Get foreign priority data for an application.

    +

    Retrieves a list of foreign priority claims for a specific application.

    +

    This method fetches the foreign_priority_bag from the patent file +wrapper. This bag contains a list of ForeignPriority objects, each +representing a claim to a foreign patent application’s priority date. +Details include the IP office name, filing date, and application number +of the foreign priority application.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +foreign priority data is being requested (e.g., “16123456”).

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A list of ForeignPriority objects,

    each detailing a claimed foreign priority. Returns None if the +application cannot be found or if no foreign priority data is +available. An empty list may be returned if the application +is found but has no foreign priority claims.

    -
    Returns:
    -

    PatentDataResponse object containing the foreign priority data

    +
    +

    +
    +
    Return type:
    +

    Optional[List[ForeignPriority]]

    @@ -474,16 +735,29 @@
    get_application_metadata(application_number)[source]
    -

    Get metadata for a specific patent application.

    +

    Retrieves key metadata for a specific patent application.

    +

    This method fetches the ApplicationMetaData component from the full +patent file wrapper. The metadata includes a wide range of information +such as application status, important dates (filing, grant, publication), +applicant and inventor details, classification data, and other core +identifying information for the application.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +metadata is being requested (e.g., “16123456” or “18/915,708”). +The application number will be automatically sanitized.

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    An ApplicationMetaData object

    containing the core details of the patent application if found. +Returns None if the application cannot be found or if metadata +is not available in the response.

    -
    Returns:
    -

    PatentDataResponse object containing the application metadata

    +
    +

    +
    +
    Return type:
    +

    Optional[ApplicationMetaData]

    @@ -491,169 +765,964 @@
    get_application_transactions(application_number)[source]
    -

    Get transaction data for an application.

    +

    Retrieves the transaction history (events) for a specific application.

    +

    This method fetches the event_data_bag from the patent file wrapper. +This bag contains a list of EventData objects, each representing a +single recorded event in the prosecution history of the patent application. +Events include details like an event code, a textual description, and +the date the event was recorded.

    Parameters:
    -

    application_number (str) – The application number

    +

    application_number (str) – The USPTO application number for which +transaction history is being requested (e.g., “16123456”).

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    A list of EventData objects, each

    detailing a transaction or event in the application’s history. +Returns None if the application cannot be found or if no +transaction data is available. An empty list may be returned if +the application is found but has no recorded transaction events.

    -
    Returns:
    -

    PatentDataResponse object containing the transaction data

    +
    +

    +
    +
    Return type:
    +

    Optional[List[EventData]]

    -
    -get_patent_applications(params=None)[source]
    -

    Get a list of patent applications using the search endpoint.

    +
    +get_search_results(query=None, sort=None, offset=0, limit=25, fields_param=None, filters_param=None, range_filters_param=None, post_body=None, application_number_q=None, patent_number_q=None, inventor_name_q=None, applicant_name_q=None, assignee_name_q=None, filing_date_from_q=None, filing_date_to_q=None, grant_date_from_q=None, grant_date_to_q=None, classification_q=None, additional_query_params=None)[source]
    +

    Fetches a dataset of patent applications based on search criteria, always requesting JSON format. +For GET, parameters align with OpenAPI for /api/v1/patent/applications/search/download. +For POST, post_body should conform to PatentDownloadRequest schema.

    +
    +
    Return type:
    +

    list[ApplicationMetaData]

    +
    +
    +
    + +
    +
    +get_status_codes(params=None)[source]
    +

    Retrieves USPTO patent application status codes and their descriptions.

    +

    This method fetches a list of defined USPTO patent application status codes +(e.g., codes for “Pending,” “Abandoned,” “Issued”) using a GET request. +The request can be customized with query parameters to filter or paginate +the results if supported by the API endpoint.

    Parameters:
    -

    params (Optional[Dict[str, Any]]) – Optional query parameters including: -- q: Search query string -- sort: Field to sort by followed by sort order -- offset: Position in dataset to start from -- limit: Number of results to return -- facets: List of fields to facet upon -- fields: Fields to include in response -- filters: Field filters -- rangeFilters: Range filters

    +

    params (Optional[Dict[str, Any]], optional) – A dictionary of query +parameters to be sent with the GET request. These parameters can +be used to filter or control the output of the status codes +list. Defaults to None, which typically retrieves all available +status codes or the API’s default set.

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    +
    An object containing a count of matching

    status codes, a StatusCodeCollection of the StatusCode +objects (code and description), and a request identifier.

    -
    Returns:
    -

    PatentDataResponse object containing the API response

    +
    +

    +
    +
    Return type:
    +

    StatusCodeSearchResponse

    -
    -get_patent_by_application_number(application_number)[source]
    -

    Get a specific patent by application number.

    +
    +paginate_applications(**kwargs)[source]
    +

    Provides an iterator to easily paginate through patent application search results.

    +

    This method simplifies the process of fetching all patent applications +that match a given search query by automatically handling pagination. +It internally calls the search_applications method for GET requests, +batching results and yielding them one by one.

    +

    All keyword arguments provided to this method (**kwargs) are passed +directly to the search_applications method to define the search +criteria. See the search_applications method for more details +on available parameters. +The offset and limit parameters are managed by the pagination logic; +setting them directly in kwargs might lead to unexpected behavior.

    Parameters:
    -

    application_number (str) – The application number

    +

    **kwargs (Any) – Keyword arguments to be passed to the +search_applications method for constructing the search query. +These define the criteria for the patent applications to be +retrieved. Do not include post_body.

    -
    Return type:
    -

    PatentFileWrapper

    +
    Returns:
    +

    +
    An iterator that yields PatentFileWrapper

    objects, allowing iteration over all matching patent applications +across multiple pages of results.

    -
    Returns:
    -

    PatentFileWrapper object containing the patent data

    +
    +

    +
    +
    Return type:
    +

    Iterator[PatentFileWrapper]

    +
    +
    Raises:
    +

    ValueError – If post_body is included in kwargs, as this + method only supports GET request parameters for pagination.

    -
    -get_patent_status_codes(params=None)[source]
    -

    Get patent application status codes and descriptions.

    +
    +sanitize_application_number(input_number)[source]
    +

    Sanitize and validate a USPTO application number.

    +

    Application numbers are either: +- 8 digits (e.g., “16123456”) +- Series code format: 2 digits + “/” + 6 digits (e.g., “08/123456”) +- PCT format: “PCT/US2024/012345” → “PCTUS2412345”

    +

    This method removes common separators (commas, spaces) while preserving +the “/” in series code format.

    Parameters:
    -

    params (Optional[Dict[str, Any]]) – Optional query parameters including: -- q: Search query string -- offset: Position in dataset to start from -- limit: Number of results to return

    +

    input_number (str) – Raw application number input. May include commas, +spaces, or other formatting.

    -
    Return type:
    -

    Dict[str, Any]

    +
    Returns:
    +

    Sanitized application number (either “NNNNNNNN” or “NN/NNNNNN”).

    -
    Returns:
    -

    Dictionary containing status codes and descriptions

    +
    Return type:
    +

    str

    +
    +
    Raises:
    +

    ValueError – If the format is invalid.

    +
    +
    +
    +

    Examples

    +
    >>> client.sanitize_application_number("16123456")
    +"16123456"
    +>>> client.sanitize_application_number("16,123,456")
    +"16123456"
    +>>> client.sanitize_application_number("08/123456")
    +"08/123456"
    +>>> client.sanitize_application_number("08/123,456")
    +"08/123456"
    +
    +
    +
    +
    + +
    +
    +search_applications(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, application_number_q=None, patent_number_q=None, inventor_name_q=None, applicant_name_q=None, assignee_name_q=None, filing_date_from_q=None, filing_date_to_q=None, grant_date_from_q=None, grant_date_to_q=None, classification_q=None, earliestPublicationNumber_q=None, pctPublicationNumber_q=None, additional_query_params=None)[source]
    +

    Searches for patent applications. +Can perform a GET request based on OpenAPI query parameters or a POST request if post_body is specified.

    +
    +
    Return type:
    +

    PatentDataResponse

    -
    -paginate_patents(**kwargs)[source]
    -

    Paginate through all patents matching the search criteria.

    +
    +search_status_codes(search_request)[source]
    +

    Searches USPTO patent application status codes using POST criteria.

    +

    Performs targeted searches for USPTO patent application status codes +(e.g., for “Pending,” “Abandoned,” “Issued”) by sending a POST request +with a JSON body containing the search_request criteria. This method +is suited for more complex queries than the GET-based get_status_codes.

    Parameters:
    -

    **kwargs (Any) – Keyword arguments to pass to search_patents

    +

    search_request (Dict[str, Any]) – A dictionary with search criteria, +sent as the JSON POST body. The structure must conform to USPTO +API requirements for this endpoint (e.g., for searching by code +or description keywords).

    -
    Yields:
    -

    PatentFileWrapper objects

    +
    Returns:
    +

    +
    An object containing a count of matching

    status codes, a StatusCodeCollection of the StatusCode +objects (code and description), and a request identifier.

    +
    +
    +

    Return type:
    -

    Iterator[PatentFileWrapper]

    +

    StatusCodeSearchResponse

    +
    + +

    clients.petition_decisions - Client for USPTO Final Petition Decisions API

    +

    This module provides a client for interacting with the USPTO Final Petition +Decisions API. It allows you to search for and retrieve final agency petition +decisions in publicly available patent applications and patents filed in 2001 or later.

    +
    +
    +class pyUSPTO.clients.petition_decisions.FinalPetitionDecisionsClient(api_key=None, base_url=None, config=None)[source]
    +

    Bases: BaseUSPTOClient[PetitionDecisionResponse]

    +

    Client for interacting with the USPTO Final Petition Decisions API.

    +

    This client provides methods to search for petition decisions, retrieve specific +decisions by ID, download decision data, and download associated documents.

    +

    Final petition decisions data are incrementally added to the USPTO Open Data Portal +on a monthly basis starting with data from 2022 and later.

    +
    +
    +ENDPOINTS = {'download_decisions': 'api/v1/petition/decisions/search/download', 'get_decision_by_id': 'api/v1/petition/decisions/{petitionDecisionRecordIdentifier}', 'search_decisions': 'api/v1/petition/decisions/search'}
    +
    +
    -
    -search_patent_applications_post(search_request)[source]
    -

    Search patent applications using POST method with JSON payload.

    +
    +__init__(api_key=None, base_url=None, config=None)[source]
    +

    Initialize the FinalPetitionDecisionsClient.

    Parameters:
    -

    search_request (Dict[str, Any]) – JSON payload with search parameters including: -- q: Search query string -- filters: Array of filter objects -- rangeFilters: Array of range filter objects -- sort: Array of sort objects -- fields: Array of field names to include -- pagination: Pagination object -- facets: Array of facet field names

    +
      +
    • api_key (str | None) – Optional API key for authentication.

    • +
    • base_url (str | None) – Optional base URL override for the API.

    • +
    • config (USPTOConfig | None) – Optional USPTOConfig instance for configuration.

    • +
    -
    Return type:
    -

    PatentDataResponse

    +
    +
    + +
    +
    +download_decisions(format='json', query=None, sort=None, offset=None, limit=None, fields=None, filters=None, range_filters=None, application_number_q=None, patent_number_q=None, inventor_name_q=None, applicant_name_q=None, decision_date_from_q=None, decision_date_to_q=None, additional_query_params=None, file_name=None, destination_path=None, overwrite=False)[source]
    +

    Downloads petition decisions data in the specified format.

    +

    This endpoint is designed for bulk downloads of petition decisions data. +It supports JSON and CSV formats.

    +
    +
    Parameters:
    +
      +
    • format (str) – Download format, either “json” or “csv”. Defaults to “json”.

    • +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • application_number_q (str | None) – Filter by application number.

    • +
    • patent_number_q (str | None) – Filter by patent number.

    • +
    • inventor_name_q (str | None) – Filter by inventor name.

    • +
    • applicant_name_q (str | None) – Filter by applicant name.

    • +
    • decision_date_from_q (str | None) – Filter decisions from this date (YYYY-MM-DD).

    • +
    • decision_date_to_q (str | None) – Filter decisions to this date (YYYY-MM-DD).

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    • file_name (str | None) – Optional filename for CSV downloads. Defaults to “petition_decisions.csv”.

    • +
    • destination_path (str | None) – Optional directory path to save CSV file. If None, returns Response.

    • +
    • overwrite (bool) – Whether to overwrite existing files. Default False.

    • +
    -
    Returns:
    -

    PatentDataResponse object containing the API response

    +
    Returns:
    +

      +
    • If format=”json”: Returns PetitionDecisionDownloadResponse

    • +
    • If format=”csv” and destination_path is None: Returns streaming Response

    • +
    • If format=”csv” and destination_path is set: Returns str path to saved file

    • +
    +

    +
    +
    Return type:
    +

    Union[PetitionDecisionDownloadResponse, requests.Response, str]

    +
    +
    Raises:
    +

    FileExistsError – If CSV file exists and overwrite=False

    +
    +

    Examples

    +

    # Download as JSON +>>> download = client.download_decisions( +… format=”json”, +… technology_center_q=”1700”, +… limit=1000 +… ) +>>> for decision in download.petition_decision_data: +… print(decision.application_number_text)

    +

    # Download CSV and save to file +>>> file_path = client.download_decisions( +… format=”csv”, +… decision_date_from_q=”2023-01-01”, +… destination_path=”./downloads” +… ) +>>> print(f”Saved to: {file_path}”)

    +

    # Download CSV as streaming response (advanced usage) +>>> response = client.download_decisions(format=”csv”) +>>> with open(“decisions.csv”, “wb”) as f: +… for chunk in response.iter_content(chunk_size=8192): +… f.write(chunk)

    +
    -
    -search_patent_status_codes_post(search_request)[source]
    -

    Search patent status codes using POST method with JSON payload.

    +
    +download_petition_document(download_option, file_name=None, destination_path=None, overwrite=False)[source]
    +

    Downloads a petition decision document in the specified format.

    Parameters:
    -

    search_request (Dict[str, Any]) – JSON payload with search parameters

    +
      +
    • download_option (DocumentDownloadOption) – DocumentDownloadOption object containing the download +URL and metadata.

    • +
    • file_name (str | None) – Optional filename for the downloaded file. If not provided, +it will be extracted from the URL or generated based on the MIME type.

    • +
    • destination_path (str | None) – Optional directory path where the file should be saved. +If not provided, saves to the current directory.

    • +
    • overwrite (bool) – Whether to overwrite an existing file. Defaults to False.

    • +
    -
    Return type:
    -

    Dict[str, Any]

    +
    Returns:
    +

    The absolute path to the downloaded file.

    -
    Returns:
    -

    Dictionary containing status codes and descriptions

    +
    Return type:
    +

    str

    +
    +
    Raises:
    +
    +
    +

    Examples

    +

    # Download first document from a decision +>>> decision = client.get_decision_by_id( +… “34044333-4b40-515f-a684-2515325c57c5”, +… include_documents=True +… ) +>>> if decision.document_bag: +… doc = decision.document_bag[0] +… if doc.download_option_bag: +… # Download PDF version +… pdf_option = next( +… opt for opt in doc.download_option_bag +… if opt.mime_type_identifier == “PDF” +… ) +… path = client.download_petition_document( +… pdf_option, +… destination_path=”./downloads” +… ) +… print(f”Downloaded to: {path}”)

    +
    -
    -search_patents(query=None, application_number=None, patent_number=None, inventor_name=None, applicant_name=None, assignee_name=None, filing_date_from=None, filing_date_to=None, grant_date_from=None, grant_date_to=None, classification=None, limit=25, offset=0)[source]
    -

    Search for patents with various filters.

    +
    +get_decision_by_id(petition_decision_record_identifier, include_documents=None)[source]
    +

    Retrieves a specific petition decision by its record identifier.

    Parameters:
      -
    • query (Optional[str]) – Search text in all fields

    • -
    • application_number (Optional[str]) – Filter by application number

    • -
    • patent_number (Optional[str]) – Filter by patent number

    • -
    • inventor_name (Optional[str]) – Filter by inventor name

    • -
    • applicant_name (Optional[str]) – Filter by applicant name

    • -
    • assignee_name (Optional[str]) – Filter by assignee name

    • -
    • filing_date_from (Optional[str]) – Filter by filing date from (YYYY-MM-DD)

    • -
    • filing_date_to (Optional[str]) – Filter by filing date to (YYYY-MM-DD)

    • -
    • grant_date_from (Optional[str]) – Filter by grant date from (YYYY-MM-DD)

    • -
    • grant_date_to (Optional[str]) – Filter by grant date to (YYYY-MM-DD)

    • -
    • classification (Optional[str]) – Filter by CPC classification

    • -
    • limit (Optional[int]) – Number of results to return (default 25)

    • -
    • offset (Optional[int]) – Position in dataset to start from (default 0)

    • +
    • petition_decision_record_identifier (str) – The unique identifier for the petition +decision record (UUID format).

    • +
    • include_documents (bool | None) – Whether to include associated documents in the response. +If True, adds includeDocuments=true query parameter.

    -
    Return type:
    -

    PatentDataResponse

    +
    Returns:
    +

    The petition decision if found, None otherwise.

    -
    Returns:
    -

    PatentDataResponse object containing matching patents

    +
    Return type:
    +

    Optional[PetitionDecision]

    +
    +
    +
    +

    Examples

    +

    # Get decision without documents +>>> decision = client.get_decision_by_id( +… “9f1a4a2b-eee1-58ec-a3aa-167c4075aed4” +… )

    +

    # Get decision with documents +>>> decision = client.get_decision_by_id( +… “34044333-4b40-515f-a684-2515325c57c5”, +… include_documents=True +… )

    +
    +
    + +
    +
    +paginate_decisions(**kwargs)[source]
    +

    Provides an iterator to paginate through petition decision search results.

    +

    This method simplifies fetching all petition decisions matching a search query +by automatically handling pagination. It internally calls the search_decisions +method for GET requests, batching results and yielding them one by one.

    +

    All keyword arguments are passed directly to search_decisions to define the +search criteria. The offset and limit parameters are managed by the pagination +logic; setting them directly in kwargs might lead to unexpected behavior.

    +
    +
    Parameters:
    +

    **kwargs (Any) – Keyword arguments passed to search_decisions for constructing +the search query. Do not include post_body.

    +
    +
    Returns:
    +

    +
    An iterator yielding PetitionDecision objects,

    allowing iteration over all matching petition decisions across multiple +pages of results.

    +
    +
    +

    +
    +
    Return type:
    +

    Iterator[PetitionDecision]

    +
    +
    Raises:
    +

    ValueError – If post_body is included in kwargs, as this method only + supports GET request parameters for pagination.

    +
    +
    +
    +

    Examples

    +

    # Paginate through all decisions for a technology center +>>> for decision in client.paginate_decisions(technology_center_q=”1700”): +… print(f”{decision.application_number_text}: {decision.decision_type_code}”)

    +

    # Paginate with date range +>>> for decision in client.paginate_decisions( +… decision_date_from_q=”2023-01-01”, +… decision_date_to_q=”2023-12-31” +… ): +… process_decision(decision)

    +
    +
    + +
    +
    +search_decisions(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, application_number_q=None, patent_number_q=None, inventor_name_q=None, applicant_name_q=None, invention_title_q=None, decision_type_code_q=None, decision_date_from_q=None, decision_date_to_q=None, petition_mail_date_from_q=None, petition_mail_date_to_q=None, technology_center_q=None, final_deciding_office_name_q=None, additional_query_params=None)[source]
    +

    Searches for final petition decisions.

    +

    This method can perform either a GET request using query parameters or a POST +request if post_body is specified. When using GET, you can provide either a +direct query string or use convenience parameters that will be automatically +combined into a query.

    +
    +
    Parameters:
    +
      +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • facets (str | None) – Facet configuration string.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • post_body (Dict[str, Any] | None) – Optional POST body for complex queries.

    • +
    • application_number_q (str | None) – Filter by application number.

    • +
    • patent_number_q (str | None) – Filter by patent number.

    • +
    • inventor_name_q (str | None) – Filter by inventor name.

    • +
    • applicant_name_q (str | None) – Filter by applicant name.

    • +
    • invention_title_q (str | None) – Filter by invention title.

    • +
    • decision_type_code_q (str | None) – Filter by decision type code.

    • +
    • decision_date_from_q (str | None) – Filter decisions from this date (YYYY-MM-DD).

    • +
    • decision_date_to_q (str | None) – Filter decisions to this date (YYYY-MM-DD).

    • +
    • petition_mail_date_from_q (str | None) – Filter petition mail dates from (YYYY-MM-DD).

    • +
    • petition_mail_date_to_q (str | None) – Filter petition mail dates to (YYYY-MM-DD).

    • +
    • technology_center_q (str | None) – Filter by technology center.

    • +
    • final_deciding_office_name_q (str | None) – Filter by deciding office name.

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    +
    +
    Returns:
    +

    Response containing matching petition decisions.

    +
    +
    Return type:
    +

    PetitionDecisionResponse

    +
    +
    +
    +

    Examples

    +

    # Search with direct query +>>> response = client.search_decisions(query=”applicationNumberText:17765301”)

    +

    # Search with convenience parameters +>>> response = client.search_decisions( +… applicant_name_q=”ACME Corp”, +… decision_date_from_q=”2022-01-01”, +… limit=50 +… )

    +

    # Search with POST body +>>> response = client.search_decisions( +… post_body={“q”: “technologyCenter:1700”, “limit”: 100} +… )

    +
    +
    + +
    + +

    clients.ptab_appeals - Client for USPTO PTAB Appeals API

    +

    This module provides a client for interacting with the USPTO PTAB (Patent Trial +and Appeal Board) Appeals API. It allows you to search for ex parte appeal decisions.

    +
    +
    +class pyUSPTO.clients.ptab_appeals.PTABAppealsClient(api_key=None, base_url=None, config=None)[source]
    +

    Bases: BaseUSPTOClient[PTABAppealResponse]

    +

    Client for interacting with the USPTO PTAB Appeals API.

    +

    This client provides methods to search for ex parte appeal decisions from the +Patent Trial and Appeal Board.

    +

    Appeals data includes decisions on patent application appeals from the examiner +to the PTAB.

    +
    +
    +ENDPOINTS = {'search_decisions': 'api/v1/patent/appeals/decisions/search'}
    +
    + +
    +
    +__init__(api_key=None, base_url=None, config=None)[source]
    +

    Initialize the PTABAppealsClient.

    +
    +
    Parameters:
    +
      +
    • api_key (str | None) – Optional API key for authentication.

    • +
    • base_url (str | None) – Optional base URL override for the API.

    • +
    • config (USPTOConfig | None) – Optional USPTOConfig instance for configuration.

    • +
    +
    +
    +
    + +
    +
    +paginate_decisions(**kwargs)[source]
    +

    Provides an iterator to paginate through appeal decision search results.

    +

    This method simplifies fetching all appeal decisions matching a search query +by automatically handling pagination. It internally calls the search_decisions +method for GET requests, batching results and yielding them one by one.

    +

    All keyword arguments are passed directly to search_decisions to define the +search criteria. The offset and limit parameters are managed by the pagination +logic; setting them directly in kwargs might lead to unexpected behavior.

    +
    +
    Parameters:
    +

    **kwargs (Any) – Keyword arguments passed to search_decisions for constructing +the search query. Do not include post_body.

    +
    +
    Returns:
    +

    +
    An iterator yielding PTABAppealDecision objects,

    allowing iteration over all matching decisions across multiple pages of results.

    +
    +
    +

    +
    +
    Return type:
    +

    Iterator[PTABAppealDecision]

    +
    +
    Raises:
    +

    ValueError – If post_body is included in kwargs, as this method only + supports GET request parameters for pagination.

    +
    +
    +
    +

    Examples

    +

    # Paginate through all decisions for a technology center +>>> for decision in client.paginate_decisions(technology_center_number_q=”3600”): +… print(f”{decision.appeal_meta_data.appeal_number}: ” +… f”{decision.decision_data.decision_type_category}”)

    +

    # Paginate with date range +>>> for decision in client.paginate_decisions( +… decision_date_from_q=”2023-01-01”, +… decision_date_to_q=”2023-12-31” +… ): +… process_decision(decision)

    +
    +
    + +
    +
    +search_decisions(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, appeal_number_q=None, application_number_text_q=None, appellant_name_q=None, requestor_name_q=None, decision_type_category_q=None, decision_date_from_q=None, decision_date_to_q=None, technology_center_number_q=None, additional_query_params=None)[source]
    +

    Searches for PTAB appeal decisions.

    +

    This method can perform either a GET request using query parameters or a POST +request if post_body is specified. When using GET, you can provide either a +direct query string or use convenience parameters that will be automatically +combined into a query.

    +
    +
    Parameters:
    +
      +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • facets (str | None) – Facet configuration string.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • post_body (Dict[str, Any] | None) – Optional POST body for complex queries.

    • +
    • appeal_number_q (str | None) – Filter by appeal number.

    • +
    • application_number_text_q (str | None) – Filter by application number.

    • +
    • appellant_name_q (str | None) – Filter by appellant name.

    • +
    • requestor_name_q (str | None) – Filter by requestor name.

    • +
    • decision_type_category_q (str | None) – Filter by decision type category.

    • +
    • decision_date_from_q (str | None) – Filter decisions from this date (YYYY-MM-DD).

    • +
    • decision_date_to_q (str | None) – Filter decisions to this date (YYYY-MM-DD).

    • +
    • technology_center_number_q (str | None) – Filter by technology center number.

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    +
    +
    Returns:
    +

    Response containing matching appeal decisions.

    +
    +
    Return type:
    +

    PTABAppealResponse

    +
    +

    Examples

    +

    # Search with direct query +>>> response = client.search_decisions(query=”appealNumber:2023-001234”)

    +

    # Search with convenience parameters +>>> response = client.search_decisions( +… technology_center_number_q=”3600”, +… decision_date_from_q=”2023-01-01”, +… limit=50 +… )

    +

    # Search with POST body +>>> response = client.search_decisions( +… post_body={“q”: “decisionTypeCategory:Affirmed”, “limit”: 100} +… )

    +
    +
    + +
    + +

    clients.ptab_interferences - Client for USPTO PTAB Interferences API

    +

    This module provides a client for interacting with the USPTO PTAB (Patent Trial +and Appeal Board) Interferences API. It allows you to search for patent interference decisions.

    +
    +
    +class pyUSPTO.clients.ptab_interferences.PTABInterferencesClient(api_key=None, base_url=None, config=None)[source]
    +

    Bases: BaseUSPTOClient[PTABInterferenceResponse]

    +

    Client for interacting with the USPTO PTAB Interferences API.

    +

    This client provides methods to search for patent interference decisions from the +Patent Trial and Appeal Board.

    +

    Interference proceedings are used to determine priority of invention when two or +more parties claim the same patentable invention.

    +
    +
    +ENDPOINTS = {'search_decisions': 'api/v1/patent/interferences/decisions/search'}
    +
    + +
    +
    +__init__(api_key=None, base_url=None, config=None)[source]
    +

    Initialize the PTABInterferencesClient.

    +
    +
    Parameters:
    +
      +
    • api_key (str | None) – Optional API key for authentication.

    • +
    • base_url (str | None) – Optional base URL override for the API.

    • +
    • config (USPTOConfig | None) – Optional USPTOConfig instance for configuration.

    • +
    +
    +
    +
    + +
    +
    +paginate_decisions(**kwargs)[source]
    +

    Provides an iterator to paginate through interference decision search results.

    +

    This method simplifies fetching all interference decisions matching a search query +by automatically handling pagination. It internally calls the search_decisions +method for GET requests, batching results and yielding them one by one.

    +

    All keyword arguments are passed directly to search_decisions to define the +search criteria. The offset and limit parameters are managed by the pagination +logic; setting them directly in kwargs might lead to unexpected behavior.

    +
    +
    Parameters:
    +

    **kwargs (Any) – Keyword arguments passed to search_decisions for constructing +the search query. Do not include post_body.

    +
    +
    Returns:
    +

    +
    An iterator yielding PTABInterferenceDecision

    objects, allowing iteration over all matching decisions across multiple pages +of results.

    +
    +
    +

    +
    +
    Return type:
    +

    Iterator[PTABInterferenceDecision]

    +
    +
    Raises:
    +

    ValueError – If post_body is included in kwargs, as this method only + supports GET request parameters for pagination.

    +
    +
    +
    +

    Examples

    +

    # Paginate through all interference decisions +>>> for decision in client.paginate_decisions(): +… print(f”{decision.interference_meta_data.interference_number}: ” +… f”{decision.document_data.interference_outcome_category}”)

    +

    # Paginate with date range +>>> for decision in client.paginate_decisions( +… decision_date_from_q=”2020-01-01”, +… decision_date_to_q=”2023-12-31” +… ): +… process_decision(decision)

    +
    +
    + +
    +
    +search_decisions(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, interference_number_q=None, senior_party_application_number_q=None, junior_party_application_number_q=None, senior_party_name_q=None, junior_party_name_q=None, real_party_in_interest_q=None, interference_outcome_category_q=None, decision_type_category_q=None, decision_date_from_q=None, decision_date_to_q=None, additional_query_params=None)[source]
    +

    Searches for PTAB interference decisions.

    +

    This method can perform either a GET request using query parameters or a POST +request if post_body is specified. When using GET, you can provide either a +direct query string or use convenience parameters that will be automatically +combined into a query.

    +
    +
    Parameters:
    +
      +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • facets (str | None) – Facet configuration string.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • post_body (Dict[str, Any] | None) – Optional POST body for complex queries.

    • +
    • interference_number_q (str | None) – Filter by interference number.

    • +
    • senior_party_application_number_q (str | None) – Filter by senior party application number.

    • +
    • junior_party_application_number_q (str | None) – Filter by junior party application number.

    • +
    • senior_party_name_q (str | None) – Filter by senior party name.

    • +
    • junior_party_name_q (str | None) – Filter by junior party name.

    • +
    • real_party_in_interest_q (str | None) – Filter by Real Party in Interest.

    • +
    • interference_outcome_category_q (str | None) – Filter by interference outcome category.

    • +
    • decision_type_category_q (str | None) – Filter by decision type category.

    • +
    • decision_date_from_q (str | None) – Filter decisions from this date (YYYY-MM-DD).

    • +
    • decision_date_to_q (str | None) – Filter decisions to this date (YYYY-MM-DD).

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    +
    +
    Returns:
    +

    Response containing matching interference decisions.

    +
    +
    Return type:
    +

    PTABInterferenceResponse

    +
    +
    +
    +

    Examples

    +

    # Search with direct query +>>> response = client.search_decisions(query=”interferenceNumber:106123”)

    +

    # Search with convenience parameters +>>> response = client.search_decisions( +… interference_outcome_category_q=”Priority to Senior Party”, +… decision_date_from_q=”2020-01-01”, +… limit=50 +… )

    +

    # Search with POST body +>>> response = client.search_decisions( +… post_body={“q”: “decisionTypeCategory:Final Decision”, “limit”: 100} +… )

    +
    +
    + +
    + +

    clients.ptab_trials - Client for USPTO PTAB Trials API

    +

    This module provides a client for interacting with the USPTO PTAB (Patent Trial +and Appeal Board) Trials API. It allows you to search for trial proceedings, +documents, and decisions.

    +
    +
    +class pyUSPTO.clients.ptab_trials.PTABTrialsClient(api_key=None, base_url=None, config=None)[source]
    +

    Bases: BaseUSPTOClient[PTABTrialProceedingResponse | PTABTrialDocumentResponse]

    +

    Client for interacting with the USPTO PTAB Trials API.

    +

    This client provides methods to search for trial proceedings, trial documents, +and trial decisions from the Patent Trial and Appeal Board.

    +

    Trial proceedings data includes IPR (Inter Partes Review), PGR (Post-Grant Review), +CBM (Covered Business Method), and DER (Derivation) proceedings.

    +
    +
    +ENDPOINTS = {'search_decisions': 'api/v1/patent/trials/decisions/search', 'search_documents': 'api/v1/patent/trials/documents/search', 'search_proceedings': 'api/v1/patent/trials/proceedings/search'}
    +
    + +
    +
    +__init__(api_key=None, base_url=None, config=None)[source]
    +

    Initialize the PTABTrialsClient.

    +
    +
    Parameters:
    +
      +
    • api_key (str | None) – Optional API key for authentication.

    • +
    • base_url (str | None) – Optional base URL override for the API.

    • +
    • config (USPTOConfig | None) – Optional USPTOConfig instance for configuration.

    • +
    +
    +
    +
    + +
    +
    +paginate_proceedings(**kwargs)[source]
    +

    Provides an iterator to paginate through trial proceeding search results.

    +
    +
    Return type:
    +

    Iterator[PTABTrialProceeding]

    +
    +
    +
    + +
    +
    +search_decisions(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, trial_number_q=None, decision_type_category_q=None, document_type_description_q=None, decision_date_from_q=None, decision_date_to_q=None, trial_type_code_q=None, patent_number_q=None, application_number_q=None, patent_owner_name_q=None, trial_status_category_q=None, real_party_in_interest_name_q=None, document_category_q=None, additional_query_params=None)[source]
    +

    Searches for PTAB trial decisions.

    +

    This method can perform either a GET request using query parameters or a POST +request if post_body is specified. When using GET, you can provide either a +direct query string or use convenience parameters that will be automatically +combined into a query.

    +
    +
    Parameters:
    +
      +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • facets (str | None) – Facet configuration string.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • post_body (Dict[str, Any] | None) – Optional POST body for complex queries.

    • +
    • trial_number_q (str | None) – Filter by trial number.

    • +
    • decision_type_category_q (str | None) – Filter by decision type category.

    • +
    • document_type_description_q (str | None) – Filter by “[description]”.

    • +
    • decision_date_from_q (str | None) – Filter decisions from this date (YYYY-MM-DD).

    • +
    • decision_date_to_q (str | None) – Filter decisions to this date (YYYY-MM-DD).

    • +
    • trial_type_code_q (str | None) – Filter by trial type code (e.g., “IPR”, “PGR”, “CBM”, “DER”).

    • +
    • patent_number_q (str | None) – Filter by patent number.

    • +
    • application_number_q (str | None) – Filter by application number.

    • +
    • patent_owner_name_q (str | None) – Filter by patent owner name.

    • +
    • trial_status_category_q (str | None) – Filter by trial status category.

    • +
    • real_party_in_interest_name_q (str | None) – Filter by real party in interest name.

    • +
    • document_category_q (str | None) – Filter by document category.

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    +
    +
    Returns:
    +

    Response containing matching trial decisions.

    +
    +
    Return type:
    +

    PTABTrialDocumentResponse

    +
    +
    +
    +

    Examples

    +

    # Search with direct query +>>> response = client.search_decisions(query=”trialNumber:IPR2023-00001”)

    +

    # Search with convenience parameters +>>> response = client.search_decisions( +… decision_type_category_q=”Final Written Decision”, +… decision_date_from_q=”2023-01-01”, +… limit=50 +… )

    +
    +
    + +
    +
    +search_documents(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, trial_number_q=None, document_category_q=None, document_type_name_q=None, filing_date_from_q=None, filing_date_to_q=None, petitioner_real_party_in_interest_name_q=None, inventor_name_q=None, real_party_in_interest_name_q=None, patent_number_q=None, patent_owner_name_q=None, additional_query_params=None)[source]
    +

    Searches for PTAB trial documents.

    +

    This method can perform either a GET request using query parameters or a POST +request if post_body is specified. When using GET, you can provide either a +direct query string or use convenience parameters that will be automatically +combined into a query.

    +
    +
    Parameters:
    +
      +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • facets (str | None) – Facet configuration string.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • post_body (Dict[str, Any] | None) – Optional POST body for complex queries.

    • +
    • trial_number_q (str | None) – Filter by trial number.

    • +
    • document_category_q (str | None) – Filter by document category (e.g., “Petition”) DOCUMENTED BUT NOT IN API.

    • +
    • document_type_name_q (str | None) – Filter by document type name (description).

    • +
    • filing_date_from_q (str | None) – Filter documents from this date (YYYY-MM-DD).

    • +
    • filing_date_to_q (str | None) – Filter documents to this date (YYYY-MM-DD).

    • +
    • petitioner_real_party_in_interest_name_q (str | None) – Filter by petitioner real party in interest.

    • +
    • inventor_name_q (str | None) – Filter by inventor name.

    • +
    • real_party_in_interest_name_q (str | None) – Filter by real party in interest (generic).

    • +
    • patent_number_q (str | None) – Filter by patent number.

    • +
    • patent_owner_name_q (str | None) – Filter by patent owner name.

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    +
    +
    Returns:
    +

    Response containing matching trial documents.

    +
    +
    Return type:
    +

    PTABTrialDocumentResponse

    +
    +
    +
    +

    Examples

    +

    # Search with direct query +>>> response = client.search_documents(query=”trialNumber:IPR2023-00001”)

    +

    # Search with convenience parameters +>>> response = client.search_documents( +… document_category_q=”Paper”, +… filing_date_from_q=”2023-01-01”, +… limit=50 +… )

    +
    +
    + +
    +
    +search_proceedings(query=None, sort=None, offset=0, limit=25, facets=None, fields=None, filters=None, range_filters=None, post_body=None, trial_number_q=None, patent_owner_name_q=None, petitioner_real_party_in_interest_name_q=None, respondent_name_q=None, trial_type_code_q=None, trial_status_category_q=None, petition_filing_date_from_q=None, petition_filing_date_to_q=None, additional_query_params=None)[source]
    +

    Searches for PTAB trial proceedings.

    +

    This method can perform either a GET request using query parameters or a POST +request if post_body is specified. When using GET, you can provide either a +direct query string or use convenience parameters that will be automatically +combined into a query.

    +
    +
    Parameters:
    +
      +
    • query (str | None) – Direct query string in USPTO search syntax.

    • +
    • sort (str | None) – Sort order for results.

    • +
    • offset (int | None) – Number of records to skip (pagination).

    • +
    • limit (int | None) – Maximum number of records to return.

    • +
    • facets (str | None) – Facet configuration string.

    • +
    • fields (str | None) – Specific fields to return.

    • +
    • filters (str | None) – Filter configuration string.

    • +
    • range_filters (str | None) – Range filter configuration string.

    • +
    • post_body (Dict[str, Any] | None) – Optional POST body for complex queries.

    • +
    • trial_number_q (str | None) – Filter by trial number (e.g., “IPR2023-00001”).

    • +
    • patent_owner_name_q (str | None) – Filter by patent owner name.

    • +
    • petitioner_real_party_in_interest_name_q (str | None) – Filter by petitioner real party in interest.

    • +
    • respondent_name_q (str | None) – Filter by respondent name.

    • +
    • trial_type_code_q (str | None) – Filter by trial type code (e.g., “IPR”, “PGR”, “CBM”, “DER”).

    • +
    • trial_status_category_q (str | None) – Filter by trial status category.

    • +
    • petition_filing_date_from_q (str | None) – Filter proceedings from this date (YYYY-MM-DD).

    • +
    • petition_filing_date_to_q (str | None) – Filter proceedings to this date (YYYY-MM-DD).

    • +
    • additional_query_params (Dict[str, Any] | None) – Additional custom query parameters.

    • +
    +
    +
    Returns:
    +

    Response containing matching trial proceedings.

    +
    +
    Return type:
    +

    PTABTrialProceedingResponse

    +
    +
    +
    +

    Examples

    +

    # Search with direct query +>>> response = client.search_proceedings(query=”trialNumber:IPR2023-00001”)

    +

    # Search with convenience parameters +>>> response = client.search_proceedings( +… trial_type_code_q=”IPR”, +… petition_filing_date_from_q=”2023-01-01”, +… limit=50 +… )

    +
    @@ -671,7 +1740,7 @@
    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/api/config.html b/docs/build/html/api/config.html index 75ef64a..2a7226f 100644 --- a/docs/build/html/api/config.html +++ b/docs/build/html/api/config.html @@ -6,7 +6,7 @@ - Configuration — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Configuration — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -58,9 +58,31 @@
  • USPTOConfig.from_env()
  • +
  • HTTPConfig +
  • Exceptions
  • +
  • Warnings
  • Examples
  • @@ -92,25 +114,32 @@
    -
    -

    Configuration

    -

    config - Configuration management for USPTO API clients

    -

    This module provides configuration management for USPTO API clients.

    +
    +

    Configuration

    +

    config - Configuration management for USPTO API clients

    +

    This module provides configuration management for USPTO API clients, +including API keys, base URLs, and HTTP transport settings.

    -class pyUSPTO.config.USPTOConfig(api_key=None, bulk_data_base_url='https://api.uspto.gov/api/v1/datasets', patent_data_base_url='https://api.uspto.gov/api/v1/patent')[source]
    -

    Bases: object

    +class pyUSPTO.config.USPTOConfig(api_key=None, bulk_data_base_url='https://api.uspto.gov', patent_data_base_url='https://api.uspto.gov', petition_decisions_base_url='https://api.uspto.gov', ptab_base_url='https://api.uspto.gov', http_config=None, include_raw_data=False)[source] +

    Bases: object

    Configuration for USPTO API clients.

    +

    Manages API-level configuration (keys, URLs) and optionally +accepts HTTP transport configuration via HTTPConfig.

    -__init__(api_key=None, bulk_data_base_url='https://api.uspto.gov/api/v1/datasets', patent_data_base_url='https://api.uspto.gov/api/v1/patent')[source]
    +__init__(api_key=None, bulk_data_base_url='https://api.uspto.gov', patent_data_base_url='https://api.uspto.gov', petition_decisions_base_url='https://api.uspto.gov', ptab_base_url='https://api.uspto.gov', http_config=None, include_raw_data=False)[source]

    Initialize the USPTOConfig.

    Parameters:
      -
    • api_key (Optional[str]) – API key for authentication, defaults to USPTO_API_KEY environment variable

    • -
    • bulk_data_base_url (str) – Base URL for the Bulk Data API

    • -
    • patent_data_base_url (str) – Base URL for the Patent Data API

    • +
    • api_key (str | None) – API key for authentication, defaults to USPTO_API_KEY environment variable

    • +
    • bulk_data_base_url (str) – Base URL for the Bulk Data API

    • +
    • patent_data_base_url (str) – Base URL for the Patent Data API

    • +
    • petition_decisions_base_url (str) – Base URL for the Final Petition Decisions API

    • +
    • ptab_base_url (str) – Base URL for the PTAB (Patent Trial and Appeal Board) API

    • +
    • http_config (HTTPConfig | None) – Optional HTTPConfig for request handling (uses defaults if None)

    • +
    • include_raw_data (bool) – If True, store raw JSON in response objects for debugging (default: False)

    @@ -125,13 +154,150 @@

    Configuration

    USPTOConfig

    Returns:
    -

    USPTOConfig instance

    +

    USPTOConfig instance with values from environment

    +

    http_config - HTTP client configuration for USPTO API requests

    +

    This module provides configuration for HTTP transport-level settings including +timeouts, retries, connection pooling, and custom headers.

    +
    +
    +class pyUSPTO.http_config.HTTPConfig(timeout=30.0, connect_timeout=10.0, max_retries=3, backoff_factor=1.0, retry_status_codes=<factory>, pool_connections=10, pool_maxsize=10, custom_headers=None)[source]
    +

    Bases: object

    +

    HTTP client configuration for request handling.

    +

    This class separates transport-level HTTP concerns from API-level +configuration, allowing fine-grained control over request behavior.

    +
    +
    +timeout
    +

    Read timeout in seconds for requests (default: 30.0)

    +
    + +
    +
    +connect_timeout
    +

    Connection establishment timeout in seconds (default: 10.0)

    +
    + +
    +
    +max_retries
    +

    Maximum number of retry attempts (default: 3)

    +
    + +
    +
    +backoff_factor
    +

    Exponential backoff multiplier for retries (default: 1.0)

    +
    + +
    +
    +retry_status_codes
    +

    HTTP status codes that trigger retries

    +
    + +
    +
    +pool_connections
    +

    Number of connection pools to cache (default: 10)

    +
    + +
    +
    +pool_maxsize
    +

    Maximum number of connections per pool (default: 10)

    +
    + +
    +
    +custom_headers
    +

    Additional headers to include in all requests

    +
    + +
    +
    +backoff_factor: float = 1.0
    +
    + +
    +
    +connect_timeout: float | None = 10.0
    +
    + +
    +
    +custom_headers: Dict[str, str] | None = None
    +
    + +
    +
    +classmethod from_env()[source]
    +

    Create HTTPConfig from environment variables.

    +
    +
    Environment variables:

    USPTO_REQUEST_TIMEOUT: Request timeout in seconds +USPTO_CONNECT_TIMEOUT: Connection timeout in seconds +USPTO_MAX_RETRIES: Maximum retry attempts +USPTO_BACKOFF_FACTOR: Retry backoff factor +USPTO_POOL_CONNECTIONS: Connection pool size +USPTO_POOL_MAXSIZE: Max connections per pool

    +
    +
    +
    +
    Return type:
    +

    HTTPConfig

    +
    +
    Returns:
    +

    HTTPConfig instance with values from environment or defaults

    +
    +
    +
    + +
    +
    +get_timeout_tuple()[source]
    +

    Get timeout as tuple for requests library.

    +
    +
    Return type:
    +

    tuple[float | None, float | None]

    +
    +
    Returns:
    +

    Tuple of (connect_timeout, read_timeout) for requests

    +
    +
    +
    + +
    +
    +max_retries: int = 3
    +
    + +
    +
    +pool_connections: int = 10
    +
    + +
    +
    +pool_maxsize: int = 10
    +
    + +
    +
    +retry_status_codes: List[int]
    +
    + +
    +
    +timeout: float | None = 30.0
    +
    + +
    +
    @@ -145,7 +311,7 @@

    Configuration -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/api/exceptions.html b/docs/build/html/api/exceptions.html index 404f47e..1c68532 100644 --- a/docs/build/html/api/exceptions.html +++ b/docs/build/html/api/exceptions.html @@ -6,7 +6,7 @@ - Exceptions — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Exceptions — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -54,12 +54,35 @@
  • Models
  • Configuration
  • Exceptions
  • +
  • Warnings
  • Examples
  • @@ -94,33 +117,196 @@

    Exceptions

    exceptions - Exception classes for USPTO API clients

    -

    This module provides exception classes for USPTO API clients.

    +

    This module provides exception classes for USPTO API errors that correspond to +the various response types from the USPTO API. It also includes helper +structures and functions for creating these exceptions.

    +
    +
    +class pyUSPTO.exceptions.APIErrorArgs(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]
    +

    Bases: object

    +

    Data structure to hold arguments for API exception constructors.

    +
    +
    +api_short_error: str | None = None
    +
    + +
    +
    +error_details: str | dict | None = None
    +
    + +
    +
    +classmethod from_http_error(http_error, client_operation_message)[source]
    +

    Creates an APIErrorArgs instance by parsing a requests.exceptions.HTTPError.

    +
    +
    Parameters:
    +
      +
    • http_error (HTTPError) – The HTTPError object from the requests library.

    • +
    • client_operation_message (str) – A message describing the client operation that failed.

    • +
    +
    +
    Return type:
    +

    APIErrorArgs

    +
    +
    Returns:
    +

    An instance of APIErrorArgs populated with details from the HTTPError.

    +
    +
    +
    + +
    +
    +classmethod from_request_exception(request_exception, client_operation_message=None)[source]
    +

    Creates an APIErrorArgs instance from a generic requests.exceptions.RequestException +(e.g., ConnectionError, Timeout) that is not an HTTPError.

    +
    +
    Return type:
    +

    APIErrorArgs

    +
    +
    +
    + +
    +
    +message: str
    +
    + +
    +
    +request_identifier: str | None = None
    +
    + +
    +
    +status_code: int | None = None
    +
    + +
    +
    -exception pyUSPTO.exceptions.USPTOApiAuthError(message, status_code=None)[source]
    +exception pyUSPTO.exceptions.USPTOApiAuthError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]

    Bases: USPTOApiError

    -

    Authentication error.

    +

    Authentication/Authorization error (HTTP 401/403).

    +
    + +
    +
    +exception pyUSPTO.exceptions.USPTOApiBadRequestError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]
    +

    Bases: USPTOApiError

    +

    Bad Request error (HTTP 400).

    -exception pyUSPTO.exceptions.USPTOApiError(message, status_code=None)[source]
    -

    Bases: Exception

    -

    Base exception for USPTO API errors.

    +exception pyUSPTO.exceptions.USPTOApiError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source] +

    Bases: Exception

    +

    Base exception for USPTO API errors. +This is the parent class for all USPTO API-specific exceptions. It includes +information about the status code, API’s short error message, detailed error +information, and request identifier from the API response.

    +
    +
    +DEFAULT_UNKNOWN_MESSAGE = 'UNK USPTO API ERROR'
    +
    + +
    +
    +__init__(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]
    +

    Initializes the USPTOApiError. +:type message: str +:param message: The primary message for the exception (often client-generated context). +:type status_code: int | None +:param status_code: The HTTP status code from the API response (e.g., 400, 403). +:type api_short_error: str | None +:param api_short_error: The short error description from the API (e.g., “Bad Request”, “Forbidden”). +:type error_details: str | dict | None +:param error_details: The detailed error message or structure from the API. +:type request_identifier: str | None +:param request_identifier: The request identifier from the API response, if available.

    +
    + +
    +
    +__str__()[source]
    +

    Provides a more informative string representation of the error.

    +
    +
    Return type:
    +

    str

    +
    +
    +
    + +
    +
    +property message: str
    +

    Provides direct access to the primary exception message. +This refers to the first argument passed to the exception, +which is conventionally the main human-readable message.

    +
    +
    -exception pyUSPTO.exceptions.USPTOApiNotFoundError(message, status_code=None)[source]
    +exception pyUSPTO.exceptions.USPTOApiNotFoundError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]

    Bases: USPTOApiError

    -

    Resource not found.

    +

    Resource not found error (HTTP 404).

    +
    + +
    +
    +exception pyUSPTO.exceptions.USPTOApiPayloadTooLargeError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]
    +

    Bases: USPTOApiError

    +

    Payload Too Large error (HTTP 413).

    -exception pyUSPTO.exceptions.USPTOApiRateLimitError(message, status_code=None)[source]
    +exception pyUSPTO.exceptions.USPTOApiRateLimitError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source] +

    Bases: USPTOApiError

    +

    Rate limit exceeded error (HTTP 429).

    +
    + +
    +
    +exception pyUSPTO.exceptions.USPTOApiServerError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]
    +

    Bases: USPTOApiError

    +

    Internal Server Error (HTTP 500 series).

    +
    + +
    +
    +exception pyUSPTO.exceptions.USPTOConnectionError(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]

    Bases: USPTOApiError

    -

    Rate limit exceeded.

    +

    Network-level connection error (DNS failure, refused connection, etc.).

    +
    + +
    +
    +exception pyUSPTO.exceptions.USPTOTimeout(message, status_code=None, api_short_error=None, error_details=None, request_identifier=None)[source]
    +

    Bases: USPTOApiError

    +

    Request to USPTO API timed out.

    +
    + +
    +
    +pyUSPTO.exceptions.get_api_exception(error_args)[source]
    +

    Determines and instantiates the appropriate USPTOApiError subclass +based on the status code in error_args.

    +
    +
    Parameters:
    +

    error_args (APIErrorArgs) – An instance of APIErrorArgs containing all necessary +information to construct the exception.

    +
    +
    Return type:
    +

    USPTOApiError

    +
    +
    Returns:
    +

    An instance of a USPTOApiError subclass.

    +
    +
    @@ -130,13 +316,13 @@
    Built with Sphinx using a diff --git a/docs/build/html/api/models.html b/docs/build/html/api/models.html index 19399b3..38c7cca 100644 --- a/docs/build/html/api/models.html +++ b/docs/build/html/api/models.html @@ -6,7 +6,7 @@ - Models — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Models — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -72,9 +72,13 @@
  • BulkDataResponse
  • @@ -97,202 +101,417 @@
  • ProductFileBag.from_dict()
  • +
  • ActiveIndicator +
  • Address
  • Applicant +
  • +
  • ApplicationContinuityData
  • ApplicationMetaData
  • Assignee
  • Assignment
  • Assignor
  • Attorney
  • ChildContinuity
  • Continuity
  • CustomerNumberCorrespondence +
  • +
  • DirectionCategory +
  • +
  • Document +
  • +
  • DocumentBag
  • -
  • DocumentMetaData
  • Examples
  • @@ -388,11 +1043,11 @@
    class pyUSPTO.models.bulk_data.BulkDataProduct(product_identifier, product_description_text, product_title_text, product_frequency_text, product_label_array_text, product_dataset_array_text, product_dataset_category_array_text, product_from_date, product_to_date, product_total_file_size, product_file_total_quantity, last_modified_date_time, mime_type_identifier_array_text, product_file_bag=None, days_of_week_text=None)[source]
    -

    Bases: object

    +

    Bases: object

    Represents a product in the bulk data API.

    -days_of_week_text: Optional[str] = None
    +days_of_week_text: str | None = None
    @@ -408,109 +1063,141 @@
    -last_modified_date_time: str
    +last_modified_date_time: str
    -mime_type_identifier_array_text: List[str]
    +mime_type_identifier_array_text: List[str]
    -product_dataset_array_text: List[str]
    +product_dataset_array_text: List[str]
    -product_dataset_category_array_text: List[str]
    +product_dataset_category_array_text: List[str]
    -product_description_text: str
    +product_description_text: str
    -product_file_bag: Optional[ProductFileBag] = None
    +product_file_bag: ProductFileBag | None = None
    -product_file_total_quantity: int
    +product_file_total_quantity: int
    -product_frequency_text: str
    +product_frequency_text: str
    -product_from_date: str
    +product_from_date: str
    -product_identifier: str
    +product_identifier: str
    -product_label_array_text: List[str]
    +product_label_array_text: List[str]
    -product_title_text: str
    +product_title_text: str
    -product_to_date: str
    +product_to_date: str
    -product_total_file_size: int
    +product_total_file_size: int
    -class pyUSPTO.models.bulk_data.BulkDataResponse(count, bulk_data_product_bag)[source]
    -

    Bases: object

    +class pyUSPTO.models.bulk_data.BulkDataResponse(count, bulk_data_product_bag, raw_data=None)[source] +

    Bases: object

    Top-level response from the bulk data API.

    +
    +
    +count
    +

    The number of bulk data products in the response.

    +
    +
    -bulk_data_product_bag: List[BulkDataProduct]
    +bulk_data_product_bag +

    List of bulk data products.

    +
    + +
    +
    +raw_data
    +

    Optional raw JSON data from the API response (for debugging).

    +
    + +
    +
    +bulk_data_product_bag: List[BulkDataProduct]
    -
    -count: int
    +
    +count: int
    -classmethod from_dict(data)[source]
    +classmethod from_dict(data, include_raw_data=False)[source]

    Create a BulkDataResponse object from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing API response data.

    • +
    • include_raw_data (bool) – If True, store the raw JSON for debugging.

    • +
    +
    +
    Returns:
    +

    An instance of BulkDataResponse.

    +
    Return type:
    -

    BulkDataResponse

    +

    BulkDataResponse

    +
    +
    +raw_data: str | None = None
    +
    +
    to_dict()[source]

    Convert the BulkDataResponse object to a dictionary.

    Return type:
    -

    Dict[str, Any]

    +

    Dict[str, Any]

    @@ -520,51 +1207,51 @@
    class pyUSPTO.models.bulk_data.FileData(file_name, file_size, file_data_from_date, file_data_to_date, file_type_text, file_release_date, file_download_uri=None, file_date=None, file_last_modified_date_time=None)[source]
    -

    Bases: object

    +

    Bases: object

    Represents a file in the bulk data API.

    -file_data_from_date: str
    +file_data_from_date: str
    -file_data_to_date: str
    +file_data_to_date: str
    -file_date: Optional[str] = None
    +file_date: str | None = None
    -file_download_uri: Optional[str] = None
    +file_download_uri: str | None = None
    -file_last_modified_date_time: Optional[str] = None
    +file_last_modified_date_time: str | None = None
    -file_name: str
    +file_name: str
    -file_release_date: str
    +file_release_date: str
    -file_size: int
    +file_size: int
    -file_type_text: str
    +file_type_text: str
    @@ -583,16 +1270,16 @@
    class pyUSPTO.models.bulk_data.ProductFileBag(count, file_data_bag)[source]
    -

    Bases: object

    +

    Bases: object

    Container for file data elements.

    -count: int
    +count: int
    -file_data_bag: List[FileData]
    +file_data_bag: List[FileData]
    @@ -609,343 +1296,4428 @@

    models.patent_data - Data models for USPTO patent data API

    -

    This module provides data models for the USPTO Patent Data API.

    +

    This module provides Pydantic-style data models, primarily using frozen +dataclasses, for representing responses from the USPTO Patent Data API. +It aims to offer more Pythonic representations (e.g., Enums, native +date/datetime objects) of the API’s JSON data. Models cover aspects like +application metadata, party information (applicants, inventors, attorneys), +document details, continuity, assignments, and more.

    -
    -class pyUSPTO.models.patent_data.Address(name_line_one_text=None, name_line_two_text=None, address_line_one_text=None, address_line_two_text=None, address_line_three_text=None, address_line_four_text=None, geographic_region_name=None, geographic_region_code=None, postal_code=None, city_name=None, country_code=None, country_name=None, postal_address_category=None, correspondent_name_text=None)[source]
    -

    Bases: object

    -

    Represents an address in the patent data API.

    +
    +class pyUSPTO.models.patent_data.ActiveIndicator(*values)[source]
    +

    Bases: Enum

    +

    Represents an active or inactive status, often used for practitioners or entities.

    +

    This Enum is designed to flexibly parse common string representations of +active/inactive or true/false states (e.g., “Y”, “N”, “true”, “false”, “Active”) +into standardized Enum members.

    -
    -address_line_four_text: Optional[str] = None
    +
    +ACTIVE = 'Active'
    -
    -address_line_one_text: Optional[str] = None
    +
    +FALSE = 'false'
    -
    -address_line_three_text: Optional[str] = None
    +
    +NO = 'N'
    -
    -address_line_two_text: Optional[str] = None
    +
    +TRUE = 'true'
    -
    -city_name: Optional[str] = None
    +
    +YES = 'Y'
    +
    + +
    +
    +class pyUSPTO.models.patent_data.Address(name_line_one_text=None, name_line_two_text=None, address_line_one_text=None, address_line_two_text=None, address_line_three_text=None, address_line_four_text=None, geographic_region_name=None, geographic_region_code=None, postal_code=None, city_name=None, country_code=None, country_name=None, postal_address_category=None, correspondent_name_text=None, country_or_state_code=None, ict_state_code=None, ict_country_code=None)[source]
    +

    Bases: object

    +

    Represents a postal address with fields for street, city, region, country, and postal code.

    +

    It can be used for various entities like applicants, inventors, or correspondence.

    -
    -correspondent_name_text: Optional[str] = None
    -
    +
    +name_line_one_text
    +

    First line of the name (e.g., company name).

    +
    -
    -country_code: Optional[str] = None
    +
    +name_line_two_text
    +

    Second line of the name.

    +
    + +
    +
    +address_line_one_text
    +

    First line of the street address.

    +
    + +
    +
    +address_line_two_text
    +

    Second line of the street address.

    +
    + +
    +
    +address_line_three_text
    +

    Third line of the street address.

    +
    + +
    +
    +address_line_four_text
    +

    Fourth line of the street address.

    +
    + +
    +
    +geographic_region_name
    +

    Name of the geographic region (e.g., state, province).

    +
    + +
    +
    +geographic_region_code
    +

    Code for the geographic region.

    +
    + +
    +
    +postal_code
    +

    Postal or ZIP code.

    +
    + +
    +
    +city_name
    +

    Name of the city.

    +
    + +
    +
    +country_code
    +

    Two-letter country code (e.g., “US”).

    +
    + +
    +
    +country_name
    +

    Full name of the country (e.g., “United States”).

    +
    + +
    +
    +postal_address_category
    +

    Category of the address (e.g., “MAILING_ADDRESS”).

    +
    + +
    +
    +correspondent_name_text
    +

    Name of the correspondent at this address.

    +
    + +
    +
    +country_or_state_code
    +

    Country or state code.

    +
    + +
    +
    +ict_state_code
    +

    International code for the state/region (USPTO format).

    +
    + +
    +
    +ict_country_code
    +

    International code for the country (USPTO format).

    +
    + +
    +
    +address_line_four_text: str | None = None
    +
    + +
    +
    +address_line_one_text: str | None = None
    +
    + +
    +
    +address_line_three_text: str | None = None
    +
    + +
    +
    +address_line_two_text: str | None = None
    +
    + +
    +
    +city_name: str | None = None
    +
    + +
    +
    +correspondent_name_text: str | None = None
    +
    + +
    +
    +country_code: str | None = None
    +
    + +
    +
    +country_name: str | None = None
    +
    + +
    +
    +country_or_state_code: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Address instance from a dictionary representation.

    +

    Maps camelCase keys from API data to class attributes.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary containing address data.

    +
    +
    Returns:
    +

    An instance of Address.

    +
    +
    Return type:
    +

    Address

    +
    +
    +
    + +
    +
    +geographic_region_code: str | None = None
    +
    + +
    +
    +geographic_region_name: str | None = None
    +
    + +
    +
    +ict_country_code: str | None = None
    +
    + +
    +
    +ict_state_code: str | None = None
    +
    + +
    +
    +name_line_one_text: str | None = None
    +
    + +
    +
    +name_line_two_text: str | None = None
    +
    + +
    +
    +postal_address_category: str | None = None
    +
    + +
    +
    +postal_code: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Address instance to a dictionary with camelCase keys.

    +
    +
    Returns:
    +

    A dictionary representation of the address.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Applicant(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None, applicant_name_text=None, correspondence_address_bag=<factory>)[source]
    +

    Bases: Person

    +

    Represents an applicant for a patent, inheriting from Person.

    +

    Includes applicant-specific name text and a list of correspondence addresses.

    +
    +
    +applicant_name_text
    +

    The full name of the applicant as a single string.

    +
    + +
    +
    +correspondence_address_bag
    +

    A list of Address objects for the applicant.

    +
    + +
    +
    +applicant_name_text: str | None = None
    +
    + +
    +
    +correspondence_address_bag: List[Address]
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Applicant instance from a dictionary.

    +

    Inherits person fields and adds applicant-specific fields.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with applicant data.

    +
    +
    Returns:
    +

    An instance of Applicant.

    +
    +
    Return type:
    +

    Applicant

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Applicant instance to a dictionary.

    +

    Includes inherited person fields and applicant-specific fields, +using camelCase keys and omitting None values or empty lists.

    +
    +
    Returns:
    +

    Dictionary representation of the applicant.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.ApplicationContinuityData(parent_continuity_bag=<factory>, child_continuity_bag=<factory>)[source]
    +

    Bases: object

    +

    Holds parent and child continuity application data for a specific patent application.

    +

    This class consolidates lists of ParentContinuity and ChildContinuity objects, +representing the lineage of an application.

    +
    +
    +parent_continuity_bag
    +

    List of ParentContinuity objects.

    +
    + +
    +
    +child_continuity_bag
    +

    List of ChildContinuity objects.

    +
    + +
    +
    +child_continuity_bag: List[ChildContinuity]
    +
    + +
    +
    +classmethod from_wrapper(wrapper)[source]
    +

    Creates an ApplicationContinuityData instance from a PatentFileWrapper.

    +

    Extracts parent and child continuity bags from the wrapper.

    +
    +
    Parameters:
    +

    wrapper (PatentFileWrapper) – The patent file wrapper containing continuity data.

    +
    +
    Returns:
    +

    An instance of ApplicationContinuityData.

    +
    +
    Return type:
    +

    ApplicationContinuityData

    +
    +
    +
    + +
    +
    +parent_continuity_bag: List[ParentContinuity]
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the ApplicationContinuityData instance to a dictionary.

    +
    +
    Returns:
    +

    +
    Dictionary representation with “parentContinuityBag”

    and “childContinuityBag” keys.

    +
    +
    +

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.ApplicationMetaData(national_stage_indicator=None, entity_status_data=None, publication_date_bag=<factory>, publication_sequence_number_bag=<factory>, publication_category_bag=<factory>, docket_number=None, first_inventor_to_file_indicator=None, first_applicant_name=None, first_inventor_name=None, application_confirmation_number=None, application_status_date=None, application_status_description_text=None, filing_date=None, effective_filing_date=None, grant_date=None, group_art_unit_number=None, application_type_code=None, application_type_label_name=None, application_type_category=None, invention_title=None, patent_number=None, application_status_code=None, earliest_publication_number=None, earliest_publication_date=None, pct_publication_number=None, pct_publication_date=None, international_registration_publication_date=None, international_registration_number=None, examiner_name_text=None, class_field=None, subclass=None, uspc_symbol_text=None, customer_number=None, cpc_classification_bag=<factory>, applicant_bag=<factory>, inventor_bag=<factory>, raw_data=None)[source]
    +

    Bases: object

    +

    Represents the metadata associated with a patent application.

    +

    This class holds a wide range of information including application status, +dates (filing, grant, publication), applicant and inventor details, +classification data, and other identifying information.

    +
    +
    +national_stage_indicator
    +

    Indicates if the application is a national stage entry.

    +
    + +
    +
    +entity_status_data
    +

    EntityStatus object detailing applicant’s entity status.

    +
    + +
    +
    +publication_date_bag
    +

    List of publication dates.

    +
    + +
    +
    +publication_sequence_number_bag
    +

    List of publication sequence numbers.

    +
    + +
    +
    +publication_category_bag
    +

    List of publication categories.

    +
    + +
    +
    +docket_number
    +

    Applicant’s or attorney’s docket number.

    +
    + +
    +
    +first_inventor_to_file_indicator
    +

    Boolean indicating if under First-Inventor-to-File.

    +
    + +
    +
    +first_applicant_name
    +

    Name of the first listed applicant.

    +
    + +
    +
    +first_inventor_name
    +

    Name of the first listed inventor.

    +
    + +
    +
    +application_confirmation_number
    +

    USPTO confirmation number for the application.

    +
    + +
    +
    +application_status_date
    +

    Date the current application status was set.

    +
    + +
    +
    +application_status_description_text
    +

    Textual description of the current application status.

    +
    + +
    +
    +filing_date
    +

    Official filing date of the application.

    +
    + +
    +
    +effective_filing_date
    +

    Effective filing date, considering priority claims.

    +
    + +
    +
    +grant_date
    +

    Date the patent was granted, if applicable.

    +
    + +
    +
    +group_art_unit_number
    +

    USPTO Group Art Unit number.

    +
    + +
    +
    +application_type_code
    +

    Code for the application type.

    +
    + +
    +
    +application_type_label_name
    +

    Label for the application type (e.g., “Utility”).

    +
    + +
    +
    +application_type_category
    +

    Category of the application type.

    +
    + +
    +
    +invention_title
    +

    Title of the invention.

    +
    + +
    +
    +patent_number
    +

    USPTO patent number, if granted.

    +
    + +
    +
    +application_status_code
    +

    Numeric code for the application status.

    +
    + +
    +
    +earliest_publication_number
    +

    Number of the earliest pre-grant publication.

    +
    + +
    +
    +earliest_publication_date
    +

    Date of the earliest pre-grant publication.

    +
    + +
    +
    +pct_publication_number
    +

    PCT publication number, if applicable.

    +
    + +
    +
    +pct_publication_date
    +

    PCT publication date, if applicable.

    +
    + +
    +
    +international_registration_publication_date
    +

    Date of international registration publication.

    +
    + +
    +
    +international_registration_number
    +

    International registration number.

    +
    + +
    +
    +examiner_name_text
    +

    Name of the patent examiner.

    +
    + +
    +
    +class_field
    +

    USPC main classification. (Named class_field to avoid keyword clash).

    +
    + +
    +
    +subclass
    +

    USPC subclass.

    +
    + +
    +
    +uspc_symbol_text
    +

    Full USPC classification symbol.

    +
    + +
    +
    +customer_number
    +

    USPTO customer number associated with the application.

    +
    + +
    +
    +cpc_classification_bag
    +

    List of CPC classification symbols.

    +
    + +
    +
    +applicant_bag
    +

    List of Applicant objects.

    +
    + +
    +
    +inventor_bag
    +

    List of Inventor objects.

    +
    + +
    +
    +raw_data
    +

    Raw JSON string of the data used to create this instance (for debugging).

    +
    + +
    +
    +applicant_bag: List[Applicant]
    +
    + +
    +
    +application_confirmation_number: int | None = None
    +
    + +
    +
    +application_status_code: int | None = None
    +
    + +
    +
    +application_status_date: date | None = None
    +
    + +
    +
    +application_status_description_text: str | None = None
    +
    + +
    +
    +application_type_category: str | None = None
    +
    + +
    +
    +application_type_code: str | None = None
    +
    + +
    +
    +application_type_label_name: str | None = None
    +
    + +
    +
    +class_field: str | None = None
    +
    + +
    +
    +cpc_classification_bag: List[str]
    +
    + +
    +
    +customer_number: int | None = None
    +
    + +
    +
    +docket_number: str | None = None
    +
    + +
    +
    +earliest_publication_date: date | None = None
    +
    + +
    +
    +earliest_publication_number: str | None = None
    +
    + +
    +
    +effective_filing_date: date | None = None
    +
    + +
    +
    +entity_status_data: EntityStatus | None = None
    +
    + +
    +
    +examiner_name_text: str | None = None
    +
    + +
    +
    +filing_date: date | None = None
    +
    + +
    +
    +first_applicant_name: str | None = None
    +
    + +
    +
    +first_inventor_name: str | None = None
    +
    + +
    +
    +first_inventor_to_file_indicator: bool | None = None
    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates an ApplicationMetaData instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary with application metadata.

    • +
    • include_raw_data (bool) – If True, store the raw JSON for debugging.

    • +
    +
    +
    Returns:
    +

    An instance of ApplicationMetaData.

    +
    +
    Return type:
    +

    ApplicationMetaData

    +
    +
    +
    + +
    +
    +grant_date: date | None = None
    +
    + +
    +
    +group_art_unit_number: str | None = None
    +
    + +
    +
    +international_registration_number: str | None = None
    +
    + +
    +
    +international_registration_publication_date: date | None = None
    +
    + +
    +
    +invention_title: str | None = None
    +
    + +
    +
    +inventor_bag: List[Inventor]
    +
    + +
    +
    +property is_aia: bool | None
    +

    Returns True if the application is AIA, False if pre-AIA, None if unknown.

    +
    + +
    +
    +property is_pre_aia: bool | None
    +

    Returns True if the application is pre-AIA, False if AIA, None if unknown.

    +
    + +
    +
    +national_stage_indicator: bool | None = None
    +
    + +
    +
    +patent_number: str | None = None
    +
    + +
    +
    +pct_publication_date: date | None = None
    +
    + +
    +
    +pct_publication_number: str | None = None
    +
    + +
    +
    +publication_category_bag: List[str]
    +
    + +
    +
    +publication_date_bag: List[date]
    +
    + +
    +
    +publication_sequence_number_bag: List[str]
    +
    + +
    +
    +raw_data: str | None = None
    +
    + +
    +
    +subclass: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the ApplicationMetaData instance to a dictionary.

    +

    Serializes attributes to camelCase keys suitable for API interaction or storage. +Omits keys with None values or empty lists. Handles date and boolean serialization.

    +
    +
    Returns:
    +

    Dictionary representation of the application metadata.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    +
    +uspc_symbol_text: str | None = None
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Assignee(assignee_name_text=None, assignee_address=None)[source]
    +

    Bases: object

    +

    Represents an assignee in a patent assignment.

    +
    +
    +assignee_name_text
    +

    The name of the party receiving the assignment.

    +
    + +
    +
    +assignee_address
    +

    The Address of the assignee.

    +
    + +
    +
    +assignee_address: Address | None = None
    +
    + +
    +
    +assignee_name_text: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Assignee instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with assignee data.

    +
    +
    Returns:
    +

    An instance of Assignee.

    +
    +
    Return type:
    +

    Assignee

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Assignee instance to a dictionary.

    +

    Omits keys with None values.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Assignment(reel_number=None, frame_number=None, reel_and_frame_number=None, page_total_quantity=None, assignment_document_location_uri=None, assignment_received_date=None, assignment_recorded_date=None, assignment_mailed_date=None, conveyance_text=None, image_available_status_code=None, attorney_docket_number=None, assignor_bag=<factory>, assignee_bag=<factory>, correspondence_address=None, domestic_representative=None)[source]
    +

    Bases: object

    +

    Represents a patent assignment, detailing the transfer of rights.

    +

    Includes information about the reel and frame, document location, dates, conveyance text, +and bags of assignors, assignees, correspondence address, and domestic representative.

    +
    +
    +reel_number
    +

    Reel number for the assignment record.

    +
    + +
    +
    +frame_number
    +

    Frame number for the assignment record.

    +
    + +
    +
    +reel_and_frame_number
    +

    Combined reel and frame number.

    +
    + +
    +
    +page_total_quantity
    +

    Total number of pages in the assignment document.

    +
    + +
    +
    +assignment_document_location_uri
    +

    URI for the assignment document.

    +
    + +
    +
    +assignment_received_date
    +

    Date the assignment was received by USPTO.

    +
    + +
    +
    +assignment_recorded_date
    +

    Date the assignment was recorded by USPTO.

    +
    + +
    +
    +assignment_mailed_date
    +

    Date the assignment notification was mailed.

    +
    + +
    +
    +conveyance_text
    +

    Text describing the nature of the conveyance.

    +
    + +
    +
    +image_available_status_code
    +

    Code to indicate the availability of the image.

    +
    + +
    +
    +attorney_docket_number
    +

    Attorney docket number for the assignment.

    +
    + +
    +
    +assignor_bag
    +

    List of Assignor objects.

    +
    + +
    +
    +assignee_bag
    +

    List of Assignee objects.

    +
    + +
    +
    +correspondence_address
    +

    Address object for correspondence (single object).

    +
    + +
    +
    +domestic_representative
    +

    Address object for the domestic representative.

    +
    + +
    +
    +assignee_bag: List[Assignee]
    +
    + +
    +
    +assignment_document_location_uri: str | None = None
    +
    + +
    +
    +assignment_mailed_date: date | None = None
    +
    + +
    +
    +assignment_received_date: date | None = None
    +
    + +
    +
    +assignment_recorded_date: date | None = None
    +
    + +
    +
    +assignor_bag: List[Assignor]
    +
    + +
    +
    +attorney_docket_number: str | None = None
    +
    + +
    +
    +conveyance_text: str | None = None
    +
    + +
    +
    +correspondence_address: Address | None = None
    +
    + +
    +
    +domestic_representative: Address | None = None
    +
    + +
    +
    +frame_number: int | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Assignment instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with assignment data.

    +
    +
    Returns:
    +

    An instance of Assignment.

    +
    +
    Return type:
    +

    Assignment

    +
    +
    +
    + +
    +
    +image_available_status_code: bool | None = None
    +
    + +
    +
    +page_total_quantity: int | None = None
    +
    + +
    +
    +reel_and_frame_number: str | None = None
    +
    + +
    +
    +reel_number: int | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Assignment instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Assignor(assignor_name=None, execution_date=None)[source]
    +

    Bases: object

    +

    Represents an assignor in a patent assignment.

    +
    +
    +assignor_name
    +

    The name of the assigning party.

    +
    + +
    +
    +execution_date
    +

    The date the assignment was executed.

    +
    + +
    +
    +assignor_name: str | None = None
    +
    + +
    +
    +execution_date: date | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Assignor instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with assignor data.

    +
    +
    Returns:
    +

    An instance of Assignor.

    +
    +
    Return type:
    +

    Assignor

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Assignor instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Attorney(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None, registration_number=None, active_indicator=None, registered_practitioner_category=None, attorney_address_bag=<factory>, telecommunication_address_bag=<factory>)[source]
    +

    Bases: Person

    +

    Represents an attorney or agent associated with a patent application, inheriting from Person.

    +

    Includes registration number, active status, practitioner category, addresses, and telecommunication details.

    +
    +
    +registration_number
    +

    The attorney’s USPTO registration number.

    +
    + +
    +
    +active_indicator
    +

    Indicates if the attorney is currently active (e.g., “Y”, “N”).

    +
    + +
    +
    +registered_practitioner_category
    +

    Category of the practitioner (e.g., “ATTORNEY”, “AGENT”).

    +
    + +
    +
    +attorney_address_bag
    +

    List of Address objects for the attorney.

    +
    + +
    +
    +telecommunication_address_bag
    +

    List of Telecommunication objects for the attorney.

    +
    + +
    +
    +active_indicator: str | None = None
    +
    + +
    +
    +attorney_address_bag: List[Address]
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Attorney instance from a dictionary.

    +

    Inherits person fields and adds attorney-specific details.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with attorney data.

    +
    +
    Returns:
    +

    An instance of Attorney.

    +
    +
    Return type:
    +

    Attorney

    +
    +
    +
    + +
    +
    +registered_practitioner_category: str | None = None
    +
    + +
    +
    +registration_number: str | None = None
    +
    + +
    +
    +telecommunication_address_bag: List[Telecommunication]
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Attorney instance to a dictionary.

    +

    Includes inherited person fields and attorney-specific fields, +using camelCase keys and omitting None values or empty lists.

    +
    +
    Returns:
    +

    Dictionary representation of the attorney.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.ChildContinuity(first_inventor_to_file_indicator=None, application_number_text=None, filing_date=None, status_code=None, status_description_text=None, patent_number=None, claim_parentage_type_code=None, claim_parentage_type_code_description_text=None, child_application_status_code=None, parent_application_number_text=None, child_application_number_text=None, child_application_status_description_text=None, child_application_filing_date=None, child_patent_number=None)[source]
    +

    Bases: Continuity

    +

    Represents a child application in a patent application’s continuity chain.

    +

    Inherits from Continuity and adds specific fields for child application details.

    +
    +
    +child_application_status_code
    +

    Status code of the child application.

    +
    + +
    +
    +parent_application_number_text
    +

    Application number of the parent (current) application.

    +
    + +
    +
    +child_application_number_text
    +

    Application number of the child application.

    +
    + +
    +
    +child_application_status_description_text
    +

    Status description of the child application.

    +
    + +
    +
    +child_application_filing_date
    +

    Filing date of the child application.

    +
    + +
    +
    +child_patent_number
    +

    Patent number of the child application, if granted.

    +
    + +
    +
    +child_application_filing_date: date | None = None
    +
    + +
    +
    +child_application_number_text: str | None = None
    +
    + +
    +
    +child_application_status_code: int | None = None
    +
    + +
    +
    +child_application_status_description_text: str | None = None
    +
    + +
    +
    +child_patent_number: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a ChildContinuity instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with child continuity data.

    +
    +
    Returns:
    +

    An instance of ChildContinuity.

    +
    +
    Return type:
    +

    ChildContinuity

    +
    +
    +
    + +
    +
    +parent_application_number_text: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the ChildContinuity instance to a dictionary.

    +

    Maps attributes to specific camelCase keys expected by the API for child continuity. +Filters out None values to match the API response structure.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Continuity(first_inventor_to_file_indicator=None, application_number_text=None, filing_date=None, status_code=None, status_description_text=None, patent_number=None, claim_parentage_type_code=None, claim_parentage_type_code_description_text=None)[source]
    +

    Bases: object

    +

    Base class representing continuity data for a patent application.

    +

    This includes details about the application’s relationship to other applications (parent/child), +its filing status under AIA (America Invents Act), and key identifiers.

    +
    +
    +first_inventor_to_file_indicator
    +

    Boolean indicating if the application is under First-Inventor-to-File provisions.

    +
    + +
    +
    +application_number_text
    +

    The application number of the related (parent or child) application.

    +
    + +
    +
    +filing_date
    +

    The filing date of the related application.

    +
    + +
    +
    +status_code
    +

    The status code of the related application.

    +
    + +
    +
    +status_description_text
    +

    The status description of the related application.

    +
    + +
    +
    +patent_number
    +

    The patent number if the related application is granted.

    +
    + +
    +
    +claim_parentage_type_code
    +

    Code indicating the type of continuity claim (e.g., “CON”, “DIV”).

    +
    + +
    +
    +claim_parentage_type_code_description_text
    +

    Description of the continuity claim type.

    +
    + +
    +
    +application_number_text: str | None = None
    +
    + +
    +
    +claim_parentage_type_code: str | None = None
    +
    + +
    +
    +claim_parentage_type_code_description_text: str | None = None
    +
    + +
    +
    +filing_date: date | None = None
    +
    + +
    +
    +first_inventor_to_file_indicator: bool | None = None
    +
    + +
    +
    +property is_aia: bool | None
    +

    Returns True if the application is AIA, False if pre-AIA, None if unknown.

    +
    + +
    +
    +property is_pre_aia: bool | None
    +

    Returns True if the application is pre-AIA, False if AIA, None if unknown.

    +
    + +
    +
    +patent_number: str | None = None
    +
    + +
    +
    +status_code: int | None = None
    +
    + +
    +
    +status_description_text: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Continuity instance to a dictionary.

    +

    Omits attributes that are None and property-derived fields. +Keys are converted to camelCase.

    +
    +
    Returns:
    +

    A dictionary representation of the continuity data.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.CustomerNumberCorrespondence(patron_identifier=None, organization_standard_name=None, power_of_attorney_address_bag=<factory>, telecommunication_address_bag=<factory>)[source]
    +

    Bases: object

    +

    Represents correspondence data associated with a USPTO customer number.

    +

    Includes patron identifier, organization name, power of attorney addresses, and telecommunication details.

    +
    +
    +patron_identifier
    +

    The USPTO customer number.

    +
    + +
    +
    +organization_standard_name
    +

    The name of the organization associated with the customer number.

    +
    + +
    +
    +power_of_attorney_address_bag
    +

    List of Address objects for power of attorney.

    +
    + +
    +
    +telecommunication_address_bag
    +

    List of Telecommunication objects.

    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a CustomerNumberCorrespondence instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with customer number correspondence data.

    +
    +
    Returns:
    +

    An instance of CustomerNumberCorrespondence.

    +
    +
    Return type:
    +

    CustomerNumberCorrespondence

    +
    +
    +
    + +
    +
    +organization_standard_name: str | None = None
    +
    + +
    +
    +patron_identifier: int | None = None
    +
    + +
    +
    +power_of_attorney_address_bag: List[Address]
    +
    + +
    +
    +telecommunication_address_bag: List[Telecommunication]
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the CustomerNumberCorrespondence instance to a dictionary.

    +

    Omits keys with None values or empty lists.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.DirectionCategory(*values)[source]
    +

    Bases: Enum

    +

    Represents the direction of a document relative to the USPTO (e.g., INCOMING, OUTGOING).

    +
    +
    +INCOMING = 'INCOMING'
    +
    + +
    +
    +OUTGOING = 'OUTGOING'
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Document(application_number_text=None, official_date=None, document_identifier=None, document_code=None, document_code_description_text=None, direction_category=None, document_formats=<factory>)[source]
    +

    Bases: object

    +

    Represents a single document associated with a patent application.

    +

    This includes metadata such as its identifier, official date, code, description, +direction (incoming/outgoing), and available download formats.

    +
    +
    +application_number_text
    +

    The application number this document belongs to.

    +
    + +
    +
    +official_date
    +

    The official date of the document.

    +
    + +
    +
    +document_identifier
    +

    A unique identifier for this document.

    +
    + +
    +
    +document_code
    +

    A code representing the type of document.

    +
    + +
    +
    +document_code_description_text
    +

    A textual description of the document code.

    +
    + +
    +
    +direction_category
    +

    The direction of the document (e.g., INCOMING, OUTGOING).

    +
    + +
    +
    +document_formats
    +

    A list of available download formats for this document.

    +
    + +
    +
    +application_number_text: str | None = None
    +
    + +
    +
    +direction_category: DirectionCategory | None = None
    +
    + +
    +
    +document_code: str | None = None
    +
    + +
    +
    +document_code_description_text: str | None = None
    +
    + +
    +
    +document_formats: List[DocumentFormat]
    +
    + +
    +
    +document_identifier: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a Document instance from a dictionary representation.

    +

    Maps API JSON keys (camelCase) to class attributes, parsing nested +objects like DocumentFormat and DirectionCategory.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – A dictionary containing document data, +typically from an API response.

    +
    +
    Returns:
    +

    An instance of Document.

    +
    +
    Return type:
    +

    Document

    +
    +
    +
    + +
    +
    +official_date: datetime | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Document instance to a dictionary for API compatibility.

    +

    Serializes attributes to camelCase keys and handles nested objects. +Omits keys with None values or empty lists.

    +
    +
    Returns:
    +

    A dictionary representation of the Document.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.DocumentBag(documents)[source]
    +

    Bases: object

    +

    A collection of Document objects associated with a patent application.

    +

    Provides iterable access and standard collection methods like len and getitem. +This class is immutable by convention after initialization.

    +
    +
    +documents
    +

    An immutable tuple of Document objects.

    +
    +
    Type:
    +

    tuple[Document, ]

    +
    +
    +
    + +
    +
    +__init__(documents)[source]
    +

    Initializes the DocumentBag with a list of documents.

    +
    +
    Parameters:
    +

    documents (List[Document]) – A list of Document instances.

    +
    +
    +
    + +
    +
    +__repr__()[source]
    +

    Returns a detailed string representation for debugging.

    +
    +
    Returns:
    +

    Detailed representation of the DocumentBag.

    +
    +
    Return type:
    +

    str

    +
    +
    +
    + +
    +
    +__str__()[source]
    +

    Returns a string representation showing document count and summary.

    +
    +
    Returns:
    +

    Human-readable summary of the DocumentBag.

    +
    +
    Return type:
    +

    str

    +
    +
    +
    + +
    +
    +property documents: tuple[Document, ...]
    +

    Provides access to the tuple of documents.

    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a DocumentBag instance from a dictionary representation.

    +

    Expects a dictionary with a “documentBag” key containing a list of +document data dictionaries.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – A dictionary, typically from an API response, +containing the document bag.

    +
    +
    Returns:
    +

    An instance of DocumentBag.

    +
    +
    Return type:
    +

    DocumentBag

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the DocumentBag instance to a dictionary.

    +

    Serializes the collection into a dictionary with a “documentBag” key, +containing a list of Document dictionaries.

    +
    +
    Returns:
    +

    A dictionary representation of the DocumentBag.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.DocumentFormat(mime_type_identifier=None, download_url=None, page_total_quantity=None)[source]
    +

    Bases: object

    +

    Represents an available download format for a specific document.

    +
    +
    +mime_type_identifier
    +

    The MIME type of the downloadable file (e.g., “PDF”).

    +
    + +
    +
    +download_url
    +

    The URL from which the document format can be downloaded.

    +
    + +
    +
    +page_total_quantity
    +

    The total number of pages in this document format.

    +
    + +
    +
    +download_url: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a DocumentFormat instance from a dictionary representation.

    +

    This factory method is typically used to construct DocumentFormat +objects from data parsed from an API JSON response. It maps +dictionary keys (expected in camelCase) to the class attributes.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – A dictionary containing the data for a +DocumentFormat. Expected keys from the API are +“mimeTypeIdentifier”, “downloadUrl”, and “pageTotalQuantity”.

    +
    +
    Returns:
    +

    +
    An instance of DocumentFormat initialized with

    data from the input dictionary.

    +
    +
    +

    +
    +
    Return type:
    +

    DocumentFormat

    +
    +
    +
    + +
    +
    +mime_type_identifier: str | None = None
    +
    + +
    +
    +page_total_quantity: int | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the DocumentFormat instance to a dictionary.

    +

    This method serializes the DocumentFormat object into a dictionary, +mapping the instance’s attributes to camelCase keys. This is typically +useful for generating JSON representations compatible with API expectations.

    +
    +
    Returns:
    +

    +
    A dictionary representation of the DocumentFormat

    instance with keys “mimeTypeIdentifier”, “downloadUrl”, and +“pageTotalQuantity”.

    +
    +
    +

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.EntityStatus(small_entity_status_indicator=None, business_entity_status_category=None)[source]
    +

    Bases: object

    +

    Represents the entity status of an applicant (e.g., small entity status).

    +
    +
    +small_entity_status_indicator
    +

    Boolean indicating if the applicant qualifies for small entity status.

    +
    + +
    +
    +business_entity_status_category
    +

    String category of the business entity status (e.g., “Undiscounted”).

    +
    + +
    +
    +business_entity_status_category: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an EntityStatus instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with entity status data.

    +
    +
    Returns:
    +

    An instance of EntityStatus.

    +
    +
    Return type:
    +

    EntityStatus

    +
    +
    +
    + +
    +
    +small_entity_status_indicator: bool | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the EntityStatus instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.EventData(event_code=None, event_description_text=None, event_date=None)[source]
    +

    Bases: object

    +

    Represents a single event in the transaction history of a patent application.

    +
    +
    +event_code
    +

    A code identifying the type of event.

    +
    + +
    +
    +event_description_text
    +

    A textual description of the event.

    +
    + +
    +
    +event_date
    +

    The date the event was recorded.

    +
    + +
    +
    +event_code: str | None = None
    +
    + +
    +
    +event_date: date | None = None
    +
    + +
    +
    +event_description_text: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an EventData instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with event data.

    +
    +
    Returns:
    +

    An instance of EventData.

    +
    +
    Return type:
    +

    EventData

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the EventData instance to a dictionary.

    +

    Omits keys with None values and converts field names to camelCase.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.ForeignPriority(ip_office_name=None, filing_date=None, application_number_text=None)[source]
    +

    Bases: object

    +

    Represents a foreign priority claim for a patent application.

    +
    +
    +ip_office_name
    +

    The name of the intellectual property office of the priority application.

    +
    + +
    +
    +filing_date
    +

    The filing date of the priority application.

    +
    + +
    +
    +application_number_text
    +

    The application number of the priority application.

    +
    + +
    +
    +application_number_text: str | None = None
    +
    + +
    +
    +filing_date: date | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a ForeignPriority instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with foreign priority data.

    +
    +
    Returns:
    +

    An instance of ForeignPriority.

    +
    +
    Return type:
    +

    ForeignPriority

    +
    +
    +
    + +
    +
    +ip_office_name: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the ForeignPriority instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Inventor(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None, inventor_name_text=None, correspondence_address_bag=<factory>)[source]
    +

    Bases: Person

    +

    Represents an inventor for a patent application, inheriting from Person.

    +

    Includes inventor-specific name text and a list of correspondence addresses.

    +
    +
    +inventor_name_text
    +

    The full name of the inventor as a single string.

    +
    + +
    +
    +correspondence_address_bag
    +

    A list of Address objects for the inventor.

    +
    + +
    +
    +correspondence_address_bag: List[Address]
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates an Inventor instance from a dictionary.

    +

    Inherits person fields and adds inventor-specific fields.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with inventor data.

    +
    +
    Returns:
    +

    An instance of Inventor.

    +
    +
    Return type:
    +

    Inventor

    +
    +
    +
    + +
    +
    +inventor_name_text: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Inventor instance to a dictionary.

    +

    Includes inherited person fields and inventor-specific fields, +using camelCase keys and omitting None values or empty lists.

    +
    +
    Returns:
    +

    Dictionary representation of the inventor.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.ParentContinuity(first_inventor_to_file_indicator=None, application_number_text=None, filing_date=None, status_code=None, status_description_text=None, patent_number=None, claim_parentage_type_code=None, claim_parentage_type_code_description_text=None, parent_application_status_code=None, parent_patent_number=None, parent_application_status_description_text=None, parent_application_filing_date=None, parent_application_number_text=None, child_application_number_text=None)[source]
    +

    Bases: Continuity

    +

    Represents a parent application in a patent application’s continuity chain.

    +

    Inherits from Continuity and adds specific fields for parent application details.

    +
    +
    +parent_application_status_code
    +

    Status code of the parent application.

    +
    + +
    +
    +parent_patent_number
    +

    Patent number of the parent application, if granted.

    +
    + +
    +
    +parent_application_status_description_text
    +

    Status description of the parent application.

    +
    + +
    +
    +parent_application_filing_date
    +

    Filing date of the parent application.

    +
    + +
    +
    +parent_application_number_text
    +

    Application number of the parent application.

    +
    + +
    +
    +child_application_number_text
    +

    Application number of the child (current) application.

    +
    + +
    +
    +child_application_number_text: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a ParentContinuity instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with parent continuity data.

    +
    +
    Returns:
    +

    An instance of ParentContinuity.

    +
    +
    Return type:
    +

    ParentContinuity

    +
    +
    +
    + +
    +
    +parent_application_filing_date: date | None = None
    +
    + +
    +
    +parent_application_number_text: str | None = None
    +
    + +
    +
    +parent_application_status_code: int | None = None
    +
    + +
    +
    +parent_application_status_description_text: str | None = None
    +
    + +
    +
    +parent_patent_number: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the ParentContinuity instance to a dictionary.

    +

    Maps attributes to specific camelCase keys expected by the API for parent continuity. +Filters out None values to match the API response structure.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.PatentDataResponse(count, patent_file_wrapper_data_bag=<factory>, request_identifier=None, raw_data=None)[source]
    +

    Bases: object

    +

    Represents the overall response from a patent data API request.

    +

    It typically includes a count of the results and a list of PatentFileWrapper objects, +each containing detailed data for a patent application.

    +
    +
    +count
    +

    The total number of patent applications found matching the query.

    +
    + +
    +
    +patent_file_wrapper_data_bag
    +

    A list of PatentFileWrapper objects.

    +
    + +
    +
    +request_identifier
    +

    An identifier for the API request, if provided.

    +
    + +
    +
    +raw_data
    +

    Optional raw JSON data from the API response (for debugging).

    +
    + +
    +
    +count: int
    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PatentDataResponse instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary with API response data.

    • +
    • include_raw_data (bool) – If True, store the raw JSON for debugging.

    • +
    +
    +
    Returns:
    +

    An instance of PatentDataResponse.

    +
    +
    Return type:
    +

    PatentDataResponse

    +
    +
    +
    + +
    +
    +patent_file_wrapper_data_bag: List[PatentFileWrapper]
    +
    + +
    +
    +raw_data: str | None = None
    +
    + +
    +
    +request_identifier: str | None = None
    +
    + +
    +
    +to_csv()[source]
    +

    Converts the patent data in this response to a CSV formatted string.

    +

    The CSV will contain key metadata fields for each application, +such as invention title, application number, filing date, status, etc.

    +
    +
    Returns:
    +

    A string containing the data in CSV format.

    +
    +
    Return type:
    +

    str

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PatentDataResponse instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.PatentFileWrapper(application_number_text=None, application_meta_data=None, correspondence_address_bag=<factory>, assignment_bag=<factory>, record_attorney=None, foreign_priority_bag=<factory>, parent_continuity_bag=<factory>, child_continuity_bag=<factory>, patent_term_adjustment_data=None, event_data_bag=<factory>, pgpub_document_meta_data=None, grant_document_meta_data=None, last_ingestion_date_time=None)[source]
    +

    Bases: object

    +

    Represents the complete file wrapper for a single patent application.

    +

    This is a top-level object containing all data sections related to an application, +such as metadata, addresses, assignments, attorney information, continuity data, +PTA data, transaction events, and associated document metadata.

    +
    +
    +application_number_text
    +

    The primary application number.

    +
    + +
    +
    +application_meta_data
    +

    Comprehensive ApplicationMetaData.

    +
    + +
    +
    +correspondence_address_bag
    +

    List of Address objects for correspondence.

    +
    + +
    +
    +assignment_bag
    +

    List of Assignment records.

    +
    + +
    +
    +record_attorney
    +

    Information about the RecordAttorney.

    +
    + +
    +
    +foreign_priority_bag
    +

    List of ForeignPriority claims.

    +
    + +
    +
    +parent_continuity_bag
    +

    List of ParentContinuity records.

    +
    + +
    +
    +child_continuity_bag
    +

    List of ChildContinuity records.

    +
    + +
    +
    +patent_term_adjustment_data
    +

    PatentTermAdjustmentData details.

    +
    + +
    +
    +event_data_bag
    +

    List of EventData (transaction history).

    +
    + +
    +
    +pgpub_document_meta_data
    +

    PrintedMetaData for Pre-Grant Publication.

    +
    + +
    +
    +grant_document_meta_data
    +

    PrintedMetaData for the granted patent.

    +
    + +
    +
    +last_ingestion_date_time
    +

    Timestamp of when this data was last ingested by the API (UTC).

    +
    + +
    +
    +application_meta_data: ApplicationMetaData | None = None
    +
    + +
    +
    +application_number_text: str | None = None
    +
    + +
    +
    +assignment_bag: List[Assignment]
    +
    + +
    +
    +child_continuity_bag: List[ChildContinuity]
    +
    + +
    +
    +correspondence_address_bag: List[Address]
    +
    + +
    +
    +event_data_bag: List[EventData]
    +
    + +
    +
    +foreign_priority_bag: List[ForeignPriority]
    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PatentFileWrapper instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary with patent file wrapper data.

    • +
    • include_raw_data (bool) – If True, store the raw JSON for debugging.

    • +
    +
    +
    Returns:
    +

    An instance of PatentFileWrapper.

    +
    +
    Return type:
    +

    PatentFileWrapper

    +
    +
    +
    + +
    +
    +grant_document_meta_data: PrintedMetaData | None = None
    +
    + +
    +
    +last_ingestion_date_time: datetime | None = None
    +
    + +
    +
    +parent_continuity_bag: List[ParentContinuity]
    +
    + +
    +
    +patent_term_adjustment_data: PatentTermAdjustmentData | None = None
    +
    + +
    +
    +pgpub_document_meta_data: PrintedMetaData | None = None
    +
    + +
    +
    +record_attorney: RecordAttorney | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PatentFileWrapper instance to a dictionary.

    +

    Omits keys with None values or empty lists. Serializes nested objects.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.PatentTermAdjustmentData(a_delay_quantity=None, adjustment_total_quantity=None, applicant_day_delay_quantity=None, b_delay_quantity=None, c_delay_quantity=None, non_overlapping_day_quantity=None, overlapping_day_quantity=None, non_overlapping_day_delay_quantity=None, ip_office_adjustment_delay_quantity=None, patent_term_adjustment_history_data_bag=<factory>)[source]
    +

    Bases: object

    +

    Represents the overall patent term adjustment (PTA) data for an application.

    +

    Includes various delay quantities (A, B, C, applicant, IP office), total adjustment, +and a history of PTA events.

    +
    +
    +a_delay_quantity
    +

    Number of days of ‘A’ delay.

    +
    + +
    +
    +adjustment_total_quantity
    +

    Total calculated PTA in days.

    +
    + +
    +
    +applicant_day_delay_quantity
    +

    Total days of delay attributable to the applicant.

    +
    + +
    +
    +b_delay_quantity
    +

    Number of days of ‘B’ delay.

    +
    + +
    +
    +c_delay_quantity
    +

    Number of days of ‘C’ delay.

    +
    + +
    +
    +non_overlapping_day_quantity
    +

    Number of non-overlapping delay days.

    +
    + +
    +
    +overlapping_day_quantity
    +

    Number of overlapping delay days.

    +
    + +
    +
    +non_overlapping_day_delay_quantity
    +

    Number of non-overlapping delay days specifically for delay calculation.

    +
    + +
    +
    +ip_office_adjustment_delay_quantity
    +

    Days of IP office delay used in adjustment calculation.

    +
    + +
    +
    +patent_term_adjustment_history_data_bag
    +

    List of PatentTermAdjustmentHistoryData events.

    +
    + +
    +
    +a_delay_quantity: float | None = None
    +
    + +
    +
    +adjustment_total_quantity: float | None = None
    +
    + +
    +
    +applicant_day_delay_quantity: float | None = None
    +
    + +
    +
    +b_delay_quantity: float | None = None
    +
    + +
    +
    +c_delay_quantity: float | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a PatentTermAdjustmentData instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with PTA data.

    +
    +
    Returns:
    +

    An instance of PatentTermAdjustmentData.

    +
    +
    Return type:
    +

    PatentTermAdjustmentData

    +
    +
    +
    + +
    +
    +ip_office_adjustment_delay_quantity: float | None = None
    +
    + +
    +
    +non_overlapping_day_delay_quantity: float | None = None
    +
    + +
    +
    +non_overlapping_day_quantity: float | None = None
    +
    + +
    +
    +overlapping_day_quantity: float | None = None
    +
    + +
    +
    +patent_term_adjustment_history_data_bag: List[PatentTermAdjustmentHistoryData]
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PatentTermAdjustmentData instance to a dictionary.

    +

    Omits keys with None values or empty lists, and converts field names to camelCase.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData(event_date=None, applicant_day_delay_quantity=None, event_description_text=None, event_sequence_number=None, originating_event_sequence_number=None, pta_pte_code=None, ip_office_day_delay_quantity=None)[source]
    +

    Bases: object

    +

    Represents a single entry in the patent term adjustment (PTA) history for an application.

    +

    Details specific events, dates, and day quantities affecting the patent term.

    +
    +
    +event_date
    +

    Date of the PTA event.

    +
    + +
    +
    +applicant_day_delay_quantity
    +

    Number of days of delay attributable to the applicant for this event.

    +
    + +
    +
    +event_description_text
    +

    Textual description of the PTA event.

    +
    + +
    +
    +event_sequence_number
    +

    Sequence number of this event in the PTA history.

    +
    + +
    +
    +originating_event_sequence_number
    +

    Sequence number of an event that originated this event.

    +
    + +
    +
    +pta_pte_code
    +

    Code indicating if the event relates to PTA or Patent Term Extension (PTE).

    +
    + +
    +
    +ip_office_day_delay_quantity
    +

    Number of days of IP office delay used in adjustment calculation for this event.

    +
    + +
    +
    +applicant_day_delay_quantity: float | None = None
    +
    + +
    +
    +event_date: date | None = None
    +
    + +
    +
    +event_description_text: str | None = None
    +
    + +
    +
    +event_sequence_number: float | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a PatentTermAdjustmentHistoryData instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with PTA history event data.

    +
    +
    Returns:
    +

    An instance of PatentTermAdjustmentHistoryData.

    +
    +
    Return type:
    +

    PatentTermAdjustmentHistoryData

    +
    +
    +
    + +
    +
    +ip_office_day_delay_quantity: float | None = None
    +
    + +
    +
    +originating_event_sequence_number: float | None = None
    +
    + +
    +
    +pta_pte_code: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PatentTermAdjustmentHistoryData instance to a dictionary.

    +

    Omits keys with None values.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Person(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None)[source]
    +

    Bases: object

    +

    A base data class representing a person with common name and country attributes.

    +

    This class is typically inherited by more specific types like Applicant, Inventor, or Attorney.

    +
    +
    +first_name
    +

    The first name of the person.

    +
    + +
    +
    +middle_name
    +

    The middle name or initial of the person.

    +
    + +
    +
    +last_name
    +

    The last name or surname of the person.

    +
    + +
    +
    +name_prefix
    +

    A prefix for the name (e.g., “Dr.”, “Mr.”).

    +
    + +
    +
    +name_suffix
    +

    A suffix for the name (e.g., “Jr.”, “PhD”).

    +
    + +
    +
    +preferred_name
    +

    The person’s preferred name, if different.

    +
    + +
    +
    +country_code
    +

    The country code associated with the person (e.g., citizenship).

    +
    + +
    +
    +country_code: str | None = None
    +
    + +
    +
    +first_name: str | None = None
    +
    + +
    +
    +last_name: str | None = None
    +
    + +
    +
    +middle_name: str | None = None
    +
    + +
    +
    +name_prefix: str | None = None
    +
    + +
    +
    +name_suffix: str | None = None
    +
    + +
    +
    +preferred_name: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Person instance to a dictionary with camelCase keys.

    +

    Omits attributes that are None.

    +
    +
    Returns:
    +

    A dictionary representation of the person.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.PrintedMetaData(zip_file_name=None, product_identifier=None, file_location_uri=None, file_create_date_time=None, xml_file_name=None)[source]
    +

    Bases: object

    +

    Represents metadata for a specific archive file, such as a PGPUB or Grant XML file.

    +
    +
    +zip_file_name
    +

    The name of the ZIP archive.

    +
    + +
    +
    +product_identifier
    +

    An identifier for the data product (e.g., “APPXML”, “PTGRXML”).

    +
    + +
    +
    +file_location_uri
    +

    The URI where the document file can be accessed.

    +
    + +
    +
    +file_create_date_time
    +

    The creation timestamp of the document file (UTC).

    +
    + +
    +
    +xml_file_name
    +

    The name of the XML file within the ZIP archive.

    +
    + +
    +
    +file_create_date_time: datetime | None = None
    +
    + +
    +
    +file_location_uri: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a PrintedMetaData instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with printed metadata.

    +
    +
    Returns:
    +

    An instance of PrintedMetaData.

    +
    +
    Return type:
    +

    PrintedMetaData

    +
    +
    +
    + +
    +
    +product_identifier: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PrintedMetaData instance to a dictionary.

    +

    Omits keys with None values. Serializes datetime to ISO format with ‘Z’.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    +
    +xml_file_name: str | None = None
    +
    + +
    +
    +zip_file_name: str | None = None
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.PrintedPublication(pgpub_document_meta_data=None, grant_document_meta_data=None)[source]
    +

    Bases: object

    +

    Holds metadata for associated documents like Pre-Grant Publications (PGPUB) +and Grant documents for a specific patent application.

    +
    +
    +pgpub_document_meta_data
    +

    PrintedMetaData for the Pre-Grant Publication, if any.

    +
    + +
    +
    +grant_document_meta_data
    +

    PrintedMetaData for the Grant document, if any.

    +
    + +
    +
    +classmethod from_wrapper(wrapper)[source]
    +

    Creates a PrintedPublication instance from a PatentFileWrapper.

    +

    Extracts PGPUB and Grant document metadata from the wrapper.

    +
    +
    Parameters:
    +

    wrapper (PatentFileWrapper) – The patent file wrapper.

    +
    +
    Returns:
    +

    An instance of PrintedPublication.

    +
    +
    Return type:
    +

    PrintedPublication

    +
    +
    +
    + +
    +
    +grant_document_meta_data: PrintedMetaData | None = None
    +
    + +
    +
    +pgpub_document_meta_data: PrintedMetaData | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PrintedPublication instance to a dictionary.

    +

    Omits keys if their corresponding metadata is None.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.RecordAttorney(customer_number_correspondence_data=None, power_of_attorney_bag=<factory>, attorney_bag=<factory>)[source]
    +

    Bases: object

    +

    Represents information about the attorney(s) of record for a patent application.

    +

    Contains customer number correspondence data, power of attorney information, and listed attorneys.

    +
    +
    +customer_number_correspondence_data
    +

    CustomerNumberCorrespondence object with customer number details.

    +
    + +
    +
    +power_of_attorney_bag
    +

    List of Attorney objects named in a power of attorney.

    +
    + +
    +
    +attorney_bag
    +

    List of Attorney objects listed as attorneys of record.

    +
    + +
    +
    +attorney_bag: List[Attorney]
    +
    + +
    +
    +customer_number_correspondence_data: CustomerNumberCorrespondence | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a RecordAttorney instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with record attorney data.

    +
    +
    Returns:
    +

    An instance of RecordAttorney.

    +
    +
    Return type:
    +

    RecordAttorney

    +
    +
    +
    + +
    +
    +power_of_attorney_bag: List[Attorney]
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the RecordAttorney instance to a dictionary.

    +

    Omits keys with None values. Includes empty lists to match API behavior.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.StatusCode(code=None, description=None)[source]
    +

    Bases: object

    +

    Represents a USPTO application status code and its textual description.

    +
    +
    +code
    +

    The numeric status code.

    +
    + +
    +
    +description
    +

    The textual description of the status code.

    +
    + +
    +
    +__str__()[source]
    +

    Returns a user-friendly string representation of the status code.

    +
    +
    Return type:
    +

    str

    +
    +
    +
    + +
    +
    +code: int | None = None
    +
    + +
    +
    +description: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a StatusCode instance from a dictionary.

    +

    Handles two possible key sets from the API for status information.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with status code data.

    +
    +
    Returns:
    +

    An instance of StatusCode.

    +
    +
    Return type:
    +

    StatusCode

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the StatusCode instance to a dictionary.

    +

    Uses keys “applicationStatusCode” and “applicationStatusDescriptionText” +for consistency with some API response parts.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.StatusCodeCollection(status_codes)[source]
    +

    Bases: object

    +

    A collection of StatusCode objects.

    +

    Provides iterable access and helper methods to find or filter status codes. +This class is immutable by convention after initialization.

    +
    +
    +status_codes
    +

    An immutable tuple of StatusCode objects.

    +
    +
    Type:
    +

    tuple[StatusCode, ]

    +
    +
    +
    + +
    +
    +__init__(status_codes)[source]
    +

    Initializes the StatusCodeCollection with a list of status codes.

    +
    +
    Parameters:
    +

    status_codes (List[StatusCode]) – A list of StatusCode instances.

    +
    +
    +
    + +
    +
    +find_by_code(code_to_find)[source]
    +

    Finds a status code by its numeric code.

    +
    +
    Parameters:
    +

    code_to_find (int) – The numeric status code to search for.

    +
    +
    Returns:
    +

    The StatusCode object if found, otherwise None.

    +
    +
    Return type:
    +

    Optional[StatusCode]

    +
    +
    +
    + +
    +
    +search_by_description(text)[source]
    +

    Searches for status codes by a case-insensitive text match in their description.

    +
    +
    Parameters:
    +

    text (str) – The text to search for within status code descriptions.

    +
    +
    Returns:
    +

    A new collection containing matching status codes.

    +
    +
    Return type:
    +

    StatusCodeCollection

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the collection of status codes to a list of dictionaries.

    +
    +
    Returns:
    +

    +
    A list where each item is the dictionary

    representation of a StatusCode.

    +
    +
    +

    +
    +
    Return type:
    +

    List[Dict[str, Any]]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.StatusCodeSearchResponse(count, status_code_bag, request_identifier=None)[source]
    +

    Bases: object

    +

    Represents the response from a search query for patent application status codes.

    +
    +
    +count
    +

    The total number of status codes found matching the query.

    +
    + +
    +
    +status_code_bag
    +

    A StatusCodeCollection of the StatusCode objects returned.

    +
    + +
    +
    +request_identifier
    +

    An identifier for the API request, if provided.

    +
    + +
    +
    +count: int
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a StatusCodeSearchResponse instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with API response data for status codes.

    +
    +
    Returns:
    +

    An instance of StatusCodeSearchResponse.

    +
    +
    Return type:
    +

    StatusCodeSearchResponse

    +
    +
    +
    + +
    +
    +request_identifier: str | None = None
    +
    + +
    +
    +status_code_bag: StatusCodeCollection
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the StatusCodeSearchResponse instance to a dictionary.

    +

    Omits keys with None values or empty lists.

    +
    +
    Returns:
    +

    Dictionary representation.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.patent_data.Telecommunication(telecommunication_number=None, extension_number=None, telecom_type_code=None)[source]
    +

    Bases: object

    +

    Represents telecommunication details, such as phone or fax numbers.

    +
    +
    +telecommunication_number
    +

    The main number (e.g., phone number).

    +
    + +
    +
    +extension_number
    +

    Any extension associated with the number.

    +
    + +
    +
    +telecom_type_code
    +

    A code indicating the type of telecommunication (e.g., “TEL”, “FAX”).

    +
    + +
    +
    +extension_number: str | None = None
    +
    + +
    +
    +classmethod from_dict(data)[source]
    +

    Creates a Telecommunication instance from a dictionary.

    +
    +
    Parameters:
    +

    data (Dict[str, Any]) – Dictionary with telecommunication data.

    +
    +
    Returns:
    +

    An instance of Telecommunication.

    +
    +
    Return type:
    +

    Telecommunication

    +
    +
    +
    + +
    +
    +telecom_type_code: str | None = None
    +
    + +
    +
    +telecommunication_number: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the Telecommunication instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary representation with camelCase keys.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +

    models.ptab - Data models for USPTO PTAB (Patent Trial and Appeal Board) APIs

    +

    This module provides data models, primarily using frozen dataclasses, for +representing responses from the USPTO PTAB APIs. These models cover: +- Patent trial proceedings (IPR, PGR, CBM, DER) +- Trial documents and decisions +- Appeal decisions +- Interference decisions

    +
    +
    +class pyUSPTO.models.ptab.AdditionalPartyData(application_number_text=None, inventor_name=None, patent_number=None, additional_party_name=None)[source]
    +

    Bases: object

    +

    Additional party information in an interference.

    +
    +
    +application_number_text
    +

    Application number.

    +
    + +
    +
    +inventor_name
    +

    Name of inventor.

    +
    + +
    +
    +patent_number
    +

    Patent number.

    +
    + +
    +
    +additional_party_name
    +

    Name of additional party.

    +
    + +
    +
    +additional_party_name: str | None = None
    +
    + +
    +
    +application_number_text: str | None = None
    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates an AdditionalPartyData instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing additional party data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of AdditionalPartyData.

    +
    +
    Return type:
    +

    AdditionalPartyData

    +
    +
    +
    + +
    +
    +inventor_name: str | None = None
    +
    + +
    +
    +patent_number: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the AdditionalPartyData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.ptab.AppealDocumentData(document_filing_date=None, document_identifier=None, document_name=None, document_size_quantity=None, document_ocr_text=None, document_type_description_text=None, file_download_uri=None)[source]
    +

    Bases: object

    +

    Appeal document metadata.

    +
    +
    +document_filing_date
    +

    Date the document was filed.

    +
    + +
    +
    +document_identifier
    +

    Unique identifier for the document.

    +
    + +
    +
    +document_name
    +

    Name of the document.

    +
    + +
    +
    +document_size_quantity
    +

    Size of the document in bytes.

    +
    + +
    +
    +document_ocr_text
    +

    Full OCR text of the document.

    +
    + +
    +
    +document_type_description_text
    +

    Description of the document type.

    +
    + +
    +
    +file_download_uri
    +

    URI to download the document.

    +
    + +
    +
    +document_filing_date: date | None = None
    +
    + +
    +
    +document_identifier: str | None = None
    +
    + +
    +
    +document_name: str | None = None
    +
    + +
    +
    +document_ocr_text: str | None = None
    +
    + +
    +
    +document_size_quantity: int | None = None
    +
    + +
    +
    +document_type_description_text: str | None = None
    +
    + +
    +
    +file_download_uri: str | None = None
    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates an AppealDocumentData instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing document data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of AppealDocumentData.

    +
    +
    Return type:
    +

    AppealDocumentData

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the AppealDocumentData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.ptab.AppealMetaData(appeal_filing_date=None, appeal_last_modified_date=None, appeal_last_modified_date_time=None, application_type_category=None, docket_notice_mailed_date=None, file_download_uri=None)[source]
    +

    Bases: object

    +

    Appeal metadata.

    +
    +
    +appeal_filing_date
    +

    Date the appeal was filed.

    +
    + +
    +
    +appeal_last_modified_date
    +

    Last modification date.

    +
    + +
    +
    +appeal_last_modified_date_time
    +

    Last modification timestamp.

    +
    + +
    +
    +application_type_category
    +

    Type of application.

    +
    + +
    +
    +docket_notice_mailed_date
    +

    Date the docket notice was mailed.

    +
    + +
    +
    +file_download_uri
    +

    URI to download ZIP of appeal documents.

    +
    + +
    +
    +appeal_filing_date: date | None = None
    +
    + +
    +
    +appeal_last_modified_date: date | None = None
    +
    + +
    +
    +appeal_last_modified_date_time: datetime | None = None
    +
    + +
    +
    +application_type_category: str | None = None
    +
    + +
    +
    +docket_notice_mailed_date: date | None = None
    +
    + +
    +
    +file_download_uri: str | None = None
    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates an AppealMetaData instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing appeal metadata from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of AppealMetaData.

    +
    +
    Return type:
    +

    AppealMetaData

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the AppealMetaData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.ptab.AppellantData(application_number_text=None, counsel_name=None, grant_date=None, group_art_unit_number=None, inventor_name=None, real_party_in_interest_name=None, patent_number=None, patent_owner_name=None, technology_center_number=None, publication_date=None, publication_number=None)[source]
    +

    Bases: PartyData

    +

    Appellant party data in PTAB appeals.

    +

    Inherits all attributes from PartyData. Used in appeal proceedings +to represent the party appealing an examiner decision.

    +
    + +
    +
    +class pyUSPTO.models.ptab.DecisionData(appeal_outcome_category=None, statute_and_rule_bag=<factory>, decision_issue_date=None, decision_type_category=None, issue_type_bag=<factory>)[source]
    +

    Bases: object

    +

    Appeal decision information.

    +
    +
    +appeal_outcome_category
    +

    Outcome of the appeal.

    +
    + +
    +
    +statute_and_rule_bag
    +

    List of applicable statutes and rules.

    +
    + +
    +
    +decision_issue_date
    +

    Date the decision was issued.

    +
    + +
    +
    +decision_type_category
    +

    Type of decision.

    +
    + +
    +
    +issue_type_bag
    +

    List of issue types.

    +
    + +
    +
    +appeal_outcome_category: str | None = None
    -
    -country_name: Optional[str] = None
    +
    +decision_issue_date: date | None = None
    +
    + +
    +
    +decision_type_category: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create an Address object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a DecisionData instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing decision data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of DecisionData.

    +
    Return type:
    -

    Address

    +

    DecisionData

    -
    -geographic_region_code: Optional[str] = None
    +
    +issue_type_bag: List[str]
    -
    -geographic_region_name: Optional[str] = None
    +
    +statute_and_rule_bag: List[str]
    -
    -
    -name_line_one_text: Optional[str] = None
    -
    +
    +
    +to_dict()[source]
    +

    Converts the DecisionData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + +
    + +
    +
    +class pyUSPTO.models.ptab.DerivationPetitionerData(application_number_text=None, counsel_name=None, grant_date=None, group_art_unit_number=None, inventor_name=None, real_party_in_interest_name=None, patent_number=None, patent_owner_name=None, technology_center_number=None, publication_date=None, publication_number=None)[source]
    +

    Bases: PartyData

    +

    Derivation petitioner data in derivation proceedings.

    +

    Inherits all attributes from PartyData. Used in DER proceedings +to represent the petitioning party claiming derivation.

    +
    + +
    +
    +class pyUSPTO.models.ptab.InterferenceDocumentData(document_identifier=None, document_name=None, document_size_quantity=None, document_ocr_text=None, document_title_text=None, interference_outcome_category=None, document_filing_date=None, decision_issue_date=None, decision_type_category=None, file_download_uri=None, statute_and_rule_bag=<factory>, issue_type_bag=<factory>)[source]
    +

    Bases: object

    +

    Interference document metadata.

    +
    +
    +document_identifier
    +

    Unique identifier for the document.

    +
    -
    -name_line_two_text: Optional[str] = None
    -
    +
    +document_name
    +

    Name of the document.

    +
    -
    -postal_address_category: Optional[str] = None
    -
    +
    +document_size_quantity
    +

    Size of the document in bytes.

    +
    -
    -postal_code: Optional[str] = None
    -
    +
    +document_ocr_text
    +

    Full OCR text of the document.

    +
    +
    +
    +document_title_text
    +

    Title of the document.

    -
    -
    -class pyUSPTO.models.patent_data.Applicant(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None, applicant_name_text=None, correspondence_address_bag=<factory>)[source]
    -

    Bases: Person

    -

    Represents an applicant in the patent data.

    -
    -applicant_name_text: Optional[str] = None
    -
    +
    +interference_outcome_category
    +

    Outcome of the interference.

    +
    -
    -correspondence_address_bag: List[Address]
    -
    +
    +document_filing_date
    +

    Date the document was filed.

    +
    -
    -
    -classmethod from_dict(data)[source]
    -

    Create an Applicant object from a dictionary.

    -
    -
    Return type:
    -

    Applicant

    -
    -
    +
    +
    +decision_issue_date
    +

    Date the decision was issued.

    +
    +
    +decision_type_category
    +

    Type of decision.

    -
    -
    -class pyUSPTO.models.patent_data.ApplicationMetaData(national_stage_indicator=None, entity_status_data=None, publication_date_bag=<factory>, publication_sequence_number_bag=<factory>, publication_category_bag=<factory>, docket_number=None, first_inventor_to_file_indicator=None, first_applicant_name=None, first_inventor_name=None, application_confirmation_number=None, application_status_date=None, application_status_description_text=None, filing_date=None, effective_filing_date=None, grant_date=None, group_art_unit_number=None, application_type_code=None, application_type_label_name=None, application_type_category=None, invention_title=None, patent_number=None, application_status_code=None, earliest_publication_number=None, earliest_publication_date=None, pct_publication_number=None, pct_publication_date=None, international_registration_publication_date=None, international_registration_number=None, examiner_name_text=None, class_field=None, subclass=None, uspc_symbol_text=None, customer_number=None, cpc_classification_bag=<factory>, applicant_bag=<factory>, inventor_bag=<factory>)[source]
    -

    Bases: object

    -

    Represents application metadata.

    -
    -applicant_bag: List[Applicant]
    -
    +
    +file_download_uri
    +

    URI to download the document.

    +
    -
    -application_confirmation_number: Optional[int] = None
    -
    +
    +statute_and_rule_bag
    +

    List of applicable statutes and rules.

    +
    -
    -application_status_code: Optional[int] = None
    -
    +
    +issue_type_bag
    +

    List of issues addressed.

    +
    -
    -application_status_date: Optional[str] = None
    +
    +decision_issue_date: date | None = None
    -
    -application_status_description_text: Optional[str] = None
    +
    +decision_type_category: str | None = None
    -
    -application_type_category: Optional[str] = None
    +
    +document_filing_date: date | None = None
    -
    -application_type_code: Optional[str] = None
    +
    +document_identifier: str | None = None
    -
    -application_type_label_name: Optional[str] = None
    +
    +document_name: str | None = None
    -
    -class_field: Optional[str] = None
    +
    +document_ocr_text: str | None = None
    -
    -cpc_classification_bag: List[str]
    +
    +document_size_quantity: int | None = None
    -
    -customer_number: Optional[int] = None
    +
    +document_title_text: str | None = None
    -
    -docket_number: Optional[str] = None
    +
    +file_download_uri: str | None = None
    +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates an InterferenceDocumentData instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing document data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of InterferenceDocumentData.

    +
    +
    Return type:
    +

    InterferenceDocumentData

    +
    +
    +
    +
    -
    -earliest_publication_date: Optional[str] = None
    +
    +interference_outcome_category: str | None = None
    -
    -earliest_publication_number: Optional[str] = None
    +
    +issue_type_bag: List[str]
    -
    -effective_filing_date: Optional[str] = None
    +
    +statute_and_rule_bag: List[str]
    +
    +
    +to_dict()[source]
    +

    Converts the InterferenceDocumentData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + + + +
    +
    +class pyUSPTO.models.ptab.InterferenceMetaData(interference_style_name=None, interference_last_modified_date=None, interference_last_modified_date_time=None, declaration_date=None, file_download_uri=None)[source]
    +

    Bases: object

    +

    Interference metadata.

    +
    +
    +interference_style_name
    +

    Style name of the interference.

    +
    +
    -
    -entity_status_data: Optional[EntityStatus] = None
    -
    +
    +interference_last_modified_date
    +

    Last modification date.

    +
    -
    -examiner_name_text: Optional[str] = None
    -
    +
    +interference_last_modified_date_time
    +

    Last modification datetime.

    +
    -
    -filing_date: Optional[str] = None
    -
    +
    +declaration_date
    +

    Declaration date.

    +
    -
    -first_applicant_name: Optional[str] = None
    -
    +
    +file_download_uri
    +

    URI to download ZIP of interference documents.

    +
    -
    -first_inventor_name: Optional[str] = None
    +
    +declaration_date: date | None = None
    -
    -first_inventor_to_file_indicator: Optional[str] = None
    +
    +file_download_uri: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create an ApplicationMetaData object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates an InterferenceMetaData instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing interference metadata from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of InterferenceMetaData.

    +
    Return type:
    -

    ApplicationMetaData

    +

    InterferenceMetaData

    -
    -grant_date: Optional[str] = None
    +
    +interference_last_modified_date: date | None = None
    -
    -group_art_unit_number: Optional[str] = None
    +
    +interference_last_modified_date_time: datetime | None = None
    -
    -international_registration_number: Optional[str] = None
    +
    +interference_style_name: str | None = None
    +
    +
    +to_dict()[source]
    +

    Converts the InterferenceMetaData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    + + + +
    +
    +class pyUSPTO.models.ptab.JuniorPartyData(application_number_text=None, counsel_name=None, grant_date=None, group_art_unit_number=None, inventor_name=None, real_party_in_interest_name=None, patent_number=None, patent_owner_name=None, technology_center_number=None, publication_date=None, publication_number=None)[source]
    +

    Bases: PartyData

    +

    Junior party information in PTAB interference proceedings.

    +

    Inherits all attributes from PartyData. Represents the party with +the later effective filing date in an interference.

    +
    + +
    +
    +class pyUSPTO.models.ptab.PTABAppealDecision(appeal_number=None, last_modified_date_time=None, appeal_document_category=None, appeal_meta_data=None, appellant_data=None, requestor_data=None, document_data=None, decision_data=None, raw_data=None)[source]
    +

    Bases: object

    +

    Individual PTAB appeal decision record.

    +
    +
    +appeal_number
    +

    Appeal number.

    +
    +
    -
    -international_registration_publication_date: Optional[str] = None
    -
    +
    +last_modified_date_time
    +

    Last modification timestamp.

    +
    -
    -invention_title: Optional[str] = None
    -
    +
    +appeal_document_category
    +

    Document category.

    +
    -
    -inventor_bag: List[Inventor]
    -
    +
    +appeal_meta_data
    +

    Appeal metadata.

    +
    -
    -national_stage_indicator: Optional[bool] = None
    +
    +appellant_data
    +

    Appellant information.

    +
    + +
    +
    +requestor_data
    +

    Third party requestor information.

    +
    + +
    +
    +document_data
    +

    Document metadata.

    +
    + +
    +
    +decision_data
    +

    Decision information.

    +
    + +
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    + +
    +
    +appeal_document_category: str | None = None
    -
    -patent_number: Optional[str] = None
    +
    +appeal_meta_data: AppealMetaData | None = None
    -
    -pct_publication_date: Optional[str] = None
    +
    +appeal_number: str | None = None
    -
    -pct_publication_number: Optional[str] = None
    +
    +appellant_data: AppellantData | None = None
    -
    -publication_category_bag: List[str]
    +
    +decision_data: DecisionData | None = None
    -
    -publication_date_bag: List[str]
    +
    +document_data: AppealDocumentData | None = None
    +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PTABAppealDecision instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing appeal decision data from API response.

    • +
    • include_raw_data (bool) – Whether to include raw JSON data in the instance.

    • +
    +
    +
    Returns:
    +

    An instance of PTABAppealDecision.

    +
    +
    Return type:
    +

    PTABAppealDecision

    +
    +
    +
    +
    -
    -publication_sequence_number_bag: List[str]
    +
    +last_modified_date_time: datetime | None = None
    -
    -subclass: Optional[str] = None
    +
    +raw_data: Dict[str, Any] | None = None
    -
    -uspc_symbol_text: Optional[str] = None
    +
    +requestor_data: RequestorData | None = None
    +
    +
    +to_dict()[source]
    +

    Converts the PTABAppealDecision instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    +
    -
    -class pyUSPTO.models.patent_data.Assignee(assignee_name_text=None, assignee_address=None)[source]
    -

    Bases: object

    -

    Represents an assignee in an assignment.

    +
    +class pyUSPTO.models.ptab.PTABAppealResponse(count=0, request_identifier=None, patent_appeal_data_bag=<factory>, raw_data=None)[source]
    +

    Bases: object

    +

    Response container for PTAB appeals search.

    +
    +
    +count
    +

    Total number of matching results.

    +
    +
    -
    -assignee_address: Optional[Address] = None
    -
    +
    +request_identifier
    +

    UUID for the API request.

    +
    -
    -assignee_name_text: Optional[str] = None
    +
    +patent_appeal_data_bag
    +

    List of appeal decisions.

    +
    + +
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    + +
    +
    +count: int = 0
    -
    -classmethod from_dict(data)[source]
    -

    Create an Assignee object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PTABAppealResponse instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing response data from API.

    • +
    • include_raw_data (bool) – Whether to include raw JSON data in the instance.

    • +
    +
    +
    Returns:
    +

    An instance of PTABAppealResponse.

    +
    Return type:
    -

    Assignee

    +

    PTABAppealResponse

    +
    +
    +
    + +
    +
    +patent_appeal_data_bag: List[PTABAppealDecision]
    +
    + +
    +
    +raw_data: Dict[str, Any] | None = None
    +
    + +
    +
    +request_identifier: str | None = None
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the PTABAppealResponse instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    @@ -953,860 +5725,1394 @@
    -
    -class pyUSPTO.models.patent_data.Assignment(reel_number=None, frame_number=None, reel_and_frame_number=None, assignment_document_location_uri=None, assignment_received_date=None, assignment_recorded_date=None, assignment_mailed_date=None, conveyance_text=None, assignor_bag=<factory>, assignee_bag=<factory>, correspondence_address_bag=<factory>)[source]
    -

    Bases: object

    -

    Represents an assignment in the patent data.

    +
    +class pyUSPTO.models.ptab.PTABInterferenceDecision(interference_number=None, last_modified_date_time=None, interference_meta_data=None, senior_party_data=None, junior_party_data=None, additional_party_data_bag=<factory>, document_data=None, raw_data=None)[source]
    +

    Bases: object

    +

    Individual PTAB interference decision record.

    +
    +
    +interference_number
    +

    Interference number.

    +
    +
    -
    -assignee_bag: List[Assignee]
    -
    +
    +last_modified_date_time
    +

    Last modification timestamp.

    +
    -
    -assignment_document_location_uri: Optional[str] = None
    +
    +interference_meta_data
    +

    Interference metadata.

    +
    + +
    +
    +senior_party_data
    +

    Senior party information.

    +
    + +
    +
    +junior_party_data
    +

    Junior party information.

    +
    + +
    +
    +additional_party_data_bag
    +

    List of additional parties.

    +
    + +
    +
    +document_data
    +

    Document metadata.

    +
    + +
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    + +
    +
    +additional_party_data_bag: List[AdditionalPartyData]
    -
    -assignment_mailed_date: Optional[str] = None
    +
    +document_data: InterferenceDocumentData | None = None
    +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PTABInterferenceDecision instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing interference decision data from API response.

    • +
    • include_raw_data (bool) – Whether to include raw JSON data in the instance.

    • +
    +
    +
    Returns:
    +

    An instance of PTABInterferenceDecision.

    +
    +
    Return type:
    +

    PTABInterferenceDecision

    +
    +
    +
    +
    -
    -assignment_received_date: Optional[str] = None
    +
    +interference_meta_data: InterferenceMetaData | None = None
    -
    -assignment_recorded_date: Optional[str] = None
    +
    +interference_number: str | None = None
    -
    -assignor_bag: List[Assignor]
    +
    +junior_party_data: JuniorPartyData | None = None
    -
    -conveyance_text: Optional[str] = None
    +
    +last_modified_date_time: datetime | None = None
    -
    -correspondence_address_bag: List[Address]
    +
    +raw_data: Dict[str, Any] | None = None
    -
    -frame_number: Optional[int] = None
    +
    +senior_party_data: SeniorPartyData | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create an Assignment object from a dictionary.

    +
    +to_dict()[source]
    +

    Converts the PTABInterferenceDecision instance to a dictionary.

    -
    Return type:
    -

    Assignment

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    -
    -
    -reel_and_frame_number: Optional[str] = None
    -
    + + +
    +
    +class pyUSPTO.models.ptab.PTABInterferenceResponse(count=0, request_identifier=None, patent_interference_data_bag=<factory>, raw_data=None)[source]
    +

    Bases: object

    +

    Response container for PTAB interferences search.

    +
    +
    +count
    +

    Total number of matching results.

    +
    -
    -reel_number: Optional[int] = None
    -
    +
    +request_identifier
    +

    UUID for the API request.

    +
    +
    +
    +patent_interference_data_bag
    +

    List of interference decisions.

    -
    -
    -class pyUSPTO.models.patent_data.Assignor(assignor_name=None, execution_date=None)[source]
    -

    Bases: object

    -

    Represents an assignor in an assignment.

    -
    -assignor_name: Optional[str] = None
    -
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    -
    -execution_date: Optional[str] = None
    +
    +count: int = 0
    -
    -classmethod from_dict(data)[source]
    -

    Create an Assignor object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PTABInterferenceResponse instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing response data from API.

    • +
    • include_raw_data (bool) – Whether to include raw JSON data in the instance.

    • +
    +
    +
    Returns:
    +

    An instance of PTABInterferenceResponse.

    +
    Return type:
    -

    Assignor

    +

    PTABInterferenceResponse

    - +
    +
    +patent_interference_data_bag: List[PTABInterferenceDecision]
    +
    -
    -
    -class pyUSPTO.models.patent_data.Attorney(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None, registration_number=None, active_indicator=None, registered_practitioner_category=None, attorney_address_bag=<factory>, telecommunication_address_bag=<factory>)[source]
    -

    Bases: Person

    -

    Represents an attorney in the patent data.

    -
    -active_indicator: Optional[str] = None
    +
    +raw_data: Dict[str, Any] | None = None
    -
    -attorney_address_bag: List[Address]
    +
    +request_identifier: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create an Attorney object from a dictionary.

    +
    +to_dict()[source]
    +

    Converts the PTABInterferenceResponse instance to a dictionary.

    -
    Return type:
    -

    Attorney

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    + +
    +
    +class pyUSPTO.models.ptab.PTABTrialDocument(trial_document_category=None, last_modified_date_time=None, trial_number=None, trial_type_code=None, trial_meta_data=None, patent_owner_data=None, regular_petitioner_data=None, respondent_data=None, derivation_petitioner_data=None, document_data=None, decision_data=None, raw_data=None)[source]
    +

    Bases: object

    +

    Individual trial document or decision record from PTAB document/decision search APIs.

    +

    Used by search_documents() and search_decisions() endpoints. Contains document-specific +metadata (documentData) or decision information (decisionData), plus trial context. +Differs from PTABTrialProceeding which represents the entire proceeding rather than +individual documents within it.

    +
    +
    +trial_document_category
    +

    Category (Document or Decision).

    +
    +
    -
    -registered_practitioner_category: Optional[str] = None
    -
    +
    +last_modified_date_time
    +

    Last modification timestamp.

    +
    -
    -registration_number: Optional[str] = None
    -
    +
    +trial_number
    +

    Trial number (e.g., “IPR2023-00123”).

    +
    -
    -telecommunication_address_bag: List[Telecommunication]
    -
    +
    +trial_type_code
    +

    Type of trial (IPR, PGR, CBM, DER).

    +
    + +
    +
    +trial_meta_data
    +

    Trial metadata.

    +
    +
    +
    +patent_owner_data
    +

    Patent owner information.

    -
    -
    -class pyUSPTO.models.patent_data.ChildContinuity(first_inventor_to_file_indicator=None, application_number_text=None, filing_date=None, status_code=None, status_description_text=None, patent_number=None, claim_parentage_type_code=None, claim_parentage_type_code_description_text=None, child_application_status_code=None, parent_application_number_text=None, child_application_number_text=None, child_application_status_description_text=None, child_application_filing_date=None, child_patent_number=None)[source]
    -

    Bases: Continuity

    -

    Represents child continuity information.

    -
    -child_application_filing_date: Optional[str] = None
    -
    +
    +regular_petitioner_data
    +

    Regular petitioner information.

    +
    -
    -child_application_number_text: Optional[str] = None
    -
    +
    +respondent_data
    +

    Respondent information.

    +
    -
    -child_application_status_code: Optional[int] = None
    +
    +derivation_petitioner_data
    +

    Derivation petitioner information.

    +
    + +
    +
    +document_data
    +

    Document metadata (if document).

    +
    + +
    +
    +decision_data
    +

    Decision information (if decision).

    +
    + +
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    + +
    +
    +decision_data: TrialDecisionData | None = None
    -
    -child_application_status_description_text: Optional[str] = None
    +
    +derivation_petitioner_data: DerivationPetitionerData | None = None
    -
    -child_patent_number: Optional[str] = None
    +
    +document_data: TrialDocumentData | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a ChildContinuity object from a dictionary.

    -
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +
    Return type:
    -

    ChildContinuity

    +

    PTABTrialDocument

    -
    -parent_application_number_text: Optional[str] = None
    +
    +last_modified_date_time: datetime | None = None
    -
    - -
    -
    -class pyUSPTO.models.patent_data.Continuity(first_inventor_to_file_indicator=None, application_number_text=None, filing_date=None, status_code=None, status_description_text=None, patent_number=None, claim_parentage_type_code=None, claim_parentage_type_code_description_text=None)[source]
    -

    Bases: object

    -

    Base class for continuity information.

    -
    -application_number_text: Optional[str] = None
    +
    +patent_owner_data: PatentOwnerData | None = None
    -
    -claim_parentage_type_code: Optional[str] = None
    +
    +raw_data: Dict[str, Any] | None = None
    -
    -claim_parentage_type_code_description_text: Optional[str] = None
    +
    +regular_petitioner_data: RegularPetitionerData | None = None
    -
    -filing_date: Optional[str] = None
    +
    +respondent_data: RespondentData | None = None
    +
    +
    +to_dict()[source]
    +

    Converts the PTABTrialDocument instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    +
    -
    -first_inventor_to_file_indicator: Optional[bool] = None
    +
    +trial_document_category: str | None = None
    -
    -patent_number: Optional[str] = None
    +
    +trial_meta_data: TrialMetaData | None = None
    -
    -status_code: Optional[int] = None
    +
    +trial_number: str | None = None
    -
    -status_description_text: Optional[str] = None
    +
    +trial_type_code: str | None = None
    -
    -class pyUSPTO.models.patent_data.CustomerNumberCorrespondence(patron_identifier=None, organization_standard_name=None, power_of_attorney_address_bag=<factory>, telecommunication_address_bag=<factory>)[source]
    -

    Bases: object

    -

    Represents customer number correspondence data.

    +
    +class pyUSPTO.models.ptab.PTABTrialDocumentResponse(count=0, request_identifier=None, patent_trial_document_data_bag=<factory>, raw_data=None)[source]
    +

    Bases: object

    +

    Response container for PTAB trial documents/decisions search.

    +
    +
    +count: int = 0
    +
    +
    -
    -classmethod from_dict(data)[source]
    -

    Create a CustomerNumberCorrespondence object from a dictionary.

    -
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +
    Return type:
    -

    CustomerNumberCorrespondence

    +

    PTABTrialDocumentResponse

    -
    -organization_standard_name: Optional[str] = None
    +
    +patent_trial_document_data_bag: List[PTABTrialDocument]
    -
    -patron_identifier: Optional[int] = None
    +
    +raw_data: Dict[str, Any] | None = None
    -
    -power_of_attorney_address_bag: List[Address]
    +
    +request_identifier: str | None = None
    -
    -
    -telecommunication_address_bag: List[Telecommunication]
    -
    +
    +
    +to_dict()[source]
    +

    Converts the PTABTrialDocumentResponse instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    -
    -class pyUSPTO.models.patent_data.DocumentMetaData(zip_file_name=None, product_identifier=None, file_location_uri=None, file_create_date_time=None, xml_file_name=None)[source]
    -

    Bases: object

    -

    Represents document metadata.

    +
    +class pyUSPTO.models.ptab.PTABTrialProceeding(trial_number=None, last_modified_date_time=None, trial_meta_data=None, patent_owner_data=None, regular_petitioner_data=None, respondent_data=None, derivation_petitioner_data=None, raw_data=None)[source]
    +

    Bases: object

    +

    Individual PTAB trial proceeding record.

    +
    +
    +trial_number
    +

    Trial number (e.g., “IPR2023-00123”).

    +
    +
    -
    -file_create_date_time: Optional[str] = None
    -
    +
    +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.

    +
    + +
    +
    +regular_petitioner_data
    +

    Regular petitioner information.

    +
    + +
    +
    +respondent_data
    +

    Respondent information.

    +
    + +
    +
    +derivation_petitioner_data
    +

    Derivation petitioner information.

    +
    + +
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    -
    -file_location_uri: Optional[str] = None
    +
    +derivation_petitioner_data: DerivationPetitionerData | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a DocumentMetaData object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PTABTrialProceeding instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing trial proceeding data from API response.

    • +
    • include_raw_data (bool) – Whether to include raw JSON data in the instance.

    • +
    +
    +
    Returns:
    +

    An instance of PTABTrialProceeding.

    +
    Return type:
    -

    DocumentMetaData

    +

    PTABTrialProceeding

    -
    -product_identifier: Optional[str] = None
    +
    +last_modified_date_time: datetime | None = None
    -
    -xml_file_name: Optional[str] = None
    +
    +patent_owner_data: PatentOwnerData | None = None
    -
    -zip_file_name: Optional[str] = None
    +
    +raw_data: Dict[str, Any] | None = None
    -
    +
    +
    +regular_petitioner_data: RegularPetitionerData | None = None
    +
    -
    -
    -class pyUSPTO.models.patent_data.EntityStatus(small_entity_status_indicator=None, business_entity_status_category=None)[source]
    -

    Bases: object

    -

    Represents entity status data.

    -
    -business_entity_status_category: Optional[str] = None
    +
    +respondent_data: RespondentData | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create an EntityStatus object from a dictionary.

    +
    +to_dict()[source]
    +

    Converts the PTABTrialProceeding instance to a dictionary.

    -
    Return type:
    -

    EntityStatus

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    -
    -small_entity_status_indicator: Optional[bool] = None
    +
    +trial_meta_data: TrialMetaData | None = None
    +
    + +
    +
    +trial_number: str | None = None
    -
    -class pyUSPTO.models.patent_data.Event(event_code=None, event_description_text=None, event_date=None)[source]
    -

    Bases: object

    -

    Represents an event in the patent data.

    +
    +class pyUSPTO.models.ptab.PTABTrialProceedingResponse(count=0, request_identifier=None, patent_trial_proceeding_data_bag=<factory>, raw_data=None)[source]
    +

    Bases: object

    +

    Response container for PTAB trial proceedings search.

    +
    +
    +count
    +

    Total number of matching results.

    +
    +
    -
    -event_code: Optional[str] = None
    -
    +
    +request_identifier
    +

    UUID for the API request.

    +
    -
    -event_date: Optional[str] = None
    -
    +
    +patent_trial_proceeding_data_bag
    +

    List of trial proceedings.

    +
    + +
    +
    +raw_data
    +

    Raw JSON response data (if include_raw_data=True).

    +
    -
    -event_description_text: Optional[str] = None
    +
    +count: int = 0
    -
    -classmethod from_dict(data)[source]
    -

    Create an Event object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a PTABTrialProceedingResponse instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing response data from API.

    • +
    • include_raw_data (bool) – Whether to include raw JSON data in the instance.

    • +
    +
    +
    Returns:
    +

    An instance of PTABTrialProceedingResponse.

    +
    Return type:
    -

    Event

    +

    PTABTrialProceedingResponse

    - +
    +
    +patent_trial_proceeding_data_bag: List[PTABTrialProceeding]
    +
    -
    -
    -class pyUSPTO.models.patent_data.ForeignPriority(ip_office_name=None, filing_date=None, application_number_text=None)[source]
    -

    Bases: object

    -

    Represents foreign priority information.

    -
    -application_number_text: Optional[str] = None
    +
    +raw_data: Dict[str, Any] | None = None
    -
    -filing_date: Optional[str] = None
    +
    +request_identifier: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a ForeignPriority object from a dictionary.

    +
    +to_dict()[source]
    +

    Converts the PTABTrialProceedingResponse instance to a dictionary.

    -
    Return type:
    -

    ForeignPriority

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    -
    -
    -ip_office_name: Optional[str] = None
    -
    +
    +
    +
    +class pyUSPTO.models.ptab.PatentOwnerData(application_number_text=None, counsel_name=None, grant_date=None, group_art_unit_number=None, inventor_name=None, real_party_in_interest_name=None, patent_number=None, patent_owner_name=None, technology_center_number=None, publication_date=None, publication_number=None)[source]
    +

    Bases: PartyData

    +

    Party data for a patent owner in PTAB trial proceedings.

    +

    Inherits all attributes from PartyData. Used in IPR, PGR, CBM, +and DER proceedings to represent the patent holder.

    -
    -class pyUSPTO.models.patent_data.Inventor(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None, inventor_name_text=None, correspondence_address_bag=<factory>)[source]
    -

    Bases: Person

    -

    Represents an inventor in the patent data.

    +
    +class pyUSPTO.models.ptab.RegularPetitionerData(counsel_name=None, real_party_in_interest_name=None)[source]
    +

    Bases: object

    +

    Regular petitioner information.

    +
    +
    +counsel_name
    +

    Name of counsel.

    +
    +
    -
    -correspondence_address_bag: List[Address]
    +
    +real_party_in_interest_name
    +

    Real party in interest name.

    +
    + +
    +
    +counsel_name: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create an Inventor object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a RegularPetitionerData instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing petitioner data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of RegularPetitionerData.

    +
    Return type:
    -

    Inventor

    +

    RegularPetitionerData

    -
    -inventor_name_text: Optional[str] = None
    +
    +real_party_in_interest_name: str | None = None
    +
    +
    +to_dict()[source]
    +

    Converts the RegularPetitionerData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +
    +
    -
    -class pyUSPTO.models.patent_data.ParentContinuity(first_inventor_to_file_indicator=None, application_number_text=None, filing_date=None, status_code=None, status_description_text=None, patent_number=None, claim_parentage_type_code=None, claim_parentage_type_code_description_text=None, parent_application_status_code=None, parent_patent_number=None, parent_application_status_description_text=None, parent_application_filing_date=None, parent_application_number_text=None, child_application_number_text=None)[source]
    -

    Bases: Continuity

    -

    Represents parent continuity information.

    +
    +class pyUSPTO.models.ptab.RequestorData(third_party_name=None)[source]
    +

    Bases: object

    +

    Third party requestor information.

    +
    +
    +third_party_name
    +

    Name of the third party.

    +
    + +
    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a RequestorData instance from a dictionary.

    +
    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing requestor data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of RequestorData.

    +
    +
    Return type:
    +

    RequestorData

    +
    +
    +
    +
    -
    -child_application_number_text: Optional[str] = None
    +
    +third_party_name: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a ParentContinuity object from a dictionary.

    +
    +to_dict()[source]
    +

    Converts the RequestorData instance to a dictionary.

    -
    Return type:
    -

    ParentContinuity

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    + +
    +
    +class pyUSPTO.models.ptab.RespondentData(application_number_text=None, counsel_name=None, grant_date=None, group_art_unit_number=None, inventor_name=None, real_party_in_interest_name=None, patent_number=None, patent_owner_name=None, technology_center_number=None, publication_date=None, publication_number=None)[source]
    +

    Bases: PartyData

    +

    Respondent party data in derivation proceedings.

    +

    Inherits all attributes from PartyData. Used in DER proceedings +to represent the responding party.

    +
    + +
    +
    +class pyUSPTO.models.ptab.SeniorPartyData(application_number_text=None, counsel_name=None, grant_date=None, group_art_unit_number=None, inventor_name=None, real_party_in_interest_name=None, patent_number=None, patent_owner_name=None, technology_center_number=None, publication_date=None, publication_number=None)[source]
    +

    Bases: PartyData

    +

    Senior party information in PTAB interference proceedings.

    +

    Inherits all attributes from PartyData. Represents the party with +the earlier effective filing date in an interference.

    +
    + +
    +
    +class pyUSPTO.models.ptab.TrialDecisionData(statute_and_rule_bag=<factory>, decision_issue_date=None, decision_type_category=None, issue_type_bag=<factory>, trial_outcome_category=None)[source]
    +

    Bases: object

    +

    Metadata for a decision in a PTAB trial.

    +
    +
    +statute_and_rule_bag
    +

    List of applicable statutes and rules.

    +
    +
    -
    -parent_application_filing_date: Optional[str] = None
    -
    +
    +decision_issue_date
    +

    Date issued.

    +
    -
    -parent_application_number_text: Optional[str] = None
    -
    +
    +decision_type_category
    +

    Type of decision (e.g. “Final Written Decision”).

    +
    -
    -parent_application_status_code: Optional[int] = None
    -
    +
    +issue_type_bag
    +

    List of issues addressed.

    +
    -
    -parent_application_status_description_text: Optional[str] = None
    -
    +
    +trial_outcome_category
    +

    Outcome (e.g., “Denied”).

    +
    -
    -parent_patent_number: Optional[str] = None
    +
    +decision_issue_date: date | None = None
    - - -
    -
    -class pyUSPTO.models.patent_data.PatentDataResponse(count, patent_file_wrapper_data_bag)[source]
    -

    Bases: object

    -

    Top-level response from the patent data API.

    -
    -count: int
    +
    +decision_type_category: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a PatentDataResponse object from a dictionary.

    -
    +
    +classmethod from_dict(data)[source]
    +
    Return type:
    -

    PatentDataResponse

    +

    TrialDecisionData

    -
    -patent_file_wrapper_data_bag: List[PatentFileWrapper]
    +
    +issue_type_bag: List[str]
    +
    + +
    +
    +statute_and_rule_bag: List[str]
    -
    -to_dict()[source]
    -

    Convert the PatentDataResponse object to a dictionary.

    +
    +to_dict()[source]
    +

    Converts the TrialDecisionData instance to a dictionary.

    -
    Return type:
    -

    Dict[str, Any]

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    +
    +trial_outcome_category: str | None = None
    +
    +
    -
    -class pyUSPTO.models.patent_data.PatentFileWrapper(application_number_text=None, application_meta_data=None, correspondence_address_bag=<factory>, assignment_bag=<factory>, record_attorney=None, foreign_priority_bag=<factory>, parent_continuity_bag=<factory>, child_continuity_bag=<factory>, patent_term_adjustment_data=None, event_data_bag=<factory>, pgpub_document_meta_data=None, grant_document_meta_data=None, last_ingestion_date_time=None)[source]
    -

    Bases: object

    -

    Represents a patent file wrapper.

    +
    +class pyUSPTO.models.ptab.TrialDocumentData(document_filing_date=None, document_identifier=None, document_name=None, document_number=None, document_size_quantity=None, document_ocr_text=None, document_title_text=None, document_type_description_text=None, file_download_uri=None, filing_party_category=None)[source]
    +

    Bases: object

    +

    Metadata for a document in a PTAB trial.

    +
    +
    +document_category
    +

    Category of the document.

    +
    +
    -
    -application_meta_data: Optional[ApplicationMetaData] = None
    -
    +
    +document_filing_date
    +

    Filing date.

    +
    -
    -application_number_text: Optional[str] = None
    -
    +
    +document_identifier
    +

    Unique ID.

    +
    -
    -assignment_bag: List[Assignment]
    -
    +
    +document_name
    +

    Filename.

    +
    -
    -child_continuity_bag: List[ChildContinuity]
    -
    +
    +document_number
    +

    Document number in the proceeding.

    +
    -
    -correspondence_address_bag: List[Address]
    -
    +
    +document_size_quantity
    +

    Size in bytes.

    +
    -
    -event_data_bag: List[Event]
    -
    +
    +document_ocr_text
    +

    OCR text content.

    +
    -
    -foreign_priority_bag: List[ForeignPriority]
    -
    +
    +document_title_text
    +

    Title of the document.

    +
    -
    -
    -classmethod from_dict(data)[source]
    -

    Create a PatentFileWrapper object from a dictionary.

    -
    -
    Return type:
    -

    PatentFileWrapper

    -
    -
    +
    +
    +document_type_description_text
    +

    Description of document type.

    -
    -grant_document_meta_data: Optional[DocumentMetaData] = None
    -
    +
    +file_download_uri
    +

    URL to download the file.

    +
    -
    -last_ingestion_date_time: Optional[str] = None
    -
    +
    +filing_party_category
    +

    Who filed (e.g., “Petitioner”).

    +
    -
    -parent_continuity_bag: List[ParentContinuity]
    -
    +
    +mime_type_identifier
    +

    MIME type (e.g., “application/pdf”).

    +
    -
    -patent_term_adjustment_data: Optional[PatentTermAdjustmentData] = None
    +
    +document_status
    +

    Public status.

    +
    + +
    +
    +document_filing_date: date | None = None
    -
    -pgpub_document_meta_data: Optional[DocumentMetaData] = None
    +
    +document_identifier: str | None = None
    -
    -record_attorney: Optional[RecordAttorney] = None
    +
    +document_name: str | None = None
    - +
    +
    +document_number: str | None = None
    +
    -
    -
    -class pyUSPTO.models.patent_data.PatentTermAdjustmentData(a_delay_quantity=None, adjustment_total_quantity=None, applicant_day_delay_quantity=None, b_delay_quantity=None, c_delay_quantity=None, filing_date=None, grant_date=None, non_overlapping_day_quantity=None, overlapping_day_quantity=None, ip_office_day_delay_quantity=None, patent_term_adjustment_history_data_bag=<factory>)[source]
    -

    Bases: object

    -

    Represents patent term adjustment data.

    -
    -a_delay_quantity: Optional[float] = None
    +
    +document_ocr_text: str | None = None
    -
    -adjustment_total_quantity: Optional[float] = None
    +
    +document_size_quantity: int | None = None
    -
    -applicant_day_delay_quantity: Optional[float] = None
    +
    +document_title_text: str | None = None
    -
    -b_delay_quantity: Optional[float] = None
    +
    +document_type_description_text: str | None = None
    -
    -c_delay_quantity: Optional[float] = None
    +
    +file_download_uri: str | None = None
    -
    -filing_date: Optional[str] = None
    +
    +filing_party_category: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a PatentTermAdjustmentData object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a TrialDocumentData instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing document data from API response.

    • +
    • include_raw_data (bool) – Ignored for this model.

    • +
    +
    +
    Returns:
    +

    An instance of TrialDocumentData.

    +
    Return type:
    -

    PatentTermAdjustmentData

    +

    TrialDocumentData

    +
    +
    +
    + +
    +
    +to_dict()[source]
    +

    Converts the TrialDocumentData instance to a dictionary.

    +
    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    +
    + +
    +
    +class pyUSPTO.models.ptab.TrialMetaData(petition_filing_date=None, accorded_filing_date=None, trial_last_modified_date_time=None, trial_last_modified_date=None, trial_status_category=None, trial_type_code=None, file_download_uri=None, termination_date=None, latest_decision_date=None, institution_decision_date=None)[source]
    +

    Bases: object

    +

    Trial metadata including status, dates, and download URI.

    +
    +
    +petition_filing_date
    +

    Date the petition was filed.

    +
    +
    -
    -grant_date: Optional[str] = None
    -
    +
    +accorded_filing_date
    +

    The filing date accorded to the petition.

    +
    -
    -ip_office_day_delay_quantity: Optional[float] = None
    -
    +
    +trial_last_modified_date_time
    +

    Last modification timestamp.

    +
    -
    -non_overlapping_day_quantity: Optional[float] = None
    -
    +
    +trial_last_modified_date
    +

    Last modification date.

    +
    -
    -overlapping_day_quantity: Optional[float] = None
    -
    +
    +trial_status_category
    +

    Status of the trial (e.g., “Institution Denied”, “Instituted”).

    +
    -
    -patent_term_adjustment_history_data_bag: List[PatentTermAdjustmentHistoryData]
    -
    +
    +trial_type_code
    +

    Type of trial (IPR, PGR, CBM, DER).

    +
    +
    +
    +file_download_uri
    +

    URI to download ZIP of all trial documents.

    -
    -
    -class pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData(event_date=None, applicant_day_delay_quantity=None, event_description_text=None, event_sequence_number=None, ip_office_day_delay_quantity=None, originating_event_sequence_number=None, pta_pte_code=None)[source]
    -

    Bases: object

    -

    Represents patent term adjustment history data.

    -
    -applicant_day_delay_quantity: Optional[float] = None
    -
    +
    +termination_date
    +

    Date the trial was terminated.

    +
    -
    -event_date: Optional[str] = None
    -
    +
    +latest_decision_date
    +

    Date of the most recent decision.

    +
    -
    -event_description_text: Optional[str] = None
    +
    +institution_decision_date
    +

    Date of the institution decision.

    +
    + +
    +
    +accorded_filing_date: date | None = None
    -
    -event_sequence_number: Optional[float] = None
    +
    +file_download_uri: str | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a PatentTermAdjustmentHistoryData object from a dictionary.

    +
    +classmethod from_dict(data, include_raw_data=False)[source]
    +

    Creates a TrialMetaData instance from a dictionary.

    +
    Parameters:
    +
      +
    • data (Dict[str, Any]) – Dictionary containing trial metadata from API response.

    • +
    • include_raw_data (bool) – Ignored for this model (no raw_data field).

    • +
    +
    +
    Returns:
    +

    An instance of TrialMetaData.

    +
    Return type:
    -

    PatentTermAdjustmentHistoryData

    +

    TrialMetaData

    -
    -ip_office_day_delay_quantity: Optional[float] = None
    -
    - -
    -
    -originating_event_sequence_number: Optional[float] = None
    +
    +institution_decision_date: date | None = None
    -
    -pta_pte_code: Optional[str] = None
    +
    +latest_decision_date: date | None = None
    - - -
    -
    -class pyUSPTO.models.patent_data.Person(first_name=None, middle_name=None, last_name=None, name_prefix=None, name_suffix=None, preferred_name=None, country_code=None)[source]
    -

    Bases: object

    -

    Base class for person-related data.

    -
    -country_code: Optional[str] = None
    +
    +petition_filing_date: date | None = None
    -
    -first_name: Optional[str] = None
    +
    +termination_date: date | None = None
    -
    -classmethod from_dict(data)[source]
    -

    Create a Person object from a dictionary.

    +
    +to_dict()[source]
    +

    Converts the TrialMetaData instance to a dictionary.

    -
    Return type:
    -

    Person

    +
    Returns:
    +

    Dictionary with camelCase keys and None values filtered.

    +
    +
    Return type:
    +

    Dict[str, Any]

    -
    -last_name: Optional[str] = None
    -
    - -
    -
    -middle_name: Optional[str] = None
    +
    +trial_last_modified_date: date | None = None
    -
    -name_prefix: Optional[str] = None
    +
    +trial_last_modified_date_time: datetime | None = None
    -
    -name_suffix: Optional[str] = None
    +
    +trial_status_category: str | None = None
    -
    -preferred_name: Optional[str] = None
    +
    +trial_type_code: str | None = None
    -
    -
    -class pyUSPTO.models.patent_data.RecordAttorney(customer_number_correspondence_data=<factory>, power_of_attorney_bag=<factory>, attorney_bag=<factory>)[source]
    -

    Bases: object

    -

    Represents record attorney data.

    -
    -
    -attorney_bag: List[Attorney]
    -
    - -
    -
    -customer_number_correspondence_data: List[CustomerNumberCorrespondence]
    -
    - -
    -
    -classmethod from_dict(data)[source]
    -

    Create a RecordAttorney object from a dictionary.

    +

    models.utils - Utility functions for USPTO data models

    +

    This module provides utility functions for parsing, serializing, and converting +data used across USPTO API data models. These utilities handle date/datetime +conversions, boolean string representations, and string transformations.

    +
    +
    +pyUSPTO.models.utils.parse_to_date(date_str, fmt='%Y-%m-%d')[source]
    +

    Parses a string representation of a date into a date object.

    +
    Parameters:
    +
      +
    • date_str (Optional[str]) – The string to parse as a date.

    • +
    • fmt (str, optional) – The expected strptime format string for parsing +the date. Defaults to “%Y-%m-%d”.

    • +
    +
    +
    Returns:
    +

    +
    A date object if parsing is successful and date_str

    is not None. Returns None if date_str is None or if parsing fails.

    +
    +
    +

    +
    Return type:
    -

    RecordAttorney

    +

    Optional[date]

    +
    +
    Warns:
    +

    USPTODateParseWarning – If the date string cannot be parsed.

    -
    -
    -power_of_attorney_bag: List[Attorney]
    -
    +
    +
    +pyUSPTO.models.utils.parse_to_datetime_utc(datetime_str)[source]
    +

    Parses a string representation of a datetime into a UTC datetime object.

    +

    Attempts to parse ISO format strings. If the input string contains timezone +information, it’s used. If the string is a naive datetime (no timezone), +it’s assumed to be in the ASSUMED_NAIVE_TIMEZONE_STR (e.g., “America/New_York”) +and then converted to UTC.

    +
    +
    Parameters:
    +

    datetime_str (Optional[str]) – The string to parse as a datetime. +Supports ISO 8601 format, including those ending with “Z”.

    +
    +
    Returns:
    +

    +
    A timezone-aware datetime object in UTC if parsing

    is successful and datetime_str is not None. Returns None if +datetime_str is None or if parsing/conversion fails.

    +
    +
    +

    +
    +
    Return type:
    +

    Optional[datetime]

    +
    +
    Warns:
    +
      +
    • USPTODateParseWarning – If the datetime string cannot be parsed.

    • +
    • USPTOTimezoneWarning – If timezone localization fails.

    • +
    +
    +
    +
    +
    +
    +pyUSPTO.models.utils.parse_yn_to_bool(value)[source]
    +

    Converts a ‘Y’/’N’ (case-insensitive) string to a boolean.

    +
    +
    Parameters:
    +

    value (Optional[str]) – The string value to convert. Expected to be +‘Y’, ‘y’, ‘N’, or ‘n’.

    +
    +
    Returns:
    +

    +
    True if value is ‘Y’ or ‘y’, False if value is

    ’N’ or ‘n’. Returns None if value is None or any other string.

    +
    +
    +

    +
    +
    Return type:
    +

    Optional[bool]

    +
    +
    Warns:
    +

    USPTOBooleanParseWarning – If the value is not ‘Y’ or ‘N’.

    +
    +
    -
    -
    -class pyUSPTO.models.patent_data.Telecommunication(telecommunication_number=None, extension_number=None, telecom_type_code=None)[source]
    -

    Bases: object

    -

    Represents telecommunication information.

    -
    -
    -extension_number: Optional[str] = None
    -
    +
    +
    +pyUSPTO.models.utils.serialize_bool_to_yn(value)[source]
    +

    Converts a boolean value to its ‘Y’/’N’ string representation.

    +
    +
    Parameters:
    +

    value (Optional[bool]) – The boolean value to convert.

    +
    +
    Returns:
    +

    +
    “Y” if value is True, “N” if value is False.

    Returns None if value is None.

    +
    +
    +

    +
    +
    Return type:
    +

    Optional[str]

    +
    +
    +
    -
    -
    -classmethod from_dict(data)[source]
    -

    Create a Telecommunication object from a dictionary.

    +
    +
    +pyUSPTO.models.utils.serialize_date(d)[source]
    +

    Serializes a date object into an ISO 8601 string (YYYY-MM-DD).

    +
    Parameters:
    +

    d (Optional[date]) – The date object to serialize.

    +
    +
    Returns:
    +

    +
    The date as an ISO 8601 formatted string, or None

    if the input is None.

    +
    +
    +

    +
    Return type:
    -

    Telecommunication

    +

    Optional[str]

    -
    -
    -telecom_type_code: Optional[str] = None
    -
    +
    +
    +pyUSPTO.models.utils.serialize_datetime_as_iso(dt)[source]
    +

    Serializes a datetime object to a local-timezone ISO 8601 string.

    +

    If the input datetime object is timezone-aware, it is converted to the +assumed local timezone defined by ASSUMED_NAIVE_TIMEZONE. +If it is naive (lacks timezone information), it is first assigned that +assumed local timezone.

    +
    +
    The resulting datetime is formatted as:

    YYYY-MM-DDTHH:MM:SS.000±HHMM

    +
    +
    +

    (e.g., “2024-12-10T00:00:00.000-0500”)

    +
    +
    Parameters:
    +

    dt (Optional[datetime]) – The datetime object to serialize. +Can be naive or timezone-aware.

    +
    +
    Returns:
    +

    +
    The datetime formatted in the assumed local timezone,

    or None if the input dt is None.

    +
    +
    +

    +
    +
    Return type:
    +

    Optional[str]

    +
    +
    +
    -
    -
    -telecommunication_number: Optional[str] = None
    -
    +
    +
    +pyUSPTO.models.utils.serialize_datetime_as_naive(dt)[source]
    +
    +
    Return type:
    +

    str

    +
    +
    +
    +
    +
    +pyUSPTO.models.utils.to_camel_case(snake_str)[source]
    +

    Converts a snake_case string to lowerCamelCase.

    +

    For example, “example_snake_string” becomes “exampleSnakeString”.

    +
    +
    Parameters:
    +

    snake_str (str) – The input string in snake_case.

    +
    +
    Returns:
    +

    The converted string in lowerCamelCase.

    +
    +
    Return type:
    +

    str

    +
    +
    @@ -1822,7 +7128,7 @@
    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/api/warnings.html b/docs/build/html/api/warnings.html new file mode 100644 index 0000000..74c1aff --- /dev/null +++ b/docs/build/html/api/warnings.html @@ -0,0 +1,202 @@ + + + + + + + + + Warnings — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Warnings

    +

    warnings - Warning classes for pyUSPTO data parsing issues

    +

    This module defines custom warning categories for different types of +data parsing issues encountered when working with USPTO API responses. +These warnings follow Python’s standard warning framework and can be +controlled using warnings.filterwarnings().

    +
    +

    Example

    +

    # Suppress all pyUSPTO data warnings +import warnings +from pyUSPTO.warnings import USPTODataWarning +warnings.filterwarnings(‘ignore’, category=USPTODataWarning)

    +

    # Turn specific warnings into errors (strict mode) +warnings.filterwarnings(‘error’, category=USPTODateParseWarning)

    +
    +
    +
    +exception pyUSPTO.warnings.USPTOBooleanParseWarning[source]
    +

    Bases: USPTODataWarning

    +

    Warning for Y/N boolean string parsing failures.

    +

    Raised when a string that should be ‘Y’ or ‘N’ has an unexpected +value. The field will be set to None.

    +
    + +
    +
    +exception pyUSPTO.warnings.USPTODataMismatchWarning[source]
    +

    Bases: USPTODataWarning

    +

    Warning for data validation mismatches.

    +

    Raised when the API returns data that doesn’t match the requested +identifier (e.g., requesting application 12345678 but receiving 87654321). +This indicates a potential API inconsistency or data integrity issue.

    +
    + +
    +
    +exception pyUSPTO.warnings.USPTODataWarning[source]
    +

    Bases: UserWarning

    +

    Base warning class for USPTO data parsing issues.

    +

    All pyUSPTO data-related warnings inherit from this class, +allowing users to filter all data warnings at once.

    +
    + +
    +
    +exception pyUSPTO.warnings.USPTODateParseWarning[source]
    +

    Bases: USPTODataWarning

    +

    Warning for date/datetime string parsing failures.

    +

    Raised when a date or datetime string from the API cannot be +parsed into a Python date/datetime object. The field will be +set to None.

    +
    + +
    +
    +exception pyUSPTO.warnings.USPTOEnumParseWarning[source]
    +

    Bases: USPTODataWarning

    +

    Warning for enum value parsing failures.

    +

    Raised when an API response contains a value that doesn’t match +any defined enum member. The field will be set to None.

    +
    + +
    +
    +exception pyUSPTO.warnings.USPTOTimezoneWarning[source]
    +

    Bases: USPTODataWarning

    +

    Warning for timezone-related issues.

    +

    Raised when timezone data is not available or timezone conversion +fails. Falls back to UTC timezone.

    +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/build/html/development.html b/docs/build/html/development.html index 5de1f26..4686953 100644 --- a/docs/build/html/development.html +++ b/docs/build/html/development.html @@ -6,7 +6,7 @@ - Development — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Development — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -104,13 +104,13 @@

    Testing

    Built with Sphinx using a diff --git a/docs/build/html/examples/ifw_example.html b/docs/build/html/examples/ifw_example.html new file mode 100644 index 0000000..0f41865 --- /dev/null +++ b/docs/build/html/examples/ifw_example.html @@ -0,0 +1,204 @@ + + + + + + + + + Image File Wrapper Example — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Image File Wrapper Example

    +
     1"""
    + 2Example usage of pyUSPTO for IFW data
    + 3
    + 4This example demonstrates how to use the PatentDataClient to interact with the USPTO Patent Data API.
    + 5It shows how to retrieve IFW based on various identifying values.
    + 6"""
    + 7
    + 8import os
    + 9from multiprocessing import Value
    +10
    +11from pyUSPTO.clients.patent_data import PatentDataClient
    +12
    +13api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE")
    +14if api_key == "YOUR_API_KEY_HERE":
    +15    raise ValueError(
    +16        "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable."
    +17    )
    +18
    +19client = PatentDataClient(api_key=api_key)
    +20
    +21
    +22print("\nBeginning API requests with configured client:")
    +23
    +24print("\nGet IFW Based on Application Number ->")
    +25app_no_ifw = client.get_IFW_metadata(application_number="14412875")
    +26if app_no_ifw and app_no_ifw.application_meta_data:
    +27    print(app_no_ifw.application_meta_data.invention_title)
    +28    print(" - IFW Found based on App No")
    +29
    +30
    +31print("\nGet IFW Based on Patent Number ->")
    +32pat_no_ifw = client.get_IFW_metadata(patent_number="10765880")
    +33if pat_no_ifw and pat_no_ifw.application_meta_data:
    +34    print(pat_no_ifw.application_meta_data.invention_title)
    +35    print(" - IFW Found based on Pat No")
    +36
    +37
    +38print("\nGet IFW Based on Publication Number ->")
    +39pub_no_ifw = client.get_IFW_metadata(publication_number="*20150157873*")
    +40if pub_no_ifw and pub_no_ifw.application_meta_data:
    +41    print(pub_no_ifw.application_meta_data.invention_title)
    +42    print(" - IFW Found based on Pub No")
    +43
    +44
    +45print("\nGet IFW Based on PCT App Number ->")
    +46pct_app_no_ifw = client.get_IFW_metadata(PCT_app_number="PCTUS0812705")
    +47if pct_app_no_ifw and pct_app_no_ifw.application_meta_data:
    +48    print(pct_app_no_ifw.application_meta_data.invention_title)
    +49    print(" - IFW Found based on PCT App No")
    +50
    +51
    +52print("\nGet IFW Based on PCT Pub Number ->")
    +53pct_pub_no_ifw = client.get_IFW_metadata(PCT_pub_number="*2009064413*")
    +54if pct_pub_no_ifw and pct_pub_no_ifw.application_meta_data:
    +55    print(pct_pub_no_ifw.application_meta_data.invention_title)
    +56    print(" - IFW Found based on PCT Pub No")
    +57
    +58print("Now let's download the Patent Publication Text -->")
    +59if app_no_ifw and app_no_ifw.pgpub_document_meta_data:
    +60    pgpub_archive = app_no_ifw.pgpub_document_meta_data
    +61    print(pgpub_archive)
    +62    download_path = "./download-example"
    +63    file_path = client.download_archive(
    +64        printed_metadata=pgpub_archive, destination_path=download_path, overwrite=True
    +65    )
    +66    print(f"-Downloaded document to: {file_path}")
    +67
    +68print("Now let's download the Patent Grant Text -->")
    +69if app_no_ifw and app_no_ifw.grant_document_meta_data:
    +70    grant_archive = app_no_ifw.grant_document_meta_data
    +71    print(grant_archive)
    +72    download_path = "./download-example"
    +73    file_path = client.download_archive(
    +74        printed_metadata=grant_archive, destination_path=download_path, overwrite=True
    +75    )
    +76    print(f"-Downloaded document to: {file_path}")
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/build/html/examples/index.html b/docs/build/html/examples/index.html index f02cf8f..1bfa839 100644 --- a/docs/build/html/examples/index.html +++ b/docs/build/html/examples/index.html @@ -6,7 +6,7 @@ - Examples — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Examples — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -23,7 +23,7 @@ - + @@ -53,6 +53,11 @@
  • Examples
  • Development
  • @@ -88,6 +93,11 @@

    Examples

    @@ -96,14 +106,14 @@

    Examples


    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/examples/patent_data.html b/docs/build/html/examples/patent_data.html index fec2642..dfd782c 100644 --- a/docs/build/html/examples/patent_data.html +++ b/docs/build/html/examples/patent_data.html @@ -6,7 +6,7 @@ - Patent Data Examples — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Patent Data Examples — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -53,6 +53,11 @@
  • Examples
  • Development
  • @@ -85,6 +90,352 @@

    Patent Data Examples

    +
      1"""
    +  2Example usage of the uspto_api module for patent data
    +  3
    +  4This example demonstrates how to use the PatentDataClient to interact with the USPTO Patent Data API.
    +  5It shows how to retrieve patent applications, search for patents by various criteria, and access
    +  6detailed patent information including inventors, applicants, assignments, and more.
    +  7"""
    +  8
    +  9import json  # For pretty printing dict
    + 10import os
    + 11
    + 12from pyUSPTO.clients.patent_data import PatentDataClient
    + 13from pyUSPTO.config import USPTOConfig
    + 14from pyUSPTO.models.patent_data import ApplicationContinuityData
    + 15
    + 16# --- Initialization ---
    + 17# Choose one method to initialize the client.
    + 18# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key.
    + 19
    + 20# Method 1: Initialize the client with direct API key
    + 21print("Method 1: Initialize with direct API key")
    + 22api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE")
    + 23if api_key == "YOUR_API_KEY_HERE":
    + 24    raise ValueError(
    + 25        "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable."
    + 26    )
    + 27client = PatentDataClient(api_key=api_key)
    + 28
    + 29# Method 2: Initialize the client with USPTOConfig (alternative)
    + 30# print("\nMethod 2: Initialize with USPTOConfig")
    + 31# config_obj = USPTOConfig(
    + 32#     api_key="YOUR_API_KEY_HERE",  # Replace with your actual API key
    + 33#     # bulk_data_base_url="https://api.uspto.gov/api/v1/datasets", # Optional, uses default if not set
    + 34#     # patent_data_base_url="https://api.uspto.gov/api/v1/patent", # Optional, uses default if not set
    + 35# )
    + 36# client = PatentDataClient(config=config_obj)
    + 37
    + 38# Method 3: Initialize the client with environment variables (recommended for production)
    + 39# print("\nMethod 3: Initialize with environment variables")
    + 40# # Ensure USPTO_API_KEY is set in your environment
    + 41# # os.environ["USPTO_API_KEY"] = "YOUR_API_KEY_HERE" # Example, should be set outside
    + 42# try:
    + 43#     config_from_env = USPTOConfig.from_env()
    + 44#     client = PatentDataClient(config=config_from_env)
    + 45# except ValueError as e:
    + 46#     print(f"Error initializing from environment: {e}")
    + 47#     print("Please ensure USPTO_API_KEY environment variable is set.")
    + 48#     # Fallback to method 1 for the rest of the script if env var not set for example purposes
    + 49#     if 'client' not in locals(): # If client wasn't set due to error
    + 50#         api_key_fallback = "YOUR_API_KEY_HERE_FALLBACK"
    + 51#         print(f"Falling back to placeholder API key for example: {api_key_fallback}")
    + 52#         client = PatentDataClient(api_key=api_key_fallback)
    + 53
    + 54
    + 55print("\nBeginning API requests with configured client:")
    + 56
    + 57# Get some patent applications (default is 25)
    + 58try:
    + 59    print("\nAttempting to get some patent applications (default search)...")
    + 60    # Calling with no specific query, relying on API defaults or client defaults (e.g., limit)
    + 61    response = client.search_applications(limit=5)  # Example: get 5 results
    + 62    print(
    + 63        f"Found {response.count} total patent applications matching default/broad criteria."
    + 64    )
    + 65    print(
    + 66        f"Displaying first {len(response.patent_file_wrapper_data_bag)} applications from response:"
    + 67    )
    + 68
    + 69    for patent_wrapper in response.patent_file_wrapper_data_bag:
    + 70        app_meta = patent_wrapper.application_meta_data
    + 71        if app_meta:
    + 72            print(f"\n  Application: {patent_wrapper.application_number_text}")
    + 73            print(f"  Title: {app_meta.invention_title}")
    + 74            print(f"  Status: {app_meta.application_status_description_text}")
    + 75            print(f"  Filing Date: {app_meta.filing_date}")
    + 76
    + 77            if app_meta.patent_number:
    + 78                print(f"  Patent Number: {app_meta.patent_number}")
    + 79                print(f"  Grant Date: {app_meta.grant_date}")
    + 80
    + 81            if app_meta.inventor_bag:
    + 82                print("  Inventors:")
    + 83                for inventor in app_meta.inventor_bag:
    + 84                    name_parts = [
    + 85                        part
    + 86                        for part in [inventor.first_name, inventor.last_name]
    + 87                        if part
    + 88                    ]
    + 89                    print(f"    - {' '.join(name_parts).strip()}")
    + 90                    if inventor.correspondence_address_bag:
    + 91                        address = inventor.correspondence_address_bag[0]
    + 92                        if address.city_name and address.geographic_region_code:
    + 93                            print(
    + 94                                f"      ({address.city_name}, {address.geographic_region_code})"
    + 95                            )
    + 96
    + 97            if app_meta.applicant_bag:
    + 98                print("  Applicants:")
    + 99                for applicant in app_meta.applicant_bag:
    +100                    print(f"    - {applicant.applicant_name_text}")
    +101        print("-" * 20)
    +102
    +103    # Example of using the to_csv method from PatentDataResponse
    +104    if response.count > 0:
    +105        print("\nGenerating CSV for the current response (first few rows shown):")
    +106        csv_data = response.to_csv()
    +107        # Print first 3 lines of CSV (header + 2 data rows if available)
    +108        print("\n".join(csv_data.splitlines()[:3]))
    +109        # You could save this csv_data to a file:
    +110        # with open("patent_search_results.csv", "w", newline="", encoding="utf-8") as f:
    +111        # f.write(csv_data)
    +112        # print("\nFull CSV data saved to patent_search_results.csv (example).")
    +113
    +114
    +115except Exception as e:
    +116    print(f"Error getting patent applications: {e}")
    +117
    +118# Search for patents by inventor name using convenience _q parameter
    +119try:
    +120    print("\nSearching for patents with 'Smith' as inventor...")
    +121    # Changed from search_patents to search_applications with inventor_name_q
    +122    inventor_search_response = client.search_applications(
    +123        inventor_name_q="Smith", limit=2
    +124    )
    +125    print(
    +126        f"Found {inventor_search_response.count} patents with 'Smith' as inventor (showing up to 2)."
    +127    )
    +128    for patent_wrapper in inventor_search_response.patent_file_wrapper_data_bag:
    +129        if patent_wrapper.application_meta_data:
    +130            print(
    +131                f"  - App No: {patent_wrapper.application_number_text}, Title: {patent_wrapper.application_meta_data.invention_title}"
    +132            )
    +133except Exception as e:
    +134    print(f"Error searching by inventor: {e}")
    +135
    +136
    +137# Search for patents filed in a date range using convenience _q parameters
    +138try:
    +139    print("\nSearching for patents filed in 2020...")
    +140    date_search_response = client.search_applications(
    +141        filing_date_from_q="2020-01-01", filing_date_to_q="2020-12-31", limit=2
    +142    )
    +143    print(
    +144        f"Found {date_search_response.count} patents filed in 2020 (showing up to 2)."
    +145    )
    +146    for patent_wrapper in date_search_response.patent_file_wrapper_data_bag:
    +147        if patent_wrapper.application_meta_data:
    +148            print(
    +149                f"  - App No: {patent_wrapper.application_number_text}, Filing Date: {patent_wrapper.application_meta_data.filing_date}"
    +150            )
    +151except Exception as e:
    +152    print(f"Error searching by date range: {e}")
    +153
    +154# Get a specific patent by application number
    +155app_no_to_fetch = "18045436"  # Known application number, ensure it's valid
    +156try:
    +157    print(f"\nAttempting to retrieve patent application: {app_no_to_fetch}")
    +158    patent_wrapper_detail = client.get_application_by_number(
    +159        application_number=app_no_to_fetch
    +160    )
    +161    if patent_wrapper_detail:
    +162        print(
    +163            f"Successfully retrieved: {patent_wrapper_detail.application_number_text}"
    +164        )
    +165        if patent_wrapper_detail.application_meta_data:
    +166            print(
    +167                f"Title: {patent_wrapper_detail.application_meta_data.invention_title}"
    +168            )
    +169
    +170        print("\nRetrieving document information...")
    +171        documents_bag = client.get_application_documents(
    +172            application_number=app_no_to_fetch
    +173        )
    +174        print(f"Found {len(documents_bag)} documents for application {app_no_to_fetch}")
    +175
    +176        if documents_bag.documents:
    +177            document_to_download = documents_bag.documents[0]  # Example: first document
    +178            print(f"\nFirst document details:")
    +179            print(f"  Document ID: {document_to_download.document_identifier}")
    +180            print(
    +181                f"  Document Type: {document_to_download.document_code} - {document_to_download.document_code_description_text}"
    +182            )
    +183            print(f"  Date: {document_to_download.official_date}")
    +184            print(f"  Direction: {document_to_download.direction_category}")
    +185
    +186            if (
    +187                document_to_download.document_formats
    +188                and document_to_download.document_identifier
    +189            ):
    +190                print("\nAttempting to download first document...")
    +191                download_path = "./download-example"
    +192                print(document_to_download.to_dict())
    +193                downloaded_path = client.download_document(
    +194                    document_format=document_to_download.document_formats[0],
    +195                    destination_path=download_path,
    +196                    overwrite=True,
    +197                )
    +198                print(f"Downloaded document to: {downloaded_path}")
    +199            else:
    +200                print(
    +201                    "No downloadable formats available for the first document or document identifier missing."
    +202                )
    +203        else:
    +204            print("No documents listed for this application.")
    +205
    +206        # Example: Download publication XML (grant or pgpub)
    +207        print("\nChecking for publication files (grant/pgpub XML)...")
    +208        if patent_wrapper_detail.grant_document_meta_data:
    +209            grant_metadata = patent_wrapper_detail.grant_document_meta_data
    +210            print(f"Grant document available: {grant_metadata.xml_file_name}")
    +211            print(f"  Product: {grant_metadata.product_identifier}")
    +212            print(f"  Created: {grant_metadata.file_create_date_time}")
    +213
    +214            # Download grant XML to downloads folder with auto-generated filename
    +215            print("\nDownloading grant XML...")
    +216            grant_path = client.download_publication(
    +217                printed_metadata=grant_metadata,
    +218                destination_path="./download-example",
    +219                overwrite=True,
    +220            )
    +221            print(f"Downloaded grant XML to: {grant_path}")
    +222
    +223        if patent_wrapper_detail.pgpub_document_meta_data:
    +224            pgpub_metadata = patent_wrapper_detail.pgpub_document_meta_data
    +225            print(f"\nPre-grant publication available: {pgpub_metadata.xml_file_name}")
    +226
    +227            # Download with custom filename
    +228            pgpub_path = client.download_publication(
    +229                printed_metadata=pgpub_metadata,
    +230                file_name="my_pgpub.xml",
    +231                destination_path="./download-example",
    +232                overwrite=True,
    +233            )
    +234            print(f"Downloaded pgpub XML to: {pgpub_path}")
    +235
    +236        if patent_wrapper_detail.assignment_bag:
    +237            print("\nAssignments:")
    +238            for assignment in patent_wrapper_detail.assignment_bag:
    +239                for assignee in assignment.assignee_bag:
    +240                    print(
    +241                        f"  - {assignee.assignee_name_text} (Recorded: {assignment.assignment_recorded_date})"
    +242                    )
    +243                    print(f"    Conveyance: {assignment.conveyance_text}")
    +244    else:
    +245        print(f"Could not retrieve details for application {app_no_to_fetch}")
    +246
    +247except Exception as e:
    +248    print(f"Error retrieving or processing patent application {app_no_to_fetch}: {e}")
    +249
    +250# Search for a specific patent by patent number (using search_applications)
    +251target_patent_number = "10000000"
    +252try:
    +253    print(f"\nSearching for patent US {target_patent_number} B2...")
    +254    patent_search_response = client.search_applications(
    +255        patent_number_q=target_patent_number, limit=1
    +256    )
    +257
    +258    if (
    +259        patent_search_response.count > 0
    +260        and patent_search_response.patent_file_wrapper_data_bag
    +261    ):
    +262        found_patent_wrapper = patent_search_response.patent_file_wrapper_data_bag[0]
    +263        if (
    +264            found_patent_wrapper.application_meta_data
    +265            and found_patent_wrapper.application_meta_data.patent_number
    +266        ):
    +267            print(
    +268                f"Retrieved patent: US {found_patent_wrapper.application_meta_data.patent_number}"
    +269            )
    +270        else:
    +271            print(
    +272                f"Retrieved patent application: {found_patent_wrapper.application_number_text}"
    +273            )
    +274
    +275        if found_patent_wrapper.patent_term_adjustment_data:
    +276            pta = found_patent_wrapper.patent_term_adjustment_data
    +277            print(f"Patent Term Adjustment: {pta.adjustment_total_quantity} days")
    +278            if pta.a_delay_quantity is not None:
    +279                print(f"  A Delay: {pta.a_delay_quantity} days")
    +280            if pta.b_delay_quantity is not None:
    +281                print(f"  B Delay: {pta.b_delay_quantity} days")
    +282            if pta.c_delay_quantity is not None:
    +283                print(f"  C Delay: {pta.c_delay_quantity} days")
    +284            if pta.applicant_day_delay_quantity is not None:
    +285                print(f"  Applicant Delay: {pta.applicant_day_delay_quantity} days")
    +286
    +287        # Example of getting continuity data (assuming it's part of the wrapper)
    +288        continuity_data = ApplicationContinuityData.from_wrapper(
    +289            wrapper=found_patent_wrapper
    +290        )
    +291        if continuity_data.parent_continuity_bag:
    +292            print("\nParent Applications:")
    +293            for p_continuity in continuity_data.parent_continuity_bag:
    +294                print(f"  - App No: {p_continuity.parent_application_number_text}")
    +295                print(
    +296                    f"    Type: {p_continuity.claim_parentage_type_code_description_text}"
    +297                )
    +298                print(f"    Filing Date: {p_continuity.parent_application_filing_date}")
    +299
    +300        if continuity_data.child_continuity_bag:
    +301            print("\nChild Applications:")
    +302            for c_continuity in continuity_data.child_continuity_bag:
    +303                print(f"  - App No: {c_continuity.child_application_number_text}")
    +304                print(
    +305                    f"    Type: {c_continuity.claim_parentage_type_code_description_text}"
    +306                )
    +307                print(f"    Filing Date: {c_continuity.child_application_filing_date}")
    +308    else:
    +309        print(f"No patents found with patent number: {target_patent_number}")
    +310
    +311except Exception as e:
    +312    print(f"Error retrieving patent by number {target_patent_number}: {e}")
    +313
    +314# Example of POST search for applications
    +315try:
    +316    print("\nAttempting POST search for applications with 'AI' in title...")
    +317    post_search_body = {
    +318        "q": "applicationMetaData.inventionTitle:AI",
    +319        "pagination": {"offset": 0, "limit": 2},
    +320    }
    +321    post_response = client.search_applications(post_body=post_search_body)
    +322    print(
    +323        f"Found {post_response.count} applications via POST search (showing up to 2)."
    +324    )
    +325    for patent_wrapper in post_response.patent_file_wrapper_data_bag:
    +326        if patent_wrapper.application_meta_data:
    +327            print(
    +328                f"  - App No: {patent_wrapper.application_number_text}, Title: {patent_wrapper.application_meta_data.invention_title}"
    +329            )
    +330except Exception as e:
    +331    print(f"Error with POST search: {e}")
    +332
    +333
    +334# Example of getting status codes
    +335try:
    +336    print("\nGetting first 5 status codes...")
    +337    status_code_response = client.get_status_codes(params={"limit": 5})
    +338    print(
    +339        f"Retrieved {len(status_code_response.status_code_bag)} status codes (out of {status_code_response.count} total)."
    +340    )
    +341    for code_obj in status_code_response.status_code_bag:
    +342        print(f"  - Code: {code_obj.code}, Description: {code_obj.description}")
    +343except Exception as e:
    +344    print(f"Error getting status codes: {e}")
    +
    +
    @@ -92,13 +443,13 @@

    Patent Data Examples

    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/examples/petition_decisions.html b/docs/build/html/examples/petition_decisions.html new file mode 100644 index 0000000..580bb9f --- /dev/null +++ b/docs/build/html/examples/petition_decisions.html @@ -0,0 +1,439 @@ + + + + + + + + + Petition Decisions Example — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Petition Decisions Example

    +
      1"""
    +  2Example usage of the pyUSPTO module for Final Petition Decisions
    +  3
    +  4This example demonstrates how to use the FinalPetitionDecisionsClient to interact with the
    +  5USPTO Final Petition Decisions API. It shows how to search for petition decisions, retrieve
    +  6specific decisions by ID, download decision data, and access detailed information about
    +  7petitions and their associated documents.
    +  8"""
    +  9
    + 10import os
    + 11
    + 12from pyUSPTO.clients import FinalPetitionDecisionsClient
    + 13from pyUSPTO.config import USPTOConfig
    + 14
    + 15# --- Initialization ---
    + 16# Choose one method to initialize the client.
    + 17# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key.
    + 18
    + 19# Method 1: Initialize the client with direct API key
    + 20print("Method 1: Initialize with direct API key")
    + 21api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE")
    + 22if api_key == "YOUR_API_KEY_HERE":
    + 23    raise ValueError(
    + 24        "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable."
    + 25    )
    + 26client = FinalPetitionDecisionsClient(api_key=api_key)
    + 27
    + 28# Method 2: Initialize the client with USPTOConfig (alternative)
    + 29# print("\nMethod 2: Initialize with USPTOConfig")
    + 30# config_obj = USPTOConfig(
    + 31#     api_key="YOUR_API_KEY_HERE",  # Replace with your actual API key
    + 32#     petition_decisions_base_url="https://api.uspto.gov",  # Optional, uses default if not set
    + 33# )
    + 34# client = FinalPetitionDecisionsClient(config=config_obj)
    + 35
    + 36# Method 3: Initialize the client with environment variables (recommended for production)
    + 37# print("\nMethod 3: Initialize with environment variables")
    + 38# # Ensure USPTO_API_KEY is set in your environment
    + 39# config_from_env = USPTOConfig.from_env()
    + 40# client = FinalPetitionDecisionsClient(config=config_from_env)
    + 41
    + 42print("\nBeginning API requests with configured client:")
    + 43
    + 44# Basic search for petition decisions
    + 45try:
    + 46    print("\n" + "=" * 60)
    + 47    print("Example 1: Basic Search for Petition Decisions")
    + 48    print("=" * 60)
    + 49
    + 50    response = client.search_decisions(limit=5)
    + 51    print(f"Found {response.count} total petition decisions.")
    + 52    print(f"Displaying first {len(response.petition_decision_data_bag)} decisions:")
    + 53
    + 54    for decision in response.petition_decision_data_bag:
    + 55        print(f"\n  Decision ID: {decision.petition_decision_record_identifier}")
    + 56        print(f"  Application Number: {decision.application_number_text}")
    + 57        print(f"  Decision Type: {decision.decision_type_code}")
    + 58        print(f"  Decision Date: {decision.decision_date}")
    + 59        print(f"  Technology Center: {decision.technology_center_number}")
    + 60
    + 61        if decision.applicant_name:
    + 62            print(f"  Applicant: {decision.applicant_name}")
    + 63
    + 64        if decision.patent_number:
    + 65            print(f"  Patent Number: {decision.patent_number}")
    + 66
    + 67        if decision.inventor_bag:
    + 68            print(f"  Inventors ({len(decision.inventor_bag)}):")
    + 69            for inventor in decision.inventor_bag[:3]:  # Show first 3
    + 70                name_parts = [
    + 71                    part for part in [inventor.first_name, inventor.last_name] if part
    + 72                ]
    + 73                print(f"    - {' '.join(name_parts).strip()}")
    + 74
    + 75        if decision.document_bag:
    + 76            print(f"  Documents: {len(decision.document_bag)}")
    + 77
    + 78        print("-" * 40)
    + 79
    + 80except Exception as e:
    + 81    print(f"Error in basic search: {e}")
    + 82
    + 83# Search with query parameter
    + 84try:
    + 85    print("\n" + "=" * 60)
    + 86    print("Example 2: Search with Custom Query")
    + 87    print("=" * 60)
    + 88
    + 89    # Search for decisions mentioning specific terms
    + 90    response = client.search_decisions(
    + 91        query="decisionTypeCode:GRANT",
    + 92        limit=3
    + 93    )
    + 94    print(f"Found {response.count} decisions with GRANT type.")
    + 95    print(f"Showing {len(response.petition_decision_data_bag)} results:")
    + 96
    + 97    for decision in response.petition_decision_data_bag:
    + 98        print(f"  - {decision.petition_decision_record_identifier}: {decision.decision_type_code}")
    + 99
    +100except Exception as e:
    +101    print(f"Error searching with query: {e}")
    +102
    +103# Search using convenience parameters
    +104try:
    +105    print("\n" + "=" * 60)
    +106    print("Example 3: Search Using Convenience Parameters")
    +107    print("=" * 60)
    +108
    +109    # Search by application number (if you have a specific one)
    +110    print("\nSearching by date range...")
    +111    response = client.search_decisions(
    +112        decision_date_from_q="2023-01-01",
    +113        decision_date_to_q="2023-12-31",
    +114        limit=5
    +115    )
    +116    print(f"Found {response.count} decisions from 2023.")
    +117
    +118    # Search by technology center
    +119    print("\nSearching by technology center...")
    +120    response = client.search_decisions(
    +121        technology_center_q="2600",
    +122        limit=3
    +123    )
    +124    print(f"Found {response.count} decisions from Technology Center 2600.")
    +125
    +126except Exception as e:
    +127    print(f"Error with convenience parameters: {e}")
    +128
    +129# Get a specific decision by ID
    +130try:
    +131    print("\n" + "=" * 60)
    +132    print("Example 4: Get Specific Decision by ID")
    +133    print("=" * 60)
    +134
    +135    # First, get a decision ID from search results
    +136    response = client.search_decisions(limit=1)
    +137    if response.count > 0:
    +138        decision_id = response.petition_decision_data_bag[0].petition_decision_record_identifier
    +139
    +140        print(f"Retrieving decision: {decision_id}")
    +141        decision = client.get_decision_by_id(decision_id)
    +142
    +143        print(f"\nDecision Details:")
    +144        print(f"  ID: {decision.petition_decision_record_identifier}")
    +145        print(f"  Application: {decision.application_number_text}")
    +146        print(f"  Patent: {decision.patent_number}")
    +147        print(f"  Decision Type: {decision.decision_type_code}")
    +148        print(f"  Decision Date: {decision.decision_date}")
    +149        print(f"  Technology Center: {decision.technology_center_number}")
    +150        print(f"  Group Art Unit: {decision.group_art_unit_number}")
    +151        print(f"  Examiner: {decision.examiner_name_text}")
    +152
    +153        if decision.rule_bag:
    +154            print(f"\n  Rules Cited ({len(decision.rule_bag)}):")
    +155            for rule in decision.rule_bag[:5]:  # Show first 5
    +156                print(f"    - {rule}")
    +157
    +158        if decision.statute_bag:
    +159            print(f"\n  Statutes Cited ({len(decision.statute_bag)}):")
    +160            for statute in decision.statute_bag[:5]:  # Show first 5
    +161                print(f"    - {statute}")
    +162
    +163        if decision.document_bag:
    +164            print(f"\n  Associated Documents ({len(decision.document_bag)}):")
    +165            for doc in decision.document_bag[:3]:  # Show first 3
    +166                print(f"    - Doc ID: {doc.document_identifier}")
    +167                print(f"      Date: {doc.official_date}")
    +168                print(f"      Direction: {doc.document_direction_category}")
    +169                if doc.page_total_quantity:
    +170                    print(f"      Pages: {doc.page_total_quantity}")
    +171                if doc.download_option_bag:
    +172                    print(f"      Download Options: {len(doc.download_option_bag)}")
    +173
    +174except Exception as e:
    +175    print(f"Error retrieving decision by ID: {e}")
    +176
    +177# Download petition decisions data
    +178try:
    +179    print("\n" + "=" * 60)
    +180    print("Example 5: Download Petition Decisions Data")
    +181    print("=" * 60)
    +182
    +183    # Download as JSON (returns response object)
    +184    print("\nDownloading decisions as JSON...")
    +185    response = client.download_decisions(
    +186        format="json",
    +187        decision_date_from_q="2023-01-01",
    +188        limit=5
    +189    )
    +190    print(f"Downloaded JSON with {len(response.petition_decision_data)} decision records")
    +191
    +192    # Download as CSV (automatically saves to file)
    +193    print("\nDownloading decisions as CSV...")
    +194    csv_path = client.download_decisions(
    +195        format="csv",
    +196        decision_date_from_q="2023-01-01",
    +197        limit=10,
    +198        destination_path="./downloads"
    +199    )
    +200    print(f"Downloaded CSV to: {csv_path}")
    +201
    +202except Exception as e:
    +203    print(f"Error downloading decisions: {e}")
    +204
    +205# Pagination example
    +206try:
    +207    print("\n" + "=" * 60)
    +208    print("Example 6: Paginating Through Results")
    +209    print("=" * 60)
    +210
    +211    page_size = 10
    +212    max_pages = 3  # Limit to 3 pages for example
    +213
    +214    print(f"Paginating through results ({page_size} per page, max {max_pages} pages)...")
    +215
    +216    page_count = 0
    +217    total_decisions = 0
    +218
    +219    for page_response in client.paginate_decisions(
    +220        limit=page_size,
    +221        query="decisionDate:[2023-01-01 TO 2023-12-31]"
    +222    ):
    +223        page_count += 1
    +224        decisions_in_page = len(page_response.petition_decision_data_bag)
    +225        total_decisions += decisions_in_page
    +226
    +227        print(f"  Page {page_count}: {decisions_in_page} decisions")
    +228
    +229        if page_count >= max_pages:
    +230            print(f"  (Stopping after {max_pages} pages for example)")
    +231            break
    +232
    +233    print(f"\nTotal decisions retrieved: {total_decisions} across {page_count} pages")
    +234
    +235except Exception as e:
    +236    print(f"Error during pagination: {e}")
    +237
    +238# Download a petition document
    +239try:
    +240    print("\n" + "=" * 60)
    +241    print("Example 7: Download Petition Decision Document")
    +242    print("=" * 60)
    +243
    +244    # Find a decision with downloadable documents
    +245    response = client.search_decisions(limit=20)
    +246
    +247    document_found = False
    +248    for decision in response.petition_decision_data_bag:
    +249        if decision.document_bag:
    +250            for doc in decision.document_bag:
    +251                if doc.download_option_bag and len(doc.download_option_bag) > 0:
    +252                    download_option = doc.download_option_bag[0]
    +253
    +254                    print(f"Found downloadable document:")
    +255                    print(f"  Document ID: {doc.document_identifier}")
    +256                    print(f"  MIME Type: {download_option.mime_type_identifier}")
    +257                    print(f"  Pages: {download_option.page_total_quantity}")
    +258                    print(f"  URL: {download_option.download_url}")
    +259
    +260                    print(f"\nDownloading document...")
    +261                    file_path = client.download_petition_document(
    +262                        download_option,
    +263                        file_path="./downloads"
    +264                    )
    +265                    print(f"Downloaded to: {file_path}")
    +266
    +267                    document_found = True
    +268                    break
    +269
    +270        if document_found:
    +271            break
    +272
    +273    if not document_found:
    +274        print("No downloadable documents found in the first 20 results")
    +275
    +276except Exception as e:
    +277    print(f"Error downloading document: {e}")
    +278
    +279# Advanced search example
    +280try:
    +281    print("\n" + "=" * 60)
    +282    print("Example 8: Advanced Search with Multiple Criteria")
    +283    print("=" * 60)
    +284
    +285    # Search with multiple parameters
    +286    response = client.search_decisions(
    +287        application_number_q="16*",  # Applications starting with 16
    +288        decision_date_from_q="2020-01-01",
    +289        technology_center_q="2600",
    +290        limit=10
    +291    )
    +292
    +293    print(f"Search criteria:")
    +294    print(f"  - Application numbers starting with '16'")
    +295    print(f"  - Decision date from 2020-01-01")
    +296    print(f"  - Technology Center 2600")
    +297    print(f"\nFound {response.count} matching decisions")
    +298
    +299    if response.count > 0:
    +300        print(f"Showing first {len(response.petition_decision_data_bag)} results:")
    +301        for decision in response.petition_decision_data_bag:
    +302            print(f"  - App: {decision.application_number_text}, "
    +303                  f"TC: {decision.technology_center_number}, "
    +304                  f"Date: {decision.decision_date}")
    +305
    +306except Exception as e:
    +307    print(f"Error in advanced search: {e}")
    +308
    +309print("\n" + "=" * 60)
    +310print("Examples completed!")
    +311print("=" * 60)
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/build/html/examples/ptab_appeals.html b/docs/build/html/examples/ptab_appeals.html new file mode 100644 index 0000000..981098c --- /dev/null +++ b/docs/build/html/examples/ptab_appeals.html @@ -0,0 +1,409 @@ + + + + + + + + + PTAB Appeals Example — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    PTAB Appeals Example

    +
      1"""
    +  2Example usage of the pyUSPTO module for PTAB Appeals API
    +  3
    +  4This example demonstrates how to use the PTABAppealsClient to interact with the USPTO PTAB
    +  5(Patent Trial and Appeal Board) Appeals API. It shows how to search for ex parte appeal
    +  6decisions using various search criteria.
    +  7
    +  8PTAB Appeals include ex parte appeals from patent application examinations to the Board.
    +  9"""
    + 10
    + 11import os
    + 12
    + 13from pyUSPTO import PTABAppealsClient, USPTOConfig
    + 14
    + 15# --- Initialization ---
    + 16# Choose one method to initialize the client.
    + 17# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key.
    + 18
    + 19# Method 1: Initialize the client with direct API key
    + 20print("Method 1: Initialize with direct API key")
    + 21api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE")
    + 22if api_key == "YOUR_API_KEY_HERE":
    + 23    raise ValueError(
    + 24        "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable."
    + 25    )
    + 26client = PTABAppealsClient(api_key=api_key)
    + 27
    + 28# Method 2: Initialize the client with USPTOConfig (alternative)
    + 29# print("\nMethod 2: Initialize with USPTOConfig")
    + 30# config_obj = USPTOConfig(
    + 31#     api_key="YOUR_API_KEY_HERE",  # Replace with your actual API key
    + 32#     ptab_base_url="https://api.uspto.gov",  # Optional, uses default if not set
    + 33# )
    + 34# client = PTABAppealsClient(config=config_obj)
    + 35
    + 36# Method 3: Initialize the client with environment variables (recommended for production)
    + 37# print("\nMethod 3: Initialize with environment variables")
    + 38# # Ensure USPTO_API_KEY is set in your environment
    + 39# try:
    + 40#     config_from_env = USPTOConfig.from_env()
    + 41#     client = PTABAppealsClient(config=config_from_env)
    + 42# except ValueError as e:
    + 43#     print(f"Error initializing from environment: {e}")
    + 44#     print("Please ensure USPTO_API_KEY environment variable is set.")
    + 45
    + 46print("\nBeginning PTAB Appeals API requests with configured client:")
    + 47
    + 48# =============================================================================
    + 49# 1. Search Appeal Decisions by Technology Center
    + 50# =============================================================================
    + 51
    + 52print("\n" + "=" * 80)
    + 53print("1. Searching for appeal decisions by technology center")
    + 54print("=" * 80)
    + 55
    + 56try:
    + 57    # Search for decisions from Technology Center 3600 (Business Methods/Software)
    + 58    response = client.search_decisions(
    + 59        technology_center_number_q="3600",
    + 60        decision_date_from_q="2023-01-01",
    + 61        decision_date_to_q="2023-12-31",
    + 62        limit=5,
    + 63    )
    + 64
    + 65    print(f"\nFound {response.count} appeal decisions from TC 3600 in 2023")
    + 66    print(f"Displaying first {len(response.patent_appeal_data_bag)} results:")
    + 67
    + 68    for decision in response.patent_appeal_data_bag:
    + 69        print(f"\n  Appeal Number: {decision.appeal_number}")
    + 70
    + 71        if decision.appeal_meta_data:
    + 72            meta = decision.appeal_meta_data
    + 73            print(f"  Application Type: {meta.application_type_category}")
    + 74            print(f"  Filing Date: {meta.appeal_filing_date}")
    + 75
    + 76        if decision.appellant_data:
    + 77            appellant = decision.appellant_data
    + 78            print(f"  Application Number: {appellant.application_number_text}")
    + 79            print(f"  Technology Center: {appellant.technology_center_number}")
    + 80
    + 81            if appellant.inventor_name:
    + 82                print(f"  Inventor: {appellant.inventor_name}")
    + 83
    + 84        if decision.decision_data:
    + 85            dec = decision.decision_data
    + 86            print(f"  Decision Type: {dec.decision_type_category}")
    + 87            print(f"  Decision Date: {dec.decision_issue_date}")
    + 88
    + 89except Exception as e:
    + 90    print(f"Error searching appeal decisions: {e}")
    + 91
    + 92# =============================================================================
    + 93# 2. Search by Decision Type
    + 94# =============================================================================
    + 95
    + 96print("\n" + "=" * 80)
    + 97print("2. Searching for 'Affirmed' decisions")
    + 98print("=" * 80)
    + 99
    +100try:
    +101    # Search for decisions where the examiner was affirmed
    +102    response = client.search_decisions(
    +103        decision_type_category_q="Affirmed",
    +104        decision_date_from_q="2024-01-01",
    +105        limit=5,
    +106    )
    +107
    +108    print(f"\nFound {response.count} 'Affirmed' decisions since 2024")
    +109    print(f"Displaying first {len(response.patent_appeal_data_bag)} results:")
    +110
    +111    for decision in response.patent_appeal_data_bag:
    +112        print(f"\n  Appeal Number: {decision.appeal_number}")
    +113
    +114        if decision.appellant_data:
    +115            print(f"  Application: {decision.appellant_data.application_number_text}")
    +116            print(f"  Inventor: {decision.appellant_data.inventor_name or 'N/A'}")
    +117
    +118        if decision.decision_data:
    +119            print(f"  Decision: {decision.decision_data.decision_type_category}")
    +120            print(f"  Date: {decision.decision_data.decision_issue_date}")
    +121
    +122except Exception as e:
    +123    print(f"Error searching by decision type: {e}")
    +124
    +125# =============================================================================
    +126# 3. Search by Application Number
    +127# =============================================================================
    +128
    +129print("\n" + "=" * 80)
    +130print("3. Searching for decisions by application number pattern")
    +131print("=" * 80)
    +132
    +133try:
    +134    # Search for decisions related to applications starting with "15/"
    +135    response = client.search_decisions(
    +136        application_number_text_q="15/*",
    +137        decision_date_from_q="2023-01-01",
    +138        limit=3,
    +139    )
    +140
    +141    print(f"\nFound {response.count} decisions for applications starting with '15/'")
    +142    print(f"Displaying first {len(response.patent_appeal_data_bag)} results:")
    +143
    +144    for decision in response.patent_appeal_data_bag:
    +145        print(f"\n  Appeal Number: {decision.appeal_number}")
    +146
    +147        if decision.appellant_data:
    +148            print(f"  Application: {decision.appellant_data.application_number_text}")
    +149            print(f"  TC Number: {decision.appellant_data.technology_center_number}")
    +150
    +151        if decision.document_data:
    +152            doc = decision.document_data
    +153            print(f"  Document Name: {doc.document_name}")
    +154            if doc.file_download_uri:
    +155                print(f"  Download URL: {doc.file_download_uri}")
    +156
    +157except Exception as e:
    +158    print(f"Error searching by application number: {e}")
    +159
    +160# =============================================================================
    +161# 4. Pagination Example
    +162# =============================================================================
    +163
    +164print("\n" + "=" * 80)
    +165print("4. Paginating through appeal decisions")
    +166print("=" * 80)
    +167
    +168try:
    +169    print("\nIterating through first 10 appeal decisions from 2024...")
    +170    count = 0
    +171    for decision in client.paginate_decisions(
    +172        decision_date_from_q="2024-01-01",
    +173        limit=5,  # Fetch 5 per page
    +174    ):
    +175        count += 1
    +176        decision_type = (
    +177            decision.decision_data.decision_type_category
    +178            if decision.decision_data
    +179            else "N/A"
    +180        )
    +181        print(f"{count}. {decision.appeal_number} - {decision_type}")
    +182
    +183        if count >= 10:  # Stop after 10 results for this example
    +184            break
    +185
    +186    print(f"\nDisplayed {count} decisions using pagination")
    +187
    +188except Exception as e:
    +189    print(f"Error paginating decisions: {e}")
    +190
    +191# =============================================================================
    +192# 5. Advanced Search with Multiple Criteria
    +193# =============================================================================
    +194
    +195print("\n" + "=" * 80)
    +196print("5. Advanced search with multiple criteria")
    +197print("=" * 80)
    +198
    +199try:
    +200    # Search with multiple convenience parameters
    +201    response = client.search_decisions(
    +202        technology_center_number_q="2100",  # Electronics
    +203        decision_type_category_q="Reversed",
    +204        decision_date_from_q="2023-01-01",
    +205        decision_date_to_q="2023-12-31",
    +206        sort="decisionDate desc",
    +207        limit=3,
    +208    )
    +209
    +210    print(
    +211        f"\nFound {response.count} 'Reversed' decisions from TC 2100 (Electronics) in 2023"
    +212    )
    +213    print(f"Displaying first {len(response.patent_appeal_data_bag)} results:")
    +214
    +215    for decision in response.patent_appeal_data_bag:
    +216        print(f"\n  Appeal Number: {decision.appeal_number}")
    +217
    +218        if decision.appellant_data:
    +219            print(f"  Application: {decision.appellant_data.application_number_text}")
    +220
    +221        if decision.decision_data:
    +222            print(f"  Decision: {decision.decision_data.decision_type_category}")
    +223            print(f"  Date: {decision.decision_data.decision_issue_date}")
    +224
    +225except Exception as e:
    +226    print(f"Error with advanced search: {e}")
    +227
    +228# =============================================================================
    +229# 6. Direct Query String Example
    +230# =============================================================================
    +231
    +232print("\n" + "=" * 80)
    +233print("6. Using direct query string for complex searches")
    +234print("=" * 80)
    +235
    +236try:
    +237    # Use a direct query string for more complex searches
    +238    response = client.search_decisions(
    +239        query="technologyCenterNumber:3600 AND decisionTypeCategory:(Affirmed OR Reversed)",
    +240        limit=3,
    +241    )
    +242
    +243    print(f"\nFound {response.count} Affirmed/Reversed decisions from TC 3600")
    +244    print(f"Displaying first {len(response.patent_appeal_data_bag)} results:")
    +245
    +246    for decision in response.patent_appeal_data_bag:
    +247        print(f"\n  Appeal Number: {decision.appeal_number}")
    +248
    +249        if decision.decision_data:
    +250            print(f"  Decision: {decision.decision_data.decision_type_category}")
    +251
    +252except Exception as e:
    +253    print(f"Error with direct query: {e}")
    +254
    +255# =============================================================================
    +256# 7. Error Handling Example
    +257# =============================================================================
    +258
    +259print("\n" + "=" * 80)
    +260print("7. Error handling demonstration")
    +261print("=" * 80)
    +262
    +263try:
    +264    # Attempt a search that might return no results
    +265    print("\nAttempting search with unlikely parameters...")
    +266    response = client.search_decisions(
    +267        appeal_number_q="INVALID-APPEAL-NUMBER",
    +268        limit=1,
    +269    )
    +270
    +271    if response.count == 0:
    +272        print("No results found for the given search criteria")
    +273    else:
    +274        print(f"Found {response.count} results")
    +275
    +276except Exception as e:
    +277    print(f"Expected error occurred: {type(e).__name__}: {e}")
    +278
    +279print("\n" + "=" * 80)
    +280print("PTAB Appeals API example completed successfully!")
    +281print("=" * 80)
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/build/html/examples/ptab_interferences.html b/docs/build/html/examples/ptab_interferences.html new file mode 100644 index 0000000..778d3ac --- /dev/null +++ b/docs/build/html/examples/ptab_interferences.html @@ -0,0 +1,463 @@ + + + + + + + + + PTAB Interferences Example — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    PTAB Interferences Example

    +
      1"""
    +  2Example usage of the pyUSPTO module for PTAB Interferences API
    +  3
    +  4This example demonstrates how to use the PTABInterferencesClient to interact with the USPTO PTAB
    +  5(Patent Trial and Appeal Board) Interferences API. It shows how to search for interference
    +  6decisions using various search criteria.
    +  7
    +  8PTAB Interferences are proceedings to determine priority of invention when two or more parties
    +  9claim the same patentable invention.
    + 10"""
    + 11
    + 12import os
    + 13
    + 14from pyUSPTO import PTABInterferencesClient, USPTOConfig
    + 15
    + 16# --- Initialization ---
    + 17# Choose one method to initialize the client.
    + 18# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key.
    + 19
    + 20# Method 1: Initialize the client with direct API key
    + 21print("Method 1: Initialize with direct API key")
    + 22api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE")
    + 23if api_key == "YOUR_API_KEY_HERE":
    + 24    raise ValueError(
    + 25        "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable."
    + 26    )
    + 27client = PTABInterferencesClient(api_key=api_key)
    + 28
    + 29# Method 2: Initialize the client with USPTOConfig (alternative)
    + 30# print("\nMethod 2: Initialize with USPTOConfig")
    + 31# config_obj = USPTOConfig(
    + 32#     api_key="YOUR_API_KEY_HERE",  # Replace with your actual API key
    + 33#     ptab_base_url="https://api.uspto.gov",  # Optional, uses default if not set
    + 34# )
    + 35# client = PTABInterferencesClient(config=config_obj)
    + 36
    + 37# Method 3: Initialize the client with environment variables (recommended for production)
    + 38# print("\nMethod 3: Initialize with environment variables")
    + 39# # Ensure USPTO_API_KEY is set in your environment
    + 40# try:
    + 41#     config_from_env = USPTOConfig.from_env()
    + 42#     client = PTABInterferencesClient(config=config_from_env)
    + 43# except ValueError as e:
    + 44#     print(f"Error initializing from environment: {e}")
    + 45#     print("Please ensure USPTO_API_KEY environment variable is set.")
    + 46
    + 47print("\nBeginning PTAB Interferences API requests with configured client:")
    + 48
    + 49# =============================================================================
    + 50# 1. Search Interference Decisions
    + 51# =============================================================================
    + 52
    + 53print("\n" + "=" * 80)
    + 54print("1. Searching for interference decisions")
    + 55print("=" * 80)
    + 56
    + 57try:
    + 58    # Search for recent interference decisions
    + 59    response = client.search_decisions(
    + 60        decision_date_from_q="2023-01-01",
    + 61        limit=5,
    + 62    )
    + 63
    + 64    print(f"\nFound {response.count} interference decisions since 2023")
    + 65    print(f"Displaying first {len(response.patent_interference_data_bag)} results:")
    + 66
    + 67    for decision in response.patent_interference_data_bag:
    + 68        print(f"\n  Interference Number: {decision.interference_number}")
    + 69
    + 70        if decision.interference_meta_data:
    + 71            meta = decision.interference_meta_data
    + 72            print(f"  Style Name: {meta.interference_style_name}")
    + 73            print(f"  Last Modified: {meta.interference_last_modified_date}")
    + 74
    + 75        if decision.senior_party_data:
    + 76            senior = decision.senior_party_data
    + 77            print(f"  Senior Party: {senior.patent_owner_name}")
    + 78            if senior.patent_number:
    + 79                print(f"  Senior Patent: {senior.patent_number}")
    + 80
    + 81        if decision.junior_party_data:
    + 82            junior = decision.junior_party_data
    + 83            print(f"  Junior Party: {junior.patent_owner_name}")
    + 84            if junior.publication_number:
    + 85                print(f"  Junior Publication: {junior.publication_number}")
    + 86
    + 87        if decision.document_data:
    + 88            doc = decision.document_data
    + 89            print(f"  Outcome: {doc.interference_outcome_category}")
    + 90            print(f"  Decision Type: {doc.decision_type_category}")
    + 91
    + 92except Exception as e:
    + 93    print(f"Error searching interference decisions: {e}")
    + 94
    + 95# =============================================================================
    + 96# 2. Search by Interference Outcome
    + 97# =============================================================================
    + 98
    + 99print("\n" + "=" * 80)
    +100print("2. Searching for decisions by outcome")
    +101print("=" * 80)
    +102
    +103try:
    +104    # Search for decisions with specific outcomes
    +105    response = client.search_decisions(
    +106        interference_outcome_category_q="Priority to Senior Party",
    +107        decision_date_from_q="2022-01-01",
    +108        limit=3,
    +109    )
    +110
    +111    print(
    +112        f"\nFound {response.count} decisions awarding priority to the senior party since 2022"
    +113    )
    +114    print(f"Displaying first {len(response.patent_interference_data_bag)} results:")
    +115
    +116    for decision in response.patent_interference_data_bag:
    +117        print(f"\n  Interference Number: {decision.interference_number}")
    +118
    +119        if decision.senior_party_data:
    +120            print(f"  Senior Party: {decision.senior_party_data.patent_owner_name}")
    +121            print(
    +122                f"  Senior Application: {decision.senior_party_data.application_number_text}"
    +123            )
    +124
    +125        if decision.junior_party_data:
    +126            print(f"  Junior Party: {decision.junior_party_data.patent_owner_name}")
    +127
    +128        if decision.document_data:
    +129            print(f"  Outcome: {decision.document_data.interference_outcome_category}")
    +130            print(f"  Decision Date: {decision.document_data.decision_issue_date}")
    +131
    +132except Exception as e:
    +133    print(f"Error searching by outcome: {e}")
    +134
    +135# =============================================================================
    +136# 3. Search by Party Name
    +137# =============================================================================
    +138
    +139print("\n" + "=" * 80)
    +140print("3. Searching for decisions by party name")
    +141print("=" * 80)
    +142
    +143try:
    +144    # Search for decisions involving a specific senior party
    +145    response = client.search_decisions(
    +146        senior_party_name_q="*Corp*",  # Any company with "Corp" in the name
    +147        limit=3,
    +148    )
    +149
    +150    print(f"\nFound {response.count} decisions with 'Corp' in senior party name")
    +151    print(f"Displaying first {len(response.patent_interference_data_bag)} results:")
    +152
    +153    for decision in response.patent_interference_data_bag:
    +154        print(f"\n  Interference Number: {decision.interference_number}")
    +155
    +156        if decision.senior_party_data:
    +157            senior = decision.senior_party_data
    +158            print(f"  Senior Party: {senior.patent_owner_name}")
    +159            if senior.counsel_name:
    +160                print(f"  Senior Counsel: {senior.counsel_name}")
    +161
    +162        if decision.junior_party_data:
    +163            junior = decision.junior_party_data
    +164            print(f"  Junior Party: {junior.patent_owner_name}")
    +165            if junior.counsel_name:
    +166                print(f"  Junior Counsel: {junior.counsel_name}")
    +167
    +168except Exception as e:
    +169    print(f"Error searching by party name: {e}")
    +170
    +171# =============================================================================
    +172# 4. Search by Application Numbers
    +173# =============================================================================
    +174
    +175print("\n" + "=" * 80)
    +176print("4. Searching for decisions by application numbers")
    +177print("=" * 80)
    +178
    +179try:
    +180    # Search for decisions involving specific application numbers
    +181    response = client.search_decisions(
    +182        senior_party_application_number_q="12/*",  # Applications starting with 12/
    +183        limit=3,
    +184    )
    +185
    +186    print(
    +187        f"\nFound {response.count} decisions with senior applications starting with '12/'"
    +188    )
    +189    print(f"Displaying first {len(response.patent_interference_data_bag)} results:")
    +190
    +191    for decision in response.patent_interference_data_bag:
    +192        print(f"\n  Interference Number: {decision.interference_number}")
    +193
    +194        if decision.senior_party_data:
    +195            print(
    +196                f"  Senior Application: {decision.senior_party_data.application_number_text}"
    +197            )
    +198
    +199        if decision.junior_party_data:
    +200            print(
    +201                f"  Junior Publication: {decision.junior_party_data.publication_number}"
    +202            )
    +203
    +204        if decision.document_data:
    +205            print(f"  Decision Type: {decision.document_data.decision_type_category}")
    +206
    +207except Exception as e:
    +208    print(f"Error searching by application numbers: {e}")
    +209
    +210# =============================================================================
    +211# 5. Pagination Example
    +212# =============================================================================
    +213
    +214print("\n" + "=" * 80)
    +215print("5. Paginating through interference decisions")
    +216print("=" * 80)
    +217
    +218try:
    +219    print("\nIterating through first 5 interference decisions from 2023...")
    +220    count = 0
    +221    for decision in client.paginate_decisions(
    +222        decision_date_from_q="2023-01-01",
    +223        limit=3,  # Fetch 3 per page
    +224    ):
    +225        count += 1
    +226        outcome = (
    +227            decision.document_data.interference_outcome_category
    +228            if decision.document_data
    +229            else "N/A"
    +230        )
    +231        print(f"{count}. {decision.interference_number} - {outcome}")
    +232
    +233        if count >= 5:  # Stop after 5 results for this example
    +234            break
    +235
    +236    print(f"\nDisplayed {count} decisions using pagination")
    +237
    +238except Exception as e:
    +239    print(f"Error paginating decisions: {e}")
    +240
    +241# =============================================================================
    +242# 6. Advanced Search with Multiple Criteria
    +243# =============================================================================
    +244
    +245print("\n" + "=" * 80)
    +246print("6. Advanced search with multiple criteria")
    +247print("=" * 80)
    +248
    +249try:
    +250    # Search with multiple convenience parameters
    +251    response = client.search_decisions(
    +252        decision_type_category_q="Final Decision",
    +253        decision_date_from_q="2020-01-01",
    +254        decision_date_to_q="2023-12-31",
    +255        sort="decisionDate desc",
    +256        limit=3,
    +257    )
    +258
    +259    print(f"\nFound {response.count} Final Decisions between 2020-2023")
    +260    print(f"Displaying first {len(response.patent_interference_data_bag)} results:")
    +261
    +262    for decision in response.patent_interference_data_bag:
    +263        print(f"\n  Interference Number: {decision.interference_number}")
    +264
    +265        if decision.interference_meta_data:
    +266            print(f"  Style: {decision.interference_meta_data.interference_style_name}")
    +267
    +268        if decision.document_data:
    +269            print(f"  Decision Type: {decision.document_data.decision_type_category}")
    +270            print(f"  Decision Date: {decision.document_data.decision_issue_date}")
    +271            print(f"  Outcome: {decision.document_data.interference_outcome_category}")
    +272
    +273        # Show additional parties if present
    +274        if decision.additional_party_data_bag:
    +275            print(f"  Additional Parties: {len(decision.additional_party_data_bag)}")
    +276            for party in decision.additional_party_data_bag:
    +277                print(f"    - {party.additional_party_name}")
    +278
    +279except Exception as e:
    +280    print(f"Error with advanced search: {e}")
    +281
    +282# =============================================================================
    +283# 7. Direct Query String Example
    +284# =============================================================================
    +285
    +286print("\n" + "=" * 80)
    +287print("7. Using direct query string for complex searches")
    +288print("=" * 80)
    +289
    +290try:
    +291    # Use a direct query string for more complex searches
    +292    response = client.search_decisions(
    +293        query='interferenceOutcomeCategory:"Priority to Senior Party" OR interferenceOutcomeCategory:"Priority to Junior Party"',
    +294        limit=3,
    +295    )
    +296
    +297    print(f"\nFound {response.count} decisions awarding priority to either party")
    +298    print(f"Displaying first {len(response.patent_interference_data_bag)} results:")
    +299
    +300    for decision in response.patent_interference_data_bag:
    +301        print(f"\n  Interference Number: {decision.interference_number}")
    +302
    +303        if decision.document_data:
    +304            print(f"  Outcome: {decision.document_data.interference_outcome_category}")
    +305
    +306except Exception as e:
    +307    print(f"Error with direct query: {e}")
    +308
    +309# =============================================================================
    +310# 8. Error Handling Example
    +311# =============================================================================
    +312
    +313print("\n" + "=" * 80)
    +314print("8. Error handling demonstration")
    +315print("=" * 80)
    +316
    +317try:
    +318    # Attempt a search that might return no results
    +319    print("\nAttempting search with unlikely parameters...")
    +320    response = client.search_decisions(
    +321        interference_number_q="999999",  # Very unlikely interference number
    +322        limit=1,
    +323    )
    +324
    +325    if response.count == 0:
    +326        print("No results found for the given search criteria")
    +327    else:
    +328        print(f"Found {response.count} results")
    +329
    +330except Exception as e:
    +331    print(f"Expected error occurred: {type(e).__name__}: {e}")
    +332
    +333print("\n" + "=" * 80)
    +334print("PTAB Interferences API example completed successfully!")
    +335print("=" * 80)
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/build/html/examples/ptab_trials.html b/docs/build/html/examples/ptab_trials.html new file mode 100644 index 0000000..c41bcc4 --- /dev/null +++ b/docs/build/html/examples/ptab_trials.html @@ -0,0 +1,376 @@ + + + + + + + + + PTAB Trials Example — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    PTAB Trials Example

    +
      1"""
    +  2Example usage of the pyUSPTO module for PTAB Trials API
    +  3
    +  4This example demonstrates how to use the PTABTrialsClient to interact with the USPTO PTAB
    +  5(Patent Trial and Appeal Board) Trials API. It shows how to search for trial proceedings,
    +  6documents, and decisions using various search criteria.
    +  7
    +  8PTAB Trials include:
    +  9- IPR (Inter Partes Review)
    + 10- PGR (Post-Grant Review)
    + 11- CBM (Covered Business Method)
    + 12- DER (Derivation) proceedings
    + 13"""
    + 14
    + 15import os
    + 16
    + 17from pyUSPTO import PTABTrialsClient, USPTOConfig
    + 18
    + 19# --- Initialization ---
    + 20# Choose one method to initialize the client.
    + 21# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key.
    + 22
    + 23# Method 1: Initialize the client with direct API key
    + 24print("Method 1: Initialize with direct API key")
    + 25api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE")
    + 26if api_key == "YOUR_API_KEY_HERE":
    + 27    raise ValueError(
    + 28        "WARNING: API key is not set. Please replace 'YOUR_API_KEY_HERE' or set USPTO_API_KEY environment variable."
    + 29    )
    + 30client = PTABTrialsClient(api_key=api_key)
    + 31
    + 32# Method 2: Initialize the client with USPTOConfig (alternative)
    + 33# print("\nMethod 2: Initialize with USPTOConfig")
    + 34# config_obj = USPTOConfig(
    + 35#     api_key="YOUR_API_KEY_HERE",  # Replace with your actual API key
    + 36#     ptab_base_url="https://api.uspto.gov",  # Optional, uses default if not set
    + 37# )
    + 38# client = PTABTrialsClient(config=config_obj)
    + 39
    + 40# Method 3: Initialize the client with environment variables (recommended for production)
    + 41# print("\nMethod 3: Initialize with environment variables")
    + 42# # Ensure USPTO_API_KEY is set in your environment
    + 43# try:
    + 44#     config_from_env = USPTOConfig.from_env()
    + 45#     client = PTABTrialsClient(config=config_from_env)
    + 46# except ValueError as e:
    + 47#     print(f"Error initializing from environment: {e}")
    + 48#     print("Please ensure USPTO_API_KEY environment variable is set.")
    + 49
    + 50print("\nBeginning PTAB Trials API requests with configured client:")
    + 51
    + 52# =============================================================================
    + 53# 1. Search Trial Proceedings
    + 54# =============================================================================
    + 55
    + 56print("\n" + "=" * 80)
    + 57print("1. Searching for IPR trial proceedings")
    + 58print("=" * 80)
    + 59
    + 60try:
    + 61    # Search for IPR proceedings filed in 2023
    + 62    response = client.search_proceedings(
    + 63        trial_type_code_q="IPR",
    + 64        petition_filing_date_from_q="2023-01-01",
    + 65        petition_filing_date_to_q="2023-12-31",
    + 66        limit=5,
    + 67    )
    + 68
    + 69    print(f"\nFound {response.count} IPR proceedings filed in 2023")
    + 70    print(f"Displaying first {len(response.patent_trial_proceeding_data_bag)} results:")
    + 71
    + 72    for proceeding in response.patent_trial_proceeding_data_bag:
    + 73        print(f"\n  Trial Number: {proceeding.trial_number}")
    + 74
    + 75        if proceeding.trial_meta_data:
    + 76            meta = proceeding.trial_meta_data
    + 77            print(f"  Trial Type: {meta.trial_type_code}")
    + 78            print(f"  Status: {meta.trial_status_category}")
    + 79            print(f"  Filing Date: {meta.petition_filing_date}")
    + 80
    + 81        if proceeding.patent_owner_data:
    + 82            print(f"  Patent Owner: {proceeding.patent_owner_data.patent_owner_name}")
    + 83            print(f"  Patent Number: {proceeding.patent_owner_data.patent_number}")
    + 84
    + 85        if proceeding.regular_petitioner_data:
    + 86            print(
    + 87                f"  Petitioner: {proceeding.regular_petitioner_data.real_party_in_interest_name}"
    + 88            )
    + 89
    + 90except Exception as e:
    + 91    print(f"Error searching proceedings: {e}")
    + 92
    + 93# =============================================================================
    + 94# 2. Search Trial Documents
    + 95# =============================================================================
    + 96
    + 97print("\n" + "=" * 80)
    + 98print("2. Searching for trial documents")
    + 99print("=" * 80)
    +100
    +101try:
    +102    # Search for documents in a specific trial
    +103    # Using the new convenience parameters for petitioner and patent owner
    +104    response = client.search_documents(
    +105        trial_number_q="IPR2023-00001",
    +106        document_category_q="Paper",
    +107        petitioner_real_party_in_interest_name_q="*",  # Any petitioner
    +108        patent_owner_name_q="*",  # Any patent owner
    +109        limit=5,
    +110    )
    +111
    +112    print(f"\nFound {response.count} documents")
    +113    print(f"Displaying first {len(response.patent_trial_document_data_bag)} results:")
    +114
    +115    for item in response.patent_trial_document_data_bag:
    +116        print(f"\n  Trial Number: {item.trial_number}")
    +117
    +118        if item.document_data:
    +119            doc = item.document_data
    +120            print(f"  Document Type: {doc.document_type_description_text}")
    +121            print(f"  Filing Date: {doc.document_filing_date}")
    +122            print(f"  Document Category: {doc.document_category}")
    +123
    +124            if doc.download_uri:
    +125                print(f"  Download URL: {doc.download_uri}")
    +126
    +127except Exception as e:
    +128    print(f"Error searching documents: {e}")
    +129
    +130# =============================================================================
    +131# 3. Search Trial Decisions with New Convenience Parameters
    +132# =============================================================================
    +133
    +134print("\n" + "=" * 80)
    +135print("3. Searching for trial decisions with new parameters")
    +136print("=" * 80)
    +137
    +138try:
    +139    # Using all the new convenience parameters
    +140    response = client.search_decisions(
    +141        trial_type_code_q="IPR",
    +142        decision_type_category_q="Final Written Decision",
    +143        patent_owner_name_q="*",
    +144        trial_status_category_q="Terminated",
    +145        decision_date_from_q="2023-01-01",
    +146        limit=5,
    +147    )
    +148
    +149    print(f"\nFound {response.count} Final Written Decisions in IPR proceedings")
    +150    print(f"Displaying first {len(response.patent_trial_document_data_bag)} results:")
    +151
    +152    for item in response.patent_trial_document_data_bag:
    +153        print(f"\n  Trial Number: {item.trial_number}")
    +154
    +155        if item.trial_meta_data:
    +156            print(f"  Trial Type: {item.trial_meta_data.trial_type_code}")
    +157            print(f"  Status: {item.trial_meta_data.trial_status_category}")
    +158
    +159        if item.decision_data:
    +160            decision = item.decision_data
    +161            print(f"  Decision Type: {decision.decision_type_category}")
    +162            print(f"  Decision Date: {decision.decision_issue_date}")
    +163
    +164except Exception as e:
    +165    print(f"Error searching decisions: {e}")
    +166
    +167# =============================================================================
    +168# 4. Pagination Example
    +169# =============================================================================
    +170
    +171print("\n" + "=" * 80)
    +172print("4. Paginating through proceedings")
    +173print("=" * 80)
    +174
    +175try:
    +176    print("\nIterating through first 10 IPR proceedings from 2024...")
    +177    count = 0
    +178    for proceeding in client.paginate_proceedings(
    +179        trial_type_code_q="IPR",
    +180        petition_filing_date_from_q="2024-01-01",
    +181        limit=5,  # Fetch 5 per page
    +182    ):
    +183        count += 1
    +184        print(f"{count}. {proceeding.trial_number}")
    +185
    +186        if count >= 10:  # Stop after 10 results for this example
    +187            break
    +188
    +189    print(f"\nDisplayed {count} proceedings using pagination")
    +190
    +191except Exception as e:
    +192    print(f"Error paginating proceedings: {e}")
    +193
    +194# =============================================================================
    +195# 5. Advanced Query with Additional Parameters
    +196# =============================================================================
    +197
    +198print("\n" + "=" * 80)
    +199print("5. Advanced search with additional query parameters")
    +200print("=" * 80)
    +201
    +202try:
    +203    # Search using additional_query_params for custom filters
    +204    response = client.search_proceedings(
    +205        trial_type_code_q="PGR",
    +206        trial_status_category_q="Instituted",
    +207        sort="petitionFilingDate desc",
    +208        fields="trialNumber,lastModifiedDateTime",
    +209        limit=3,
    +210    )
    +211
    +212    print(f"\nFound {response.count} Instituted PGR proceedings")
    +213    print(f"Displaying first {len(response.patent_trial_proceeding_data_bag)} results:")
    +214
    +215    for proceeding in response.patent_trial_proceeding_data_bag:
    +216        print(f"\n  Trial Number: {proceeding.trial_number}")
    +217        print(f"  Last Modified: {proceeding.last_modified_date_time}")
    +218
    +219except Exception as e:
    +220    print(f"Error with advanced search: {e}")
    +221
    +222# =============================================================================
    +223# 6. Error Handling Example
    +224# =============================================================================
    +225
    +226print("\n" + "=" * 80)
    +227print("6. Error handling demonstration")
    +228print("=" * 80)
    +229
    +230try:
    +231    # Attempt a search that might fail (invalid date format)
    +232    print("\nAttempting search with potentially invalid parameters...")
    +233    response = client.search_proceedings(
    +234        trial_number_q="INVALID-TRIAL-NUMBER",
    +235        limit=1,
    +236    )
    +237
    +238    if response.count == 0:
    +239        print("No results found for the given search criteria")
    +240    else:
    +241        print(f"Found {response.count} results")
    +242
    +243except Exception as e:
    +244    print(f"Expected error occurred: {type(e).__name__}: {e}")
    +245
    +246print("\n" + "=" * 80)
    +247print("PTAB Trials API example completed successfully!")
    +248print("=" * 80)
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html index 7eb9533..e0a41db 100644 --- a/docs/build/html/genindex.html +++ b/docs/build/html/genindex.html @@ -5,7 +5,7 @@ - Index — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Index — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -86,7 +86,9 @@

    Index

    | E | F | G + | H | I + | J | L | M | N @@ -97,6 +99,7 @@

    Index

    | T | U | X + | Y | Z

    @@ -106,9 +109,33 @@

    _

  • __init__() (pyUSPTO.clients.bulk_data.BulkDataClient method)
  • + + @@ -117,93 +144,143 @@

    _

    A

    + -
    @@ -211,19 +288,21 @@

    A

    B

    @@ -231,73 +310,97 @@

    B

    C

    - +
    @@ -329,49 +568,59 @@

    D

    E

    +
  • entity_status_data (pyUSPTO.models.patent_data.ApplicationMetaData attribute), [1]
  • -
  • event_code (pyUSPTO.models.patent_data.Event attribute) +
  • EntityStatus (class in pyUSPTO.models.patent_data)
  • @@ -379,7 +628,9 @@

    E

    F

    G

    +
    + +

    H

    + +
    @@ -547,67 +892,167 @@

    G

    I

    -

    L

    +

    J

    -

    M

    +

    L

    + +
    + +

    M

    + + @@ -616,19 +1061,23 @@

    M

    N

    @@ -636,13 +1085,17 @@

    N

    O

    @@ -650,74 +1103,132 @@

    O

    P

    - + @@ -802,19 +1391,83 @@

    P

    R

    @@ -822,23 +1475,73 @@

    R

    S

    @@ -846,45 +1549,213 @@

    S

    T

    - +

    U

    - +
    @@ -892,7 +1763,15 @@

    U

    X

    +
    + +

    Y

    + +
    @@ -900,7 +1779,7 @@

    X

    Z

    @@ -914,7 +1793,7 @@

    Z


    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/index.html b/docs/build/html/index.html index 72e44e6..3b25da1 100644 --- a/docs/build/html/index.html +++ b/docs/build/html/index.html @@ -6,7 +6,7 @@ - Welcome to pyUSPTO’s documentation! — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Welcome to pyUSPTO’s documentation! — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -93,11 +93,16 @@

    Welcome to pyUSPTO’s documentation!Models
  • Configuration
  • Exceptions
  • +
  • Warnings
  • Examples
  • Development
  • Built with Sphinx using a diff --git a/docs/build/html/installation.html b/docs/build/html/installation.html index f161e66..7af4191 100644 --- a/docs/build/html/installation.html +++ b/docs/build/html/installation.html @@ -6,7 +6,7 @@ - Installation — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Installation — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -102,7 +102,7 @@

    Installation -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv index feb4cfa..3a802fa 100644 Binary files a/docs/build/html/objects.inv and b/docs/build/html/objects.inv differ diff --git a/docs/build/html/py-modindex.html b/docs/build/html/py-modindex.html index 91f79f6..4b43cb9 100644 --- a/docs/build/html/py-modindex.html +++ b/docs/build/html/py-modindex.html @@ -5,7 +5,7 @@ - Python Module Index — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Python Module Index — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -13,7 +13,7 @@ - + @@ -104,6 +104,26 @@

    Python Module Index

        pyUSPTO.clients.patent_data + + +     + pyUSPTO.clients.petition_decisions + + + +     + pyUSPTO.clients.ptab_appeals + + + +     + pyUSPTO.clients.ptab_interferences + + + +     + pyUSPTO.clients.ptab_trials +     @@ -114,6 +134,11 @@

    Python Module Index

        pyUSPTO.exceptions + + +     + pyUSPTO.http_config +     @@ -124,6 +149,21 @@

    Python Module Index

        pyUSPTO.models.patent_data + + +     + pyUSPTO.models.ptab + + + +     + pyUSPTO.models.utils + + + +     + pyUSPTO.warnings + @@ -134,7 +174,7 @@

    Python Module Index


    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/quickstart.html b/docs/build/html/quickstart.html index 81ad36c..52382b6 100644 --- a/docs/build/html/quickstart.html +++ b/docs/build/html/quickstart.html @@ -6,7 +6,7 @@ - Quick Start — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Quick Start — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -131,7 +131,7 @@

    Examples
    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with
    Sphinx using a diff --git a/docs/build/html/search.html b/docs/build/html/search.html index c6afbd7..a5b31d3 100644 --- a/docs/build/html/search.html +++ b/docs/build/html/search.html @@ -5,7 +5,7 @@ - Search — pyUSPTO 0.1.4.dev0+ga92fa00.d20250320 documentation + Search — pyUSPTO 0.2.3.dev1+g2c5d89b38.d20251125 documentation @@ -14,7 +14,7 @@ - + @@ -97,7 +97,7 @@
    -

    © Copyright 2025, Andrew Piechocki.

    +

    © Copyright 2025, Dunlap Codding, P.C..

    Built with Sphinx using a diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js index 001beb8..33274b1 100644 --- a/docs/build/html/searchindex.js +++ b/docs/build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{"API Reference":[[3,null]],"Bulk Data Examples":[[6,null]],"Clients":[[0,null]],"Configuration":[[1,null],[11,"configuration"]],"Contents:":[[9,null]],"Contributing":[[5,"contributing"]],"Development":[[5,null]],"Examples":[[7,null],[11,"examples"]],"Exceptions":[[2,null]],"Indices and tables":[[9,"indices-and-tables"]],"Installation":[[10,null]],"Models":[[4,null]],"Patent Data Examples":[[8,null]],"Quick Start":[[11,null]],"Testing":[[5,"testing"]],"Welcome to pyUSPTO\u2019s documentation!":[[9,null]]},"docnames":["api/clients","api/config","api/exceptions","api/index","api/models","development","examples/bulk_data","examples/index","examples/patent_data","index","installation","quickstart"],"envversion":{"sphinx":65,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1},"filenames":["api\\clients.rst","api\\config.rst","api\\exceptions.rst","api\\index.rst","api\\models.rst","development.rst","examples\\bulk_data.rst","examples\\index.rst","examples\\patent_data.rst","index.rst","installation.rst","quickstart.rst"],"indexentries":{"__init__() (pyuspto.clients.bulk_data.bulkdataclient method)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.__init__",false]],"__init__() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.__init__",false]],"__init__() (pyuspto.config.usptoconfig method)":[[1,"pyUSPTO.config.USPTOConfig.__init__",false]],"a_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.a_delay_quantity",false]],"active_indicator (pyuspto.models.patent_data.attorney attribute)":[[4,"pyUSPTO.models.patent_data.Attorney.active_indicator",false]],"address (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Address",false]],"address_line_four_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.address_line_four_text",false]],"address_line_one_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.address_line_one_text",false]],"address_line_three_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.address_line_three_text",false]],"address_line_two_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.address_line_two_text",false]],"adjustment_total_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.adjustment_total_quantity",false]],"applicant (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Applicant",false]],"applicant_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.applicant_bag",false]],"applicant_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.applicant_day_delay_quantity",false]],"applicant_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.applicant_day_delay_quantity",false]],"applicant_name_text (pyuspto.models.patent_data.applicant attribute)":[[4,"pyUSPTO.models.patent_data.Applicant.applicant_name_text",false]],"application_confirmation_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_confirmation_number",false]],"application_meta_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.application_meta_data",false]],"application_number_text (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.application_number_text",false]],"application_number_text (pyuspto.models.patent_data.foreignpriority attribute)":[[4,"pyUSPTO.models.patent_data.ForeignPriority.application_number_text",false]],"application_number_text (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.application_number_text",false]],"application_status_code (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_status_code",false]],"application_status_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_status_date",false]],"application_status_description_text (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_status_description_text",false]],"application_type_category (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_type_category",false]],"application_type_code (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_type_code",false]],"application_type_label_name (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_type_label_name",false]],"applicationmetadata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData",false]],"assignee (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Assignee",false]],"assignee_address (pyuspto.models.patent_data.assignee attribute)":[[4,"pyUSPTO.models.patent_data.Assignee.assignee_address",false]],"assignee_bag (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.assignee_bag",false]],"assignee_name_text (pyuspto.models.patent_data.assignee attribute)":[[4,"pyUSPTO.models.patent_data.Assignee.assignee_name_text",false]],"assignment (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Assignment",false]],"assignment_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.assignment_bag",false]],"assignment_document_location_uri (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.assignment_document_location_uri",false]],"assignment_mailed_date (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.assignment_mailed_date",false]],"assignment_received_date (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.assignment_received_date",false]],"assignment_recorded_date (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.assignment_recorded_date",false]],"assignor (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Assignor",false]],"assignor_bag (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.assignor_bag",false]],"assignor_name (pyuspto.models.patent_data.assignor attribute)":[[4,"pyUSPTO.models.patent_data.Assignor.assignor_name",false]],"attorney (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Attorney",false]],"attorney_address_bag (pyuspto.models.patent_data.attorney attribute)":[[4,"pyUSPTO.models.patent_data.Attorney.attorney_address_bag",false]],"attorney_bag (pyuspto.models.patent_data.recordattorney attribute)":[[4,"pyUSPTO.models.patent_data.RecordAttorney.attorney_bag",false]],"b_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.b_delay_quantity",false]],"bulk_data_product_bag (pyuspto.models.bulk_data.bulkdataresponse attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse.bulk_data_product_bag",false]],"bulkdataclient (class in pyuspto.clients.bulk_data)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient",false]],"bulkdataproduct (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct",false]],"bulkdataresponse (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse",false]],"business_entity_status_category (pyuspto.models.patent_data.entitystatus attribute)":[[4,"pyUSPTO.models.patent_data.EntityStatus.business_entity_status_category",false]],"c_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.c_delay_quantity",false]],"child_application_filing_date (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_filing_date",false]],"child_application_number_text (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_number_text",false]],"child_application_number_text (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.child_application_number_text",false]],"child_application_status_code (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_status_code",false]],"child_application_status_description_text (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_status_description_text",false]],"child_continuity_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.child_continuity_bag",false]],"child_patent_number (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.child_patent_number",false]],"childcontinuity (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ChildContinuity",false]],"city_name (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.city_name",false]],"claim_parentage_type_code (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.claim_parentage_type_code",false]],"claim_parentage_type_code_description_text (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.claim_parentage_type_code_description_text",false]],"class_field (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.class_field",false]],"continuity (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Continuity",false]],"conveyance_text (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.conveyance_text",false]],"correspondence_address_bag (pyuspto.models.patent_data.applicant attribute)":[[4,"pyUSPTO.models.patent_data.Applicant.correspondence_address_bag",false]],"correspondence_address_bag (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.correspondence_address_bag",false]],"correspondence_address_bag (pyuspto.models.patent_data.inventor attribute)":[[4,"pyUSPTO.models.patent_data.Inventor.correspondence_address_bag",false]],"correspondence_address_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.correspondence_address_bag",false]],"correspondent_name_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.correspondent_name_text",false]],"count (pyuspto.models.bulk_data.bulkdataresponse attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse.count",false]],"count (pyuspto.models.bulk_data.productfilebag attribute)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag.count",false]],"count (pyuspto.models.patent_data.patentdataresponse attribute)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.count",false]],"country_code (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.country_code",false]],"country_code (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.country_code",false]],"country_name (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.country_name",false]],"cpc_classification_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.cpc_classification_bag",false]],"customer_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.customer_number",false]],"customer_number_correspondence_data (pyuspto.models.patent_data.recordattorney attribute)":[[4,"pyUSPTO.models.patent_data.RecordAttorney.customer_number_correspondence_data",false]],"customernumbercorrespondence (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence",false]],"days_of_week_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.days_of_week_text",false]],"docket_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.docket_number",false]],"documentmetadata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData",false]],"download_application_document() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.download_application_document",false]],"download_file() (pyuspto.clients.bulk_data.bulkdataclient method)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.download_file",false]],"download_patent_applications() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.download_patent_applications",false]],"download_patent_applications_post() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.download_patent_applications_post",false]],"earliest_publication_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.earliest_publication_date",false]],"earliest_publication_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.earliest_publication_number",false]],"effective_filing_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.effective_filing_date",false]],"endpoints (pyuspto.clients.bulk_data.bulkdataclient attribute)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.ENDPOINTS",false]],"endpoints (pyuspto.clients.patent_data.patentdataclient attribute)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.ENDPOINTS",false]],"entity_status_data (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.entity_status_data",false]],"entitystatus (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.EntityStatus",false]],"event (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Event",false]],"event_code (pyuspto.models.patent_data.event attribute)":[[4,"pyUSPTO.models.patent_data.Event.event_code",false]],"event_data_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.event_data_bag",false]],"event_date (pyuspto.models.patent_data.event attribute)":[[4,"pyUSPTO.models.patent_data.Event.event_date",false]],"event_date (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.event_date",false]],"event_description_text (pyuspto.models.patent_data.event attribute)":[[4,"pyUSPTO.models.patent_data.Event.event_description_text",false]],"event_description_text (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.event_description_text",false]],"event_sequence_number (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.event_sequence_number",false]],"examiner_name_text (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.examiner_name_text",false]],"execution_date (pyuspto.models.patent_data.assignor attribute)":[[4,"pyUSPTO.models.patent_data.Assignor.execution_date",false]],"extension_number (pyuspto.models.patent_data.telecommunication attribute)":[[4,"pyUSPTO.models.patent_data.Telecommunication.extension_number",false]],"file_create_date_time (pyuspto.models.patent_data.documentmetadata attribute)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData.file_create_date_time",false]],"file_data_bag (pyuspto.models.bulk_data.productfilebag attribute)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag.file_data_bag",false]],"file_data_from_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_data_from_date",false]],"file_data_to_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_data_to_date",false]],"file_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_date",false]],"file_download_uri (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_download_uri",false]],"file_last_modified_date_time (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_last_modified_date_time",false]],"file_location_uri (pyuspto.models.patent_data.documentmetadata attribute)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData.file_location_uri",false]],"file_name (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_name",false]],"file_release_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_release_date",false]],"file_size (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_size",false]],"file_type_text (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_type_text",false]],"filedata (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.FileData",false]],"filing_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.filing_date",false]],"filing_date (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.filing_date",false]],"filing_date (pyuspto.models.patent_data.foreignpriority attribute)":[[4,"pyUSPTO.models.patent_data.ForeignPriority.filing_date",false]],"filing_date (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.filing_date",false]],"first_applicant_name (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.first_applicant_name",false]],"first_inventor_name (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.first_inventor_name",false]],"first_inventor_to_file_indicator (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.first_inventor_to_file_indicator",false]],"first_inventor_to_file_indicator (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.first_inventor_to_file_indicator",false]],"first_name (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.first_name",false]],"foreign_priority_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.foreign_priority_bag",false]],"foreignpriority (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ForeignPriority",false]],"frame_number (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.frame_number",false]],"from_dict() (pyuspto.models.bulk_data.bulkdataproduct class method)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.from_dict",false]],"from_dict() (pyuspto.models.bulk_data.bulkdataresponse class method)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse.from_dict",false]],"from_dict() (pyuspto.models.bulk_data.filedata class method)":[[4,"pyUSPTO.models.bulk_data.FileData.from_dict",false]],"from_dict() (pyuspto.models.bulk_data.productfilebag class method)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag.from_dict",false]],"from_dict() (pyuspto.models.patent_data.address class method)":[[4,"pyUSPTO.models.patent_data.Address.from_dict",false]],"from_dict() (pyuspto.models.patent_data.applicant class method)":[[4,"pyUSPTO.models.patent_data.Applicant.from_dict",false]],"from_dict() (pyuspto.models.patent_data.applicationmetadata class method)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.assignee class method)":[[4,"pyUSPTO.models.patent_data.Assignee.from_dict",false]],"from_dict() (pyuspto.models.patent_data.assignment class method)":[[4,"pyUSPTO.models.patent_data.Assignment.from_dict",false]],"from_dict() (pyuspto.models.patent_data.assignor class method)":[[4,"pyUSPTO.models.patent_data.Assignor.from_dict",false]],"from_dict() (pyuspto.models.patent_data.attorney class method)":[[4,"pyUSPTO.models.patent_data.Attorney.from_dict",false]],"from_dict() (pyuspto.models.patent_data.childcontinuity class method)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.from_dict",false]],"from_dict() (pyuspto.models.patent_data.customernumbercorrespondence class method)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.from_dict",false]],"from_dict() (pyuspto.models.patent_data.documentmetadata class method)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.entitystatus class method)":[[4,"pyUSPTO.models.patent_data.EntityStatus.from_dict",false]],"from_dict() (pyuspto.models.patent_data.event class method)":[[4,"pyUSPTO.models.patent_data.Event.from_dict",false]],"from_dict() (pyuspto.models.patent_data.foreignpriority class method)":[[4,"pyUSPTO.models.patent_data.ForeignPriority.from_dict",false]],"from_dict() (pyuspto.models.patent_data.inventor class method)":[[4,"pyUSPTO.models.patent_data.Inventor.from_dict",false]],"from_dict() (pyuspto.models.patent_data.parentcontinuity class method)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patentdataresponse class method)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patentfilewrapper class method)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patenttermadjustmentdata class method)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patenttermadjustmenthistorydata class method)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.person class method)":[[4,"pyUSPTO.models.patent_data.Person.from_dict",false]],"from_dict() (pyuspto.models.patent_data.recordattorney class method)":[[4,"pyUSPTO.models.patent_data.RecordAttorney.from_dict",false]],"from_dict() (pyuspto.models.patent_data.telecommunication class method)":[[4,"pyUSPTO.models.patent_data.Telecommunication.from_dict",false]],"from_env() (pyuspto.config.usptoconfig class method)":[[1,"pyUSPTO.config.USPTOConfig.from_env",false]],"geographic_region_code (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.geographic_region_code",false]],"geographic_region_name (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.geographic_region_name",false]],"get_application_adjustment() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_adjustment",false]],"get_application_assignment() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_assignment",false]],"get_application_associated_documents() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_associated_documents",false]],"get_application_attorney() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_attorney",false]],"get_application_continuity() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_continuity",false]],"get_application_documents() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_documents",false]],"get_application_foreign_priority() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_foreign_priority",false]],"get_application_metadata() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_metadata",false]],"get_application_transactions() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_application_transactions",false]],"get_patent_applications() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_patent_applications",false]],"get_patent_by_application_number() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_patent_by_application_number",false]],"get_patent_status_codes() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.get_patent_status_codes",false]],"get_product_by_id() (pyuspto.clients.bulk_data.bulkdataclient method)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.get_product_by_id",false]],"get_products() (pyuspto.clients.bulk_data.bulkdataclient method)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.get_products",false]],"grant_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.grant_date",false]],"grant_date (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.grant_date",false]],"grant_document_meta_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.grant_document_meta_data",false]],"group_art_unit_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.group_art_unit_number",false]],"international_registration_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.international_registration_number",false]],"international_registration_publication_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.international_registration_publication_date",false]],"invention_title (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.invention_title",false]],"inventor (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Inventor",false]],"inventor_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.inventor_bag",false]],"inventor_name_text (pyuspto.models.patent_data.inventor attribute)":[[4,"pyUSPTO.models.patent_data.Inventor.inventor_name_text",false]],"ip_office_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.ip_office_day_delay_quantity",false]],"ip_office_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.ip_office_day_delay_quantity",false]],"ip_office_name (pyuspto.models.patent_data.foreignpriority attribute)":[[4,"pyUSPTO.models.patent_data.ForeignPriority.ip_office_name",false]],"last_ingestion_date_time (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.last_ingestion_date_time",false]],"last_modified_date_time (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.last_modified_date_time",false]],"last_name (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.last_name",false]],"middle_name (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.middle_name",false]],"mime_type_identifier_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.mime_type_identifier_array_text",false]],"module":[[0,"module-pyUSPTO.clients.bulk_data",false],[0,"module-pyUSPTO.clients.patent_data",false],[1,"module-pyUSPTO.config",false],[2,"module-pyUSPTO.exceptions",false],[4,"module-pyUSPTO.models.bulk_data",false],[4,"module-pyUSPTO.models.patent_data",false]],"name_line_one_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.name_line_one_text",false]],"name_line_two_text (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.name_line_two_text",false]],"name_prefix (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.name_prefix",false]],"name_suffix (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.name_suffix",false]],"national_stage_indicator (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.national_stage_indicator",false]],"non_overlapping_day_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.non_overlapping_day_quantity",false]],"organization_standard_name (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.organization_standard_name",false]],"originating_event_sequence_number (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.originating_event_sequence_number",false]],"overlapping_day_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.overlapping_day_quantity",false]],"paginate_patents() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.paginate_patents",false]],"paginate_products() (pyuspto.clients.bulk_data.bulkdataclient method)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.paginate_products",false]],"parent_application_filing_date (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_filing_date",false]],"parent_application_number_text (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.parent_application_number_text",false]],"parent_application_number_text (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_number_text",false]],"parent_application_status_code (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_status_code",false]],"parent_application_status_description_text (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_status_description_text",false]],"parent_continuity_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.parent_continuity_bag",false]],"parent_patent_number (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_patent_number",false]],"parentcontinuity (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ParentContinuity",false]],"patent_file_wrapper_data_bag (pyuspto.models.patent_data.patentdataresponse attribute)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.patent_file_wrapper_data_bag",false]],"patent_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.patent_number",false]],"patent_number (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.patent_number",false]],"patent_term_adjustment_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.patent_term_adjustment_data",false]],"patent_term_adjustment_history_data_bag (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.patent_term_adjustment_history_data_bag",false]],"patentdataclient (class in pyuspto.clients.patent_data)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient",false]],"patentdataresponse (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse",false]],"patentfilewrapper (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper",false]],"patenttermadjustmentdata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData",false]],"patenttermadjustmenthistorydata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData",false]],"patron_identifier (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.patron_identifier",false]],"pct_publication_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.pct_publication_date",false]],"pct_publication_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.pct_publication_number",false]],"person (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Person",false]],"pgpub_document_meta_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.pgpub_document_meta_data",false]],"postal_address_category (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.postal_address_category",false]],"postal_code (pyuspto.models.patent_data.address attribute)":[[4,"pyUSPTO.models.patent_data.Address.postal_code",false]],"power_of_attorney_address_bag (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.power_of_attorney_address_bag",false]],"power_of_attorney_bag (pyuspto.models.patent_data.recordattorney attribute)":[[4,"pyUSPTO.models.patent_data.RecordAttorney.power_of_attorney_bag",false]],"preferred_name (pyuspto.models.patent_data.person attribute)":[[4,"pyUSPTO.models.patent_data.Person.preferred_name",false]],"product_dataset_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_dataset_array_text",false]],"product_dataset_category_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_dataset_category_array_text",false]],"product_description_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_description_text",false]],"product_file_bag (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_file_bag",false]],"product_file_total_quantity (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_file_total_quantity",false]],"product_frequency_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_frequency_text",false]],"product_from_date (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_from_date",false]],"product_identifier (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_identifier",false]],"product_identifier (pyuspto.models.patent_data.documentmetadata attribute)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData.product_identifier",false]],"product_label_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_label_array_text",false]],"product_title_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_title_text",false]],"product_to_date (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_to_date",false]],"product_total_file_size (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_total_file_size",false]],"productfilebag (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag",false]],"pta_pte_code (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.pta_pte_code",false]],"publication_category_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.publication_category_bag",false]],"publication_date_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.publication_date_bag",false]],"publication_sequence_number_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.publication_sequence_number_bag",false]],"pyuspto.clients.bulk_data":[[0,"module-pyUSPTO.clients.bulk_data",false]],"pyuspto.clients.patent_data":[[0,"module-pyUSPTO.clients.patent_data",false]],"pyuspto.config":[[1,"module-pyUSPTO.config",false]],"pyuspto.exceptions":[[2,"module-pyUSPTO.exceptions",false]],"pyuspto.models.bulk_data":[[4,"module-pyUSPTO.models.bulk_data",false]],"pyuspto.models.patent_data":[[4,"module-pyUSPTO.models.patent_data",false]],"record_attorney (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.record_attorney",false]],"recordattorney (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.RecordAttorney",false]],"reel_and_frame_number (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.reel_and_frame_number",false]],"reel_number (pyuspto.models.patent_data.assignment attribute)":[[4,"pyUSPTO.models.patent_data.Assignment.reel_number",false]],"registered_practitioner_category (pyuspto.models.patent_data.attorney attribute)":[[4,"pyUSPTO.models.patent_data.Attorney.registered_practitioner_category",false]],"registration_number (pyuspto.models.patent_data.attorney attribute)":[[4,"pyUSPTO.models.patent_data.Attorney.registration_number",false]],"search_patent_applications_post() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.search_patent_applications_post",false]],"search_patent_status_codes_post() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.search_patent_status_codes_post",false]],"search_patents() (pyuspto.clients.patent_data.patentdataclient method)":[[0,"pyUSPTO.clients.patent_data.PatentDataClient.search_patents",false]],"search_products() (pyuspto.clients.bulk_data.bulkdataclient method)":[[0,"pyUSPTO.clients.bulk_data.BulkDataClient.search_products",false]],"small_entity_status_indicator (pyuspto.models.patent_data.entitystatus attribute)":[[4,"pyUSPTO.models.patent_data.EntityStatus.small_entity_status_indicator",false]],"status_code (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.status_code",false]],"status_description_text (pyuspto.models.patent_data.continuity attribute)":[[4,"pyUSPTO.models.patent_data.Continuity.status_description_text",false]],"subclass (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.subclass",false]],"telecom_type_code (pyuspto.models.patent_data.telecommunication attribute)":[[4,"pyUSPTO.models.patent_data.Telecommunication.telecom_type_code",false]],"telecommunication (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Telecommunication",false]],"telecommunication_address_bag (pyuspto.models.patent_data.attorney attribute)":[[4,"pyUSPTO.models.patent_data.Attorney.telecommunication_address_bag",false]],"telecommunication_address_bag (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.telecommunication_address_bag",false]],"telecommunication_number (pyuspto.models.patent_data.telecommunication attribute)":[[4,"pyUSPTO.models.patent_data.Telecommunication.telecommunication_number",false]],"to_dict() (pyuspto.models.bulk_data.bulkdataresponse method)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse.to_dict",false]],"to_dict() (pyuspto.models.patent_data.patentdataresponse method)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.to_dict",false]],"uspc_symbol_text (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.uspc_symbol_text",false]],"usptoapiautherror":[[2,"pyUSPTO.exceptions.USPTOApiAuthError",false]],"usptoapierror":[[2,"pyUSPTO.exceptions.USPTOApiError",false]],"usptoapinotfounderror":[[2,"pyUSPTO.exceptions.USPTOApiNotFoundError",false]],"usptoapiratelimiterror":[[2,"pyUSPTO.exceptions.USPTOApiRateLimitError",false]],"usptoconfig (class in pyuspto.config)":[[1,"pyUSPTO.config.USPTOConfig",false]],"xml_file_name (pyuspto.models.patent_data.documentmetadata attribute)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData.xml_file_name",false]],"zip_file_name (pyuspto.models.patent_data.documentmetadata attribute)":[[4,"pyUSPTO.models.patent_data.DocumentMetaData.zip_file_name",false]]},"objects":{"pyUSPTO":[[1,0,0,"-","config"],[2,0,0,"-","exceptions"]],"pyUSPTO.clients":[[0,0,0,"-","bulk_data"],[0,0,0,"-","patent_data"]],"pyUSPTO.clients.bulk_data":[[0,1,1,"","BulkDataClient"]],"pyUSPTO.clients.bulk_data.BulkDataClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","download_file"],[0,3,1,"","get_product_by_id"],[0,3,1,"","get_products"],[0,3,1,"","paginate_products"],[0,3,1,"","search_products"]],"pyUSPTO.clients.patent_data":[[0,1,1,"","PatentDataClient"]],"pyUSPTO.clients.patent_data.PatentDataClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","download_application_document"],[0,3,1,"","download_patent_applications"],[0,3,1,"","download_patent_applications_post"],[0,3,1,"","get_application_adjustment"],[0,3,1,"","get_application_assignment"],[0,3,1,"","get_application_associated_documents"],[0,3,1,"","get_application_attorney"],[0,3,1,"","get_application_continuity"],[0,3,1,"","get_application_documents"],[0,3,1,"","get_application_foreign_priority"],[0,3,1,"","get_application_metadata"],[0,3,1,"","get_application_transactions"],[0,3,1,"","get_patent_applications"],[0,3,1,"","get_patent_by_application_number"],[0,3,1,"","get_patent_status_codes"],[0,3,1,"","paginate_patents"],[0,3,1,"","search_patent_applications_post"],[0,3,1,"","search_patent_status_codes_post"],[0,3,1,"","search_patents"]],"pyUSPTO.config":[[1,1,1,"","USPTOConfig"]],"pyUSPTO.config.USPTOConfig":[[1,3,1,"","__init__"],[1,3,1,"","from_env"]],"pyUSPTO.exceptions":[[2,4,1,"","USPTOApiAuthError"],[2,4,1,"","USPTOApiError"],[2,4,1,"","USPTOApiNotFoundError"],[2,4,1,"","USPTOApiRateLimitError"]],"pyUSPTO.models":[[4,0,0,"-","bulk_data"],[4,0,0,"-","patent_data"]],"pyUSPTO.models.bulk_data":[[4,1,1,"","BulkDataProduct"],[4,1,1,"","BulkDataResponse"],[4,1,1,"","FileData"],[4,1,1,"","ProductFileBag"]],"pyUSPTO.models.bulk_data.BulkDataProduct":[[4,2,1,"","days_of_week_text"],[4,3,1,"","from_dict"],[4,2,1,"","last_modified_date_time"],[4,2,1,"","mime_type_identifier_array_text"],[4,2,1,"","product_dataset_array_text"],[4,2,1,"","product_dataset_category_array_text"],[4,2,1,"","product_description_text"],[4,2,1,"","product_file_bag"],[4,2,1,"","product_file_total_quantity"],[4,2,1,"","product_frequency_text"],[4,2,1,"","product_from_date"],[4,2,1,"","product_identifier"],[4,2,1,"","product_label_array_text"],[4,2,1,"","product_title_text"],[4,2,1,"","product_to_date"],[4,2,1,"","product_total_file_size"]],"pyUSPTO.models.bulk_data.BulkDataResponse":[[4,2,1,"","bulk_data_product_bag"],[4,2,1,"","count"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.bulk_data.FileData":[[4,2,1,"","file_data_from_date"],[4,2,1,"","file_data_to_date"],[4,2,1,"","file_date"],[4,2,1,"","file_download_uri"],[4,2,1,"","file_last_modified_date_time"],[4,2,1,"","file_name"],[4,2,1,"","file_release_date"],[4,2,1,"","file_size"],[4,2,1,"","file_type_text"],[4,3,1,"","from_dict"]],"pyUSPTO.models.bulk_data.ProductFileBag":[[4,2,1,"","count"],[4,2,1,"","file_data_bag"],[4,3,1,"","from_dict"]],"pyUSPTO.models.patent_data":[[4,1,1,"","Address"],[4,1,1,"","Applicant"],[4,1,1,"","ApplicationMetaData"],[4,1,1,"","Assignee"],[4,1,1,"","Assignment"],[4,1,1,"","Assignor"],[4,1,1,"","Attorney"],[4,1,1,"","ChildContinuity"],[4,1,1,"","Continuity"],[4,1,1,"","CustomerNumberCorrespondence"],[4,1,1,"","DocumentMetaData"],[4,1,1,"","EntityStatus"],[4,1,1,"","Event"],[4,1,1,"","ForeignPriority"],[4,1,1,"","Inventor"],[4,1,1,"","ParentContinuity"],[4,1,1,"","PatentDataResponse"],[4,1,1,"","PatentFileWrapper"],[4,1,1,"","PatentTermAdjustmentData"],[4,1,1,"","PatentTermAdjustmentHistoryData"],[4,1,1,"","Person"],[4,1,1,"","RecordAttorney"],[4,1,1,"","Telecommunication"]],"pyUSPTO.models.patent_data.Address":[[4,2,1,"","address_line_four_text"],[4,2,1,"","address_line_one_text"],[4,2,1,"","address_line_three_text"],[4,2,1,"","address_line_two_text"],[4,2,1,"","city_name"],[4,2,1,"","correspondent_name_text"],[4,2,1,"","country_code"],[4,2,1,"","country_name"],[4,3,1,"","from_dict"],[4,2,1,"","geographic_region_code"],[4,2,1,"","geographic_region_name"],[4,2,1,"","name_line_one_text"],[4,2,1,"","name_line_two_text"],[4,2,1,"","postal_address_category"],[4,2,1,"","postal_code"]],"pyUSPTO.models.patent_data.Applicant":[[4,2,1,"","applicant_name_text"],[4,2,1,"","correspondence_address_bag"],[4,3,1,"","from_dict"]],"pyUSPTO.models.patent_data.ApplicationMetaData":[[4,2,1,"","applicant_bag"],[4,2,1,"","application_confirmation_number"],[4,2,1,"","application_status_code"],[4,2,1,"","application_status_date"],[4,2,1,"","application_status_description_text"],[4,2,1,"","application_type_category"],[4,2,1,"","application_type_code"],[4,2,1,"","application_type_label_name"],[4,2,1,"","class_field"],[4,2,1,"","cpc_classification_bag"],[4,2,1,"","customer_number"],[4,2,1,"","docket_number"],[4,2,1,"","earliest_publication_date"],[4,2,1,"","earliest_publication_number"],[4,2,1,"","effective_filing_date"],[4,2,1,"","entity_status_data"],[4,2,1,"","examiner_name_text"],[4,2,1,"","filing_date"],[4,2,1,"","first_applicant_name"],[4,2,1,"","first_inventor_name"],[4,2,1,"","first_inventor_to_file_indicator"],[4,3,1,"","from_dict"],[4,2,1,"","grant_date"],[4,2,1,"","group_art_unit_number"],[4,2,1,"","international_registration_number"],[4,2,1,"","international_registration_publication_date"],[4,2,1,"","invention_title"],[4,2,1,"","inventor_bag"],[4,2,1,"","national_stage_indicator"],[4,2,1,"","patent_number"],[4,2,1,"","pct_publication_date"],[4,2,1,"","pct_publication_number"],[4,2,1,"","publication_category_bag"],[4,2,1,"","publication_date_bag"],[4,2,1,"","publication_sequence_number_bag"],[4,2,1,"","subclass"],[4,2,1,"","uspc_symbol_text"]],"pyUSPTO.models.patent_data.Assignee":[[4,2,1,"","assignee_address"],[4,2,1,"","assignee_name_text"],[4,3,1,"","from_dict"]],"pyUSPTO.models.patent_data.Assignment":[[4,2,1,"","assignee_bag"],[4,2,1,"","assignment_document_location_uri"],[4,2,1,"","assignment_mailed_date"],[4,2,1,"","assignment_received_date"],[4,2,1,"","assignment_recorded_date"],[4,2,1,"","assignor_bag"],[4,2,1,"","conveyance_text"],[4,2,1,"","correspondence_address_bag"],[4,2,1,"","frame_number"],[4,3,1,"","from_dict"],[4,2,1,"","reel_and_frame_number"],[4,2,1,"","reel_number"]],"pyUSPTO.models.patent_data.Assignor":[[4,2,1,"","assignor_name"],[4,2,1,"","execution_date"],[4,3,1,"","from_dict"]],"pyUSPTO.models.patent_data.Attorney":[[4,2,1,"","active_indicator"],[4,2,1,"","attorney_address_bag"],[4,3,1,"","from_dict"],[4,2,1,"","registered_practitioner_category"],[4,2,1,"","registration_number"],[4,2,1,"","telecommunication_address_bag"]],"pyUSPTO.models.patent_data.ChildContinuity":[[4,2,1,"","child_application_filing_date"],[4,2,1,"","child_application_number_text"],[4,2,1,"","child_application_status_code"],[4,2,1,"","child_application_status_description_text"],[4,2,1,"","child_patent_number"],[4,3,1,"","from_dict"],[4,2,1,"","parent_application_number_text"]],"pyUSPTO.models.patent_data.Continuity":[[4,2,1,"","application_number_text"],[4,2,1,"","claim_parentage_type_code"],[4,2,1,"","claim_parentage_type_code_description_text"],[4,2,1,"","filing_date"],[4,2,1,"","first_inventor_to_file_indicator"],[4,2,1,"","patent_number"],[4,2,1,"","status_code"],[4,2,1,"","status_description_text"]],"pyUSPTO.models.patent_data.CustomerNumberCorrespondence":[[4,3,1,"","from_dict"],[4,2,1,"","organization_standard_name"],[4,2,1,"","patron_identifier"],[4,2,1,"","power_of_attorney_address_bag"],[4,2,1,"","telecommunication_address_bag"]],"pyUSPTO.models.patent_data.DocumentMetaData":[[4,2,1,"","file_create_date_time"],[4,2,1,"","file_location_uri"],[4,3,1,"","from_dict"],[4,2,1,"","product_identifier"],[4,2,1,"","xml_file_name"],[4,2,1,"","zip_file_name"]],"pyUSPTO.models.patent_data.EntityStatus":[[4,2,1,"","business_entity_status_category"],[4,3,1,"","from_dict"],[4,2,1,"","small_entity_status_indicator"]],"pyUSPTO.models.patent_data.Event":[[4,2,1,"","event_code"],[4,2,1,"","event_date"],[4,2,1,"","event_description_text"],[4,3,1,"","from_dict"]],"pyUSPTO.models.patent_data.ForeignPriority":[[4,2,1,"","application_number_text"],[4,2,1,"","filing_date"],[4,3,1,"","from_dict"],[4,2,1,"","ip_office_name"]],"pyUSPTO.models.patent_data.Inventor":[[4,2,1,"","correspondence_address_bag"],[4,3,1,"","from_dict"],[4,2,1,"","inventor_name_text"]],"pyUSPTO.models.patent_data.ParentContinuity":[[4,2,1,"","child_application_number_text"],[4,3,1,"","from_dict"],[4,2,1,"","parent_application_filing_date"],[4,2,1,"","parent_application_number_text"],[4,2,1,"","parent_application_status_code"],[4,2,1,"","parent_application_status_description_text"],[4,2,1,"","parent_patent_number"]],"pyUSPTO.models.patent_data.PatentDataResponse":[[4,2,1,"","count"],[4,3,1,"","from_dict"],[4,2,1,"","patent_file_wrapper_data_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.PatentFileWrapper":[[4,2,1,"","application_meta_data"],[4,2,1,"","application_number_text"],[4,2,1,"","assignment_bag"],[4,2,1,"","child_continuity_bag"],[4,2,1,"","correspondence_address_bag"],[4,2,1,"","event_data_bag"],[4,2,1,"","foreign_priority_bag"],[4,3,1,"","from_dict"],[4,2,1,"","grant_document_meta_data"],[4,2,1,"","last_ingestion_date_time"],[4,2,1,"","parent_continuity_bag"],[4,2,1,"","patent_term_adjustment_data"],[4,2,1,"","pgpub_document_meta_data"],[4,2,1,"","record_attorney"]],"pyUSPTO.models.patent_data.PatentTermAdjustmentData":[[4,2,1,"","a_delay_quantity"],[4,2,1,"","adjustment_total_quantity"],[4,2,1,"","applicant_day_delay_quantity"],[4,2,1,"","b_delay_quantity"],[4,2,1,"","c_delay_quantity"],[4,2,1,"","filing_date"],[4,3,1,"","from_dict"],[4,2,1,"","grant_date"],[4,2,1,"","ip_office_day_delay_quantity"],[4,2,1,"","non_overlapping_day_quantity"],[4,2,1,"","overlapping_day_quantity"],[4,2,1,"","patent_term_adjustment_history_data_bag"]],"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData":[[4,2,1,"","applicant_day_delay_quantity"],[4,2,1,"","event_date"],[4,2,1,"","event_description_text"],[4,2,1,"","event_sequence_number"],[4,3,1,"","from_dict"],[4,2,1,"","ip_office_day_delay_quantity"],[4,2,1,"","originating_event_sequence_number"],[4,2,1,"","pta_pte_code"]],"pyUSPTO.models.patent_data.Person":[[4,2,1,"","country_code"],[4,2,1,"","first_name"],[4,3,1,"","from_dict"],[4,2,1,"","last_name"],[4,2,1,"","middle_name"],[4,2,1,"","name_prefix"],[4,2,1,"","name_suffix"],[4,2,1,"","preferred_name"]],"pyUSPTO.models.patent_data.RecordAttorney":[[4,2,1,"","attorney_bag"],[4,2,1,"","customer_number_correspondence_data"],[4,3,1,"","from_dict"],[4,2,1,"","power_of_attorney_bag"]],"pyUSPTO.models.patent_data.Telecommunication":[[4,2,1,"","extension_number"],[4,3,1,"","from_dict"],[4,2,1,"","telecom_type_code"],[4,2,1,"","telecommunication_number"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"],"3":["py","method","Python method"],"4":["py","exception","Python exception"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute","3":"py:method","4":"py:exception"},"terms":{"0":0,"1":11,"2":11,"25":0,"3":11,"For":10,"It":0,"The":[0,5],"__init__":[0,1],"a_delay_quant":4,"active_ind":4,"address":[3,4],"address_line_four_text":4,"address_line_one_text":4,"address_line_three_text":4,"address_line_two_text":4,"adjust":[0,4],"adjustment_total_quant":4,"agent":0,"all":[0,5],"allow":0,"an":[0,4],"ani":[0,4],"api":[0,1,2,4,9,11],"api_kei":[0,1,11],"applic":[0,3,4],"applicant_bag":4,"applicant_day_delay_quant":4,"applicant_nam":0,"applicant_name_text":4,"application_adjust":0,"application_assign":0,"application_associated_docu":0,"application_attornei":0,"application_by_numb":0,"application_confirmation_numb":4,"application_continu":0,"application_docu":0,"application_foreign_prior":0,"application_meta_data":4,"application_metadata":0,"application_numb":0,"application_number_text":4,"application_status_cod":4,"application_status_d":4,"application_status_description_text":4,"application_transact":0,"application_type_categori":4,"application_type_cod":4,"application_type_label_nam":4,"applicationmetadata":[3,4],"applications_search":0,"applications_search_download":0,"argument":0,"arrai":0,"assign":[0,3,4],"assigne":[0,3,4],"assignee_address":4,"assignee_bag":4,"assignee_nam":0,"assignee_name_text":4,"assignment_bag":4,"assignment_document_location_uri":4,"assignment_mailed_d":4,"assignment_received_d":4,"assignment_recorded_d":4,"assignor":[3,4],"assignor_bag":4,"assignor_nam":4,"associ":0,"attornei":[0,3,4],"attorney_address_bag":4,"attorney_bag":4,"authent":[0,1,2],"b_delay_quant":4,"base":[0,1,2,4],"base_url":0,"baseusptocli":0,"bool":[0,4],"bulk":[0,1,4,7,9],"bulk_data":[0,4],"bulk_data_base_url":[0,1],"bulk_data_product_bag":4,"bulkdatacli":[0,3,11],"bulkdataproduct":[0,3,4],"bulkdatarespons":[0,3,4],"business_entity_status_categori":4,"c_delay_quant":4,"categori":0,"cd":10,"child":4,"child_application_filing_d":4,"child_application_number_text":4,"child_application_status_cod":4,"child_application_status_description_text":4,"child_continuity_bag":4,"child_patent_numb":4,"childcontinu":[3,4],"city_nam":4,"claim_parentage_type_cod":4,"claim_parentage_type_code_description_text":4,"class":[0,1,2,4],"class_field":4,"classif":0,"classmethod":[1,4],"client":[1,2,3,9,11],"client1":11,"client2":11,"client3":11,"clone":10,"code":0,"collect":0,"com":10,"comprehens":5,"config":[0,1,11],"config_from_env":11,"configur":[3,9],"contain":[0,4],"continu":[0,3,4],"contribut":9,"convert":4,"conveyance_text":4,"correspond":4,"correspondence_address_bag":4,"correspondent_name_text":4,"count":[4,11],"country_cod":4,"country_nam":4,"cov":5,"coverag":5,"cpc":0,"cpc_classification_bag":4,"creat":[1,4],"criteria":0,"csv":0,"custom":4,"customer_numb":4,"customer_number_correspondence_data":4,"customernumbercorrespond":[3,4],"data":[0,1,4,7,9],"dataset":[0,1],"date":0,"days_of_week_text":4,"dd":0,"default":[0,1],"deprec":0,"descript":0,"destin":0,"detail":[0,5],"develop":[9,10],"dict":[0,4],"dictionari":[0,4],"direct":11,"directori":0,"docket_numb":4,"document":[0,4],"document_id":0,"documentmetadata":[3,4],"download":0,"download_application_docu":0,"download_docu":0,"download_fil":0,"download_patent_appl":0,"download_patent_applications_post":0,"download_request":0,"dunlapcoddingpc":10,"e":10,"each":0,"earliest_publication_d":4,"earliest_publication_numb":4,"effective_filing_d":4,"element":4,"enabl":0,"endpoint":0,"entiti":4,"entity_status_data":4,"entitystatu":[3,4],"environ":[1,11],"error":2,"event":[3,4],"event_cod":4,"event_d":4,"event_data_bag":4,"event_description_text":4,"event_sequence_numb":4,"examiner_name_text":4,"exampl":9,"exceed":2,"except":[3,9],"execution_d":4,"extension_numb":4,"f":11,"facet":0,"factori":4,"field":0,"file":[0,4,5],"file_create_date_tim":4,"file_d":4,"file_data":0,"file_data_bag":4,"file_data_from_d":[0,4],"file_data_to_d":[0,4],"file_download_uri":[0,4],"file_last_modified_date_tim":4,"file_location_uri":4,"file_nam":4,"file_release_d":4,"file_s":4,"file_typ":0,"file_type_text":4,"filedata":[0,3,4],"filing_d":4,"filing_date_from":0,"filing_date_to":0,"filter":0,"first_applicant_nam":4,"first_inventor_nam":4,"first_inventor_to_file_ind":4,"first_nam":4,"float":4,"follow":0,"foreign":[0,4],"foreign_priority_bag":4,"foreignprior":[3,4],"format":0,"found":[2,11],"frame_numb":4,"from":[0,1,4,11],"from_dat":0,"from_dict":4,"from_env":[1,11],"geographic_region_cod":4,"geographic_region_nam":4,"get":[0,11],"get_application_adjust":0,"get_application_assign":0,"get_application_associated_docu":0,"get_application_attornei":0,"get_application_continu":0,"get_application_docu":0,"get_application_foreign_prior":0,"get_application_metadata":0,"get_application_transact":0,"get_patent_appl":0,"get_patent_by_application_numb":0,"get_patent_status_cod":0,"get_product":0,"get_product_by_id":0,"git":10,"github":10,"gov":[0,1],"grant":0,"grant_dat":4,"grant_date_from":0,"grant_date_to":0,"grant_document_meta_data":4,"group_art_unit_numb":4,"guid":11,"help":11,"histori":4,"how":5,"http":[0,1,10],"i":0,"id":0,"identifi":0,"import":11,"includ":[0,5],"include_fil":0,"index":9,"inform":[0,4],"initi":[0,1,11],"instal":9,"instanc":[0,1],"instead":0,"int":[0,4],"interact":0,"international_registration_numb":4,"international_registration_publication_d":4,"invention_titl":4,"inventor":[0,3,4,11],"inventor_bag":4,"inventor_nam":[0,11],"inventor_name_text":4,"inventor_search":11,"ip_office_day_delay_quant":4,"ip_office_nam":4,"iter":0,"json":0,"kei":[0,1,11],"keyword":0,"kwarg":0,"label":0,"last_ingestion_date_tim":4,"last_modified_date_tim":4,"last_nam":4,"latest":0,"level":4,"librari":[5,11],"limit":[0,2],"list":[0,4],"m":5,"manag":1,"match":0,"md":5,"messag":2,"meta":0,"metadata":[0,4],"method":[0,11],"middle_nam":4,"mime_type_identifier_array_text":4,"mm":0,"model":[3,9],"modul":[0,1,2,4,9],"name":[0,11],"name_line_one_text":4,"name_line_two_text":4,"name_prefix":4,"name_suffix":4,"national_stage_ind":4,"non_overlapping_day_quant":4,"none":[0,1,2,4],"number":[0,4],"o":11,"object":[0,1,4],"odp":[0,4],"offset":0,"onli":0,"open":[0,4],"option":[0,1,4],"order":0,"organization_standard_nam":4,"originating_event_sequence_numb":4,"overlapping_day_quant":4,"page":9,"pagin":0,"paginate_pat":0,"paginate_product":0,"param":0,"paramet":[0,1],"parent":4,"parent_application_filing_d":4,"parent_application_number_text":4,"parent_application_status_cod":4,"parent_application_status_description_text":4,"parent_continuity_bag":4,"parent_patent_numb":4,"parentcontinu":[3,4],"pass":0,"patent":[0,1,4,7,9,11],"patent_data":[0,4],"patent_data_base_url":[0,1],"patent_file_wrapper_data_bag":4,"patent_numb":[0,4],"patent_term_adjustment_data":4,"patent_term_adjustment_history_data_bag":4,"patentdatacli":[0,3,11],"patentdatarespons":[0,3,4],"patentfilewrapp":[0,3,4],"patenttermadjustmentdata":[3,4],"patenttermadjustmenthistorydata":[3,4],"path":0,"patron_identifi":4,"payload":0,"pct_publication_d":4,"pct_publication_numb":4,"person":[3,4],"pgpub_document_meta_data":4,"pip":10,"portal":[0,4],"posit":0,"post":0,"postal_address_categori":4,"postal_cod":4,"power_of_attorney_address_bag":4,"power_of_attorney_bag":4,"preferred_nam":4,"print":11,"prioriti":[0,4],"product":[0,4],"product_by_id":0,"product_dataset_array_text":4,"product_dataset_category_array_text":4,"product_descript":0,"product_description_text":4,"product_file_bag":4,"product_file_total_quant":4,"product_frequency_text":4,"product_from_d":4,"product_id":0,"product_identifi":4,"product_label_array_text":4,"product_short_nam":0,"product_titl":0,"product_title_text":4,"product_to_d":4,"product_total_file_s":4,"productfilebag":[3,4],"products_search":0,"project":5,"provid":[0,1,2,4],"pta_pte_cod":4,"publication_category_bag":4,"publication_date_bag":4,"publication_sequence_number_bag":4,"pytest":5,"python":5,"pyuspto":[0,1,2,4,5,10,11],"q":0,"queri":0,"quick":9,"rang":0,"rangefilt":0,"rate":2,"record":[0,4],"record_attornei":4,"recordattornei":[3,4],"reel_and_frame_numb":4,"reel_numb":4,"refer":9,"registered_practitioner_categori":4,"registration_numb":4,"relat":4,"report":5,"repres":4,"resourc":2,"respons":[0,4],"result":0,"retriev":0,"return":[0,1,4],"run":5,"save":0,"search":[0,9,11],"search_pat":[0,11],"search_patent_applications_post":0,"search_patent_status_codes_post":0,"search_product":0,"search_request":0,"see":5,"short":0,"should":0,"skip":0,"small_entity_status_ind":4,"smith":11,"sort":0,"sourc":[0,1,2,4],"specif":0,"specifi":0,"start":[0,9],"statu":[0,4],"status_cod":[0,2,4],"status_description_text":4,"str":[0,1,4],"string":0,"subclass":4,"suit":5,"telecom_type_cod":4,"telecommun":[3,4],"telecommunication_address_bag":4,"telecommunication_numb":4,"term":[0,4],"test":9,"text":0,"thi":[0,1,2,4,11],"through":0,"titl":0,"to_dat":0,"to_dict":4,"top":4,"transact":0,"type":[0,1,4],"until":0,"upon":0,"url":[0,1],"us":[0,11],"uspc_symbol_text":4,"uspto":[0,1,2,4],"uspto_api_kei":[1,11],"usptoapiautherror":[2,3],"usptoapierror":[2,3],"usptoapinotfounderror":[2,3],"usptoapiratelimiterror":[2,3],"usptoconfig":[0,1,3,11],"v1":[0,1],"variabl":[1,11],"variou":0,"where":0,"whether":0,"wrapper":4,"xml_file_nam":4,"yield":0,"you":[0,11],"your_api_key_her":11,"yyyi":0,"zip_file_nam":4},"titles":["Clients","Configuration","Exceptions","API Reference","Models","Development","Bulk Data Examples","Examples","Patent Data Examples","Welcome to pyUSPTO\u2019s documentation!","Installation","Quick Start"],"titleterms":{"":9,"api":3,"bulk":6,"client":0,"configur":[1,11],"content":9,"contribut":5,"data":[6,8],"develop":5,"document":9,"exampl":[6,7,8,11],"except":2,"indic":9,"instal":10,"model":4,"patent":8,"pyuspto":9,"quick":11,"refer":3,"start":11,"tabl":9,"test":5,"welcom":9}}) \ No newline at end of file +Search.setIndex({"alltitles":{"API Reference":[[3,null]],"Bulk Data Examples":[[7,null]],"Clients":[[0,null]],"Configuration":[[1,null],[17,"configuration"]],"Contents:":[[15,null]],"Contributing":[[6,"contributing"]],"Development":[[6,null]],"Example":[[5,null]],"Examples":[[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[0,null],[9,null],[17,"examples"]],"Exceptions":[[2,null]],"Image File Wrapper Example":[[8,null]],"Indices and tables":[[15,"indices-and-tables"]],"Installation":[[16,null]],"Models":[[4,null]],"PTAB Appeals Example":[[12,null]],"PTAB Interferences Example":[[13,null]],"PTAB Trials Example":[[14,null]],"Patent Data Examples":[[10,null]],"Petition Decisions Example":[[11,null]],"Quick Start":[[17,null]],"Testing":[[6,"testing"]],"Warnings":[[5,null]],"Welcome to pyUSPTO\u2019s documentation!":[[15,null]]},"docnames":["api/clients","api/config","api/exceptions","api/index","api/models","api/warnings","development","examples/bulk_data","examples/ifw_example","examples/index","examples/patent_data","examples/petition_decisions","examples/ptab_appeals","examples/ptab_interferences","examples/ptab_trials","index","installation","quickstart"],"envversion":{"sphinx":65,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1},"filenames":["api\\clients.rst","api\\config.rst","api\\exceptions.rst","api\\index.rst","api\\models.rst","api\\warnings.rst","development.rst","examples\\bulk_data.rst","examples\\ifw_example.rst","examples\\index.rst","examples\\patent_data.rst","examples\\petition_decisions.rst","examples\\ptab_appeals.rst","examples\\ptab_interferences.rst","examples\\ptab_trials.rst","index.rst","installation.rst","quickstart.rst"],"indexentries":{"__init__() (pyuspto.models.patent_data.documentbag method)":[[4,"pyUSPTO.models.patent_data.DocumentBag.__init__",false]],"__init__() (pyuspto.models.patent_data.statuscodecollection method)":[[4,"pyUSPTO.models.patent_data.StatusCodeCollection.__init__",false]],"__repr__() (pyuspto.models.patent_data.documentbag method)":[[4,"pyUSPTO.models.patent_data.DocumentBag.__repr__",false]],"__str__() (pyuspto.models.patent_data.documentbag method)":[[4,"pyUSPTO.models.patent_data.DocumentBag.__str__",false]],"__str__() (pyuspto.models.patent_data.statuscode method)":[[4,"pyUSPTO.models.patent_data.StatusCode.__str__",false]],"a_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id147",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.a_delay_quantity",false]],"accorded_filing_date (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id295",false],[4,"pyUSPTO.models.ptab.TrialMetaData.accorded_filing_date",false]],"active (pyuspto.models.patent_data.activeindicator attribute)":[[4,"pyUSPTO.models.patent_data.ActiveIndicator.ACTIVE",false]],"active_indicator (pyuspto.models.patent_data.attorney attribute)":[[4,"id80",false],[4,"pyUSPTO.models.patent_data.Attorney.active_indicator",false]],"activeindicator (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ActiveIndicator",false]],"additional_party_data_bag (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id241",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.additional_party_data_bag",false]],"additional_party_name (pyuspto.models.ptab.additionalpartydata attribute)":[[4,"id189",false],[4,"pyUSPTO.models.ptab.AdditionalPartyData.additional_party_name",false]],"additionalpartydata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.AdditionalPartyData",false]],"address (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Address",false]],"address_line_four_text (pyuspto.models.patent_data.address attribute)":[[4,"id3",false],[4,"pyUSPTO.models.patent_data.Address.address_line_four_text",false]],"address_line_one_text (pyuspto.models.patent_data.address attribute)":[[4,"id4",false],[4,"pyUSPTO.models.patent_data.Address.address_line_one_text",false]],"address_line_three_text (pyuspto.models.patent_data.address attribute)":[[4,"id5",false],[4,"pyUSPTO.models.patent_data.Address.address_line_three_text",false]],"address_line_two_text (pyuspto.models.patent_data.address attribute)":[[4,"id6",false],[4,"pyUSPTO.models.patent_data.Address.address_line_two_text",false]],"adjustment_total_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id148",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.adjustment_total_quantity",false]],"appeal_document_category (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id228",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.appeal_document_category",false]],"appeal_filing_date (pyuspto.models.ptab.appealmetadata attribute)":[[4,"id200",false],[4,"pyUSPTO.models.ptab.AppealMetaData.appeal_filing_date",false]],"appeal_last_modified_date (pyuspto.models.ptab.appealmetadata attribute)":[[4,"id201",false],[4,"pyUSPTO.models.ptab.AppealMetaData.appeal_last_modified_date",false]],"appeal_last_modified_date_time (pyuspto.models.ptab.appealmetadata attribute)":[[4,"id202",false],[4,"pyUSPTO.models.ptab.AppealMetaData.appeal_last_modified_date_time",false]],"appeal_meta_data (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id229",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.appeal_meta_data",false]],"appeal_number (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id230",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.appeal_number",false]],"appeal_outcome_category (pyuspto.models.ptab.decisiondata attribute)":[[4,"id206",false],[4,"pyUSPTO.models.ptab.DecisionData.appeal_outcome_category",false]],"appealdocumentdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.AppealDocumentData",false]],"appealmetadata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.AppealMetaData",false]],"appellant_data (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id231",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.appellant_data",false]],"appellantdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.AppellantData",false]],"applicant (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Applicant",false]],"applicant_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id24",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.applicant_bag",false]],"applicant_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id149",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.applicant_day_delay_quantity",false]],"applicant_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id157",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.applicant_day_delay_quantity",false]],"applicant_name_text (pyuspto.models.patent_data.applicant attribute)":[[4,"id20",false],[4,"pyUSPTO.models.patent_data.Applicant.applicant_name_text",false]],"application_confirmation_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id25",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_confirmation_number",false]],"application_meta_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id134",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.application_meta_data",false]],"application_number_text (pyuspto.models.patent_data.continuity attribute)":[[4,"id91",false],[4,"pyUSPTO.models.patent_data.Continuity.application_number_text",false]],"application_number_text (pyuspto.models.patent_data.document attribute)":[[4,"id103",false],[4,"pyUSPTO.models.patent_data.Document.application_number_text",false]],"application_number_text (pyuspto.models.patent_data.foreignpriority attribute)":[[4,"id119",false],[4,"pyUSPTO.models.patent_data.ForeignPriority.application_number_text",false]],"application_number_text (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id135",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.application_number_text",false]],"application_number_text (pyuspto.models.ptab.additionalpartydata attribute)":[[4,"id190",false],[4,"pyUSPTO.models.ptab.AdditionalPartyData.application_number_text",false]],"application_status_code (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id26",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_status_code",false]],"application_status_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id27",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_status_date",false]],"application_status_description_text (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id28",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_status_description_text",false]],"application_type_category (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id29",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_type_category",false]],"application_type_category (pyuspto.models.ptab.appealmetadata attribute)":[[4,"id203",false],[4,"pyUSPTO.models.ptab.AppealMetaData.application_type_category",false]],"application_type_code (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id30",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_type_code",false]],"application_type_label_name (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id31",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.application_type_label_name",false]],"applicationcontinuitydata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ApplicationContinuityData",false]],"applicationmetadata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData",false]],"assignee (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Assignee",false]],"assignee_address (pyuspto.models.patent_data.assignee attribute)":[[4,"id61",false],[4,"pyUSPTO.models.patent_data.Assignee.assignee_address",false]],"assignee_bag (pyuspto.models.patent_data.assignment attribute)":[[4,"id63",false],[4,"pyUSPTO.models.patent_data.Assignment.assignee_bag",false]],"assignee_name_text (pyuspto.models.patent_data.assignee attribute)":[[4,"id62",false],[4,"pyUSPTO.models.patent_data.Assignee.assignee_name_text",false]],"assignment (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Assignment",false]],"assignment_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id136",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.assignment_bag",false]],"assignment_document_location_uri (pyuspto.models.patent_data.assignment attribute)":[[4,"id64",false],[4,"pyUSPTO.models.patent_data.Assignment.assignment_document_location_uri",false]],"assignment_mailed_date (pyuspto.models.patent_data.assignment attribute)":[[4,"id65",false],[4,"pyUSPTO.models.patent_data.Assignment.assignment_mailed_date",false]],"assignment_received_date (pyuspto.models.patent_data.assignment attribute)":[[4,"id66",false],[4,"pyUSPTO.models.patent_data.Assignment.assignment_received_date",false]],"assignment_recorded_date (pyuspto.models.patent_data.assignment attribute)":[[4,"id67",false],[4,"pyUSPTO.models.patent_data.Assignment.assignment_recorded_date",false]],"assignor (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Assignor",false]],"assignor_bag (pyuspto.models.patent_data.assignment attribute)":[[4,"id68",false],[4,"pyUSPTO.models.patent_data.Assignment.assignor_bag",false]],"assignor_name (pyuspto.models.patent_data.assignor attribute)":[[4,"id78",false],[4,"pyUSPTO.models.patent_data.Assignor.assignor_name",false]],"attorney (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Attorney",false]],"attorney_address_bag (pyuspto.models.patent_data.attorney attribute)":[[4,"id81",false],[4,"pyUSPTO.models.patent_data.Attorney.attorney_address_bag",false]],"attorney_bag (pyuspto.models.patent_data.recordattorney attribute)":[[4,"id178",false],[4,"pyUSPTO.models.patent_data.RecordAttorney.attorney_bag",false]],"attorney_docket_number (pyuspto.models.patent_data.assignment attribute)":[[4,"id69",false],[4,"pyUSPTO.models.patent_data.Assignment.attorney_docket_number",false]],"b_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id150",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.b_delay_quantity",false]],"bulk_data_product_bag (pyuspto.models.bulk_data.bulkdataresponse attribute)":[[4,"id0",false],[4,"pyUSPTO.models.bulk_data.BulkDataResponse.bulk_data_product_bag",false]],"bulkdataproduct (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct",false]],"bulkdataresponse (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse",false]],"business_entity_status_category (pyuspto.models.patent_data.entitystatus attribute)":[[4,"id114",false],[4,"pyUSPTO.models.patent_data.EntityStatus.business_entity_status_category",false]],"c_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id151",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.c_delay_quantity",false]],"child_application_filing_date (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"id85",false],[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_filing_date",false]],"child_application_number_text (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"id86",false],[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_number_text",false]],"child_application_number_text (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"id124",false],[4,"pyUSPTO.models.patent_data.ParentContinuity.child_application_number_text",false]],"child_application_status_code (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"id87",false],[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_status_code",false]],"child_application_status_description_text (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"id88",false],[4,"pyUSPTO.models.patent_data.ChildContinuity.child_application_status_description_text",false]],"child_continuity_bag (pyuspto.models.patent_data.applicationcontinuitydata attribute)":[[4,"id22",false],[4,"pyUSPTO.models.patent_data.ApplicationContinuityData.child_continuity_bag",false]],"child_continuity_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id137",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.child_continuity_bag",false]],"child_patent_number (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"id89",false],[4,"pyUSPTO.models.patent_data.ChildContinuity.child_patent_number",false]],"childcontinuity (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ChildContinuity",false]],"city_name (pyuspto.models.patent_data.address attribute)":[[4,"id7",false],[4,"pyUSPTO.models.patent_data.Address.city_name",false]],"claim_parentage_type_code (pyuspto.models.patent_data.continuity attribute)":[[4,"id92",false],[4,"pyUSPTO.models.patent_data.Continuity.claim_parentage_type_code",false]],"claim_parentage_type_code_description_text (pyuspto.models.patent_data.continuity attribute)":[[4,"id93",false],[4,"pyUSPTO.models.patent_data.Continuity.claim_parentage_type_code_description_text",false]],"class_field (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id32",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.class_field",false]],"code (pyuspto.models.patent_data.statuscode attribute)":[[4,"id181",false],[4,"pyUSPTO.models.patent_data.StatusCode.code",false]],"continuity (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Continuity",false]],"conveyance_text (pyuspto.models.patent_data.assignment attribute)":[[4,"id70",false],[4,"pyUSPTO.models.patent_data.Assignment.conveyance_text",false]],"correspondence_address (pyuspto.models.patent_data.assignment attribute)":[[4,"id71",false],[4,"pyUSPTO.models.patent_data.Assignment.correspondence_address",false]],"correspondence_address_bag (pyuspto.models.patent_data.applicant attribute)":[[4,"id21",false],[4,"pyUSPTO.models.patent_data.Applicant.correspondence_address_bag",false]],"correspondence_address_bag (pyuspto.models.patent_data.inventor attribute)":[[4,"id122",false],[4,"pyUSPTO.models.patent_data.Inventor.correspondence_address_bag",false]],"correspondence_address_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id138",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.correspondence_address_bag",false]],"correspondent_name_text (pyuspto.models.patent_data.address attribute)":[[4,"id8",false],[4,"pyUSPTO.models.patent_data.Address.correspondent_name_text",false]],"counsel_name (pyuspto.models.ptab.regularpetitionerdata attribute)":[[4,"id277",false],[4,"pyUSPTO.models.ptab.RegularPetitionerData.counsel_name",false]],"count (pyuspto.models.bulk_data.bulkdataresponse attribute)":[[4,"id1",false],[4,"pyUSPTO.models.bulk_data.BulkDataResponse.count",false]],"count (pyuspto.models.bulk_data.productfilebag attribute)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag.count",false]],"count (pyuspto.models.patent_data.patentdataresponse attribute)":[[4,"id130",false],[4,"pyUSPTO.models.patent_data.PatentDataResponse.count",false]],"count (pyuspto.models.patent_data.statuscodesearchresponse attribute)":[[4,"id183",false],[4,"pyUSPTO.models.patent_data.StatusCodeSearchResponse.count",false]],"count (pyuspto.models.ptab.ptabappealresponse attribute)":[[4,"id237",false],[4,"pyUSPTO.models.ptab.PTABAppealResponse.count",false]],"count (pyuspto.models.ptab.ptabinterferenceresponse attribute)":[[4,"id249",false],[4,"pyUSPTO.models.ptab.PTABInterferenceResponse.count",false]],"count (pyuspto.models.ptab.ptabtrialdocumentresponse attribute)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse.count",false]],"count (pyuspto.models.ptab.ptabtrialproceedingresponse attribute)":[[4,"id273",false],[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse.count",false]],"country_code (pyuspto.models.patent_data.address attribute)":[[4,"id9",false],[4,"pyUSPTO.models.patent_data.Address.country_code",false]],"country_code (pyuspto.models.patent_data.person attribute)":[[4,"id164",false],[4,"pyUSPTO.models.patent_data.Person.country_code",false]],"country_name (pyuspto.models.patent_data.address attribute)":[[4,"id10",false],[4,"pyUSPTO.models.patent_data.Address.country_name",false]],"country_or_state_code (pyuspto.models.patent_data.address attribute)":[[4,"id11",false],[4,"pyUSPTO.models.patent_data.Address.country_or_state_code",false]],"cpc_classification_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id33",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.cpc_classification_bag",false]],"customer_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id34",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.customer_number",false]],"customer_number_correspondence_data (pyuspto.models.patent_data.recordattorney attribute)":[[4,"id179",false],[4,"pyUSPTO.models.patent_data.RecordAttorney.customer_number_correspondence_data",false]],"customernumbercorrespondence (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence",false]],"days_of_week_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.days_of_week_text",false]],"decision_data (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id232",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.decision_data",false]],"decision_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id253",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.decision_data",false]],"decision_issue_date (pyuspto.models.ptab.decisiondata attribute)":[[4,"id207",false],[4,"pyUSPTO.models.ptab.DecisionData.decision_issue_date",false]],"decision_issue_date (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id211",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.decision_issue_date",false]],"decision_issue_date (pyuspto.models.ptab.trialdecisiondata attribute)":[[4,"id280",false],[4,"pyUSPTO.models.ptab.TrialDecisionData.decision_issue_date",false]],"decision_type_category (pyuspto.models.ptab.decisiondata attribute)":[[4,"id208",false],[4,"pyUSPTO.models.ptab.DecisionData.decision_type_category",false]],"decision_type_category (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id212",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.decision_type_category",false]],"decision_type_category (pyuspto.models.ptab.trialdecisiondata attribute)":[[4,"id281",false],[4,"pyUSPTO.models.ptab.TrialDecisionData.decision_type_category",false]],"decisiondata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.DecisionData",false]],"declaration_date (pyuspto.models.ptab.interferencemetadata attribute)":[[4,"id223",false],[4,"pyUSPTO.models.ptab.InterferenceMetaData.declaration_date",false]],"derivation_petitioner_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id254",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.derivation_petitioner_data",false]],"derivation_petitioner_data (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id265",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.derivation_petitioner_data",false]],"derivationpetitionerdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.DerivationPetitionerData",false]],"description (pyuspto.models.patent_data.statuscode attribute)":[[4,"id182",false],[4,"pyUSPTO.models.patent_data.StatusCode.description",false]],"direction_category (pyuspto.models.patent_data.document attribute)":[[4,"id104",false],[4,"pyUSPTO.models.patent_data.Document.direction_category",false]],"directioncategory (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.DirectionCategory",false]],"docket_notice_mailed_date (pyuspto.models.ptab.appealmetadata attribute)":[[4,"id204",false],[4,"pyUSPTO.models.ptab.AppealMetaData.docket_notice_mailed_date",false]],"docket_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id35",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.docket_number",false]],"document (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Document",false]],"document_category (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"pyUSPTO.models.ptab.TrialDocumentData.document_category",false]],"document_code (pyuspto.models.patent_data.document attribute)":[[4,"id105",false],[4,"pyUSPTO.models.patent_data.Document.document_code",false]],"document_code_description_text (pyuspto.models.patent_data.document attribute)":[[4,"id106",false],[4,"pyUSPTO.models.patent_data.Document.document_code_description_text",false]],"document_data (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id233",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.document_data",false]],"document_data (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id242",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.document_data",false]],"document_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id255",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.document_data",false]],"document_filing_date (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id193",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.document_filing_date",false]],"document_filing_date (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id213",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.document_filing_date",false]],"document_filing_date (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id285",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_filing_date",false]],"document_formats (pyuspto.models.patent_data.document attribute)":[[4,"id107",false],[4,"pyUSPTO.models.patent_data.Document.document_formats",false]],"document_identifier (pyuspto.models.patent_data.document attribute)":[[4,"id108",false],[4,"pyUSPTO.models.patent_data.Document.document_identifier",false]],"document_identifier (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id194",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.document_identifier",false]],"document_identifier (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id214",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.document_identifier",false]],"document_identifier (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id286",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_identifier",false]],"document_name (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id195",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.document_name",false]],"document_name (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id215",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.document_name",false]],"document_name (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id287",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_name",false]],"document_number (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id288",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_number",false]],"document_ocr_text (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id196",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.document_ocr_text",false]],"document_ocr_text (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id216",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.document_ocr_text",false]],"document_ocr_text (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id289",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_ocr_text",false]],"document_size_quantity (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id197",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.document_size_quantity",false]],"document_size_quantity (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id217",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.document_size_quantity",false]],"document_size_quantity (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id290",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_size_quantity",false]],"document_status (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"pyUSPTO.models.ptab.TrialDocumentData.document_status",false]],"document_title_text (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id218",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.document_title_text",false]],"document_title_text (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id291",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_title_text",false]],"document_type_description_text (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id198",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.document_type_description_text",false]],"document_type_description_text (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id292",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.document_type_description_text",false]],"documentbag (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.DocumentBag",false]],"documentformat (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.DocumentFormat",false]],"documents (pyuspto.models.patent_data.documentbag attribute)":[[4,"pyUSPTO.models.patent_data.DocumentBag.documents",false]],"documents (pyuspto.models.patent_data.documentbag property)":[[4,"id110",false]],"domestic_representative (pyuspto.models.patent_data.assignment attribute)":[[4,"id72",false],[4,"pyUSPTO.models.patent_data.Assignment.domestic_representative",false]],"download_url (pyuspto.models.patent_data.documentformat attribute)":[[4,"id111",false],[4,"pyUSPTO.models.patent_data.DocumentFormat.download_url",false]],"earliest_publication_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id36",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.earliest_publication_date",false]],"earliest_publication_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id37",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.earliest_publication_number",false]],"effective_filing_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id38",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.effective_filing_date",false]],"entity_status_data (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id39",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.entity_status_data",false]],"entitystatus (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.EntityStatus",false]],"event_code (pyuspto.models.patent_data.eventdata attribute)":[[4,"id116",false],[4,"pyUSPTO.models.patent_data.EventData.event_code",false]],"event_data_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id139",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.event_data_bag",false]],"event_date (pyuspto.models.patent_data.eventdata attribute)":[[4,"id117",false],[4,"pyUSPTO.models.patent_data.EventData.event_date",false]],"event_date (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id158",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.event_date",false]],"event_description_text (pyuspto.models.patent_data.eventdata attribute)":[[4,"id118",false],[4,"pyUSPTO.models.patent_data.EventData.event_description_text",false]],"event_description_text (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id159",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.event_description_text",false]],"event_sequence_number (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id160",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.event_sequence_number",false]],"eventdata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.EventData",false]],"examiner_name_text (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id40",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.examiner_name_text",false]],"execution_date (pyuspto.models.patent_data.assignor attribute)":[[4,"id79",false],[4,"pyUSPTO.models.patent_data.Assignor.execution_date",false]],"extension_number (pyuspto.models.patent_data.telecommunication attribute)":[[4,"id186",false],[4,"pyUSPTO.models.patent_data.Telecommunication.extension_number",false]],"false (pyuspto.models.patent_data.activeindicator attribute)":[[4,"pyUSPTO.models.patent_data.ActiveIndicator.FALSE",false]],"file_create_date_time (pyuspto.models.patent_data.printedmetadata attribute)":[[4,"id171",false],[4,"pyUSPTO.models.patent_data.PrintedMetaData.file_create_date_time",false]],"file_data_bag (pyuspto.models.bulk_data.productfilebag attribute)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag.file_data_bag",false]],"file_data_from_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_data_from_date",false]],"file_data_to_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_data_to_date",false]],"file_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_date",false]],"file_download_uri (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_download_uri",false]],"file_download_uri (pyuspto.models.ptab.appealdocumentdata attribute)":[[4,"id199",false],[4,"pyUSPTO.models.ptab.AppealDocumentData.file_download_uri",false]],"file_download_uri (pyuspto.models.ptab.appealmetadata attribute)":[[4,"id205",false],[4,"pyUSPTO.models.ptab.AppealMetaData.file_download_uri",false]],"file_download_uri (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id219",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.file_download_uri",false]],"file_download_uri (pyuspto.models.ptab.interferencemetadata attribute)":[[4,"id224",false],[4,"pyUSPTO.models.ptab.InterferenceMetaData.file_download_uri",false]],"file_download_uri (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id293",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.file_download_uri",false]],"file_download_uri (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id296",false],[4,"pyUSPTO.models.ptab.TrialMetaData.file_download_uri",false]],"file_last_modified_date_time (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_last_modified_date_time",false]],"file_location_uri (pyuspto.models.patent_data.printedmetadata attribute)":[[4,"id172",false],[4,"pyUSPTO.models.patent_data.PrintedMetaData.file_location_uri",false]],"file_name (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_name",false]],"file_release_date (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_release_date",false]],"file_size (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_size",false]],"file_type_text (pyuspto.models.bulk_data.filedata attribute)":[[4,"pyUSPTO.models.bulk_data.FileData.file_type_text",false]],"filedata (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.FileData",false]],"filing_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id41",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.filing_date",false]],"filing_date (pyuspto.models.patent_data.continuity attribute)":[[4,"id94",false],[4,"pyUSPTO.models.patent_data.Continuity.filing_date",false]],"filing_date (pyuspto.models.patent_data.foreignpriority attribute)":[[4,"id120",false],[4,"pyUSPTO.models.patent_data.ForeignPriority.filing_date",false]],"filing_party_category (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"id294",false],[4,"pyUSPTO.models.ptab.TrialDocumentData.filing_party_category",false]],"find_by_code() (pyuspto.models.patent_data.statuscodecollection method)":[[4,"pyUSPTO.models.patent_data.StatusCodeCollection.find_by_code",false]],"first_applicant_name (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id42",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.first_applicant_name",false]],"first_inventor_name (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id43",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.first_inventor_name",false]],"first_inventor_to_file_indicator (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id44",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.first_inventor_to_file_indicator",false]],"first_inventor_to_file_indicator (pyuspto.models.patent_data.continuity attribute)":[[4,"id95",false],[4,"pyUSPTO.models.patent_data.Continuity.first_inventor_to_file_indicator",false]],"first_name (pyuspto.models.patent_data.person attribute)":[[4,"id165",false],[4,"pyUSPTO.models.patent_data.Person.first_name",false]],"foreign_priority_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id140",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.foreign_priority_bag",false]],"foreignpriority (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ForeignPriority",false]],"frame_number (pyuspto.models.patent_data.assignment attribute)":[[4,"id73",false],[4,"pyUSPTO.models.patent_data.Assignment.frame_number",false]],"from_dict() (pyuspto.models.bulk_data.bulkdataproduct class method)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.from_dict",false]],"from_dict() (pyuspto.models.bulk_data.bulkdataresponse class method)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse.from_dict",false]],"from_dict() (pyuspto.models.bulk_data.filedata class method)":[[4,"pyUSPTO.models.bulk_data.FileData.from_dict",false]],"from_dict() (pyuspto.models.bulk_data.productfilebag class method)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag.from_dict",false]],"from_dict() (pyuspto.models.patent_data.address class method)":[[4,"pyUSPTO.models.patent_data.Address.from_dict",false]],"from_dict() (pyuspto.models.patent_data.applicant class method)":[[4,"pyUSPTO.models.patent_data.Applicant.from_dict",false]],"from_dict() (pyuspto.models.patent_data.applicationmetadata class method)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.assignee class method)":[[4,"pyUSPTO.models.patent_data.Assignee.from_dict",false]],"from_dict() (pyuspto.models.patent_data.assignment class method)":[[4,"pyUSPTO.models.patent_data.Assignment.from_dict",false]],"from_dict() (pyuspto.models.patent_data.assignor class method)":[[4,"pyUSPTO.models.patent_data.Assignor.from_dict",false]],"from_dict() (pyuspto.models.patent_data.attorney class method)":[[4,"pyUSPTO.models.patent_data.Attorney.from_dict",false]],"from_dict() (pyuspto.models.patent_data.childcontinuity class method)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.from_dict",false]],"from_dict() (pyuspto.models.patent_data.customernumbercorrespondence class method)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.from_dict",false]],"from_dict() (pyuspto.models.patent_data.document class method)":[[4,"pyUSPTO.models.patent_data.Document.from_dict",false]],"from_dict() (pyuspto.models.patent_data.documentbag class method)":[[4,"pyUSPTO.models.patent_data.DocumentBag.from_dict",false]],"from_dict() (pyuspto.models.patent_data.documentformat class method)":[[4,"pyUSPTO.models.patent_data.DocumentFormat.from_dict",false]],"from_dict() (pyuspto.models.patent_data.entitystatus class method)":[[4,"pyUSPTO.models.patent_data.EntityStatus.from_dict",false]],"from_dict() (pyuspto.models.patent_data.eventdata class method)":[[4,"pyUSPTO.models.patent_data.EventData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.foreignpriority class method)":[[4,"pyUSPTO.models.patent_data.ForeignPriority.from_dict",false]],"from_dict() (pyuspto.models.patent_data.inventor class method)":[[4,"pyUSPTO.models.patent_data.Inventor.from_dict",false]],"from_dict() (pyuspto.models.patent_data.parentcontinuity class method)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patentdataresponse class method)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patentfilewrapper class method)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patenttermadjustmentdata class method)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.patenttermadjustmenthistorydata class method)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.printedmetadata class method)":[[4,"pyUSPTO.models.patent_data.PrintedMetaData.from_dict",false]],"from_dict() (pyuspto.models.patent_data.recordattorney class method)":[[4,"pyUSPTO.models.patent_data.RecordAttorney.from_dict",false]],"from_dict() (pyuspto.models.patent_data.statuscode class method)":[[4,"pyUSPTO.models.patent_data.StatusCode.from_dict",false]],"from_dict() (pyuspto.models.patent_data.statuscodesearchresponse class method)":[[4,"pyUSPTO.models.patent_data.StatusCodeSearchResponse.from_dict",false]],"from_dict() (pyuspto.models.patent_data.telecommunication class method)":[[4,"pyUSPTO.models.patent_data.Telecommunication.from_dict",false]],"from_dict() (pyuspto.models.ptab.additionalpartydata class method)":[[4,"pyUSPTO.models.ptab.AdditionalPartyData.from_dict",false]],"from_dict() (pyuspto.models.ptab.appealdocumentdata class method)":[[4,"pyUSPTO.models.ptab.AppealDocumentData.from_dict",false]],"from_dict() (pyuspto.models.ptab.appealmetadata class method)":[[4,"pyUSPTO.models.ptab.AppealMetaData.from_dict",false]],"from_dict() (pyuspto.models.ptab.decisiondata class method)":[[4,"pyUSPTO.models.ptab.DecisionData.from_dict",false]],"from_dict() (pyuspto.models.ptab.interferencedocumentdata class method)":[[4,"pyUSPTO.models.ptab.InterferenceDocumentData.from_dict",false]],"from_dict() (pyuspto.models.ptab.interferencemetadata class method)":[[4,"pyUSPTO.models.ptab.InterferenceMetaData.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabappealdecision class method)":[[4,"pyUSPTO.models.ptab.PTABAppealDecision.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabappealresponse class method)":[[4,"pyUSPTO.models.ptab.PTABAppealResponse.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabinterferencedecision class method)":[[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabinterferenceresponse class method)":[[4,"pyUSPTO.models.ptab.PTABInterferenceResponse.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabtrialdocument class method)":[[4,"pyUSPTO.models.ptab.PTABTrialDocument.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabtrialdocumentresponse class method)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabtrialproceeding class method)":[[4,"pyUSPTO.models.ptab.PTABTrialProceeding.from_dict",false]],"from_dict() (pyuspto.models.ptab.ptabtrialproceedingresponse class method)":[[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse.from_dict",false]],"from_dict() (pyuspto.models.ptab.regularpetitionerdata class method)":[[4,"pyUSPTO.models.ptab.RegularPetitionerData.from_dict",false]],"from_dict() (pyuspto.models.ptab.requestordata class method)":[[4,"pyUSPTO.models.ptab.RequestorData.from_dict",false]],"from_dict() (pyuspto.models.ptab.trialdecisiondata class method)":[[4,"pyUSPTO.models.ptab.TrialDecisionData.from_dict",false]],"from_dict() (pyuspto.models.ptab.trialdocumentdata class method)":[[4,"pyUSPTO.models.ptab.TrialDocumentData.from_dict",false]],"from_dict() (pyuspto.models.ptab.trialmetadata class method)":[[4,"pyUSPTO.models.ptab.TrialMetaData.from_dict",false]],"from_wrapper() (pyuspto.models.patent_data.applicationcontinuitydata class method)":[[4,"pyUSPTO.models.patent_data.ApplicationContinuityData.from_wrapper",false]],"from_wrapper() (pyuspto.models.patent_data.printedpublication class method)":[[4,"pyUSPTO.models.patent_data.PrintedPublication.from_wrapper",false]],"geographic_region_code (pyuspto.models.patent_data.address attribute)":[[4,"id12",false],[4,"pyUSPTO.models.patent_data.Address.geographic_region_code",false]],"geographic_region_name (pyuspto.models.patent_data.address attribute)":[[4,"id13",false],[4,"pyUSPTO.models.patent_data.Address.geographic_region_name",false]],"grant_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id45",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.grant_date",false]],"grant_document_meta_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id141",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.grant_document_meta_data",false]],"grant_document_meta_data (pyuspto.models.patent_data.printedpublication attribute)":[[4,"id176",false],[4,"pyUSPTO.models.patent_data.PrintedPublication.grant_document_meta_data",false]],"group_art_unit_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id46",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.group_art_unit_number",false]],"ict_country_code (pyuspto.models.patent_data.address attribute)":[[4,"id14",false],[4,"pyUSPTO.models.patent_data.Address.ict_country_code",false]],"ict_state_code (pyuspto.models.patent_data.address attribute)":[[4,"id15",false],[4,"pyUSPTO.models.patent_data.Address.ict_state_code",false]],"image_available_status_code (pyuspto.models.patent_data.assignment attribute)":[[4,"id74",false],[4,"pyUSPTO.models.patent_data.Assignment.image_available_status_code",false]],"incoming (pyuspto.models.patent_data.directioncategory attribute)":[[4,"pyUSPTO.models.patent_data.DirectionCategory.INCOMING",false]],"institution_decision_date (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id297",false],[4,"pyUSPTO.models.ptab.TrialMetaData.institution_decision_date",false]],"interference_last_modified_date (pyuspto.models.ptab.interferencemetadata attribute)":[[4,"id225",false],[4,"pyUSPTO.models.ptab.InterferenceMetaData.interference_last_modified_date",false]],"interference_last_modified_date_time (pyuspto.models.ptab.interferencemetadata attribute)":[[4,"id226",false],[4,"pyUSPTO.models.ptab.InterferenceMetaData.interference_last_modified_date_time",false]],"interference_meta_data (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id243",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.interference_meta_data",false]],"interference_number (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id244",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.interference_number",false]],"interference_outcome_category (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id220",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.interference_outcome_category",false]],"interference_style_name (pyuspto.models.ptab.interferencemetadata attribute)":[[4,"id227",false],[4,"pyUSPTO.models.ptab.InterferenceMetaData.interference_style_name",false]],"interferencedocumentdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.InterferenceDocumentData",false]],"interferencemetadata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.InterferenceMetaData",false]],"international_registration_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id47",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.international_registration_number",false]],"international_registration_publication_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id48",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.international_registration_publication_date",false]],"invention_title (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id49",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.invention_title",false]],"inventor (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Inventor",false]],"inventor_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id50",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.inventor_bag",false]],"inventor_name (pyuspto.models.ptab.additionalpartydata attribute)":[[4,"id191",false],[4,"pyUSPTO.models.ptab.AdditionalPartyData.inventor_name",false]],"inventor_name_text (pyuspto.models.patent_data.inventor attribute)":[[4,"id123",false],[4,"pyUSPTO.models.patent_data.Inventor.inventor_name_text",false]],"ip_office_adjustment_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id152",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.ip_office_adjustment_delay_quantity",false]],"ip_office_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id161",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.ip_office_day_delay_quantity",false]],"ip_office_name (pyuspto.models.patent_data.foreignpriority attribute)":[[4,"id121",false],[4,"pyUSPTO.models.patent_data.ForeignPriority.ip_office_name",false]],"is_aia (pyuspto.models.patent_data.applicationmetadata property)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.is_aia",false]],"is_aia (pyuspto.models.patent_data.continuity property)":[[4,"pyUSPTO.models.patent_data.Continuity.is_aia",false]],"is_pre_aia (pyuspto.models.patent_data.applicationmetadata property)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.is_pre_aia",false]],"is_pre_aia (pyuspto.models.patent_data.continuity property)":[[4,"pyUSPTO.models.patent_data.Continuity.is_pre_aia",false]],"issue_type_bag (pyuspto.models.ptab.decisiondata attribute)":[[4,"id209",false],[4,"pyUSPTO.models.ptab.DecisionData.issue_type_bag",false]],"issue_type_bag (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id221",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.issue_type_bag",false]],"issue_type_bag (pyuspto.models.ptab.trialdecisiondata attribute)":[[4,"id282",false],[4,"pyUSPTO.models.ptab.TrialDecisionData.issue_type_bag",false]],"junior_party_data (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id245",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.junior_party_data",false]],"juniorpartydata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.JuniorPartyData",false]],"last_ingestion_date_time (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id142",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.last_ingestion_date_time",false]],"last_modified_date_time (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.last_modified_date_time",false]],"last_modified_date_time (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id234",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.last_modified_date_time",false]],"last_modified_date_time (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id246",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.last_modified_date_time",false]],"last_modified_date_time (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id256",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.last_modified_date_time",false]],"last_modified_date_time (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id266",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.last_modified_date_time",false]],"last_name (pyuspto.models.patent_data.person attribute)":[[4,"id166",false],[4,"pyUSPTO.models.patent_data.Person.last_name",false]],"latest_decision_date (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id298",false],[4,"pyUSPTO.models.ptab.TrialMetaData.latest_decision_date",false]],"middle_name (pyuspto.models.patent_data.person attribute)":[[4,"id167",false],[4,"pyUSPTO.models.patent_data.Person.middle_name",false]],"mime_type_identifier (pyuspto.models.patent_data.documentformat attribute)":[[4,"id112",false],[4,"pyUSPTO.models.patent_data.DocumentFormat.mime_type_identifier",false]],"mime_type_identifier (pyuspto.models.ptab.trialdocumentdata attribute)":[[4,"pyUSPTO.models.ptab.TrialDocumentData.mime_type_identifier",false]],"mime_type_identifier_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.mime_type_identifier_array_text",false]],"module":[[4,"module-pyUSPTO.models.bulk_data",false],[4,"module-pyUSPTO.models.patent_data",false],[4,"module-pyUSPTO.models.ptab",false],[4,"module-pyUSPTO.models.utils",false]],"name_line_one_text (pyuspto.models.patent_data.address attribute)":[[4,"id16",false],[4,"pyUSPTO.models.patent_data.Address.name_line_one_text",false]],"name_line_two_text (pyuspto.models.patent_data.address attribute)":[[4,"id17",false],[4,"pyUSPTO.models.patent_data.Address.name_line_two_text",false]],"name_prefix (pyuspto.models.patent_data.person attribute)":[[4,"id168",false],[4,"pyUSPTO.models.patent_data.Person.name_prefix",false]],"name_suffix (pyuspto.models.patent_data.person attribute)":[[4,"id169",false],[4,"pyUSPTO.models.patent_data.Person.name_suffix",false]],"national_stage_indicator (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id51",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.national_stage_indicator",false]],"no (pyuspto.models.patent_data.activeindicator attribute)":[[4,"pyUSPTO.models.patent_data.ActiveIndicator.NO",false]],"non_overlapping_day_delay_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id153",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.non_overlapping_day_delay_quantity",false]],"non_overlapping_day_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id154",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.non_overlapping_day_quantity",false]],"official_date (pyuspto.models.patent_data.document attribute)":[[4,"id109",false],[4,"pyUSPTO.models.patent_data.Document.official_date",false]],"organization_standard_name (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"id99",false],[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.organization_standard_name",false]],"originating_event_sequence_number (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id162",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.originating_event_sequence_number",false]],"outgoing (pyuspto.models.patent_data.directioncategory attribute)":[[4,"pyUSPTO.models.patent_data.DirectionCategory.OUTGOING",false]],"overlapping_day_quantity (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id155",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.overlapping_day_quantity",false]],"page_total_quantity (pyuspto.models.patent_data.assignment attribute)":[[4,"id75",false],[4,"pyUSPTO.models.patent_data.Assignment.page_total_quantity",false]],"page_total_quantity (pyuspto.models.patent_data.documentformat attribute)":[[4,"id113",false],[4,"pyUSPTO.models.patent_data.DocumentFormat.page_total_quantity",false]],"parent_application_filing_date (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"id125",false],[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_filing_date",false]],"parent_application_number_text (pyuspto.models.patent_data.childcontinuity attribute)":[[4,"id90",false],[4,"pyUSPTO.models.patent_data.ChildContinuity.parent_application_number_text",false]],"parent_application_number_text (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"id126",false],[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_number_text",false]],"parent_application_status_code (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"id127",false],[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_status_code",false]],"parent_application_status_description_text (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"id128",false],[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_application_status_description_text",false]],"parent_continuity_bag (pyuspto.models.patent_data.applicationcontinuitydata attribute)":[[4,"id23",false],[4,"pyUSPTO.models.patent_data.ApplicationContinuityData.parent_continuity_bag",false]],"parent_continuity_bag (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id143",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.parent_continuity_bag",false]],"parent_patent_number (pyuspto.models.patent_data.parentcontinuity attribute)":[[4,"id129",false],[4,"pyUSPTO.models.patent_data.ParentContinuity.parent_patent_number",false]],"parentcontinuity (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.ParentContinuity",false]],"parse_to_date() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.parse_to_date",false]],"parse_to_datetime_utc() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.parse_to_datetime_utc",false]],"parse_yn_to_bool() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.parse_yn_to_bool",false]],"patent_appeal_data_bag (pyuspto.models.ptab.ptabappealresponse attribute)":[[4,"id238",false],[4,"pyUSPTO.models.ptab.PTABAppealResponse.patent_appeal_data_bag",false]],"patent_file_wrapper_data_bag (pyuspto.models.patent_data.patentdataresponse attribute)":[[4,"id131",false],[4,"pyUSPTO.models.patent_data.PatentDataResponse.patent_file_wrapper_data_bag",false]],"patent_interference_data_bag (pyuspto.models.ptab.ptabinterferenceresponse attribute)":[[4,"id250",false],[4,"pyUSPTO.models.ptab.PTABInterferenceResponse.patent_interference_data_bag",false]],"patent_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id52",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.patent_number",false]],"patent_number (pyuspto.models.patent_data.continuity attribute)":[[4,"id96",false],[4,"pyUSPTO.models.patent_data.Continuity.patent_number",false]],"patent_number (pyuspto.models.ptab.additionalpartydata attribute)":[[4,"id192",false],[4,"pyUSPTO.models.ptab.AdditionalPartyData.patent_number",false]],"patent_owner_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id257",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.patent_owner_data",false]],"patent_owner_data (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id267",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.patent_owner_data",false]],"patent_term_adjustment_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id144",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.patent_term_adjustment_data",false]],"patent_term_adjustment_history_data_bag (pyuspto.models.patent_data.patenttermadjustmentdata attribute)":[[4,"id156",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.patent_term_adjustment_history_data_bag",false]],"patent_trial_document_data_bag (pyuspto.models.ptab.ptabtrialdocumentresponse attribute)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse.patent_trial_document_data_bag",false]],"patent_trial_proceeding_data_bag (pyuspto.models.ptab.ptabtrialproceedingresponse attribute)":[[4,"id274",false],[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse.patent_trial_proceeding_data_bag",false]],"patentdataresponse (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse",false]],"patentfilewrapper (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper",false]],"patentownerdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PatentOwnerData",false]],"patenttermadjustmentdata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData",false]],"patenttermadjustmenthistorydata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData",false]],"patron_identifier (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"id100",false],[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.patron_identifier",false]],"pct_publication_date (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id53",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.pct_publication_date",false]],"pct_publication_number (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id54",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.pct_publication_number",false]],"person (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Person",false]],"petition_filing_date (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id299",false],[4,"pyUSPTO.models.ptab.TrialMetaData.petition_filing_date",false]],"pgpub_document_meta_data (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id145",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.pgpub_document_meta_data",false]],"pgpub_document_meta_data (pyuspto.models.patent_data.printedpublication attribute)":[[4,"id177",false],[4,"pyUSPTO.models.patent_data.PrintedPublication.pgpub_document_meta_data",false]],"postal_address_category (pyuspto.models.patent_data.address attribute)":[[4,"id18",false],[4,"pyUSPTO.models.patent_data.Address.postal_address_category",false]],"postal_code (pyuspto.models.patent_data.address attribute)":[[4,"id19",false],[4,"pyUSPTO.models.patent_data.Address.postal_code",false]],"power_of_attorney_address_bag (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"id101",false],[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.power_of_attorney_address_bag",false]],"power_of_attorney_bag (pyuspto.models.patent_data.recordattorney attribute)":[[4,"id180",false],[4,"pyUSPTO.models.patent_data.RecordAttorney.power_of_attorney_bag",false]],"preferred_name (pyuspto.models.patent_data.person attribute)":[[4,"id170",false],[4,"pyUSPTO.models.patent_data.Person.preferred_name",false]],"printedmetadata (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PrintedMetaData",false]],"printedpublication (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.PrintedPublication",false]],"product_dataset_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_dataset_array_text",false]],"product_dataset_category_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_dataset_category_array_text",false]],"product_description_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_description_text",false]],"product_file_bag (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_file_bag",false]],"product_file_total_quantity (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_file_total_quantity",false]],"product_frequency_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_frequency_text",false]],"product_from_date (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_from_date",false]],"product_identifier (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_identifier",false]],"product_identifier (pyuspto.models.patent_data.printedmetadata attribute)":[[4,"id173",false],[4,"pyUSPTO.models.patent_data.PrintedMetaData.product_identifier",false]],"product_label_array_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_label_array_text",false]],"product_title_text (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_title_text",false]],"product_to_date (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_to_date",false]],"product_total_file_size (pyuspto.models.bulk_data.bulkdataproduct attribute)":[[4,"pyUSPTO.models.bulk_data.BulkDataProduct.product_total_file_size",false]],"productfilebag (class in pyuspto.models.bulk_data)":[[4,"pyUSPTO.models.bulk_data.ProductFileBag",false]],"pta_pte_code (pyuspto.models.patent_data.patenttermadjustmenthistorydata attribute)":[[4,"id163",false],[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.pta_pte_code",false]],"ptabappealdecision (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABAppealDecision",false]],"ptabappealresponse (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABAppealResponse",false]],"ptabinterferencedecision (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABInterferenceDecision",false]],"ptabinterferenceresponse (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABInterferenceResponse",false]],"ptabtrialdocument (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABTrialDocument",false]],"ptabtrialdocumentresponse (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse",false]],"ptabtrialproceeding (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABTrialProceeding",false]],"ptabtrialproceedingresponse (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse",false]],"publication_category_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id55",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.publication_category_bag",false]],"publication_date_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id56",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.publication_date_bag",false]],"publication_sequence_number_bag (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id57",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.publication_sequence_number_bag",false]],"pyuspto.models.bulk_data":[[4,"module-pyUSPTO.models.bulk_data",false]],"pyuspto.models.patent_data":[[4,"module-pyUSPTO.models.patent_data",false]],"pyuspto.models.ptab":[[4,"module-pyUSPTO.models.ptab",false]],"pyuspto.models.utils":[[4,"module-pyUSPTO.models.utils",false]],"raw_data (pyuspto.models.bulk_data.bulkdataresponse attribute)":[[4,"id2",false],[4,"pyUSPTO.models.bulk_data.BulkDataResponse.raw_data",false]],"raw_data (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id58",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.raw_data",false]],"raw_data (pyuspto.models.patent_data.patentdataresponse attribute)":[[4,"id132",false],[4,"pyUSPTO.models.patent_data.PatentDataResponse.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id235",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabappealresponse attribute)":[[4,"id239",false],[4,"pyUSPTO.models.ptab.PTABAppealResponse.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id247",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabinterferenceresponse attribute)":[[4,"id251",false],[4,"pyUSPTO.models.ptab.PTABInterferenceResponse.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id258",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabtrialdocumentresponse attribute)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id268",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.raw_data",false]],"raw_data (pyuspto.models.ptab.ptabtrialproceedingresponse attribute)":[[4,"id275",false],[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse.raw_data",false]],"real_party_in_interest_name (pyuspto.models.ptab.regularpetitionerdata attribute)":[[4,"id278",false],[4,"pyUSPTO.models.ptab.RegularPetitionerData.real_party_in_interest_name",false]],"record_attorney (pyuspto.models.patent_data.patentfilewrapper attribute)":[[4,"id146",false],[4,"pyUSPTO.models.patent_data.PatentFileWrapper.record_attorney",false]],"recordattorney (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.RecordAttorney",false]],"reel_and_frame_number (pyuspto.models.patent_data.assignment attribute)":[[4,"id76",false],[4,"pyUSPTO.models.patent_data.Assignment.reel_and_frame_number",false]],"reel_number (pyuspto.models.patent_data.assignment attribute)":[[4,"id77",false],[4,"pyUSPTO.models.patent_data.Assignment.reel_number",false]],"registered_practitioner_category (pyuspto.models.patent_data.attorney attribute)":[[4,"id82",false],[4,"pyUSPTO.models.patent_data.Attorney.registered_practitioner_category",false]],"registration_number (pyuspto.models.patent_data.attorney attribute)":[[4,"id83",false],[4,"pyUSPTO.models.patent_data.Attorney.registration_number",false]],"regular_petitioner_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id259",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.regular_petitioner_data",false]],"regular_petitioner_data (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id269",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.regular_petitioner_data",false]],"regularpetitionerdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.RegularPetitionerData",false]],"request_identifier (pyuspto.models.patent_data.patentdataresponse attribute)":[[4,"id133",false],[4,"pyUSPTO.models.patent_data.PatentDataResponse.request_identifier",false]],"request_identifier (pyuspto.models.patent_data.statuscodesearchresponse attribute)":[[4,"id184",false],[4,"pyUSPTO.models.patent_data.StatusCodeSearchResponse.request_identifier",false]],"request_identifier (pyuspto.models.ptab.ptabappealresponse attribute)":[[4,"id240",false],[4,"pyUSPTO.models.ptab.PTABAppealResponse.request_identifier",false]],"request_identifier (pyuspto.models.ptab.ptabinterferenceresponse attribute)":[[4,"id252",false],[4,"pyUSPTO.models.ptab.PTABInterferenceResponse.request_identifier",false]],"request_identifier (pyuspto.models.ptab.ptabtrialdocumentresponse attribute)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse.request_identifier",false]],"request_identifier (pyuspto.models.ptab.ptabtrialproceedingresponse attribute)":[[4,"id276",false],[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse.request_identifier",false]],"requestor_data (pyuspto.models.ptab.ptabappealdecision attribute)":[[4,"id236",false],[4,"pyUSPTO.models.ptab.PTABAppealDecision.requestor_data",false]],"requestordata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.RequestorData",false]],"respondent_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id260",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.respondent_data",false]],"respondent_data (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id270",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.respondent_data",false]],"respondentdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.RespondentData",false]],"search_by_description() (pyuspto.models.patent_data.statuscodecollection method)":[[4,"pyUSPTO.models.patent_data.StatusCodeCollection.search_by_description",false]],"senior_party_data (pyuspto.models.ptab.ptabinterferencedecision attribute)":[[4,"id248",false],[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.senior_party_data",false]],"seniorpartydata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.SeniorPartyData",false]],"serialize_bool_to_yn() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.serialize_bool_to_yn",false]],"serialize_date() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.serialize_date",false]],"serialize_datetime_as_iso() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.serialize_datetime_as_iso",false]],"serialize_datetime_as_naive() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.serialize_datetime_as_naive",false]],"small_entity_status_indicator (pyuspto.models.patent_data.entitystatus attribute)":[[4,"id115",false],[4,"pyUSPTO.models.patent_data.EntityStatus.small_entity_status_indicator",false]],"status_code (pyuspto.models.patent_data.continuity attribute)":[[4,"id97",false],[4,"pyUSPTO.models.patent_data.Continuity.status_code",false]],"status_code_bag (pyuspto.models.patent_data.statuscodesearchresponse attribute)":[[4,"id185",false],[4,"pyUSPTO.models.patent_data.StatusCodeSearchResponse.status_code_bag",false]],"status_codes (pyuspto.models.patent_data.statuscodecollection attribute)":[[4,"pyUSPTO.models.patent_data.StatusCodeCollection.status_codes",false]],"status_description_text (pyuspto.models.patent_data.continuity attribute)":[[4,"id98",false],[4,"pyUSPTO.models.patent_data.Continuity.status_description_text",false]],"statuscode (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.StatusCode",false]],"statuscodecollection (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.StatusCodeCollection",false]],"statuscodesearchresponse (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.StatusCodeSearchResponse",false]],"statute_and_rule_bag (pyuspto.models.ptab.decisiondata attribute)":[[4,"id210",false],[4,"pyUSPTO.models.ptab.DecisionData.statute_and_rule_bag",false]],"statute_and_rule_bag (pyuspto.models.ptab.interferencedocumentdata attribute)":[[4,"id222",false],[4,"pyUSPTO.models.ptab.InterferenceDocumentData.statute_and_rule_bag",false]],"statute_and_rule_bag (pyuspto.models.ptab.trialdecisiondata attribute)":[[4,"id283",false],[4,"pyUSPTO.models.ptab.TrialDecisionData.statute_and_rule_bag",false]],"subclass (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id59",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.subclass",false]],"telecom_type_code (pyuspto.models.patent_data.telecommunication attribute)":[[4,"id187",false],[4,"pyUSPTO.models.patent_data.Telecommunication.telecom_type_code",false]],"telecommunication (class in pyuspto.models.patent_data)":[[4,"pyUSPTO.models.patent_data.Telecommunication",false]],"telecommunication_address_bag (pyuspto.models.patent_data.attorney attribute)":[[4,"id84",false],[4,"pyUSPTO.models.patent_data.Attorney.telecommunication_address_bag",false]],"telecommunication_address_bag (pyuspto.models.patent_data.customernumbercorrespondence attribute)":[[4,"id102",false],[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.telecommunication_address_bag",false]],"telecommunication_number (pyuspto.models.patent_data.telecommunication attribute)":[[4,"id188",false],[4,"pyUSPTO.models.patent_data.Telecommunication.telecommunication_number",false]],"termination_date (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id300",false],[4,"pyUSPTO.models.ptab.TrialMetaData.termination_date",false]],"third_party_name (pyuspto.models.ptab.requestordata attribute)":[[4,"id279",false],[4,"pyUSPTO.models.ptab.RequestorData.third_party_name",false]],"to_camel_case() (in module pyuspto.models.utils)":[[4,"pyUSPTO.models.utils.to_camel_case",false]],"to_csv() (pyuspto.models.patent_data.patentdataresponse method)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.to_csv",false]],"to_dict() (pyuspto.models.bulk_data.bulkdataresponse method)":[[4,"pyUSPTO.models.bulk_data.BulkDataResponse.to_dict",false]],"to_dict() (pyuspto.models.patent_data.address method)":[[4,"pyUSPTO.models.patent_data.Address.to_dict",false]],"to_dict() (pyuspto.models.patent_data.applicant method)":[[4,"pyUSPTO.models.patent_data.Applicant.to_dict",false]],"to_dict() (pyuspto.models.patent_data.applicationcontinuitydata method)":[[4,"pyUSPTO.models.patent_data.ApplicationContinuityData.to_dict",false]],"to_dict() (pyuspto.models.patent_data.applicationmetadata method)":[[4,"pyUSPTO.models.patent_data.ApplicationMetaData.to_dict",false]],"to_dict() (pyuspto.models.patent_data.assignee method)":[[4,"pyUSPTO.models.patent_data.Assignee.to_dict",false]],"to_dict() (pyuspto.models.patent_data.assignment method)":[[4,"pyUSPTO.models.patent_data.Assignment.to_dict",false]],"to_dict() (pyuspto.models.patent_data.assignor method)":[[4,"pyUSPTO.models.patent_data.Assignor.to_dict",false]],"to_dict() (pyuspto.models.patent_data.attorney method)":[[4,"pyUSPTO.models.patent_data.Attorney.to_dict",false]],"to_dict() (pyuspto.models.patent_data.childcontinuity method)":[[4,"pyUSPTO.models.patent_data.ChildContinuity.to_dict",false]],"to_dict() (pyuspto.models.patent_data.continuity method)":[[4,"pyUSPTO.models.patent_data.Continuity.to_dict",false]],"to_dict() (pyuspto.models.patent_data.customernumbercorrespondence method)":[[4,"pyUSPTO.models.patent_data.CustomerNumberCorrespondence.to_dict",false]],"to_dict() (pyuspto.models.patent_data.document method)":[[4,"pyUSPTO.models.patent_data.Document.to_dict",false]],"to_dict() (pyuspto.models.patent_data.documentbag method)":[[4,"pyUSPTO.models.patent_data.DocumentBag.to_dict",false]],"to_dict() (pyuspto.models.patent_data.documentformat method)":[[4,"pyUSPTO.models.patent_data.DocumentFormat.to_dict",false]],"to_dict() (pyuspto.models.patent_data.entitystatus method)":[[4,"pyUSPTO.models.patent_data.EntityStatus.to_dict",false]],"to_dict() (pyuspto.models.patent_data.eventdata method)":[[4,"pyUSPTO.models.patent_data.EventData.to_dict",false]],"to_dict() (pyuspto.models.patent_data.foreignpriority method)":[[4,"pyUSPTO.models.patent_data.ForeignPriority.to_dict",false]],"to_dict() (pyuspto.models.patent_data.inventor method)":[[4,"pyUSPTO.models.patent_data.Inventor.to_dict",false]],"to_dict() (pyuspto.models.patent_data.parentcontinuity method)":[[4,"pyUSPTO.models.patent_data.ParentContinuity.to_dict",false]],"to_dict() (pyuspto.models.patent_data.patentdataresponse method)":[[4,"pyUSPTO.models.patent_data.PatentDataResponse.to_dict",false]],"to_dict() (pyuspto.models.patent_data.patentfilewrapper method)":[[4,"pyUSPTO.models.patent_data.PatentFileWrapper.to_dict",false]],"to_dict() (pyuspto.models.patent_data.patenttermadjustmentdata method)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentData.to_dict",false]],"to_dict() (pyuspto.models.patent_data.patenttermadjustmenthistorydata method)":[[4,"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData.to_dict",false]],"to_dict() (pyuspto.models.patent_data.person method)":[[4,"pyUSPTO.models.patent_data.Person.to_dict",false]],"to_dict() (pyuspto.models.patent_data.printedmetadata method)":[[4,"pyUSPTO.models.patent_data.PrintedMetaData.to_dict",false]],"to_dict() (pyuspto.models.patent_data.printedpublication method)":[[4,"pyUSPTO.models.patent_data.PrintedPublication.to_dict",false]],"to_dict() (pyuspto.models.patent_data.recordattorney method)":[[4,"pyUSPTO.models.patent_data.RecordAttorney.to_dict",false]],"to_dict() (pyuspto.models.patent_data.statuscode method)":[[4,"pyUSPTO.models.patent_data.StatusCode.to_dict",false]],"to_dict() (pyuspto.models.patent_data.statuscodecollection method)":[[4,"pyUSPTO.models.patent_data.StatusCodeCollection.to_dict",false]],"to_dict() (pyuspto.models.patent_data.statuscodesearchresponse method)":[[4,"pyUSPTO.models.patent_data.StatusCodeSearchResponse.to_dict",false]],"to_dict() (pyuspto.models.patent_data.telecommunication method)":[[4,"pyUSPTO.models.patent_data.Telecommunication.to_dict",false]],"to_dict() (pyuspto.models.ptab.additionalpartydata method)":[[4,"pyUSPTO.models.ptab.AdditionalPartyData.to_dict",false]],"to_dict() (pyuspto.models.ptab.appealdocumentdata method)":[[4,"pyUSPTO.models.ptab.AppealDocumentData.to_dict",false]],"to_dict() (pyuspto.models.ptab.appealmetadata method)":[[4,"pyUSPTO.models.ptab.AppealMetaData.to_dict",false]],"to_dict() (pyuspto.models.ptab.decisiondata method)":[[4,"pyUSPTO.models.ptab.DecisionData.to_dict",false]],"to_dict() (pyuspto.models.ptab.interferencedocumentdata method)":[[4,"pyUSPTO.models.ptab.InterferenceDocumentData.to_dict",false]],"to_dict() (pyuspto.models.ptab.interferencemetadata method)":[[4,"pyUSPTO.models.ptab.InterferenceMetaData.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabappealdecision method)":[[4,"pyUSPTO.models.ptab.PTABAppealDecision.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabappealresponse method)":[[4,"pyUSPTO.models.ptab.PTABAppealResponse.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabinterferencedecision method)":[[4,"pyUSPTO.models.ptab.PTABInterferenceDecision.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabinterferenceresponse method)":[[4,"pyUSPTO.models.ptab.PTABInterferenceResponse.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabtrialdocument method)":[[4,"pyUSPTO.models.ptab.PTABTrialDocument.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabtrialdocumentresponse method)":[[4,"pyUSPTO.models.ptab.PTABTrialDocumentResponse.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabtrialproceeding method)":[[4,"pyUSPTO.models.ptab.PTABTrialProceeding.to_dict",false]],"to_dict() (pyuspto.models.ptab.ptabtrialproceedingresponse method)":[[4,"pyUSPTO.models.ptab.PTABTrialProceedingResponse.to_dict",false]],"to_dict() (pyuspto.models.ptab.regularpetitionerdata method)":[[4,"pyUSPTO.models.ptab.RegularPetitionerData.to_dict",false]],"to_dict() (pyuspto.models.ptab.requestordata method)":[[4,"pyUSPTO.models.ptab.RequestorData.to_dict",false]],"to_dict() (pyuspto.models.ptab.trialdecisiondata method)":[[4,"pyUSPTO.models.ptab.TrialDecisionData.to_dict",false]],"to_dict() (pyuspto.models.ptab.trialdocumentdata method)":[[4,"pyUSPTO.models.ptab.TrialDocumentData.to_dict",false]],"to_dict() (pyuspto.models.ptab.trialmetadata method)":[[4,"pyUSPTO.models.ptab.TrialMetaData.to_dict",false]],"trial_document_category (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id261",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.trial_document_category",false]],"trial_last_modified_date (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id301",false],[4,"pyUSPTO.models.ptab.TrialMetaData.trial_last_modified_date",false]],"trial_last_modified_date_time (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id302",false],[4,"pyUSPTO.models.ptab.TrialMetaData.trial_last_modified_date_time",false]],"trial_meta_data (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id262",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.trial_meta_data",false]],"trial_meta_data (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id271",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.trial_meta_data",false]],"trial_number (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id263",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.trial_number",false]],"trial_number (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"id272",false],[4,"pyUSPTO.models.ptab.PTABTrialProceeding.trial_number",false]],"trial_outcome_category (pyuspto.models.ptab.trialdecisiondata attribute)":[[4,"id284",false],[4,"pyUSPTO.models.ptab.TrialDecisionData.trial_outcome_category",false]],"trial_record_identifier (pyuspto.models.ptab.ptabtrialproceeding attribute)":[[4,"pyUSPTO.models.ptab.PTABTrialProceeding.trial_record_identifier",false]],"trial_status_category (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id303",false],[4,"pyUSPTO.models.ptab.TrialMetaData.trial_status_category",false]],"trial_type_code (pyuspto.models.ptab.ptabtrialdocument attribute)":[[4,"id264",false],[4,"pyUSPTO.models.ptab.PTABTrialDocument.trial_type_code",false]],"trial_type_code (pyuspto.models.ptab.trialmetadata attribute)":[[4,"id304",false],[4,"pyUSPTO.models.ptab.TrialMetaData.trial_type_code",false]],"trialdecisiondata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.TrialDecisionData",false]],"trialdocumentdata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.TrialDocumentData",false]],"trialmetadata (class in pyuspto.models.ptab)":[[4,"pyUSPTO.models.ptab.TrialMetaData",false]],"true (pyuspto.models.patent_data.activeindicator attribute)":[[4,"pyUSPTO.models.patent_data.ActiveIndicator.TRUE",false]],"uspc_symbol_text (pyuspto.models.patent_data.applicationmetadata attribute)":[[4,"id60",false],[4,"pyUSPTO.models.patent_data.ApplicationMetaData.uspc_symbol_text",false]],"xml_file_name (pyuspto.models.patent_data.printedmetadata attribute)":[[4,"id174",false],[4,"pyUSPTO.models.patent_data.PrintedMetaData.xml_file_name",false]],"yes (pyuspto.models.patent_data.activeindicator attribute)":[[4,"pyUSPTO.models.patent_data.ActiveIndicator.YES",false]],"zip_file_name (pyuspto.models.patent_data.printedmetadata attribute)":[[4,"id175",false],[4,"pyUSPTO.models.patent_data.PrintedMetaData.zip_file_name",false]]},"objects":{"pyUSPTO":[[1,0,0,"-","config"],[2,0,0,"-","exceptions"],[1,0,0,"-","http_config"],[5,0,0,"-","warnings"]],"pyUSPTO.clients":[[0,0,0,"-","bulk_data"],[0,0,0,"-","patent_data"],[0,0,0,"-","petition_decisions"],[0,0,0,"-","ptab_appeals"],[0,0,0,"-","ptab_interferences"],[0,0,0,"-","ptab_trials"]],"pyUSPTO.clients.bulk_data":[[0,1,1,"","BulkDataClient"]],"pyUSPTO.clients.bulk_data.BulkDataClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","download_file"],[0,3,1,"","get_product_by_id"],[0,3,1,"","get_products"],[0,3,1,"","paginate_products"],[0,3,1,"","search_products"]],"pyUSPTO.clients.patent_data":[[0,1,1,"","PatentDataClient"]],"pyUSPTO.clients.patent_data.PatentDataClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","download_archive"],[0,3,1,"","download_document"],[0,3,1,"","download_publication"],[0,3,1,"","get_IFW_metadata"],[0,3,1,"","get_application_adjustment"],[0,3,1,"","get_application_assignment"],[0,3,1,"","get_application_associated_documents"],[0,3,1,"","get_application_attorney"],[0,3,1,"","get_application_by_number"],[0,3,1,"","get_application_continuity"],[0,3,1,"","get_application_documents"],[0,3,1,"","get_application_foreign_priority"],[0,3,1,"","get_application_metadata"],[0,3,1,"","get_application_transactions"],[0,3,1,"","get_search_results"],[0,3,1,"","get_status_codes"],[0,3,1,"","paginate_applications"],[0,3,1,"","sanitize_application_number"],[0,3,1,"","search_applications"],[0,3,1,"","search_status_codes"]],"pyUSPTO.clients.petition_decisions":[[0,1,1,"","FinalPetitionDecisionsClient"]],"pyUSPTO.clients.petition_decisions.FinalPetitionDecisionsClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","download_decisions"],[0,3,1,"","download_petition_document"],[0,3,1,"","get_decision_by_id"],[0,3,1,"","paginate_decisions"],[0,3,1,"","search_decisions"]],"pyUSPTO.clients.ptab_appeals":[[0,1,1,"","PTABAppealsClient"]],"pyUSPTO.clients.ptab_appeals.PTABAppealsClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","paginate_decisions"],[0,3,1,"","search_decisions"]],"pyUSPTO.clients.ptab_interferences":[[0,1,1,"","PTABInterferencesClient"]],"pyUSPTO.clients.ptab_interferences.PTABInterferencesClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","paginate_decisions"],[0,3,1,"","search_decisions"]],"pyUSPTO.clients.ptab_trials":[[0,1,1,"","PTABTrialsClient"]],"pyUSPTO.clients.ptab_trials.PTABTrialsClient":[[0,2,1,"","ENDPOINTS"],[0,3,1,"","__init__"],[0,3,1,"","paginate_proceedings"],[0,3,1,"","search_decisions"],[0,3,1,"","search_documents"],[0,3,1,"","search_proceedings"]],"pyUSPTO.config":[[1,1,1,"","USPTOConfig"]],"pyUSPTO.config.USPTOConfig":[[1,3,1,"","__init__"],[1,3,1,"","from_env"]],"pyUSPTO.exceptions":[[2,1,1,"","APIErrorArgs"],[2,4,1,"","USPTOApiAuthError"],[2,4,1,"","USPTOApiBadRequestError"],[2,4,1,"","USPTOApiError"],[2,4,1,"","USPTOApiNotFoundError"],[2,4,1,"","USPTOApiPayloadTooLargeError"],[2,4,1,"","USPTOApiRateLimitError"],[2,4,1,"","USPTOApiServerError"],[2,4,1,"","USPTOConnectionError"],[2,4,1,"","USPTOTimeout"],[2,6,1,"","get_api_exception"]],"pyUSPTO.exceptions.APIErrorArgs":[[2,2,1,"","api_short_error"],[2,2,1,"","error_details"],[2,3,1,"","from_http_error"],[2,3,1,"","from_request_exception"],[2,2,1,"","message"],[2,2,1,"","request_identifier"],[2,2,1,"","status_code"]],"pyUSPTO.exceptions.USPTOApiError":[[2,2,1,"","DEFAULT_UNKNOWN_MESSAGE"],[2,3,1,"","__init__"],[2,3,1,"","__str__"],[2,5,1,"","message"]],"pyUSPTO.http_config":[[1,1,1,"","HTTPConfig"]],"pyUSPTO.http_config.HTTPConfig":[[1,2,1,"id0","backoff_factor"],[1,2,1,"id1","connect_timeout"],[1,2,1,"id2","custom_headers"],[1,3,1,"","from_env"],[1,3,1,"","get_timeout_tuple"],[1,2,1,"id3","max_retries"],[1,2,1,"id4","pool_connections"],[1,2,1,"id5","pool_maxsize"],[1,2,1,"id6","retry_status_codes"],[1,2,1,"id7","timeout"]],"pyUSPTO.models":[[4,0,0,"-","bulk_data"],[4,0,0,"-","patent_data"],[4,0,0,"-","ptab"],[4,0,0,"-","utils"]],"pyUSPTO.models.bulk_data":[[4,1,1,"","BulkDataProduct"],[4,1,1,"","BulkDataResponse"],[4,1,1,"","FileData"],[4,1,1,"","ProductFileBag"]],"pyUSPTO.models.bulk_data.BulkDataProduct":[[4,2,1,"","days_of_week_text"],[4,3,1,"","from_dict"],[4,2,1,"","last_modified_date_time"],[4,2,1,"","mime_type_identifier_array_text"],[4,2,1,"","product_dataset_array_text"],[4,2,1,"","product_dataset_category_array_text"],[4,2,1,"","product_description_text"],[4,2,1,"","product_file_bag"],[4,2,1,"","product_file_total_quantity"],[4,2,1,"","product_frequency_text"],[4,2,1,"","product_from_date"],[4,2,1,"","product_identifier"],[4,2,1,"","product_label_array_text"],[4,2,1,"","product_title_text"],[4,2,1,"","product_to_date"],[4,2,1,"","product_total_file_size"]],"pyUSPTO.models.bulk_data.BulkDataResponse":[[4,2,1,"id0","bulk_data_product_bag"],[4,2,1,"id1","count"],[4,3,1,"","from_dict"],[4,2,1,"id2","raw_data"],[4,3,1,"","to_dict"]],"pyUSPTO.models.bulk_data.FileData":[[4,2,1,"","file_data_from_date"],[4,2,1,"","file_data_to_date"],[4,2,1,"","file_date"],[4,2,1,"","file_download_uri"],[4,2,1,"","file_last_modified_date_time"],[4,2,1,"","file_name"],[4,2,1,"","file_release_date"],[4,2,1,"","file_size"],[4,2,1,"","file_type_text"],[4,3,1,"","from_dict"]],"pyUSPTO.models.bulk_data.ProductFileBag":[[4,2,1,"","count"],[4,2,1,"","file_data_bag"],[4,3,1,"","from_dict"]],"pyUSPTO.models.patent_data":[[4,1,1,"","ActiveIndicator"],[4,1,1,"","Address"],[4,1,1,"","Applicant"],[4,1,1,"","ApplicationContinuityData"],[4,1,1,"","ApplicationMetaData"],[4,1,1,"","Assignee"],[4,1,1,"","Assignment"],[4,1,1,"","Assignor"],[4,1,1,"","Attorney"],[4,1,1,"","ChildContinuity"],[4,1,1,"","Continuity"],[4,1,1,"","CustomerNumberCorrespondence"],[4,1,1,"","DirectionCategory"],[4,1,1,"","Document"],[4,1,1,"","DocumentBag"],[4,1,1,"","DocumentFormat"],[4,1,1,"","EntityStatus"],[4,1,1,"","EventData"],[4,1,1,"","ForeignPriority"],[4,1,1,"","Inventor"],[4,1,1,"","ParentContinuity"],[4,1,1,"","PatentDataResponse"],[4,1,1,"","PatentFileWrapper"],[4,1,1,"","PatentTermAdjustmentData"],[4,1,1,"","PatentTermAdjustmentHistoryData"],[4,1,1,"","Person"],[4,1,1,"","PrintedMetaData"],[4,1,1,"","PrintedPublication"],[4,1,1,"","RecordAttorney"],[4,1,1,"","StatusCode"],[4,1,1,"","StatusCodeCollection"],[4,1,1,"","StatusCodeSearchResponse"],[4,1,1,"","Telecommunication"]],"pyUSPTO.models.patent_data.ActiveIndicator":[[4,2,1,"","ACTIVE"],[4,2,1,"","FALSE"],[4,2,1,"","NO"],[4,2,1,"","TRUE"],[4,2,1,"","YES"]],"pyUSPTO.models.patent_data.Address":[[4,2,1,"id3","address_line_four_text"],[4,2,1,"id4","address_line_one_text"],[4,2,1,"id5","address_line_three_text"],[4,2,1,"id6","address_line_two_text"],[4,2,1,"id7","city_name"],[4,2,1,"id8","correspondent_name_text"],[4,2,1,"id9","country_code"],[4,2,1,"id10","country_name"],[4,2,1,"id11","country_or_state_code"],[4,3,1,"","from_dict"],[4,2,1,"id12","geographic_region_code"],[4,2,1,"id13","geographic_region_name"],[4,2,1,"id14","ict_country_code"],[4,2,1,"id15","ict_state_code"],[4,2,1,"id16","name_line_one_text"],[4,2,1,"id17","name_line_two_text"],[4,2,1,"id18","postal_address_category"],[4,2,1,"id19","postal_code"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Applicant":[[4,2,1,"id20","applicant_name_text"],[4,2,1,"id21","correspondence_address_bag"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.ApplicationContinuityData":[[4,2,1,"id22","child_continuity_bag"],[4,3,1,"","from_wrapper"],[4,2,1,"id23","parent_continuity_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.ApplicationMetaData":[[4,2,1,"id24","applicant_bag"],[4,2,1,"id25","application_confirmation_number"],[4,2,1,"id26","application_status_code"],[4,2,1,"id27","application_status_date"],[4,2,1,"id28","application_status_description_text"],[4,2,1,"id29","application_type_category"],[4,2,1,"id30","application_type_code"],[4,2,1,"id31","application_type_label_name"],[4,2,1,"id32","class_field"],[4,2,1,"id33","cpc_classification_bag"],[4,2,1,"id34","customer_number"],[4,2,1,"id35","docket_number"],[4,2,1,"id36","earliest_publication_date"],[4,2,1,"id37","earliest_publication_number"],[4,2,1,"id38","effective_filing_date"],[4,2,1,"id39","entity_status_data"],[4,2,1,"id40","examiner_name_text"],[4,2,1,"id41","filing_date"],[4,2,1,"id42","first_applicant_name"],[4,2,1,"id43","first_inventor_name"],[4,2,1,"id44","first_inventor_to_file_indicator"],[4,3,1,"","from_dict"],[4,2,1,"id45","grant_date"],[4,2,1,"id46","group_art_unit_number"],[4,2,1,"id47","international_registration_number"],[4,2,1,"id48","international_registration_publication_date"],[4,2,1,"id49","invention_title"],[4,2,1,"id50","inventor_bag"],[4,5,1,"","is_aia"],[4,5,1,"","is_pre_aia"],[4,2,1,"id51","national_stage_indicator"],[4,2,1,"id52","patent_number"],[4,2,1,"id53","pct_publication_date"],[4,2,1,"id54","pct_publication_number"],[4,2,1,"id55","publication_category_bag"],[4,2,1,"id56","publication_date_bag"],[4,2,1,"id57","publication_sequence_number_bag"],[4,2,1,"id58","raw_data"],[4,2,1,"id59","subclass"],[4,3,1,"","to_dict"],[4,2,1,"id60","uspc_symbol_text"]],"pyUSPTO.models.patent_data.Assignee":[[4,2,1,"id61","assignee_address"],[4,2,1,"id62","assignee_name_text"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Assignment":[[4,2,1,"id63","assignee_bag"],[4,2,1,"id64","assignment_document_location_uri"],[4,2,1,"id65","assignment_mailed_date"],[4,2,1,"id66","assignment_received_date"],[4,2,1,"id67","assignment_recorded_date"],[4,2,1,"id68","assignor_bag"],[4,2,1,"id69","attorney_docket_number"],[4,2,1,"id70","conveyance_text"],[4,2,1,"id71","correspondence_address"],[4,2,1,"id72","domestic_representative"],[4,2,1,"id73","frame_number"],[4,3,1,"","from_dict"],[4,2,1,"id74","image_available_status_code"],[4,2,1,"id75","page_total_quantity"],[4,2,1,"id76","reel_and_frame_number"],[4,2,1,"id77","reel_number"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Assignor":[[4,2,1,"id78","assignor_name"],[4,2,1,"id79","execution_date"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Attorney":[[4,2,1,"id80","active_indicator"],[4,2,1,"id81","attorney_address_bag"],[4,3,1,"","from_dict"],[4,2,1,"id82","registered_practitioner_category"],[4,2,1,"id83","registration_number"],[4,2,1,"id84","telecommunication_address_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.ChildContinuity":[[4,2,1,"id85","child_application_filing_date"],[4,2,1,"id86","child_application_number_text"],[4,2,1,"id87","child_application_status_code"],[4,2,1,"id88","child_application_status_description_text"],[4,2,1,"id89","child_patent_number"],[4,3,1,"","from_dict"],[4,2,1,"id90","parent_application_number_text"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Continuity":[[4,2,1,"id91","application_number_text"],[4,2,1,"id92","claim_parentage_type_code"],[4,2,1,"id93","claim_parentage_type_code_description_text"],[4,2,1,"id94","filing_date"],[4,2,1,"id95","first_inventor_to_file_indicator"],[4,5,1,"","is_aia"],[4,5,1,"","is_pre_aia"],[4,2,1,"id96","patent_number"],[4,2,1,"id97","status_code"],[4,2,1,"id98","status_description_text"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.CustomerNumberCorrespondence":[[4,3,1,"","from_dict"],[4,2,1,"id99","organization_standard_name"],[4,2,1,"id100","patron_identifier"],[4,2,1,"id101","power_of_attorney_address_bag"],[4,2,1,"id102","telecommunication_address_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.DirectionCategory":[[4,2,1,"","INCOMING"],[4,2,1,"","OUTGOING"]],"pyUSPTO.models.patent_data.Document":[[4,2,1,"id103","application_number_text"],[4,2,1,"id104","direction_category"],[4,2,1,"id105","document_code"],[4,2,1,"id106","document_code_description_text"],[4,2,1,"id107","document_formats"],[4,2,1,"id108","document_identifier"],[4,3,1,"","from_dict"],[4,2,1,"id109","official_date"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.DocumentBag":[[4,3,1,"","__init__"],[4,3,1,"","__repr__"],[4,3,1,"","__str__"],[4,5,1,"id110","documents"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.DocumentFormat":[[4,2,1,"id111","download_url"],[4,3,1,"","from_dict"],[4,2,1,"id112","mime_type_identifier"],[4,2,1,"id113","page_total_quantity"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.EntityStatus":[[4,2,1,"id114","business_entity_status_category"],[4,3,1,"","from_dict"],[4,2,1,"id115","small_entity_status_indicator"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.EventData":[[4,2,1,"id116","event_code"],[4,2,1,"id117","event_date"],[4,2,1,"id118","event_description_text"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.ForeignPriority":[[4,2,1,"id119","application_number_text"],[4,2,1,"id120","filing_date"],[4,3,1,"","from_dict"],[4,2,1,"id121","ip_office_name"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Inventor":[[4,2,1,"id122","correspondence_address_bag"],[4,3,1,"","from_dict"],[4,2,1,"id123","inventor_name_text"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.ParentContinuity":[[4,2,1,"id124","child_application_number_text"],[4,3,1,"","from_dict"],[4,2,1,"id125","parent_application_filing_date"],[4,2,1,"id126","parent_application_number_text"],[4,2,1,"id127","parent_application_status_code"],[4,2,1,"id128","parent_application_status_description_text"],[4,2,1,"id129","parent_patent_number"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.PatentDataResponse":[[4,2,1,"id130","count"],[4,3,1,"","from_dict"],[4,2,1,"id131","patent_file_wrapper_data_bag"],[4,2,1,"id132","raw_data"],[4,2,1,"id133","request_identifier"],[4,3,1,"","to_csv"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.PatentFileWrapper":[[4,2,1,"id134","application_meta_data"],[4,2,1,"id135","application_number_text"],[4,2,1,"id136","assignment_bag"],[4,2,1,"id137","child_continuity_bag"],[4,2,1,"id138","correspondence_address_bag"],[4,2,1,"id139","event_data_bag"],[4,2,1,"id140","foreign_priority_bag"],[4,3,1,"","from_dict"],[4,2,1,"id141","grant_document_meta_data"],[4,2,1,"id142","last_ingestion_date_time"],[4,2,1,"id143","parent_continuity_bag"],[4,2,1,"id144","patent_term_adjustment_data"],[4,2,1,"id145","pgpub_document_meta_data"],[4,2,1,"id146","record_attorney"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.PatentTermAdjustmentData":[[4,2,1,"id147","a_delay_quantity"],[4,2,1,"id148","adjustment_total_quantity"],[4,2,1,"id149","applicant_day_delay_quantity"],[4,2,1,"id150","b_delay_quantity"],[4,2,1,"id151","c_delay_quantity"],[4,3,1,"","from_dict"],[4,2,1,"id152","ip_office_adjustment_delay_quantity"],[4,2,1,"id153","non_overlapping_day_delay_quantity"],[4,2,1,"id154","non_overlapping_day_quantity"],[4,2,1,"id155","overlapping_day_quantity"],[4,2,1,"id156","patent_term_adjustment_history_data_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.PatentTermAdjustmentHistoryData":[[4,2,1,"id157","applicant_day_delay_quantity"],[4,2,1,"id158","event_date"],[4,2,1,"id159","event_description_text"],[4,2,1,"id160","event_sequence_number"],[4,3,1,"","from_dict"],[4,2,1,"id161","ip_office_day_delay_quantity"],[4,2,1,"id162","originating_event_sequence_number"],[4,2,1,"id163","pta_pte_code"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Person":[[4,2,1,"id164","country_code"],[4,2,1,"id165","first_name"],[4,2,1,"id166","last_name"],[4,2,1,"id167","middle_name"],[4,2,1,"id168","name_prefix"],[4,2,1,"id169","name_suffix"],[4,2,1,"id170","preferred_name"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.PrintedMetaData":[[4,2,1,"id171","file_create_date_time"],[4,2,1,"id172","file_location_uri"],[4,3,1,"","from_dict"],[4,2,1,"id173","product_identifier"],[4,3,1,"","to_dict"],[4,2,1,"id174","xml_file_name"],[4,2,1,"id175","zip_file_name"]],"pyUSPTO.models.patent_data.PrintedPublication":[[4,3,1,"","from_wrapper"],[4,2,1,"id176","grant_document_meta_data"],[4,2,1,"id177","pgpub_document_meta_data"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.RecordAttorney":[[4,2,1,"id178","attorney_bag"],[4,2,1,"id179","customer_number_correspondence_data"],[4,3,1,"","from_dict"],[4,2,1,"id180","power_of_attorney_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.StatusCode":[[4,3,1,"","__str__"],[4,2,1,"id181","code"],[4,2,1,"id182","description"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.StatusCodeCollection":[[4,3,1,"","__init__"],[4,3,1,"","find_by_code"],[4,3,1,"","search_by_description"],[4,2,1,"","status_codes"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.StatusCodeSearchResponse":[[4,2,1,"id183","count"],[4,3,1,"","from_dict"],[4,2,1,"id184","request_identifier"],[4,2,1,"id185","status_code_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.patent_data.Telecommunication":[[4,2,1,"id186","extension_number"],[4,3,1,"","from_dict"],[4,2,1,"id187","telecom_type_code"],[4,2,1,"id188","telecommunication_number"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab":[[4,1,1,"","AdditionalPartyData"],[4,1,1,"","AppealDocumentData"],[4,1,1,"","AppealMetaData"],[4,1,1,"","AppellantData"],[4,1,1,"","DecisionData"],[4,1,1,"","DerivationPetitionerData"],[4,1,1,"","InterferenceDocumentData"],[4,1,1,"","InterferenceMetaData"],[4,1,1,"","JuniorPartyData"],[4,1,1,"","PTABAppealDecision"],[4,1,1,"","PTABAppealResponse"],[4,1,1,"","PTABInterferenceDecision"],[4,1,1,"","PTABInterferenceResponse"],[4,1,1,"","PTABTrialDocument"],[4,1,1,"","PTABTrialDocumentResponse"],[4,1,1,"","PTABTrialProceeding"],[4,1,1,"","PTABTrialProceedingResponse"],[4,1,1,"","PatentOwnerData"],[4,1,1,"","RegularPetitionerData"],[4,1,1,"","RequestorData"],[4,1,1,"","RespondentData"],[4,1,1,"","SeniorPartyData"],[4,1,1,"","TrialDecisionData"],[4,1,1,"","TrialDocumentData"],[4,1,1,"","TrialMetaData"]],"pyUSPTO.models.ptab.AdditionalPartyData":[[4,2,1,"id189","additional_party_name"],[4,2,1,"id190","application_number_text"],[4,3,1,"","from_dict"],[4,2,1,"id191","inventor_name"],[4,2,1,"id192","patent_number"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.AppealDocumentData":[[4,2,1,"id193","document_filing_date"],[4,2,1,"id194","document_identifier"],[4,2,1,"id195","document_name"],[4,2,1,"id196","document_ocr_text"],[4,2,1,"id197","document_size_quantity"],[4,2,1,"id198","document_type_description_text"],[4,2,1,"id199","file_download_uri"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.AppealMetaData":[[4,2,1,"id200","appeal_filing_date"],[4,2,1,"id201","appeal_last_modified_date"],[4,2,1,"id202","appeal_last_modified_date_time"],[4,2,1,"id203","application_type_category"],[4,2,1,"id204","docket_notice_mailed_date"],[4,2,1,"id205","file_download_uri"],[4,3,1,"","from_dict"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.DecisionData":[[4,2,1,"id206","appeal_outcome_category"],[4,2,1,"id207","decision_issue_date"],[4,2,1,"id208","decision_type_category"],[4,3,1,"","from_dict"],[4,2,1,"id209","issue_type_bag"],[4,2,1,"id210","statute_and_rule_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.InterferenceDocumentData":[[4,2,1,"id211","decision_issue_date"],[4,2,1,"id212","decision_type_category"],[4,2,1,"id213","document_filing_date"],[4,2,1,"id214","document_identifier"],[4,2,1,"id215","document_name"],[4,2,1,"id216","document_ocr_text"],[4,2,1,"id217","document_size_quantity"],[4,2,1,"id218","document_title_text"],[4,2,1,"id219","file_download_uri"],[4,3,1,"","from_dict"],[4,2,1,"id220","interference_outcome_category"],[4,2,1,"id221","issue_type_bag"],[4,2,1,"id222","statute_and_rule_bag"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.InterferenceMetaData":[[4,2,1,"id223","declaration_date"],[4,2,1,"id224","file_download_uri"],[4,3,1,"","from_dict"],[4,2,1,"id225","interference_last_modified_date"],[4,2,1,"id226","interference_last_modified_date_time"],[4,2,1,"id227","interference_style_name"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.PTABAppealDecision":[[4,2,1,"id228","appeal_document_category"],[4,2,1,"id229","appeal_meta_data"],[4,2,1,"id230","appeal_number"],[4,2,1,"id231","appellant_data"],[4,2,1,"id232","decision_data"],[4,2,1,"id233","document_data"],[4,3,1,"","from_dict"],[4,2,1,"id234","last_modified_date_time"],[4,2,1,"id235","raw_data"],[4,2,1,"id236","requestor_data"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.PTABAppealResponse":[[4,2,1,"id237","count"],[4,3,1,"","from_dict"],[4,2,1,"id238","patent_appeal_data_bag"],[4,2,1,"id239","raw_data"],[4,2,1,"id240","request_identifier"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.PTABInterferenceDecision":[[4,2,1,"id241","additional_party_data_bag"],[4,2,1,"id242","document_data"],[4,3,1,"","from_dict"],[4,2,1,"id243","interference_meta_data"],[4,2,1,"id244","interference_number"],[4,2,1,"id245","junior_party_data"],[4,2,1,"id246","last_modified_date_time"],[4,2,1,"id247","raw_data"],[4,2,1,"id248","senior_party_data"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.PTABInterferenceResponse":[[4,2,1,"id249","count"],[4,3,1,"","from_dict"],[4,2,1,"id250","patent_interference_data_bag"],[4,2,1,"id251","raw_data"],[4,2,1,"id252","request_identifier"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.PTABTrialDocument":[[4,2,1,"id253","decision_data"],[4,2,1,"id254","derivation_petitioner_data"],[4,2,1,"id255","document_data"],[4,3,1,"","from_dict"],[4,2,1,"id256","last_modified_date_time"],[4,2,1,"id257","patent_owner_data"],[4,2,1,"id258","raw_data"],[4,2,1,"id259","regular_petitioner_data"],[4,2,1,"id260","respondent_data"],[4,3,1,"","to_dict"],[4,2,1,"id261","trial_document_category"],[4,2,1,"id262","trial_meta_data"],[4,2,1,"id263","trial_number"],[4,2,1,"id264","trial_type_code"]],"pyUSPTO.models.ptab.PTABTrialDocumentResponse":[[4,2,1,"","count"],[4,3,1,"","from_dict"],[4,2,1,"","patent_trial_document_data_bag"],[4,2,1,"","raw_data"],[4,2,1,"","request_identifier"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.PTABTrialProceeding":[[4,2,1,"id265","derivation_petitioner_data"],[4,3,1,"","from_dict"],[4,2,1,"id266","last_modified_date_time"],[4,2,1,"id267","patent_owner_data"],[4,2,1,"id268","raw_data"],[4,2,1,"id269","regular_petitioner_data"],[4,2,1,"id270","respondent_data"],[4,3,1,"","to_dict"],[4,2,1,"id271","trial_meta_data"],[4,2,1,"id272","trial_number"],[4,2,1,"","trial_record_identifier"]],"pyUSPTO.models.ptab.PTABTrialProceedingResponse":[[4,2,1,"id273","count"],[4,3,1,"","from_dict"],[4,2,1,"id274","patent_trial_proceeding_data_bag"],[4,2,1,"id275","raw_data"],[4,2,1,"id276","request_identifier"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.RegularPetitionerData":[[4,2,1,"id277","counsel_name"],[4,3,1,"","from_dict"],[4,2,1,"id278","real_party_in_interest_name"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.RequestorData":[[4,3,1,"","from_dict"],[4,2,1,"id279","third_party_name"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.TrialDecisionData":[[4,2,1,"id280","decision_issue_date"],[4,2,1,"id281","decision_type_category"],[4,3,1,"","from_dict"],[4,2,1,"id282","issue_type_bag"],[4,2,1,"id283","statute_and_rule_bag"],[4,3,1,"","to_dict"],[4,2,1,"id284","trial_outcome_category"]],"pyUSPTO.models.ptab.TrialDocumentData":[[4,2,1,"","document_category"],[4,2,1,"id285","document_filing_date"],[4,2,1,"id286","document_identifier"],[4,2,1,"id287","document_name"],[4,2,1,"id288","document_number"],[4,2,1,"id289","document_ocr_text"],[4,2,1,"id290","document_size_quantity"],[4,2,1,"","document_status"],[4,2,1,"id291","document_title_text"],[4,2,1,"id292","document_type_description_text"],[4,2,1,"id293","file_download_uri"],[4,2,1,"id294","filing_party_category"],[4,3,1,"","from_dict"],[4,2,1,"","mime_type_identifier"],[4,3,1,"","to_dict"]],"pyUSPTO.models.ptab.TrialMetaData":[[4,2,1,"id295","accorded_filing_date"],[4,2,1,"id296","file_download_uri"],[4,3,1,"","from_dict"],[4,2,1,"id297","institution_decision_date"],[4,2,1,"id298","latest_decision_date"],[4,2,1,"id299","petition_filing_date"],[4,2,1,"id300","termination_date"],[4,3,1,"","to_dict"],[4,2,1,"id301","trial_last_modified_date"],[4,2,1,"id302","trial_last_modified_date_time"],[4,2,1,"id303","trial_status_category"],[4,2,1,"id304","trial_type_code"]],"pyUSPTO.models.utils":[[4,6,1,"","parse_to_date"],[4,6,1,"","parse_to_datetime_utc"],[4,6,1,"","parse_yn_to_bool"],[4,6,1,"","serialize_bool_to_yn"],[4,6,1,"","serialize_date"],[4,6,1,"","serialize_datetime_as_iso"],[4,6,1,"","serialize_datetime_as_naive"],[4,6,1,"","to_camel_case"]],"pyUSPTO.warnings":[[5,4,1,"","USPTOBooleanParseWarning"],[5,4,1,"","USPTODataMismatchWarning"],[5,4,1,"","USPTODataWarning"],[5,4,1,"","USPTODateParseWarning"],[5,4,1,"","USPTOEnumParseWarning"],[5,4,1,"","USPTOTimezoneWarning"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"],"3":["py","method","Python method"],"4":["py","exception","Python exception"],"5":["py","property","Python property"],"6":["py","function","Python function"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute","3":"py:method","4":"py:exception","5":"py:property","6":"py:function"},"terms":{"":[0,2,4,5,8,10],"0":[0,1,4,10,11,12,13,14],"00":4,"000":4,"00001":[0,14],"00123":4,"001234":0,"01":[0,10,11,12,13,14],"012345":0,"0500":4,"08":0,"1":[1,10,11,12,13,14,17],"10":[1,11,12,14],"100":0,"1000":0,"10000000":10,"106123":0,"10765880":8,"10t00":4,"11000000":0,"12":[0,4,10,11,12,13,14],"123":0,"123456":0,"12345678":5,"14412875":8,"15":[0,12],"16":[0,11],"16123456":0,"167c4075aed4":0,"1700":0,"17765301":0,"18":0,"18045436":10,"18915708_12307527":0,"2":[0,10,11,12,13,14,17],"20":[10,11],"2001":0,"2009064413":8,"20150157873":8,"2020":[0,10,11,13],"2022":[0,13],"2023":[0,11,12,13,14],"2023012345":0,"20230123456":0,"2024":[4,12,14],"2100":12,"25":[0,10],"2515325c57c5":0,"2600":11,"3":[1,10,11,12,13,14,17],"30":1,"31":[0,10,11,12,13,14],"34044333":0,"3600":[0,12],"4":[11,12,13,14],"40":11,"400":2,"401":2,"403":2,"404":2,"413":2,"429":2,"456":0,"4b40":0,"5":[10,11,12,13,14],"50":0,"500":2,"515f":0,"58ec":0,"6":[0,11,12,13,14],"60":11,"7":[11,12,13],"708":0,"8":[0,10,11,13],"80":[12,13,14],"8192":0,"8601":4,"87654321":5,"915":0,"999999":13,"9f1a4a2b":0,"A":[0,2,4,10,12,13],"AND":12,"BUT":0,"For":[0,4,10,11,12,13,14,16],"IN":0,"If":[0,1,4,10],"It":[0,2,4,8,10,11,12,13,14],"NO":4,"NOT":0,"No":[8,10,11,12,13,14],"OR":[0,12,13],"TO":11,"The":[0,2,4,5,6],"These":[0,4,5],"__init__":[0,1,2,4],"__name__":[12,13,14],"__repr__":4,"__str__":[2,4],"_q":10,"a3aa":0,"a684":0,"a_delay_quant":[4,10],"abandon":0,"about":[0,2,4,11],"absolut":0,"abst":0,"accept":1,"access":[2,4,10,11],"accord":4,"accorded_filing_d":4,"acm":0,"across":[0,4,11],"act":4,"activ":[4,10,11,12,13,14],"active_ind":4,"activeind":[3,4],"actual":[10,11,12,13,14],"ad":0,"add":[0,4],"addit":[0,1,4,13,14],"additional_party_data_bag":[4,13],"additional_party_nam":[4,13],"additional_query_param":[0,14],"additionalpartydata":[3,4],"address":[0,3,4,10],"address_line_four_text":4,"address_line_one_text":4,"address_line_three_text":4,"address_line_two_text":4,"adjust":[0,4,10],"adjustment_total_quant":[4,10],"advanc":[0,11,12,13,14],"affect":4,"affirm":[0,12],"after":[4,11,12,13,14],"agenc":0,"agent":[0,4],"ai":10,"aia":4,"aim":4,"align":0,"all":[0,1,2,4,5,6,14],"allow":[0,1,5],"alreadi":0,"also":[0,2],"altern":[10,11,12,13,14],"alwai":0,"america":4,"an":[0,2,4,5],"ani":[0,4,5,13,14],"api":[0,1,2,4,5,8,10,11,12,13,14,15,17],"api_kei":[0,1,8,10,11,12,13,14,17],"api_key_fallback":10,"api_short_error":2,"apierrorarg":[2,3],"app":[0,8,10,11],"app_meta":10,"app_no_ifw":8,"app_no_to_fetch":10,"appeal":[0,1,4,9,13,14,15],"appeal_document_categori":4,"appeal_filing_d":[4,12],"appeal_last_modified_d":4,"appeal_last_modified_date_tim":4,"appeal_meta_data":[0,4,12],"appeal_numb":[0,4,12],"appeal_number_q":[0,12],"appeal_outcome_categori":4,"appealdocumentdata":[3,4],"appealmetadata":[3,4],"appealnumb":0,"appel":[0,4,12],"appellant_data":[4,12],"appellant_name_q":0,"appellantdata":[3,4],"applic":[0,3,4,5,8,10,11,12,13],"applicant_bag":[4,10],"applicant_day_delay_quant":[4,10],"applicant_nam":11,"applicant_name_q":0,"applicant_name_text":[4,10],"application_confirmation_numb":4,"application_meta_data":[4,8,10],"application_numb":[0,8,10],"application_number_q":[0,11],"application_number_text":[0,4,10,11,12,13],"application_number_text_q":[0,12],"application_status_cod":4,"application_status_d":4,"application_status_description_text":[4,10],"application_type_categori":[4,12],"application_type_cod":4,"application_type_label_nam":4,"applicationcontinuitydata":[0,3,4,10],"applicationmetadata":[0,3,4,10],"applicationnumbertext":0,"applicationstatuscod":4,"applicationstatusdescriptiontext":4,"appropri":2,"appxml":4,"ar":[0,4,13],"archiv":4,"archivemetadata":0,"argument":[0,2],"art":[4,11],"aspect":4,"assign":[0,3,4,10],"assigne":[0,3,4,10],"assignee_address":4,"assignee_bag":[4,10],"assignee_name_q":0,"assignee_name_text":[4,10],"assignment_bag":[0,4,10],"assignment_document_location_uri":4,"assignment_mailed_d":4,"assignment_received_d":4,"assignment_recorded_d":[4,10],"assignor":[0,3,4],"assignor_bag":4,"assignor_nam":4,"associ":[0,4,11],"assum":[4,10],"assumed_naive_timezon":4,"assumed_naive_timezone_str":4,"attempt":[1,4,12,13,14],"attornei":[0,3,4],"attorney_address_bag":4,"attorney_bag":4,"attorney_docket_numb":4,"attribut":4,"authent":[0,1,2],"author":2,"auto":[0,10],"automat":[0,11],"avail":[0,2,4,5,10],"avoid":4,"awar":4,"award":13,"b":[0,4,10],"b2":10,"b_delay_quant":[4,10],"back":[5,10],"backoff":1,"backoff_factor":1,"bad":2,"bag":[0,4],"base":[0,1,2,4,5,8],"base_url":0,"baseusptocli":0,"basi":0,"basic":11,"batch":0,"becom":4,"behavior":[0,1,4],"being":0,"belong":4,"between":13,"board":[0,1,4,12,13,14],"bodi":0,"bool":[0,1,4],"boolean":[4,5],"break":[11,12,13,14],"broad":10,"bulk":[0,1,4,9,15],"bulk_data":[0,4],"bulk_data_base_url":[0,1,10],"bulk_data_product_bag":4,"bulkdatacli":[0,3,17],"bulkdataproduct":[0,3,4],"bulkdatarespons":[0,3,4],"busi":[0,4,12,14],"business_entity_status_categori":4,"byte":4,"c":[0,4,10],"c_continu":10,"c_delay_quant":[4,10],"cach":1,"calcul":[0,4],"call":[0,10],"camelcas":4,"can":[0,4,5],"cannot":[0,4,5],"case":[0,4],"categori":[0,4,5,14],"cbm":[0,4,14],"cd":16,"center":[0,11,12],"chain":4,"chang":10,"check":0,"child":[0,4],"child_application_filing_d":[4,10],"child_application_number_text":[4,10],"child_application_status_cod":4,"child_application_status_description_text":4,"child_continuity_bag":[4,10],"child_patent_numb":4,"childcontinu":[0,3,4],"childcontinuitybag":4,"choos":[10,11,12,13,14],"chunk":0,"chunk_siz":0,"cite":11,"citi":4,"citizenship":4,"city_nam":[4,10],"claim":[0,4,13],"claim_parentage_type_cod":4,"claim_parentage_type_code_description_text":[4,10],"clash":4,"class":[0,1,2,4,5],"class_field":4,"classif":[0,4],"classification_q":0,"classmethod":[1,2,4],"clearer":0,"client":[1,2,3,8,10,11,12,13,14,15,17],"client1":17,"client2":17,"client3":17,"client_operation_messag":2,"clm":0,"clone":16,"code":[0,1,2,4,10],"code_obj":10,"code_to_find":4,"collect":[0,4],"com":16,"combin":[0,4],"comma":0,"common":[0,4],"compani":[4,13],"compat":4,"complet":[0,4,11,12,13,14],"complex":[0,12,13],"compon":0,"comprehens":[0,4,6],"con":4,"concern":1,"config":[0,1,10,11,12,13,14,17],"config_from_env":[10,11,12,13,14,17],"config_obj":[10,11,12,13,14],"configur":[0,3,8,10,11,12,13,14,15],"confirm":4,"conform":0,"connect":[1,2],"connect_timeout":1,"connectionerror":2,"consid":4,"consist":4,"consolid":[0,4],"construct":[0,2,4],"constructor":2,"contain":[0,2,4,5],"content":4,"context":[2,4],"continu":[0,3,4,10],"continuity_data":10,"contribut":15,"control":[0,1,5],"conveni":[0,10,11,12,13,14],"convent":4,"convention":2,"convers":[4,5],"convert":4,"convey":[0,4,10],"conveyance_text":[4,10],"core":0,"corp":[0,13],"correspond":[0,2,4],"correspondence_address":4,"correspondence_address_bag":[4,10],"correspondent_name_text":4,"could":10,"counsel":[4,13],"counsel_nam":[4,13],"count":[0,4,10,11,12,13,14,17],"countri":4,"country_cod":4,"country_nam":4,"country_or_state_cod":4,"cov":6,"cover":[0,4,14],"coverag":6,"cpc":4,"cpc_classification_bag":4,"creat":[0,1,2,4,10],"creation":4,"criteria":[0,10,11,12,13,14],"csv":[0,4,10,11],"csv_data":10,"csv_path":11,"current":[0,4,10],"custom":[0,1,4,5,10,11,14],"custom_head":1,"customer_numb":4,"customer_number_correspondence_data":4,"customernumbercorrespond":[3,4],"d":4,"dai":[4,10],"data":[0,1,2,4,5,8,9,11,15],"dataclass":4,"dataset":[0,10],"date":[0,4,5,10,11,12,13,14],"date_search_respons":10,"date_str":4,"datetim":[4,5],"datetime_str":4,"days_of_week_text":4,"dd":[0,4],"ddthh":4,"debug":[1,4],"dec":12,"decid":0,"decis":[0,1,4,9,12,13,14,15],"decision_d":11,"decision_data":[0,4,12,14],"decision_date_from_q":[0,11,12,13,14],"decision_date_to_q":[0,11,12,13],"decision_id":11,"decision_issue_d":[4,12,13,14],"decision_typ":12,"decision_type_categori":[0,4,12,13,14],"decision_type_category_q":[0,12,13,14],"decision_type_cod":[0,11],"decision_type_code_q":0,"decisiond":[11,12,13],"decisiondata":[3,4],"decisions_in_pag":11,"decisiontypecategori":[0,12],"decisiontypecod":11,"declar":4,"declaration_d":4,"default":[0,1,4,10,11,12,13,14],"default_unknown_messag":2,"defin":[0,4,5],"delai":[0,4,10],"demonstr":[8,10,11,12,13,14],"deni":4,"deprec":0,"der":[0,4,14],"deriv":[0,4,14],"derivation_petitioner_data":4,"derivationpetitionerdata":[3,4],"desc":[12,13,14],"describ":[2,4],"descript":[0,2,4,10],"design":[0,4],"destin":0,"destination_path":[0,8,10,11],"detail":[0,2,4,6,10,11],"determin":[0,2,13],"develop":[15,16],"dict":[0,1,2,4,10],"dictionari":[0,4],"differ":[4,5],"digit":0,"direct":[0,2,4,10,11,12,13,14,17],"direction_categori":[4,10],"directioncategori":[3,4],"directli":0,"directori":0,"displai":[10,11,12,13,14],"div":4,"dn":2,"do":0,"doc":[0,11,12,13,14],"docket":4,"docket_notice_mailed_d":4,"docket_numb":4,"document":[0,3,4,8,10,11,12,14],"document_bag":[0,11],"document_categori":[4,14],"document_category_q":[0,14],"document_cod":[0,4,10],"document_code_description_text":[4,10],"document_data":[0,4,12,13,14],"document_direction_categori":11,"document_filing_d":[4,14],"document_format":[0,4,10],"document_found":11,"document_id":0,"document_identifi":[4,10,11],"document_nam":[4,12],"document_numb":4,"document_ocr_text":4,"document_size_quant":4,"document_statu":4,"document_title_text":4,"document_to_download":10,"document_type_description_q":0,"document_type_description_text":[4,14],"document_type_name_q":0,"documentbag":[0,3,4],"documentdata":4,"documentdownloadopt":0,"documentformat":[0,3,4],"documents_bag":10,"doe":0,"doesn":[0,5],"domest":4,"domestic_repres":4,"download":[0,4,8,10,11,12,14],"download_application_docu":0,"download_arch":[0,8],"download_decis":[0,11],"download_docu":[0,10],"download_fil":0,"download_opt":[0,11],"download_option_bag":[0,11],"download_path":[8,10],"download_petition_docu":[0,11],"download_publ":[0,10],"download_uri":14,"download_url":[4,11],"downloaded_path":10,"downloadurl":4,"dr":4,"drwd":0,"dt":4,"due":10,"dunlapcoddingpc":16,"dure":11,"e":[0,2,4,5,10,11,12,13,14,16],"each":[0,4],"earlier":4,"earliest":4,"earliest_publication_d":4,"earliest_publication_numb":4,"earliestpublicationnumber_q":0,"easili":0,"eee1":0,"effect":4,"effective_filing_d":4,"either":[0,13],"electron":12,"element":4,"els":[10,12,13,14],"empti":[0,4],"enabl":0,"encod":10,"encount":5,"end":4,"endpoint":[0,4],"ensur":[10,11,12,13,14],"entir":4,"entiti":4,"entity_status_data":4,"entitystatu":[3,4],"entri":4,"enum":[4,5],"env":10,"environ":[1,8,10,11,12,13,14,17],"error":[2,5,10,11,12,13,14],"error_arg":2,"error_detail":2,"establish":1,"etc":[2,4],"event":[0,4],"event_cod":4,"event_d":4,"event_data_bag":[0,4],"event_description_text":4,"event_sequence_numb":4,"eventdata":[0,3,4],"ex":[0,12],"examin":[0,4,11,12],"examiner_name_text":[4,11],"exampl":[4,15],"example_snake_str":4,"examplesnakestr":4,"exceed":2,"except":[3,5,10,11,12,13,14,15],"execut":4,"execution_d":4,"exist":0,"expect":[0,4,12,13,14],"exponenti":1,"extens":4,"extension_numb":4,"extract":[0,4],"f":[0,8,10,11,12,13,14,17],"facet":0,"factor":1,"factori":[1,4],"fail":[2,4,5,14],"failur":[2,5],"fall":[5,10],"fallback":10,"fals":[0,1,4,11],"fax":4,"fetch":[0,12,13,14],"few":10,"field":[0,4,5,14],"fields_param":0,"fifth":0,"file":[0,4,6,9,10,11,12,14,15],"file_create_date_tim":[4,10],"file_d":4,"file_data":0,"file_data_bag":4,"file_data_from_d":[0,4],"file_data_to_d":[0,4],"file_download_uri":[0,4,12],"file_last_modified_date_tim":4,"file_location_uri":[0,4],"file_nam":[0,4,10],"file_path":[0,8,11],"file_release_d":4,"file_s":4,"file_typ":0,"file_type_text":4,"filedata":[0,3,4],"fileexistserror":0,"filenam":[0,4,10],"filing_d":[4,10],"filing_date_from_q":[0,10],"filing_date_to_q":[0,10],"filing_party_categori":4,"filter":[0,4,5,14],"filters_param":0,"filterwarn":5,"final":[0,1,4,11,13,14],"final_deciding_office_name_q":0,"finalpetitiondecisionscli":[0,3,11],"find":[4,11],"find_by_cod":4,"fine":1,"first":[0,2,4,10,11,12,13,14],"first_applicant_nam":4,"first_inventor_nam":4,"first_inventor_to_file_ind":4,"first_nam":[4,10,11],"flexibli":4,"float":[1,4],"fmt":4,"folder":10,"follow":5,"forbidden":2,"foreign":[0,4],"foreign_priority_bag":[0,4],"foreignprior":[0,3,4],"format":[0,4,10,11,14],"found":[0,2,4,8,10,11,12,13,14,17],"found_patent_wrapp":10,"fourth":[0,4],"frame":[0,4],"frame_numb":4,"framework":5,"friendli":4,"from":[0,1,2,4,5,8,10,11,12,13,14,17],"from_dat":0,"from_dict":4,"from_env":[1,10,11,12,13,14,17],"from_http_error":2,"from_request_except":2,"from_wrapp":[4,10],"frozen":4,"full":[0,4],"function":[0,2,4],"g":[0,2,4,5,10],"gener":[0,2,4,10],"geograph":4,"geographic_region_cod":[4,10],"geographic_region_nam":4,"get":[0,1,8,10,11,12,13,14,17],"get_api_except":[2,3],"get_application_adjust":0,"get_application_assign":0,"get_application_associated_docu":0,"get_application_attornei":0,"get_application_by_numb":[0,10],"get_application_continu":0,"get_application_docu":[0,10],"get_application_foreign_prior":0,"get_application_metadata":0,"get_application_transact":0,"get_decision_by_id":[0,11],"get_ifw_metadata":[0,8],"get_product":0,"get_product_by_id":0,"get_search_result":0,"get_status_cod":[0,10],"get_timeout_tupl":1,"getitem":4,"git":16,"github":16,"given":[0,12,13,14],"gov":[0,1,10,11,12,13,14],"grain":1,"grant":[0,4,8,10,11,14],"grant_arch":8,"grant_dat":[4,10],"grant_date_from_q":0,"grant_date_to_q":0,"grant_document_meta_data":[0,4,8,10],"grant_metadata":[0,10],"grant_path":10,"group":[4,11],"group_art_unit_numb":[4,11],"guid":17,"ha":[0,5],"handl":[0,1,4,12,13,14],"have":11,"header":[1,10],"help":17,"helper":[2,4],"hhmm":4,"histori":[0,4],"hold":[0,2,4],"holder":4,"how":[6,8,10,11,12,13,14],"http":[0,1,2,10,11,12,13,14,16],"http_config":1,"http_error":2,"httpconfig":[1,3],"httperror":2,"human":[2,4],"i":[0,2,4,5,8,10,11,12,13,14],"ict_country_cod":4,"ict_state_cod":4,"id":[0,4,10,11],"ident":0,"identifi":[0,2,4,5,8,10],"ifw":[0,8],"ignor":[4,5],"imag":[4,9,15],"image_available_status_cod":4,"immut":4,"import":[0,5,8,10,11,12,13,14,17],"inact":4,"includ":[0,1,2,4,6,10,12,14],"include_docu":0,"include_fil":0,"include_raw_data":[1,4],"includedocu":0,"inclus":0,"incom":[0,4],"inconsist":5,"increment":0,"index":15,"indic":[0,4,5],"individu":4,"influenc":0,"inform":[0,2,4,10,11],"ingest":4,"inherit":[4,5],"initi":[0,1,2,4,10,11,12,13,14,17],"input":[0,4],"input_numb":0,"insensit":4,"instal":15,"instanc":[0,1,2,4],"instanti":2,"instead":0,"institut":[4,14],"institution_decision_d":4,"int":[0,1,2,4],"integr":5,"intellectu":4,"inter":[0,14],"interact":[0,4,8,10,11,12,13,14],"interest":[0,4],"interfer":[0,4,9],"interference_last_modified_d":[4,13],"interference_last_modified_date_tim":4,"interference_meta_data":[0,4,13],"interference_numb":[0,4,13],"interference_number_q":[0,13],"interference_outcome_categori":[0,4,13],"interference_outcome_category_q":[0,13],"interference_style_nam":[4,13],"interferencedocumentdata":[3,4],"interferencemetadata":[3,4],"interferencenumb":0,"interferenceoutcomecategori":13,"intern":[0,2,4],"international_registration_numb":4,"international_registration_publication_d":4,"invalid":[0,12,14],"invent":[0,4,13],"invention_titl":[4,8,10],"invention_title_q":0,"inventiontitl":10,"inventor":[0,3,4,10,11,12,17],"inventor_bag":[4,10,11],"inventor_nam":[4,12,17],"inventor_name_q":[0,10],"inventor_name_text":4,"inventor_search":17,"inventor_search_respons":10,"involv":13,"ip":[0,4],"ip_office_adjustment_delay_quant":4,"ip_office_day_delay_quant":4,"ip_office_nam":4,"ipr":[0,4,14],"ipr2023":[0,4,14],"is_aia":4,"is_pre_aia":4,"iso":4,"issu":[0,4,5],"issue_type_bag":4,"item":[4,14],"iter":[0,4],"iter_cont":0,"its":[0,4],"join":[10,11],"jr":4,"json":[0,1,4,10,11],"junior":[0,4,13],"junior_party_application_number_q":0,"junior_party_data":[4,13],"junior_party_name_q":0,"juniorpartydata":[3,4],"kei":[0,1,4,8,10,11,12,13,14,17],"keyword":[0,4],"known":10,"kwarg":0,"label":[0,4],"lack":4,"larg":[0,2],"last":[4,13,14],"last_ingestion_date_tim":4,"last_modified_date_tim":[4,14],"last_nam":[4,10,11],"lastmodifieddatetim":14,"later":[0,4],"latest":0,"latest_decision_d":4,"lead":0,"len":[4,10,11,12,13,14],"let":8,"letter":4,"level":[1,2,4],"librari":[1,2,6,17],"like":[0,4],"limit":[0,2,10,11,12,13,14],"line":[4,10],"lineag":[0,4],"link":0,"list":[0,1,4,10],"local":[4,10],"locat":4,"logic":0,"lookup":0,"lowercamelcas":4,"m":[4,6],"mai":0,"mail":[0,4],"mailing_address":4,"main":[2,4],"manag":[0,1],"map":4,"match":[0,4,5,10,11],"max":[1,11],"max_pag":11,"max_retri":1,"maximum":[0,1],"md":6,"member":[4,5],"mention":11,"messag":2,"meta":[0,12,13,14],"metadata":[0,4],"method":[0,4,10,11,12,13,14,17],"middl":4,"middle_nam":4,"might":[0,12,13,14],"mime":[0,4,11],"mime_type_identifi":[0,4,11],"mime_type_identifier_array_text":4,"mimetypeidentifi":4,"mismatch":5,"miss":10,"mm":[0,4],"mode":5,"model":[3,10,15],"modif":4,"modifi":[13,14],"modul":[0,1,2,4,5,10,11,12,13,14,15],"monthli":0,"more":[0,2,4,10,12,13],"most":4,"mr":4,"multipl":[0,11,12,13],"multipli":1,"multiprocess":8,"must":0,"my_pgpub":10,"my_publ":0,"n":[4,5,10,11,12,13,14],"naiv":4,"name":[0,4,10,12,13,17],"name_line_one_text":4,"name_line_two_text":4,"name_part":[10,11],"name_prefix":4,"name_suffix":4,"nassign":10,"nation":4,"national_stage_ind":4,"nativ":4,"nattempt":[10,12,13,14],"natur":4,"nbegin":[8,10,11,12,13,14],"ncheck":10,"nchild":10,"ndecis":11,"ndisplai":[12,13,14],"ndownload":[10,11],"necessari":2,"nest":4,"network":2,"new":[4,14],"new_york":4,"newlin":10,"next":0,"nfirst":10,"nfound":[11,12,13,14],"nfull":10,"ngener":10,"nget":[8,10],"niter":[12,13,14],"nmethod":[10,11,12,13,14],"nn":0,"nnnnnn":0,"nnnnnnnn":0,"non":4,"non_overlapping_day_delay_quant":4,"non_overlapping_day_quant":4,"none":[0,1,2,4,5,10],"notic":4,"notif":4,"now":8,"nparent":10,"npre":10,"nretriev":10,"nsearch":[10,11],"ntotal":11,"number":[0,1,4,8,10,11,12,13,14],"numer":4,"o":[8,10,11,12,13,14,17],"object":[0,1,2,4,5,11],"obtain":0,"occur":[12,13,14],"ocr":4,"odp":[0,4],"offer":4,"offic":[0,4],"offici":[0,4],"official_d":[4,10,11],"official_date_from":0,"official_date_to":0,"offset":[0,10],"often":[2,4],"omit":4,"onc":5,"one":[0,10,11,12,13,14],"onli":0,"open":[0,4,10],"openapi":0,"oper":2,"opt":0,"option":[0,1,4,10,11,12,13,14],"order":0,"organ":4,"organization_standard_nam":4,"origin":4,"originating_event_sequence_numb":4,"other":[0,4],"otherwis":[0,4],"out":[2,4,10],"outcom":[0,4,13],"outgo":[0,4],"output":0,"outsid":10,"over":[0,1],"overal":4,"overlap":4,"overlapping_day_quant":4,"overrid":0,"overwrit":[0,8,10],"owner":[0,4,14],"p_continu":10,"page":[0,4,11,12,13,14,15],"page_count":11,"page_respons":11,"page_s":11,"page_total_quant":[4,11],"pagetotalquant":4,"pagin":[0,10,11,12,13,14],"paginate_appl":0,"paginate_decis":[0,11,12,13],"paginate_proceed":[0,14],"paginate_product":0,"paper":[0,14],"param":[0,2,10],"paramet":[0,1,2,4,10,11,12,13,14],"parent":[0,2,4],"parent_application_filing_d":[4,10],"parent_application_number_text":[4,10],"parent_application_status_cod":4,"parent_application_status_description_text":4,"parent_continuity_bag":[4,10],"parent_patent_numb":4,"parentcontinu":[0,3,4],"parentcontinuitybag":4,"pars":[2,4,5],"parse_to_d":[3,4],"parse_to_datetime_utc":[3,4],"parse_yn_to_bool":[3,4],"part":[0,4,10,11,12,14],"parti":[0,4,13],"particular":0,"partydata":4,"pass":[0,2],"pat":8,"pat_no_ifw":8,"patent":[0,1,4,8,9,11,12,13,14,15,17],"patent_appeal_data_bag":[4,12],"patent_data":[0,4,8,10],"patent_data_base_url":[1,10],"patent_file_wrapper_data_bag":[4,10],"patent_interference_data_bag":[4,13],"patent_numb":[0,4,8,10,11,13,14],"patent_number_q":[0,10],"patent_owner_data":[4,14],"patent_owner_nam":[4,13,14],"patent_owner_name_q":[0,14],"patent_search_respons":10,"patent_search_result":10,"patent_term_adjustment_data":[4,10],"patent_term_adjustment_history_data_bag":4,"patent_trial_document_data_bag":[4,14],"patent_trial_proceeding_data_bag":[4,14],"patent_wrapp":10,"patent_wrapper_detail":10,"patentdatacli":[0,3,8,10,17],"patentdatarespons":[0,3,4,10],"patentdownloadrequest":0,"patentfilewrapp":[0,3,4],"patentownerdata":[3,4],"patenttermadjustmentdata":[0,3,4],"patenttermadjustmenthistorydata":[3,4],"path":0,"patron":4,"patron_identifi":4,"pattern":12,"payload":2,"pct":[0,4,8],"pct_app_no_ifw":8,"pct_app_numb":[0,8],"pct_pub_no_ifw":8,"pct_pub_numb":[0,8],"pct_publication_d":4,"pct_publication_numb":4,"pctpublicationnumber_q":0,"pctus0812705":8,"pctus2412345":0,"pdf":[0,4],"pdf_option":0,"pend":0,"per":[1,11,12,13,14],"perform":0,"person":[3,4],"petit":[0,1,4,9,15],"petition":[0,4,14],"petition_decis":0,"petition_decision_data":[0,11],"petition_decision_data_bag":11,"petition_decision_record_identifi":[0,11],"petition_decisions_base_url":[1,11],"petition_filing_d":[4,14],"petition_filing_date_from_q":[0,14],"petition_filing_date_to_q":[0,14],"petition_mail_date_from_q":0,"petition_mail_date_to_q":0,"petitiondecis":0,"petitiondecisiondownloadrespons":0,"petitiondecisionrecordidentifi":0,"petitiondecisionrespons":0,"petitioner_real_party_in_interest_name_q":[0,14],"petitionfilingd":14,"pg_publication_document_meta_data":0,"pgpub":[0,4,10],"pgpub_arch":8,"pgpub_document_meta_data":[0,4,8,10],"pgpub_metadata":[0,10],"pgpub_path":10,"pgr":[0,4,14],"phd":4,"phone":4,"pip":16,"placehold":10,"pleas":[8,10,11,12,13,14],"plu":4,"pool":1,"pool_connect":1,"pool_maxs":1,"popul":2,"portal":[0,4],"possibl":[0,4],"post":[0,10,14],"post_bodi":[0,10],"post_respons":10,"post_search_bodi":10,"postal":4,"postal_address_categori":4,"postal_cod":4,"potenti":[5,14],"power":[0,4],"power_of_attorney_address_bag":4,"power_of_attorney_bag":4,"practition":4,"pre":[0,4],"prefer":4,"preferred_nam":4,"prefix":4,"present":13,"preserv":0,"pretti":10,"primari":[2,4],"primarili":4,"print":[0,4,8,10,11,12,13,14,17],"printed_metadata":[0,8,10],"printedmetadata":[0,3,4],"printedpubl":[0,3,4],"prioriti":[0,4,13],"proceed":[0,4,13,14],"process":[0,10],"process_decis":0,"product":[0,4,10,11,12,13,14],"product_by_id":0,"product_dataset_array_text":4,"product_dataset_category_array_text":4,"product_descript":0,"product_description_text":4,"product_file_bag":4,"product_file_total_quant":4,"product_frequency_text":4,"product_from_d":4,"product_id":0,"product_identifi":[4,10],"product_label_array_text":4,"product_short_nam":0,"product_titl":0,"product_title_text":4,"product_to_d":4,"product_total_file_s":4,"productfilebag":[3,4],"products_search":0,"project":6,"properti":[2,4],"prosecut":0,"provid":[0,1,2,4],"provinc":4,"provis":4,"pta":[0,4,10],"pta_pte_cod":4,"ptab":[0,1,4,9,15],"ptab_app":0,"ptab_base_url":[1,12,13,14],"ptab_interfer":0,"ptab_trial":0,"ptabappealdecis":[0,3,4],"ptabappealrespons":[0,3,4],"ptabappealscli":[0,3,12],"ptabinterferencedecis":[0,3,4],"ptabinterferencerespons":[0,3,4],"ptabinterferencescli":[0,3,13],"ptabtrialdocu":[3,4],"ptabtrialdocumentrespons":[0,3,4],"ptabtrialproceed":[0,3,4],"ptabtrialproceedingrespons":[0,3,4],"ptabtrialscli":[0,3,14],"pte":[0,4],"ptgrxml":4,"pub":8,"pub_no_ifw":8,"public":[0,4,8,10,13],"publication_category_bag":4,"publication_d":4,"publication_date_bag":4,"publication_numb":[0,4,8,13],"publication_sequence_number_bag":4,"publicli":0,"publish":0,"purpos":10,"pydant":4,"pytest":6,"python":[4,5,6],"pyuspto":[0,1,2,4,5,6,8,10,11,12,13,14,16,17],"q":[0,10],"qualifi":4,"quantiti":[0,4],"queri":[0,4,10,11,12,13,14],"quick":15,"rais":[0,5,8,10,11,12,13,14],"rang":[0,4,10,11],"range_filt":0,"range_filters_param":0,"rate":2,"rather":4,"raw":[0,1,4],"raw_data":4,"read":1,"read_timeout":1,"readabl":[2,4],"real":[0,4],"real_party_in_interest_nam":[4,14],"real_party_in_interest_name_q":0,"real_party_in_interest_q":0,"receiv":[4,5],"recent":[4,13],"recommend":[10,11,12,13,14],"record":[0,4,10,11],"record_attornei":4,"recordattornei":[0,3,4],"reel":[0,4],"reel_and_frame_numb":4,"reel_numb":4,"refer":[2,15],"refus":2,"region":4,"registered_practitioner_categori":4,"registr":4,"registration_numb":4,"regular":4,"regular_petitioner_data":[4,14],"regularpetitionerdata":[3,4],"rel":4,"relat":[0,4,5,12],"relationship":[0,4],"reli":10,"remov":0,"replac":[8,10,11,12,13,14],"report":6,"repres":[0,4],"represent":[2,4],"request":[0,1,2,4,5,8,10,11,12,13,14],"request_except":2,"request_identifi":[2,4],"requestexcept":2,"requestor":[0,4],"requestor_data":4,"requestor_name_q":0,"requestordata":[3,4],"requir":0,"resourc":2,"respond":[0,4],"respondent_data":4,"respondent_name_q":0,"respondentdata":[3,4],"respons":[0,1,2,4,5,10,11,12,13,14],"rest":10,"result":[0,4,10,11,12,13,14],"retri":1,"retriev":[0,8,10,11],"retry_status_cod":1,"return":[0,1,2,4,5,11,12,13],"revers":12,"review":[0,14],"right":4,"row":10,"rule":[4,11],"rule_bag":11,"run":6,"same":[0,13],"sanit":0,"sanitize_application_numb":0,"save":[0,10,11],"schema":0,"script":10,"search":[0,4,10,11,12,13,14,15,17],"search_appl":[0,10],"search_by_descript":4,"search_decis":[0,4,11,12,13,14],"search_docu":[0,4,14],"search_pat":[10,17],"search_proceed":[0,14],"search_product":0,"search_request":0,"search_status_cod":0,"second":[0,1,4],"section":[0,4],"see":[0,6],"send":0,"senior":[0,4,13],"senior_party_application_number_q":[0,13],"senior_party_data":[4,13],"senior_party_name_q":[0,13],"seniorpartydata":[3,4],"sent":0,"separ":[0,1],"sequenc":4,"seri":[0,2],"serial":4,"serialize_bool_to_yn":[3,4],"serialize_d":[3,4],"serialize_datetime_as_iso":[3,4],"serialize_datetime_as_na":[3,4],"server":2,"set":[0,1,4,5,8,10,11,12,13,14],"short":[0,2],"should":[0,5,10],"show":[4,8,10,11,12,13,14],"shown":10,"simplifi":0,"sinc":[12,13],"singl":[0,4],"size":[1,4],"skip":0,"small":4,"small_entity_status_ind":4,"smith":[10,17],"snake_cas":4,"snake_str":4,"softwar":12,"some":[4,10],"sort":[0,12,13,14],"sourc":[0,1,2,4,5],"space":0,"spec":0,"specif":[0,2,4,5,10,11,13,14],"specifi":0,"splitlin":10,"ss":4,"stage":4,"standard":[4,5],"start":[0,11,12,13,15],"state":4,"statu":[0,1,2,4,10,14],"status_cod":[0,2,4],"status_code_bag":[4,10],"status_code_respons":10,"status_description_text":4,"statuscod":[0,3,4],"statuscodecollect":[0,3,4],"statuscodesearchrespons":[0,3,4],"statut":[4,11],"statute_and_rule_bag":4,"statute_bag":11,"stop":[11,12,13,14],"storag":4,"store":[1,4],"str":[0,1,2,4],"stream":0,"street":4,"strict":5,"string":[0,2,4,5,12,13],"strip":[10,11],"strptime":4,"structur":[0,2,4],"style":[4,13],"subclass":[2,4],"success":[0,4],"successfulli":[10,12,13,14],"suffix":4,"suit":[0,6],"suitabl":4,"summari":4,"support":[0,4],"suppress":5,"surnam":4,"symbol":4,"syntax":0,"t":[0,5,10],"target":0,"target_patent_numb":10,"tc":[11,12],"technologi":[0,11,12],"technology_center_numb":[4,11,12],"technology_center_number_q":[0,12],"technology_center_q":[0,11],"technologycent":0,"technologycenternumb":12,"tel":4,"telecom_type_cod":4,"telecommun":[3,4],"telecommunication_address_bag":4,"telecommunication_numb":4,"term":[0,4,10,11],"termin":[4,14],"termination_d":4,"test":15,"text":[0,4,8],"textual":[0,4],"than":[0,4],"thei":0,"them":0,"themselv":0,"thi":[0,1,2,4,5,8,10,11,12,13,14,17],"third":[0,4],"third_party_nam":4,"those":4,"through":[0,11,12,13,14],"time":2,"timeout":[1,2],"timestamp":4,"timezon":[4,5],"titl":[0,4,10],"to_camel_cas":[3,4],"to_csv":[4,10],"to_dat":0,"to_dict":[4,10],"too":2,"top":4,"total":[0,4,10,11],"total_decis":11,"transact":[0,4],"transfer":4,"transform":4,"transport":1,"treat":0,"trial":[0,1,4,9,12,13,15],"trial_document_categori":4,"trial_last_modified_d":4,"trial_last_modified_date_tim":4,"trial_meta_data":[4,14],"trial_numb":[4,14],"trial_number_q":[0,14],"trial_outcome_categori":4,"trial_record_identifi":4,"trial_status_categori":[4,14],"trial_status_category_q":[0,14],"trial_type_cod":[4,14],"trial_type_code_q":[0,14],"trialdecisiondata":[3,4],"trialdocumentdata":[3,4],"trialmetadata":[3,4],"trialnumb":[0,14],"trigger":1,"true":[0,1,4,8,10,11],"try":[10,11,12,13,14],"tupl":[1,4],"turn":5,"two":[0,4,13],"type":[0,1,2,4,5,10,11,12,13,14],"typic":[0,4],"u":[4,10],"under":4,"underli":0,"undiscount":4,"unexpect":[0,5],"union":0,"uniqu":[0,4],"unit":[4,11],"unk":2,"unknown":4,"unlik":[12,13],"until":0,"up":10,"uri":[0,4],"url":[0,1,4,11,12,14],"us":[0,1,4,5,8,10,11,12,13,14,17],"us2024":0,"usag":[0,8,10,11,12,13,14],"user":[4,5],"userwarn":5,"uspc":4,"uspc_symbol_text":4,"uspto":[0,1,2,4,5,8,10,11,12,13,14],"uspto_api":10,"uspto_api_kei":[1,8,10,11,12,13,14,17],"uspto_backoff_factor":1,"uspto_connect_timeout":1,"uspto_max_retri":1,"uspto_pool_connect":1,"uspto_pool_maxs":1,"uspto_request_timeout":1,"usptoapiautherror":[2,3],"usptoapibadrequesterror":[2,3],"usptoapierror":[2,3],"usptoapinotfounderror":[2,3],"usptoapipayloadtoolargeerror":[2,3],"usptoapiratelimiterror":[2,3],"usptoapiservererror":[2,3],"usptobooleanparsewarn":[3,4,5],"usptoconfig":[0,1,3,10,11,12,13,14,17],"usptoconnectionerror":[2,3],"usptodatamismatchwarn":[3,5],"usptodatawarn":[3,5],"usptodateparsewarn":[3,4,5],"usptoenumparsewarn":[3,5],"usptotimeout":[2,3],"usptotimezonewarn":[3,4,5],"utc":[4,5],"utf":10,"util":[0,4],"uuid":[0,4],"v1":[0,10],"valid":[0,5,10],"valu":[1,4,5,8],"valueerror":[0,8,10,11,12,13,14],"var":10,"variabl":[1,8,10,11,12,13,14,17],"variou":[0,2,4,8,10,12,13,14],"veri":13,"version":0,"via":[1,10],"w":10,"wa":[0,4,12],"warn":[3,4,8,10,11,12,13,14,15],"wasn":10,"wb":0,"when":[0,4,5,13],"where":[0,4,12],"whether":[0,4],"which":[0,2,4],"while":0,"who":4,"wide":[0,4],"within":[0,4],"without":0,"work":5,"wrapper":[0,4,9,10,15],"write":[0,10],"written":[0,4,14],"xml":[0,4,10],"xml_file_nam":[0,4,10],"y":[4,5],"ye":4,"yield":0,"you":[0,10,11,17],"your":[10,11,12,13,14],"your_api_key_her":[8,10,11,12,13,14,17],"your_api_key_here_fallback":10,"yyyi":[0,4],"z":4,"zip":4,"zip_file_nam":4},"titles":["Clients","Configuration","Exceptions","API Reference","Models","Warnings","Development","Bulk Data Examples","Image File Wrapper Example","Examples","Patent Data Examples","Petition Decisions Example","PTAB Appeals Example","PTAB Interferences Example","PTAB Trials Example","Welcome to pyUSPTO\u2019s documentation!","Installation","Quick Start"],"titleterms":{"":15,"api":3,"appeal":12,"bulk":7,"client":0,"configur":[1,17],"content":15,"contribut":6,"data":[7,10],"decis":11,"develop":6,"document":15,"exampl":[0,5,7,8,9,10,11,12,13,14,17],"except":2,"file":8,"imag":8,"indic":15,"instal":16,"interfer":13,"model":4,"patent":10,"petit":11,"ptab":[12,13,14],"pyuspto":15,"quick":17,"refer":3,"start":17,"tabl":15,"test":6,"trial":14,"warn":5,"welcom":15,"wrapper":8}}) \ No newline at end of file diff --git a/docs/source/api/clients.rst b/docs/source/api/clients.rst index 66d240e..3c555b9 100644 --- a/docs/source/api/clients.rst +++ b/docs/source/api/clients.rst @@ -10,3 +10,23 @@ Clients :members: :undoc-members: :show-inheritance: + +.. automodule:: pyUSPTO.clients.petition_decisions + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.clients.ptab_appeals + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.clients.ptab_interferences + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.clients.ptab_trials + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/api/config.rst b/docs/source/api/config.rst index e085fda..91dc506 100644 --- a/docs/source/api/config.rst +++ b/docs/source/api/config.rst @@ -5,3 +5,8 @@ Configuration :members: :undoc-members: :show-inheritance: + +.. automodule:: pyUSPTO.http_config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index e048dad..3137977 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -8,3 +8,4 @@ API Reference models config exceptions + warnings diff --git a/docs/source/api/models.rst b/docs/source/api/models.rst index ff0a4aa..55b2c67 100644 --- a/docs/source/api/models.rst +++ b/docs/source/api/models.rst @@ -10,3 +10,18 @@ Models :members: :undoc-members: :show-inheritance: + +.. automodule:: pyUSPTO.models.petition_data + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.models.ptab + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pyUSPTO.models.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/warnings.rst b/docs/source/api/warnings.rst new file mode 100644 index 0000000..293a606 --- /dev/null +++ b/docs/source/api/warnings.rst @@ -0,0 +1,7 @@ +Warnings +======== + +.. automodule:: pyUSPTO.warnings + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py index e026c44..ef47c6d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "pyUSPTO" -copyright = "2025, Dunlap Codding, PC" +copyright = "2025, Dunlap Codding, P.C." author = "Andrew Piechocki" # The full version, including alpha/beta/rc tags diff --git a/docs/source/examples/bulk_data.rst b/docs/source/examples/bulk_data.rst index a4547ab..97b6114 100644 --- a/docs/source/examples/bulk_data.rst +++ b/docs/source/examples/bulk_data.rst @@ -1,6 +1,6 @@ Bulk Data Examples ================== -.. literalinclude:: ../../examples/bulk_data_example.py +.. literalinclude:: ../../../examples/bulk_data_example.py :language: python :linenos: diff --git a/docs/source/examples/ifw_example.rst b/docs/source/examples/ifw_example.rst new file mode 100644 index 0000000..d228b48 --- /dev/null +++ b/docs/source/examples/ifw_example.rst @@ -0,0 +1,6 @@ +Image File Wrapper Example +========================== + +.. literalinclude:: ../../../examples/ifw_example.py + :language: python + :linenos: diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index 0b0a3f0..029a8c4 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -6,3 +6,8 @@ Examples bulk_data patent_data + ifw_example + petition_decisions + ptab_appeals + ptab_interferences + ptab_trials diff --git a/docs/source/examples/patent_data.rst b/docs/source/examples/patent_data.rst index b0696d3..31a7253 100644 --- a/docs/source/examples/patent_data.rst +++ b/docs/source/examples/patent_data.rst @@ -1,6 +1,6 @@ Patent Data Examples ==================== -.. literalinclude:: ../../examples/patent_data_example.py +.. literalinclude:: ../../../examples/patent_data_example.py :language: python :linenos: diff --git a/docs/source/examples/petition_decisions.rst b/docs/source/examples/petition_decisions.rst new file mode 100644 index 0000000..06bf5b8 --- /dev/null +++ b/docs/source/examples/petition_decisions.rst @@ -0,0 +1,6 @@ +Petition Decisions Example +========================== + +.. literalinclude:: ../../../examples/petition_decisions_example.py + :language: python + :linenos: diff --git a/docs/source/examples/ptab_appeals.rst b/docs/source/examples/ptab_appeals.rst new file mode 100644 index 0000000..7bd1173 --- /dev/null +++ b/docs/source/examples/ptab_appeals.rst @@ -0,0 +1,6 @@ +PTAB Appeals Example +==================== + +.. literalinclude:: ../../../examples/ptab_appeals_example.py + :language: python + :linenos: diff --git a/docs/source/examples/ptab_interferences.rst b/docs/source/examples/ptab_interferences.rst new file mode 100644 index 0000000..2c61060 --- /dev/null +++ b/docs/source/examples/ptab_interferences.rst @@ -0,0 +1,6 @@ +PTAB Interferences Example +========================== + +.. literalinclude:: ../../../examples/ptab_interferences_example.py + :language: python + :linenos: diff --git a/docs/source/examples/ptab_trials.rst b/docs/source/examples/ptab_trials.rst new file mode 100644 index 0000000..24336ec --- /dev/null +++ b/docs/source/examples/ptab_trials.rst @@ -0,0 +1,6 @@ +PTAB Trials Example +=================== + +.. literalinclude:: ../../../examples/ptab_trials_example.py + :language: python + :linenos: diff --git a/examples/bulk_data_example.py b/examples/bulk_data_example.py index 5534329..362efa9 100644 --- a/examples/bulk_data_example.py +++ b/examples/bulk_data_example.py @@ -1,20 +1,19 @@ -""" -Example usage of the uspto_api module for bulk data +"""Example usage of the uspto_api module for bulk data. This example demonstrates how to use the BulkDataClient to interact with the USPTO Bulk Data API. It shows how to retrieve product information, search for products, and download files. """ import os + import requests -from typing import Union, List, Optional + from pyUSPTO.clients import BulkDataClient # Import from top-level package from pyUSPTO.config import USPTOConfig -def format_size(size_bytes: Union[int, float]) -> str: - """ - Format a size in bytes to a human-readable string (KB, MB, GB, etc.) +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 @@ -91,7 +90,7 @@ def format_size(size_bytes: Union[int, float]) -> str: if file_data.file_download_uri: print(f" Download URI: {file_data.file_download_uri}") else: - print(f"\nNo files available for this product") + print("\nNo files available for this product") except Exception as e: print(f"\nError retrieving detailed product info: {e}") diff --git a/examples/ifw_example.py b/examples/ifw_example.py index d8272ce..fb6e443 100644 --- a/examples/ifw_example.py +++ b/examples/ifw_example.py @@ -1,12 +1,11 @@ -""" -Example usage of pyUSPTO for IFW data +"""Example usage of pyUSPTO for IFW data. This example demonstrates how to use the PatentDataClient to interact with the USPTO Patent Data API. It shows how to retrieve IFW based on various identifying values. """ +import json import os -from multiprocessing import Value from pyUSPTO.clients.patent_data import PatentDataClient @@ -22,53 +21,59 @@ print("\nBeginning API requests with configured client:") print("\nGet IFW Based on Application Number ->") -app_no_ifw = client.get_IFW_metadata(application_number="14412875") +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(app_no_ifw.application_meta_data.invention_title) - print(" - IFW Found based on App No") + print(f"Title: {app_no_ifw.application_meta_data.invention_title}") + print(f" - IFW Found based on App No: {application_number}") print("\nGet IFW Based on Patent Number ->") -pat_no_ifw = client.get_IFW_metadata(patent_number="10765880") +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(pat_no_ifw.application_meta_data.invention_title) - print(" - IFW Found based on Pat No") + print(f"Title: {pat_no_ifw.application_meta_data.invention_title}") + print(f" - IFW Found based on Pat No: {patent_number}") print("\nGet IFW Based on Publication Number ->") -pub_no_ifw = client.get_IFW_metadata(publication_number="*20150157873*") +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(pub_no_ifw.application_meta_data.invention_title) - print(" - IFW Found based on Pub No") + print(f"Title: {pub_no_ifw.application_meta_data.invention_title}") + print(f" - IFW Found based on Pub No: {publication_number}") print("\nGet IFW Based on PCT App Number ->") -pct_app_no_ifw = client.get_IFW_metadata(PCT_app_number="PCTUS0812705") +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(pct_app_no_ifw.application_meta_data.invention_title) - print(" - IFW Found based on PCT App No") + 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("\nGet IFW Based on PCT Pub Number ->") -pct_pub_no_ifw = client.get_IFW_metadata(PCT_pub_number="*2009064413*") +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(pct_pub_no_ifw.application_meta_data.invention_title) - print(" - IFW Found based on PCT Pub No") + 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("Now let's download the Patent Publication Text -->") +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(pgpub_archive) + print(json.dumps(pgpub_archive.to_dict(), indent=2)) download_path = "./download-example" file_path = client.download_archive( printed_metadata=pgpub_archive, destination_path=download_path, overwrite=True ) print(f"-Downloaded document to: {file_path}") -print("Now let's download the Patent Grant Text -->") +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(grant_archive) + print(json.dumps(grant_archive.to_dict(), indent=2)) download_path = "./download-example" file_path = client.download_archive( printed_metadata=grant_archive, destination_path=download_path, overwrite=True diff --git a/examples/patent_data_example.py b/examples/patent_data_example.py index f77b0d3..f3ae121 100644 --- a/examples/patent_data_example.py +++ b/examples/patent_data_example.py @@ -1,24 +1,19 @@ -""" -Example usage of the uspto_api module for patent data +"""Example usage of the uspto_api module for patent data. 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. """ -import json # For pretty printing dict +import json import os from pyUSPTO.clients.patent_data import PatentDataClient -from pyUSPTO.config import USPTOConfig from pyUSPTO.models.patent_data import ApplicationContinuityData # --- Initialization --- -# Choose one method to initialize the client. -# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key. - -# Method 1: Initialize the client with direct API key -print("Method 1: Initialize with direct API key") +# Initialize the client with API key from ENV Var. +print("Initialize with direct API key") api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( @@ -26,31 +21,7 @@ ) client = PatentDataClient(api_key=api_key) -# Method 2: Initialize the client with USPTOConfig (alternative) -# print("\nMethod 2: Initialize with USPTOConfig") -# config_obj = USPTOConfig( -# api_key="YOUR_API_KEY_HERE", # Replace with your actual API key -# # bulk_data_base_url="https://api.uspto.gov/api/v1/datasets", # Optional, uses default if not set -# # patent_data_base_url="https://api.uspto.gov/api/v1/patent", # Optional, uses default if not set -# ) -# client = PatentDataClient(config=config_obj) - -# Method 3: Initialize the client with environment variables (recommended for production) -# print("\nMethod 3: Initialize with environment variables") -# # Ensure USPTO_API_KEY is set in your environment -# # os.environ["USPTO_API_KEY"] = "YOUR_API_KEY_HERE" # Example, should be set outside -# try: -# config_from_env = USPTOConfig.from_env() -# client = PatentDataClient(config=config_from_env) -# except ValueError as e: -# print(f"Error initializing from environment: {e}") -# print("Please ensure USPTO_API_KEY environment variable is set.") -# # Fallback to method 1 for the rest of the script if env var not set for example purposes -# if 'client' not in locals(): # If client wasn't set due to error -# api_key_fallback = "YOUR_API_KEY_HERE_FALLBACK" -# print(f"Falling back to placeholder API key for example: {api_key_fallback}") -# client = PatentDataClient(api_key=api_key_fallback) - +DEST_PATH = "./download-example" print("\nBeginning API requests with configured client:") @@ -104,8 +75,6 @@ if response.count > 0: print("\nGenerating CSV for the current response (first few rows shown):") csv_data = response.to_csv() - # Print first 3 lines of CSV (header + 2 data rows if available) - print("\n".join(csv_data.splitlines()[:3])) # 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) @@ -175,7 +144,7 @@ if documents_bag.documents: document_to_download = documents_bag.documents[0] # Example: first document - print(f"\nFirst document details:") + 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}" @@ -188,11 +157,10 @@ and document_to_download.document_identifier ): print("\nAttempting to download first document...") - download_path = "./download-example" - print(document_to_download.to_dict()) + print(json.dumps(document_to_download.to_dict(), indent=2)) downloaded_path = client.download_document( document_format=document_to_download.document_formats[0], - destination_path=download_path, + destination_path=DEST_PATH, overwrite=True, ) print(f"Downloaded document to: {downloaded_path}") @@ -215,7 +183,7 @@ print("\nDownloading grant XML...") grant_path = client.download_publication( printed_metadata=grant_metadata, - destination_path="./download-example", + destination_path=DEST_PATH, overwrite=True, ) print(f"Downloaded grant XML to: {grant_path}") @@ -228,7 +196,7 @@ pgpub_path = client.download_publication( printed_metadata=pgpub_metadata, file_name="my_pgpub.xml", - destination_path="./download-example", + destination_path=DEST_PATH, overwrite=True, ) print(f"Downloaded pgpub XML to: {pgpub_path}") diff --git a/examples/petition_decisions_example.py b/examples/petition_decisions_example.py index f621eac..b612250 100644 --- a/examples/petition_decisions_example.py +++ b/examples/petition_decisions_example.py @@ -1,5 +1,4 @@ -""" -Example usage of the pyUSPTO module for Final Petition Decisions +"""Example usage of the pyUSPTO module 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 @@ -7,17 +6,15 @@ petitions and their associated documents. """ +import json import os from pyUSPTO.clients import FinalPetitionDecisionsClient -from pyUSPTO.config import USPTOConfig +from pyUSPTO.models.petition_decisions import PetitionDecisionDownloadResponse # --- Initialization --- -# Choose one method to initialize the client. -# For this example, Method 1 is active. Replace "YOUR_API_KEY_HERE" with your actual key. - -# Method 1: Initialize the client with direct API key -print("Method 1: Initialize with direct API key") +# Initialize the client with direct API key +print("Initialize with direct API key") api_key = os.environ.get("USPTO_API_KEY", "YOUR_API_KEY_HERE") if api_key == "YOUR_API_KEY_HERE": raise ValueError( @@ -25,19 +22,7 @@ ) client = FinalPetitionDecisionsClient(api_key=api_key) -# Method 2: Initialize the client with USPTOConfig (alternative) -# print("\nMethod 2: Initialize with USPTOConfig") -# config_obj = USPTOConfig( -# api_key="YOUR_API_KEY_HERE", # Replace with your actual API key -# petition_decisions_base_url="https://api.uspto.gov", # Optional, uses default if not set -# ) -# client = FinalPetitionDecisionsClient(config=config_obj) - -# Method 3: Initialize the client with environment variables (recommended for production) -# print("\nMethod 3: Initialize with environment variables") -# # Ensure USPTO_API_KEY is set in your environment -# config_from_env = USPTOConfig.from_env() -# client = FinalPetitionDecisionsClient(config=config_from_env) +DEST_PATH = "./download-example" print("\nBeginning API requests with configured client:") @@ -56,10 +41,10 @@ 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_number}") + print(f" Technology Center: {decision.technology_center}") - if decision.applicant_name: - print(f" Applicant: {decision.applicant_name}") + if decision.first_applicant_name: + print(f" Applicant: {decision.first_applicant_name}") if decision.patent_number: print(f" Patent Number: {decision.patent_number}") @@ -67,10 +52,7 @@ if decision.inventor_bag: print(f" Inventors ({len(decision.inventor_bag)}):") for inventor in decision.inventor_bag[:3]: # Show first 3 - name_parts = [ - part for part in [inventor.first_name, inventor.last_name] if part - ] - print(f" - {' '.join(name_parts).strip()}") + print(f" - {inventor}") if decision.document_bag: print(f" Documents: {len(decision.document_bag)}") @@ -87,15 +69,14 @@ print("=" * 60) # Search for decisions mentioning specific terms - response = client.search_decisions( - query="decisionTypeCode:GRANT", - limit=3 - ) - print(f"Found {response.count} decisions with GRANT type.") + 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}") + print( + f" - {decision.petition_decision_record_identifier}: {decision.decision_type_code}" + ) except Exception as e: print(f"Error searching with query: {e}") @@ -109,18 +90,13 @@ # 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 + 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 - ) + response = client.search_decisions(technology_center_q="2600", limit=3) print(f"Found {response.count} decisions from Technology Center 2600.") except Exception as e: @@ -135,41 +111,46 @@ # 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 - - print(f"Retrieving decision: {decision_id}") - decision = client.get_decision_by_id(decision_id) - - print(f"\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_number}") - print(f" Group Art Unit: {decision.group_art_unit_number}") - print(f" Examiner: {decision.examiner_name_text}") - - 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" Direction: {doc.document_direction_category}") - if doc.page_total_quantity: - print(f" Pages: {doc.page_total_quantity}") - if doc.download_option_bag: - print(f" Download Options: {len(doc.download_option_bag)}") + 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}") @@ -183,11 +164,13 @@ # 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 + format="json", decision_date_from_q="2023-01-01", limit=5 ) - print(f"Downloaded JSON with {len(response.petition_decision_data)} decision records") + 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...") @@ -195,7 +178,7 @@ format="csv", decision_date_from_q="2023-01-01", limit=10, - destination_path="./downloads" + destination_path=DEST_PATH, ) print(f"Downloaded CSV to: {csv_path}") @@ -211,26 +194,25 @@ page_size = 10 max_pages = 3 # Limit to 3 pages for example - print(f"Paginating through results ({page_size} per page, max {max_pages} pages)...") + print( + f"Paginating through results ({page_size} per page, max {max_pages} pages)..." + ) - page_count = 0 total_decisions = 0 - for page_response in client.paginate_decisions( - limit=page_size, - query="decisionDate:[2023-01-01 TO 2023-12-31]" + for decision in client.paginate_decisions( + limit=page_size, query="decisionDate:[2023-01-01 TO 2023-12-31]" ): - page_count += 1 - decisions_in_page = len(page_response.petition_decision_data_bag) - total_decisions += decisions_in_page + total_decisions += 1 - print(f" Page {page_count}: {decisions_in_page} decisions") + if total_decisions % page_size == 0: + print(f" Retrieved {total_decisions} decisions so far...") - if page_count >= max_pages: + if total_decisions >= (page_size * max_pages): print(f" (Stopping after {max_pages} pages for example)") break - print(f"\nTotal decisions retrieved: {total_decisions} across {page_count} pages") + print(f"\nTotal decisions retrieved: {total_decisions}") except Exception as e: print(f"Error during pagination: {e}") @@ -246,21 +228,24 @@ document_found = False for decision in response.petition_decision_data_bag: - if decision.document_bag: - for doc in decision.document_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(f"Found downloadable document:") + 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(f"\nDownloading document...") + print("\nDownloading document...") file_path = client.download_petition_document( - download_option, - file_path="./downloads" + download_option, destination_path=DEST_PATH ) print(f"Downloaded to: {file_path}") @@ -287,21 +272,23 @@ application_number_q="16*", # Applications starting with 16 decision_date_from_q="2020-01-01", technology_center_q="2600", - limit=10 + limit=10, ) - print(f"Search criteria:") - print(f" - Application numbers starting with '16'") - print(f" - Decision date from 2020-01-01") - print(f" - Technology Center 2600") + 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_number}, " - f"Date: {decision.decision_date}") + 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}") diff --git a/examples/ptab_appeals_example.py b/examples/ptab_appeals_example.py new file mode 100644 index 0000000..5717d54 --- /dev/null +++ b/examples/ptab_appeals_example.py @@ -0,0 +1,259 @@ +"""Example usage of the pyUSPTO module for PTAB Appeals API. + +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. +""" + +import os + +from pyUSPTO import PTABAppealsClient + +# --- Initialization --- +# Initialize the client with direct API key +print("Method 1: Initialize with direct API key") +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." + ) +client = PTABAppealsClient(api_key=api_key) + + +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(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}") + +except Exception as e: + print(f"Error searching by decision type: {e}") + +# ============================================================================= +# 3. Search by Application Number +# ============================================================================= + +print("\n" + "=" * 80) +print("3. Searching for decisions by application number pattern") +print("=" * 80) + +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="decisionDate desc", + limit=3, + ) + + print(f"\nFound {response.count} 'Decision's from TC 2100 (Electronics) 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.appellant_data: + print(f" Application: {decision.appellant_data.application_number_text}") + + if decision.decision_data: + print(f" Decision: {decision.decision_data.decision_type_category}") + print(f" Date: {decision.decision_data.decision_issue_date}") + +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, + ) + + if response.count == 0: + print("No results found for the given search criteria") + else: + print(f"Found {response.count} results") + +except Exception as e: + print(f"Expected error occurred: {type(e).__name__}: {e}") + +print("\n" + "=" * 80) +print("PTAB Appeals API example completed successfully!") +print("=" * 80) diff --git a/examples/ptab_interferences_example.py b/examples/ptab_interferences_example.py new file mode 100644 index 0000000..d09dbbd --- /dev/null +++ b/examples/ptab_interferences_example.py @@ -0,0 +1,287 @@ +"""Example usage of the pyUSPTO module for PTAB Interferences API. + +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. +""" + +import os + +from pyUSPTO import PTABInterferencesClient + +# --- Initialization --- +# Initialize the client with direct API key +print("Initialize with direct API key") +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." + ) +client = PTABInterferencesClient(api_key=api_key) + +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(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 +# ============================================================================= + +print("\n" + "=" * 80) +print("6. Advanced search with multiple criteria") +print("=" * 80) + +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, + ) + + print(f"\nFound {response.count} Decisions between 2020-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: + print(f" Style: {decision.interference_meta_data.interference_style_name}") + + 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}") + + # 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}") + +except Exception as e: + print(f"Error with advanced search: {e}") + +# ============================================================================= +# 7. Direct Query String Example +# ============================================================================= + +print("\n" + "=" * 80) +print("7. Using direct query string for complex searches") +print("=" * 80) + +try: + # Use a direct query string for more complex searches + response = client.search_decisions( + query='documentData.interferenceOutcomeCategory:"Final Decision"', + limit=3, + ) + + print(f"\nFound {response.count} final decisions.") + 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.document_data: + print(f" Outcome: {decision.document_data.interference_outcome_category}") + +except Exception as e: + print(f"Error with direct query: {e}") + +print("\n" + "=" * 80) +print("PTAB Interferences API example completed successfully!") +print("=" * 80) diff --git a/examples/ptab_trials_example.py b/examples/ptab_trials_example.py new file mode 100644 index 0000000..779eeeb --- /dev/null +++ b/examples/ptab_trials_example.py @@ -0,0 +1,194 @@ +"""Example usage of the pyUSPTO module for PTAB Trials API. + +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 +""" + +import os + +from pyUSPTO import PTABTrialsClient + +# --- Initialization --- +# Initialize the client with direct API key +print("Initialize with direct API key") +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." + ) +client = PTABTrialsClient(api_key=api_key) + +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}") diff --git a/pyproject.toml b/pyproject.toml index 514f887..92edad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "pyUSPTO" description = "A Modern Python client for accessing United Stated Patent and Trademark Office (USPTO) Open Data Portal (ODP) APIs." authors = [ - {name = "Andrew Piechocki", email = "apiechocki@dunlapcodding.com"} + { name = "Andrew Piechocki", email = "apiechocki@dunlapcodding.com" }, ] readme = "README.md" requires-python = ">=3.10" @@ -20,17 +20,55 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", + "Environment :: Console", "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Information Technology", + "Intended Audience :: Legal Industry", + "Intended Audience :: Science/Research", + "Intended Audience :: Other Audience", + "Natural Language :: English", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", "Topic :: Software Development :: Libraries", + "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Education", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Office/Business", + "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", ] + keywords = ["uspto", "patent", "odp", "client", "bulk data", "patent data"] dependencies = [ "requests>=2.32.5", + "typing-extensions>=4.15.0; python_version < '3.11'", + "tzdata>=2025.2", ] dynamic = ["version"] +[project.optional-dependencies] +dev = [ + # Testing + "pytest>=9.0.2", + "pytest-cov>=7.0.0", + "pytest-mock>=3.15.1", + # Documentation + "sphinx==8.1.3", + "sphinx_immaterial>=0.13.8", + "sphinx-autodoc-typehints==3.0.1", + "sphinx-copybutton>=0.5.2", + "myst-parser>=4.0.1", + # Type checking + "mypy>=1.19.0", + "types-requests>=2.32.4", + # Code quality and formatting + "ruff>=0.8.0", +] + [project.urls] GitHub = "https://github.com/DunlapCoddingPC/pyUSPTO" issues = "https://github.com/DunlapCoddingPC/pyUSPTO/issues" @@ -42,21 +80,39 @@ Documentation = "https://pyuspto.readthedocs.io/en/latest/" [tool.setuptools] zip-safe = true -[tool.setuptools.packages.find] +[tool.setuptools.packages.find] where = ["src"] [tool.setuptools.package-data] pyUSPTO = ["py.typed"] -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -line_length = 88 -profile = "black" -src_paths = ["src"] -known_first_party = ["pyUSPTO"] +[tool.ruff] +line-length = 88 +target-version = "py310" + +[tool.ruff.lint] +# Enable pycodestyle (E), Pyflakes (F), pydocstyle (D), isort (I), and pyupgrade (UP) +select = ["E", "F", "W", "D", "I", "UP"] +ignore = [ + "D203", # one-blank-line-before-class (conflicts with D211) + "D213", # multi-line-summary-second-line (conflicts with D212) + "E501", # line-too-long (handled by formatter where possible) +] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["D"] # Ignore all pydocstyle rules in tests + + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.isort] +known-first-party = ["pyUSPTO"] +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +line-ending = "auto" [tool.pytest.ini_options] testpaths = ["tests"] @@ -83,7 +139,7 @@ exclude_lines = [ [tool.mypy] mypy_path = "src" -python_version = "3.10" +python_version = "3.11" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true @@ -98,7 +154,7 @@ warn_unreachable = true # Per-module configs for production code [[tool.mypy.overrides]] -module= "pyUSPTO.*" +module = "pyUSPTO.*" disallow_untyped_defs = true # Less strict for tests - allow untyped test functions @@ -113,5 +169,5 @@ warn_unreachable = false # Ignore missing stubs for third-party packages [[tool.mypy.overrides]] -module = ["pytest.*","pytest", "requests.*", "urllib3.*"] +module = ["pytest.*", "pytest", "requests.*", "urllib3.*"] ignore_missing_imports = true diff --git a/requirements-dev.txt b/requirements-dev.txt index 647fd5b..2031df6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,25 +1,143 @@ -# Include all base requirements --r requirements.txt - -# Testing -pytest>=8.4.2 -pytest-cov>=7.0.0 -pytest-mock>=3.15.1 - -# Documentation -sphinx>=8.1.3 -sphinx-rtd-theme>=3.0.2 -sphinx-autodoc-typehints>=3.0.1 -sphinx-copybutton>=0.5.2 -myst-parser>=4.0.1 - -# Type checking -mypy >= 1.18.2 -types-requests >= 2.31.0 - -# Code quality and formatting -flake8>=7.3.0 -black>=25.9.0 -isort>=7.0.0 - -setuptools>=80.9.0 \ No newline at end of file +# +# This file is autogenerated by pip-compile with Python 3.14 +# by the following command: +# +# pip-compile --extra=dev --output-file=requirements-dev.txt --strip-extras pyproject.toml +# +alabaster==1.0.0 + # via sphinx +annotated-types==0.7.0 + # via pydantic +appdirs==1.4.4 + # via sphinx-immaterial +babel==2.17.0 + # via sphinx +certifi==2025.11.12 + # via requests +charset-normalizer==3.4.4 + # via requests +colorama==0.4.6 + # via + # pytest + # sphinx +coverage==7.13.0 + # via pytest-cov +docutils==0.21.2 + # via + # myst-parser + # sphinx +idna==3.11 + # via requests +imagesize==1.4.1 + # via sphinx +iniconfig==2.3.0 + # via pytest +jinja2==3.1.6 + # via + # myst-parser + # sphinx +librt==0.7.3 + # via mypy +markdown-it-py==3.0.0 + # via + # mdit-py-plugins + # myst-parser +markupsafe==3.0.3 + # via + # jinja2 + # sphinx-immaterial +mdit-py-plugins==0.5.0 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +mypy==1.19.0 + # via pyUSPTO (pyproject.toml) +mypy-extensions==1.1.0 + # via mypy +myst-parser==4.0.1 + # via pyUSPTO (pyproject.toml) +packaging==25.0 + # via + # pytest + # sphinx +pathspec==0.12.1 + # via mypy +pluggy==1.6.0 + # via + # pytest + # pytest-cov +pydantic==2.12.5 + # via + # pydantic-extra-types + # sphinx-immaterial +pydantic-core==2.41.5 + # via pydantic +pydantic-extra-types==2.10.6 + # via sphinx-immaterial +pygments==2.19.2 + # via + # pytest + # sphinx +pytest==9.0.2 + # via + # pyUSPTO (pyproject.toml) + # pytest-cov + # pytest-mock +pytest-cov==7.0.0 + # via pyUSPTO (pyproject.toml) +pytest-mock==3.15.1 + # via pyUSPTO (pyproject.toml) +pyyaml==6.0.3 + # via myst-parser +requests==2.32.5 + # via + # pyUSPTO (pyproject.toml) + # sphinx + # sphinx-immaterial +ruff==0.14.8 + # via pyUSPTO (pyproject.toml) +snowballstemmer==3.0.1 + # via sphinx +sphinx==8.1.3 + # via + # myst-parser + # pyUSPTO (pyproject.toml) + # sphinx-autodoc-typehints + # sphinx-copybutton + # sphinx-immaterial +sphinx-autodoc-typehints==3.0.1 + # via pyUSPTO (pyproject.toml) +sphinx-copybutton==0.5.2 + # via pyUSPTO (pyproject.toml) +sphinx-immaterial==0.13.8 + # via pyUSPTO (pyproject.toml) +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +types-requests==2.32.4.20250913 + # via pyUSPTO (pyproject.toml) +typing-extensions==4.15.0 + # via + # mypy + # pydantic + # pydantic-core + # pydantic-extra-types + # sphinx-immaterial + # typing-inspection +typing-inspection==0.4.2 + # via pydantic +tzdata==2025.2 + # via pyUSPTO (pyproject.toml) +urllib3==2.6.1 + # via + # requests + # types-requests diff --git a/requirements.txt b/requirements.txt index 479e353..9c85d9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,16 @@ -# Core dependencies -requests>=2.32.5 -urllib3>=2.5.0 -tzdata>=2025.2 - -# Development dependencies -pytest>=8.4.2 -pytest-cov>=7.0.0 +# +# This file is autogenerated by pip-compile with Python 3.14 +# by the following command: +# +# pip-compile --output-file=requirements.txt --strip-extras pyproject.toml +# +certifi==2025.11.12 + # via requests +charset-normalizer==3.4.4 + # via requests +idna==3.11 + # via requests +requests==2.32.5 + # via pyUSPTO (pyproject.toml) +urllib3==2.6.1 + # via requests diff --git a/src/pyUSPTO/__init__.py b/src/pyUSPTO/__init__.py index 0bf8ed6..d63de7c 100644 --- a/src/pyUSPTO/__init__.py +++ b/src/pyUSPTO/__init__.py @@ -1,5 +1,4 @@ -""" -USPTO API Client - A Python client library for interacting with the USPTO APIs. +"""USPTO API Client - A Python client library for interacting with the USPTO APIs. This package provides clients for interacting with the USPTO Open Data Portal APIs. """ @@ -15,6 +14,9 @@ from pyUSPTO.clients.bulk_data import BulkDataClient from pyUSPTO.clients.patent_data import PatentDataClient from pyUSPTO.clients.petition_decisions import FinalPetitionDecisionsClient +from pyUSPTO.clients.ptab_appeals import PTABAppealsClient +from pyUSPTO.clients.ptab_interferences import PTABInterferencesClient +from pyUSPTO.clients.ptab_trials import PTABTrialsClient from pyUSPTO.config import USPTOConfig from pyUSPTO.exceptions import ( USPTOApiAuthError, @@ -37,6 +39,14 @@ PetitionDecisionDocument, PetitionDecisionResponse, ) +from pyUSPTO.models.ptab import ( + PTABAppealDecision, + PTABAppealResponse, + PTABInterferenceDecision, + PTABInterferenceResponse, + PTABTrialProceeding, + PTABTrialProceedingResponse, +) from pyUSPTO.warnings import ( USPTOBooleanParseWarning, USPTODataMismatchWarning, @@ -76,4 +86,14 @@ "PetitionDecisionResponse", "PetitionDecision", "PetitionDecisionDocument", + # PTAB API + "PTABTrialsClient", + "PTABAppealsClient", + "PTABInterferencesClient", + "PTABTrialProceeding", + "PTABTrialProceedingResponse", + "PTABAppealDecision", + "PTABAppealResponse", + "PTABInterferenceDecision", + "PTABInterferenceResponse", ] diff --git a/src/pyUSPTO/clients/__init__.py b/src/pyUSPTO/clients/__init__.py index 03e37f2..2aab87b 100644 --- a/src/pyUSPTO/clients/__init__.py +++ b/src/pyUSPTO/clients/__init__.py @@ -1,5 +1,4 @@ -""" -clients - USPTO API client implementations +"""clients - USPTO API client implementations. This package provides client implementations for USPTO APIs. """ @@ -7,9 +6,15 @@ from pyUSPTO.clients.bulk_data import BulkDataClient from pyUSPTO.clients.patent_data import PatentDataClient from pyUSPTO.clients.petition_decisions import FinalPetitionDecisionsClient +from pyUSPTO.clients.ptab_appeals import PTABAppealsClient +from pyUSPTO.clients.ptab_interferences import PTABInterferencesClient +from pyUSPTO.clients.ptab_trials import PTABTrialsClient __all__ = [ "BulkDataClient", "PatentDataClient", "FinalPetitionDecisionsClient", + "PTABTrialsClient", + "PTABAppealsClient", + "PTABInterferencesClient", ] diff --git a/src/pyUSPTO/clients/base.py b/src/pyUSPTO/clients/base.py index e89bfd0..364405e 100644 --- a/src/pyUSPTO/clients/base.py +++ b/src/pyUSPTO/clients/base.py @@ -1,24 +1,18 @@ -""" -base - Base client class for USPTO API clients +"""base - Base client class for USPTO API clients. This module provides a base client class with common functionality for all USPTO API clients. """ import re +from collections.abc import Generator from pathlib import Path from typing import ( Any, - Dict, - Generator, Generic, - Optional, Protocol, - Type, TypeVar, - Union, runtime_checkable, ) -from urllib.parse import urlparse import requests from requests.adapters import HTTPAdapter @@ -27,12 +21,10 @@ from pyUSPTO.config import USPTOConfig from pyUSPTO.exceptions import ( APIErrorArgs, - USPTOApiError, USPTOConnectionError, USPTOTimeout, get_api_exception, ) -from pyUSPTO.http_config import HTTPConfig @runtime_checkable @@ -40,7 +32,7 @@ class FromDictProtocol(Protocol): """Protocol for classes that can be created from a dictionary.""" @classmethod - def from_dict(cls, data: Dict[str, Any], include_raw_data: bool = False) -> Any: + def from_dict(cls, data: dict[str, Any], include_raw_data: bool = False) -> Any: """Create an object from a dictionary.""" ... @@ -54,9 +46,9 @@ class BaseUSPTOClient(Generic[T]): def __init__( self, - api_key: Optional[str] = None, + api_key: str | None = None, base_url: str = "", - config: Optional[USPTOConfig] = None, + config: USPTOConfig | None = None, ): """Initialize the BaseUSPTOClient. @@ -123,15 +115,14 @@ def _make_request( self, method: str, endpoint: str, - params: Optional[Dict[str, Any]] = None, - json_data: Optional[Dict[str, Any]] = None, + params: dict[str, Any] | None = None, + json_data: dict[str, Any] | None = None, stream: bool = False, - response_class: Optional[Type[T]] = None, - custom_url: Optional[str] = None, - custom_base_url: Optional[str] = None, - ) -> Dict[str, Any] | T | requests.Response: - """ - Make an HTTP request to the USPTO API. + response_class: type[T] | None = None, + custom_url: str | None = None, + custom_base_url: str | None = None, + ) -> dict[str, Any] | T | requests.Response: + """Make an HTTP request to the USPTO API. Args: method: HTTP method (GET, POST, etc.) @@ -140,6 +131,7 @@ def _make_request( json_data: Optional JSON body for POST requests stream: Whether to stream the response response_class: Class to use for parsing the response + custom_url: Optional full custom URL to use (overrides endpoint and base URL) custom_base_url: Optional custom base URL to use instead of self.base_url Returns: @@ -188,7 +180,7 @@ def _make_request( return parsed_response # Return the raw JSON for other requests - json_response: Dict[str, Any] = response.json() + json_response: dict[str, Any] = response.json() return json_response except requests.exceptions.HTTPError as http_err: @@ -239,8 +231,7 @@ def _make_request( def paginate_results( self, method_name: str, response_container_attr: str, **kwargs: Any ) -> Generator[Any, None, None]: - """ - Paginate through all results of a method. + """Paginate through all results of a method. Args: method_name: Name of the method to call @@ -264,8 +255,7 @@ def paginate_results( break container = getattr(response, response_container_attr) - for item in container: - yield item + yield from container if response.count < limit: break @@ -274,8 +264,8 @@ def paginate_results( @staticmethod def _extract_filename_from_content_disposition( - content_disposition: Optional[str], - ) -> Optional[str]: + content_disposition: str | None, + ) -> str | None: """Extract filename from Content-Disposition header. Supports both RFC 2231 (filename*) and simple filename formats. @@ -314,7 +304,7 @@ def _extract_filename_from_content_disposition( return None @staticmethod - def _get_extension_from_mime_type(mime_type: Optional[str]) -> Optional[str]: + def _get_extension_from_mime_type(mime_type: str | None) -> str | None: """Map MIME type to file extension. Maps common USPTO file formats to their appropriate extensions. @@ -375,8 +365,6 @@ def _save_response_to_file( FileExistsError: If file exists and overwrite=False ValueError: If file_path is a directory but no filename can be determined """ - from pathlib import Path - path = Path(file_path) # If path is a directory, try to extract filename from Content-Disposition @@ -447,4 +435,9 @@ def _download_file(self, url: str, file_path: str, overwrite: bool = False) -> s @property def api_key(self) -> str: + """Return a masked representation of the API key for security purposes. + + Returns: + str: A string of asterisks masking the actual API key. + """ return "********" diff --git a/src/pyUSPTO/clients/bulk_data.py b/src/pyUSPTO/clients/bulk_data.py index dc56a62..3817d30 100644 --- a/src/pyUSPTO/clients/bulk_data.py +++ b/src/pyUSPTO/clients/bulk_data.py @@ -1,12 +1,12 @@ -""" -clients.bulk_data - Client for USPTO bulk data API +"""clients.bulk_data - Client for USPTO bulk data API. This module provides a client for interacting with the USPTO Open Data Portal (ODP) Bulk Data API. It allows you to search for and download bulk data products. """ import os -from typing import Any, Dict, Iterator, List, Optional +from collections.abc import Iterator +from typing import Any from urllib.parse import urlparse from pyUSPTO.clients.base import BaseUSPTOClient @@ -28,12 +28,11 @@ class BulkDataClient(BaseUSPTOClient[BulkDataResponse]): def __init__( self, - api_key: Optional[str] = None, - base_url: Optional[str] = None, - config: Optional[USPTOConfig] = None, + api_key: str | None = None, + base_url: str | None = None, + config: USPTOConfig | None = None, ): - """ - Initialize the BulkDataClient. + """Initialize the BulkDataClient. Args: api_key: Optional API key for authentication @@ -51,9 +50,8 @@ def __init__( super().__init__(api_key=api_key, base_url=base_url, config=self.config) - def get_products(self, params: Optional[Dict[str, Any]] = None) -> BulkDataResponse: - """ - Get a list of bulk data products. + def get_products(self, params: dict[str, Any] | None = None) -> BulkDataResponse: + """Get a list of bulk data products. This method is deprecated. Use search_products instead. @@ -76,15 +74,14 @@ def get_products(self, params: Optional[Dict[str, Any]] = None) -> BulkDataRespo def get_product_by_id( self, product_id: str, - file_data_from_date: Optional[str] = None, - file_data_to_date: Optional[str] = None, - offset: Optional[int] = None, - limit: Optional[int] = None, - include_files: Optional[bool] = None, - latest: Optional[bool] = None, + file_data_from_date: str | None = None, + file_data_to_date: str | None = None, + offset: int | None = None, + limit: int | None = None, + include_files: bool | None = None, + latest: bool | None = None, ) -> BulkDataProduct: - """ - Get a specific bulk data product by ID. + """Get a specific bulk data product by ID. Args: product_id: The product identifier @@ -146,8 +143,7 @@ def get_product_by_id( raise TypeError(f"Expected dict, got {type(data)}") def download_file(self, file_data: FileData, destination: str) -> str: - """ - Download a file from the API. + """Download a file from the API. Args: file_data: FileData object containing file information @@ -202,8 +198,7 @@ def download_file(self, file_data: FileData, destination: str) -> str: return file_path def paginate_products(self, **kwargs: Any) -> Iterator[BulkDataProduct]: - """ - Paginate through all products matching the search criteria. + """Paginate through all products matching the search criteria. Args: **kwargs: Keyword arguments to pass to search_products @@ -219,24 +214,23 @@ def paginate_products(self, **kwargs: Any) -> Iterator[BulkDataProduct]: def search_products( self, - query: Optional[str] = None, - product_title: Optional[str] = None, - product_description: Optional[str] = None, - product_short_name: Optional[str] = None, - from_date: Optional[str] = None, - to_date: Optional[str] = None, - categories: Optional[List[str]] = None, - labels: Optional[List[str]] = None, - datasets: Optional[List[str]] = None, - file_types: Optional[List[str]] = None, - offset: Optional[int] = None, - limit: Optional[int] = None, - include_files: Optional[bool] = None, - latest: Optional[bool] = None, - facets: Optional[bool] = None, + query: str | None = None, + product_title: str | None = None, + product_description: str | None = None, + product_short_name: str | None = None, + from_date: str | None = None, + to_date: str | None = None, + categories: list[str] | None = None, + labels: list[str] | None = None, + datasets: list[str] | None = None, + file_types: list[str] | None = None, + offset: int | None = None, + limit: int | None = None, + include_files: bool | None = None, + latest: bool | None = None, + facets: bool | None = None, ) -> BulkDataResponse: - """ - Search for products with various filters. + """Search for products with various filters. Args: query: Search text diff --git a/src/pyUSPTO/clients/patent_data.py b/src/pyUSPTO/clients/patent_data.py index c988e58..3389421 100644 --- a/src/pyUSPTO/clients/patent_data.py +++ b/src/pyUSPTO/clients/patent_data.py @@ -1,14 +1,13 @@ -""" -clients.patent_data - Client for USPTO patent data API +"""clients.patent_data - Client for USPTO patent data API. This module provides a client for interacting with the USPTO Patent Data API. It allows you to search for and retrieve patent application data. """ import warnings +from collections.abc import Iterator from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional -from urllib.parse import urljoin, urlparse +from typing import Any from pyUSPTO.clients.base import BaseUSPTOClient from pyUSPTO.config import USPTOConfig @@ -16,19 +15,16 @@ ApplicationContinuityData, ApplicationMetaData, Assignment, - ChildContinuity, DocumentBag, DocumentFormat, EventData, ForeignPriority, - ParentContinuity, PatentDataResponse, PatentFileWrapper, PatentTermAdjustmentData, PrintedMetaData, PrintedPublication, RecordAttorney, - StatusCodeCollection, StatusCodeSearchResponse, ) from pyUSPTO.warnings import USPTODataMismatchWarning @@ -56,10 +52,17 @@ class PatentDataClient(BaseUSPTOClient[PatentDataResponse]): def __init__( self, - api_key: Optional[str] = None, - base_url: Optional[str] = None, - config: Optional[USPTOConfig] = None, + api_key: str | None = None, + base_url: str | None = None, + config: USPTOConfig | None = None, ): + """Initialize the PatentDataClient. + + Args: + api_key: USPTO API key. If not provided, uses key from config or environment. + base_url: Base URL for the USPTO Patent Data API. Defaults to https://api.uspto.gov. + config: USPTOConfig instance. If not provided, creates one with the given api_key. + """ self.config = config or USPTOConfig(api_key=api_key) api_key_to_use = api_key or self.config.api_key effective_base_url = ( @@ -181,8 +184,7 @@ def sanitize_application_number(self, input_number: str) -> str: if len(cleaned) != 8: raise ValueError( - f"Invalid application number format: {input_number}. " - "Expected 8 digits." + f"Invalid application number format: {input_number}. Expected 8 digits." ) return cleaned @@ -190,9 +192,9 @@ def sanitize_application_number(self, input_number: str) -> str: def _get_wrapper_from_response( self, response_data: PatentDataResponse, - application_number_for_validation: Optional[str] = None, - ) -> Optional[PatentFileWrapper]: - """Helper to extract a single PatentFileWrapper, optionally validating the app number.""" + application_number_for_validation: str | None = None, + ) -> PatentFileWrapper | None: + """Extract a single PatentFileWrapper, optionally validating the app number.""" if not response_data or not response_data.patent_file_wrapper_data_bag: return None @@ -214,31 +216,31 @@ def _get_wrapper_from_response( def search_applications( self, - query: Optional[str] = None, - sort: Optional[str] = None, - offset: Optional[int] = 0, - limit: Optional[int] = 25, - facets: Optional[str] = None, - fields: Optional[str] = None, - filters: Optional[str] = None, - range_filters: Optional[str] = None, - post_body: Optional[Dict[str, Any]] = None, - application_number_q: Optional[str] = None, - patent_number_q: Optional[str] = None, - inventor_name_q: Optional[str] = None, - applicant_name_q: Optional[str] = None, - assignee_name_q: Optional[str] = None, - filing_date_from_q: Optional[str] = None, - filing_date_to_q: Optional[str] = None, - grant_date_from_q: Optional[str] = None, - grant_date_to_q: Optional[str] = None, - classification_q: Optional[str] = None, - earliestPublicationNumber_q: Optional[str] = None, - pctPublicationNumber_q: Optional[str] = None, - additional_query_params: Optional[Dict[str, Any]] = None, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, + application_number_q: str | None = None, + patent_number_q: str | None = None, + inventor_name_q: str | None = None, + applicant_name_q: str | None = None, + assignee_name_q: str | None = None, + filing_date_from_q: str | None = None, + filing_date_to_q: str | None = None, + grant_date_from_q: str | None = None, + grant_date_to_q: str | None = None, + classification_q: str | None = None, + earliestPublicationNumber_q: str | None = None, + pctPublicationNumber_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, ) -> PatentDataResponse: - """ - Searches for patent applications. + """Search for patent applications. + Can perform a GET request based on OpenAPI query parameters or a POST request if post_body is specified. """ endpoint = self.ENDPOINTS["search_applications"] @@ -252,7 +254,7 @@ def search_applications( response_class=PatentDataResponse, ) else: - params: Dict[str, Any] = {} + params: dict[str, Any] = {} final_q = query if final_q is None: @@ -344,28 +346,28 @@ def search_applications( def get_search_results( self, - query: Optional[str] = None, - sort: Optional[str] = None, - offset: Optional[int] = 0, - limit: Optional[int] = 25, - fields_param: Optional[str] = None, - filters_param: Optional[str] = None, - range_filters_param: Optional[str] = None, - post_body: Optional[Dict[str, Any]] = None, - application_number_q: Optional[str] = None, - patent_number_q: Optional[str] = None, - inventor_name_q: Optional[str] = None, - applicant_name_q: Optional[str] = None, - assignee_name_q: Optional[str] = None, - filing_date_from_q: Optional[str] = None, - filing_date_to_q: Optional[str] = None, - grant_date_from_q: Optional[str] = None, - grant_date_to_q: Optional[str] = None, - classification_q: Optional[str] = None, - additional_query_params: Optional[Dict[str, Any]] = None, - ) -> PatentDataResponse: - """ - Fetches a dataset of patent applications based on search criteria, always requesting JSON format. + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + fields_param: str | None = None, + filters_param: str | None = None, + range_filters_param: str | None = None, + post_body: dict[str, Any] | None = None, + application_number_q: str | None = None, + patent_number_q: str | None = None, + inventor_name_q: str | None = None, + applicant_name_q: str | None = None, + assignee_name_q: str | None = None, + filing_date_from_q: str | None = None, + filing_date_to_q: str | None = None, + grant_date_from_q: str | None = None, + grant_date_to_q: str | None = None, + classification_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, + ) -> list[ApplicationMetaData]: + """Fetch a dataset of patent applications based on search criteria, always requesting JSON format. + For GET, parameters align with OpenAPI for /api/v1/patent/applications/search/download. For POST, post_body should conform to PatentDownloadRequest schema. """ @@ -380,10 +382,9 @@ def get_search_results( endpoint=endpoint, json_data=post_body, params=additional_query_params, - response_class=PatentDataResponse, ) else: - params: Dict[str, Any] = {} + params: dict[str, Any] = {} final_q = query if final_q is None: @@ -462,15 +463,18 @@ def get_search_results( method="GET", endpoint=endpoint, params=params, - response_class=PatentDataResponse, ) - assert isinstance(result, PatentDataResponse) - return result + assert isinstance(result, dict) + amd_list = [ + ApplicationMetaData.from_dict(item["applicationMetaData"]) + for item in result["patentdata"] + ] + return amd_list def get_application_by_number( self, application_number: str - ) -> Optional[PatentFileWrapper]: - """Retrieves the full details for a specific patent application by its number. + ) -> PatentFileWrapper | None: + """Retrieve the full details for a specific patent application by its number. This method fetches comprehensive information for a single patent application identified by its unique application number. @@ -503,8 +507,8 @@ def get_application_by_number( def get_application_metadata( self, application_number: str - ) -> Optional[ApplicationMetaData]: - """Retrieves key metadata for a specific patent application. + ) -> ApplicationMetaData | None: + """Retrieve key metadata for a specific patent application. This method fetches the `ApplicationMetaData` component from the full patent file wrapper. The metadata includes a wide range of information @@ -535,8 +539,8 @@ def get_application_metadata( def get_application_adjustment( self, application_number: str - ) -> Optional[PatentTermAdjustmentData]: - """Retrieves patent term adjustment (PTA) data for a specific application. + ) -> PatentTermAdjustmentData | None: + """Retrieve patent term adjustment (PTA) data for a specific application. This method fetches the `PatentTermAdjustmentData` component from the full patent file wrapper. This data includes details on various delay @@ -566,8 +570,8 @@ def get_application_adjustment( def get_application_assignment( self, application_number: str - ) -> Optional[List[Assignment]]: - """Retrieves a list of patent assignments for a specific application. + ) -> list[Assignment] | None: + """Retrieve a list of patent assignments for a specific application. This method fetches the `assignment_bag` from the patent file wrapper, which contains a list of `Assignment` objects. Each `Assignment` object @@ -598,8 +602,8 @@ def get_application_assignment( def get_application_attorney( self, application_number: str - ) -> Optional[RecordAttorney]: - """Retrieves data for the attorney(s) of record for a specific application. + ) -> RecordAttorney | None: + """Retrieve data for the attorney(s) of record for a specific application. This method fetches the `RecordAttorney` object associated with the patent application. This object contains details about the attorney(s) @@ -628,8 +632,8 @@ def get_application_attorney( def get_application_continuity( self, application_number: str - ) -> Optional[ApplicationContinuityData]: - """Retrieves continuity data (parent/child applications) for a specific application. + ) -> ApplicationContinuityData | None: + """Retrieve continuity data (parent/child applications) for a specific application. This method fetches the lineage of the specified application, returning an `ApplicationContinuityData` object. This object consolidates lists of @@ -661,8 +665,8 @@ def get_application_continuity( def get_application_foreign_priority( self, application_number: str - ) -> Optional[List[ForeignPriority]]: - """Retrieves a list of foreign priority claims for a specific application. + ) -> list[ForeignPriority] | None: + """Retrieve a list of foreign priority claims for a specific application. This method fetches the `foreign_priority_bag` from the patent file wrapper. This bag contains a list of `ForeignPriority` objects, each @@ -693,8 +697,8 @@ def get_application_foreign_priority( def get_application_transactions( self, application_number: str - ) -> Optional[List[EventData]]: - """Retrieves the transaction history (events) for a specific application. + ) -> list[EventData] | None: + """Retrieve the transaction history (events) for a specific application. This method fetches the `event_data_bag` from the patent file wrapper. This bag contains a list of `EventData` objects, each representing a @@ -726,11 +730,11 @@ def get_application_transactions( def get_application_documents( self, application_number: str, - document_codes: Optional[List[str]] = None, - official_date_from: Optional[str] = None, - official_date_to: Optional[str] = None, + document_codes: list[str] | None = None, + official_date_from: str | None = None, + official_date_to: str | None = None, ) -> DocumentBag: - """Retrieves metadata for documents associated with a specific application. + """Retrieve metadata for documents associated with a specific application. This method fetches a collection of document metadata related to the given patent application. The result is a `DocumentBag` object, which is an @@ -778,8 +782,8 @@ def get_application_documents( def get_application_associated_documents( self, application_number: str - ) -> Optional[PrintedPublication]: - """Retrieves metadata for Pre-Grant Publication and Grant documents. + ) -> PrintedPublication | None: + """Retrieve metadata for Pre-Grant Publication and Grant documents. This method fetches metadata specifically for published documents associated with the patent application, such as Pre-Grant Publications (PGPUBs) @@ -814,7 +818,7 @@ def get_application_associated_documents( return PrintedPublication.from_wrapper(wrapper) if wrapper else None def paginate_applications(self, **kwargs: Any) -> Iterator[PatentFileWrapper]: - """Provides an iterator to easily paginate through patent application search results. + """Provide an iterator to easily paginate through patent application search results. This method simplifies the process of fetching all patent applications that match a given search query by automatically handling pagination. @@ -856,9 +860,9 @@ def paginate_applications(self, **kwargs: Any) -> Iterator[PatentFileWrapper]: ) def get_status_codes( - self, params: Optional[Dict[str, Any]] = None + self, params: dict[str, Any] | None = None ) -> StatusCodeSearchResponse: - """Retrieves USPTO patent application status codes and their descriptions. + """Retrieve USPTO patent application status codes and their descriptions. This method fetches a list of defined USPTO patent application status codes (e.g., codes for "Pending," "Abandoned," "Issued") using a GET request. @@ -884,9 +888,9 @@ def get_status_codes( return StatusCodeSearchResponse.from_dict(result_dict) def search_status_codes( - self, search_request: Dict[str, Any] + self, search_request: dict[str, Any] ) -> StatusCodeSearchResponse: - """Searches USPTO patent application status codes using POST criteria. + """Search USPTO patent application status codes using POST criteria. Performs targeted searches for USPTO patent application status codes (e.g., for "Pending," "Abandoned," "Issued") by sending a POST request @@ -915,12 +919,12 @@ def search_status_codes( def download_document( self, document_format: DocumentFormat, - file_name: Optional[str] = None, - destination_path: Optional[str] = None, + file_name: str | None = None, + destination_path: str | None = None, overwrite: bool = False, stream: bool = True, ) -> str: - """Downloads a document in the specified format. + """Download a document in the specified format. Args: document_format: DocumentFormat object containing download URL and metadata @@ -971,13 +975,13 @@ def download_document( def get_IFW_metadata( self, - application_number: Optional[str] = None, - publication_number: Optional[str] = None, - patent_number: Optional[str] = None, - PCT_app_number: Optional[str] = None, - PCT_pub_number: Optional[str] = None, - ) -> Optional[PatentFileWrapper]: - """Retrieves complete patent file wrapper data using common identifiers. + application_number: str | None = None, + publication_number: str | None = None, + patent_number: str | None = None, + PCT_app_number: str | None = None, + PCT_pub_number: str | None = None, + ) -> PatentFileWrapper | None: + """Retrieve complete patent file wrapper data using common identifiers. This utility fetches the `PatentFileWrapper`, which contains comprehensive IFW metadata, application details, and more. Provide only one @@ -1001,7 +1005,6 @@ def get_IFW_metadata( comprehensive data if found using one of the identifiers, otherwise None. """ - if application_number: return self.get_application_by_number(application_number=application_number) if patent_number: @@ -1027,11 +1030,13 @@ def get_IFW_metadata( def download_archive( self, printed_metadata: PrintedMetaData, - file_name: Optional[str] = None, - destination_path: Optional[str] = None, + file_name: str | None = None, + destination_path: str | None = None, overwrite: bool = False, ) -> str: - """Downloads Printed Metadata (XML data). These are XML files of the patent as printed. + """Download Printed Metadata (XML data). + + These are XML files of the patent as printed. Note: See also `download_publication()` for a clearer method name with identical functionality. @@ -1083,14 +1088,16 @@ def download_archive( # Download the Printed Metadata return self._download_file( - url=printed_metadata.file_location_uri, file_path=final_file_path.as_posix() + url=printed_metadata.file_location_uri, + file_path=final_file_path.as_posix(), + overwrite=overwrite, ) def download_publication( self, printed_metadata: PrintedMetaData, - file_name: Optional[str] = None, - destination_path: Optional[str] = None, + file_name: str | None = None, + destination_path: str | None = None, overwrite: bool = False, ) -> str: """Download a publication XML file (grant or pre-grant publication). diff --git a/src/pyUSPTO/clients/petition_decisions.py b/src/pyUSPTO/clients/petition_decisions.py index 2dd0ac1..a9b8ca2 100644 --- a/src/pyUSPTO/clients/petition_decisions.py +++ b/src/pyUSPTO/clients/petition_decisions.py @@ -1,5 +1,4 @@ -""" -clients.petition_decisions - Client for USPTO Final Petition Decisions API +"""clients.petition_decisions - Client for USPTO Final Petition Decisions API. This module provides a client for interacting with the USPTO Final Petition Decisions API. It allows you to search for and retrieve final agency petition @@ -7,8 +6,9 @@ """ import warnings +from collections.abc import Iterator from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Union +from typing import Any import requests @@ -41,9 +41,9 @@ class FinalPetitionDecisionsClient(BaseUSPTOClient[PetitionDecisionResponse]): def __init__( self, - api_key: Optional[str] = None, - base_url: Optional[str] = None, - config: Optional[USPTOConfig] = None, + api_key: str | None = None, + base_url: str | None = None, + config: USPTOConfig | None = None, ): """Initialize the FinalPetitionDecisionsClient. @@ -66,9 +66,9 @@ def __init__( def _get_decision_from_response( self, response_data: PetitionDecisionResponse, - petition_decision_record_identifier_for_validation: Optional[str] = None, - ) -> Optional[PetitionDecision]: - """Helper to extract a single PetitionDecision from response. + petition_decision_record_identifier_for_validation: str | None = None, + ) -> PetitionDecision | None: + """Extract a single PetitionDecision from the response. Args: response_data: The API response containing petition decisions. @@ -99,31 +99,31 @@ def _get_decision_from_response( def search_decisions( self, - query: Optional[str] = None, - sort: Optional[str] = None, - offset: Optional[int] = 0, - limit: Optional[int] = 25, - facets: Optional[str] = None, - fields: Optional[str] = None, - filters: Optional[str] = None, - range_filters: Optional[str] = None, - post_body: Optional[Dict[str, Any]] = None, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, # Convenience query parameters - application_number_q: Optional[str] = None, - patent_number_q: Optional[str] = None, - inventor_name_q: Optional[str] = None, - applicant_name_q: Optional[str] = None, - invention_title_q: Optional[str] = None, - decision_type_code_q: Optional[str] = None, - decision_date_from_q: Optional[str] = None, - decision_date_to_q: Optional[str] = None, - petition_mail_date_from_q: Optional[str] = None, - petition_mail_date_to_q: Optional[str] = None, - technology_center_q: Optional[str] = None, - final_deciding_office_name_q: Optional[str] = None, - additional_query_params: Optional[Dict[str, Any]] = None, + application_number_q: str | None = None, + patent_number_q: str | None = None, + inventor_name_q: str | None = None, + applicant_name_q: str | None = None, + invention_title_q: str | None = None, + decision_type_code_q: str | None = None, + decision_date_from_q: str | None = None, + decision_date_to_q: str | None = None, + petition_mail_date_from_q: str | None = None, + petition_mail_date_to_q: str | None = None, + technology_center_q: str | None = None, + final_deciding_office_name_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, ) -> PetitionDecisionResponse: - """Searches for final petition decisions. + """Return final petition decisions matching the given criteria. This method can perform either a GET request using query parameters or a POST request if post_body is specified. When using GET, you can provide either a @@ -186,7 +186,7 @@ def search_decisions( ) else: # GET request path - params: Dict[str, Any] = {} + params: dict[str, Any] = {} final_q = query # Build query from convenience parameters @@ -268,9 +268,9 @@ def search_decisions( def get_decision_by_id( self, petition_decision_record_identifier: str, - include_documents: Optional[bool] = None, - ) -> Optional[PetitionDecision]: - """Retrieves a specific petition decision by its record identifier. + include_documents: bool | None = None, + ) -> PetitionDecision | None: + """Retrieve a specific petition decision by its record identifier. Args: petition_decision_record_identifier: The unique identifier for the petition @@ -316,27 +316,27 @@ def get_decision_by_id( def download_decisions( self, format: str = "json", - query: Optional[str] = None, - sort: Optional[str] = None, - offset: Optional[int] = None, - limit: Optional[int] = None, - fields: Optional[str] = None, - filters: Optional[str] = None, - range_filters: Optional[str] = None, + query: str | None = None, + sort: str | None = None, + offset: int | None = None, + limit: int | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, # Convenience query parameters - application_number_q: Optional[str] = None, - patent_number_q: Optional[str] = None, - inventor_name_q: Optional[str] = None, - applicant_name_q: Optional[str] = None, - decision_date_from_q: Optional[str] = None, - decision_date_to_q: Optional[str] = None, - additional_query_params: Optional[Dict[str, Any]] = None, + application_number_q: str | None = None, + patent_number_q: str | None = None, + inventor_name_q: str | None = None, + applicant_name_q: str | None = None, + decision_date_from_q: str | None = None, + decision_date_to_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, # File save options (for CSV format) - file_name: Optional[str] = None, - destination_path: Optional[str] = None, + file_name: str | None = None, + destination_path: str | None = None, overwrite: bool = False, - ) -> Union[PetitionDecisionDownloadResponse, requests.Response, str]: - """Downloads petition decisions data in the specified format. + ) -> PetitionDecisionDownloadResponse | requests.Response | str: + """Download petition decisions data in the specified format. This endpoint is designed for bulk downloads of petition decisions data. It supports JSON and CSV formats. @@ -396,7 +396,7 @@ def download_decisions( """ endpoint = self.ENDPOINTS["download_decisions"] - params: Dict[str, Any] = {"format": format} + params: dict[str, Any] = {"format": format} final_q = query # Build query from convenience parameters @@ -479,7 +479,7 @@ def download_decisions( return result def paginate_decisions(self, **kwargs: Any) -> Iterator[PetitionDecision]: - """Provides an iterator to paginate through petition decision search results. + """Provide an iterator to paginate through petition decision search results. This method simplifies fetching all petition decisions matching a search query by automatically handling pagination. It internally calls the search_decisions @@ -529,11 +529,11 @@ def paginate_decisions(self, **kwargs: Any) -> Iterator[PetitionDecision]: def download_petition_document( self, download_option: DocumentDownloadOption, - file_name: Optional[str] = None, - destination_path: Optional[str] = None, + file_name: str | None = None, + destination_path: str | None = None, overwrite: bool = False, ) -> str: - """Downloads a petition decision document in the specified format. + """Download a petition decision document in the specified format. Args: download_option: DocumentDownloadOption object containing the download diff --git a/src/pyUSPTO/clients/ptab_appeals.py b/src/pyUSPTO/clients/ptab_appeals.py new file mode 100644 index 0000000..7ef848f --- /dev/null +++ b/src/pyUSPTO/clients/ptab_appeals.py @@ -0,0 +1,253 @@ +"""clients.ptab_appeals - Client for USPTO PTAB Appeals API. + +This module provides a client for interacting with the USPTO PTAB (Patent Trial +and Appeal Board) Appeals API. It allows you to search for ex parte appeal decisions. +""" + +from collections.abc import Iterator +from typing import Any + +from pyUSPTO.clients.base import BaseUSPTOClient +from pyUSPTO.config import USPTOConfig +from pyUSPTO.models.ptab import PTABAppealDecision, PTABAppealResponse + + +class PTABAppealsClient(BaseUSPTOClient[PTABAppealResponse]): + """Client for interacting with the USPTO PTAB Appeals API. + + This client provides methods to search for ex parte appeal decisions from the + Patent Trial and Appeal Board. + + Appeals data includes decisions on patent application appeals from the examiner + to the PTAB. + """ + + ENDPOINTS = { + "search_decisions": "api/v1/patent/appeals/decisions/search", + } + + def __init__( + self, + api_key: str | None = None, + base_url: str | None = None, + config: USPTOConfig | None = None, + ): + """Initialize the PTABAppealsClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.ptab_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + ) + + def search_decisions( + self, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, + # Convenience query parameters + appeal_number_q: str | None = None, + application_number_text_q: str | None = None, + appellant_name_q: str | None = None, + requestor_name_q: str | None = None, + decision_type_category_q: str | None = None, + decision_date_from_q: str | None = None, + decision_date_to_q: str | None = None, + technology_center_number_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, + ) -> PTABAppealResponse: + """Search for PTAB appeal decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + appeal_number_q: Filter by appeal number. + application_number_text_q: Filter by application number. + appellant_name_q: Filter by appellant name. + requestor_name_q: Filter by requestor name. + decision_type_category_q: Filter by decision type category. + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + technology_center_number_q: Filter by technology center number. + additional_query_params: Additional custom query parameters. + + Returns: + PTABAppealResponse: Response containing matching appeal decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="appealNumber:2023-001234") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... technology_center_number_q="3600", + ... decision_date_from_q="2023-01-01", + ... limit=50 + ... ) + + # Search with POST body + >>> response = client.search_decisions( + ... post_body={"q": "decisionTypeCategory:Affirmed", "limit": 100} + ... ) + """ + endpoint = self.ENDPOINTS["search_decisions"] + + if post_body is not None: + # POST request path + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + response_class=PTABAppealResponse, + ) + else: + # GET request path + params: dict[str, Any] = {} + final_q = query + + # Build query from convenience parameters + if final_q is None: + q_parts = [] + if appeal_number_q: + q_parts.append(f"appealNumber:{appeal_number_q}") + if application_number_text_q: + q_parts.append( + f"appellantData.applicationNumberText:{application_number_text_q}" + ) + if appellant_name_q: + q_parts.append( + f"appellantData.realPartyInInterestName:{appellant_name_q}" + ) + if requestor_name_q: + q_parts.append(f"appellantData.counselName:{requestor_name_q}") + if decision_type_category_q: + q_parts.append( + f"decisionData.decisionTypeCategory:{decision_type_category_q}" + ) + if technology_center_number_q: + q_parts.append( + f"appellantData.technologyCenterNumber:{technology_center_number_q}" + ) + + # Handle decision date range + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"decisionData.decisionIssueDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append( + f"decisionData.decisionIssueDate:>={decision_date_from_q}" + ) + elif decision_date_to_q: + q_parts.append( + f"decisionData.decisionIssueDate:<={decision_date_to_q}" + ) + + if q_parts: + final_q = " AND ".join(q_parts) + + # Add parameters + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets is not None: + params["facets"] = facets + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=PTABAppealResponse, + ) + + assert isinstance(result, PTABAppealResponse) + return result + + def paginate_decisions(self, **kwargs: Any) -> Iterator[PTABAppealDecision]: + """Provide an iterator to paginate through appeal decision search results. + + This method simplifies fetching all appeal decisions matching a search query + by automatically handling pagination. It internally calls the search_decisions + method for GET requests, batching results and yielding them one by one. + + All keyword arguments are passed directly to search_decisions to define the + search criteria. The offset and limit parameters are managed by the pagination + logic; setting them directly in kwargs might lead to unexpected behavior. + + Args: + **kwargs: Keyword arguments passed to search_decisions for constructing + the search query. Do not include post_body. + + Returns: + Iterator[PTABAppealDecision]: An iterator yielding PTABAppealDecision objects, + allowing iteration over all matching decisions across multiple pages of results. + + Raises: + ValueError: If post_body is included in kwargs, as this method only + supports GET request parameters for pagination. + + Examples: + # Paginate through all decisions for a technology center + >>> for decision in client.paginate_decisions(technology_center_number_q="3600"): + ... print(f"{decision.appeal_meta_data.appeal_number}: " + ... f"{decision.decision_data.decision_type_category}") + + # Paginate with date range + >>> for decision in client.paginate_decisions( + ... decision_date_from_q="2023-01-01", + ... decision_date_to_q="2023-12-31" + ... ): + ... process_decision(decision) + """ + if "post_body" in kwargs: + raise ValueError( + "paginate_decisions uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_decisions", + response_container_attr="patent_appeal_data_bag", + **kwargs, + ) diff --git a/src/pyUSPTO/clients/ptab_interferences.py b/src/pyUSPTO/clients/ptab_interferences.py new file mode 100644 index 0000000..ff55314 --- /dev/null +++ b/src/pyUSPTO/clients/ptab_interferences.py @@ -0,0 +1,269 @@ +"""clients.ptab_interferences - Client for USPTO PTAB Interferences API. + +This module provides a client for interacting with the USPTO PTAB (Patent Trial +and Appeal Board) Interferences API. It allows you to search for patent interference decisions. +""" + +from collections.abc import Iterator +from typing import Any + +from pyUSPTO.clients.base import BaseUSPTOClient +from pyUSPTO.config import USPTOConfig +from pyUSPTO.models.ptab import PTABInterferenceDecision, PTABInterferenceResponse + + +class PTABInterferencesClient(BaseUSPTOClient[PTABInterferenceResponse]): + """Client for interacting with the USPTO PTAB Interferences API. + + This client provides methods to search for patent interference decisions from the + Patent Trial and Appeal Board. + + Interference proceedings are used to determine priority of invention when two or + more parties claim the same patentable invention. + """ + + ENDPOINTS = { + "search_decisions": "api/v1/patent/interferences/decisions/search", + } + + def __init__( + self, + api_key: str | None = None, + base_url: str | None = None, + config: USPTOConfig | None = None, + ): + """Initialize the PTABInterferencesClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.ptab_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + ) + + def search_decisions( + self, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, + # Convenience query parameters + interference_number_q: str | None = None, + senior_party_application_number_q: str | None = None, + junior_party_application_number_q: str | None = None, + senior_party_name_q: str | None = None, + junior_party_name_q: str | None = None, + real_party_in_interest_q: str | None = None, + interference_outcome_category_q: str | None = None, + decision_type_category_q: str | None = None, + decision_date_from_q: str | None = None, + decision_date_to_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, + ) -> PTABInterferenceResponse: + """Search for PTAB interference decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + interference_number_q: Filter by interference number. + senior_party_application_number_q: Filter by senior party application number. + junior_party_application_number_q: Filter by junior party application number. + senior_party_name_q: Filter by senior party name. + junior_party_name_q: Filter by junior party name. + real_party_in_interest_q: Filter by Real Party in Interest. + interference_outcome_category_q: Filter by interference outcome category. + decision_type_category_q: Filter by decision type category. + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + additional_query_params: Additional custom query parameters. + + Returns: + PTABInterferenceResponse: Response containing matching interference decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="interferenceNumber:106123") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... interference_outcome_category_q="Priority to Senior Party", + ... decision_date_from_q="2020-01-01", + ... limit=50 + ... ) + + # Search with POST body + >>> response = client.search_decisions( + ... post_body={"q": "decisionTypeCategory:Final Decision", "limit": 100} + ... ) + """ + endpoint = self.ENDPOINTS["search_decisions"] + + if post_body is not None: + # POST request path + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_query_params, + response_class=PTABInterferenceResponse, + ) + else: + # GET request path + params: dict[str, Any] = {} + final_q = query + + # Build query from convenience parameters + if final_q is None: + q_parts = [] + if interference_number_q: + q_parts.append(f"interferenceNumber:{interference_number_q}") + if senior_party_application_number_q: + q_parts.append( + f"seniorPartyData.applicationNumberText:{senior_party_application_number_q}" + ) + if junior_party_application_number_q: + q_parts.append( + f"juniorPartyData.applicationNumberText:{junior_party_application_number_q}" + ) + if senior_party_name_q: + q_parts.append( + f'seniorPartyData.patentOwnerName:"{senior_party_name_q}" OR seniorPartyData.inventorName:"{senior_party_name_q}" OR seniorPartyData.realPartyInInterestName:"{senior_party_name_q}"' + ) + if junior_party_name_q: + q_parts.append( + f'juniorPartyData.patentOwnerName:"{junior_party_name_q}" OR juniorPartyData.inventorName:"{junior_party_name_q}" OR juniorPartyData.realPartyInInterestName:"{junior_party_name_q}"' + ) + if real_party_in_interest_q: + q_parts.append( + f'seniorPartyData.realPartyInInterestName:"{real_party_in_interest_q}" OR juniorPartyData.realPartyInInterestName:"{real_party_in_interest_q}"' + ) + + if interference_outcome_category_q: + q_parts.append( + f'documentData.interferenceOutcomeCategory:"{interference_outcome_category_q}"' + ) + if decision_type_category_q: + q_parts.append( + f'documentData.decisionTypeCategory:"{decision_type_category_q}"' + ) + + # Handle decision date range + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"documentData.decisionIssueDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append( + f"documentData.decisionIssueDate:>={decision_date_from_q}" + ) + elif decision_date_to_q: + q_parts.append( + f"documentData.decisionIssueDate:<={decision_date_to_q}" + ) + + if q_parts: + final_q = " AND ".join(q_parts) + + # Add parameters + if final_q is not None: + params["q"] = final_q + if sort is not None: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets is not None: + params["facets"] = facets + if fields is not None: + params["fields"] = fields + if filters is not None: + params["filters"] = filters + if range_filters is not None: + params["rangeFilters"] = range_filters + + if additional_query_params: + params.update(additional_query_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=PTABInterferenceResponse, + ) + + assert isinstance(result, PTABInterferenceResponse) + return result + + def paginate_decisions(self, **kwargs: Any) -> Iterator[PTABInterferenceDecision]: + """Provide an iterator to paginate through interference decision search results. + + This method simplifies fetching all interference decisions matching a search query + by automatically handling pagination. It internally calls the search_decisions + method for GET requests, batching results and yielding them one by one. + + All keyword arguments are passed directly to search_decisions to define the + search criteria. The offset and limit parameters are managed by the pagination + logic; setting them directly in kwargs might lead to unexpected behavior. + + Args: + **kwargs: Keyword arguments passed to search_decisions for constructing + the search query. Do not include post_body. + + Returns: + Iterator[PTABInterferenceDecision]: An iterator yielding PTABInterferenceDecision + objects, allowing iteration over all matching decisions across multiple pages + of results. + + Raises: + ValueError: If post_body is included in kwargs, as this method only + supports GET request parameters for pagination. + + Examples: + # Paginate through all interference decisions + >>> for decision in client.paginate_decisions(): + ... print(f"{decision.interference_meta_data.interference_number}: " + ... f"{decision.document_data.interference_outcome_category}") + + # Paginate with date range + >>> for decision in client.paginate_decisions( + ... decision_date_from_q="2020-01-01", + ... decision_date_to_q="2023-12-31" + ... ): + ... process_decision(decision) + """ + if "post_body" in kwargs: + raise ValueError( + "paginate_decisions uses GET requests and does not support 'post_body'. " + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_decisions", + response_container_attr="patent_interference_data_bag", + **kwargs, + ) diff --git a/src/pyUSPTO/clients/ptab_trials.py b/src/pyUSPTO/clients/ptab_trials.py new file mode 100644 index 0000000..bc2bc57 --- /dev/null +++ b/src/pyUSPTO/clients/ptab_trials.py @@ -0,0 +1,496 @@ +"""clients.ptab_trials - Client for USPTO PTAB Trials API. + +This module provides a client for interacting with the USPTO PTAB (Patent Trial +and Appeal Board) Trials API. It allows you to search for trial proceedings, +documents, and decisions. +""" + +from collections.abc import Iterator +from typing import Any + +from pyUSPTO.clients.base import BaseUSPTOClient +from pyUSPTO.config import USPTOConfig +from pyUSPTO.models.ptab import ( + PTABTrialDocumentResponse, + PTABTrialProceeding, + PTABTrialProceedingResponse, +) + + +class PTABTrialsClient( + BaseUSPTOClient[PTABTrialProceedingResponse | PTABTrialDocumentResponse] +): + """Client for interacting with the USPTO PTAB Trials API. + + This client provides methods to search for trial proceedings, trial documents, + and trial decisions from the Patent Trial and Appeal Board. + + Trial proceedings data includes IPR (Inter Partes Review), PGR (Post-Grant Review), + CBM (Covered Business Method), and DER (Derivation) proceedings. + """ + + ENDPOINTS = { + "search_proceedings": "api/v1/patent/trials/proceedings/search", + "search_documents": "api/v1/patent/trials/documents/search", + "search_decisions": "api/v1/patent/trials/decisions/search", + } + + def __init__( + self, + api_key: str | None = None, + base_url: str | None = None, + config: USPTOConfig | None = None, + ): + """Initialize the PTABTrialsClient. + + Args: + api_key: Optional API key for authentication. + base_url: Optional base URL override for the API. + config: Optional USPTOConfig instance for configuration. + """ + self.config = config or USPTOConfig(api_key=api_key) + api_key_to_use = api_key or self.config.api_key + effective_base_url = ( + base_url or self.config.ptab_base_url or "https://api.uspto.gov" + ) + super().__init__( + api_key=api_key_to_use, base_url=effective_base_url, config=self.config + ) + + def _perform_search( + self, + endpoint_key: str, + response_class: Any, + query: str | None, + query_parts: list[str], + post_body: dict[str, Any] | None, + sort: str | None, + offset: int | None, + limit: int | None, + facets: str | None, + fields: str | None, + filters: str | None, + range_filters: str | None, + additional_params: dict[str, Any] | None, + ) -> PTABTrialProceedingResponse | PTABTrialDocumentResponse: + """Execute a PTAB trial search request using GET or POST. + + If a POST body is provided, perform a POST request; otherwise, build + query parameters and send a GET request. + """ + endpoint = self.ENDPOINTS[endpoint_key] + + # Handle POST request + if post_body is not None: + result = self._make_request( + method="POST", + endpoint=endpoint, + json_data=post_body, + params=additional_params, + response_class=response_class, + ) + return result # type: ignore + + # Handle GET request + params: dict[str, Any] = {} + final_q = query + + # Combine specific convenience query parts if no direct query is provided + if final_q is None and query_parts: + final_q = " AND ".join(query_parts) + + if final_q: + params["q"] = final_q + if sort: + params["sort"] = sort + if offset is not None: + params["offset"] = offset + if limit is not None: + params["limit"] = limit + if facets: + params["facets"] = facets + if fields: + params["fields"] = fields + if filters: + params["filters"] = filters + if range_filters: + params["rangeFilters"] = range_filters + + if additional_params: + params.update(additional_params) + + result = self._make_request( + method="GET", + endpoint=endpoint, + params=params, + response_class=response_class, + ) + return result # type: ignore + + def search_proceedings( + self, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, + # Convenience query parameters + trial_number_q: str | None = None, + patent_owner_name_q: str | None = None, + petitioner_real_party_in_interest_name_q: str | None = None, + respondent_name_q: str | None = None, + trial_type_code_q: str | None = None, + trial_status_category_q: str | None = None, + petition_filing_date_from_q: str | None = None, + petition_filing_date_to_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, + ) -> PTABTrialProceedingResponse: + """Search for PTAB trial proceedings. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + trial_number_q: Filter by trial number (e.g., "IPR2023-00001"). + patent_owner_name_q: Filter by patent owner name. + petitioner_real_party_in_interest_name_q: Filter by petitioner real party in interest. + respondent_name_q: Filter by respondent name. + trial_type_code_q: Filter by trial type code (e.g., "IPR", "PGR", "CBM", "DER"). + trial_status_category_q: Filter by trial status category. + petition_filing_date_from_q: Filter proceedings from this date (YYYY-MM-DD). + petition_filing_date_to_q: Filter proceedings to this date (YYYY-MM-DD). + additional_query_params: Additional custom query parameters. + + Returns: + PTABTrialProceedingResponse: Response containing matching trial proceedings. + + Examples: + # Search with direct query + >>> response = client.search_proceedings(query="trialNumber:IPR2023-00001") + + # Search with convenience parameters + >>> response = client.search_proceedings( + ... trial_type_code_q="IPR", + ... petition_filing_date_from_q="2023-01-01", + ... limit=50 + ... ) + """ + q_parts = [] + if trial_number_q: + q_parts.append(f"trialNumber:{trial_number_q}") + if patent_owner_name_q: + q_parts.append(f'patentOwnerData.patentOwnerName:"{patent_owner_name_q}"') + if petitioner_real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{petitioner_real_party_in_interest_name_q}"' + ) + if respondent_name_q: + q_parts.append(f'respondentData.patentOwnerName:"{respondent_name_q}"') + if trial_type_code_q: + q_parts.append(f"trialMetaData.trialTypeCode:{trial_type_code_q}") + if trial_status_category_q: + q_parts.append( + f'trialMetaData.trialStatusCategory:"{trial_status_category_q}"' + ) + + if petition_filing_date_from_q and petition_filing_date_to_q: + q_parts.append( + f"trialMetaData.petitionFilingDate:[{petition_filing_date_from_q} TO {petition_filing_date_to_q}]" + ) + elif petition_filing_date_from_q: + q_parts.append( + f"trialMetaData.petitionFilingDate:>={petition_filing_date_from_q}" + ) + elif petition_filing_date_to_q: + q_parts.append( + f"trialMetaData.petitionFilingDate:<={petition_filing_date_to_q}" + ) + + return self._perform_search( + endpoint_key="search_proceedings", + response_class=PTABTrialProceedingResponse, + query=query, + query_parts=q_parts, + post_body=post_body, + sort=sort, + offset=offset, + limit=limit, + facets=facets, + fields=fields, + filters=filters, + range_filters=range_filters, + additional_params=additional_query_params, + ) # type: ignore + + def search_documents( + self, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, + # Convenience query parameters + trial_number_q: str | None = None, + document_category_q: str | None = None, + document_type_name_q: str | None = None, + filing_date_from_q: str | None = None, + filing_date_to_q: str | None = None, + petitioner_real_party_in_interest_name_q: str | None = None, + inventor_name_q: str | None = None, + real_party_in_interest_name_q: str | None = None, + patent_number_q: str | None = None, + patent_owner_name_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, + ) -> PTABTrialDocumentResponse: + """Search for PTAB trial documents. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + trial_number_q: Filter by trial number. + document_category_q: Filter by document category (e.g., "Petition") DOCUMENTED BUT NOT IN API. + document_type_name_q: Filter by document type name (description). + filing_date_from_q: Filter documents from this date (YYYY-MM-DD). + filing_date_to_q: Filter documents to this date (YYYY-MM-DD). + petitioner_real_party_in_interest_name_q: Filter by petitioner real party in interest. + inventor_name_q: Filter by inventor name. + real_party_in_interest_name_q: Filter by real party in interest (generic). + patent_number_q: Filter by patent number. + patent_owner_name_q: Filter by patent owner name. + additional_query_params: Additional custom query parameters. + + Returns: + PTABTrialDocumentResponse: Response containing matching trial documents. + + Examples: + # Search with direct query + >>> response = client.search_documents(query="trialNumber:IPR2023-00001") + + # Search with convenience parameters + >>> response = client.search_documents( + ... document_category_q="Paper", + ... filing_date_from_q="2023-01-01", + ... limit=50 + ... ) + """ + q_parts = [] + if trial_number_q: + q_parts.append(f"trialNumber:{trial_number_q}") + if document_category_q: + q_parts.append(f'documentData.documentCategory:"{document_category_q}"') + if document_type_name_q: + q_parts.append( + f'documentData.documentTypeDescriptionText:"{document_type_name_q}"' + ) + if petitioner_real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{petitioner_real_party_in_interest_name_q}"' + ) + if inventor_name_q: + q_parts.append(f'patentOwnerData.inventorName:"{inventor_name_q}"') + if real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{real_party_in_interest_name_q}"' + ) + if patent_number_q: + q_parts.append(f"patentOwnerData.patentNumber:{patent_number_q}") + if patent_owner_name_q: + q_parts.append(f'patentOwnerData.patentOwnerName:"{patent_owner_name_q}"') + + if filing_date_from_q and filing_date_to_q: + q_parts.append( + f"documentData.documentFilingDate:[{filing_date_from_q} TO {filing_date_to_q}]" + ) + elif filing_date_from_q: + q_parts.append(f"documentData.documentFilingDate:>={filing_date_from_q}") + elif filing_date_to_q: + q_parts.append(f"documentData.documentFilingDate:<={filing_date_to_q}") + + return self._perform_search( + endpoint_key="search_documents", + response_class=PTABTrialDocumentResponse, + query=query, + query_parts=q_parts, + post_body=post_body, + sort=sort, + offset=offset, + limit=limit, + facets=facets, + fields=fields, + filters=filters, + range_filters=range_filters, + additional_params=additional_query_params, + ) # type: ignore + + def search_decisions( + self, + query: str | None = None, + sort: str | None = None, + offset: int | None = 0, + limit: int | None = 25, + facets: str | None = None, + fields: str | None = None, + filters: str | None = None, + range_filters: str | None = None, + post_body: dict[str, Any] | None = None, + # Convenience query parameters + trial_number_q: str | None = None, + decision_type_category_q: str | None = None, + document_type_description_q: str | None = None, + decision_date_from_q: str | None = None, + decision_date_to_q: str | None = None, + trial_type_code_q: str | None = None, + patent_number_q: str | None = None, + application_number_q: str | None = None, + patent_owner_name_q: str | None = None, + trial_status_category_q: str | None = None, + real_party_in_interest_name_q: str | None = None, + document_category_q: str | None = None, + additional_query_params: dict[str, Any] | None = None, + ) -> PTABTrialDocumentResponse: + """Search for PTAB trial decisions. + + This method can perform either a GET request using query parameters or a POST + request if post_body is specified. When using GET, you can provide either a + direct query string or use convenience parameters that will be automatically + combined into a query. + + Args: + query: Direct query string in USPTO search syntax. + sort: Sort order for results. + offset: Number of records to skip (pagination). + limit: Maximum number of records to return. + facets: Facet configuration string. + fields: Specific fields to return. + filters: Filter configuration string. + range_filters: Range filter configuration string. + post_body: Optional POST body for complex queries. + trial_number_q: Filter by trial number. + decision_type_category_q: Filter by decision type category. + document_type_description_q: Filter by "*[description]*". + decision_date_from_q: Filter decisions from this date (YYYY-MM-DD). + decision_date_to_q: Filter decisions to this date (YYYY-MM-DD). + trial_type_code_q: Filter by trial type code (e.g., "IPR", "PGR", "CBM", "DER"). + patent_number_q: Filter by patent number. + application_number_q: Filter by application number. + patent_owner_name_q: Filter by patent owner name. + trial_status_category_q: Filter by trial status category. + real_party_in_interest_name_q: Filter by real party in interest name. + document_category_q: Filter by document category. + additional_query_params: Additional custom query parameters. + + Returns: + PTABTrialDocumentResponse: Response containing matching trial decisions. + + Examples: + # Search with direct query + >>> response = client.search_decisions(query="trialNumber:IPR2023-00001") + + # Search with convenience parameters + >>> response = client.search_decisions( + ... decision_type_category_q="Final Written Decision", + ... decision_date_from_q="2023-01-01", + ... limit=50 + ... ) + """ + q_parts = [] + if trial_number_q: + q_parts.append(f"trialNumber:{trial_number_q}") + if decision_type_category_q: + q_parts.append( + f'decisionData.decisionTypeCategory:"{decision_type_category_q}"' + ) + if document_type_description_q: + q_parts.append( + f'documentData.documentTypeDescriptionText:"*{document_type_description_q}*"' + ) + if trial_type_code_q: + q_parts.append(f"trialMetaData.trialTypeCode:{trial_type_code_q}") + if patent_number_q: + q_parts.append(f"patentOwnerData.patentNumber:{patent_number_q}") + if application_number_q: + q_parts.append( + f"patentOwnerData.applicationNumberText:{application_number_q}" + ) + if patent_owner_name_q: + q_parts.append(f'patentOwnerData.patentOwnerName:"{patent_owner_name_q}"') + if trial_status_category_q: + q_parts.append( + f'trialMetaData.trialStatusCategory:"{trial_status_category_q}"' + ) + if real_party_in_interest_name_q: + q_parts.append( + f'regularPetitionerData.realPartyInInterestName:"{real_party_in_interest_name_q}"' + ) + if document_category_q: + q_parts.append(f'documentData.documentCategory:"{document_category_q}"') + + if decision_date_from_q and decision_date_to_q: + q_parts.append( + f"decisionData.decisionIssueDate:[{decision_date_from_q} TO {decision_date_to_q}]" + ) + elif decision_date_from_q: + q_parts.append(f"decisionData.decisionIssueDate:>={decision_date_from_q}") + elif decision_date_to_q: + q_parts.append(f"decisionData.decisionIssueDate:<={decision_date_to_q}") + + return self._perform_search( + endpoint_key="search_decisions", + response_class=PTABTrialDocumentResponse, + query=query, + query_parts=q_parts, + post_body=post_body, + sort=sort, + offset=offset, + limit=limit, + facets=facets, + fields=fields, + filters=filters, + range_filters=range_filters, + additional_params=additional_query_params, + ) # type: ignore + + def paginate_proceedings(self, **kwargs: Any) -> Iterator[PTABTrialProceeding]: + """Provide an iterator to paginate through trial proceeding search results.""" + if "post_body" in kwargs: + raise ValueError( + "paginate_proceedings uses GET requests and does not support 'post_body'." + "Use keyword arguments for search criteria." + ) + + return self.paginate_results( + method_name="search_proceedings", + response_container_attr="patent_trial_proceeding_data_bag", + **kwargs, + ) diff --git a/src/pyUSPTO/config.py b/src/pyUSPTO/config.py index 32819d9..72fedbc 100644 --- a/src/pyUSPTO/config.py +++ b/src/pyUSPTO/config.py @@ -1,12 +1,10 @@ -""" -config - Configuration management for USPTO API clients +"""config - Configuration management for USPTO API clients. This module provides configuration management for USPTO API clients, including API keys, base URLs, and HTTP transport settings. """ import os -from typing import Optional from pyUSPTO.http_config import HTTPConfig @@ -20,11 +18,12 @@ class USPTOConfig: def __init__( self, - api_key: Optional[str] = None, + api_key: str | None = None, bulk_data_base_url: str = "https://api.uspto.gov", patent_data_base_url: str = "https://api.uspto.gov", petition_decisions_base_url: str = "https://api.uspto.gov", - http_config: Optional[HTTPConfig] = None, + ptab_base_url: str = "https://api.uspto.gov", + http_config: HTTPConfig | None = None, include_raw_data: bool = False, ): """Initialize the USPTOConfig. @@ -34,6 +33,7 @@ def __init__( bulk_data_base_url: Base URL for the Bulk Data API patent_data_base_url: Base URL for the Patent Data API petition_decisions_base_url: Base URL for the Final Petition Decisions API + ptab_base_url: Base URL for the PTAB (Patent Trial and Appeal Board) API http_config: Optional HTTPConfig for request handling (uses defaults if None) include_raw_data: If True, store raw JSON in response objects for debugging (default: False) """ @@ -44,6 +44,7 @@ def __init__( self.bulk_data_base_url = bulk_data_base_url self.patent_data_base_url = patent_data_base_url self.petition_decisions_base_url = petition_decisions_base_url + self.ptab_base_url = ptab_base_url # Use provided HTTPConfig or create default self.http_config = http_config if http_config is not None else HTTPConfig() @@ -69,6 +70,9 @@ def from_env(cls) -> "USPTOConfig": petition_decisions_base_url=os.environ.get( "USPTO_PETITION_DECISIONS_BASE_URL", "https://api.uspto.gov" ), + ptab_base_url=os.environ.get( + "USPTO_PTAB_BASE_URL", "https://api.uspto.gov" + ), # Also read HTTP config from environment http_config=HTTPConfig.from_env(), ) diff --git a/src/pyUSPTO/exceptions.py b/src/pyUSPTO/exceptions.py index aee5b24..40f2663 100644 --- a/src/pyUSPTO/exceptions.py +++ b/src/pyUSPTO/exceptions.py @@ -1,5 +1,4 @@ -""" -exceptions - Exception classes for USPTO API clients +"""exceptions - Exception classes for USPTO API clients. This module provides exception classes for USPTO API errors that correspond to the various response types from the USPTO API. It also includes helper @@ -7,7 +6,7 @@ """ from dataclasses import asdict, dataclass -from typing import TYPE_CHECKING, Optional, Type, Union +from typing import TYPE_CHECKING # To avoid circular imports if requests is type-hinted directly, # use TYPE_CHECKING guard or a string literal for the type hint. @@ -18,6 +17,7 @@ # --- Exception Classes (largely unchanged) --- class USPTOApiError(Exception): """Base exception for USPTO API errors. + This is the parent class for all USPTO API-specific exceptions. It includes information about the status code, API's short error message, detailed error information, and request identifier from the API response. @@ -28,17 +28,16 @@ class USPTOApiError(Exception): def __init__( self, message: str, # Primary client-facing message for the exception context - status_code: Optional[int] = None, - api_short_error: Optional[ - str - ] = None, # From API 'error' or 'message' (for 413) field - error_details: Optional[ - Union[str, dict] - ] = None, # From API 'errorDetails' or 'detailedMessage' field - request_identifier: Optional[str] = None, + status_code: int | None = None, + api_short_error: str + | None = None, # From API 'error' or 'message' (for 413) field + error_details: str + | dict + | None = None, # From API 'errorDetails' or 'detailedMessage' field + request_identifier: str | None = None, ): - """ - Initializes the USPTOApiError. + """Initialize a USPTOApiError. + Args: message: The primary message for the exception (often client-generated context). status_code: The HTTP status code from the API response (e.g., 400, 403). @@ -55,15 +54,15 @@ def __init__( @property def message(self) -> str: - """ - Provides direct access to the primary exception message. + """Provides direct access to the primary exception message. + This refers to the first argument passed to the exception, which is conventionally the main human-readable message. """ return str(object=self.args[0]) def __str__(self) -> str: - """Provides a more informative string representation of the error.""" + """Provide a more informative string representation of the error.""" parts = [super().__str__()] if self.status_code: @@ -141,10 +140,10 @@ class APIErrorArgs: """Data structure to hold arguments for API exception constructors.""" message: str - status_code: Optional[int] = None - api_short_error: Optional[str] = None - error_details: Optional[Union[str, dict]] = None - request_identifier: Optional[str] = None + status_code: int | None = None + api_short_error: str | None = None + error_details: str | dict | None = None + request_identifier: str | None = None @classmethod def from_http_error( @@ -152,8 +151,7 @@ def from_http_error( http_error: "requests.exceptions.HTTPError", # String literal for type hint client_operation_message: str, ) -> "APIErrorArgs": - """ - Creates an APIErrorArgs instance by parsing a requests.exceptions.HTTPError. + """Create an APIErrorArgs instance by parsing a requests.exceptions.HTTPError. Args: http_error: The HTTPError object from the requests library. @@ -213,10 +211,11 @@ def from_http_error( def from_request_exception( cls, request_exception: "requests.exceptions.RequestException", # String for type hint - client_operation_message: Optional[str] = None, + client_operation_message: str | None = None, ) -> "APIErrorArgs": - """ - Creates an APIErrorArgs instance from a generic requests.exceptions.RequestException + """Create an APIErrorArgs instance. + + Create an APIErrorArgs instance from a generic requests.exceptions.RequestException. (e.g., ConnectionError, Timeout) that is not an HTTPError. """ message_prefix = client_operation_message or "API request failed" @@ -227,9 +226,9 @@ def from_request_exception( def get_api_exception(error_args: APIErrorArgs) -> USPTOApiError: - """ - Determines and instantiates the appropriate USPTOApiError subclass - based on the status code in error_args. + """Determine and instantiate the appropriate USPTOApiError subclass. + + Based on the status code in error_args. Args: error_args: An instance of APIErrorArgs containing all necessary @@ -239,7 +238,7 @@ def get_api_exception(error_args: APIErrorArgs) -> USPTOApiError: An instance of a USPTOApiError subclass. """ status_code = error_args.status_code - exception_class: Type[USPTOApiError] + exception_class: type[USPTOApiError] match status_code: case 400: diff --git a/src/pyUSPTO/http_config.py b/src/pyUSPTO/http_config.py index 64baccc..3d0e3c3 100644 --- a/src/pyUSPTO/http_config.py +++ b/src/pyUSPTO/http_config.py @@ -1,5 +1,4 @@ -""" -http_config - HTTP client configuration for USPTO API requests +"""http_config - HTTP client configuration for USPTO API requests. This module provides configuration for HTTP transport-level settings including timeouts, retries, connection pooling, and custom headers. @@ -7,7 +6,6 @@ import os from dataclasses import dataclass, field -from typing import Dict, List, Optional @dataclass @@ -29,13 +27,13 @@ class HTTPConfig: """ # Timeout configuration - timeout: Optional[float] = 30.0 - connect_timeout: Optional[float] = 10.0 + timeout: float | None = 30.0 + connect_timeout: float | None = 10.0 # Retry configuration max_retries: int = 3 backoff_factor: float = 1.0 - retry_status_codes: List[int] = field( + retry_status_codes: list[int] = field( default_factory=lambda: [429, 500, 502, 503, 504] ) @@ -44,7 +42,7 @@ class HTTPConfig: pool_maxsize: int = 10 # Custom headers (User-Agent, tracking, etc.) - custom_headers: Optional[Dict[str, str]] = None + custom_headers: dict[str, str] | None = None @classmethod def from_env(cls) -> "HTTPConfig": @@ -70,7 +68,7 @@ def from_env(cls) -> "HTTPConfig": pool_maxsize=int(os.environ.get("USPTO_POOL_MAXSIZE", "10")), ) - def get_timeout_tuple(self) -> tuple[Optional[float], Optional[float]]: + def get_timeout_tuple(self) -> tuple[float | None, float | None]: """Get timeout as tuple for requests library. Returns: diff --git a/src/pyUSPTO/models/__init__.py b/src/pyUSPTO/models/__init__.py index 7bd2599..2210a6b 100644 --- a/src/pyUSPTO/models/__init__.py +++ b/src/pyUSPTO/models/__init__.py @@ -1,5 +1,4 @@ -""" -models - Data models for USPTO APIs +"""models - Data models for USPTO APIs. This package provides data models for USPTO APIs. """ @@ -18,6 +17,14 @@ PetitionDecisionDownloadResponse, PetitionDecisionResponse, ) +from pyUSPTO.models.ptab import ( + PTABAppealDecision, + PTABAppealResponse, + PTABInterferenceDecision, + PTABInterferenceResponse, + PTABTrialProceeding, + PTABTrialProceedingResponse, +) __all__ = [ "FromDictProtocol", @@ -30,4 +37,11 @@ "PetitionDecisionResponse", "PetitionDecisionDownloadResponse", "DocumentDownloadOption", + # PTAB Models + "PTABTrialProceeding", + "PTABTrialProceedingResponse", + "PTABAppealDecision", + "PTABAppealResponse", + "PTABInterferenceDecision", + "PTABInterferenceResponse", ] diff --git a/src/pyUSPTO/models/base.py b/src/pyUSPTO/models/base.py index 486b811..34069b9 100644 --- a/src/pyUSPTO/models/base.py +++ b/src/pyUSPTO/models/base.py @@ -1,10 +1,9 @@ -""" -models.base - Base model classes and protocols for USPTO API models +"""models.base - Base model classes and protocols for USPTO API models. This module provides base model classes and protocols for USPTO API models. """ -from typing import Any, Dict, Optional, Protocol, runtime_checkable +from typing import Any, Protocol, runtime_checkable @runtime_checkable @@ -12,7 +11,7 @@ class FromDictProtocol(Protocol): """Protocol for classes that can be created from a dictionary.""" @classmethod - def from_dict(cls, data: Dict[str, Any]) -> Any: + def from_dict(cls, data: dict[str, Any]) -> Any: """Create an object from a dictionary.""" ... @@ -20,9 +19,15 @@ def from_dict(cls, data: Dict[str, Any]) -> Any: class BaseModel: """A base model providing common functionality like raw data storage.""" - raw_data: Optional[Any] + raw_data: Any | None + + def __init__(self, raw_data: Any | None = None, **kwargs: Any) -> None: + """Initialize the BaseModel. - def __init__(self, raw_data: Optional[Any] = None, **kwargs: Any) -> None: + Args: + raw_data: The original API JSON data to store for reference. + **kwargs: Additional keyword arguments to set as attributes on the instance. + """ # raw_data holds the original API JSON data. self.raw_data = raw_data diff --git a/src/pyUSPTO/models/bulk_data.py b/src/pyUSPTO/models/bulk_data.py index f8c23ef..fba5967 100644 --- a/src/pyUSPTO/models/bulk_data.py +++ b/src/pyUSPTO/models/bulk_data.py @@ -1,17 +1,16 @@ -""" -models.bulk_data - Data models for USPTO bulk data API +"""models.bulk_data - Data models for USPTO bulk data API. This module provides data models for the USPTO Open Data Portal (ODP) Bulk Data API. """ import json from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional +from typing import Any @dataclass class FileData: - """Represents a file in the bulk data API.""" + """Represent a file in the bulk data API.""" file_name: str file_size: int @@ -19,12 +18,12 @@ class FileData: file_data_to_date: str file_type_text: str file_release_date: str - file_download_uri: Optional[str] = None - file_date: Optional[str] = None - file_last_modified_date_time: Optional[str] = None + file_download_uri: str | None = None + file_date: str | None = None + file_last_modified_date_time: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "FileData": + def from_dict(cls, data: dict[str, Any]) -> "FileData": """Create a FileData object from a dictionary.""" return cls( file_name=data.get("fileName", ""), @@ -44,10 +43,10 @@ class ProductFileBag: """Container for file data elements.""" count: int - file_data_bag: List[FileData] + file_data_bag: list[FileData] @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ProductFileBag": + def from_dict(cls, data: dict[str, Any]) -> "ProductFileBag": """Create a ProductFileBag object from a dictionary.""" return cls( count=data.get("count", 0), @@ -60,26 +59,26 @@ def from_dict(cls, data: Dict[str, Any]) -> "ProductFileBag": @dataclass class BulkDataProduct: - """Represents a product in the bulk data API.""" + """Represent a product in the bulk data API.""" product_identifier: str product_description_text: str product_title_text: str product_frequency_text: str - product_label_array_text: List[str] - product_dataset_array_text: List[str] - product_dataset_category_array_text: List[str] + product_label_array_text: list[str] + product_dataset_array_text: list[str] + product_dataset_category_array_text: list[str] product_from_date: str product_to_date: str product_total_file_size: int product_file_total_quantity: int last_modified_date_time: str - mime_type_identifier_array_text: List[str] - product_file_bag: Optional[ProductFileBag] = None - days_of_week_text: Optional[str] = None + mime_type_identifier_array_text: list[str] + product_file_bag: ProductFileBag | None = None + days_of_week_text: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "BulkDataProduct": + def from_dict(cls, data: dict[str, Any]) -> "BulkDataProduct": """Create a BulkDataProduct object from a dictionary.""" return cls( product_identifier=data.get("productIdentifier", ""), @@ -113,12 +112,12 @@ class BulkDataResponse: """ count: int - bulk_data_product_bag: List[BulkDataProduct] - raw_data: Optional[str] = field(default=None, compare=False, repr=False) + bulk_data_product_bag: list[BulkDataProduct] + raw_data: str | None = field(default=None, compare=False, repr=False) @classmethod def from_dict( - cls, data: Dict[str, Any], include_raw_data: bool = False + cls, data: dict[str, Any], include_raw_data: bool = False ) -> "BulkDataResponse": """Create a BulkDataResponse object from a dictionary. @@ -138,7 +137,7 @@ def from_dict( raw_data=json.dumps(data) if include_raw_data else None, ) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Convert the BulkDataResponse object to a dictionary.""" return { "count": self.count, diff --git a/src/pyUSPTO/models/patent_data.py b/src/pyUSPTO/models/patent_data.py index fe43a4c..1ccc0f0 100644 --- a/src/pyUSPTO/models/patent_data.py +++ b/src/pyUSPTO/models/patent_data.py @@ -1,5 +1,4 @@ -""" -models.patent_data - Data models for USPTO patent data API +"""models.patent_data - Data models for USPTO patent data API. This module provides Pydantic-style data models, primarily using frozen dataclasses, for representing responses from the USPTO Patent Data API. @@ -13,21 +12,21 @@ import io import json import warnings +from collections.abc import Iterator from dataclasses import asdict, dataclass, field from datetime import date, datetime from enum import Enum -from typing import Any, Dict, Iterator, List, Optional, Union +from typing import Any # Import utility functions from models.utils module from pyUSPTO.models.utils import ( - ASSUMED_NAIVE_TIMEZONE, - ASSUMED_NAIVE_TIMEZONE_STR, parse_to_date, parse_to_datetime_utc, parse_yn_to_bool, serialize_bool_to_yn, serialize_date, serialize_datetime_as_iso, + serialize_datetime_as_naive, to_camel_case, ) from pyUSPTO.warnings import USPTOEnumParseWarning @@ -42,7 +41,7 @@ class DirectionCategory(Enum): class ActiveIndicator(Enum): - """Represents an active or inactive status, often used for practitioners or entities. + """Represent an active or inactive status, often used for practitioners or entities. This Enum is designed to flexibly parse common string representations of active/inactive or true/false states (e.g., "Y", "N", "true", "false", "Active") @@ -75,7 +74,7 @@ def _missing_(cls, value: Any) -> "ActiveIndicator": # --- Data Models --- @dataclass(frozen=True) class DocumentFormat: - """Represents an available download format for a specific document. + """Represent an available download format for a specific document. Attributes: mime_type_identifier: The MIME type of the downloadable file (e.g., "PDF"). @@ -83,21 +82,31 @@ class DocumentFormat: page_total_quantity: The total number of pages in this document format. """ - mime_type_identifier: Optional[str] = None - download_url: Optional[str] = None - page_total_quantity: Optional[int] = None + mime_type_identifier: str | None = None + download_url: str | None = None + page_total_quantity: int | None = None def __str__(self) -> str: + """Return a human-readable string representation of the DocumentFormat. + + Returns: + str: A description of the format type and page count. + """ return ( f"{self.mime_type_identifier} format with {self.page_total_quantity} pages" ) def __repr__(self) -> str: + """Return a developer-friendly string representation of the DocumentFormat. + + Returns: + str: A string showing the mime type and page count. + """ return f"DocumentFormat(mime_type={self.mime_type_identifier}, pages={self.page_total_quantity})" @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "DocumentFormat": - """Creates a `DocumentFormat` instance from a dictionary representation. + def from_dict(cls, data: dict[str, Any]) -> "DocumentFormat": + """Create a `DocumentFormat` instance from a dictionary representation. This factory method is typically used to construct `DocumentFormat` objects from data parsed from an API JSON response. It maps @@ -112,15 +121,14 @@ def from_dict(cls, data: Dict[str, Any]) -> "DocumentFormat": DocumentFormat: An instance of `DocumentFormat` initialized with data from the input dictionary. """ - return cls( mime_type_identifier=data.get("mimeTypeIdentifier"), download_url=data.get("downloadUrl"), page_total_quantity=data.get("pageTotalQuantity"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `DocumentFormat` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `DocumentFormat` instance to a dictionary. This method serializes the `DocumentFormat` object into a dictionary, mapping the instance's attributes to camelCase keys. This is typically @@ -131,7 +139,6 @@ def to_dict(self) -> Dict[str, Any]: instance with keys "mimeTypeIdentifier", "downloadUrl", and "pageTotalQuantity". """ - return { "mimeTypeIdentifier": self.mime_type_identifier, "downloadUrl": self.download_url, @@ -141,7 +148,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Document: - """Represents a single document associated with a patent application. + """Represent a single document associated with a patent application. This includes metadata such as its identifier, official date, code, description, direction (incoming/outgoing), and available download formats. @@ -156,26 +163,36 @@ class Document: document_formats: A list of available download formats for this document. """ - application_number_text: Optional[str] = None - official_date: Optional[datetime] = None - document_identifier: Optional[str] = None - document_code: Optional[str] = None - document_code_description_text: Optional[str] = None - direction_category: Optional[DirectionCategory] = None - document_formats: List[DocumentFormat] = field(default_factory=list) + application_number_text: str | None = None + official_date: datetime | None = None + document_identifier: str | None = None + document_code: str | None = None + document_code_description_text: str | None = None + direction_category: DirectionCategory | None = None + document_formats: list[DocumentFormat] = field(default_factory=list) def __str__(self) -> str: + """Return a human-readable string representation of the Document. + + Returns: + str: A description including document ID, code, description, and date. + """ date_str = ( self.official_date.strftime("%Y-%m-%d") if self.official_date else "No date" ) return f"Document {self.document_identifier} ({self.document_code}): {self.document_code_description_text} - {date_str}" def __repr__(self) -> str: + """Return a developer-friendly string representation of the Document. + + Returns: + str: A string showing the document ID, code, and date. + """ return f"Document(id={self.document_identifier}, code={self.document_code}, date={self.official_date.strftime('%Y-%m-%d') if self.official_date else 'None'})" @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Document": - """Creates a `Document` instance from a dictionary representation. + def from_dict(cls, data: dict[str, Any]) -> "Document": + """Create a `Document` instance from a dictionary representation. Maps API JSON keys (camelCase) to class attributes, parsing nested objects like `DocumentFormat` and `DirectionCategory`. @@ -187,7 +204,6 @@ def from_dict(cls, data: Dict[str, Any]) -> "Document": Returns: Document: An instance of `Document`. """ - dl_formats = [ DocumentFormat.from_dict(f) for f in data.get("downloadOptionBag", []) @@ -214,8 +230,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "Document": document_formats=dl_formats, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Document` instance to a dictionary for API compatibility. + def to_dict(self) -> dict[str, Any]: + """Convert the `Document` instance to a dictionary for API compatibility. Serializes attributes to camelCase keys and handles nested objects. Omits keys with None values or empty lists. @@ -223,10 +239,13 @@ def to_dict(self) -> Dict[str, Any]: Returns: Dict[str, Any]: A dictionary representation of the `Document`. """ - d = { "applicationNumberText": self.application_number_text, - "officialDate": serialize_datetime_as_iso(self.official_date), + "officialDate": ( + serialize_datetime_as_iso(self.official_date) + if self.official_date + else None + ), "documentIdentifier": self.document_identifier, "documentCode": self.document_code, "documentCodeDescriptionText": self.document_code_description_text, @@ -252,8 +271,8 @@ class DocumentBag: documents (tuple[Document, ...]): An immutable tuple of `Document` objects. """ - def __init__(self, documents: List[Document]): - """Initializes the DocumentBag with a list of documents. + def __init__(self, documents: list[Document]): + """Initialize a DocumentBag with a list of documents. Args: documents (List[Document]): A list of `Document` instances. @@ -262,20 +281,38 @@ def __init__(self, documents: List[Document]): @property def documents(self) -> tuple[Document, ...]: - """Provides access to the tuple of documents.""" + """Provide access to the tuple of documents.""" return self._documents def __iter__(self) -> Iterator[Document]: + """Return an iterator over the documents in the collection. + + Returns: + Iterator[Document]: An iterator of Document instances. + """ return iter(self._documents) def __len__(self) -> int: + """Return the number of documents in the collection. + + Returns: + int: The count of documents. + """ return len(self._documents) def __getitem__(self, index: int) -> Document: + """Return the document at the specified index. + + Args: + index: The index of the document to retrieve. + + Returns: + Document: The document at the specified index. + """ return self._documents[index] def __str__(self) -> str: - """Returns a string representation showing document count and summary. + """Return a string representation showing document count and summary. Returns: str: Human-readable summary of the DocumentBag. @@ -285,7 +322,7 @@ def __str__(self) -> str: return "DocumentBag(0 documents)" # Count unique document codes - doc_codes: Dict[str, int] = {} + doc_codes: dict[str, int] = {} for doc in self._documents: code = doc.document_code or "Unknown" doc_codes[code] = doc_codes.get(code, 0) + 1 @@ -307,7 +344,7 @@ def __str__(self) -> str: return f"DocumentBag({count} documents: {code_summary})" def __repr__(self) -> str: - """Returns a detailed string representation for debugging. + """Return a detailed string representation for debugging. Returns: str: Detailed representation of the DocumentBag. @@ -315,8 +352,8 @@ def __repr__(self) -> str: return f"DocumentBag(documents={self._documents!r})" @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "DocumentBag": - """Creates a `DocumentBag` instance from a dictionary representation. + def from_dict(cls, data: dict[str, Any]) -> "DocumentBag": + """Create a `DocumentBag` instance from a dictionary representation. Expects a dictionary with a "documentBag" key containing a list of document data dictionaries. @@ -336,8 +373,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "DocumentBag": ) return cls(documents=docs) - def to_dict(self) -> Dict[str, Any]: - """Converts the `DocumentBag` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `DocumentBag` instance to a dictionary. Serializes the collection into a dictionary with a "documentBag" key, containing a list of `Document` dictionaries. @@ -350,7 +387,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Address: - """Represents a postal address with fields for street, city, region, country, and postal code. + """Represent a postal address with fields for street, city, region, country, and postal code. It can be used for various entities like applicants, inventors, or correspondence. @@ -374,27 +411,27 @@ class Address: ict_country_code: International code for the country (USPTO format). """ - name_line_one_text: Optional[str] = None - name_line_two_text: Optional[str] = None - address_line_one_text: Optional[str] = None - address_line_two_text: Optional[str] = None - address_line_three_text: Optional[str] = None - address_line_four_text: Optional[str] = None - geographic_region_name: Optional[str] = None - geographic_region_code: Optional[str] = None - postal_code: Optional[str] = None - city_name: Optional[str] = None - country_code: Optional[str] = None - country_name: Optional[str] = None - postal_address_category: Optional[str] = None - correspondent_name_text: Optional[str] = None - country_or_state_code: Optional[str] = None - ict_state_code: Optional[str] = None - ict_country_code: Optional[str] = None + name_line_one_text: str | None = None + name_line_two_text: str | None = None + address_line_one_text: str | None = None + address_line_two_text: str | None = None + address_line_three_text: str | None = None + address_line_four_text: str | None = None + geographic_region_name: str | None = None + geographic_region_code: str | None = None + postal_code: str | None = None + city_name: str | None = None + country_code: str | None = None + country_name: str | None = None + postal_address_category: str | None = None + correspondent_name_text: str | None = None + country_or_state_code: str | None = None + ict_state_code: str | None = None + ict_country_code: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Address": - """Creates an `Address` instance from a dictionary representation. + def from_dict(cls, data: dict[str, Any]) -> "Address": + """Create an `Address` instance from a dictionary representation. Maps camelCase keys from API data to class attributes. @@ -424,14 +461,13 @@ def from_dict(cls, data: Dict[str, Any]) -> "Address": ict_country_code=data.get("ictCountryCode"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Address` instance to a dictionary with camelCase keys. + def to_dict(self) -> dict[str, Any]: + """Convert the `Address` instance to a dictionary with camelCase keys. Returns: Dict[str, Any]: A dictionary representation of the address. """ - - return { + _dict = { "nameLineOneText": self.name_line_one_text, "nameLineTwoText": self.name_line_two_text, "addressLineOneText": self.address_line_one_text, @@ -450,11 +486,13 @@ def to_dict(self) -> Dict[str, Any]: "ictStateCode": self.ict_state_code, "ictCountryCode": self.ict_country_code, } + # Filter out None values to match API behavior + return {k: v for k, v in _dict.items() if v is not None} @dataclass(frozen=True) class Telecommunication: - """Represents telecommunication details, such as phone or fax numbers. + """Represent telecommunication details, such as phone or fax numbers. Attributes: telecommunication_number: The main number (e.g., phone number). @@ -462,13 +500,13 @@ class Telecommunication: telecom_type_code: A code indicating the type of telecommunication (e.g., "TEL", "FAX"). """ - telecommunication_number: Optional[str] = None - extension_number: Optional[str] = None - telecom_type_code: Optional[str] = None + telecommunication_number: str | None = None + extension_number: str | None = None + telecom_type_code: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Telecommunication": - """Creates a `Telecommunication` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Telecommunication": + """Create a `Telecommunication` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with telecommunication data. @@ -482,17 +520,19 @@ def from_dict(cls, data: Dict[str, Any]) -> "Telecommunication": telecom_type_code=data.get("telecomTypeCode"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Telecommunication` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Telecommunication` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. """ - return { + _dict = { "telecommunicationNumber": self.telecommunication_number, "extensionNumber": self.extension_number, "telecomTypeCode": self.telecom_type_code, } + # Filter out None values to match API behavior + return {k: v for k, v in _dict.items() if v is not None} @dataclass(frozen=True) @@ -511,16 +551,16 @@ class Person: country_code: The country code associated with the person (e.g., citizenship). """ - first_name: Optional[str] = None - middle_name: Optional[str] = None - last_name: Optional[str] = None - name_prefix: Optional[str] = None - name_suffix: Optional[str] = None - preferred_name: Optional[str] = None - country_code: Optional[str] = None + first_name: str | None = None + middle_name: str | None = None + last_name: str | None = None + name_prefix: str | None = None + name_suffix: str | None = None + preferred_name: str | None = None + country_code: str | None = None @classmethod - def _extract_person_fields(cls, data: Dict[str, Any]) -> Dict[str, Any]: + def _extract_person_fields(cls, data: dict[str, Any]) -> dict[str, Any]: return { "first_name": data.get("firstName"), "middle_name": data.get("middleName"), @@ -531,8 +571,8 @@ def _extract_person_fields(cls, data: Dict[str, Any]) -> Dict[str, Any]: "country_code": data.get("countryCode"), } - def to_dict(self) -> Dict[str, Any]: - """Converts the `Person` instance to a dictionary with camelCase keys. + def to_dict(self) -> dict[str, Any]: + """Convert the `Person` instance to a dictionary with camelCase keys. Omits attributes that are None. @@ -544,7 +584,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Applicant(Person): - """Represents an applicant for a patent, inheriting from Person. + """Represent an applicant for a patent, inheriting from Person. Includes applicant-specific name text and a list of correspondence addresses. @@ -553,12 +593,12 @@ class Applicant(Person): correspondence_address_bag: A list of `Address` objects for the applicant. """ - applicant_name_text: Optional[str] = None - correspondence_address_bag: List[Address] = field(default_factory=list) + applicant_name_text: str | None = None + correspondence_address_bag: list[Address] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Applicant": - """Creates an `Applicant` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Applicant": + """Create an `Applicant` instance from a dictionary. Inherits person fields and adds applicant-specific fields. @@ -580,8 +620,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "Applicant": correspondence_address_bag=addrs, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Applicant` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Applicant` instance to a dictionary. Includes inherited person fields and applicant-specific fields, using camelCase keys and omitting None values or empty lists. @@ -607,7 +647,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Inventor(Person): - """Represents an inventor for a patent application, inheriting from Person. + """Represent an inventor for a patent application, inheriting from Person. Includes inventor-specific name text and a list of correspondence addresses. @@ -616,12 +656,12 @@ class Inventor(Person): correspondence_address_bag: A list of `Address` objects for the inventor. """ - inventor_name_text: Optional[str] = None - correspondence_address_bag: List[Address] = field(default_factory=list) + inventor_name_text: str | None = None + correspondence_address_bag: list[Address] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Inventor": - """Creates an `Inventor` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Inventor": + """Create an `Inventor` instance from a dictionary. Inherits person fields and adds inventor-specific fields. @@ -643,8 +683,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "Inventor": correspondence_address_bag=addrs, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Inventor` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Inventor` instance to a dictionary. Includes inherited person fields and inventor-specific fields, using camelCase keys and omitting None values or empty lists. @@ -670,7 +710,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Attorney(Person): - """Represents an attorney or agent associated with a patent application, inheriting from Person. + """Represent an attorney or agent associated with a patent application, inheriting from Person. Includes registration number, active status, practitioner category, addresses, and telecommunication details. @@ -682,15 +722,15 @@ class Attorney(Person): telecommunication_address_bag: List of `Telecommunication` objects for the attorney. """ - registration_number: Optional[str] = None - active_indicator: Optional[str] = None - registered_practitioner_category: Optional[str] = None - attorney_address_bag: List[Address] = field(default_factory=list) - telecommunication_address_bag: List[Telecommunication] = field(default_factory=list) + registration_number: str | None = None + active_indicator: str | None = None + registered_practitioner_category: str | None = None + attorney_address_bag: list[Address] = field(default_factory=list) + telecommunication_address_bag: list[Telecommunication] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Attorney": - """Creates an `Attorney` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Attorney": + """Create an `Attorney` instance from a dictionary. Inherits person fields and adds attorney-specific details. @@ -720,8 +760,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "Attorney": telecommunication_address_bag=telecoms, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Attorney` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Attorney` instance to a dictionary. Includes inherited person fields and attorney-specific fields, using camelCase keys and omitting None values or empty lists. @@ -757,12 +797,12 @@ class EntityStatus: business_entity_status_category: String category of the business entity status (e.g., "Undiscounted"). """ - small_entity_status_indicator: Optional[bool] = None - business_entity_status_category: Optional[str] = None + small_entity_status_indicator: bool | None = None + business_entity_status_category: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "EntityStatus": - """Creates an `EntityStatus` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "EntityStatus": + """Create an `EntityStatus` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with entity status data. @@ -775,8 +815,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "EntityStatus": business_entity_status_category=data.get("businessEntityStatusCategory"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `EntityStatus` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `EntityStatus` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. @@ -800,14 +840,14 @@ class CustomerNumberCorrespondence: telecommunication_address_bag: List of `Telecommunication` objects. """ - patron_identifier: Optional[int] = None - organization_standard_name: Optional[str] = None - power_of_attorney_address_bag: List[Address] = field(default_factory=list) - telecommunication_address_bag: List[Telecommunication] = field(default_factory=list) + patron_identifier: int | None = None + organization_standard_name: str | None = None + power_of_attorney_address_bag: list[Address] = field(default_factory=list) + telecommunication_address_bag: list[Telecommunication] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "CustomerNumberCorrespondence": - """Creates a `CustomerNumberCorrespondence` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "CustomerNumberCorrespondence": + """Create a `CustomerNumberCorrespondence` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with customer number correspondence data. @@ -832,8 +872,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "CustomerNumberCorrespondence": telecommunication_address_bag=telecoms, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `CustomerNumberCorrespondence` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `CustomerNumberCorrespondence` instance to a dictionary. Omits keys with None values or empty lists. @@ -864,20 +904,18 @@ class RecordAttorney: Contains customer number correspondence data, power of attorney information, and listed attorneys. Attributes: - customer_number_correspondence_data: List of `CustomerNumberCorrespondence` objects. + customer_number_correspondence_data: `CustomerNumberCorrespondence` object with customer number details. power_of_attorney_bag: List of `Attorney` objects named in a power of attorney. attorney_bag: List of `Attorney` objects listed as attorneys of record. """ - customer_number_correspondence_data: List[CustomerNumberCorrespondence] = field( - default_factory=list - ) - power_of_attorney_bag: List[Attorney] = field(default_factory=list) - attorney_bag: List[Attorney] = field(default_factory=list) + customer_number_correspondence_data: CustomerNumberCorrespondence | None = None + power_of_attorney_bag: list[Attorney] = field(default_factory=list) + attorney_bag: list[Attorney] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "RecordAttorney": - """Creates a `RecordAttorney` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "RecordAttorney": + """Create a `RecordAttorney` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with record attorney data. @@ -885,11 +923,12 @@ def from_dict(cls, data: Dict[str, Any]) -> "RecordAttorney": Returns: RecordAttorney: An instance of `RecordAttorney`. """ - cust_corr = [ - CustomerNumberCorrespondence.from_dict(c) - for c in data.get("customerNumberCorrespondenceData", []) - if isinstance(c, dict) - ] + cust_corr_data = data.get("customerNumberCorrespondenceData") + cust_corr = ( + CustomerNumberCorrespondence.from_dict(cust_corr_data) + if isinstance(cust_corr_data, dict) + else None + ) poa_bag = [ Attorney.from_dict(a) for a in data.get("powerOfAttorneyBag", []) @@ -906,43 +945,41 @@ def from_dict(cls, data: Dict[str, Any]) -> "RecordAttorney": attorney_bag=att_bag, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `RecordAttorney` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `RecordAttorney` instance to a dictionary. - Omits keys with None values or empty lists. + Omits keys with None values. Includes empty lists to match API behavior. Returns: Dict[str, Any]: Dictionary representation. """ d = { - "customerNumberCorrespondenceData": [ - c.to_dict() for c in self.customer_number_correspondence_data - ], + "customerNumberCorrespondenceData": ( + self.customer_number_correspondence_data.to_dict() + if self.customer_number_correspondence_data + else None + ), "powerOfAttorneyBag": [p.to_dict() for p in self.power_of_attorney_bag], "attorneyBag": [a.to_dict() for a in self.attorney_bag], } - return { - k: v - for k, v in d.items() - if v is not None and (not isinstance(v, list) or v) - } + return {k: v for k, v in d.items() if v is not None} @dataclass(frozen=True) class Assignor: - """Represents an assignor in a patent assignment. + """Represent an assignor in a patent assignment. Attributes: assignor_name: The name of the assigning party. execution_date: The date the assignment was executed. """ - assignor_name: Optional[str] = None - execution_date: Optional[date] = None + assignor_name: str | None = None + execution_date: date | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Assignor": - """Creates an `Assignor` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Assignor": + """Create an `Assignor` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with assignor data. @@ -955,8 +992,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "Assignor": execution_date=parse_to_date(data.get("executionDate")), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Assignor` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Assignor` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. @@ -969,19 +1006,19 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Assignee: - """Represents an assignee in a patent assignment. + """Represent an assignee in a patent assignment. Attributes: assignee_name_text: The name of the party receiving the assignment. assignee_address: The `Address` of the assignee. """ - assignee_name_text: Optional[str] = None - assignee_address: Optional[Address] = None + assignee_name_text: str | None = None + assignee_address: Address | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Assignee": - """Creates an `Assignee` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Assignee": + """Create an `Assignee` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with assignee data. @@ -995,8 +1032,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "Assignee": assignee_name_text=data.get("assigneeNameText"), assignee_address=addr ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Assignee` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Assignee` instance to a dictionary. Omits keys with None values. @@ -1014,7 +1051,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class Assignment: - """Represents a patent assignment, detailing the transfer of rights. + """Represent a patent assignment, detailing the transfer of rights. Includes information about the reel and frame, document location, dates, conveyance text, and bags of assignors, assignees, correspondence address, and domestic representative. @@ -1037,25 +1074,25 @@ class Assignment: domestic_representative: `Address` object for the domestic representative. """ - reel_number: Optional[int] = None - frame_number: Optional[int] = None - reel_and_frame_number: Optional[str] = None - page_total_quantity: Optional[int] = None - assignment_document_location_uri: Optional[str] = None - assignment_received_date: Optional[date] = None - assignment_recorded_date: Optional[date] = None - assignment_mailed_date: Optional[date] = None - conveyance_text: Optional[str] = None - image_available_status_code: Optional[bool] = None - attorney_docket_number: Optional[str] = None - assignor_bag: List[Assignor] = field(default_factory=list) - assignee_bag: List[Assignee] = field(default_factory=list) - correspondence_address: Optional[Address] = None - domestic_representative: Optional[Address] = None + reel_number: int | None = None + frame_number: int | None = None + reel_and_frame_number: str | None = None + page_total_quantity: int | None = None + assignment_document_location_uri: str | None = None + assignment_received_date: date | None = None + assignment_recorded_date: date | None = None + assignment_mailed_date: date | None = None + conveyance_text: str | None = None + image_available_status_code: bool | None = None + attorney_docket_number: str | None = None + assignor_bag: list[Assignor] = field(default_factory=list) + assignee_bag: list[Assignee] = field(default_factory=list) + correspondence_address: Address | None = None + domestic_representative: Address | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Assignment": - """Creates an `Assignment` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "Assignment": + """Create an `Assignment` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with assignment data. @@ -1106,13 +1143,13 @@ def from_dict(cls, data: Dict[str, Any]) -> "Assignment": domestic_representative=dom_rep, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `Assignment` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Assignment` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. """ - return { + _dict = { "reelNumber": self.reel_number, "frameNumber": self.frame_number, "reelAndFrameNumber": self.reel_and_frame_number, @@ -1137,11 +1174,13 @@ def to_dict(self) -> Dict[str, Any]: else None ), } + # Filter out None values to match API behavior + return {k: v for k, v in _dict.items() if v is not None} @dataclass(frozen=True) class ForeignPriority: - """Represents a foreign priority claim for a patent application. + """Represent a foreign priority claim for a patent application. Attributes: ip_office_name: The name of the intellectual property office of the priority application. @@ -1149,13 +1188,13 @@ class ForeignPriority: application_number_text: The application number of the priority application. """ - ip_office_name: Optional[str] = None - filing_date: Optional[date] = None - application_number_text: Optional[str] = None + ip_office_name: str | None = None + filing_date: date | None = None + application_number_text: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ForeignPriority": - """Creates a `ForeignPriority` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "ForeignPriority": + """Create a `ForeignPriority` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with foreign priority data. @@ -1169,8 +1208,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "ForeignPriority": application_number_text=data.get("applicationNumberText"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `ForeignPriority` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `ForeignPriority` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. @@ -1200,29 +1239,29 @@ class Continuity: claim_parentage_type_code_description_text: Description of the continuity claim type. """ - first_inventor_to_file_indicator: Optional[bool] = None - application_number_text: Optional[str] = None - filing_date: Optional[date] = None - status_code: Optional[int] = None - status_description_text: Optional[str] = None - patent_number: Optional[str] = None - claim_parentage_type_code: Optional[str] = None - claim_parentage_type_code_description_text: Optional[str] = None + first_inventor_to_file_indicator: bool | None = None + application_number_text: str | None = None + filing_date: date | None = None + status_code: int | None = None + status_description_text: str | None = None + patent_number: str | None = None + claim_parentage_type_code: str | None = None + claim_parentage_type_code_description_text: str | None = None @property - def is_aia(self) -> Optional[bool]: + def is_aia(self) -> bool | None: """Returns True if the application is AIA, False if pre-AIA, None if unknown.""" return self.first_inventor_to_file_indicator @property - def is_pre_aia(self) -> Optional[bool]: + def is_pre_aia(self) -> bool | None: """Returns True if the application is pre-AIA, False if AIA, None if unknown.""" if self.first_inventor_to_file_indicator is None: return None return not self.first_inventor_to_file_indicator - def to_dict(self) -> Dict[str, Any]: - """Converts the `Continuity` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `Continuity` instance to a dictionary. Omits attributes that are None and property-derived fields. Keys are converted to camelCase. @@ -1239,7 +1278,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class ParentContinuity(Continuity): - """Represents a parent application in a patent application's continuity chain. + """Represent a parent application in a patent application's continuity chain. Inherits from Continuity and adds specific fields for parent application details. @@ -1252,16 +1291,16 @@ class ParentContinuity(Continuity): child_application_number_text: Application number of the child (current) application. """ - parent_application_status_code: Optional[int] = None - parent_patent_number: Optional[str] = None - parent_application_status_description_text: Optional[str] = None - parent_application_filing_date: Optional[date] = None - parent_application_number_text: Optional[str] = None - child_application_number_text: Optional[str] = None + parent_application_status_code: int | None = None + parent_patent_number: str | None = None + parent_application_status_description_text: str | None = None + parent_application_filing_date: date | None = None + parent_application_number_text: str | None = None + child_application_number_text: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ParentContinuity": - """Creates a `ParentContinuity` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "ParentContinuity": + """Create a `ParentContinuity` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with parent continuity data. @@ -1291,15 +1330,16 @@ def from_dict(cls, data: Dict[str, Any]) -> "ParentContinuity": patent_number=data.get("parentPatentNumber"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `ParentContinuity` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `ParentContinuity` instance to a dictionary. Maps attributes to specific camelCase keys expected by the API for parent continuity. + Filters out None values to match the API response structure. Returns: Dict[str, Any]: Dictionary representation. """ - return { + _dict = { "firstInventorToFileIndicator": self.first_inventor_to_file_indicator, "parentApplicationStatusCode": self.parent_application_status_code, "parentPatentNumber": self.parent_patent_number, @@ -1312,11 +1352,12 @@ def to_dict(self) -> Dict[str, Any]: "claimParentageTypeCode": self.claim_parentage_type_code, "claimParentageTypeCodeDescriptionText": self.claim_parentage_type_code_description_text, } + return {k: v for k, v in _dict.items() if v is not None} @dataclass(frozen=True) class ChildContinuity(Continuity): - """Represents a child application in a patent application's continuity chain. + """Represent a child application in a patent application's continuity chain. Inherits from Continuity and adds specific fields for child application details. @@ -1329,16 +1370,16 @@ class ChildContinuity(Continuity): child_patent_number: Patent number of the child application, if granted. """ - child_application_status_code: Optional[int] = None - parent_application_number_text: Optional[str] = None - child_application_number_text: Optional[str] = None - child_application_status_description_text: Optional[str] = None - child_application_filing_date: Optional[date] = None - child_patent_number: Optional[str] = None + child_application_status_code: int | None = None + parent_application_number_text: str | None = None + child_application_number_text: str | None = None + child_application_status_description_text: str | None = None + child_application_filing_date: date | None = None + child_patent_number: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ChildContinuity": - """Creates a `ChildContinuity` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "ChildContinuity": + """Create a `ChildContinuity` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with child continuity data. @@ -1368,15 +1409,16 @@ def from_dict(cls, data: Dict[str, Any]) -> "ChildContinuity": patent_number=data.get("childPatentNumber"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `ChildContinuity` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `ChildContinuity` instance to a dictionary. Maps attributes to specific camelCase keys expected by the API for child continuity. + Filters out None values to match the API response structure. Returns: Dict[str, Any]: Dictionary representation. """ - return { + _dict = { "childApplicationStatusCode": self.child_application_status_code, "parentApplicationNumberText": self.parent_application_number_text, "childApplicationNumberText": self.child_application_number_text, @@ -1389,11 +1431,12 @@ def to_dict(self) -> Dict[str, Any]: "claimParentageTypeCode": self.claim_parentage_type_code, "claimParentageTypeCodeDescriptionText": self.claim_parentage_type_code_description_text, } + return {k: v for k, v in _dict.items() if v is not None} @dataclass(frozen=True) class PatentTermAdjustmentHistoryData: - """Represents a single entry in the patent term adjustment (PTA) history for an application. + """Represent a single entry in the patent term adjustment (PTA) history for an application. Details specific events, dates, and day quantities affecting the patent term. @@ -1404,18 +1447,20 @@ class PatentTermAdjustmentHistoryData: event_sequence_number: Sequence number of this event in the PTA history. originating_event_sequence_number: Sequence number of an event that originated this event. pta_pte_code: Code indicating if the event relates to PTA or Patent Term Extension (PTE). + ip_office_day_delay_quantity: Number of days of IP office delay used in adjustment calculation for this event. """ - event_date: Optional[date] = None - applicant_day_delay_quantity: Optional[float] = None - event_description_text: Optional[str] = None - event_sequence_number: Optional[float] = None - originating_event_sequence_number: Optional[float] = None - pta_pte_code: Optional[str] = None + event_date: date | None = None + applicant_day_delay_quantity: float | None = None + event_description_text: str | None = None + event_sequence_number: float | None = None + originating_event_sequence_number: float | None = None + pta_pte_code: str | None = None + ip_office_day_delay_quantity: float | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentHistoryData": - """Creates a `PatentTermAdjustmentHistoryData` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "PatentTermAdjustmentHistoryData": + """Create a `PatentTermAdjustmentHistoryData` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with PTA history event data. @@ -1432,17 +1477,18 @@ def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentHistoryData": "originatingEventSequenceNumber" ), pta_pte_code=data.get("ptaPTECode"), + ip_office_day_delay_quantity=data.get("ipOfficeDayDelayQuantity"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `PatentTermAdjustmentHistoryData` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `PatentTermAdjustmentHistoryData` instance to a dictionary. Omits keys with None values. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. """ - final_dict: Dict[str, Any] = {} + final_dict: dict[str, Any] = {} if self.event_date is not None: final_dict["eventDate"] = serialize_date(self.event_date) if self.applicant_day_delay_quantity is not None: @@ -1457,6 +1503,8 @@ def to_dict(self) -> Dict[str, Any]: ) if self.pta_pte_code is not None: final_dict["ptaPTECode"] = self.pta_pte_code + if self.ip_office_day_delay_quantity is not None: + final_dict["ipOfficeDayDelayQuantity"] = self.ip_office_day_delay_quantity return final_dict @@ -1475,25 +1523,27 @@ class PatentTermAdjustmentData: c_delay_quantity: Number of days of 'C' delay. non_overlapping_day_quantity: Number of non-overlapping delay days. overlapping_day_quantity: Number of overlapping delay days. - ip_office_day_delay_quantity: Total days of delay attributable to the IP office. + non_overlapping_day_delay_quantity: Number of non-overlapping delay days specifically for delay calculation. + ip_office_adjustment_delay_quantity: Days of IP office delay used in adjustment calculation. patent_term_adjustment_history_data_bag: List of `PatentTermAdjustmentHistoryData` events. """ - a_delay_quantity: Optional[float] = None - adjustment_total_quantity: Optional[float] = None - applicant_day_delay_quantity: Optional[float] = None - b_delay_quantity: Optional[float] = None - c_delay_quantity: Optional[float] = None - non_overlapping_day_quantity: Optional[float] = None - overlapping_day_quantity: Optional[float] = None - ip_office_day_delay_quantity: Optional[float] = None - patent_term_adjustment_history_data_bag: List[PatentTermAdjustmentHistoryData] = ( + a_delay_quantity: float | None = None + adjustment_total_quantity: float | None = None + applicant_day_delay_quantity: float | None = None + b_delay_quantity: float | None = None + c_delay_quantity: float | None = None + non_overlapping_day_quantity: float | None = None + overlapping_day_quantity: float | None = None + non_overlapping_day_delay_quantity: float | None = None + ip_office_adjustment_delay_quantity: float | None = None + patent_term_adjustment_history_data_bag: list[PatentTermAdjustmentHistoryData] = ( field(default_factory=list) ) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentData": - """Creates a `PatentTermAdjustmentData` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "PatentTermAdjustmentData": + """Create a `PatentTermAdjustmentData` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with PTA data. @@ -1514,12 +1564,17 @@ def from_dict(cls, data: Dict[str, Any]) -> "PatentTermAdjustmentData": c_delay_quantity=data.get("cDelayQuantity"), non_overlapping_day_quantity=data.get("nonOverlappingDayQuantity"), overlapping_day_quantity=data.get("overlappingDayQuantity"), - ip_office_day_delay_quantity=data.get("ipOfficeDayDelayQuantity"), + non_overlapping_day_delay_quantity=data.get( + "nonOverlappingDayDelayQuantity" + ), + ip_office_adjustment_delay_quantity=data.get( + "ipOfficeAdjustmentDelayQuantity" + ), patent_term_adjustment_history_data_bag=history, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `PatentTermAdjustmentData` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `PatentTermAdjustmentData` instance to a dictionary. Omits keys with None values or empty lists, and converts field names to camelCase. @@ -1539,7 +1594,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class EventData: - """Represents a single event in the transaction history of a patent application. + """Represent a single event in the transaction history of a patent application. Attributes: event_code: A code identifying the type of event. @@ -1547,13 +1602,13 @@ class EventData: event_date: The date the event was recorded. """ - event_code: Optional[str] = None - event_description_text: Optional[str] = None - event_date: Optional[date] = None + event_code: str | None = None + event_description_text: str | None = None + event_date: date | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "EventData": - """Creates an `EventData` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "EventData": + """Create an `EventData` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with event data. @@ -1567,8 +1622,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "EventData": event_date=parse_to_date(data.get("eventDate")), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `EventData` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `EventData` instance to a dictionary. Omits keys with None values and converts field names to camelCase. @@ -1592,15 +1647,15 @@ class PrintedMetaData: xml_file_name: The name of the XML file within the ZIP archive. """ - zip_file_name: Optional[str] = None - product_identifier: Optional[str] = None - file_location_uri: Optional[str] = None - file_create_date_time: Optional[datetime] = None - xml_file_name: Optional[str] = None + zip_file_name: str | None = None + product_identifier: str | None = None + file_location_uri: str | None = None + file_create_date_time: datetime | None = None + xml_file_name: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PrintedMetaData": - """Creates a `PrintedMetaData` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "PrintedMetaData": + """Create a `PrintedMetaData` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with printed metadata. @@ -1616,15 +1671,15 @@ def from_dict(cls, data: Dict[str, Any]) -> "PrintedMetaData": xml_file_name=data.get("xmlFileName"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `PrintedMetaData` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `PrintedMetaData` instance to a dictionary. Omits keys with None values. Serializes datetime to ISO format with 'Z'. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. """ - final_dict: Dict[str, Any] = {} + final_dict: dict[str, Any] = {} if self.zip_file_name is not None: final_dict["zipFileName"] = self.zip_file_name if self.product_identifier is not None: @@ -1632,7 +1687,7 @@ def to_dict(self) -> Dict[str, Any]: if self.file_location_uri is not None: final_dict["fileLocationURI"] = self.file_location_uri if self.file_create_date_time is not None: - final_dict["fileCreateDateTime"] = serialize_datetime_as_iso( + final_dict["fileCreateDateTime"] = serialize_datetime_as_naive( self.file_create_date_time ) if self.xml_file_name is not None: @@ -1642,7 +1697,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class ApplicationMetaData: - """Represents the comprehensive metadata associated with a patent application. + """Represents the metadata associated with a patent application. This class holds a wide range of information including application status, dates (filing, grant, publication), applicant and inventor details, @@ -1688,51 +1743,51 @@ class ApplicationMetaData: raw_data: Raw JSON string of the data used to create this instance (for debugging). """ - national_stage_indicator: Optional[bool] = None - entity_status_data: Optional[EntityStatus] = None - publication_date_bag: List[date] = field(default_factory=list) - publication_sequence_number_bag: List[str] = field(default_factory=list) - publication_category_bag: List[str] = field(default_factory=list) - docket_number: Optional[str] = None - first_inventor_to_file_indicator: Optional[bool] = None - first_applicant_name: Optional[str] = None - first_inventor_name: Optional[str] = None - application_confirmation_number: Optional[int] = None - application_status_date: Optional[date] = None - application_status_description_text: Optional[str] = None - filing_date: Optional[date] = None - effective_filing_date: Optional[date] = None - grant_date: Optional[date] = None - group_art_unit_number: Optional[str] = None - application_type_code: Optional[str] = None - application_type_label_name: Optional[str] = None - application_type_category: Optional[str] = None - invention_title: Optional[str] = None - patent_number: Optional[str] = None - application_status_code: Optional[int] = None - earliest_publication_number: Optional[str] = None - earliest_publication_date: Optional[date] = None - pct_publication_number: Optional[str] = None - pct_publication_date: Optional[date] = None - international_registration_publication_date: Optional[date] = None - international_registration_number: Optional[str] = None - examiner_name_text: Optional[str] = None - class_field: Optional[str] = None - subclass: Optional[str] = None - uspc_symbol_text: Optional[str] = None - customer_number: Optional[int] = None - cpc_classification_bag: List[str] = field(default_factory=list) - applicant_bag: List[Applicant] = field(default_factory=list) - inventor_bag: List[Inventor] = field(default_factory=list) - raw_data: Optional[str] = field(default=None, compare=False) + national_stage_indicator: bool | None = None + entity_status_data: EntityStatus | None = None + publication_date_bag: list[date] = field(default_factory=list) + publication_sequence_number_bag: list[str] = field(default_factory=list) + publication_category_bag: list[str] = field(default_factory=list) + docket_number: str | None = None + first_inventor_to_file_indicator: bool | None = None + first_applicant_name: str | None = None + first_inventor_name: str | None = None + application_confirmation_number: int | None = None + application_status_date: date | None = None + application_status_description_text: str | None = None + filing_date: date | None = None + effective_filing_date: date | None = None + grant_date: date | None = None + group_art_unit_number: str | None = None + application_type_code: str | None = None + application_type_label_name: str | None = None + application_type_category: str | None = None + invention_title: str | None = None + patent_number: str | None = None + application_status_code: int | None = None + earliest_publication_number: str | None = None + earliest_publication_date: date | None = None + pct_publication_number: str | None = None + pct_publication_date: date | None = None + international_registration_publication_date: date | None = None + international_registration_number: str | None = None + examiner_name_text: str | None = None + class_field: str | None = None + subclass: str | None = None + uspc_symbol_text: str | None = None + customer_number: int | None = None + cpc_classification_bag: list[str] = field(default_factory=list) + applicant_bag: list[Applicant] = field(default_factory=list) + inventor_bag: list[Inventor] = field(default_factory=list) + raw_data: str | None = field(default=None, compare=False) @property - def is_aia(self) -> Optional[bool]: + def is_aia(self) -> bool | None: """Returns True if the application is AIA, False if pre-AIA, None if unknown.""" return self.first_inventor_to_file_indicator @property - def is_pre_aia(self) -> Optional[bool]: + def is_pre_aia(self) -> bool | None: """Returns True if the application is pre-AIA, False if AIA, None if unknown.""" if self.first_inventor_to_file_indicator is None: return None @@ -1740,9 +1795,9 @@ def is_pre_aia(self) -> Optional[bool]: @classmethod def from_dict( - cls, data: Dict[str, Any], include_raw_data: bool = False + cls, data: dict[str, Any], include_raw_data: bool = False ) -> "ApplicationMetaData": - """Creates an `ApplicationMetaData` instance from a dictionary. + """Create an `ApplicationMetaData` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with application metadata. @@ -1824,8 +1879,8 @@ def from_dict( raw_data=json.dumps(data) if include_raw_data else None, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `ApplicationMetaData` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `ApplicationMetaData` instance to a dictionary. Serializes attributes to camelCase keys suitable for API interaction or storage. Omits keys with None values or empty lists. Handles date and boolean serialization. @@ -1924,25 +1979,25 @@ class PatentFileWrapper: last_ingestion_date_time: Timestamp of when this data was last ingested by the API (UTC). """ - application_number_text: Optional[str] = None - application_meta_data: Optional[ApplicationMetaData] = None - correspondence_address_bag: List[Address] = field(default_factory=list) - assignment_bag: List[Assignment] = field(default_factory=list) - record_attorney: Optional[RecordAttorney] = None - foreign_priority_bag: List[ForeignPriority] = field(default_factory=list) - parent_continuity_bag: List[ParentContinuity] = field(default_factory=list) - child_continuity_bag: List[ChildContinuity] = field(default_factory=list) - patent_term_adjustment_data: Optional[PatentTermAdjustmentData] = None - event_data_bag: List[EventData] = field(default_factory=list) - pgpub_document_meta_data: Optional[PrintedMetaData] = None - grant_document_meta_data: Optional[PrintedMetaData] = None - last_ingestion_date_time: Optional[datetime] = None + application_number_text: str | None = None + application_meta_data: ApplicationMetaData | None = None + correspondence_address_bag: list[Address] = field(default_factory=list) + assignment_bag: list[Assignment] = field(default_factory=list) + record_attorney: RecordAttorney | None = None + foreign_priority_bag: list[ForeignPriority] = field(default_factory=list) + parent_continuity_bag: list[ParentContinuity] = field(default_factory=list) + child_continuity_bag: list[ChildContinuity] = field(default_factory=list) + patent_term_adjustment_data: PatentTermAdjustmentData | None = None + event_data_bag: list[EventData] = field(default_factory=list) + pgpub_document_meta_data: PrintedMetaData | None = None + grant_document_meta_data: PrintedMetaData | None = None + last_ingestion_date_time: datetime | None = None @classmethod def from_dict( - cls, data: Dict[str, Any], include_raw_data: bool = False + cls, data: dict[str, Any], include_raw_data: bool = False ) -> "PatentFileWrapper": - """Creates a `PatentFileWrapper` instance from a dictionary. + """Create a `PatentFileWrapper` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with patent file wrapper data. @@ -2029,8 +2084,8 @@ def from_dict( ), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `PatentFileWrapper` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `PatentFileWrapper` instance to a dictionary. Omits keys with None values or empty lists. Serializes nested objects. @@ -2070,8 +2125,10 @@ def to_dict(self) -> Dict[str, Any]: if self.grant_document_meta_data else None ), - "lastIngestionDateTime": serialize_datetime_as_iso( - self.last_ingestion_date_time + "lastIngestionDateTime": ( + serialize_datetime_as_naive(self.last_ingestion_date_time) + if self.last_ingestion_date_time + else None ), } return { @@ -2091,18 +2148,20 @@ class PatentDataResponse: Attributes: count: The total number of patent applications found matching the query. patent_file_wrapper_data_bag: A list of `PatentFileWrapper` objects. + request_identifier: An identifier for the API request, if provided. raw_data: Optional raw JSON data from the API response (for debugging). """ count: int - patent_file_wrapper_data_bag: List[PatentFileWrapper] = field(default_factory=list) - raw_data: Optional[str] = field(default=None, compare=False, repr=False) + patent_file_wrapper_data_bag: list[PatentFileWrapper] = field(default_factory=list) + request_identifier: str | None = None + raw_data: str | None = field(default=None, compare=False, repr=False) @classmethod def from_dict( - cls, data: Dict[str, Any], include_raw_data: bool = False + cls, data: dict[str, Any], include_raw_data: bool = False ) -> "PatentDataResponse": - """Creates a `PatentDataResponse` instance from a dictionary. + """Create a `PatentDataResponse` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with API response data. @@ -2119,24 +2178,31 @@ def from_dict( return cls( count=data.get("count", 0), patent_file_wrapper_data_bag=wrappers, + request_identifier=data.get("requestIdentifier"), raw_data=json.dumps(data) if include_raw_data else None, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `PatentDataResponse` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `PatentDataResponse` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation. """ - return { + _dict = { "count": self.count, "patentFileWrapperDataBag": [ w.to_dict() for w in self.patent_file_wrapper_data_bag ], + "requestIdentifier": self.request_identifier, + } + return { + k: v + for k, v in _dict.items() + if v is not None and (not isinstance(v, list) or v) } def to_csv(self) -> str: - """Converts the patent data in this response to a CSV formatted string. + """Convert the patent data in this response to a CSV formatted string. The CSV will contain key metadata fields for each application, such as invention title, application number, filing date, status, etc. @@ -2192,23 +2258,23 @@ def to_csv(self) -> str: @dataclass(frozen=True) class StatusCode: - """Represents a USPTO application status code and its textual description. + """Represent a USPTO application status code and its textual description. Attributes: code: The numeric status code. description: The textual description of the status code. """ - code: Optional[int] = None - description: Optional[str] = None + code: int | None = None + description: str | None = None def __str__(self) -> str: - """Returns a user-friendly string representation of the status code.""" + """Return a user-friendly string representation of the status code.""" return f"{self.code}: {self.description}" @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "StatusCode": - """Creates a `StatusCode` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "StatusCode": + """Create a `StatusCode` instance from a dictionary. Handles two possible key sets from the API for status information. @@ -2229,8 +2295,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "StatusCode": description=data.get("applicationStatusDescriptionText"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `StatusCode` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `StatusCode` instance to a dictionary. Uses keys "applicationStatusCode" and "applicationStatusDescriptionText" for consistency with some API response parts. @@ -2254,8 +2320,8 @@ class StatusCodeCollection: status_codes (tuple[StatusCode, ...]): An immutable tuple of `StatusCode` objects. """ - def __init__(self, status_codes: List[StatusCode]): - """Initializes the StatusCodeCollection with a list of status codes. + def __init__(self, status_codes: list[StatusCode]): + """Initialize a StatusCodeCollection with a list of status codes. Args: status_codes (List[StatusCode]): A list of `StatusCode` instances. @@ -2263,18 +2329,46 @@ def __init__(self, status_codes: List[StatusCode]): self._status_codes: tuple[StatusCode, ...] = tuple(status_codes) def __iter__(self) -> Iterator[StatusCode]: + """Return an iterator over the status codes in the collection. + + Returns: + Iterator[StatusCode]: An iterator of StatusCode instances. + """ return iter(self._status_codes) def __len__(self) -> int: + """Return the number of status codes in the collection. + + Returns: + int: The count of status codes. + """ return len(self._status_codes) def __getitem__(self, index: int) -> StatusCode: + """Return the status code at the specified index. + + Args: + index: The index of the status code to retrieve. + + Returns: + StatusCode: The status code at the specified index. + """ return self._status_codes[index] def __str__(self) -> str: + """Return a human-readable string representation of the StatusCodeCollection. + + Returns: + str: A description of the collection size. + """ return f"StatusCodeCollection with {len(self)} status codes." def __repr__(self) -> str: + """Return a developer-friendly string representation of the StatusCodeCollection. + + Returns: + str: A string showing the collection size and sample status codes. + """ if not self._status_codes: return "StatusCodeCollection(empty)" @@ -2285,8 +2379,8 @@ def __repr__(self) -> str: first_codes = ", ".join(str(s.code) for s in self._status_codes[:3]) return f"StatusCodeCollection({len(self)} status codes: {first_codes}, ...)" - def find_by_code(self, code_to_find: int) -> Optional[StatusCode]: - """Finds a status code by its numeric code. + def find_by_code(self, code_to_find: int) -> StatusCode | None: + """Find a status code by its numeric code. Args: code_to_find (int): The numeric status code to search for. @@ -2300,7 +2394,7 @@ def find_by_code(self, code_to_find: int) -> Optional[StatusCode]: return None def search_by_description(self, text: str) -> "StatusCodeCollection": - """Searches for status codes by a case-insensitive text match in their description. + """Search for status codes by a case-insensitive text match in their description. Args: text (str): The text to search for within status code descriptions. @@ -2315,8 +2409,8 @@ def search_by_description(self, text: str) -> "StatusCodeCollection": ] return StatusCodeCollection(status_codes=matching) - def to_dict(self) -> List[Dict[str, Any]]: - """Converts the collection of status codes to a list of dictionaries. + def to_dict(self) -> list[dict[str, Any]]: + """Convert the collection of status codes to a list of dictionaries. Returns: List[Dict[str, Any]]: A list where each item is the dictionary @@ -2337,11 +2431,11 @@ class StatusCodeSearchResponse: count: int status_code_bag: StatusCodeCollection - request_identifier: Optional[str] = None + request_identifier: str | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "StatusCodeSearchResponse": - """Creates a `StatusCodeSearchResponse` instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "StatusCodeSearchResponse": + """Create a `StatusCodeSearchResponse` instance from a dictionary. Args: data (Dict[str, Any]): Dictionary with API response data for status codes. @@ -2362,8 +2456,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "StatusCodeSearchResponse": request_identifier=data.get("requestIdentifier"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `StatusCodeSearchResponse` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `StatusCodeSearchResponse` instance to a dictionary. Omits keys with None values or empty lists. @@ -2394,12 +2488,12 @@ class ApplicationContinuityData: child_continuity_bag: List of `ChildContinuity` objects. """ - parent_continuity_bag: List[ParentContinuity] = field(default_factory=list) - child_continuity_bag: List[ChildContinuity] = field(default_factory=list) + parent_continuity_bag: list[ParentContinuity] = field(default_factory=list) + child_continuity_bag: list[ChildContinuity] = field(default_factory=list) @classmethod def from_wrapper(cls, wrapper: PatentFileWrapper) -> "ApplicationContinuityData": - """Creates an `ApplicationContinuityData` instance from a `PatentFileWrapper`. + """Create an `ApplicationContinuityData` instance from a `PatentFileWrapper`. Extracts parent and child continuity bags from the wrapper. @@ -2416,8 +2510,8 @@ def from_wrapper(cls, wrapper: PatentFileWrapper) -> "ApplicationContinuityData" def to_dict( self, - ) -> Dict[str, Any]: - """Converts the `ApplicationContinuityData` instance to a dictionary. + ) -> dict[str, Any]: + """Convert the `ApplicationContinuityData` instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with "parentContinuityBag" @@ -2431,20 +2525,22 @@ def to_dict( @dataclass(frozen=True) class PrintedPublication: - """Holds metadata for associated documents like Pre-Grant Publications (PGPUB) - and Grant documents for a specific patent application. + """Represent metadata for associated documents such as PGPUB and Grant publications. + + Note: + PGPUB refers to a Pre-Grant Publication. Attributes: pgpub_document_meta_data: `PrintedMetaData` for the Pre-Grant Publication, if any. grant_document_meta_data: `PrintedMetaData` for the Grant document, if any. """ - pgpub_document_meta_data: Optional[PrintedMetaData] = None - grant_document_meta_data: Optional[PrintedMetaData] = None + pgpub_document_meta_data: PrintedMetaData | None = None + grant_document_meta_data: PrintedMetaData | None = None @classmethod def from_wrapper(cls, wrapper: PatentFileWrapper) -> "PrintedPublication": - """Creates a `PrintedPublication` instance from a `PatentFileWrapper`. + """Create a `PrintedPublication` instance from a `PatentFileWrapper`. Extracts PGPUB and Grant document metadata from the wrapper. @@ -2459,8 +2555,8 @@ def from_wrapper(cls, wrapper: PatentFileWrapper) -> "PrintedPublication": grant_document_meta_data=wrapper.grant_document_meta_data, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the `PrintedPublication` instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the `PrintedPublication` instance to a dictionary. Omits keys if their corresponding metadata is None. diff --git a/src/pyUSPTO/models/petition_decisions.py b/src/pyUSPTO/models/petition_decisions.py index 1a22a16..c15365c 100644 --- a/src/pyUSPTO/models/petition_decisions.py +++ b/src/pyUSPTO/models/petition_decisions.py @@ -1,5 +1,4 @@ -""" -models.petition_decisions - Data models for USPTO Final Petition Decisions API +"""models.petition_decisions - Data models for USPTO Final Petition Decisions API. This module provides data models, primarily using frozen dataclasses, for representing responses from the USPTO Final Petition Decisions API. These models @@ -10,14 +9,14 @@ from dataclasses import dataclass, field from datetime import date, datetime from enum import Enum -from typing import Any, Dict, List, Optional +from typing import Any # Import parsing utilities from models utils module from pyUSPTO.models.utils import ( parse_to_date, parse_to_datetime_utc, serialize_date, - serialize_datetime_as_iso, + serialize_datetime_as_naive, ) @@ -69,7 +68,7 @@ def _missing_(cls, value: Any) -> "DocumentDirectionCategory": # --- Data Models --- @dataclass(frozen=True) class DocumentDownloadOption: - """Represents a download option for a petition decision document. + """Represent a download option for a petition decision document. Attributes: mime_type_identifier: The document format type (e.g., "PDF", "XML", "MS_WORD"). @@ -77,13 +76,13 @@ class DocumentDownloadOption: page_total_quantity: The total number of pages in the document (if applicable). """ - mime_type_identifier: Optional[str] = None - download_url: Optional[str] = None - page_total_quantity: Optional[int] = None + mime_type_identifier: str | None = None + download_url: str | None = None + page_total_quantity: int | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "DocumentDownloadOption": - """Creates a DocumentDownloadOption instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "DocumentDownloadOption": + """Create a DocumentDownloadOption instance from a dictionary. Args: data: Dictionary containing download option data from API response. @@ -97,8 +96,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "DocumentDownloadOption": page_total_quantity=data.get("pageTotalQuantity"), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the DocumentDownloadOption instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the DocumentDownloadOption instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. @@ -113,7 +112,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class PetitionDecisionDocument: - """Represents a document associated with a petition decision. + """Represent a document associated with a petition decision. Attributes: application_number_text: The application number associated with the document. @@ -121,21 +120,21 @@ class PetitionDecisionDocument: document_identifier: A unique identifier for the document. document_code: The code identifying the document type. document_code_description_text: Description of the document code. - document_direction_category: Whether the document is INCOMING or OUTGOING. + direction_category: Whether the document is INCOMING or OUTGOING. download_option_bag: List of available download options for the document. """ - application_number_text: Optional[str] = None - official_date: Optional[datetime] = None - document_identifier: Optional[str] = None - document_code: Optional[str] = None - document_code_description_text: Optional[str] = None - document_direction_category: Optional[str] = None - download_option_bag: List[DocumentDownloadOption] = field(default_factory=list) + application_number_text: str | None = None + official_date: datetime | None = None + document_identifier: str | None = None + document_code: str | None = None + document_code_description_text: str | None = None + direction_category: str | None = None + download_option_bag: list[DocumentDownloadOption] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecisionDocument": - """Creates a PetitionDecisionDocument instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "PetitionDecisionDocument": + """Create a PetitionDecisionDocument instance from a dictionary. Args: data: Dictionary containing document data from API response. @@ -161,23 +160,27 @@ def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecisionDocument": document_identifier=data.get("documentIdentifier"), document_code=data.get("documentCode"), document_code_description_text=data.get("documentCodeDescriptionText"), - document_direction_category=data.get("documentDirectionCategory"), + direction_category=data.get("directionCategory"), download_option_bag=download_options, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the PetitionDecisionDocument instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the PetitionDecisionDocument instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. """ d = { "applicationNumberText": self.application_number_text, - "officialDate": serialize_datetime_as_iso(self.official_date), + "officialDate": ( + serialize_datetime_as_naive(self.official_date) + if self.official_date + else None + ), "documentIdentifier": self.document_identifier, "documentCode": self.document_code, "documentCodeDescriptionText": self.document_code_description_text, - "documentDirectionCategory": self.document_direction_category, + "directionCategory": self.direction_category, "downloadOptionBag": [opt.to_dict() for opt in self.download_option_bag], } return { @@ -189,7 +192,7 @@ def to_dict(self) -> Dict[str, Any]: @dataclass(frozen=True) class PetitionDecision: - """Represents a final petition decision record. + """Represent a final petition decision record. This is the main data model representing a single petition decision from the USPTO Final Petition Decisions API. It contains comprehensive information about @@ -226,37 +229,37 @@ class PetitionDecision: last_ingestion_datetime: The last time the record was ingested/updated. """ - petition_decision_record_identifier: Optional[str] = None - application_number_text: Optional[str] = None - patent_number: Optional[str] = None - decision_date: Optional[date] = None - petition_mail_date: Optional[date] = None - decision_petition_type_code: Optional[int] = None - decision_type_code: Optional[str] = None - decision_type_code_description_text: Optional[str] = None - final_deciding_office_name: Optional[str] = None - first_applicant_name: Optional[str] = None - first_inventor_name: Optional[str] = None - invention_title: Optional[str] = None - first_inventor_to_file_indicator: Optional[bool] = None - business_entity_status_category: Optional[str] = None - customer_number: Optional[int] = None - group_art_unit_number: Optional[str] = None - technology_center: Optional[str] = None - prosecution_status_code: Optional[str] = None - prosecution_status_code_description_text: Optional[str] = None - action_taken_by_court_name: Optional[str] = None - court_action_indicator: Optional[bool] = None - inventor_bag: List[str] = field(default_factory=list) - petition_issue_considered_text_bag: List[str] = field(default_factory=list) - statute_bag: List[str] = field(default_factory=list) - rule_bag: List[str] = field(default_factory=list) - document_bag: List[PetitionDecisionDocument] = field(default_factory=list) - last_ingestion_datetime: Optional[datetime] = None + petition_decision_record_identifier: str + application_number_text: str | None = None + patent_number: str | None = None + decision_date: date | None = None + petition_mail_date: date | None = None + decision_petition_type_code: int | None = None + decision_type_code: str | None = None + decision_type_code_description_text: str | None = None + final_deciding_office_name: str | None = None + first_applicant_name: str | None = None + first_inventor_name: str | None = None + invention_title: str | None = None + first_inventor_to_file_indicator: bool | None = None + business_entity_status_category: str | None = None + customer_number: int | None = None + group_art_unit_number: str | None = None + technology_center: str | None = None + prosecution_status_code: str | None = None + prosecution_status_code_description_text: str | None = None + action_taken_by_court_name: str | None = None + court_action_indicator: bool | None = None + inventor_bag: list[str] = field(default_factory=list) + petition_issue_considered_text_bag: list[str] = field(default_factory=list) + statute_bag: list[str] = field(default_factory=list) + rule_bag: list[str] = field(default_factory=list) + document_bag: list[PetitionDecisionDocument] = field(default_factory=list) + last_ingestion_datetime: datetime | None = None @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecision": - """Creates a PetitionDecision instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "PetitionDecision": + """Create a PetitionDecision instance from a dictionary. Args: data: Dictionary containing petition decision data from API response. @@ -298,7 +301,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecision": return cls( petition_decision_record_identifier=data.get( - "petitionDecisionRecordIdentifier" + "petitionDecisionRecordIdentifier", "" ), application_number_text=data.get("applicationNumberText"), patent_number=data.get("patentNumber"), @@ -334,8 +337,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecision": ), ) - def to_dict(self) -> Dict[str, Any]: - """Converts the PetitionDecision instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the PetitionDecision instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. @@ -366,8 +369,10 @@ def to_dict(self) -> Dict[str, Any]: "statuteBag": self.statute_bag, "ruleBag": self.rule_bag, "documentBag": [doc.to_dict() for doc in self.document_bag], - "lastIngestionDateTime": serialize_datetime_as_iso( - self.last_ingestion_datetime + "lastIngestionDateTime": ( + serialize_datetime_as_naive(self.last_ingestion_datetime) + if self.last_ingestion_datetime + else None ), } return { @@ -391,16 +396,16 @@ class PetitionDecisionResponse: raw_data: Optional raw JSON data from the API response (for debugging). """ - count: Optional[int] = None - request_identifier: Optional[str] = None - petition_decision_data_bag: List[PetitionDecision] = field(default_factory=list) - raw_data: Optional[str] = field(default=None, compare=False, repr=False) + count: int = 0 + request_identifier: str | None = None + petition_decision_data_bag: list[PetitionDecision] = field(default_factory=list) + raw_data: str | None = field(default=None, compare=False, repr=False) @classmethod def from_dict( - cls, data: Dict[str, Any], include_raw_data: bool = False + cls, data: dict[str, Any], include_raw_data: bool = False ) -> "PetitionDecisionResponse": - """Creates a PetitionDecisionResponse instance from a dictionary. + """Create a PetitionDecisionResponse instance from a dictionary. Args: data: Dictionary containing API response data. @@ -422,14 +427,14 @@ def from_dict( ) return cls( - count=data.get("count"), + count=data.get("count", 0), request_identifier=data.get("requestIdentifier"), petition_decision_data_bag=decisions, raw_data=json.dumps(data) if include_raw_data else None, ) - def to_dict(self) -> Dict[str, Any]: - """Converts the PetitionDecisionResponse instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the PetitionDecisionResponse instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. @@ -460,11 +465,11 @@ class PetitionDecisionDownloadResponse: petition_decision_data: List of petition decision records. """ - petition_decision_data: List[PetitionDecision] = field(default_factory=list) + petition_decision_data: list[PetitionDecision] = field(default_factory=list) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecisionDownloadResponse": - """Creates a PetitionDecisionDownloadResponse instance from a dictionary. + def from_dict(cls, data: dict[str, Any]) -> "PetitionDecisionDownloadResponse": + """Create a PetitionDecisionDownloadResponse instance from a dictionary. Args: data: Dictionary containing download API response data. @@ -486,8 +491,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "PetitionDecisionDownloadResponse": return cls(petition_decision_data=decisions) - def to_dict(self) -> Dict[str, Any]: - """Converts the PetitionDecisionDownloadResponse instance to a dictionary. + def to_dict(self) -> dict[str, Any]: + """Convert the PetitionDecisionDownloadResponse instance to a dictionary. Returns: Dict[str, Any]: Dictionary representation with camelCase keys. diff --git a/src/pyUSPTO/models/ptab.py b/src/pyUSPTO/models/ptab.py new file mode 100644 index 0000000..4b557aa --- /dev/null +++ b/src/pyUSPTO/models/ptab.py @@ -0,0 +1,1711 @@ +"""models.ptab - Data models for USPTO PTAB (Patent Trial and Appeal Board) APIs. + +This module provides data models, primarily using frozen dataclasses, for +representing responses from the USPTO PTAB APIs. These models cover: +- Patent trial proceedings (IPR, PGR, CBM, DER) +- Trial documents and decisions +- Appeal decisions +- Interference decisions +""" + +from dataclasses import asdict, dataclass, field +from datetime import date, datetime +from typing import Any + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + +# Import parsing utilities from models utils module +from pyUSPTO.models.utils import ( + parse_to_date, + parse_to_datetime_utc, + serialize_date, + serialize_datetime_as_naive, + to_camel_case, +) + + +@dataclass(frozen=True) +class PartyData: + """Base class for all party data models across PTAB endpoints. + + Attributes: + application_number_text: Application number. + counsel_name: Name of counsel. + grant_date: Patent grant date. + group_art_unit_number: Art unit number. + inventor_name: Name of inventor. + patent_number: Patent number. + technology_center_number: Technology center number. + real_party_in_interest_name: Real party in interest name. + patent_owner_name: Patent owner name. + publication_date: Publication date (if applicable). + publication_number: Publication number (if applicable). + """ + + application_number_text: str | None = None + counsel_name: str | None = None + grant_date: date | None = None + group_art_unit_number: str | None = None + inventor_name: str | None = None + real_party_in_interest_name: str | None = None + patent_number: str | None = None + patent_owner_name: str | None = None + technology_center_number: str | None = None + publication_date: date | None = None + publication_number: str | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any], include_raw_data: bool = False) -> Self: + """Create a PartyData instance from a dictionary. + + Args: + data: Dictionary containing party data from API response. + include_raw_data: Ignored for this model. + + Returns: + PartyData: A populated PartyData instance. + """ + return cls( + application_number_text=data.get("applicationNumberText"), + counsel_name=data.get("counselName"), + grant_date=parse_to_date(data.get("grantDate")), + group_art_unit_number=data.get("groupArtUnitNumber"), + inventor_name=data.get("inventorName"), + real_party_in_interest_name=data.get("realPartyInInterestName"), + patent_number=data.get("patentNumber"), + patent_owner_name=data.get("patentOwnerName"), + technology_center_number=data.get("technologyCenterNumber"), + publication_date=parse_to_date(data.get("publicationDate")), + publication_number=data.get("publicationNumber"), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PartyData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + for k, v in asdict(self).items(): + if v is not None: + if isinstance(v, date): + result[to_camel_case(k)] = serialize_date(v) + else: + result[to_camel_case(k)] = v + return result + + +# ============================================================================ +# TRIAL PROCEEDINGS MODELS +# ============================================================================ + + +@dataclass(frozen=True) +class TrialMetaData: + """Trial metadata including status, dates, and download URI. + + Attributes: + petition_filing_date: Date the petition was filed. + accorded_filing_date: The filing date accorded to the petition. + trial_last_modified_date_time: Last modification timestamp. + trial_last_modified_date: Last modification date. + trial_status_category: Status of the trial (e.g., "Institution Denied", "Instituted"). + trial_type_code: Type of trial (IPR, PGR, CBM, DER). + file_download_uri: URI to download ZIP of all trial documents. + termination_date: Date the trial was terminated. + latest_decision_date: Date of the most recent decision. + institution_decision_date: Date of the institution decision. + """ + + petition_filing_date: date | None = None + accorded_filing_date: date | None = None + trial_last_modified_date_time: datetime | None = None + trial_last_modified_date: date | None = None + trial_status_category: str | None = None + trial_type_code: str | None = None + file_download_uri: str | None = None + termination_date: date | None = None + latest_decision_date: date | None = None + institution_decision_date: date | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "TrialMetaData": + """Create a TrialMetaData instance from a dictionary. + + Args: + data: Dictionary containing trial metadata from API response. + include_raw_data: Ignored for this model (no raw_data field). + + Returns: + TrialMetaData: An instance of TrialMetaData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + petition_filing_date=parse_to_date(data.get("petitionFilingDate")), + accorded_filing_date=parse_to_date(data.get("accordedFilingDate")), + trial_last_modified_date_time=parse_to_datetime_utc( + data.get("trialLastModifiedDateTime") + ), + trial_last_modified_date=parse_to_date(data.get("trialLastModifiedDate")), + trial_status_category=data.get("trialStatusCategory"), + trial_type_code=data.get("trialTypeCode"), + file_download_uri=file_download_uri, + termination_date=parse_to_date(data.get("terminationDate")), + latest_decision_date=parse_to_date(data.get("latestDecisionDate")), + institution_decision_date=parse_to_date( + data.get("institutionDecisionDate") + ), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the TrialMetaData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.petition_filing_date is not None: + result["petitionFilingDate"] = serialize_date(self.petition_filing_date) + if self.accorded_filing_date is not None: + result["accordedFilingDate"] = serialize_date(self.accorded_filing_date) + if self.trial_last_modified_date_time is not None: + result["trialLastModifiedDateTime"] = serialize_datetime_as_naive( + self.trial_last_modified_date_time + ) + if self.trial_last_modified_date is not None: + result["trialLastModifiedDate"] = serialize_date( + self.trial_last_modified_date + ) + if self.trial_status_category is not None: + result["trialStatusCategory"] = self.trial_status_category + if self.trial_type_code is not None: + result["trialTypeCode"] = self.trial_type_code + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + if self.termination_date is not None: + result["terminationDate"] = serialize_date(self.termination_date) + if self.latest_decision_date is not None: + result["latestDecisionDate"] = serialize_date(self.latest_decision_date) + if self.institution_decision_date is not None: + result["institutionDecisionDate"] = serialize_date( + self.institution_decision_date + ) + + return result + + +@dataclass(frozen=True) +class PatentOwnerData(PartyData): + """Party data for a patent owner in PTAB trial proceedings. + + Inherits all attributes from PartyData. Used in IPR, PGR, CBM, + and DER proceedings to represent the patent holder. + """ + + pass + + +@dataclass(frozen=True) +class RegularPetitionerData: + """Regular petitioner information. + + Attributes: + counsel_name: Name of counsel. + real_party_in_interest_name: Real party in interest name. + """ + + counsel_name: str | None = None + real_party_in_interest_name: str | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "RegularPetitionerData": + """Create a RegularPetitionerData instance from a dictionary. + + Args: + data: Dictionary containing petitioner data from API response. + include_raw_data: Ignored for this model. + + Returns: + RegularPetitionerData: An instance of RegularPetitionerData. + """ + return cls( + counsel_name=data.get("counselName"), + real_party_in_interest_name=data.get("realPartyInInterestName"), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the RegularPetitionerData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.counsel_name is not None: + result["counselName"] = self.counsel_name + if self.real_party_in_interest_name is not None: + result["realPartyInInterestName"] = self.real_party_in_interest_name + + return result + + +@dataclass(frozen=True) +class RespondentData(PartyData): + """Respondent party data in derivation proceedings. + + Inherits all attributes from PartyData. Used in DER proceedings + to represent the responding party. + """ + + pass + + +@dataclass(frozen=True) +class DerivationPetitionerData(PartyData): + """Derivation petitioner data in derivation proceedings. + + Inherits all attributes from PartyData. Used in DER proceedings + to represent the petitioning party claiming derivation. + """ + + pass + + +@dataclass(frozen=True) +class PTABTrialProceeding: + """Individual PTAB trial proceeding record. + + 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. + regular_petitioner_data: Regular petitioner information. + respondent_data: Respondent information. + derivation_petitioner_data: Derivation petitioner information. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + 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 + regular_petitioner_data: RegularPetitionerData | None = None + respondent_data: RespondentData | None = None + derivation_petitioner_data: DerivationPetitionerData | None = None + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialProceeding": + """Create a PTABTrialProceeding instance from a dictionary. + + Args: + data: Dictionary containing trial proceeding data from API response. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABTrialProceeding: An instance of PTABTrialProceeding. + """ + # Parse nested objects + trial_meta = data.get("trialMetaData") + trial_meta_data = TrialMetaData.from_dict(trial_meta) if trial_meta else None + + patent_owner = data.get("patentOwnerData") + patent_owner_data = ( + PatentOwnerData.from_dict(patent_owner) if patent_owner else None + ) + + reg_petitioner = data.get("regularPetitionerData") + regular_petitioner_data = ( + RegularPetitionerData.from_dict(reg_petitioner) if reg_petitioner else None + ) + + respondent = data.get("respondentData") + respondent_data = RespondentData.from_dict(respondent) if respondent else None + + deriv_petitioner = data.get("derivationPetitionerData") + derivation_petitioner_data = ( + DerivationPetitionerData.from_dict(deriv_petitioner) + if deriv_petitioner + else None + ) + + return cls( + trial_number=data.get("trialNumber"), + # trial_record_identifier=data.get("trialRecordIdentifier"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + trial_meta_data=trial_meta_data, + patent_owner_data=patent_owner_data, + regular_petitioner_data=regular_petitioner_data, + respondent_data=respondent_data, + derivation_petitioner_data=derivation_petitioner_data, + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABTrialProceeding instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: 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 + ) + if self.trial_meta_data is not None: + result["trialMetaData"] = self.trial_meta_data.to_dict() + if self.patent_owner_data is not None: + result["patentOwnerData"] = self.patent_owner_data.to_dict() + if self.regular_petitioner_data is not None: + result["regularPetitionerData"] = self.regular_petitioner_data.to_dict() + if self.respondent_data is not None: + result["respondentData"] = self.respondent_data.to_dict() + if self.derivation_petitioner_data is not None: + result["derivationPetitionerData"] = ( + self.derivation_petitioner_data.to_dict() + ) + + return result + + +@dataclass(frozen=True) +class PTABTrialProceedingResponse: + """Response container for PTAB trial proceedings search. + + Attributes: + count: Total number of matching results. + request_identifier: UUID for the API request. + patent_trial_proceeding_data_bag: List of trial proceedings. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + count: int = 0 + request_identifier: str | None = None + patent_trial_proceeding_data_bag: list[PTABTrialProceeding] = field( + default_factory=list + ) + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialProceedingResponse": + """Create a PTABTrialProceedingResponse instance from a dictionary. + + Args: + data: Dictionary containing response data from API. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABTrialProceedingResponse: An instance of PTABTrialProceedingResponse. + """ + proceedings_data = data.get("patentTrialProceedingDataBag", []) + proceedings = [ + PTABTrialProceeding.from_dict(item, include_raw_data=include_raw_data) + for item in proceedings_data + ] + + return cls( + count=data.get("count", 0), + request_identifier=data.get("requestIdentifier"), + patent_trial_proceeding_data_bag=proceedings, + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABTrialProceedingResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_trial_proceeding_data_bag is not None + and len(self.patent_trial_proceeding_data_bag) > 0 + ): + result["patentTrialProceedingDataBag"] = [ + proceeding.to_dict() + for proceeding in self.patent_trial_proceeding_data_bag + ] + + return result + + +@dataclass(frozen=True) +class TrialDocumentData: + """Metadata for a document in a PTAB trial. + + Attributes: + document_category: Category of the document. + document_filing_date: Filing date. + document_identifier: Unique ID. + document_name: Filename. + document_number: Document number in the proceeding. + document_size_quantity: Size in bytes. + document_ocr_text: OCR text content. + document_title_text: Title of the document. + document_type_description_text: Description of document type. + file_download_uri: URL to download the file. + filing_party_category: Who filed (e.g., "Petitioner"). + mime_type_identifier: MIME type (e.g., "application/pdf"). + document_status: Public status. + """ + + # document_category: Optional[str] = None # Removed: Documented but not in API. + document_filing_date: date | None = None + document_identifier: str | None = None + document_name: str | None = None + document_number: str | None = None + document_size_quantity: int | None = None + document_ocr_text: str | None = None + document_title_text: str | None = None + 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( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "TrialDocumentData": + """Create a TrialDocumentData instance from a dictionary. + + Args: + data: Dictionary containing document data from API response. + include_raw_data: Ignored for this model. + + Returns: + TrialDocumentData: An instance of TrialDocumentData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + # document_category=data.get("documentCategory"), # Removed: Documented but not in API. + document_filing_date=parse_to_date(data.get("documentFilingDate")), + document_identifier=data.get("documentIdentifier"), + document_name=data.get("documentName"), + document_number=data.get("documentNumber"), + document_size_quantity=data.get("documentSizeQuantity"), + document_ocr_text=data.get("documentOCRText"), + document_title_text=data.get("documentTitleText"), + 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]: + """Convert the TrialDocumentData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + # Removed: Documented but not in API. + # if self.document_category is not None: + # result["documentCategory"] = self.document_category + if self.document_filing_date is not None: + result["documentFilingDate"] = serialize_date(self.document_filing_date) + if self.document_identifier is not None: + result["documentIdentifier"] = self.document_identifier + if self.document_name is not None: + result["documentName"] = self.document_name + if self.document_number is not None: + result["documentNumber"] = self.document_number + if self.document_size_quantity is not None: + result["documentSizeQuantity"] = self.document_size_quantity + if self.document_ocr_text is not None: + result["documentOCRText"] = self.document_ocr_text # Uppercase OCR + if self.document_title_text is not None: + result["documentTitleText"] = self.document_title_text + if self.document_type_description_text is not None: + result["documentTypeDescriptionText"] = self.document_type_description_text + if self.file_download_uri is not None: + 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 + + +@dataclass(frozen=True) +class TrialDecisionData: + """Metadata for a decision in a PTAB trial. + + Attributes: + statute_and_rule_bag: List of applicable statutes and rules. + decision_issue_date: Date issued. + decision_type_category: Type of decision (e.g. "Final Written Decision"). + issue_type_bag: List of issues addressed. + trial_outcome_category: Outcome (e.g., "Denied"). + """ + + statute_and_rule_bag: list[str] = field(default_factory=list) + decision_issue_date: date | None = None + decision_type_category: str | None = None + issue_type_bag: list[str] = field(default_factory=list) + trial_outcome_category: str | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "TrialDecisionData": + """Create a TrialDecisionData instance from a dictionary. + + Args: + data: Dictionary with API response data containing trial decision information. + + Returns: + TrialDecisionData: A new instance populated with data from the dictionary. + """ + return cls( + statute_and_rule_bag=data.get("statuteAndRuleBag", []), + decision_issue_date=parse_to_date(data.get("decisionIssueDate")), + decision_type_category=data.get("decisionTypeCategory"), + issue_type_bag=data.get("issueTypeBag", []), + trial_outcome_category=data.get("trialOutcomeCategory"), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the TrialDecisionData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.statute_and_rule_bag is not None and len(self.statute_and_rule_bag) > 0: + result["statuteAndRuleBag"] = self.statute_and_rule_bag + if self.decision_issue_date is not None: + result["decisionIssueDate"] = serialize_date(self.decision_issue_date) + if self.decision_type_category is not None: + result["decisionTypeCategory"] = self.decision_type_category + if self.issue_type_bag is not None and len(self.issue_type_bag) > 0: + result["issueTypeBag"] = self.issue_type_bag + if self.trial_outcome_category is not None: + result["trialOutcomeCategory"] = self.trial_outcome_category + + return result + + +@dataclass(frozen=True) +class PTABTrialDocument: + """Individual trial document or decision record from PTAB document/decision search APIs. + + Used by search_documents() and search_decisions() endpoints. Contains document-specific + metadata (documentData) or decision information (decisionData), plus trial context. + Differs from PTABTrialProceeding which represents the entire proceeding rather than + individual documents within it. + + Attributes: + trial_document_category: Category (Document or Decision). + last_modified_date_time: Last modification timestamp. + trial_number: Trial number (e.g., "IPR2023-00123"). + trial_type_code: Type of trial (IPR, PGR, CBM, DER). + trial_meta_data: Trial metadata. + patent_owner_data: Patent owner information. + regular_petitioner_data: Regular petitioner information. + respondent_data: Respondent information. + derivation_petitioner_data: Derivation petitioner information. + document_data: Document metadata (if document). + decision_data: Decision information (if decision). + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + trial_document_category: str | None = None + last_modified_date_time: datetime | None = None + trial_number: str | None = None + trial_type_code: str | None = None + trial_meta_data: TrialMetaData | None = None + patent_owner_data: PatentOwnerData | None = None + regular_petitioner_data: RegularPetitionerData | None = None + respondent_data: RespondentData | None = None + derivation_petitioner_data: DerivationPetitionerData | None = None + document_data: TrialDocumentData | None = None + decision_data: TrialDecisionData | None = None + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialDocument": + """Create a PTABTrialDocument instance from a dictionary. + + Args: + data: Dictionary with API response data containing PTAB trial document information. + include_raw_data: If True, includes the raw API response data in the instance. + + Returns: + PTABTrialDocument: A new instance populated with data from the dictionary. + """ + trial_meta = data.get("trialMetaData") + patent_owner = data.get("patentOwnerData") + reg_petitioner = data.get("regularPetitionerData") + respondent = data.get("respondentData") + deriv_petitioner = data.get("derivationPetitionerData") + doc_data = data.get("documentData") + dec_data = data.get("decisionData") + + return cls( + trial_document_category=data.get("trialDocumentCategory"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + trial_number=data.get("trialNumber"), + trial_meta_data=( + TrialMetaData.from_dict(trial_meta) if trial_meta else None + ), + patent_owner_data=( + PatentOwnerData.from_dict(patent_owner) if patent_owner else None + ), + regular_petitioner_data=( + RegularPetitionerData.from_dict(reg_petitioner) + if reg_petitioner + else None + ), + respondent_data=( + RespondentData.from_dict(respondent) if respondent else None + ), + derivation_petitioner_data=( + DerivationPetitionerData.from_dict(deriv_petitioner) + if deriv_petitioner + else None + ), + document_data=(TrialDocumentData.from_dict(doc_data) if doc_data else None), + decision_data=(TrialDecisionData.from_dict(dec_data) if dec_data else None), + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABTrialDocument instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.trial_document_category is not None: + result["trialDocumentCategory"] = self.trial_document_category + if self.last_modified_date_time is not None: + result["lastModifiedDateTime"] = serialize_datetime_as_naive( + self.last_modified_date_time + ) + if self.trial_number is not None: + result["trialNumber"] = self.trial_number + if self.trial_meta_data is not None: + result["trialMetaData"] = self.trial_meta_data.to_dict() + if self.patent_owner_data is not None: + result["patentOwnerData"] = self.patent_owner_data.to_dict() + if self.regular_petitioner_data is not None: + result["regularPetitionerData"] = self.regular_petitioner_data.to_dict() + if self.respondent_data is not None: + result["respondentData"] = self.respondent_data.to_dict() + if self.derivation_petitioner_data is not None: + result["derivationPetitionerData"] = ( + self.derivation_petitioner_data.to_dict() + ) + if self.document_data is not None: + result["documentData"] = self.document_data.to_dict() + if self.decision_data is not None: + result["decisionData"] = self.decision_data.to_dict() + + return result + + +@dataclass(frozen=True) +class PTABTrialDocumentResponse: + """Response container for PTAB trial documents/decisions search.""" + + count: int = 0 + request_identifier: str | None = None + patent_trial_document_data_bag: list[PTABTrialDocument] = field( + default_factory=list + ) + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABTrialDocumentResponse": + """Create a PTABTrialDocumentResponse instance from a dictionary. + + Args: + data: Dictionary with API response data containing PTAB trial document response information. + include_raw_data: If True, includes the raw API response data in the instance. + + Returns: + PTABTrialDocumentResponse: A new instance populated with data from the dictionary. + """ + docs_data = data.get("patentTrialDocumentDataBag", []) + docs = [ + PTABTrialDocument.from_dict(item, include_raw_data=include_raw_data) + for item in docs_data + ] + return cls( + count=data.get("count", 0), + patent_trial_document_data_bag=docs, + request_identifier=data.get("requestIdentifier"), + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABTrialDocumentResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_trial_document_data_bag is not None + and len(self.patent_trial_document_data_bag) > 0 + ): + result["patentTrialDocumentDataBag"] = [ + doc.to_dict() for doc in self.patent_trial_document_data_bag + ] + + return result + + +# ============================================================================ +# APPEAL DECISIONS MODELS +# ============================================================================ + + +@dataclass(frozen=True) +class AppealMetaData: + """Appeal metadata. + + Attributes: + appeal_filing_date: Date the appeal was filed. + appeal_last_modified_date: Last modification date. + appeal_last_modified_date_time: Last modification timestamp. + application_type_category: Type of application. + docket_notice_mailed_date: Date the docket notice was mailed. + file_download_uri: URI to download ZIP of appeal documents. + """ + + appeal_filing_date: date | None = None + appeal_last_modified_date: date | None = None + appeal_last_modified_date_time: datetime | None = None + application_type_category: str | None = None + docket_notice_mailed_date: date | None = None + file_download_uri: str | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "AppealMetaData": + """Create an AppealMetaData instance from a dictionary. + + Args: + data: Dictionary containing appeal metadata from API response. + include_raw_data: Ignored for this model. + + Returns: + AppealMetaData: An instance of AppealMetaData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + appeal_filing_date=parse_to_date(data.get("appealFilingDate")), + appeal_last_modified_date=parse_to_date(data.get("appealLastModifiedDate")), + appeal_last_modified_date_time=parse_to_datetime_utc( + data.get("appealLastModifiedDateTime") + ), + application_type_category=data.get("applicationTypeCategory"), + docket_notice_mailed_date=parse_to_date(data.get("docketNoticeMailedDate")), + file_download_uri=file_download_uri, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the AppealMetaData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.appeal_filing_date is not None: + result["appealFilingDate"] = serialize_date(self.appeal_filing_date) + if self.appeal_last_modified_date is not None: + result["appealLastModifiedDate"] = serialize_date( + self.appeal_last_modified_date + ) + if self.appeal_last_modified_date_time is not None: + result["appealLastModifiedDateTime"] = serialize_datetime_as_naive( + self.appeal_last_modified_date_time + ) + if self.application_type_category is not None: + result["applicationTypeCategory"] = self.application_type_category + if self.docket_notice_mailed_date is not None: + result["docketNoticeMailedDate"] = serialize_date( + self.docket_notice_mailed_date + ) + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + + return result + + +@dataclass(frozen=True) +class AppellantData(PartyData): + """Appellant party data in PTAB appeals. + + Inherits all attributes from PartyData. Used in appeal proceedings + to represent the party appealing an examiner decision. + """ + + pass + + +@dataclass(frozen=True) +class RequestorData: + """Third party requestor information. + + Attributes: + third_party_name: Name of the third party. + """ + + third_party_name: str | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "RequestorData": + """Create a RequestorData instance from a dictionary. + + Args: + data: Dictionary containing requestor data from API response. + include_raw_data: Ignored for this model. + + Returns: + RequestorData: An instance of RequestorData. + """ + return cls( + third_party_name=data.get("thirdPartyName"), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the RequestorData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + for k, v in asdict(self).items(): + if v is not None: + result[to_camel_case(k)] = v + return result + + +@dataclass(frozen=True) +class AppealDocumentData: + """Appeal document metadata. + + Attributes: + document_filing_date: Date the document was filed. + document_identifier: Unique identifier for the document. + document_name: Name of the document. + document_size_quantity: Size of the document in bytes. + document_ocr_text: Full OCR text of the document. + document_type_description_text: Description of the document type. + file_download_uri: URI to download the document. + """ + + document_filing_date: date | None = None + document_identifier: str | None = None + document_name: str | None = None + document_size_quantity: int | None = None + document_ocr_text: str | None = None + document_type_description_text: str | None = None + file_download_uri: str | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "AppealDocumentData": + """Create an AppealDocumentData instance from a dictionary. + + Args: + data: Dictionary containing document data from API response. + include_raw_data: Ignored for this model. + + Returns: + AppealDocumentData: An instance of AppealDocumentData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + doc_type = data.get("documentTypeDescriptionText") or data.get( + "documentTypeCategory" + ) + + return cls( + document_filing_date=parse_to_date(data.get("documentFilingDate")), + document_identifier=data.get("documentIdentifier"), + document_name=data.get("documentName"), + document_size_quantity=data.get("documentSizeQuantity"), + document_ocr_text=data.get("documentOCRText"), + document_type_description_text=doc_type, + file_download_uri=file_download_uri, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the AppealDocumentData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.document_filing_date is not None: + result["documentFilingDate"] = serialize_date(self.document_filing_date) + if self.document_identifier is not None: + result["documentIdentifier"] = self.document_identifier + if self.document_name is not None: + result["documentName"] = self.document_name + if self.document_size_quantity is not None: + result["documentSizeQuantity"] = self.document_size_quantity + if self.document_ocr_text is not None: + result["documentOCRText"] = self.document_ocr_text + if self.document_type_description_text is not None: + result["documentTypeDescriptionText"] = self.document_type_description_text + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + + return result + + +@dataclass(frozen=True) +class DecisionData: + """Appeal decision information. + + Attributes: + appeal_outcome_category: Outcome of the appeal. + statute_and_rule_bag: List of applicable statutes and rules. + decision_issue_date: Date the decision was issued. + decision_type_category: Type of decision. + issue_type_bag: List of issue types. + """ + + appeal_outcome_category: str | None = None + statute_and_rule_bag: list[str] = field(default_factory=list) + decision_issue_date: date | None = None + decision_type_category: str | None = None + issue_type_bag: list[str] = field(default_factory=list) + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "DecisionData": + """Create a DecisionData instance from a dictionary. + + Args: + data: Dictionary containing decision data from API response. + include_raw_data: Ignored for this model. + + Returns: + DecisionData: An instance of DecisionData. + """ + return cls( + appeal_outcome_category=data.get("appealOutcomeCategory"), + statute_and_rule_bag=data.get("statuteAndRuleBag", []), + decision_issue_date=parse_to_date(data.get("decisionIssueDate")), + decision_type_category=data.get("decisionTypeCategory"), + issue_type_bag=data.get("issueTypeBag", []), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the DecisionData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + for k, v in asdict(self).items(): + if v is not None: + if isinstance(v, date): + result[to_camel_case(k)] = serialize_date(v) + elif isinstance(v, list) and len(v) == 0: + # Skip empty lists + continue + else: + result[to_camel_case(k)] = v + return result + + +@dataclass(frozen=True) +class PTABAppealDecision: + """Individual PTAB appeal decision record. + + Attributes: + appeal_number: Appeal number. + last_modified_date_time: Last modification timestamp. + appeal_document_category: Document category. + appeal_meta_data: Appeal metadata. + appellant_data: Appellant information. + requestor_data: Third party requestor information. + document_data: Document metadata. + decision_data: Decision information. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + appeal_number: str | None = None + last_modified_date_time: datetime | None = None + appeal_document_category: str | None = None + appeal_meta_data: AppealMetaData | None = None + appellant_data: AppellantData | None = None + requestor_data: RequestorData | None = None + document_data: AppealDocumentData | None = None + decision_data: DecisionData | None = None + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABAppealDecision": + """Create a PTABAppealDecision instance from a dictionary. + + Args: + data: Dictionary containing appeal decision data from API response. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABAppealDecision: An instance of PTABAppealDecision. + """ + # Parse nested objects + appeal_meta = data.get("appealMetaData") + appeal_meta_data = ( + AppealMetaData.from_dict(appeal_meta) if appeal_meta else None + ) + + # Handle potential typo 'appelantData' vs 'appellantData' + appellant = data.get("appellantData") or data.get("appelantData") + appellant_data = AppellantData.from_dict(appellant) if appellant else None + + requestor = data.get("requestorData") + requestor_data = RequestorData.from_dict(requestor) if requestor else None + + document = data.get("documentData") + document_data = AppealDocumentData.from_dict(document) if document else None + + decision = data.get("decisionData") + decision_data = DecisionData.from_dict(decision) if decision else None + + return cls( + appeal_number=data.get("appealNumber"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + appeal_document_category=data.get("appealDocumentCategory"), + appeal_meta_data=appeal_meta_data, + appellant_data=appellant_data, + requestor_data=requestor_data, + document_data=document_data, + decision_data=decision_data, + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABAppealDecision instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + # Manually process each field to preserve nested objects + if self.appeal_number is not None: + result["appealNumber"] = self.appeal_number + if self.last_modified_date_time is not None: + result["lastModifiedDateTime"] = serialize_datetime_as_naive( + self.last_modified_date_time + ) + if self.appeal_document_category is not None: + result["appealDocumentCategory"] = self.appeal_document_category + if self.appeal_meta_data is not None: + result["appealMetaData"] = self.appeal_meta_data.to_dict() + if self.appellant_data is not None: + result["appellantData"] = self.appellant_data.to_dict() + if self.requestor_data is not None: + result["requestorData"] = self.requestor_data.to_dict() + if self.document_data is not None: + result["documentData"] = self.document_data.to_dict() + if self.decision_data is not None: + result["decisionData"] = self.decision_data.to_dict() + + return result + + +@dataclass(frozen=True) +class PTABAppealResponse: + """Response container for PTAB appeals search. + + Attributes: + count: Total number of matching results. + request_identifier: UUID for the API request. + patent_appeal_data_bag: List of appeal decisions. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + count: int = 0 + request_identifier: str | None = None + patent_appeal_data_bag: list[PTABAppealDecision] = field(default_factory=list) + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABAppealResponse": + """Create a PTABAppealResponse instance from a dictionary. + + Args: + data: Dictionary containing response data from API. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABAppealResponse: An instance of PTABAppealResponse. + """ + appeals_data = data.get("patentAppealDataBag", []) + appeals = [ + PTABAppealDecision.from_dict(item, include_raw_data=include_raw_data) + for item in appeals_data + ] + + return cls( + count=data.get("count", 0), + request_identifier=data.get("requestIdentifier"), + patent_appeal_data_bag=appeals, + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABAppealResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + # Manually process each field + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_appeal_data_bag is not None + and len(self.patent_appeal_data_bag) > 0 + ): + result["patentAppealDataBag"] = [ + decision.to_dict() for decision in self.patent_appeal_data_bag + ] + + return result + + +# ============================================================================ +# INTERFERENCE DECISIONS MODELS +# ============================================================================ + + +@dataclass(frozen=True) +class InterferenceMetaData: + """Interference metadata. + + Attributes: + interference_style_name: Style name of the interference. + interference_last_modified_date: Last modification date. + interference_last_modified_date_time: Last modification datetime. + declaration_date: Declaration date. + file_download_uri: URI to download ZIP of interference documents. + """ + + interference_style_name: str | None = None + interference_last_modified_date: date | None = None + interference_last_modified_date_time: datetime | None = None + declaration_date: date | None = None + file_download_uri: str | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "InterferenceMetaData": + """Create an InterferenceMetaData instance from a dictionary. + + Args: + data: Dictionary containing interference metadata from API response. + include_raw_data: Ignored for this model. + + Returns: + InterferenceMetaData: An instance of InterferenceMetaData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + return cls( + interference_style_name=data.get("interferenceStyleName"), + interference_last_modified_date=parse_to_date( + data.get("interferenceLastModifiedDate") + ), + interference_last_modified_date_time=parse_to_datetime_utc( + data.get("interferenceLastModifiedDateTime") + ), + declaration_date=parse_to_date(data.get("declarationDate")), + file_download_uri=file_download_uri, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the InterferenceMetaData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.interference_style_name is not None: + result["interferenceStyleName"] = self.interference_style_name + if self.interference_last_modified_date is not None: + result["interferenceLastModifiedDate"] = serialize_date( + self.interference_last_modified_date + ) + if self.interference_last_modified_date_time is not None: + result["interferenceLastModifiedDateTime"] = serialize_datetime_as_naive( + self.interference_last_modified_date_time + ) + if self.declaration_date is not None: + result["declarationDate"] = serialize_date(self.declaration_date) + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + + return result + + +@dataclass(frozen=True) +class SeniorPartyData(PartyData): + """Senior party information in PTAB interference proceedings. + + Inherits all attributes from PartyData. Represents the party with + the earlier effective filing date in an interference. + """ + + pass + + +@dataclass(frozen=True) +class JuniorPartyData(PartyData): + """Junior party information in PTAB interference proceedings. + + Inherits all attributes from PartyData. Represents the party with + the later effective filing date in an interference. + """ + + pass + + +@dataclass(frozen=True) +class AdditionalPartyData: + """Additional party information in an interference. + + Attributes: + application_number_text: Application number. + inventor_name: Name of inventor. + patent_number: Patent number. + additional_party_name: Name of additional party. + """ + + application_number_text: str | None = None + inventor_name: str | None = None + patent_number: str | None = None + additional_party_name: str | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "AdditionalPartyData": + """Create an AdditionalPartyData instance from a dictionary. + + Args: + data: Dictionary containing additional party data from API response. + include_raw_data: Ignored for this model. + + Returns: + AdditionalPartyData: An instance of AdditionalPartyData. + """ + return cls( + application_number_text=data.get("applicationNumberText"), + inventor_name=data.get("inventorName"), + patent_number=data.get("patentNumber"), + additional_party_name=data.get("additionalPartyName"), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the AdditionalPartyData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.application_number_text is not None: + result["applicationNumberText"] = self.application_number_text + if self.inventor_name is not None: + result["inventorName"] = self.inventor_name + if self.patent_number is not None: + result["patentNumber"] = self.patent_number + if self.additional_party_name is not None: + result["additionalPartyName"] = self.additional_party_name + + return result + + +@dataclass(frozen=True) +class InterferenceDocumentData: + """Interference document metadata. + + Attributes: + document_identifier: Unique identifier for the document. + document_name: Name of the document. + document_size_quantity: Size of the document in bytes. + document_ocr_text: Full OCR text of the document. + document_title_text: Title of the document. + interference_outcome_category: Outcome of the interference. + document_filing_date: Date the document was filed. + decision_issue_date: Date the decision was issued. + decision_type_category: Type of decision. + file_download_uri: URI to download the document. + statute_and_rule_bag: List of applicable statutes and rules. + issue_type_bag: List of issues addressed. + """ + + document_identifier: str | None = None + document_name: str | None = None + document_size_quantity: int | None = None + document_ocr_text: str | None = None + document_title_text: str | None = None + interference_outcome_category: str | None = None + document_filing_date: date | None = None + decision_issue_date: date | None = None + decision_type_category: str | None = None + file_download_uri: str | None = None + statute_and_rule_bag: list[str] = field(default_factory=list) + issue_type_bag: list[str] = field(default_factory=list) + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "InterferenceDocumentData": + """Create an InterferenceDocumentData instance from a dictionary. + + Args: + data: Dictionary containing document data from API response. + include_raw_data: Ignored for this model. + + Returns: + InterferenceDocumentData: An instance of InterferenceDocumentData. + """ + # Handle aliases + file_download_uri = data.get("fileDownloadURI") or data.get("downloadURI") + + return cls( + document_identifier=data.get("documentIdentifier"), + document_name=data.get("documentName"), + document_size_quantity=data.get("documentSizeQuantity"), + document_ocr_text=data.get("documentOCRText"), + document_title_text=data.get("documentTitleText"), + interference_outcome_category=data.get("interferenceOutcomeCategory"), + document_filing_date=parse_to_date(data.get("documentFilingDate")), + decision_issue_date=parse_to_date(data.get("decisionIssueDate")), + decision_type_category=data.get("decisionTypeCategory"), + file_download_uri=file_download_uri, + statute_and_rule_bag=data.get("statuteAndRuleBag", []), + issue_type_bag=data.get("issueTypeBag", []), + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the InterferenceDocumentData instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + if self.document_identifier is not None: + result["documentIdentifier"] = self.document_identifier + if self.document_name is not None: + result["documentName"] = self.document_name + if self.document_size_quantity is not None: + result["documentSizeQuantity"] = self.document_size_quantity + if self.document_ocr_text is not None: + result["documentOCRText"] = self.document_ocr_text + if self.document_title_text is not None: + result["documentTitleText"] = self.document_title_text + if self.interference_outcome_category is not None: + result["interferenceOutcomeCategory"] = self.interference_outcome_category + if self.document_filing_date is not None: + result["documentFilingDate"] = serialize_date(self.document_filing_date) + if self.decision_issue_date is not None: + result["decisionIssueDate"] = serialize_date(self.decision_issue_date) + if self.decision_type_category is not None: + result["decisionTypeCategory"] = self.decision_type_category + if self.file_download_uri is not None: + result["fileDownloadURI"] = self.file_download_uri + if self.statute_and_rule_bag is not None and len(self.statute_and_rule_bag) > 0: + result["statuteAndRuleBag"] = self.statute_and_rule_bag + if self.issue_type_bag is not None and len(self.issue_type_bag) > 0: + result["issueTypeBag"] = self.issue_type_bag + + return result + + +@dataclass(frozen=True) +class PTABInterferenceDecision: + """Individual PTAB interference decision record. + + Attributes: + interference_number: Interference number. + last_modified_date_time: Last modification timestamp. + interference_meta_data: Interference metadata. + senior_party_data: Senior party information. + junior_party_data: Junior party information. + additional_party_data_bag: List of additional parties. + document_data: Document metadata. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + interference_number: str | None = None + last_modified_date_time: datetime | None = None + interference_meta_data: InterferenceMetaData | None = None + senior_party_data: SeniorPartyData | None = None + junior_party_data: JuniorPartyData | None = None + additional_party_data_bag: list[AdditionalPartyData] = field(default_factory=list) + document_data: InterferenceDocumentData | None = None + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABInterferenceDecision": + """Create a PTABInterferenceDecision instance from a dictionary. + + Args: + data: Dictionary containing interference decision data from API response. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABInterferenceDecision: An instance of PTABInterferenceDecision. + """ + # Parse nested objects + interference_meta = data.get("interferenceMetaData") + interference_meta_data = ( + InterferenceMetaData.from_dict(interference_meta) + if interference_meta + else None + ) + + senior_party = data.get("seniorPartyData") + senior_party_data = ( + SeniorPartyData.from_dict(senior_party) if senior_party else None + ) + + junior_party = data.get("juniorPartyData") + junior_party_data = ( + JuniorPartyData.from_dict(junior_party) if junior_party else None + ) + + additional_parties_data = data.get("additionalPartyDataBag", []) + additional_party_data_bag = [ + AdditionalPartyData.from_dict(item) for item in additional_parties_data + ] + + # Handle alias: documentData vs decisionDocumentData + document = data.get("documentData") or data.get("decisionDocumentData") + document_data = ( + InterferenceDocumentData.from_dict(document) if document else None + ) + + return cls( + interference_number=data.get("interferenceNumber"), + last_modified_date_time=parse_to_datetime_utc( + data.get("lastModifiedDateTime") + ), + interference_meta_data=interference_meta_data, + senior_party_data=senior_party_data, + junior_party_data=junior_party_data, + additional_party_data_bag=additional_party_data_bag, + document_data=document_data, + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABInterferenceDecision instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + # Manually process each field to preserve nested objects + if self.interference_number is not None: + result["interferenceNumber"] = self.interference_number + if self.last_modified_date_time is not None: + result["lastModifiedDateTime"] = serialize_datetime_as_naive( + self.last_modified_date_time + ) + if self.interference_meta_data is not None: + result["interferenceMetaData"] = self.interference_meta_data.to_dict() + if self.senior_party_data is not None: + result["seniorPartyData"] = self.senior_party_data.to_dict() + if self.junior_party_data is not None: + result["juniorPartyData"] = self.junior_party_data.to_dict() + if ( + self.additional_party_data_bag is not None + and len(self.additional_party_data_bag) > 0 + ): + result["additionalPartyDataBag"] = [ + party.to_dict() for party in self.additional_party_data_bag + ] + if self.document_data is not None: + result["documentData"] = self.document_data.to_dict() + + return result + + +@dataclass(frozen=True) +class PTABInterferenceResponse: + """Response container for PTAB interferences search. + + Attributes: + count: Total number of matching results. + request_identifier: UUID for the API request. + patent_interference_data_bag: List of interference decisions. + raw_data: Raw JSON response data (if include_raw_data=True). + """ + + count: int = 0 + request_identifier: str | None = None + patent_interference_data_bag: list[PTABInterferenceDecision] = field( + default_factory=list + ) + raw_data: dict[str, Any] | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any], include_raw_data: bool = False + ) -> "PTABInterferenceResponse": + """Create a PTABInterferenceResponse instance from a dictionary. + + Args: + data: Dictionary containing response data from API. + include_raw_data: Whether to include raw JSON data in the instance. + + Returns: + PTABInterferenceResponse: An instance of PTABInterferenceResponse. + """ + interferences_data = data.get("patentInterferenceDataBag", []) + interferences = [ + PTABInterferenceDecision.from_dict(item, include_raw_data=include_raw_data) + for item in interferences_data + ] + + return cls( + count=data.get("count", 0), + request_identifier=data.get("requestIdentifier"), + patent_interference_data_bag=interferences, + raw_data=data if include_raw_data else None, + ) + + def to_dict(self) -> dict[str, Any]: + """Convert the PTABInterferenceResponse instance to a dictionary. + + Returns: + Dict[str, Any]: Dictionary with camelCase keys and None values filtered. + """ + result: dict[str, Any] = {} + + # Manually process each field + if self.count is not None: + result["count"] = self.count + if self.request_identifier is not None: + result["requestIdentifier"] = self.request_identifier + if ( + self.patent_interference_data_bag is not None + and len(self.patent_interference_data_bag) > 0 + ): + result["patentInterferenceDataBag"] = [ + decision.to_dict() for decision in self.patent_interference_data_bag + ] + + return result + + +# ============================================================================ +# EXPORTS +# ============================================================================ + +__all__ = [ + # Trial Proceedings Models + "TrialMetaData", + "PatentOwnerData", + "RegularPetitionerData", + "RespondentData", + "DerivationPetitionerData", + "PTABTrialProceeding", + "PTABTrialProceedingResponse", + # Trial Documents/Decisions Models + "TrialDocumentData", + "TrialDecisionData", + "PTABTrialDocument", + "PTABTrialDocumentResponse", + # Appeal Decisions Models + "AppealMetaData", + "AppellantData", + "RequestorData", + "AppealDocumentData", + "DecisionData", + "PTABAppealDecision", + "PTABAppealResponse", + # Interference Decisions Models + "InterferenceMetaData", + "SeniorPartyData", + "JuniorPartyData", + "AdditionalPartyData", + "InterferenceDocumentData", + "PTABInterferenceDecision", + "PTABInterferenceResponse", +] diff --git a/src/pyUSPTO/models/utils.py b/src/pyUSPTO/models/utils.py index a6092ab..a9f2833 100644 --- a/src/pyUSPTO/models/utils.py +++ b/src/pyUSPTO/models/utils.py @@ -1,5 +1,4 @@ -""" -models.utils - Utility functions for USPTO data models +"""models.utils - Utility functions for USPTO data models. This module provides utility functions for parsing, serializing, and converting data used across USPTO API data models. These utilities handle date/datetime @@ -8,7 +7,6 @@ import warnings from datetime import date, datetime, timezone, tzinfo -from typing import Optional from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from pyUSPTO.warnings import ( @@ -20,7 +18,7 @@ # --- Timezone and Parsing Utilities --- ASSUMED_NAIVE_TIMEZONE_STR = "America/New_York" try: - ASSUMED_NAIVE_TIMEZONE: Optional[tzinfo] = ZoneInfo(ASSUMED_NAIVE_TIMEZONE_STR) + ASSUMED_NAIVE_TIMEZONE: tzinfo | None = ZoneInfo(ASSUMED_NAIVE_TIMEZONE_STR) except ZoneInfoNotFoundError: warnings.warn( f"Timezone '{ASSUMED_NAIVE_TIMEZONE_STR}' not found. " @@ -31,8 +29,8 @@ ASSUMED_NAIVE_TIMEZONE = timezone.utc -def parse_to_date(date_str: Optional[str], fmt: str = "%Y-%m-%d") -> Optional[date]: - """Parses a string representation of a date into a date object. +def parse_to_date(date_str: str | None, fmt: str = "%Y-%m-%d") -> date | None: + """Parse a string representation of a date into a date object. Args: date_str (Optional[str]): The string to parse as a date. @@ -46,7 +44,6 @@ def parse_to_date(date_str: Optional[str], fmt: str = "%Y-%m-%d") -> Optional[da Warns: USPTODateParseWarning: If the date string cannot be parsed. """ - if not date_str: return None try: @@ -60,12 +57,12 @@ def parse_to_date(date_str: Optional[str], fmt: str = "%Y-%m-%d") -> Optional[da return None -def parse_to_datetime_utc(datetime_str: Optional[str]) -> Optional[datetime]: - """Parses a string representation of a datetime into a UTC datetime object. +def parse_to_datetime_utc(datetime_str: str | None) -> datetime | None: + """Parse a string representation of a datetime into a UTC datetime object. Attempts to parse ISO format strings. If the input string contains timezone information, it's used. If the string is a naive datetime (no timezone), - it's assumed to be in the `ASSUMED_NAIVE_TIMEZONE` (e.g., "America/New_York") + it's assumed to be in the `ASSUMED_NAIVE_TIMEZONE_STR` (e.g., "America/New_York") and then converted to UTC. Args: @@ -80,21 +77,32 @@ def parse_to_datetime_utc(datetime_str: Optional[str]) -> Optional[datetime]: Warns: USPTODateParseWarning: If the datetime string cannot be parsed. USPTOTimezoneWarning: If timezone localization fails. - """ + """ if not datetime_str: return None - dt_obj: Optional[datetime] = None + dt_obj: datetime | None = None parsed_successfully = False if isinstance(datetime_str, str): try: if datetime_str.endswith("Z"): dt_obj = datetime.fromisoformat(datetime_str.replace("Z", "+00:00")) + # Normalize offsets like -0500 → -05:00 for Python <3.11 + elif ( + len(datetime_str) > 5 + and (datetime_str[-5] in "+-") + and datetime_str[-3] != ":" + ): + datetime_str = ( + datetime_str[:-5] + datetime_str[-5:-2] + ":" + datetime_str[-2:] + ) + dt_obj = datetime.fromisoformat(datetime_str) else: dt_obj = datetime.fromisoformat(datetime_str) parsed_successfully = True except ValueError: pass + if not parsed_successfully or dt_obj is None: warnings.warn( f"Could not parse datetime string '{datetime_str}' with any known format", @@ -120,8 +128,8 @@ def parse_to_datetime_utc(datetime_str: Optional[str]) -> Optional[datetime]: return dt_obj.astimezone(timezone.utc) -def serialize_date(d: Optional[date]) -> Optional[str]: - """Serializes a date object into an ISO 8601 string (YYYY-MM-DD). +def serialize_date(d: date | None) -> str | None: + """Serialize a date object into an ISO 8601 string (YYYY-MM-DD). Args: d (Optional[date]): The date object to serialize. @@ -129,38 +137,63 @@ def serialize_date(d: Optional[date]) -> Optional[str]: Returns: Optional[str]: The date as an ISO 8601 formatted string, or None if the input is None. + """ return d.isoformat() if d else None -def serialize_datetime_as_iso(dt: Optional[datetime]) -> Optional[str]: - """Serializes a datetime object to an ISO 8601 string in UTC, using 'Z'. +def serialize_datetime_as_iso(dt: datetime | None) -> str | None: + """Serialize a datetime object to a local-timezone ISO 8601 string. + + If the input datetime object is timezone-aware, it is converted to the + assumed local timezone defined by `ASSUMED_NAIVE_TIMEZONE`. + If it is naive (lacks timezone information), it is first assigned that + assumed local timezone. - If the input datetime object is timezone-aware, it is converted to UTC. - If it is naive (lacks timezone information), it is assumed to be UTC. - The resulting UTC datetime is then formatted as an ISO 8601 string, - with the UTC timezone explicitly indicated by 'Z'. + The resulting datetime is formatted as: + YYYY-MM-DDTHH:MM:SS.000±HHMM + (e.g., "2024-12-10T00:00:00.000-0500") Args: dt (Optional[datetime]): The datetime object to serialize. Can be naive or timezone-aware. Returns: - Optional[str]: The datetime as a UTC ISO 8601 formatted string - (e.g., "YYYY-MM-DDTHH:MM:SS.ffffffZ" or "YYYY-MM-DDTHH:MM:SSZ"), + Optional[str]: The datetime formatted in the assumed local timezone, or None if the input `dt` is None. - """ + """ if not dt: return None - dt_utc = ( - dt.astimezone(timezone.utc) if dt.tzinfo else dt.replace(tzinfo=timezone.utc) - ) - return dt_utc.isoformat().replace("+00:00", "Z") + if dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None: + dt = dt.replace(tzinfo=ASSUMED_NAIVE_TIMEZONE) + + dt_local = dt.astimezone(ASSUMED_NAIVE_TIMEZONE) + return dt_local.strftime("%Y-%m-%dT%H:%M:%S.000%z") -def parse_yn_to_bool(value: Optional[str]) -> Optional[bool]: - """Converts a 'Y'/'N' (case-insensitive) string to a boolean. + +def serialize_datetime_as_naive(dt: datetime) -> str: + """Serialize a datetime object to ISO format as a naive datetime. + + Converts aware datetimes to the assumed timezone and strips timezone information + before serializing to ISO format. + + Args: + dt: The datetime object to serialize. + + Returns: + str: ISO formatted datetime string without timezone information. + """ + if dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None: + dt = dt.astimezone(ASSUMED_NAIVE_TIMEZONE).replace(tzinfo=None) + else: + dt = dt.replace(tzinfo=ASSUMED_NAIVE_TIMEZONE).replace(tzinfo=None) + return dt.isoformat() + + +def parse_yn_to_bool(value: str | None) -> bool | None: + """Convert a 'Y'/'N' (case-insensitive) string to a boolean. Args: value (Optional[str]): The string value to convert. Expected to be @@ -173,7 +206,6 @@ def parse_yn_to_bool(value: Optional[str]) -> Optional[bool]: Warns: USPTOBooleanParseWarning: If the value is not 'Y' or 'N'. """ - if value is None: return None if value == "": @@ -190,8 +222,8 @@ def parse_yn_to_bool(value: Optional[str]) -> Optional[bool]: return None -def serialize_bool_to_yn(value: Optional[bool]) -> Optional[str]: - """Converts a boolean value to its 'Y'/'N' string representation. +def serialize_bool_to_yn(value: bool | None) -> str | None: + """Convert a boolean value to its 'Y'/'N' string representation. Args: value (Optional[bool]): The boolean value to convert. @@ -200,14 +232,13 @@ def serialize_bool_to_yn(value: Optional[bool]) -> Optional[str]: Optional[str]: "Y" if `value` is True, "N" if `value` is False. Returns None if `value` is None. """ - if value is None: return None return "Y" if value else "N" def to_camel_case(snake_str: str) -> str: - """Converts a snake_case string to lowerCamelCase. + """Convert a snake_case string to lowerCamelCase. For example, "example_snake_string" becomes "exampleSnakeString". diff --git a/src/pyUSPTO/utils/__init__.py b/src/pyUSPTO/utils/__init__.py index 105ad1f..16d72fa 100644 --- a/src/pyUSPTO/utils/__init__.py +++ b/src/pyUSPTO/utils/__init__.py @@ -1,5 +1,4 @@ -""" -utils - Utility functions for USPTO API clients +"""utils - Utility functions for USPTO API clients. This package provides utility functions for USPTO API clients. """ diff --git a/src/pyUSPTO/utils/http.py b/src/pyUSPTO/utils/http.py index 74ab35c..5b63fdb 100644 --- a/src/pyUSPTO/utils/http.py +++ b/src/pyUSPTO/utils/http.py @@ -1,19 +1,17 @@ -""" -utils.http - HTTP utilities for USPTO API clients +"""utils.http - HTTP utilities for USPTO API clients. This module provides HTTP utilities for USPTO API clients. """ -from typing import Any, Dict, Optional +from typing import Any import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry -def create_session(headers: Optional[Dict[str, str]] = None) -> requests.Session: - """ - Create a requests session with retry configuration. +def create_session(headers: dict[str, str] | None = None) -> requests.Session: + """Create a requests session with retry configuration. Args: headers: Optional headers to add to the session @@ -39,9 +37,8 @@ def create_session(headers: Optional[Dict[str, str]] = None) -> requests.Session return session -def parse_response(response: requests.Response) -> Dict[str, Any]: - """ - Parse a response from the USPTO API. +def parse_response(response: requests.Response) -> dict[str, Any]: + """Parse a response from the USPTO API. Args: response: Response from the USPTO API @@ -49,5 +46,5 @@ def parse_response(response: requests.Response) -> Dict[str, Any]: Returns: Parsed response data """ - json_response: Dict[str, Any] = response.json() + json_response: dict[str, Any] = response.json() return json_response diff --git a/src/pyUSPTO/warnings.py b/src/pyUSPTO/warnings.py index 16ce924..b1ba85c 100644 --- a/src/pyUSPTO/warnings.py +++ b/src/pyUSPTO/warnings.py @@ -1,5 +1,4 @@ -""" -warnings - Warning classes for pyUSPTO data parsing issues +"""warnings - Warning classes for pyUSPTO data parsing issues. This module defines custom warning categories for different types of data parsing issues encountered when working with USPTO API responses. diff --git a/tests/clients/test_base.py b/tests/clients/test_base.py index ffa8e3c..6625aa8 100644 --- a/tests/clients/test_base.py +++ b/tests/clients/test_base.py @@ -4,7 +4,7 @@ This module contains tests for the BaseUSPTOClient class. """ -from typing import Any, Dict, cast +from typing import Any, cast from unittest.mock import MagicMock, mock_open, patch import pytest @@ -12,7 +12,7 @@ from requests.adapters import HTTPAdapter import pyUSPTO.models.base as BaseModels -from pyUSPTO.clients.base import BaseUSPTOClient, FromDictProtocol +from pyUSPTO.clients.base import BaseUSPTOClient from pyUSPTO.exceptions import ( USPTOApiAuthError, USPTOApiBadRequestError, @@ -78,11 +78,11 @@ def test_base_model_initialization(self) -> None: class TestResponseClass: """Test class implementing FromDictProtocol for testing.""" - data: Dict[str, Any] + data: dict[str, Any] @classmethod def from_dict( - cls, data: Dict[str, Any], include_raw_data: bool = False + cls, data: dict[str, Any], include_raw_data: bool = False ) -> "TestResponseClass": """Create a TestResponseClass object from a dictionary.""" instance = cls() @@ -576,8 +576,8 @@ def test_save_response_to_file(self, mock_session: MagicMock) -> None: def test_base_client_with_http_config(self) -> None: """Test BaseUSPTOClient applies HTTPConfig settings""" - from pyUSPTO.http_config import HTTPConfig from pyUSPTO.config import USPTOConfig + from pyUSPTO.http_config import HTTPConfig http_cfg = HTTPConfig( max_retries=7, @@ -619,8 +619,8 @@ def test_base_client_backward_compatibility(self) -> None: def test_base_client_timeout_applied(self, mock_session: MagicMock) -> None: """Test that timeout is passed to requests""" - from pyUSPTO.http_config import HTTPConfig from pyUSPTO.config import USPTOConfig + from pyUSPTO.http_config import HTTPConfig http_cfg = HTTPConfig(timeout=45.0, connect_timeout=8.0) config = USPTOConfig(api_key="test", http_config=http_cfg) @@ -788,7 +788,6 @@ def test_save_to_directory_with_content_disposition( self, mock_file_open: MagicMock, tmp_path: Any ) -> None: """Test saving to directory extracts filename from Content-Disposition.""" - from pathlib import Path # Create a test client client: BaseUSPTOClient[Any] = BaseUSPTOClient( @@ -797,7 +796,9 @@ def test_save_to_directory_with_content_disposition( # Mock response with Content-Disposition header mock_response = MagicMock() - mock_response.headers = {"Content-Disposition": 'attachment; filename="test_doc.pdf"'} + mock_response.headers = { + "Content-Disposition": 'attachment; filename="test_doc.pdf"' + } mock_response.iter_content.return_value = [b"data1", b"data2"] # Save to directory (using tmp_path from pytest fixture) @@ -813,7 +814,6 @@ def test_save_to_directory_without_content_disposition( self, mock_file_open: MagicMock, tmp_path: Any ) -> None: """Test saving to directory without Content-Disposition raises ValueError.""" - from pathlib import Path client: BaseUSPTOClient[Any] = BaseUSPTOClient( api_key="test", base_url="https://test.com" diff --git a/tests/clients/test_bulk_data_clients.py b/tests/clients/test_bulk_data_clients.py index 7935f89..ddc94d0 100644 --- a/tests/clients/test_bulk_data_clients.py +++ b/tests/clients/test_bulk_data_clients.py @@ -6,7 +6,7 @@ """ import os -from typing import Any, Dict +from typing import Any from unittest.mock import MagicMock, mock_open, patch import pytest @@ -249,7 +249,7 @@ class TestBulkDataClientCore: """Tests for the core functionality of the BulkDataClient class.""" def test_get_products( - self, mock_bulk_data_client: BulkDataClient, bulk_data_sample: Dict[str, Any] + self, mock_bulk_data_client: BulkDataClient, bulk_data_sample: dict[str, Any] ) -> None: """Test get_products method.""" # Setup @@ -278,7 +278,7 @@ def test_get_products( assert len(response.bulk_data_product_bag) == 2 def test_get_product_by_id( - self, mock_bulk_data_client: BulkDataClient, bulk_data_sample: Dict[str, Any] + self, mock_bulk_data_client: BulkDataClient, bulk_data_sample: dict[str, Any] ) -> None: """Test get_product_by_id method.""" # Setup @@ -404,7 +404,7 @@ def test_download_file_with_relative_url( with patch.object(mock_bulk_data_client, "_make_request", mock_make_request): with ( patch("os.path.exists", return_value=True), - patch("builtins.open", mock_open()) as mock_file, + patch("builtins.open", mock_open()), ): file_path = mock_bulk_data_client.download_file( file_data=file_data, destination=destination @@ -419,7 +419,7 @@ def test_download_file_with_relative_url( assert file_path == os.path.join(destination, "test.zip") def test_search_products( - self, mock_bulk_data_client: BulkDataClient, bulk_data_sample: Dict[str, Any] + self, mock_bulk_data_client: BulkDataClient, bulk_data_sample: dict[str, Any] ) -> None: """Test search_products method with all parameters.""" # Setup diff --git a/tests/clients/test_patent_data_clients.py b/tests/clients/test_patent_data_clients.py index 29279e7..543c3c6 100644 --- a/tests/clients/test_patent_data_clients.py +++ b/tests/clients/test_patent_data_clients.py @@ -1,5 +1,4 @@ -""" -Consolidated tests for the pyUSPTO.clients.patent_data.PatentDataClient. +"""Consolidated tests for the pyUSPTO.clients.patent_data.PatentDataClient. This module combines tests for initialization, core functionality, document handling, metadata retrieval, status codes, return type validation, and edge cases for the @@ -8,13 +7,11 @@ import csv import io -import os -import re +from collections.abc import Iterator from datetime import date, datetime, timezone -from typing import Any, Dict, Iterator, List, Optional +from typing import Any from unittest import mock -from unittest.mock import MagicMock, Mock, mock_open, patch -from urllib.parse import unquote, urlparse +from unittest.mock import MagicMock, mock_open, patch import pytest import requests @@ -22,8 +19,7 @@ from pyUSPTO.clients.base import BaseUSPTOClient from pyUSPTO.clients.patent_data import PatentDataClient from pyUSPTO.config import USPTOConfig -from pyUSPTO.exceptions import USPTOApiBadRequestError, USPTOApiError -from pyUSPTO.warnings import USPTODataMismatchWarning +from pyUSPTO.exceptions import USPTOApiBadRequestError from pyUSPTO.models.patent_data import ( ApplicationContinuityData, ApplicationMetaData, @@ -49,6 +45,7 @@ StatusCodeSearchResponse, serialize_date, ) +from pyUSPTO.warnings import USPTODataMismatchWarning # --- Fixtures --- @@ -174,8 +171,8 @@ def mock_patent_file_wrapper( mock_pgpub_document_meta_data: PrintedMetaData, mock_grant_document_meta_data: PrintedMetaData, ) -> PatentFileWrapper: - """ - Provides a comprehensive mock PatentFileWrapper instance. + """Provides a comprehensive mock PatentFileWrapper instance. + Application number is set to '12345678'. """ return PatentFileWrapper( @@ -203,9 +200,7 @@ def mock_patent_file_wrapper_minimal() -> PatentFileWrapper: def mock_patent_data_response_with_data( mock_patent_file_wrapper: PatentFileWrapper, ) -> PatentDataResponse: - """ - Provides a mock PatentDataResponse instance containing one mock_patent_file_wrapper. - """ + """Provides a mock PatentDataResponse instance containing one mock_patent_file_wrapper.""" return PatentDataResponse( count=1, patent_file_wrapper_data_bag=[mock_patent_file_wrapper] ) @@ -217,12 +212,18 @@ def mock_patent_data_response_empty() -> PatentDataResponse: return PatentDataResponse(count=0, patent_file_wrapper_data_bag=[]) +@pytest.fixture +def mock_get_search_results_empty() -> dict: + """Provides an empty mock PatentDataResponse instance.""" + return {"patentdata": {}} + + @pytest.fixture def client_with_mocked_request( patent_data_client: PatentDataClient, ) -> Iterator[tuple[PatentDataClient, MagicMock]]: - """ - Provides a PatentDataClient instance with its _make_request method mocked. + """Provides a PatentDataClient instance with its _make_request method mocked. + Returns a tuple (client, mock_make_request). """ with patch.object( @@ -297,8 +298,8 @@ def test_search_applications_get_direct_query( client, mock_make_request = client_with_mocked_request mock_make_request.return_value = mock_patent_data_response_with_data - params_to_send: Dict[str, Any] = {"query": "Test", "limit": 10, "offset": 0} - expected_api_params: Dict[str, Any] = {"q": "Test", "limit": 10, "offset": 0} + params_to_send: dict[str, Any] = {"query": "Test", "limit": 10, "offset": 0} + expected_api_params: dict[str, Any] = {"q": "Test", "limit": 10, "offset": 0} result = client.search_applications(**params_to_send) @@ -419,7 +420,7 @@ def test_search_applications_post( ) def test_search_applications_get_various_q_convenience_filters( self, - search_q_params: Dict[str, Any], + search_q_params: dict[str, Any], expected_q_part: str, client_with_mocked_request: tuple[PatentDataClient, MagicMock], mock_patent_data_response_empty: PatentDataResponse, @@ -543,7 +544,7 @@ def test_search_applications_get_with_openapi_params( # New test client, mock_make_request = client_with_mocked_request mock_make_request.return_value = mock_patent_data_response_empty - method_kwargs: Dict[str, Any] = { + method_kwargs: dict[str, Any] = { api_param_name: api_param_value, "limit": 5, "offset": 0, @@ -1136,7 +1137,7 @@ def test_download_file_filters_empty_chunks( mock_response.iter_content.return_value = [b"data", b"", None, b"more"] mock_make_request.return_value = mock_response - result = patent_data_client._download_file("https://test.com", "/tmp/file") + patent_data_client._download_file("https://test.com", "/tmp/file") # Should only write non-empty chunks mock_file_open().write.assert_has_calls( @@ -1249,7 +1250,7 @@ def test_get_ifw_by_pct_app_number( # Should call get_application_by_number mock_make_request.assert_called_once_with( method="GET", - endpoint=f"api/v1/patent/applications/PCTUS24012345", + endpoint="api/v1/patent/applications/PCTUS24012345", response_class=PatentDataResponse, ) assert result is mock_patent_file_wrapper @@ -1282,7 +1283,7 @@ def test_get_ifw_by_short_pct_app_number( # Should call get_application_by_number mock_make_request.assert_called_once_with( method="GET", - endpoint=f"api/v1/patent/applications/PCTUS24012345", + endpoint="api/v1/patent/applications/PCTUS24012345", response_class=PatentDataResponse, ) assert result is mock_patent_file_wrapper @@ -1487,7 +1488,7 @@ def test_download_archive_basic( client_with_mocked_download: tuple[PatentDataClient, MagicMock], sample_printed_metadata: PrintedMetaData, ) -> None: - """Test basic archive download.""" + """Test basic archive download with default overwrite=False.""" client, mock_download_file = client_with_mocked_download mock_exists.return_value = False @@ -1498,8 +1499,11 @@ def test_download_archive_basic( printed_metadata=sample_printed_metadata, destination_path="/printedmeta" ) + # Verify overwrite=False is passed by default mock_download_file.assert_called_once_with( - url=sample_printed_metadata.file_location_uri, file_path=expected_path + url=sample_printed_metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1527,7 +1531,9 @@ def test_download_archive_custom_filename( ) mock_download_file.assert_called_once_with( - url=sample_printed_metadata.file_location_uri, file_path=expected_path + url=sample_printed_metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1548,7 +1554,9 @@ def test_download_archive_no_destination_path( result = client.download_archive(printed_metadata=sample_printed_metadata) mock_download_file.assert_called_once_with( - url=sample_printed_metadata.file_location_uri, file_path=expected_path + url=sample_printed_metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1605,7 +1613,10 @@ def test_download_archive_overwrite_existing( printed_metadata=sample_printed_metadata, overwrite=True ) + # Verify overwrite=True is passed to _download_file mock_download_file.assert_called_once() + call_kwargs = mock_download_file.call_args[1] + assert call_kwargs["overwrite"] is True assert result == expected_path @patch("pathlib.Path.exists") @@ -1630,7 +1641,9 @@ def test_download_archive_fallback_filename_from_url( result = client.download_archive(printed_metadata=metadata) mock_download_file.assert_called_once_with( - url=metadata.file_location_uri, file_path=expected_path + url=metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1656,7 +1669,9 @@ def test_download_archive_last_resort_filename( result = client.download_archive(printed_metadata=metadata) mock_download_file.assert_called_once_with( - url=metadata.file_location_uri, file_path=expected_path + url=metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1670,7 +1685,7 @@ def test_download_publication_basic( client_with_mocked_download: tuple[PatentDataClient, MagicMock], sample_printed_metadata: PrintedMetaData, ) -> None: - """Test basic publication download.""" + """Test basic publication download with default overwrite=False.""" client, mock_download_file = client_with_mocked_download mock_exists.return_value = False @@ -1681,8 +1696,11 @@ def test_download_publication_basic( printed_metadata=sample_printed_metadata, destination_path="/downloads" ) + # Verify overwrite=False is passed by default mock_download_file.assert_called_once_with( - url=sample_printed_metadata.file_location_uri, file_path=expected_path + url=sample_printed_metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1710,7 +1728,9 @@ def test_download_publication_custom_filename( ) mock_download_file.assert_called_once_with( - url=sample_printed_metadata.file_location_uri, file_path=expected_path + url=sample_printed_metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1731,7 +1751,9 @@ def test_download_publication_no_destination_path( result = client.download_publication(printed_metadata=sample_printed_metadata) mock_download_file.assert_called_once_with( - url=sample_printed_metadata.file_location_uri, file_path=expected_path + url=sample_printed_metadata.file_location_uri, + file_path=expected_path, + overwrite=False, ) assert result == expected_path @@ -1788,7 +1810,10 @@ def test_download_publication_overwrite_existing( printed_metadata=sample_printed_metadata, overwrite=True ) + # Verify overwrite=True is passed to _download_file mock_download_file.assert_called_once() + call_kwargs = mock_download_file.call_args[1] + assert call_kwargs["overwrite"] is True assert result == expected_path @@ -1798,13 +1823,13 @@ class TestPatentApplicationDataRetrieval: def test_get_search_results_get_direct_query( self, client_with_mocked_request: tuple[PatentDataClient, MagicMock], - mock_patent_data_response_empty: PatentDataResponse, + mock_get_search_results_empty: list[ApplicationMetaData], ) -> None: """Test GET path of get_search_results with direct query, always requests JSON.""" client, mock_make_request = client_with_mocked_request - mock_make_request.return_value = mock_patent_data_response_empty + mock_make_request.return_value = mock_get_search_results_empty - method_params: Dict[str, Any] = {"query": "bulk test"} + method_params: dict[str, Any] = {"query": "bulk test"} expected_api_params = { "q": "bulk test", "format": "json", @@ -1818,18 +1843,17 @@ def test_get_search_results_get_direct_query( method="GET", endpoint="api/v1/patent/applications/search/download", params=expected_api_params, - response_class=PatentDataResponse, ) - assert result is mock_patent_data_response_empty + assert result == [] def test_get_search_results_get_with_combined_q_convenience_params( self, client_with_mocked_request: tuple[PatentDataClient, MagicMock], - mock_patent_data_response_empty: PatentDataResponse, + mock_get_search_results_empty: list[ApplicationMetaData], ) -> None: """Test get_search_results GET path with a combination of _q convenience params.""" client, mock_make_request = client_with_mocked_request - mock_make_request.return_value = mock_patent_data_response_empty + mock_make_request.return_value = mock_get_search_results_empty client.get_search_results( inventor_name_q="Doe", filing_date_from_q="2021-01-01", limit=5 @@ -1845,7 +1869,6 @@ def test_get_search_results_get_with_combined_q_convenience_params( method="GET", endpoint="api/v1/patent/applications/search/download", params=expected_api_params, - response_class=PatentDataResponse, ) @pytest.mark.parametrize( @@ -1897,14 +1920,14 @@ def test_get_search_results_get_with_combined_q_convenience_params( ) def test_get_search_results_get_various_q_convenience_filters( self, - search_q_params: Dict[str, Any], + search_q_params: dict[str, Any], expected_q_part: str, client_with_mocked_request: tuple[PatentDataClient, MagicMock], - mock_patent_data_response_empty: PatentDataResponse, + mock_get_search_results_empty: list[ApplicationMetaData], ) -> None: """Test get_search_results GET path with various individual _q convenience filters.""" client, mock_make_request = client_with_mocked_request - mock_make_request.return_value = mock_patent_data_response_empty + mock_make_request.return_value = mock_get_search_results_empty limit = 15 offset = 5 @@ -1927,7 +1950,6 @@ def test_get_search_results_get_various_q_convenience_filters( method="GET", endpoint="api/v1/patent/applications/search/download", params=expected_call_params, - response_class=PatentDataResponse, ) # mock_make_request.reset_mock() # Removed to avoid issues with parametrize if tests are run in certain ways @@ -1939,24 +1961,24 @@ def test_get_search_results_get_various_q_convenience_filters( ("filters_param", "applicationMetaData.applicationTypeCode DES", "filters"), ( "range_filters_param", - "applicationMetaData.filingDate 2021-01-01:2021-12-31", + "applicationMetaData.filingDate 2021-01-01", "rangeFilters", ), ], ) - def test_get_search_results_get_with_openapi_params( # New test + def test_get_search_results_get_with_openapi_params( self, method_param_name: str, param_value: str, expected_api_key: str, client_with_mocked_request: tuple[PatentDataClient, MagicMock], - mock_patent_data_response_empty: PatentDataResponse, + mock_get_search_results_empty: list[ApplicationMetaData], ) -> None: """Test get_search_results GET path with various direct OpenAPI parameters.""" client, mock_make_request = client_with_mocked_request - mock_make_request.return_value = mock_patent_data_response_empty + mock_make_request.return_value = mock_get_search_results_empty - method_kwargs: Dict[str, Any] = { + method_kwargs: dict[str, Any] = { method_param_name: param_value, "limit": 7, "offset": 1, @@ -1973,18 +1995,17 @@ def test_get_search_results_get_with_openapi_params( # New test method="GET", endpoint="api/v1/patent/applications/search/download", params=expected_api_params, - response_class=PatentDataResponse, ) # mock_make_request.reset_mock() # Parametrized tests should not reset mock if one instance per test function def test_get_search_results_get_with_additional_query_params( # New test self, client_with_mocked_request: tuple[PatentDataClient, MagicMock], - mock_patent_data_response_empty: PatentDataResponse, + mock_get_search_results_empty: list[ApplicationMetaData], ) -> None: """Test get_search_results GET path with additional_query_params.""" client, mock_make_request = client_with_mocked_request - mock_make_request.return_value = mock_patent_data_response_empty + mock_make_request.return_value = mock_get_search_results_empty client.get_search_results( query="main_download_query", @@ -2009,17 +2030,16 @@ def test_get_search_results_get_with_additional_query_params( # New test method="GET", endpoint="api/v1/patent/applications/search/download", params=expected_api_params, - response_class=PatentDataResponse, ) def test_get_search_results_post( self, client_with_mocked_request: tuple[PatentDataClient, MagicMock], - mock_patent_data_response_with_data: PatentDataResponse, + mock_get_search_results_empty: PatentDataResponse, ) -> None: """Test POST path of get_search_results.""" client, mock_make_request = client_with_mocked_request - mock_make_request.return_value = mock_patent_data_response_with_data + mock_make_request.return_value = mock_get_search_results_empty post_body_request = {"q": "Test POST", "fields": ["patentNumber"]} @@ -2036,9 +2056,8 @@ def test_get_search_results_post( endpoint="api/v1/patent/applications/search/download", json_data=expected_post_body_sent_to_api, params=None, - response_class=PatentDataResponse, ) - assert result is mock_patent_data_response_with_data + assert result == [] class TestApplicationSpecificDataRetrieval: @@ -2451,7 +2470,7 @@ def test_raw_data_enabled_via_config( ) -> None: """Test that raw_data is populated when config.include_raw_data=True.""" config = USPTOConfig(api_key="test_key", include_raw_data=True) - client = PatentDataClient(config=config) + PatentDataClient(config=config) # Create a response with raw_data enabled test_data = { @@ -2698,7 +2717,7 @@ def test_to_csv_with_multiple_wrappers( csv_string = response.to_csv() reader = csv.reader(io.StringIO(csv_string)) - header_row = next(reader) + next(reader) data_rows = list(reader) assert len(data_rows) == response.count diff --git a/tests/clients/test_petition_decision_clients.py b/tests/clients/test_petition_decision_clients.py index 5a4d338..4eba4f9 100644 --- a/tests/clients/test_petition_decision_clients.py +++ b/tests/clients/test_petition_decision_clients.py @@ -1,11 +1,11 @@ -""" -Tests for the pyUSPTO.clients.petition_decisions.FinalPetitionDecisionsClient. +"""Tests for the pyUSPTO.clients.petition_decisions.FinalPetitionDecisionsClient. This module contains comprehensive tests for initialization, search functionality, retrieval, pagination, and document downloads. """ -from typing import Any, Dict, Iterator +from collections.abc import Iterator +from typing import Any from unittest.mock import MagicMock, Mock, patch import pytest @@ -21,7 +21,6 @@ ) from pyUSPTO.warnings import USPTODataMismatchWarning - # --- Fixtures --- @@ -201,7 +200,7 @@ def test_search_with_date_range( client, mock_make_request = client_with_mocked_request mock_make_request.return_value = mock_petition_response_with_data - result = client.search_decisions( + client.search_decisions( decision_date_from_q="2022-01-01", decision_date_to_q="2022-12-31", limit=25, @@ -228,7 +227,7 @@ def test_search_with_multiple_params( client, mock_make_request = client_with_mocked_request mock_make_request.return_value = mock_petition_response_with_data - result = client.search_decisions( + client.search_decisions( applicant_name_q="Test Corp", technology_center_q="1700", decision_type_code_q="C", @@ -256,7 +255,7 @@ def test_search_post_request( mock_make_request.return_value = mock_petition_response_with_data post_body = {"q": "technologyCenter:1700", "limit": 100} - result = client.search_decisions(post_body=post_body) + client.search_decisions(post_body=post_body) mock_make_request.assert_called_once_with( method="POST", @@ -725,14 +724,23 @@ def test_paginate_decisions( page1 = PetitionDecisionResponse( count=2, petition_decision_data_bag=[ - PetitionDecision(application_number_text="111"), - PetitionDecision(application_number_text="222"), + PetitionDecision( + application_number_text="111", + petition_decision_record_identifier="Test-Record-01", + ), + PetitionDecision( + application_number_text="222", + petition_decision_record_identifier="Test-Record-01", + ), ], ) page2 = PetitionDecisionResponse( count=1, petition_decision_data_bag=[ - PetitionDecision(application_number_text="333"), + PetitionDecision( + application_number_text="333", + petition_decision_record_identifier="Test-Record-01", + ), ], ) @@ -859,7 +867,7 @@ def test_download_document_no_destination_path( with patch.object(petition_client, "_download_file") as mock_download: mock_download.return_value = "test.pdf" - result = petition_client.download_petition_document(option) + petition_client.download_petition_document(option) call_args = mock_download.call_args file_path = call_args[1]["file_path"] diff --git a/tests/clients/test_ptab_appeals_client.py b/tests/clients/test_ptab_appeals_client.py new file mode 100644 index 0000000..668a013 --- /dev/null +++ b/tests/clients/test_ptab_appeals_client.py @@ -0,0 +1,424 @@ +"""Tests for PTABAppealsClient. + +This module contains unit tests for the PTABAppealsClient class. +""" + +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from pyUSPTO import PTABAppealsClient, USPTOConfig +from pyUSPTO.models.ptab import PTABAppealResponse + + +@pytest.fixture +def api_key_fixture() -> str: + """Fixture for test API key.""" + return "test_key" + + +@pytest.fixture +def appeal_decision_sample() -> dict[str, Any]: + """Sample appeal decision data for testing.""" + return { + "count": 2, + "requestIdentifier": "req-123", + "patentAppealDataBag": [ + { + "appealNumber": "2023-001234", + "appealRecordIdentifier": "appeal-uuid-1", + "lastModifiedDateTime": "2023-06-15T10:30:00Z", + "appealMetaData": { + "applicationNumberText": "15/123456", + "technologyCenterNumber": "3600", + }, + "decisionData": { + "decisionTypeCategory": "Affirmed", + "decisionDate": "2023-06-01", + }, + }, + { + "appealNumber": "2023-001235", + "appealRecordIdentifier": "appeal-uuid-2", + "lastModifiedDateTime": "2023-06-20T14:00:00Z", + "appealMetaData": { + "applicationNumberText": "16/789012", + "technologyCenterNumber": "2100", + }, + "decisionData": { + "decisionTypeCategory": "Reversed", + "decisionDate": "2023-06-10", + }, + }, + ], + } + + +@pytest.fixture +def mock_ptab_appeals_client(api_key_fixture: str) -> PTABAppealsClient: + """Fixture for mock PTABAppealsClient.""" + return PTABAppealsClient(api_key=api_key_fixture) + + +class TestPTABAppealsClientInit: + """Tests for initialization of PTABAppealsClient.""" + + def test_init_with_api_key(self, api_key_fixture: str) -> None: + """Test initialization with API key.""" + client = PTABAppealsClient(api_key=api_key_fixture) + assert client._api_key == api_key_fixture + assert client.base_url == "https://api.uspto.gov" + + def test_init_with_custom_base_url(self, api_key_fixture: str) -> None: + """Test initialization with custom base URL.""" + custom_url = "https://custom.api.test.com" + client = PTABAppealsClient(api_key=api_key_fixture, base_url=custom_url) + assert client._api_key == api_key_fixture + assert client.base_url == custom_url + + def test_init_with_config(self) -> None: + """Test initialization with config object.""" + config_key = "config_key" + config_url = "https://config.api.test.com" + config = USPTOConfig(api_key=config_key, ptab_base_url=config_url) + client = PTABAppealsClient(config=config) + assert client._api_key == config_key + assert client.base_url == config_url + assert client.config is config + + def test_init_with_api_key_and_config(self, api_key_fixture: str) -> None: + """Test initialization with both API key and config.""" + config = USPTOConfig( + api_key="config_key", + ptab_base_url="https://config.api.test.com", + ) + client = PTABAppealsClient(api_key=api_key_fixture, config=config) + # API key parameter takes precedence + assert client._api_key == api_key_fixture + # But base_url comes from config + assert client.base_url == "https://config.api.test.com" + + +class TestPTABAppealsClientSearchDecisions: + """Tests for search_decisions method.""" + + def test_search_decisions_get_with_query( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with GET and direct query.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + # Test + result = mock_ptab_appeals_client.search_decisions( + query="appealNumber:2024518758", limit=10 + ) + + # Verify + assert isinstance(result, PTABAppealResponse) + assert result.count == 2 + assert len(result.patent_appeal_data_bag) == 2 + mock_session.get.assert_called_once() + call_args = mock_session.get.call_args + assert "q" in call_args[1]["params"] + assert call_args[1]["params"]["q"] == "appealNumber:2024518758" + + def test_search_decisions_get_with_convenience_params( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + # Test + result = mock_ptab_appeals_client.search_decisions( + additional_query_params={"appealNumber": "2015000194"}, + technology_center_number_q="3600", + decision_type_category_q="Affirmed", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + limit=25, + ) + + # Verify + assert isinstance(result, PTABAppealResponse) + mock_session.get.assert_called_once() + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "q" in params + assert "2015000194" in params["appealNumber"] + assert "technologyCenterNumber:3600" in params["q"] + assert "decisionTypeCategory:Affirmed" in params["q"] + assert ( + "decisionData.decisionIssueDate:[2023-01-01 TO 2023-12-31]" in params["q"] + ) + assert params["limit"] == 25 + + def test_search_decisions_get_with_date_from_only( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with only date_from parameter.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + # Test + result = mock_ptab_appeals_client.search_decisions( + decision_date_from_q="2023-01-01" + ) + + # Verify + assert isinstance(result, PTABAppealResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "decisionData.decisionIssueDate:>=2023-01-01" in params["q"] + + def test_search_decisions_get_with_date_to_only( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with only date_to parameter.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + # Test + result = mock_ptab_appeals_client.search_decisions( + decision_date_to_q="2023-12-31" + ) + + # Verify + assert isinstance(result, PTABAppealResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "decisionData.decisionIssueDate:<=2023-12-31" in params["q"] + + def test_search_decisions_get_with_all_convenience_params( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with all convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + # Test + result = mock_ptab_appeals_client.search_decisions( + appeal_number_q="2023-001234", + application_number_text_q="15/123456", + appellant_name_q="Test Appellant", + requestor_name_q="Test Requestor", + decision_type_category_q="Affirmed", + technology_center_number_q="3600", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + ) + + # Verify + assert isinstance(result, PTABAppealResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "appealNumber:2023-001234" in params["q"] + assert "applicationNumberText:15/123456" in params["q"] + assert "appellantData.realPartyInInterestName:Test Appellant" in params["q"] + assert "appellantData.counselName:Test Requestor" in params["q"] + assert "decisionTypeCategory:Affirmed" in params["q"] + assert "technologyCenterNumber:3600" in params["q"] + + def test_search_decisions_post_with_body( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with POST body.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.post.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + post_body = {"q": "technologyCenterNumber:3600", "limit": 100} + + # Test + result = mock_ptab_appeals_client.search_decisions(post_body=post_body) + + # Verify + assert isinstance(result, PTABAppealResponse) + mock_session.post.assert_called_once() + call_args = mock_session.post.call_args + assert call_args[1]["json"] == post_body + + def test_search_decisions_with_optional_params( + self, + mock_ptab_appeals_client: PTABAppealsClient, + appeal_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with optional parameters like sort, facets, etc.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = appeal_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_appeals_client.session = mock_session + + # Test + result = mock_ptab_appeals_client.search_decisions( + query="appealNumber:2023-001234", + sort="decisionDate desc", + offset=10, + limit=50, + facets="technologyCenterNumber", + fields="appealNumber,decisionDate", + filters="decisionTypeCategory:Affirmed", + range_filters="decisionData.decisionIssueDate:[2023-01-01 TO 2023-12-31]", + ) + + # Verify + assert isinstance(result, PTABAppealResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert params["sort"] == "decisionDate desc" + assert params["offset"] == 10 + assert params["limit"] == 50 + assert params["facets"] == "technologyCenterNumber" + assert params["fields"] == "appealNumber,decisionDate" + assert params["filters"] == "decisionTypeCategory:Affirmed" + assert ( + params["rangeFilters"] + == "decisionData.decisionIssueDate:[2023-01-01 TO 2023-12-31]" + ) + + +class TestPTABAppealsClientPaginate: + """Tests for paginate_decisions method.""" + + def test_paginate_decisions( + self, mock_ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test paginate_decisions method.""" + # Setup mock responses + first_response = PTABAppealResponse.from_dict( + { + "count": 2, + "requestIdentifier": "req-1", + "patentAppealDataBag": [ + {"appealNumber": "2023-001234"}, + {"appealNumber": "2023-001235"}, + ], + } + ) + + second_response = PTABAppealResponse.from_dict( + { + "count": 1, + "requestIdentifier": "req-2", + "patentAppealDataBag": [ + {"appealNumber": "2023-001236"}, + ], + } + ) + + third_response = PTABAppealResponse.from_dict( + { + "count": 0, + "requestIdentifier": "req-3", + "patentAppealDataBag": [], + } + ) + + # Mock search_decisions to return different responses + with patch.object(mock_ptab_appeals_client, "search_decisions") as mock_search: + mock_search.side_effect = [first_response, second_response, third_response] + + # Test + results = list( + mock_ptab_appeals_client.paginate_decisions( + technology_center_number_q="3600", limit=2 + ) + ) + + # Verify + assert len(results) == 3 + assert results[0].appeal_number == "2023-001234" + assert results[1].appeal_number == "2023-001235" + assert results[2].appeal_number == "2023-001236" + assert mock_search.call_count == 2 # Stops when count < limit + + def test_paginate_decisions_raises_on_post_body( + self, mock_ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test that paginate_decisions raises ValueError with post_body.""" + with pytest.raises(ValueError, match="does not support 'post_body'"): + list(mock_ptab_appeals_client.paginate_decisions(post_body={"q": "test"})) + + def test_paginate_decisions_with_multiple_params( + self, mock_ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test paginate_decisions with multiple search parameters.""" + # Setup mock responses + first_response = PTABAppealResponse.from_dict( + { + "count": 2, + "requestIdentifier": "req-1", + "patentAppealDataBag": [ + {"appealNumber": "2023-001234"}, + {"appealNumber": "2023-001235"}, + ], + } + ) + + second_response = PTABAppealResponse.from_dict( + { + "count": 0, + "requestIdentifier": "req-2", + "patentAppealDataBag": [], + } + ) + + with patch.object(mock_ptab_appeals_client, "search_decisions") as mock_search: + mock_search.side_effect = [first_response, second_response] + + # Test + results = list( + mock_ptab_appeals_client.paginate_decisions( + technology_center_number_q="3600", + decision_type_category_q="Affirmed", + decision_date_from_q="2023-01-01", + limit=2, + ) + ) + + # Verify + assert len(results) == 2 + # Verify that search_decisions was called with correct params + call_args = mock_search.call_args_list[0] + assert call_args[1]["technology_center_number_q"] == "3600" + assert call_args[1]["decision_type_category_q"] == "Affirmed" + assert call_args[1]["decision_date_from_q"] == "2023-01-01" diff --git a/tests/clients/test_ptab_interferences_client.py b/tests/clients/test_ptab_interferences_client.py new file mode 100644 index 0000000..4addd38 --- /dev/null +++ b/tests/clients/test_ptab_interferences_client.py @@ -0,0 +1,527 @@ +""" +Tests for PTABInterferencesClient. + +This module contains unit tests for the PTABInterferencesClient class. +""" + +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from pyUSPTO import PTABInterferencesClient, USPTOConfig +from pyUSPTO.models.ptab import PTABInterferenceResponse + + +@pytest.fixture +def api_key_fixture() -> str: + """Fixture for test API key.""" + return "test_key" + + +@pytest.fixture +def interference_decision_sample() -> dict[str, Any]: + """Sample interference decision data for testing.""" + return { + "count": 2, + "requestIdentifier": "c76aa849-bd60-40db-a98b-c8cbc143d4f9", + "patentInterferenceDataBag": [ + { + "interferenceNumber": "104807", + "lastModifiedDateTime": "2025-11-20T03:12:32", + "interferenceMetaData": { + "interferenceLastModifiedDateTime": "2006-12-22T00:00:00", + "interferenceLastModifiedDate": "2006-12-22", + "declarationDate": "2002-12-11", + "interferenceStyleName": "VINOGRADOV V. FLAMM", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/104807/104807.zip", + }, + "seniorPartyData": { + "applicationNumberText": "08739037", + "grantDate": "1999-10-12", + "groupArtUnitNumber": "1763", + "inventorName": "GEORGY VINOGRADOV et al", + "patentNumber": "5965034", + "patentOwnerName": "GEORGY VINOGRADOV et al", + "realPartyInInterestName": "GEORGY VINOGRADOV et al", + "technologyCenterNumber": "1700", + }, + "juniorPartyData": { + "applicationNumberText": "08748746", + "grantDate": "2005-02-22", + "groupArtUnitNumber": "1763", + "inventorName": "DANIEL L. FLAMM et al", + "patentNumber": "6858112", + "patentOwnerName": "DANIEL L. FLAMM et al", + "publicationDate": "2003-09-11", + "publicationNumber": "20030168427A1", + "realPartyInInterestName": "DANIEL L. FLAMM et al", + "technologyCenterNumber": "1700", + }, + "documentData": { + "documentIdentifier": "b8c473a3bcab88d5c33ef3231daf45a10f967103a89b8db9c791d1ee", + "documentName": "fd10480712-11-2002", + "documentSizeQuantity": 160468, + "documentOCRText": "The opinion in support of the decision being \nentered today is not binding precedent of the Board. \n\nPaper 20 \nFiled by: Trial Section Motions Panel \n\nBox Interference Filed: December 11, 2002 \nWashington, D.C. 20231 \nTel: 703-308-9797 \nFax: 703-305-0942 \n\nUNITED STATES PATENT AND TRADEMARK OFFICE \n\nBEFORE THE BOARD OF PATENT APPEALS \nAND INTERFERENCES \n\nMAILED \nDANIEL L. FLAMM \n\nJunior Party DEC 2002 \n(U.S. Application 08/748,746), \n\nPAT & TM OFFICE BOARD OF PATENT \nV. AND INTERFER,'N\"FALS \n\nGEORGY", + "documentTitleText": "DECISION-104807", + "interferenceOutcomeCategory": "Final Decision", + "decisionIssueDate": "2002-12-11", + "decisionTypeCategory": "Decision", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/104807/Intf608_10480708739037_1039582800000.pdf", + "documentFilingDate": "2002-12-11", + }, + }, + { + "interferenceNumber": "103751", + "lastModifiedDateTime": "2025-11-20T03:12:32", + "interferenceMetaData": { + "interferenceLastModifiedDateTime": "2006-02-08T00:00:00", + "interferenceLastModifiedDate": "2006-02-08", + "declarationDate": "2002-02-25", + "interferenceStyleName": "TANG", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/103751/103751.zip", + }, + "seniorPartyData": { + "applicationNumberText": "07996817", + "grantDate": "2003-09-02", + "groupArtUnitNumber": "1763", + "inventorName": " TANG", + "patentNumber": "6614529", + "patentOwnerName": " TANG", + "realPartyInInterestName": " TANG", + "technologyCenterNumber": "1700", + }, + "documentData": { + "documentIdentifier": "a1adf3e98f9e07271420b607222b5ad0ddc7198317be600b7c48a3be", + "documentName": "jd103751", + "documentSizeQuantity": 45224, + "documentOCRText": "E:\\FY2002~5\\FEB200~7\\FEB200~4\\JD103751.WPD\n\n\nThe opinion in support of the decision being\nentered today is not binding precedent of the Board.\n\n Paper 62\nFiled by: Interference Trial Section Merits Panel\n Box Interference Filed:\n Washington, D.C. 20231 25 February 2002\n Tel: 703-308-9797\n Fax: 703-305-0942\n\nUNITED STATES PATENT AND TRADEMARK OFFICE\n____________", + "documentTitleText": "DECISION-103751", + "interferenceOutcomeCategory": "Final Decision", + "decisionIssueDate": "2002-02-25", + "decisionTypeCategory": "Decision", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/103751/Intf608_10375107996817_1014613200000.pdf", + "documentFilingDate": "2002-02-25", + }, + }, + ], + } + + +@pytest.fixture +def mock_ptab_interferences_client(api_key_fixture: str) -> PTABInterferencesClient: + """Fixture for mock PTABInterferencesClient.""" + return PTABInterferencesClient(api_key=api_key_fixture) + + +class TestPTABInterferencesClientInit: + """Tests for initialization of PTABInterferencesClient.""" + + def test_init_with_api_key(self, api_key_fixture: str) -> None: + """Test initialization with API key.""" + client = PTABInterferencesClient(api_key=api_key_fixture) + assert client._api_key == api_key_fixture + assert client.base_url == "https://api.uspto.gov" + + def test_init_with_custom_base_url(self, api_key_fixture: str) -> None: + """Test initialization with custom base URL.""" + custom_url = "https://custom.api.test.com" + client = PTABInterferencesClient(api_key=api_key_fixture, base_url=custom_url) + assert client._api_key == api_key_fixture + assert client.base_url == custom_url + + def test_init_with_config(self) -> None: + """Test initialization with config object.""" + config_key = "config_key" + config_url = "https://config.api.test.com" + config = USPTOConfig(api_key=config_key, ptab_base_url=config_url) + client = PTABInterferencesClient(config=config) + assert client._api_key == config_key + assert client.base_url == config_url + assert client.config is config + + def test_init_with_api_key_and_config(self, api_key_fixture: str) -> None: + """Test initialization with both API key and config.""" + config = USPTOConfig( + api_key="config_key", + ptab_base_url="https://config.api.test.com", + ) + client = PTABInterferencesClient(api_key=api_key_fixture, config=config) + # API key parameter takes precedence + assert client._api_key == api_key_fixture + # But base_url comes from config + assert client.base_url == "https://config.api.test.com" + + +class TestPTABInterferencesClientSearchDecisions: + """Tests for search_decisions method.""" + + def test_search_decisions_get_with_query( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with GET and direct query.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + query="interferenceNumber:106123", limit=10 + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + assert result.count == 2 + assert len(result.patent_interference_data_bag) == 2 + mock_session.get.assert_called_once() + call_args = mock_session.get.call_args + assert "q" in call_args[1]["params"] + assert call_args[1]["params"]["q"] == "interferenceNumber:106123" + + def test_search_decisions_get_with_convenience_params( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + interference_number_q="106123", + senior_party_name_q="Senior Party Inc.", + junior_party_name_q="Junior Party LLC", + interference_outcome_category_q="Final Decision", + decision_type_category_q="Decision", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + limit=25, + additional_query_params={"interferenceNumber": "106123"}, + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + mock_session.get.assert_called_once() + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "q" in params + assert "106123" in params["interferenceNumber"] + assert 'seniorPartyData.patentOwnerName:"Senior Party Inc."' in params["q"] + assert 'juniorPartyData.patentOwnerName:"Junior Party LLC"' in params["q"] + assert ( + 'documentData.interferenceOutcomeCategory:"Final Decision"' in params["q"] + ) + assert 'documentData.decisionTypeCategory:"Decision"' in params["q"] + assert ( + "documentData.decisionIssueDate:[2023-01-01 TO 2023-12-31]" in params["q"] + ) + assert params["limit"] == 25 + + def test_search_decisions_get_with_date_from_only( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with only date_from parameter.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + decision_date_from_q="2023-01-01" + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "documentData.decisionIssueDate:>=2023-01-01" in params["q"] + + def test_search_decisions_get_with_date_to_only( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with only date_to parameter.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + decision_date_to_q="2023-12-31" + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "documentData.decisionIssueDate:<=2023-12-31" in params["q"] + + def test_search_decisions_get_with_all_convenience_params( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with all convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + interference_number_q="106123", + senior_party_application_number_q="12/345678", + junior_party_application_number_q="13/987654", + senior_party_name_q="Senior Party Inc.", + junior_party_name_q="Junior Party LLC", + interference_outcome_category_q="Priority to Senior Party", + decision_type_category_q="Final Decision", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "interferenceNumber:106123" in params["q"] + assert "seniorPartyData.applicationNumberText:12/345678" in params["q"] + assert "juniorPartyData.applicationNumberText:13/987654" in params["q"] + assert 'seniorPartyData.patentOwnerName:"Senior Party Inc."' in params["q"] + assert 'juniorPartyData.patentOwnerName:"Junior Party LLC"' in params["q"] + assert ( + 'documentData.interferenceOutcomeCategory:"Priority to Senior Party"' + in params["q"] + ) + assert 'documentData.decisionTypeCategory:"Final Decision"' in params["q"] + + def test_search_decisions_with_real_party_in_interest_q( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with real_party_in_interest_q parameter.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + real_party_in_interest_q="Tech Company Inc." + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert ( + 'seniorPartyData.realPartyInInterestName:"Tech Company Inc." OR juniorPartyData.realPartyInInterestName:"Tech Company Inc."' + in params["q"] + ) + + def test_search_decisions_post_with_body( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with POST body.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.post.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + post_body = { + "q": "interferenceOutcomeCategory:Priority to Senior Party", + "limit": 100, + } + + # Test + result = mock_ptab_interferences_client.search_decisions(post_body=post_body) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + mock_session.post.assert_called_once() + call_args = mock_session.post.call_args + assert call_args[1]["json"] == post_body + + def test_search_decisions_with_optional_params( + self, + mock_ptab_interferences_client: PTABInterferencesClient, + interference_decision_sample: dict[str, Any], + ) -> None: + """Test search_decisions with optional parameters like sort, facets, etc.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = interference_decision_sample + mock_session.get.return_value = mock_response + mock_ptab_interferences_client.session = mock_session + + # Test + result = mock_ptab_interferences_client.search_decisions( + query="interferenceNumber:106123", + sort="decisionDate desc", + offset=10, + limit=50, + facets="interferenceOutcomeCategory", + fields="interferenceNumber,decisionDate", + filters="decisionTypeCategory:Final Decision", + range_filters="decisionDate:[2023-01-01 TO 2023-12-31]", + ) + + # Verify + assert isinstance(result, PTABInterferenceResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert params["sort"] == "decisionDate desc" + assert params["offset"] == 10 + assert params["limit"] == 50 + assert params["facets"] == "interferenceOutcomeCategory" + assert params["fields"] == "interferenceNumber,decisionDate" + assert params["filters"] == "decisionTypeCategory:Final Decision" + assert params["rangeFilters"] == "decisionDate:[2023-01-01 TO 2023-12-31]" + + +class TestPTABInterferencesClientPaginate: + """Tests for paginate_decisions method.""" + + def test_paginate_decisions( + self, mock_ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test paginate_decisions method.""" + # Setup mock responses + first_response = PTABInterferenceResponse.from_dict( + { + "count": 2, + "requestIdentifier": "req-1", + "patentInterferenceDataBag": [ + {"interferenceNumber": "106123"}, + {"interferenceNumber": "106124"}, + ], + } + ) + + second_response = PTABInterferenceResponse.from_dict( + { + "count": 1, + "requestIdentifier": "req-2", + "patentInterferenceDataBag": [ + {"interferenceNumber": "106125"}, + ], + } + ) + + third_response = PTABInterferenceResponse.from_dict( + { + "count": 0, + "requestIdentifier": "req-3", + "patentInterferenceDataBag": [], + } + ) + + # Mock search_decisions to return different responses + with patch.object( + mock_ptab_interferences_client, "search_decisions" + ) as mock_search: + mock_search.side_effect = [first_response, second_response, third_response] + + # Test + results = list( + mock_ptab_interferences_client.paginate_decisions( + interference_outcome_category_q="Priority to Senior Party", limit=2 + ) + ) + + # Verify + assert len(results) == 3 + assert results[0].interference_number == "106123" + assert results[1].interference_number == "106124" + assert results[2].interference_number == "106125" + assert mock_search.call_count == 2 # Stops when count < limit + + def test_paginate_decisions_raises_on_post_body( + self, mock_ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test that paginate_decisions raises ValueError with post_body.""" + with pytest.raises(ValueError, match="does not support 'post_body'"): + list( + mock_ptab_interferences_client.paginate_decisions( + post_body={"q": "test"} + ) + ) + + def test_paginate_decisions_with_multiple_params( + self, mock_ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test paginate_decisions with multiple search parameters.""" + # Setup mock responses + first_response = PTABInterferenceResponse.from_dict( + { + "count": 2, + "requestIdentifier": "req-1", + "patentInterferenceDataBag": [ + {"interferenceNumber": "106123"}, + {"interferenceNumber": "106124"}, + ], + } + ) + + second_response = PTABInterferenceResponse.from_dict( + { + "count": 0, + "requestIdentifier": "req-2", + "patentInterferenceDataBag": [], + } + ) + + with patch.object( + mock_ptab_interferences_client, "search_decisions" + ) as mock_search: + mock_search.side_effect = [first_response, second_response] + + # Test + results = list( + mock_ptab_interferences_client.paginate_decisions( + interference_outcome_category_q="Priority to Senior Party", + decision_type_category_q="Final Decision", + decision_date_from_q="2023-01-01", + limit=2, + ) + ) + + # Verify + assert len(results) == 2 + # Verify that search_decisions was called with correct params + call_args = mock_search.call_args_list[0] + assert ( + call_args[1]["interference_outcome_category_q"] + == "Priority to Senior Party" + ) + assert call_args[1]["decision_type_category_q"] == "Final Decision" + assert call_args[1]["decision_date_from_q"] == "2023-01-01" diff --git a/tests/clients/test_ptab_trials_client.py b/tests/clients/test_ptab_trials_client.py new file mode 100644 index 0000000..99ab908 --- /dev/null +++ b/tests/clients/test_ptab_trials_client.py @@ -0,0 +1,867 @@ +""" +Tests for PTABTrialsClient. + +This module contains unit tests for the PTABTrialsClient class. +""" + +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from pyUSPTO import PTABTrialsClient, USPTOConfig +from pyUSPTO.models.ptab import ( + PTABTrialDocumentResponse, + PTABTrialProceedingResponse, +) + + +@pytest.fixture +def api_key_fixture() -> str: + """Fixture for test API key.""" + return "test_key" + + +@pytest.fixture +def trial_proceeding_sample() -> dict[str, Any]: + """Sample trial proceeding data for testing.""" + return { + "count": 2, + "requestIdentifier": "req-123", + "patentTrialProceedingDataBag": [ + { + "trialNumber": "IPR2023-00001", + "trialRecordIdentifier": "trial-uuid-1", + "lastModifiedDateTime": "2023-01-15T10:30:00Z", + "trialMetaData": { + "petitionFilingDate": "2023-01-01", + "trialStatusCategory": "Active", + "trialTypeCode": "IPR", + }, + "patentOwnerData": { + "patentOwnerName": "Test Company", + "patentNumber": "US1234567", + }, + }, + { + "trialNumber": "IPR2023-00002", + "trialRecordIdentifier": "trial-uuid-2", + "lastModifiedDateTime": "2023-01-20T14:00:00Z", + "trialMetaData": { + "petitionFilingDate": "2023-01-10", + "trialStatusCategory": "Terminated", + "trialTypeCode": "PGR", + }, + }, + ], + } + + +@pytest.fixture +def trial_document_sample() -> dict[str, Any]: + """Sample trial document data for testing.""" + return { + "count": 2, + "patentTrialDocumentDataBag": [ + { + "trialNumber": "IPR2023-00001", + "trialDocumentCategory": "Document", + "lastModifiedDateTime": "2023-01-15T10:30:00Z", + "trialTypeCode": "IPR", + "documentData": { + "documentName": "Petition.pdf", + "documentIdentifier": "doc-123", + "documentFilingDate": "2023-01-10", + }, + }, + { + "trialNumber": "IPR2023-00002", + "trialDocumentCategory": "Decision", + "lastModifiedDateTime": "2023-06-15T14:00:00Z", + "trialTypeCode": "IPR", + "decisionData": { + "decisionTypeCategory": "Final Written Decision", + "decisionIssueDate": "2023-06-10", + }, + }, + ], + } + + +@pytest.fixture +def mock_ptab_trials_client(api_key_fixture: str) -> PTABTrialsClient: + """Fixture for mock PTABTrialsClient.""" + return PTABTrialsClient(api_key=api_key_fixture) + + +class TestPTABTrialsClientInit: + """Tests for initialization of PTABTrialsClient.""" + + def test_init_with_api_key(self, api_key_fixture: str) -> None: + """Test initialization with API key.""" + client = PTABTrialsClient(api_key=api_key_fixture) + assert client._api_key == api_key_fixture + assert client.base_url == "https://api.uspto.gov" + + def test_init_with_custom_base_url(self, api_key_fixture: str) -> None: + """Test initialization with custom base URL.""" + custom_url = "https://custom.api.test.com" + client = PTABTrialsClient(api_key=api_key_fixture, base_url=custom_url) + assert client._api_key == api_key_fixture + assert client.base_url == custom_url + + def test_init_with_config(self) -> None: + """Test initialization with config object.""" + config_key = "config_key" + config_url = "https://config.api.test.com" + config = USPTOConfig(api_key=config_key, ptab_base_url=config_url) + client = PTABTrialsClient(config=config) + assert client._api_key == config_key + assert client.base_url == config_url + assert client.config is config + + def test_init_with_api_key_and_config(self, api_key_fixture: str) -> None: + """Test initialization with both API key and config.""" + config = USPTOConfig( + api_key="config_key", + ptab_base_url="https://config.api.test.com", + ) + client = PTABTrialsClient(api_key=api_key_fixture, config=config) + # API key parameter takes precedence + assert client._api_key == api_key_fixture + # But base_url comes from config + assert client.base_url == "https://config.api.test.com" + + +class TestPTABTrialsClientSearchProceedings: + """Tests for search_proceedings method.""" + + def test_search_proceedings_get_with_query( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with GET and direct query.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_proceedings( + query="trialNumber:IPR2023-00001", limit=10 + ) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + assert result.count == 2 + assert len(result.patent_trial_proceeding_data_bag) == 2 + mock_session.get.assert_called_once() + call_args = mock_session.get.call_args + assert "q" in call_args[1]["params"] + assert call_args[1]["params"]["q"] == "trialNumber:IPR2023-00001" + + def test_search_proceedings_get_with_convenience_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_proceedings( + trial_number_q="IPR2023-00001", + trial_type_code_q="IPR", + petition_filing_date_from_q="2023-01-01", + petition_filing_date_to_q="2023-12-31", + limit=25, + ) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + mock_session.get.assert_called_once() + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "q" in params + assert "trialNumber:IPR2023-00001" in params["q"] + assert "trialTypeCode:IPR" in params["q"] + assert "petitionFilingDate:[2023-01-01 TO 2023-12-31]" in params["q"] + assert params["limit"] == 25 + + def test_search_proceedings_with_all_convenience_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with all convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_proceedings( + trial_number_q="IPR2023-00001", + patent_owner_name_q="Test Owner", + petitioner_real_party_in_interest_name_q="Test Petitioner", + respondent_name_q="Test Respondent", + trial_type_code_q="IPR", + trial_status_category_q="Instituted", + petition_filing_date_from_q="2023-01-01", + petition_filing_date_to_q="2023-12-31", + ) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert 'patentOwnerData.patentOwnerName:"Test Owner"' in params["q"] + assert ( + 'regularPetitionerData.realPartyInInterestName:"Test Petitioner"' + in params["q"] + ) + assert 'respondentData.patentOwnerName:"Test Respondent"' in params["q"] + assert 'trialMetaData.trialStatusCategory:"Instituted"' in params["q"] + + def test_search_proceedings_with_date_from_only( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with only petition_filing_date_from.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_proceedings( + petition_filing_date_from_q="2023-01-01" + ) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "petitionFilingDate:>=2023-01-01" in params["q"] + + def test_search_proceedings_with_date_to_only( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with only petition_filing_date_to.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_proceedings( + petition_filing_date_to_q="2023-12-31" + ) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "petitionFilingDate:<=2023-12-31" in params["q"] + + def test_search_proceedings_with_optional_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with optional parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_proceedings( + query="trialNumber:IPR2023-00001", + sort="petitionFilingDate desc", + offset=10, + limit=50, + facets="trialTypeCode", + fields="trialNumber,petitionFilingDate", + filters="trialStatusCategory:Instituted", + range_filters="petitionFilingDate:[2023-01-01 TO 2023-12-31]", + additional_query_params={"customParam": "value"}, + ) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert params["sort"] == "petitionFilingDate desc" + assert params["offset"] == 10 + assert params["limit"] == 50 + assert params["facets"] == "trialTypeCode" + assert params["fields"] == "trialNumber,petitionFilingDate" + assert params["filters"] == "trialStatusCategory:Instituted" + assert params["rangeFilters"] == "petitionFilingDate:[2023-01-01 TO 2023-12-31]" + assert params["customParam"] == "value" + + def test_search_proceedings_post_with_body( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_proceeding_sample: dict[str, Any], + ) -> None: + """Test search_proceedings with POST body.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_proceeding_sample + mock_session.post.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + post_body = {"q": "trialTypeCode:IPR", "limit": 100} + + # Test + result = mock_ptab_trials_client.search_proceedings(post_body=post_body) + + # Verify + assert isinstance(result, PTABTrialProceedingResponse) + mock_session.post.assert_called_once() + call_args = mock_session.post.call_args + assert call_args[1]["json"] == post_body + + +class TestPTABTrialsClientSearchDocuments: + """Tests for search_documents method.""" + + def test_search_documents_get_with_query( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with GET and direct query.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_documents( + query="trialNumber:IPR2023-00001", limit=10 + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + mock_session.get.assert_called_once() + + def test_search_documents_with_convenience_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_documents( + trial_number_q="IPR2023-00001", + document_category_q="Paper", + filing_date_from_q="2023-01-01", + filing_date_to_q="2023-12-31", + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "q" in params + assert ( + "documentData.documentFilingDate:[2023-01-01 TO 2023-12-31]" in params["q"] + ) + + def test_search_documents_with_all_convenience_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with all convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_documents( + trial_number_q="IPR2023-00001", + document_category_q="Paper", + document_type_name_q="Patent Owner Response", + filing_date_from_q="2023-01-01", + filing_date_to_q="2023-12-31", + petitioner_real_party_in_interest_name_q="Test Petitioner", + inventor_name_q="Jane Inventor", + real_party_in_interest_name_q="Real Party LLC", + patent_number_q="US1234567", + patent_owner_name_q="Test Owner", + limit=50, + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "trialNumber:IPR2023-00001" in params["q"] + assert 'documentData.documentCategory:"Paper"' in params["q"] + assert ( + 'documentData.documentTypeDescriptionText:"Patent Owner Response"' + in params["q"] + ) + assert ( + 'regularPetitionerData.realPartyInInterestName:"Test Petitioner"' + in params["q"] + ) + assert 'patentOwnerData.inventorName:"Jane Inventor"' in params["q"] + assert ( + 'regularPetitionerData.realPartyInInterestName:"Real Party LLC"' + in params["q"] + ) + assert "patentOwnerData.patentNumber:US1234567" in params["q"] + assert 'patentOwnerData.patentOwnerName:"Test Owner"' in params["q"] + assert params["limit"] == 50 + + def test_search_documents_with_date_from_only( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with only filing_date_from.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_documents( + filing_date_from_q="2023-01-01" + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "documentData.documentFilingDate:>=2023-01-01" in params["q"] + + def test_search_documents_with_date_to_only( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with only filing_date_to.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_documents(filing_date_to_q="2023-12-31") + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "documentData.documentFilingDate:<=2023-12-31" in params["q"] + + def test_search_documents_post_with_body( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with POST body.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.post.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + post_body = {"q": "documentCategory:Paper", "limit": 100} + + # Test + result = mock_ptab_trials_client.search_documents(post_body=post_body) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + mock_session.post.assert_called_once() + call_args = mock_session.post.call_args + assert call_args[1]["json"] == post_body + + def test_search_documents_with_optional_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_documents with optional parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_documents( + query="trialNumber:IPR2023-00001", + sort="filingDate desc", + offset=10, + limit=50, + facets="documentCategory", + fields="trialNumber,filingDate", + filters="documentCategory:Paper", + range_filters="filingDate:[2023-01-01 TO 2023-12-31]", + additional_query_params={"customParam": "value"}, + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert params["sort"] == "filingDate desc" + assert params["offset"] == 10 + assert params["limit"] == 50 + assert params["facets"] == "documentCategory" + assert params["fields"] == "trialNumber,filingDate" + assert params["filters"] == "documentCategory:Paper" + assert params["rangeFilters"] == "filingDate:[2023-01-01 TO 2023-12-31]" + assert params["customParam"] == "value" + + +class TestPTABTrialsClientSearchDecisions: + """Tests for search_decisions method.""" + + def test_search_decisions_get_with_query( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with GET and direct query.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + query="trialNumber:IPR2023-00001", limit=10 + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + mock_session.get.assert_called_once() + + def test_search_decisions_with_convenience_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + trial_number_q="IPR2023-00001", + decision_type_category_q="Final Written Decision", + decision_date_from_q="2023-01-01", + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "q" in params + assert "decisionData.decisionIssueDate:>=2023-01-01" in params["q"] + + def test_search_decisions_with_all_convenience_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with all convenience parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + trial_number_q="IPR2023-00001", + decision_type_category_q="Final Written Decision", + decision_date_from_q="2023-01-01", + decision_date_to_q="2023-12-31", + trial_type_code_q="IPR", + patent_number_q="US1234567", + application_number_q="15/123456", + patent_owner_name_q="Test Owner", + trial_status_category_q="Instituted", + real_party_in_interest_name_q="Real Party LLC", + document_category_q="Decision", + limit=50, + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "trialNumber:IPR2023-00001" in params["q"] + assert ( + 'decisionData.decisionTypeCategory:"Final Written Decision"' in params["q"] + ) + assert "trialTypeCode:IPR" in params["q"] + assert "patentOwnerData.patentNumber:US1234567" in params["q"] + assert "patentOwnerData.applicationNumberText:15/123456" in params["q"] + assert 'patentOwnerData.patentOwnerName:"Test Owner"' in params["q"] + assert 'trialMetaData.trialStatusCategory:"Instituted"' in params["q"] + assert ( + 'regularPetitionerData.realPartyInInterestName:"Real Party LLC"' + in params["q"] + ) + assert 'documentData.documentCategory:"Decision"' in params["q"] + assert ( + "decisionData.decisionIssueDate:[2023-01-01 TO 2023-12-31]" in params["q"] + ) + assert params["limit"] == 50 + + def test_search_decisions_with_date_from_only( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with only decision_date_from.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + decision_date_from_q="2023-01-01" + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "decisionData.decisionIssueDate:>=2023-01-01" in params["q"] + + def test_search_decisions_with_date_to_only( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with only decision_date_to.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + decision_date_to_q="2023-12-31" + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert "decisionData.decisionIssueDate:<=2023-12-31" in params["q"] + + def test_search_decisions_with_document_type_description_q( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with document_type_description_q parameter.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + document_type_description_q="Final Written Decision" + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert ( + 'documentData.documentTypeDescriptionText:"*Final Written Decision*"' + in params["q"] + ) + + def test_search_decisions_post_with_body( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with POST body.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.post.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + post_body = {"q": "decisionTypeCategory:Final Written Decision", "limit": 100} + + # Test + result = mock_ptab_trials_client.search_decisions(post_body=post_body) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + mock_session.post.assert_called_once() + call_args = mock_session.post.call_args + assert call_args[1]["json"] == post_body + + def test_search_decisions_with_optional_params( + self, + mock_ptab_trials_client: PTABTrialsClient, + trial_document_sample: dict[str, Any], + ) -> None: + """Test search_decisions with optional parameters.""" + # Setup + mock_session = MagicMock() + mock_response = MagicMock() + mock_response.json.return_value = trial_document_sample + mock_session.get.return_value = mock_response + mock_ptab_trials_client.session = mock_session + + # Test + result = mock_ptab_trials_client.search_decisions( + query="trialNumber:IPR2023-00001", + sort="decisionDate desc", + offset=10, + limit=50, + facets="decisionTypeCategory", + fields="trialNumber,decisionDate", + filters="decisionTypeCategory:Final Written Decision", + range_filters="decisionDate:[2023-01-01 TO 2023-12-31]", + additional_query_params={"customParam": "value"}, + ) + + # Verify + assert isinstance(result, PTABTrialDocumentResponse) + call_args = mock_session.get.call_args + params = call_args[1]["params"] + assert params["sort"] == "decisionDate desc" + assert params["offset"] == 10 + assert params["limit"] == 50 + assert params["facets"] == "decisionTypeCategory" + assert params["fields"] == "trialNumber,decisionDate" + assert params["filters"] == "decisionTypeCategory:Final Written Decision" + assert params["rangeFilters"] == "decisionDate:[2023-01-01 TO 2023-12-31]" + assert params["customParam"] == "value" + + +class TestPTABTrialsClientPaginate: + """Tests for paginate_proceedings method.""" + + def test_paginate_proceedings( + self, mock_ptab_trials_client: PTABTrialsClient + ) -> None: + """Test paginate_proceedings method.""" + # Setup mock responses + first_response = PTABTrialProceedingResponse.from_dict( + { + "count": 2, + "requestIdentifier": "req-1", + "patentTrialProceedingDataBag": [ + {"trialNumber": "IPR2023-00001"}, + {"trialNumber": "IPR2023-00002"}, + ], + } + ) + + second_response = PTABTrialProceedingResponse.from_dict( + { + "count": 1, + "requestIdentifier": "req-2", + "patentTrialProceedingDataBag": [ + {"trialNumber": "IPR2023-00003"}, + ], + } + ) + + third_response = PTABTrialProceedingResponse.from_dict( + { + "count": 0, + "requestIdentifier": "req-3", + "patentTrialProceedingDataBag": [], + } + ) + + # Mock search_proceedings to return different responses + with patch.object(mock_ptab_trials_client, "search_proceedings") as mock_search: + mock_search.side_effect = [first_response, second_response, third_response] + + # Test + results = list( + mock_ptab_trials_client.paginate_proceedings( + trial_type_code_q="IPR", limit=2 + ) + ) + + # Verify + assert len(results) == 3 + assert results[0].trial_number == "IPR2023-00001" + assert results[1].trial_number == "IPR2023-00002" + assert results[2].trial_number == "IPR2023-00003" + assert mock_search.call_count == 2 # Stops when count < limit + + def test_paginate_proceedings_raises_on_post_body( + self, mock_ptab_trials_client: PTABTrialsClient + ) -> None: + """Test that paginate_proceedings raises ValueError with post_body.""" + with pytest.raises(ValueError, match="does not support 'post_body'"): + list(mock_ptab_trials_client.paginate_proceedings(post_body={"q": "test"})) diff --git a/tests/conftest.py b/tests/conftest.py index 3c6312d..fec2b62 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,17 +4,14 @@ This module provides pytest fixtures for testing the USPTO API clients. """ -from typing import Any, Dict, Generator +from collections.abc import Generator +from typing import Any from unittest.mock import MagicMock, patch import pytest from pyUSPTO.clients import BulkDataClient, PatentDataClient from pyUSPTO.config import USPTOConfig -from pyUSPTO.models.patent_data import ( - PatentDataResponse, - PatentFileWrapper, -) @pytest.fixture @@ -60,7 +57,7 @@ def mock_session() -> Generator[MagicMock, None, None]: @pytest.fixture -def bulk_data_sample() -> Dict[str, Any]: +def bulk_data_sample() -> dict[str, Any]: """ Provide a sample bulk data API response. @@ -142,7 +139,7 @@ def bulk_data_sample() -> Dict[str, Any]: @pytest.fixture -def patent_data_sample() -> Dict[str, Any]: +def patent_data_sample() -> dict[str, Any]: """ Provide a sample patent data API response. diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 7282298..19c534a 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,7 +6,7 @@ import os import shutil -from typing import Iterator, Optional +from collections.abc import Iterator import pytest @@ -33,11 +33,13 @@ def manage_test_download_dir() -> Iterator[None]: shutil.rmtree(TEST_DOWNLOAD_DIR) -@pytest.fixture -def api_key() -> Optional[str]: +@pytest.fixture(scope="module") +def api_key() -> str | None: """ Get the API key from the environment. + Uses module scope to cache the API key for all tests in the module. + Returns: Optional[str]: The API key or None if not set """ @@ -49,11 +51,13 @@ def api_key() -> Optional[str]: return key -@pytest.fixture -def config(api_key: Optional[str]) -> USPTOConfig: +@pytest.fixture(scope="module") +def config(api_key: str | None) -> USPTOConfig: """ Create a USPTOConfig instance for integration tests. + Uses module scope to reuse the same config for all tests in the module. + Args: api_key: The API key from the environment diff --git a/tests/integration/test_bulk_data_integration.py b/tests/integration/test_bulk_data_integration.py index 8bb1e5a..3f2f9a5 100644 --- a/tests/integration/test_bulk_data_integration.py +++ b/tests/integration/test_bulk_data_integration.py @@ -7,17 +7,14 @@ """ import os -from typing import Iterator, List, Optional import pytest from pyUSPTO.clients import BulkDataClient from pyUSPTO.config import USPTOConfig -from pyUSPTO.exceptions import USPTOApiError from pyUSPTO.models.bulk_data import BulkDataProduct, BulkDataResponse # Import shared fixtures -from tests.integration.conftest import TEST_DOWNLOAD_DIR # Skip all tests in this module unless ENABLE_INTEGRATION_TESTS is set to 'true' pytestmark = pytest.mark.skipif( diff --git a/tests/integration/test_patent_data_integration.py b/tests/integration/test_patent_data_integration.py index 8877c90..a121545 100644 --- a/tests/integration/test_patent_data_integration.py +++ b/tests/integration/test_patent_data_integration.py @@ -8,13 +8,9 @@ import datetime import os -from typing import Iterator, List, Optional import pytest -# Import shared fixtures -from tests.integration.conftest import TEST_DOWNLOAD_DIR - from pyUSPTO.clients import PatentDataClient from pyUSPTO.config import USPTOConfig from pyUSPTO.exceptions import USPTOApiError, USPTOApiNotFoundError @@ -29,7 +25,6 @@ PatentDataResponse, PatentFileWrapper, PatentTermAdjustmentData, - PrintedMetaData, PrintedPublication, RecordAttorney, StatusCode, @@ -37,6 +32,9 @@ StatusCodeSearchResponse, ) +# Import shared fixtures +from tests.integration.conftest import TEST_DOWNLOAD_DIR + # Skip all tests in this module unless ENABLE_INTEGRATION_TESTS is set to 'true' pytestmark = pytest.mark.skipif( os.environ.get("ENABLE_INTEGRATION_TESTS", "").lower() != "true", @@ -44,11 +42,14 @@ ) -@pytest.fixture +@pytest.fixture(scope="module") def patent_data_client(config: USPTOConfig) -> PatentDataClient: """ Create a PatentDataClient instance for integration tests. + Uses module scope to reuse the same client for all tests in the module, + reducing overhead from creating multiple client instances. + Args: config: The configuration instance @@ -58,13 +59,16 @@ def patent_data_client(config: USPTOConfig) -> PatentDataClient: return PatentDataClient(config=config) -@pytest.fixture +@pytest.fixture(scope="module") def sample_application_number(patent_data_client: PatentDataClient) -> str: - """Provides a sample application number for tests.""" + """Provides a sample application number for tests. + + Uses module scope to execute once per test module and cache the result, + reducing redundant API calls from 11 to 1. + """ try: - # Updated to use search_applications (GET path) response = patent_data_client.search_applications( - query='applicationMetaData.applicationTypeCategory:Utility AND applicationMetaData.applicationStatusDescriptionText:(Pending OR "Patented Case")', + query='applicationMetaData.applicationTypeLabelName:Utility AND applicationMetaData.applicationStatusDescriptionText:"Patented Case"', limit=1, ) if response.count > 0 and response.patent_file_wrapper_data_bag: @@ -72,14 +76,14 @@ def sample_application_number(patent_data_client: PatentDataClient) -> str: if app_num: return app_num - pytest.skip( + pytest.fail( "Could not retrieve a sample application number. Ensure API is reachable and query is valid." ) except USPTOApiError as e: - pytest.skip(f"Could not fetch sample application number due to API error: {e}") + pytest.fail(f"Could not fetch sample application number due to API error: {e}") except Exception as e: - pytest.skip( + pytest.fail( f"Could not fetch sample application number due to unexpected error: {e}" ) return "" @@ -88,13 +92,12 @@ def sample_application_number(patent_data_client: PatentDataClient) -> str: class TestPatentDataIntegration: """Integration tests for the PatentDataClient.""" - KNOWN_APP_NUM_WITH_DOCS = "18045436" + KNOWN_APP_NUM_WITH_DOCS = "14412875" - def test_search_applications_get( # Renamed test + def test_search_applications_get( self, patent_data_client: PatentDataClient ) -> None: """Test getting patent applications from the API using GET path of search_applications.""" - # Updated to use search_applications response = patent_data_client.search_applications( query="applicationMetaData.applicationTypeLabelName:Utility", limit=2 ) @@ -114,9 +117,8 @@ def test_search_applications_get( # Renamed test def test_search_applications_with_convenience_q_param( self, patent_data_client: PatentDataClient - ) -> None: # Renamed test + ) -> None: """Test searching for patents using convenience _q parameters of search_applications.""" - # Updated to use search_applications with _q parameter response = patent_data_client.search_applications( assignee_name_q="International Business Machines", limit=2 ) @@ -131,29 +133,172 @@ def test_search_applications_with_convenience_q_param( response.patent_file_wrapper_data_bag[0], PatentFileWrapper ) else: - assert response.patent_file_wrapper_data_bag == [] + pytest.fail( + 'No PatentDate returned for: `assignee_name_q="International Business Machines"`' + ) - def test_get_application_by_number( # Renamed test - self, patent_data_client: PatentDataClient, sample_application_number: str + def test_get_application_by_number( + self, + patent_data_client: PatentDataClient, ) -> None: """Test getting a specific patent by application number.""" - # Updated to use get_application_by_number patent_wrapper = patent_data_client.get_application_by_number( - sample_application_number + self.KNOWN_APP_NUM_WITH_DOCS ) assert patent_wrapper is not None assert isinstance(patent_wrapper, PatentFileWrapper) - assert patent_wrapper.application_number_text == sample_application_number + assert patent_wrapper.application_number_text == self.KNOWN_APP_NUM_WITH_DOCS assert patent_wrapper.application_meta_data is not None assert isinstance(patent_wrapper.application_meta_data, ApplicationMetaData) assert patent_wrapper.application_meta_data.invention_title is not None - def test_get_status_codes( # Renamed test - self, patent_data_client: PatentDataClient + def test_to_dict_matches_raw_api_response(self, api_key: str | None) -> None: + """Test that to_dict() output matches the original API response stored in raw_data. + + This test compares the to_dict() serialization with the original API response + to ensure that the model correctly reconstructs the API format. + """ + # TEMPORARILY DISABLED: See GitHub issue #17 + # API returns naive datetime strings (e.g., '2025-12-03T07:21:12') without timezone + # indicators, but we serialize with UTC 'Z' suffix (e.g., '2025-12-03T12:21:12Z'). + # Waiting for USPTO ODP to adopt UTC standard for datetime fields. + # pytest.skip( + # "Test disabled pending USPTO API fix for datetime format. See issue #17" + # ) + + # Create a config with include_raw_data=True to preserve original API JSON + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = PatentDataClient(config=config_with_raw) + + # Use search_applications to get a PatentDataResponse (which has raw_data) + response = client_with_raw.search_applications( + application_number_q=self.KNOWN_APP_NUM_WITH_DOCS, limit=1 + ) + + assert response is not None + assert isinstance(response, PatentDataResponse) + assert ( + response.raw_data is not None + ), "raw_data should be populated when include_raw_data=True" + + # Parse the raw API response JSON + import json + + raw_api_dict = json.loads(response.raw_data) + + # Convert the model back to dict + to_dict_output = response.to_dict() + + # Fields that are expected to be in raw API but not in model serialization + # (these are API metadata, not domain data) + expected_missing_fields: set[str] = set() + + # Remove expected metadata fields from raw API for comparison + raw_api_dict_filtered = { + k: v for k, v in raw_api_dict.items() if k not in expected_missing_fields + } + + # Deep comparison of the two dictionaries + def compare_dicts(dict1, dict2, path=""): + """Recursively compare two dictionaries and report differences.""" + differences = [] + + # Check keys present in dict1 but not dict2 + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + # Compare values for common keys + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts(item1, item2, f"{current_path}[{i}]") + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + # Perform the comparison + differences = compare_dicts(to_dict_output, raw_api_dict_filtered) + + # If there are differences, print them and fail + if differences: + diff_report = "\n".join(differences[:20]) # Limit to first 20 differences + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + def test_round_trip_data_integrity( + self, patent_data_client: PatentDataClient, sample_application_number: str ) -> None: + """Test that parsing and serialization preserves data (round-trip test).""" + # Get application from API + original = patent_data_client.get_application_by_number( + sample_application_number + ) + + assert original is not None + + # Convert to dict + data_dict = original.to_dict() + + # Parse back from dict + reconstructed = PatentFileWrapper.from_dict(data_dict) + + # Verify key fields match + assert reconstructed.application_number_text == original.application_number_text + + if original.application_meta_data: + assert reconstructed.application_meta_data is not None + assert ( + reconstructed.application_meta_data.invention_title + == original.application_meta_data.invention_title + ) + assert ( + reconstructed.application_meta_data.filing_date + == original.application_meta_data.filing_date + ) + + def test_get_status_codes(self, patent_data_client: PatentDataClient) -> None: """Test getting patent status codes.""" - # Updated to use get_status_codes status_codes_response = patent_data_client.get_status_codes() assert status_codes_response is not None @@ -176,7 +321,7 @@ def test_get_application_metadata( sample_application_number ) if metadata is None: - pytest.skip( + pytest.fail( f"No metadata available for application {sample_application_number}" ) @@ -185,11 +330,11 @@ def test_get_application_metadata( assert metadata.filing_date is not None assert isinstance(metadata.filing_date, datetime.date) except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Metadata not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"API call for metadata failed for {sample_application_number}: {e}" ) @@ -202,16 +347,16 @@ def test_get_application_adjustment( sample_application_number ) if adjustment_data is None: - pytest.skip(f"No adjustment data for {sample_application_number}") + pytest.fail(f"No adjustment data for {sample_application_number}") assert isinstance(adjustment_data, PatentTermAdjustmentData) assert adjustment_data.adjustment_total_quantity is not None except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Adjustment data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Adjustment data not available or API error for {sample_application_number}: {e}" ) @@ -224,13 +369,13 @@ def test_get_application_assignment( sample_application_number ) if assignments is None: - pytest.skip( + pytest.fail( f"No assignment data (returned None) for {sample_application_number}" ) assert isinstance(assignments, list) if not assignments: - pytest.skip( + pytest.fail( f"Assignment data list is empty for {sample_application_number}" ) @@ -243,11 +388,11 @@ def test_get_application_assignment( assert assignments[0].assignee_bag[0].assignee_name_text is not None except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Assignment data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Assignment data not available or API error for {sample_application_number}: {e}" ) @@ -260,7 +405,7 @@ def test_get_application_attorney( sample_application_number ) if attorney_data is None: - pytest.skip(f"No attorney data for {sample_application_number}") + pytest.fail(f"No attorney data for {sample_application_number}") assert isinstance(attorney_data, RecordAttorney) has_attorney_info = False @@ -271,24 +416,22 @@ def test_get_application_attorney( has_attorney_info = True if attorney_data.customer_number_correspondence_data: assert ( - attorney_data.customer_number_correspondence_data[ - 0 - ].patron_identifier + attorney_data.customer_number_correspondence_data.patron_identifier is not None ) has_attorney_info = True if not has_attorney_info: - pytest.skip( + pytest.fail( f"Attorney data present but bags are empty for {sample_application_number}" ) except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Attorney data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Attorney data not available or API error for {sample_application_number}: {e}" ) @@ -301,7 +444,7 @@ def test_get_application_continuity( sample_application_number ) if continuity_data is None: - pytest.skip(f"No continuity data for {sample_application_number}") + pytest.fail(f"No continuity data for {sample_application_number}") assert isinstance(continuity_data, ApplicationContinuityData) assert continuity_data.parent_continuity_bag is not None @@ -314,11 +457,11 @@ def test_get_application_continuity( is not None ) except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Continuity data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Continuity data not available or API error for {sample_application_number}: {e}" ) @@ -331,13 +474,13 @@ def test_get_application_foreign_priority( sample_application_number ) if priorities is None: - pytest.skip( + pytest.fail( f"No foreign priority data (returned None) for {sample_application_number}" ) assert isinstance(priorities, list) if not priorities: - pytest.skip( + pytest.fail( f"Foreign priority data list is empty for {sample_application_number}" ) @@ -347,11 +490,11 @@ def test_get_application_foreign_priority( assert isinstance(priorities[0].filing_date, datetime.date) except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Foreign priority data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Foreign priority data not available or API error for {sample_application_number}: {e}" ) @@ -364,13 +507,13 @@ def test_get_application_transactions( sample_application_number ) if transactions is None: - pytest.skip( + pytest.fail( f"No transaction data (returned None) for {sample_application_number}" ) assert isinstance(transactions, list) if not transactions: - pytest.skip( + pytest.fail( f"Transaction data list is empty for {sample_application_number}" ) @@ -379,11 +522,11 @@ def test_get_application_transactions( assert transactions[0].event_date is not None assert isinstance(transactions[0].event_date, datetime.date) except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Transaction data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Transaction data not available or API error for {sample_application_number}: {e}" ) @@ -396,14 +539,14 @@ def test_get_application_documents( self.KNOWN_APP_NUM_WITH_DOCS ) if documents_bag is None: - pytest.skip( + pytest.fail( f"No document bag returned for {self.KNOWN_APP_NUM_WITH_DOCS}" ) assert isinstance(documents_bag, DocumentBag) assert documents_bag.documents is not None if not documents_bag.documents: - pytest.skip(f"Document bag is empty for {self.KNOWN_APP_NUM_WITH_DOCS}") + pytest.fail(f"Document bag is empty for {self.KNOWN_APP_NUM_WITH_DOCS}") first_doc = documents_bag.documents[0] assert isinstance(first_doc, Document) @@ -413,9 +556,9 @@ def test_get_application_documents( assert isinstance(first_doc.official_date, datetime.datetime) except USPTOApiNotFoundError: - pytest.skip(f"Documents not found (404) for {self.KNOWN_APP_NUM_WITH_DOCS}") + pytest.fail(f"Documents not found (404) for {self.KNOWN_APP_NUM_WITH_DOCS}") except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Document endpoint failed for {self.KNOWN_APP_NUM_WITH_DOCS}: {e}" ) @@ -428,15 +571,19 @@ def test_get_application_associated_documents( sample_application_number ) if assoc_docs_data is None: - pytest.skip( + pytest.fail( f"No associated documents data for {sample_application_number}" ) - assert isinstance(assoc_docs_data, PrintedMetaData) - assert ( - assoc_docs_data.pgpub_document_meta_data is not None - or assoc_docs_data.grant_document_meta_data is not None - ) + assert isinstance(assoc_docs_data, PrintedPublication) + + if ( + assoc_docs_data.pgpub_document_meta_data is None + and assoc_docs_data.grant_document_meta_data is None + ): + pytest.fail( + f"No pgpub or grant document metadata for {sample_application_number}" + ) if assoc_docs_data.pgpub_document_meta_data: assert ( assoc_docs_data.pgpub_document_meta_data.file_location_uri @@ -448,24 +595,24 @@ def test_get_application_associated_documents( is not None ) except USPTOApiNotFoundError: - pytest.skip( + pytest.fail( f"Associated documents data not found (404) for application {sample_application_number}" ) except USPTOApiError as e: - pytest.skip( + pytest.fail( f"Associated documents data not available or API error for {sample_application_number}: {e}" ) def test_download_application_document( self, patent_data_client: PatentDataClient - ) -> None: # Renamed test + ) -> None: """Test downloading a document file.""" try: documents_bag = patent_data_client.get_application_documents( self.KNOWN_APP_NUM_WITH_DOCS ) if documents_bag is None or not documents_bag.documents: - pytest.skip( + pytest.fail( f"No documents found for {self.KNOWN_APP_NUM_WITH_DOCS} to test download." ) @@ -476,7 +623,7 @@ def test_download_application_document( break if doc_to_download is None or doc_to_download.document_identifier is None: - pytest.skip( + pytest.fail( f"No downloadable document found for {self.KNOWN_APP_NUM_WITH_DOCS}" ) @@ -484,7 +631,7 @@ def test_download_application_document( file_path = patent_data_client.download_document( document_format=doc_to_download.document_formats[0], - file_path=TEST_DOWNLOAD_DIR, + destination_path=TEST_DOWNLOAD_DIR, ) assert file_path is not None @@ -503,16 +650,15 @@ def test_download_application_document( f"No documents available in bag for {self.KNOWN_APP_NUM_WITH_DOCS} to test download." ) - def test_search_applications_post( # Renamed test + def test_search_applications_post( self, patent_data_client: PatentDataClient ) -> None: """Test searching patent applications using POST method with search_applications.""" - search_request_body = { # Renamed from search_request to search_request_body for clarity - "q": "applicationMetaData.applicationTypeCategory:Utility AND applicationMetaData.inventionTitle:(computer OR software)", + search_request_body = { + "q": "applicationMetaData.applicationTypeLabelName:Utility AND applicationMetaData.inventionTitle:(computer OR software)", "pagination": {"offset": 0, "limit": 2}, } try: - # Updated to use search_applications with post_body response = patent_data_client.search_applications( post_body=search_request_body ) @@ -527,70 +673,57 @@ def test_search_applications_post( # Renamed test response.patent_file_wrapper_data_bag[0], PatentFileWrapper ) else: - assert response.patent_file_wrapper_data_bag == [] + pytest.fail(f"No PatentDate returned for: {search_request_body}") except USPTOApiError as e: - pytest.skip(f"POST search failed: {e}") + pytest.fail(f"POST search failed: {e}") - def test_get_search_results_get( # Renamed test - self, patent_data_client: PatentDataClient - ) -> None: + def test_get_search_results_get(self, patent_data_client: PatentDataClient) -> None: """Test getting search results (as JSON structure) using GET path of get_search_results.""" - # format is now handled internally by get_search_results for GET try: - # Updated to use get_search_results response = patent_data_client.get_search_results( query=f"applicationNumberText:{self.KNOWN_APP_NUM_WITH_DOCS}", - limit=1, # Pass as keyword argument + limit=1, ) assert response is not None - assert isinstance(response, PatentDataResponse) - if response.count > 0 and response.patent_file_wrapper_data_bag: - assert ( - response.patent_file_wrapper_data_bag[0].application_number_text - == self.KNOWN_APP_NUM_WITH_DOCS + assert isinstance(response, list) + if len(response) > 0: + assert response[0].application_type_label_name == "Utility" + elif len(response) == 0: + pytest.fail( + f"No applicationMetaData returned for {self.KNOWN_APP_NUM_WITH_DOCS}" ) - elif response.count == 0: - assert response.patent_file_wrapper_data_bag == [] else: pytest.fail( - f"Unexpected response structure for get_search_results GET: count={response.count} but bag is {response.patent_file_wrapper_data_bag}" + f"Unexpected response structure for get_search_results GET: app no={self.KNOWN_APP_NUM_WITH_DOCS}" ) except USPTOApiError as e: - pytest.skip(f"get_search_results GET test failed: {e}") + pytest.fail(f"get_search_results GET test failed: {e}") - def test_get_search_results_post( # Renamed test + def test_get_search_results_post( self, patent_data_client: PatentDataClient ) -> None: - """Test getting search results (as JSON structure) using POST path of get_search_results.""" - # format should be part of the post_body for POST requests to this endpoint - post_body_request = { # Renamed for clarity + """Test getting search results using POST path of get_search_results.""" + post_body_request = { "q": f"applicationNumberText:{self.KNOWN_APP_NUM_WITH_DOCS}", "pagination": {"offset": 0, "limit": 1}, - "format": "json", # Explicitly set format for POST body } try: - # Updated to use get_search_results with post_body response = patent_data_client.get_search_results( post_body=post_body_request ) assert response is not None - assert isinstance(response, PatentDataResponse) + assert isinstance(response, list) - if response.count > 0 and response.patent_file_wrapper_data_bag: - assert ( - response.patent_file_wrapper_data_bag[0].application_number_text - == self.KNOWN_APP_NUM_WITH_DOCS - ) + if len(response) > 0: + assert response[0].application_type_label_name == "Utility" elif response.count == 0: - assert response.patent_file_wrapper_data_bag == [] - else: pytest.fail( - f"Unexpected response structure for get_search_results POST: count={response.count} but bag is {response.patent_file_wrapper_data_bag}" + f"No PatentData returned for US App No.: {self.KNOWN_APP_NUM_WITH_DOCS} with: {post_body_request}" ) except USPTOApiError as e: pytest.skip(f"get_search_results POST test failed: {e}") - def test_search_status_codes_post( # Renamed test + def test_search_status_codes_post( self, patent_data_client: PatentDataClient ) -> None: """Test searching status codes using POST method with search_status_codes.""" @@ -599,7 +732,6 @@ def test_search_status_codes_post( # Renamed test "pagination": {"offset": 0, "limit": 5}, } try: - # Updated to use search_status_codes response = patent_data_client.search_status_codes(search_request) assert response is not None assert isinstance(response, StatusCodeSearchResponse) @@ -612,7 +744,9 @@ def test_search_status_codes_post( # Renamed test assert isinstance(response.status_code_bag[0], StatusCode) assert response.status_code_bag[0].code is not None else: - assert len(response.status_code_bag) == 0 + pytest.fail( + "No PatentDate returned for abandoned OR expired OR pending applications." + ) except USPTOApiError as e: pytest.skip(f"Status codes POST search failed: {e}") @@ -628,6 +762,9 @@ def test_invalid_application_number_handling( assert ( metadata is None ), "Expected None for invalid application number if client handles 404 by returning None" + except ValueError as e: + # Client validates application number format before API call + assert "Invalid application number format" in str(e) except USPTOApiNotFoundError as e: assert e.status_code == 404, f"Expected 404 error, got {e.status_code}" except USPTOApiError as e: diff --git a/tests/integration/test_petition_decisions_integration.py b/tests/integration/test_petition_decisions_integration.py index 6baf908..1ef6741 100644 --- a/tests/integration/test_petition_decisions_integration.py +++ b/tests/integration/test_petition_decisions_integration.py @@ -7,26 +7,24 @@ """ import os -from typing import Iterator, List, Optional import pytest -# Import shared fixtures -from tests.integration.conftest import TEST_DOWNLOAD_DIR - from pyUSPTO.clients import FinalPetitionDecisionsClient from pyUSPTO.config import USPTOConfig from pyUSPTO.exceptions import USPTOApiError, USPTOApiNotFoundError from pyUSPTO.models.petition_decisions import ( DecisionTypeCode, DocumentDirectionCategory, - DocumentDownloadOption, PetitionDecision, PetitionDecisionDocument, PetitionDecisionDownloadResponse, PetitionDecisionResponse, ) +# Import shared fixtures +from tests.integration.conftest import TEST_DOWNLOAD_DIR + # Skip all tests in this module unless ENABLE_INTEGRATION_TESTS is set to 'true' pytestmark = pytest.mark.skipif( os.environ.get("ENABLE_INTEGRATION_TESTS", "").lower() != "true", @@ -34,11 +32,14 @@ ) -@pytest.fixture +@pytest.fixture(scope="module") def petition_decisions_client(config: USPTOConfig) -> FinalPetitionDecisionsClient: """ Create a FinalPetitionDecisionsClient instance for integration tests. + Uses module scope to reuse the same client for all tests in the module, + reducing overhead from creating multiple client instances. + Args: config: The configuration instance @@ -48,37 +49,57 @@ def petition_decisions_client(config: USPTOConfig) -> FinalPetitionDecisionsClie return FinalPetitionDecisionsClient(config=config) -@pytest.fixture -def sample_petition_decision_id( +@pytest.fixture(scope="module") +def sample_petition_decision( petition_decisions_client: FinalPetitionDecisionsClient, -) -> str: - """Provides a sample petition decision record ID for tests.""" +) -> PetitionDecision: + """Provides a sample petition decision for tests. + + Uses module scope to execute once per test module and cache the result, + reducing redundant API calls from multiple tests. + """ try: # Search for a recent decision response = petition_decisions_client.search_decisions(limit=1) - if response.count > 0 and response.petition_decision_data_bag: - decision_id = response.petition_decision_data_bag[ - 0 - ].petition_decision_record_identifier - if decision_id: - return decision_id + if ( + response.count is not None + and response.count > 0 + and response.petition_decision_data_bag + ): + return response.petition_decision_data_bag[0] pytest.skip( - "Could not retrieve a sample petition decision ID. Ensure API is reachable." + "Could not retrieve a sample petition decision. Ensure API is reachable." ) except USPTOApiError as e: - pytest.skip(f"Could not fetch sample petition decision ID due to API error: {e}") + pytest.skip(f"Could not fetch sample petition decision due to API error: {e}") except Exception as e: pytest.skip( - f"Could not fetch sample petition decision ID due to unexpected error: {e}" + f"Could not fetch sample petition decision due to unexpected error: {e}" ) + # This return is unreachable but satisfies type checker + return PetitionDecision() + + +@pytest.fixture(scope="module") +def sample_petition_decision_id(sample_petition_decision: PetitionDecision) -> str: + """Provides a sample petition decision record ID for tests. + + Derives from sample_petition_decision to avoid redundant API calls. + """ + if sample_petition_decision.petition_decision_record_identifier: + return sample_petition_decision.petition_decision_record_identifier + pytest.skip("Sample petition decision does not have a record identifier") return "" class TestFinalPetitionDecisionsIntegration: """Integration tests for the FinalPetitionDecisionsClient.""" + # Known decision ID for consistent testing (update with a stable ID from the API) + KNOWN_DECISION_ID = None # TODO: Add a known stable decision ID when available + def test_search_decisions_basic( self, petition_decisions_client: FinalPetitionDecisionsClient ) -> None: @@ -90,7 +111,7 @@ def test_search_decisions_basic( assert response.count is not None assert response.count >= 0 - if response.count > 0: + if response.count is not None and response.count > 0: assert response.petition_decision_data_bag is not None assert len(response.petition_decision_data_bag) > 0 assert len(response.petition_decision_data_bag) <= 5 @@ -105,88 +126,104 @@ def test_search_decisions_with_query( """Test searching with a custom query.""" try: response = petition_decisions_client.search_decisions( - query="applicantName:*", limit=3 + query="firstApplicantName:*", limit=3 ) assert response is not None assert isinstance(response, PetitionDecisionResponse) - assert response.count >= 0 + assert response.count is not None and response.count >= 0 - if response.count > 0: + if response.count is not None and response.count > 0: assert response.petition_decision_data_bag is not None assert len(response.petition_decision_data_bag) <= 3 except USPTOApiNotFoundError: # 404 may be returned if no records match the query pytest.skip("No records found matching query criteria") + except USPTOApiError as e: + pytest.skip(f"Query search failed: {e}") def test_search_decisions_with_application_number( - self, petition_decisions_client: FinalPetitionDecisionsClient + self, + petition_decisions_client: FinalPetitionDecisionsClient, + sample_petition_decision: PetitionDecision, ) -> None: """Test searching using convenience application_number_q parameter.""" - # First get any decision with an application number - response = petition_decisions_client.search_decisions(limit=10) - if response.count == 0: - pytest.skip("No decisions available to test application number search") - - # Find a decision with an application number - app_num = None - for decision in response.petition_decision_data_bag: - if decision.application_number_text: - app_num = decision.application_number_text - break - - if not app_num: - pytest.skip("No decisions with application numbers found") - - # Search for that specific application number - response = petition_decisions_client.search_decisions( - application_number_q=app_num, limit=5 - ) + # Use the application number from the sample decision + if not sample_petition_decision.application_number_text: + pytest.skip( + "Sample decision does not have an application number to test with" + ) - assert response is not None - assert response.count > 0 - if response.petition_decision_data_bag: - # At least one should match - found = any( - d.application_number_text == app_num - for d in response.petition_decision_data_bag + app_num = sample_petition_decision.application_number_text + + try: + # Search for that specific application number + response = petition_decisions_client.search_decisions( + application_number_q=app_num, limit=5 ) - assert found, f"Expected to find application number {app_num} in results" + + assert response is not None + assert response.count is not None and response.count > 0 + if response.petition_decision_data_bag: + # At least one should match + found = any( + d.application_number_text == app_num + for d in response.petition_decision_data_bag + ) + assert ( + found + ), f"Expected to find application number {app_num} in results" + except USPTOApiError as e: + pytest.skip(f"Application number search failed: {e}") def test_search_decisions_with_patent_number( - self, petition_decisions_client: FinalPetitionDecisionsClient + self, + petition_decisions_client: FinalPetitionDecisionsClient, + sample_petition_decision: PetitionDecision, ) -> None: """Test searching using convenience patent_number_q parameter.""" - # First get any decision with a patent number - response = petition_decisions_client.search_decisions(limit=20) - if response.count == 0: - pytest.skip("No decisions available to test patent number search") - - # Find a decision with a patent number - patent_num = None - for decision in response.petition_decision_data_bag: - if decision.patent_number: - patent_num = decision.patent_number - break - - if not patent_num: - pytest.skip("No decisions with patent numbers found in first 20 results") - - # Search for that specific patent number - response = petition_decisions_client.search_decisions( - patent_number_q=patent_num, limit=5 - ) + try: + # Try to use patent number from sample decision first + patent_num = sample_petition_decision.patent_number + + # If sample doesn't have a patent number, search for one + if not patent_num: + response = petition_decisions_client.search_decisions(limit=20) + if response.count == 0: + pytest.skip("No decisions available to test patent number search") + + # Find a decision with a patent number + for decision in response.petition_decision_data_bag: + if decision.patent_number: + patent_num = decision.patent_number + break - assert response is not None - if response.count > 0 and response.petition_decision_data_bag: - # At least one should match - found = any( - d.patent_number == patent_num - for d in response.petition_decision_data_bag + if not patent_num: + pytest.skip( + "No decisions with patent numbers found in first 20 results" + ) + + # Search for that specific patent number + response = petition_decisions_client.search_decisions( + patent_number_q=patent_num, limit=5 ) - assert ( - found - ), f"Expected to find patent number {patent_num} in results but count is {response.count}" + + assert response is not None + if ( + response.count is not None + and response.count > 0 + and response.petition_decision_data_bag + ): + # At least one should match + found = any( + d.patent_number == patent_num + for d in response.petition_decision_data_bag + ) + assert ( + found + ), f"Expected to find patent number {patent_num} in results but count is {response.count}" + except USPTOApiError as e: + pytest.skip(f"Patent number search failed: {e}") def test_search_decisions_with_technology_center( self, petition_decisions_client: FinalPetitionDecisionsClient @@ -200,7 +237,7 @@ def test_search_decisions_with_technology_center( assert response is not None assert isinstance(response, PetitionDecisionResponse) # May or may not have results depending on data availability - assert response.count >= 0 + assert response.count is not None and response.count >= 0 def test_search_decisions_with_date_range( self, petition_decisions_client: FinalPetitionDecisionsClient @@ -213,9 +250,9 @@ def test_search_decisions_with_date_range( assert response is not None assert isinstance(response, PetitionDecisionResponse) - assert response.count >= 0 + assert response.count is not None and response.count >= 0 - if response.count > 0: + if response.count is not None and response.count > 0: assert response.petition_decision_data_bag is not None assert len(response.petition_decision_data_bag) <= 5 @@ -225,14 +262,205 @@ def test_get_decision_by_id( sample_petition_decision_id: str, ) -> None: """Test getting a specific decision by ID.""" - decision = petition_decisions_client.get_decision_by_id( - sample_petition_decision_id - ) + try: + decision = petition_decisions_client.get_decision_by_id( + sample_petition_decision_id + ) + + assert decision is not None + assert isinstance(decision, PetitionDecision) + assert ( + decision.petition_decision_record_identifier + == sample_petition_decision_id + ) + assert decision.decision_type_code is not None + except USPTOApiNotFoundError: + pytest.skip( + f"Decision not found (404) for ID {sample_petition_decision_id}" + ) + except USPTOApiError as e: + pytest.skip(f"Failed to get decision by ID: {e}") + + def test_round_trip_data_integrity( + self, + petition_decisions_client: FinalPetitionDecisionsClient, + sample_petition_decision_id: str, + ) -> None: + """Test that parsing and serialization preserves data (round-trip test).""" + try: + # Get decision from API + original = petition_decisions_client.get_decision_by_id( + sample_petition_decision_id + ) + + if original is None: + pytest.skip( + f"No decision data returned for {sample_petition_decision_id}" + ) + + # Convert to dict + data_dict = original.to_dict() + + # Parse back from dict + reconstructed = PetitionDecision.from_dict(data_dict) + + # Verify key fields match + assert ( + reconstructed.petition_decision_record_identifier + == original.petition_decision_record_identifier + ) + assert reconstructed.decision_type_code == original.decision_type_code - assert decision is not None - assert isinstance(decision, PetitionDecision) - assert decision.petition_decision_record_identifier == sample_petition_decision_id - assert decision.decision_type_code is not None + if original.decision_date: + assert reconstructed.decision_date == original.decision_date + + if original.application_number_text: + assert ( + reconstructed.application_number_text + == original.application_number_text + ) + + except USPTOApiNotFoundError: + pytest.skip( + f"Decision not found (404) for round-trip test: {sample_petition_decision_id}" + ) + except USPTOApiError as e: + pytest.skip(f"Round-trip test failed due to API error: {e}") + + def test_to_dict_matches_raw_api_response( + self, api_key: str | None, sample_petition_decision_id: str + ) -> None: + """Test that to_dict() output matches the original API response stored in raw_data. + + This test compares the to_dict() serialization with the original API response + to ensure that the model correctly reconstructs the API format. Some differences + are expected (e.g., requestIdentifier field is not part of the model). + """ + # TEMPORARILY DISABLED: See GitHub issue #17 + # API returns naive datetime strings (e.g., '2025-12-03T07:21:12') without timezone + # indicators, but we serialize with UTC 'Z' suffix (e.g., '2025-12-03T12:21:12Z'). + # Waiting for USPTO ODP to adopt UTC standard for datetime fields. + # pytest.skip( + # "Test disabled pending USPTO API fix for datetime format. See issue #17" + # ) + + # Create a config with include_raw_data=True to preserve original API JSON + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = FinalPetitionDecisionsClient(config=config_with_raw) + + try: + # Get decision with raw data + response = client_with_raw.search_decisions( + query=f"petitionDecisionRecordIdentifier:{sample_petition_decision_id}", + limit=1, + ) + + if response is None or response.count == 0: + pytest.skip( + f"No decision found for raw API comparison: {sample_petition_decision_id}" + ) + + assert ( + response.raw_data is not None + ), "raw_data should be populated when include_raw_data=True" + + # Parse the raw API response JSON + import json + + raw_api_dict = json.loads(response.raw_data) + + # Convert the model back to dict + to_dict_output = response.to_dict() + + # Fields that are expected to be in raw API but not in model serialization + # (these are API metadata, not domain data) + # expected_missing_fields = {"requestIdentifier"} + expected_missing_fields = {} + + # Remove expected metadata fields from raw API for comparison + raw_api_dict_filtered = { + k: v + for k, v in raw_api_dict.items() + if k not in expected_missing_fields + } + + # Deep comparison of the two dictionaries + def compare_dicts(dict1, dict2, path=""): + """Recursively compare two dictionaries and report differences.""" + differences = [] + + # Check keys present in dict1 but not dict2 + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + # Compare values for common keys + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts( + item1, item2, f"{current_path}[{i}]" + ) + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + # Perform the comparison + differences = compare_dicts(to_dict_output, raw_api_dict_filtered) + + # If there are differences, print them and fail + if differences: + diff_report = "\n".join( + differences[:20] + ) # Limit to first 20 differences + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + except USPTOApiNotFoundError: + pytest.skip( + f"Decision not found (404) for raw API comparison: {sample_petition_decision_id}" + ) + except USPTOApiError as e: + pytest.skip(f"Raw API comparison failed due to API error: {e}") def test_get_decision_by_invalid_id( self, petition_decisions_client: FinalPetitionDecisionsClient @@ -267,9 +495,7 @@ def test_download_decisions_json( if len(response.petition_decision_data) > 0: assert len(response.petition_decision_data) <= 2 - assert isinstance( - response.petition_decision_data[0], PetitionDecision - ) + assert isinstance(response.petition_decision_data[0], PetitionDecision) except USPTOApiError as e: pytest.skip(f"Download endpoint failed: {e}") @@ -295,7 +521,7 @@ def test_download_decisions_csv( assert file_path.endswith(".csv") # Read first line to verify CSV format - with open(file_path, "r", encoding="utf-8") as f: + with open(file_path, encoding="utf-8") as f: first_line = f.readline() # CSV should have headers with commas assert len(first_line) > 0 @@ -309,95 +535,66 @@ def test_paginate_decisions( ) -> None: """Test pagination through petition decisions.""" page_size = 5 - max_pages = 2 # Only test 2 pages to keep test fast + max_decisions = 10 # Only test first 10 decisions to keep test fast - pages_retrieved = 0 total_decisions = 0 try: - for response in petition_decisions_client.paginate_decisions( - limit=page_size, query="applicantName:*" + for decision in petition_decisions_client.paginate_decisions( + limit=page_size, query="firstApplicantName:*" ): - assert isinstance(response, PetitionDecisionResponse) - assert len(response.petition_decision_data_bag) <= page_size + assert isinstance(decision, PetitionDecision) + assert decision.petition_decision_record_identifier is not None - total_decisions += len(response.petition_decision_data_bag) - pages_retrieved += 1 + total_decisions += 1 - if pages_retrieved >= max_pages: + if total_decisions >= max_decisions: break - assert pages_retrieved > 0, "Should have retrieved at least one page" assert total_decisions > 0, "Should have retrieved at least one decision" except USPTOApiError as e: pytest.skip(f"Pagination test failed: {e}") - def test_decision_with_documents( - self, petition_decisions_client: FinalPetitionDecisionsClient - ) -> None: - """Test retrieving a decision that has associated documents.""" - # Search for decisions and find one with documents - response = petition_decisions_client.search_decisions(limit=20) - - if response.count == 0: - pytest.skip("No decisions available to test document retrieval") - - # Find a decision with documents - decision_with_docs = None - for decision in response.petition_decision_data_bag: - if decision.document_bag and len(decision.document_bag) > 0: - decision_with_docs = decision - break - - if not decision_with_docs: - pytest.skip("No decisions with documents found in first 20 results") - - # Verify document structure - assert decision_with_docs.document_bag is not None - assert len(decision_with_docs.document_bag) > 0 - - doc = decision_with_docs.document_bag[0] - assert isinstance(doc, PetitionDecisionDocument) - assert doc.document_identifier is not None - - def test_download_petition_document( - self, petition_decisions_client: FinalPetitionDecisionsClient + def test_decision_with_documents_and_download( + self, + petition_decisions_client: FinalPetitionDecisionsClient, + sample_petition_decision_id: str, ) -> None: - """Test downloading a petition decision document.""" - # Search for decisions with downloadable documents - response = petition_decisions_client.search_decisions(limit=20) - - if response.count == 0: - pytest.skip("No decisions available to test document download") - - # Find a document with download options - download_option = None - for decision in response.petition_decision_data_bag: - if decision.document_bag: - for doc in decision.document_bag: - if doc.download_option_bag and len(doc.download_option_bag) > 0: - download_option = doc.download_option_bag[0] - break - if download_option: - break - - if not download_option or not download_option.download_url: - pytest.skip("No downloadable documents found in first 20 results") - + """Test retrieving a decision with documents and downloading them.""" try: - file_path = petition_decisions_client.download_petition_document( - download_option, file_path=TEST_DOWNLOAD_DIR + # Get decision with documents + response = petition_decisions_client.get_decision_by_id( + petition_decision_record_identifier=sample_petition_decision_id, + include_documents=True, ) - assert file_path is not None - assert os.path.exists(file_path) - assert os.path.getsize(file_path) > 0 + assert response is not None + assert isinstance(response, PetitionDecision) + + if response and response.document_bag and len(response.document_bag) > 0: + doc = response.document_bag[0] + assert isinstance(doc, PetitionDecisionDocument) + assert doc.document_identifier is not None + + # If document has download options, test downloading + if doc.download_option_bag and len(doc.download_option_bag) > 0: + download_option = doc.download_option_bag[0] + if download_option.download_url: + file_path = ( + petition_decisions_client.download_petition_document( + download_option, destination_path=TEST_DOWNLOAD_DIR + ) + ) + + assert file_path is not None + assert os.path.exists(file_path) + assert os.path.getsize(file_path) > 0 + else: + pytest.fail("No decisions with documents found") except USPTOApiError as e: - pytest.skip(f"Document download failed: {e}") - except Exception as e: - pytest.skip(f"Document download failed with unexpected error: {e}") + pytest.fail(f"Document retrieval test failed with API error: {e}") def test_search_decisions_with_multiple_params( self, petition_decisions_client: FinalPetitionDecisionsClient @@ -411,76 +608,74 @@ def test_search_decisions_with_multiple_params( assert response is not None assert isinstance(response, PetitionDecisionResponse) - assert response.count >= 0 + assert response.count is not None and response.count >= 0 - if response.count > 0: + if response.count is not None and response.count > 0: assert len(response.petition_decision_data_bag) <= 10 - def test_search_decisions_response_fields( - self, petition_decisions_client: FinalPetitionDecisionsClient - ) -> None: - """Test that response includes expected fields.""" - response = petition_decisions_client.search_decisions(limit=5) - - assert response is not None - assert response.count is not None - assert response.request_identifier is not None - - if response.count > 0: - decision = response.petition_decision_data_bag[0] - - # Check for key fields (some may be None depending on the decision) - assert hasattr(decision, "petition_decision_record_identifier") - assert hasattr(decision, "application_number_text") - assert hasattr(decision, "decision_type_code") - assert hasattr(decision, "decision_date") - assert hasattr(decision, "document_bag") - assert hasattr(decision, "inventor_bag") - assert hasattr(decision, "rule_bag") - assert hasattr(decision, "statute_bag") - def test_decision_type_code_enum( self, petition_decisions_client: FinalPetitionDecisionsClient ) -> None: """Test that decision type codes are properly parsed into enums.""" - response = petition_decisions_client.search_decisions(limit=10) + try: + response = petition_decisions_client.search_decisions(limit=10) + + if response.count == 0: + pytest.fail("No decisions available to test decision type codes") + + # Find a decision with a decision_type_code + found = False + for decision in response.petition_decision_data_bag: + if decision.decision_type_code: + # Should be a valid DecisionTypeCode enum or string + assert isinstance( + decision.decision_type_code, (DecisionTypeCode, str) + ) + found = True + break - if response.count == 0: - pytest.skip("No decisions available to test decision type codes") + if not found: + pytest.fail("No decisions with decision_type_code found in 10 results") - # Find a decision with a decision_type_code - for decision in response.petition_decision_data_bag: - if decision.decision_type_code: - # Should be a valid DecisionTypeCode enum or string - assert isinstance(decision.decision_type_code, (DecisionTypeCode, str)) - break - else: - pytest.skip("No decisions with decision_type_code found") + except USPTOApiError as e: + pytest.fail(f"Decision type code test failed with API error: {e}") def test_document_direction_category_enum( - self, petition_decisions_client: FinalPetitionDecisionsClient + self, + petition_decisions_client: FinalPetitionDecisionsClient, + sample_petition_decision_id: str, ) -> None: """Test that document direction categories are properly parsed into enums.""" - response = petition_decisions_client.search_decisions(limit=20) + try: + # Get decision with documents - same pattern as test_decision_with_documents_and_download + response = petition_decisions_client.get_decision_by_id( + petition_decision_record_identifier=sample_petition_decision_id, + include_documents=True, + ) - if response.count == 0: - pytest.skip("No decisions available to test document direction categories") + assert response is not None + assert isinstance(response, PetitionDecision) - # Find a document with a direction category - found = False - for decision in response.petition_decision_data_bag: - if decision.document_bag: - for doc in decision.document_bag: - if doc.document_direction_category: + # Find a document with a direction category + found = False + if response and response.document_bag and len(response.document_bag) > 0: + for doc in response.document_bag: + if doc.direction_category: # Should be a valid DocumentDirectionCategory enum or string assert isinstance( - doc.document_direction_category, + doc.direction_category, (DocumentDirectionCategory, str), ) found = True break - if found: - break + else: + pytest.fail( + f"No documents found to test in decision: {sample_petition_decision_id}" + ) + if not found: + pytest.fail( + f"No documents with direction categories found to test in decision: {sample_petition_decision_id}" + ) - if not found: - pytest.skip("No documents with direction_category found") + except USPTOApiError as e: + pytest.fail(f"Document direction category test failed with API error: {e}") diff --git a/tests/integration/test_ptab_appeals_integration.py b/tests/integration/test_ptab_appeals_integration.py new file mode 100644 index 0000000..0a62848 --- /dev/null +++ b/tests/integration/test_ptab_appeals_integration.py @@ -0,0 +1,362 @@ +""" +Integration tests for the USPTO PTAB Appeals API client. + +This module contains integration tests that make real API calls to the USPTO PTAB Appeals API. +These tests are optional and are skipped by default unless the ENABLE_INTEGRATION_TESTS +environment variable is set to 'true'. +""" + +import os + +import pytest + +from pyUSPTO.clients import PTABAppealsClient +from pyUSPTO.config import USPTOConfig +from pyUSPTO.exceptions import USPTOApiError +from pyUSPTO.models.ptab import AppealMetaData, PTABAppealDecision, PTABAppealResponse + +# Skip all tests in this module unless ENABLE_INTEGRATION_TESTS is set to 'true' +pytestmark = pytest.mark.skipif( + os.environ.get("ENABLE_INTEGRATION_TESTS", "").lower() != "true", + reason="Integration tests are disabled. Set ENABLE_INTEGRATION_TESTS=true to enable.", +) + + +@pytest.fixture +def ptab_appeals_client(config: USPTOConfig) -> PTABAppealsClient: + """ + Create a PTABAppealsClient instance for integration tests. + + Args: + config: The configuration instance + + Returns: + PTABAppealsClient: A client instance + """ + return PTABAppealsClient(config=config) + + +class TestPTABAppealsIntegration: + """Integration tests for the PTABAppealsClient.""" + + def test_search_decisions_get(self, ptab_appeals_client: PTABAppealsClient) -> None: + """Test searching PTAB appeal decisions using GET method.""" + try: + response = ptab_appeals_client.search_decisions( + query="appealMetaData.applicationTypeCategory:Appeal", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_appeal_data_bag is not None + assert len(response.patent_appeal_data_bag) > 0 + assert len(response.patent_appeal_data_bag) <= 2 + + decision = response.patent_appeal_data_bag[0] + assert isinstance(decision, PTABAppealDecision) + assert decision.appeal_number is not None + else: + pytest.fail("There should always be a response to this query.") + + except USPTOApiError as e: + pytest.fail(f"PTAB Appeals API error during search_decisions GET: {e}") + + def test_search_decisions_with_convenience_params( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test searching appeal decisions with application number.""" + try: + # Use direct query since applicationNumberText is nested under appellantData + response = ptab_appeals_client.search_decisions( + query="appellantData.applicationNumberText:12608694", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_appeal_data_bag is not None + for decision in response.patent_appeal_data_bag: + assert isinstance(decision, PTABAppealDecision) + # Verify application type if metadata present + if decision.appeal_meta_data: + assert isinstance(decision.appeal_meta_data, AppealMetaData) + + except USPTOApiError as e: + pytest.fail( + f"PTAB Appeals API error during search_decisions with convenience params: {e}" + ) + + def test_search_decisions_post( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test searching PTAB appeal decisions using POST method.""" + post_body = { + "q": "appealMetaData.applicationTypeCategory:Appeal", + "pagination": {"offset": 0, "limit": 2}, + } + + try: + response = ptab_appeals_client.search_decisions(post_body=post_body) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_appeal_data_bag is not None + assert len(response.patent_appeal_data_bag) <= 2 + + except USPTOApiError as e: + pytest.fail(f"PTAB Appeals API error during search_decisions POST: {e}") + + def test_search_decisions_with_date_filters( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test searching appeal decisions with date range filters.""" + try: + # Use direct query with correct field name (decisionIssueDate, not decisionDate) + response = ptab_appeals_client.search_decisions( + query="decisionData.decisionIssueDate:[2014-01-01 TO 2020-12-31]", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_appeal_data_bag is not None + + except USPTOApiError as e: + pytest.fail( + f"PTAB Appeals API error during search_decisions with date filters: {e}" + ) + + def test_search_decisions_by_decision_type( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test searching appeal decisions by decision type.""" + try: + # Use direct query since decisionTypeCategory is nested under decisionData + response = ptab_appeals_client.search_decisions( + query="decisionData.decisionTypeCategory:Decision", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_appeal_data_bag is not None + for decision in response.patent_appeal_data_bag: + # Verify decision type if present + if decision.decision_data: + if decision.decision_data.decision_type_category: + assert ( + "Decision" + in decision.decision_data.decision_type_category + ) + + except USPTOApiError as e: + pytest.fail(f"PTAB Appeals API error during search by decision type: {e}") + + def test_search_decisions_by_appellant( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test searching appeal decisions by inventor name.""" + try: + # Search by inventor name (realPartyInInterestName contains inventor info) + response = ptab_appeals_client.search_decisions( + query="appellantData.inventorName:*", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_appeal_data_bag is not None + + except USPTOApiError as e: + pytest.fail(f"PTAB Appeals API error during search by appellant: {e}") + + def test_paginate_decisions(self, ptab_appeals_client: PTABAppealsClient) -> None: + """Test paginating through appeal decisions.""" + page_size = 5 + max_decisions = 10 # Only test first 10 decisions to keep test fast + + total_decisions = 0 + + try: + for decision in ptab_appeals_client.paginate_decisions( + query="appealMetaData.applicationTypeCategory:Appeal", + limit=page_size, + ): + assert isinstance(decision, PTABAppealDecision) + assert decision.appeal_number is not None + + total_decisions += 1 + + if total_decisions >= max_decisions: + break + + assert total_decisions > 0, "Should have retrieved at least one decision" + + except USPTOApiError as e: + pytest.fail(f"Pagination test failed with API error: {e}") + + def test_search_with_optional_params( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test searching with optional parameters like sort and facets.""" + try: + response = ptab_appeals_client.search_decisions( + query="appealMetaData.applicationTypeCategory:Appeal", + limit=2, + sort="appealNumber desc", + offset=0, + ) + + assert response is not None + assert isinstance(response, PTABAppealResponse) + assert response.count >= 0 + + except USPTOApiError as e: + pytest.fail( + f"PTAB Appeals API error during search with optional params: {e}" + ) + + def test_to_dict_matches_raw_api_response(self, api_key: str | None) -> None: + """Test that to_dict() output matches the original API response stored in raw_data. + + This test compares the to_dict() serialization with the original API response + to ensure that the model correctly reconstructs the API format. + """ + # Create a config with include_raw_data=True to preserve original API data + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = PTABAppealsClient(config=config_with_raw) + + try: + # Search for a known appeal number + response = client_with_raw.search_decisions( + query="appealNumber:2015000194", + limit=1, + ) + + if response is None or response.count == 0: + pytest.fail("No decision found for raw API comparison test") + + assert ( + response.raw_data is not None + ), "raw_data should be populated when include_raw_data=True" + + # PTAB models store raw_data as dict (not JSON string like other models) + assert isinstance( + response.raw_data, dict + ), "raw_data should be a dictionary" + + # Get the raw API response dict + raw_api_dict = response.raw_data + + # Convert the model back to dict + to_dict_output = response.to_dict() + + # Deep comparison function + def compare_dicts(dict1, dict2, path=""): + """Recursively compare two dictionaries and report differences.""" + differences = [] + + # Check keys present in dict1 but not dict2 + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + # Compare values for common keys + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts( + item1, item2, f"{current_path}[{i}]" + ) + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + # Perform the comparison + differences = compare_dicts(to_dict_output, raw_api_dict) + + # If there are differences, print them and fail + if differences: + diff_report = "\n".join( + differences[:20] + ) # Limit to first 20 differences + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + except USPTOApiError as e: + pytest.fail(f"Raw API comparison test failed with API error: {e}") + + def test_invalid_query_handling( + self, ptab_appeals_client: PTABAppealsClient + ) -> None: + """Test proper error handling with an invalid query.""" + try: + # Use an obviously malformed query + response = ptab_appeals_client.search_decisions( + query="INVALID_FIELD:value", limit=1 + ) + + # API may return 0 results instead of error for invalid field + assert isinstance(response, PTABAppealResponse) + + except USPTOApiError as e: + # This is acceptable - API may return error for invalid queries + assert e.status_code in [400, 404, 500] diff --git a/tests/integration/test_ptab_interferences_integration.py b/tests/integration/test_ptab_interferences_integration.py new file mode 100644 index 0000000..6fa2afb --- /dev/null +++ b/tests/integration/test_ptab_interferences_integration.py @@ -0,0 +1,417 @@ +""" +Integration tests for the USPTO PTAB Interferences API client. + +This module contains integration tests that make real API calls to the USPTO PTAB Interferences API. +These tests are optional and are skipped by default unless the ENABLE_INTEGRATION_TESTS +environment variable is set to 'true'. +""" + +import os + +import pytest + +from pyUSPTO.clients import PTABInterferencesClient +from pyUSPTO.config import USPTOConfig +from pyUSPTO.exceptions import USPTOApiError +from pyUSPTO.models.ptab import PTABInterferenceDecision, PTABInterferenceResponse + +# Skip all tests in this module unless ENABLE_INTEGRATION_TESTS is set to 'true' +pytestmark = pytest.mark.skipif( + os.environ.get("ENABLE_INTEGRATION_TESTS", "").lower() != "true", + reason="Integration tests are disabled. Set ENABLE_INTEGRATION_TESTS=true to enable.", +) + + +@pytest.fixture +def ptab_interferences_client(config: USPTOConfig) -> PTABInterferencesClient: + """ + Create a PTABInterferencesClient instance for integration tests. + + Args: + config: The configuration instance + + Returns: + PTABInterferencesClient: A client instance + """ + return PTABInterferencesClient(config=config) + + +class TestPTABInterferencesIntegration: + """Integration tests for the PTABInterferencesClient.""" + + def test_search_decisions_get( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching PTAB interference decisions using GET method.""" + try: + response = ptab_interferences_client.search_decisions( + query="interferenceNumber:*", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + assert len(response.patent_interference_data_bag) > 0 + assert len(response.patent_interference_data_bag) <= 2 + + decision = response.patent_interference_data_bag[0] + assert isinstance(decision, PTABInterferenceDecision) + assert decision.interference_number is not None + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search_decisions GET: {e}" + ) + + def test_search_decisions_with_convenience_params( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching interference decisions with convenience parameters.""" + try: + response = ptab_interferences_client.search_decisions( + interference_outcome_category_q="Final Decision", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + for decision in response.patent_interference_data_bag: + assert isinstance(decision, PTABInterferenceDecision) + # Verify outcome if document data present + if decision.document_data: + if decision.document_data.interference_outcome_category: + assert ( + "Final Decision" + in decision.document_data.interference_outcome_category + ) + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search_decisions with convenience params: {e}" + ) + + def test_search_decisions_post( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching PTAB interference decisions using POST method.""" + post_body = { + "q": "interferenceNumber:*", + "pagination": {"offset": 0, "limit": 2}, + } + + try: + response = ptab_interferences_client.search_decisions(post_body=post_body) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + assert len(response.patent_interference_data_bag) <= 2 + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search_decisions POST: {e}" + ) + + def test_search_decisions_with_date_filters( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching interference decisions with date range filters.""" + try: + # Use date range that matches actual interference data + response = ptab_interferences_client.search_decisions( + decision_date_from_q="2000-01-01", + decision_date_to_q="2010-12-31", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search_decisions with date filters: {e}" + ) + + def test_search_decisions_by_decision_type( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching interference decisions by decision type.""" + try: + response = ptab_interferences_client.search_decisions( + decision_type_category_q="Decision", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + for decision in response.patent_interference_data_bag: + # Verify decision type if present + if decision.document_data: + if decision.document_data.decision_type_category: + assert ( + "Decision" + in decision.document_data.decision_type_category + ) + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search by decision type: {e}" + ) + + def test_search_decisions_by_party( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching interference decisions by party name.""" + try: + # Search for senior or junior party with wildcard to ensure results + response = ptab_interferences_client.search_decisions( + senior_party_name_q="PATRICE", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + + except USPTOApiError as e: + pytest.fail(f"PTAB Interferences API error during search by party: {e}") + + def test_search_decisions_by_patent_number( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching interference decisions by patent number or application number.""" + try: + # Use application number which is more common than patent number + response = ptab_interferences_client.search_decisions( + query="seniorPartyData.applicationNumberText:10618977", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search by patent number: {e}" + ) + + def test_paginate_decisions( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test paginating through interference decisions.""" + try: + # Limit to small number to avoid long test times + max_results = 10 + results = [] + for decision in ptab_interferences_client.paginate_decisions( + query="interferenceNumber:*", + limit=5, + ): + results.append(decision) + if len(results) >= max_results: + break + + assert isinstance(results, list) + if len(results) > 0: + assert all(isinstance(d, PTABInterferenceDecision) for d in results) + assert len(results) <= max_results + + except USPTOApiError as e: + pytest.skip(f"PTAB Interferences API error during paginate_decisions: {e}") + + def test_search_with_optional_params( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching with optional parameters like sort and facets.""" + try: + response = ptab_interferences_client.search_decisions( + query="interferenceNumber:*", + limit=2, + sort="interferenceNumber desc", + offset=0, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search with optional params: {e}" + ) + + def test_search_with_style_name( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test searching by interference style name.""" + try: + response = ptab_interferences_client.search_decisions( + query="interferenceMetaData.interferenceStyleName:*", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABInterferenceResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_interference_data_bag is not None + + except USPTOApiError as e: + pytest.skip( + f"PTAB Interferences API error during search by style name: {e}" + ) + + def test_to_dict_matches_raw_api_response(self, api_key: str | None) -> None: + """Test that to_dict() output matches the original API response stored in raw_data. + + This test compares the to_dict() serialization with the original API response + to ensure that the model correctly reconstructs the API format. + """ + # Create a config with include_raw_data=True to preserve original API data + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = PTABInterferencesClient(config=config_with_raw) + + try: + # Search for any interference decision + response = client_with_raw.search_decisions( + query="interferenceNumber:*", + limit=1, + ) + + if response is None or response.count == 0: + pytest.skip( + "No interference decisions found for raw API comparison test" + ) + + assert ( + response.raw_data is not None + ), "raw_data should be populated when include_raw_data=True" + + # PTAB models store raw_data as dict (not JSON string like other models) + assert isinstance( + response.raw_data, dict + ), "raw_data should be a dictionary" + + # Get the raw API response dict + raw_api_dict = response.raw_data + + # Convert the model back to dict + to_dict_output = response.to_dict() + + # Deep comparison function + def compare_dicts(dict1, dict2, path=""): + """Recursively compare two dictionaries and report differences.""" + differences = [] + + # Check keys present in dict1 but not dict2 + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + # Compare values for common keys + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts( + item1, item2, f"{current_path}[{i}]" + ) + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + # Perform the comparison + differences = compare_dicts(to_dict_output, raw_api_dict) + + # If there are differences, print them and fail + if differences: + diff_report = "\n".join( + differences[:20] + ) # Limit to first 20 differences + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + except USPTOApiError as e: + pytest.skip(f"Raw API comparison test failed with API error: {e}") + + def test_invalid_query_handling( + self, ptab_interferences_client: PTABInterferencesClient + ) -> None: + """Test proper error handling with an invalid query.""" + try: + # Use an obviously malformed query + response = ptab_interferences_client.search_decisions( + query="INVALID_FIELD:value", limit=1 + ) + + # API may return 0 results instead of error for invalid field + assert isinstance(response, PTABInterferenceResponse) + + except USPTOApiError as e: + # This is acceptable - API may return error for invalid queries + assert e.status_code in [400, 404, 500] diff --git a/tests/integration/test_ptab_trials_integration.py b/tests/integration/test_ptab_trials_integration.py new file mode 100644 index 0000000..17cf43c --- /dev/null +++ b/tests/integration/test_ptab_trials_integration.py @@ -0,0 +1,599 @@ +""" +Integration tests for the USPTO PTAB Trials API client. + +This module contains integration tests that make real API calls to the USPTO PTAB Trials API. +These tests are optional and are skipped by default unless the ENABLE_INTEGRATION_TESTS +environment variable is set to 'true'. +""" + +import os + +import pytest + +from pyUSPTO.clients import PTABTrialsClient +from pyUSPTO.config import USPTOConfig +from pyUSPTO.exceptions import USPTOApiError +from pyUSPTO.models.ptab import ( + PTABTrialDocumentResponse, + PTABTrialProceeding, + PTABTrialProceedingResponse, +) + +# Skip all tests in this module unless ENABLE_INTEGRATION_TESTS is set to 'true' +pytestmark = pytest.mark.skipif( + os.environ.get("ENABLE_INTEGRATION_TESTS", "").lower() != "true", + reason="Integration tests are disabled. Set ENABLE_INTEGRATION_TESTS=true to enable.", +) + + +@pytest.fixture +def ptab_trials_client(config: USPTOConfig) -> PTABTrialsClient: + """ + Create a PTABTrialsClient instance for integration tests. + + Args: + config: The configuration instance + + Returns: + PTABTrialsClient: A client instance + """ + return PTABTrialsClient(config=config) + + +class TestPTABTrialsIntegration: + """Integration tests for the PTABTrialsClient.""" + + def test_search_proceedings_get(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test searching PTAB trial proceedings using GET method.""" + try: + response = ptab_trials_client.search_proceedings( + query="trialMetaData.trialStatusCategory:Instituted", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABTrialProceedingResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_proceeding_data_bag is not None + assert len(response.patent_trial_proceeding_data_bag) > 0 + assert len(response.patent_trial_proceeding_data_bag) <= 2 + + proceeding = response.patent_trial_proceeding_data_bag[0] + assert isinstance(proceeding, PTABTrialProceeding) + assert proceeding.trial_number is not None + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during search_proceedings GET: {e}") + + def test_search_proceedings_with_convenience_params( + self, ptab_trials_client: PTABTrialsClient + ) -> None: + """Test searching proceedings with convenience parameters.""" + try: + response = ptab_trials_client.search_proceedings( + trial_type_code_q="IPR", + trial_status_category_q="Instituted", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABTrialProceedingResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_proceeding_data_bag is not None + for proceeding in response.patent_trial_proceeding_data_bag: + assert isinstance(proceeding, PTABTrialProceeding) + if proceeding.trial_meta_data: + # Verify trial type if present + if proceeding.trial_meta_data.trial_type_code: + assert proceeding.trial_meta_data.trial_type_code == "IPR" + + except USPTOApiError as e: + pytest.skip( + f"PTAB Trials API error during search_proceedings with convenience params: {e}" + ) + + def test_search_proceedings_post( + self, ptab_trials_client: PTABTrialsClient + ) -> None: + """Test searching PTAB trial proceedings using POST method.""" + post_body = { + "q": "trialMetaData.trialTypeCode:IPR", + "pagination": {"offset": 0, "limit": 2}, + } + + try: + response = ptab_trials_client.search_proceedings(post_body=post_body) + + assert response is not None + assert isinstance(response, PTABTrialProceedingResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_proceeding_data_bag is not None + assert len(response.patent_trial_proceeding_data_bag) <= 2 + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during search_proceedings POST: {e}") + + def test_search_documents_get(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test searching PTAB trial documents using GET method.""" + try: + response = ptab_trials_client.search_documents( + query="documentData.documentTypeDescriptionText:Exhibit", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABTrialDocumentResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_document_data_bag is not None + assert len(response.patent_trial_document_data_bag) > 0 + assert len(response.patent_trial_document_data_bag) <= 2 + + document = response.patent_trial_document_data_bag[0] + assert document.trial_number is not None + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during search_documents GET: {e}") + + def test_search_documents_with_convenience_params( + self, ptab_trials_client: PTABTrialsClient + ) -> None: + """Test searching documents with convenience parameters.""" + try: + response = ptab_trials_client.search_documents( + document_type_name_q="Exhibit", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABTrialDocumentResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_document_data_bag is not None + + except USPTOApiError as e: + pytest.skip( + f"PTAB Trials API error during search_documents with convenience params: {e}" + ) + + def test_search_documents_post(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test searching PTAB trial documents using POST method.""" + post_body = { + "q": "trialMetaData.trialTypeCode:IPR", + "pagination": {"offset": 0, "limit": 1}, + } + + try: + response = ptab_trials_client.search_documents(post_body=post_body) + + assert response is not None + assert isinstance(response, PTABTrialDocumentResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_document_data_bag is not None + assert len(response.patent_trial_document_data_bag) <= 2 + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during search_documents POST: {e}") + + def test_search_decisions_get(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test searching PTAB trial decisions using GET method.""" + try: + response = ptab_trials_client.search_decisions( + query="decisionData.decisionTypeCategory:Final Written Decision", + limit=2, + ) + + assert response is not None + assert isinstance(response, PTABTrialDocumentResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_document_data_bag is not None + assert len(response.patent_trial_document_data_bag) > 0 + assert len(response.patent_trial_document_data_bag) <= 2 + + decision = response.patent_trial_document_data_bag[0] + assert decision.trial_number is not None + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during search_decisions GET: {e}") + + def test_search_decisions_with_convenience_params( + self, ptab_trials_client: PTABTrialsClient + ) -> None: + """Test searching decisions with convenience parameters.""" + try: + response = ptab_trials_client.search_decisions( + trial_type_code_q="IPR", + limit=1, + ) + + assert response is not None + assert isinstance(response, PTABTrialDocumentResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_document_data_bag is not None + for document in response.patent_trial_document_data_bag: + if document.trial_type_code: + assert document.trial_type_code == "IPR" + + except USPTOApiError as e: + pytest.skip( + f"PTAB Trials API error during search_decisions with convenience params: {e}" + ) + + def test_search_decisions_post(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test searching PTAB trial decisions using POST method.""" + post_body = { + "q": "trialMetaData.trialTypeCode:IPR", + "pagination": {"offset": 0, "limit": 2}, + } + + try: + response = ptab_trials_client.search_decisions(post_body=post_body) + + assert response is not None + assert isinstance(response, PTABTrialDocumentResponse) + assert response.count >= 0 + + if response.count > 0: + assert response.patent_trial_document_data_bag is not None + assert len(response.patent_trial_document_data_bag) <= 2 + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during search_decisions POST: {e}") + + def test_paginate_proceedings(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test paginating through trial proceedings.""" + try: + # Limit to small number to avoid long test times + max_results = 10 + results = [] + for proceeding in ptab_trials_client.paginate_proceedings( + query="trialMetaData.trialTypeCode:IPR", + limit=5, + ): + results.append(proceeding) + if len(results) >= max_results: + break + + assert isinstance(results, list) + if len(results) > 0: + assert all(isinstance(p, PTABTrialProceeding) for p in results) + assert len(results) <= max_results + + except USPTOApiError as e: + pytest.skip(f"PTAB Trials API error during paginate_proceedings: {e}") + + def test_to_dict_matches_raw_api_response_proceedings( + self, api_key: str | None + ) -> None: + """Test that to_dict() output matches the original API response for trial proceedings. + + This test compares the to_dict() serialization with the original API response + to ensure that the model correctly reconstructs the API format. + """ + # Create a config with include_raw_data=True to preserve original API data + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = PTABTrialsClient(config=config_with_raw) + + try: + # Search for trial proceedings + response = client_with_raw.search_proceedings( + query="trialMetaData.trialTypeCode:IPR", + limit=1, + ) + + if response is None or response.count == 0: + pytest.skip("No trial proceedings found for raw API comparison test") + + assert ( + response.raw_data is not None + ), "raw_data should be populated when include_raw_data=True" + + # PTAB models store raw_data as dict (not JSON string like other models) + assert isinstance( + response.raw_data, dict + ), "raw_data should be a dictionary" + + # Get the raw API response dict + raw_api_dict = response.raw_data + + # Convert the model back to dict + to_dict_output = response.to_dict() + + # Deep comparison function + def compare_dicts(dict1, dict2, path=""): + """Recursively compare two dictionaries and report differences.""" + differences = [] + + # Check keys present in dict1 but not dict2 + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + # Compare values for common keys + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts( + item1, item2, f"{current_path}[{i}]" + ) + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + # Perform the comparison + differences = compare_dicts(to_dict_output, raw_api_dict) + + # If there are differences, print them and fail + if differences: + diff_report = "\n".join( + differences[:20] + ) # Limit to first 20 differences + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + except USPTOApiError as e: + pytest.skip(f"Raw API comparison test failed with API error: {e}") + + def test_to_dict_matches_raw_api_response_documents( + self, api_key: str | None + ) -> None: + """Test that to_dict() output matches the original API response for trial documents. + + This test compares the to_dict() serialization with the original API response + to ensure that the model correctly reconstructs the API format. + """ + # Create a config with include_raw_data=True to preserve original API data + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = PTABTrialsClient(config=config_with_raw) + + try: + # Search for trial documents + response = client_with_raw.search_documents( + query="trialMetaData.trialTypeCode:IPR", + limit=1, + ) + + if response is None or response.count == 0: + pytest.fail("No trial documents found for raw API comparison test") + + assert ( + response.raw_data is not None + ), "raw_data should be populated when include_raw_data=True" + + # PTAB models store raw_data as dict (not JSON string like other models) + assert isinstance( + response.raw_data, dict + ), "raw_data should be a dictionary" + + # Get the raw API response dict + raw_api_dict = response.raw_data + + # Convert the model back to dict + to_dict_output = response.to_dict() + + # Deep comparison function + def compare_dicts(dict1, dict2, path=""): + """Recursively compare two dictionaries and report differences.""" + differences = [] + + # Check keys present in dict1 but not dict2 + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + # Compare values for common keys + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts( + item1, item2, f"{current_path}[{i}]" + ) + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + # Perform the comparison + differences = compare_dicts(to_dict_output, raw_api_dict) + + # If there are differences, print them and fail + if differences: + diff_report = "\n".join( + differences[:20] + ) # Limit to first 20 differences + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + except USPTOApiError as e: + pytest.fail(f"Raw API comparison test failed with API error: {e}") + + def test_to_dict_matches_raw_api_response_trials(self, api_key: str | None) -> None: + """Test that to_dict() output matches the original API response for trial decisions.""" + config_with_raw = USPTOConfig(api_key=api_key, include_raw_data=True) + client_with_raw = PTABTrialsClient(config=config_with_raw) + + try: + response = client_with_raw.search_decisions( + query="trialMetaData.trialTypeCode:IPR", + limit=1, + ) + + if response is None or response.count == 0: + pytest.fail("No trial decisions found for raw API comparison test") + + assert response.raw_data is not None + assert isinstance(response.raw_data, dict) + + raw_api_dict = response.raw_data + to_dict_output = response.to_dict() + + def compare_dicts(dict1, dict2, path=""): + differences = [] + keys1 = set(dict1.keys()) + keys2 = set(dict2.keys()) + + missing_in_dict2 = keys1 - keys2 + if missing_in_dict2: + differences.append( + f"Keys in to_dict but not in raw API at {path}: {missing_in_dict2}" + ) + + missing_in_dict1 = keys2 - keys1 + if missing_in_dict1: + differences.append( + f"Keys in raw API but not in to_dict at {path}: {missing_in_dict1}" + ) + + for key in keys1 & keys2: + val1 = dict1[key] + val2 = dict2[key] + current_path = f"{path}.{key}" if path else key + + if type(val1) is not type(val2): + differences.append( + f"Type mismatch at {current_path}: {type(val1).__name__} vs {type(val2).__name__}" + ) + elif isinstance(val1, dict): + differences.extend(compare_dicts(val1, val2, current_path)) + elif isinstance(val1, list): + if len(val1) != len(val2): + differences.append( + f"List length mismatch at {current_path}: {len(val1)} vs {len(val2)}" + ) + else: + for i, (item1, item2) in enumerate(zip(val1, val2)): + if isinstance(item1, dict) and isinstance(item2, dict): + differences.extend( + compare_dicts( + item1, item2, f"{current_path}[{i}]" + ) + ) + elif item1 != item2: + differences.append( + f"Value mismatch at {current_path}[{i}]: {item1!r} vs {item2!r}" + ) + elif val1 != val2: + differences.append( + f"Value mismatch at {current_path}: {val1!r} vs {val2!r}" + ) + + return differences + + differences = compare_dicts(to_dict_output, raw_api_dict) + + if differences: + diff_report = "\n".join(differences[:20]) + if len(differences) > 20: + diff_report += f"\n... and {len(differences) - 20} more differences" + pytest.fail( + f"to_dict() output does not match raw API response. Differences found:\n{diff_report}" + ) + + except USPTOApiError as e: + pytest.skip(f"Raw API comparison test failed with API error: {e}") + + def test_invalid_query_handling(self, ptab_trials_client: PTABTrialsClient) -> None: + """Test proper error handling with an invalid query.""" + try: + # Use an obviously malformed query + response = ptab_trials_client.search_proceedings( + query="INVALID_FIELD:value", limit=1 + ) + + # API may return 0 results instead of error for invalid field + assert isinstance(response, PTABTrialProceedingResponse) + + except USPTOApiError as e: + # This is acceptable - API may return error for invalid queries + assert e.status_code in [400, 404, 500] diff --git a/tests/models/test_bulk_data_models.py b/tests/models/test_bulk_data_models.py index f40e6aa..26a674a 100644 --- a/tests/models/test_bulk_data_models.py +++ b/tests/models/test_bulk_data_models.py @@ -4,7 +4,7 @@ This module contains consolidated tests for all classes in pyUSPTO.models.bulk_data. """ -from typing import Any, Dict +from typing import Any from pyUSPTO.models.bulk_data import BulkDataProduct, BulkDataResponse @@ -44,7 +44,7 @@ def test_bulk_data_product_from_empty_dict(self) -> None: class TestBulkDataModelToDict: """Tests for converting model objects to dictionaries.""" - def test_bulk_data_response_to_dict(self, bulk_data_sample: Dict[str, Any]) -> None: + def test_bulk_data_response_to_dict(self, bulk_data_sample: dict[str, Any]) -> None: """Test BulkDataResponse.to_dict method.""" # Create a BulkDataResponse from the sample data response = BulkDataResponse.from_dict(bulk_data_sample) diff --git a/tests/models/test_patent_data_models.py b/tests/models/test_patent_data_models.py index 439078a..3dfb6bf 100644 --- a/tests/models/test_patent_data_models.py +++ b/tests/models/test_patent_data_models.py @@ -5,20 +5,13 @@ """ import csv -import importlib import io -from datetime import date, datetime, timedelta, timezone, tzinfo -from typing import Any, Dict, List, Optional -from unittest.mock import MagicMock, patch import warnings -from zoneinfo import ZoneInfo, ZoneInfoNotFoundError +from datetime import date, datetime, timezone +from typing import Any import pytest -from pyUSPTO.warnings import ( - USPTODateParseWarning, - USPTOEnumParseWarning, -) from pyUSPTO.models.patent_data import ( ActiveIndicator, Address, @@ -54,19 +47,18 @@ StatusCodeSearchResponse, Telecommunication, parse_to_date, - parse_to_datetime_utc, - parse_yn_to_bool, - serialize_bool_to_yn, serialize_date, - serialize_datetime_as_iso, - to_camel_case, +) +from pyUSPTO.warnings import ( + USPTODateParseWarning, + USPTOEnumParseWarning, ) # --- Pytest Fixtures --- @pytest.fixture -def sample_address_data() -> Dict[str, Any]: +def sample_address_data() -> dict[str, Any]: return { "nameLineOneText": "Test Name", "nameLineTwoText": "Test Name 2", @@ -82,14 +74,11 @@ def sample_address_data() -> Dict[str, Any]: "countryName": "United States", "postalAddressCategory": "Mailing", "correspondentNameText": "Test Correspondent", - "countryOrStateCode": None, - "ictStateCode": None, - "ictCountryCode": None, } @pytest.fixture -def sample_telecommunication_data() -> Dict[str, Any]: +def sample_telecommunication_data() -> dict[str, Any]: return { "telecommunicationNumber": "555-123-4567", "extensionNumber": "123", @@ -98,7 +87,7 @@ def sample_telecommunication_data() -> Dict[str, Any]: @pytest.fixture -def sample_person_base_data() -> Dict[str, Any]: +def sample_person_base_data() -> dict[str, Any]: return { "firstName": "Test", "lastName": "Person", @@ -111,8 +100,8 @@ def sample_person_base_data() -> Dict[str, Any]: @pytest.fixture def sample_applicant_data( - sample_person_base_data: Dict[str, Any], sample_address_data: Dict[str, Any] -) -> Dict[str, Any]: + sample_person_base_data: dict[str, Any], sample_address_data: dict[str, Any] +) -> dict[str, Any]: data = sample_person_base_data.copy() data.update( { @@ -125,8 +114,8 @@ def sample_applicant_data( @pytest.fixture def sample_inventor_data( - sample_person_base_data: Dict[str, Any], sample_address_data: Dict[str, Any] -) -> Dict[str, Any]: + sample_person_base_data: dict[str, Any], sample_address_data: dict[str, Any] +) -> dict[str, Any]: data = sample_person_base_data.copy() data.update( { @@ -139,10 +128,10 @@ def sample_inventor_data( @pytest.fixture def sample_attorney_data( - sample_person_base_data: Dict[str, Any], - sample_address_data: Dict[str, Any], - sample_telecommunication_data: Dict[str, Any], -) -> Dict[str, Any]: + sample_person_base_data: dict[str, Any], + sample_address_data: dict[str, Any], + sample_telecommunication_data: dict[str, Any], +) -> dict[str, Any]: data = sample_person_base_data.copy() data.update( { @@ -157,7 +146,7 @@ def sample_attorney_data( @pytest.fixture -def sample_document_download_format_data() -> Dict[str, Any]: +def sample_document_download_format_data() -> dict[str, Any]: return { "mimeTypeIdentifier": "application/pdf", "downloadUrl": "https://example.com/doc.pdf", @@ -166,7 +155,7 @@ def sample_document_download_format_data() -> Dict[str, Any]: @pytest.fixture -def sample_document_meta_data_data() -> Dict[str, Any]: +def sample_document_meta_data_data() -> dict[str, Any]: return { "zipFileName": "test.zip", "productIdentifier": "PRODUCT1", @@ -177,7 +166,7 @@ def sample_document_meta_data_data() -> Dict[str, Any]: @pytest.fixture -def sample_parent_continuity_data() -> Dict[str, Any]: +def sample_parent_continuity_data() -> dict[str, Any]: return { "firstInventorToFileIndicator": True, "parentApplicationStatusCode": 150, @@ -192,7 +181,7 @@ def sample_parent_continuity_data() -> Dict[str, Any]: @pytest.fixture -def sample_child_continuity_data() -> Dict[str, Any]: +def sample_child_continuity_data() -> dict[str, Any]: return { "firstInventorToFileIndicator": True, "childApplicationStatusCode": 30, @@ -200,7 +189,6 @@ def sample_child_continuity_data() -> Dict[str, Any]: "childApplicationNumberText": "87654321", "childApplicationStatusDescriptionText": "Docketed New Case - Ready for Examination", "childApplicationFilingDate": "2022-01-01", - "childPatentNumber": None, "claimParentageTypeCode": "CON", "claimParentageTypeCodeDescriptionText": "Continuation", } @@ -208,10 +196,10 @@ def sample_child_continuity_data() -> Dict[str, Any]: @pytest.fixture def sample_application_meta_data( - sample_applicant_data: Dict[str, Any], - sample_inventor_data: Dict[str, Any], - sample_address_data: Dict[str, Any], -) -> Dict[str, Any]: + sample_applicant_data: dict[str, Any], + sample_inventor_data: dict[str, Any], + sample_address_data: dict[str, Any], +) -> dict[str, Any]: """ Provides a comprehensive dictionary of data for ApplicationMetaData, suitable for round-trip (from_dict -> to_dict) testing. @@ -279,8 +267,8 @@ def sample_application_meta_data( @pytest.fixture def patent_data_sample( - sample_application_meta_data: Dict[str, Any], -) -> Dict[str, Any]: + sample_application_meta_data: dict[str, Any], +) -> dict[str, Any]: """ Provides a sample dictionary representing a PatentDataResponse, suitable for testing. @@ -353,7 +341,7 @@ def patent_data_sample( @pytest.fixture -def sample_assignor_data() -> Dict[str, Any]: +def sample_assignor_data() -> dict[str, Any]: """Provides sample data for an Assignor.""" return { "assignorName": "Original Tech Holder Inc.", @@ -362,7 +350,7 @@ def sample_assignor_data() -> Dict[str, Any]: @pytest.fixture -def sample_assignee_data(sample_address_data: Dict[str, Any]) -> Dict[str, Any]: +def sample_assignee_data(sample_address_data: dict[str, Any]) -> dict[str, Any]: """Provides sample data for an Assignee.""" return { "assigneeNameText": "New Tech Acquirer LLC", @@ -372,10 +360,10 @@ def sample_assignee_data(sample_address_data: Dict[str, Any]) -> Dict[str, Any]: @pytest.fixture def sample_assignment_data( - sample_assignor_data: Dict[str, Any], - sample_assignee_data: Dict[str, Any], - sample_address_data: Dict[str, Any], -) -> Dict[str, Any]: + sample_assignor_data: dict[str, Any], + sample_assignee_data: dict[str, Any], + sample_address_data: dict[str, Any], +) -> dict[str, Any]: """Provides sample data for an Assignment.""" return { "reelNumber": "R00123", @@ -405,7 +393,7 @@ def sample_assignment_data( @pytest.fixture -def sample_document_download_format_data_for_doc_fixture() -> Dict[str, Any]: +def sample_document_download_format_data_for_doc_fixture() -> dict[str, Any]: """Provides sample data for DocumentDownloadFormat, specifically for the Document fixture.""" return { "mimeTypeIdentifier": "application/pdf", @@ -416,12 +404,12 @@ def sample_document_download_format_data_for_doc_fixture() -> Dict[str, Any]: @pytest.fixture def sample_document_data( - sample_document_download_format_data_for_doc_fixture: Dict[str, Any], -) -> Dict[str, Any]: + sample_document_download_format_data_for_doc_fixture: dict[str, Any], +) -> dict[str, Any]: """Provides sample data for a Document.""" return { "applicationNumberText": "16000001", - "officialDate": "2023-03-15T10:30:00Z", + "officialDate": "2024-12-10T00:00:00.000-0500", "documentIdentifier": "OFFICE_ACTION_NON_FINAL", "documentCode": "CTNF", "documentCodeDescriptionText": "Non-Final Rejection", @@ -438,14 +426,14 @@ def sample_document_data( @pytest.fixture -def sample_pta_history_data() -> Dict[str, Any]: +def sample_pta_history_data() -> dict[str, Any]: """Provides sample data for PatentTermAdjustmentHistoryData.""" return { "eventDate": "2022-05-01", "applicantDayDelayQuantity": 5.0, "eventDescriptionText": "Applicant Delay - Late Response", "eventSequenceNumber": 2.0, - "ipOfficeDayDelayQuantity": 0.0, + "ipOfficeAdjustmentDelayQuantity": 0.0, "originatingEventSequenceNumber": 1.0, "ptaPTECode": "APL", } @@ -453,8 +441,8 @@ def sample_pta_history_data() -> Dict[str, Any]: @pytest.fixture def sample_patent_term_adjustment_data( - sample_pta_history_data: Dict[str, Any], -) -> Dict[str, Any]: + sample_pta_history_data: dict[str, Any], +) -> dict[str, Any]: """Provides sample data for PatentTermAdjustmentData.""" return { "aDelayQuantity": 100.0, @@ -466,7 +454,7 @@ def sample_patent_term_adjustment_data( "grantDate": "2023-11-20", "nonOverlappingDayQuantity": 120.0, "overlappingDayQuantity": 20.0, - "ipOfficeDayDelayQuantity": 130.0, + "ipOfficeAdjustmentDelayQuantity": 130.0, "patentTermAdjustmentHistoryDataBag": [ sample_pta_history_data, { @@ -474,7 +462,7 @@ def sample_patent_term_adjustment_data( "applicantDayDelayQuantity": 0.0, "eventDescriptionText": "USPTO Delay - Examination", "eventSequenceNumber": 1.0, - "ipOfficeDayDelayQuantity": 15.0, + "ipOfficeAdjustmentDelayQuantity": 15.0, "originatingEventSequenceNumber": 0.0, "ptaPTECode": "PTO", }, @@ -484,8 +472,8 @@ def sample_patent_term_adjustment_data( @pytest.fixture def sample_customer_number_correspondence_data( - sample_address_data: Dict[str, Any], sample_telecommunication_data: Dict[str, Any] -) -> Dict[str, Any]: + sample_address_data: dict[str, Any], sample_telecommunication_data: dict[str, Any] +) -> dict[str, Any]: """Provides sample data for CustomerNumberCorrespondence.""" return { "patronIdentifier": 778899, @@ -497,9 +485,9 @@ def sample_customer_number_correspondence_data( @pytest.fixture def sample_record_attorney_data( - sample_customer_number_correspondence_data: Dict[str, Any], - sample_attorney_data: Dict[str, Any], -) -> Dict[str, Any]: + sample_customer_number_correspondence_data: dict[str, Any], + sample_attorney_data: dict[str, Any], +) -> dict[str, Any]: """Provides sample data for RecordAttorney.""" attorney_2_data = sample_attorney_data.copy() attorney_2_data["registrationNumber"] = "67890" @@ -507,9 +495,7 @@ def sample_record_attorney_data( attorney_2_data["lastName"] = "Practitioner" return { - "customerNumberCorrespondenceData": [ - sample_customer_number_correspondence_data - ], + "customerNumberCorrespondenceData": sample_customer_number_correspondence_data, "powerOfAttorneyBag": [sample_attorney_data], "attorneyBag": [ sample_attorney_data, @@ -554,7 +540,7 @@ class TestDocumentDownloadFormat: """Tests for the DocumentDownloadFormat class.""" def test_document_download_format_from_dict( - self, sample_document_download_format_data: Dict[str, Any] + self, sample_document_download_format_data: dict[str, Any] ) -> None: fmt = DocumentFormat.from_dict(sample_document_download_format_data) assert ( @@ -568,7 +554,7 @@ def test_document_download_format_from_dict( ) def test_document_download_format_to_dict( - self, sample_document_download_format_data: Dict[str, Any] + self, sample_document_download_format_data: dict[str, Any] ) -> None: fmt = DocumentFormat( mime_type_identifier=sample_document_download_format_data[ @@ -614,7 +600,7 @@ def test_document_from_dict_basic(self) -> None: data = { "documentIdentifier": "doc123", "documentCode": "CODE_X", - "officialDate": "2023-03-15T10:30:00Z", + "officialDate": "2024-12-10T00:00:00.000-0500", "documentDirectionCategory": "INCOMING", "downloadOptionBag": [ { @@ -628,9 +614,7 @@ def test_document_from_dict_basic(self) -> None: assert doc.document_identifier == "doc123" assert doc.document_code == "CODE_X" - assert doc.official_date == datetime( - 2023, 3, 15, 10, 30, 0, tzinfo=timezone.utc - ) + assert doc.official_date == datetime(2024, 12, 10, 5, 0, 0, tzinfo=timezone.utc) assert doc.direction_category == DirectionCategory.INCOMING assert len(doc.document_formats) == 1 assert doc.document_formats[0].mime_type_identifier == "application/pdf" @@ -639,7 +623,7 @@ def test_document_to_dict_basic(self) -> None: doc = Document( document_identifier="doc123", document_code="CODE_X", - official_date=datetime(2023, 3, 15, 10, 30, 0, tzinfo=timezone.utc), + official_date=datetime(2024, 12, 10, 5, 0, 0, tzinfo=timezone.utc), direction_category=DirectionCategory.OUTGOING, document_formats=[ DocumentFormat(mime_type_identifier="image/tiff", page_total_quantity=5) @@ -647,7 +631,7 @@ def test_document_to_dict_basic(self) -> None: ) data = doc.to_dict() assert data["documentIdentifier"] == "doc123" - assert data["officialDate"] == "2023-03-15T10:30:00Z" + assert data["officialDate"] == "2024-12-10T00:00:00.000-0500" assert data["documentDirectionCategory"] == "OUTGOING" assert len(data["downloadOptionBag"]) == 1 assert data["downloadOptionBag"][0]["mimeTypeIdentifier"] == "image/tiff" @@ -687,7 +671,7 @@ def test_document_repr(self) -> None: expected = "Document(id=doc123, code=CODE_X, date=2023-03-15)" assert repr(doc) == expected - def test_document_roundtrip(self, sample_document_data: Dict[str, Any]) -> None: + def test_document_roundtrip(self, sample_document_data: dict[str, Any]) -> None: """ Tests the round-trip serialization for the Document class. """ @@ -843,7 +827,7 @@ def test_document_bag_repr(self) -> None: class TestAddress: """Tests for the Address class.""" - def test_address_from_dict(self, sample_address_data: Dict[str, Any]) -> None: + def test_address_from_dict(self, sample_address_data: dict[str, Any]) -> None: address = Address.from_dict(sample_address_data) for key, value in sample_address_data.items(): snake_case_key = "".join( @@ -851,7 +835,7 @@ def test_address_from_dict(self, sample_address_data: Dict[str, Any]) -> None: ).lstrip("_") assert getattr(address, snake_case_key) == value - def test_address_to_dict(self, sample_address_data: Dict[str, Any]) -> None: + def test_address_to_dict(self, sample_address_data: dict[str, Any]) -> None: address = Address( **{ "".join(["_" + i.lower() if i.isupper() else i for i in k]).lstrip( @@ -869,33 +853,15 @@ def test_address_from_dict_empty(self) -> None: def test_address_to_dict_empty(self) -> None: address = Address() - expected_camel_case_empty_dict = { - "nameLineOneText": None, - "nameLineTwoText": None, - "addressLineOneText": None, - "addressLineTwoText": None, - "addressLineThreeText": None, - "addressLineFourText": None, - "geographicRegionName": None, - "geographicRegionCode": None, - "postalCode": None, - "cityName": None, - "countryCode": None, - "countryName": None, - "postalAddressCategory": None, - "correspondentNameText": None, - "countryOrStateCode": None, - "ictStateCode": None, - "ictCountryCode": None, - } - assert address.to_dict() == expected_camel_case_empty_dict + # Empty address should serialize to empty dict (None values are filtered out) + assert address.to_dict() == {} class TestTelecommunication: """Tests for the Telecommunication class.""" def test_telecommunication_from_dict( - self, sample_telecommunication_data: Dict[str, Any] + self, sample_telecommunication_data: dict[str, Any] ) -> None: telecom = Telecommunication.from_dict(sample_telecommunication_data) assert ( @@ -911,7 +877,7 @@ def test_telecommunication_from_dict( ) def test_telecommunication_to_dict( - self, sample_telecommunication_data: Dict[str, Any] + self, sample_telecommunication_data: dict[str, Any] ) -> None: telecom = Telecommunication( telecommunication_number=sample_telecommunication_data[ @@ -930,17 +896,14 @@ def test_telecommunication_from_dict_empty(self) -> None: def test_telecommunication_to_dict_empty(self) -> None: telecom = Telecommunication() - assert telecom.to_dict() == { - "telecommunicationNumber": None, - "extensionNumber": None, - "telecomTypeCode": None, - } + # Empty telecommunication should serialize to empty dict (None values are filtered out) + assert telecom.to_dict() == {} class TestPerson: """Tests for the Person base class.""" - def test_person_to_dict(self, sample_person_base_data: Dict[str, Any]) -> None: + def test_person_to_dict(self, sample_person_base_data: dict[str, Any]) -> None: data_snake = { "first_name": sample_person_base_data["firstName"], "middle_name": sample_person_base_data.get("middleName"), @@ -967,7 +930,7 @@ def test_person_to_dict_with_nones(self) -> None: class TestApplicant: """Tests for the Applicant class.""" - def test_applicant_from_dict(self, sample_applicant_data: Dict[str, Any]) -> None: + def test_applicant_from_dict(self, sample_applicant_data: dict[str, Any]) -> None: applicant = Applicant.from_dict(sample_applicant_data) assert applicant.first_name == sample_applicant_data["firstName"] assert applicant.last_name == sample_applicant_data["lastName"] @@ -981,7 +944,7 @@ def test_applicant_from_dict(self, sample_applicant_data: Dict[str, Any]) -> Non ) def test_applicant_to_dict( - self, sample_applicant_data: Dict[str, Any], sample_address_data: Dict[str, Any] + self, sample_applicant_data: dict[str, Any], sample_address_data: dict[str, Any] ) -> None: applicant = Applicant( first_name=sample_applicant_data["firstName"], @@ -1015,7 +978,7 @@ def test_applicant_to_dict_empty_fields(self) -> None: class TestInventor: """Tests for the Inventor class.""" - def test_inventor_from_dict(self, sample_inventor_data: Dict[str, Any]) -> None: + def test_inventor_from_dict(self, sample_inventor_data: dict[str, Any]) -> None: inventor = Inventor.from_dict(sample_inventor_data) assert inventor.first_name == sample_inventor_data["firstName"] assert inventor.last_name == sample_inventor_data["lastName"] @@ -1027,7 +990,7 @@ def test_inventor_from_dict(self, sample_inventor_data: Dict[str, Any]) -> None: ) def test_inventor_to_dict( - self, sample_inventor_data: Dict[str, Any], sample_address_data: Dict[str, Any] + self, sample_inventor_data: dict[str, Any], sample_address_data: dict[str, Any] ) -> None: inventor = Inventor( first_name=sample_inventor_data["firstName"], @@ -1055,7 +1018,7 @@ def test_inventor_to_dict_empty_bag(self) -> None: class TestAttorney: """Tests for the Attorney class.""" - def test_attorney_from_dict(self, sample_attorney_data: Dict[str, Any]) -> None: + def test_attorney_from_dict(self, sample_attorney_data: dict[str, Any]) -> None: attorney = Attorney.from_dict(sample_attorney_data) assert attorney.first_name == sample_attorney_data["firstName"] assert ( @@ -1077,9 +1040,9 @@ def test_attorney_from_dict(self, sample_attorney_data: Dict[str, Any]) -> None: def test_attorney_to_dict( self, - sample_attorney_data: Dict[str, Any], - sample_address_data: Dict[str, Any], - sample_telecommunication_data: Dict[str, Any], + sample_attorney_data: dict[str, Any], + sample_address_data: dict[str, Any], + sample_telecommunication_data: dict[str, Any], ) -> None: attorney = Attorney( first_name=sample_attorney_data["firstName"], @@ -1147,8 +1110,8 @@ class TestCustomerNumberCorrespondence: def test_customer_number_correspondence_from_dict( self, - sample_address_data: Dict[str, Any], - sample_telecommunication_data: Dict[str, Any], + sample_address_data: dict[str, Any], + sample_telecommunication_data: dict[str, Any], ) -> None: data = { "patronIdentifier": 12345, @@ -1172,8 +1135,8 @@ def test_customer_number_correspondence_from_dict( def test_customer_number_correspondence_to_dict( self, - sample_address_data: Dict[str, Any], - sample_telecommunication_data: Dict[str, Any], + sample_address_data: dict[str, Any], + sample_telecommunication_data: dict[str, Any], ) -> None: cust_corr = CustomerNumberCorrespondence( patron_identifier=54321, @@ -1203,19 +1166,20 @@ class TestRecordAttorney: """Tests for the RecordAttorney class.""" def test_record_attorney_from_dict( - self, sample_attorney_data: Dict[str, Any] + self, sample_attorney_data: dict[str, Any] ) -> None: data = { - "customerNumberCorrespondenceData": [ - {"patronIdentifier": 12345, "organizationStandardName": "Test Law Firm"} - ], + "customerNumberCorrespondenceData": { + "patronIdentifier": 12345, + "organizationStandardName": "Test Law Firm", + }, "powerOfAttorneyBag": [sample_attorney_data], "attorneyBag": [sample_attorney_data], } record_attorney = RecordAttorney.from_dict(data) - assert len(record_attorney.customer_number_correspondence_data) == 1 + assert record_attorney.customer_number_correspondence_data is not None assert ( - record_attorney.customer_number_correspondence_data[0].patron_identifier + record_attorney.customer_number_correspondence_data.patron_identifier == 12345 ) assert len(record_attorney.power_of_attorney_bag) == 1 @@ -1230,19 +1194,19 @@ def test_record_attorney_from_dict( ) def test_record_attorney_to_dict( - self, sample_attorney_data: Dict[str, Any] + self, sample_attorney_data: dict[str, Any] ) -> None: attorney_obj = Attorney.from_dict(sample_attorney_data) cust_corr_obj = CustomerNumberCorrespondence(patron_identifier=999) record_attorney = RecordAttorney( - customer_number_correspondence_data=[cust_corr_obj], + customer_number_correspondence_data=cust_corr_obj, power_of_attorney_bag=[attorney_obj], attorney_bag=[attorney_obj], ) data = record_attorney.to_dict() - assert len(data["customerNumberCorrespondenceData"]) == 1 - assert data["customerNumberCorrespondenceData"][0]["patronIdentifier"] == 999 + assert "customerNumberCorrespondenceData" in data + assert data["customerNumberCorrespondenceData"]["patronIdentifier"] == 999 assert len(data["powerOfAttorneyBag"]) == 1 assert ( data["powerOfAttorneyBag"][0]["firstName"] @@ -1252,15 +1216,16 @@ def test_record_attorney_to_dict( def test_record_attorney_to_dict_all_empty_bags(self) -> None: record_attorney = RecordAttorney( - customer_number_correspondence_data=[], + customer_number_correspondence_data=None, power_of_attorney_bag=[], attorney_bag=[], ) data = record_attorney.to_dict() - assert data == {} + # Empty lists are now included to match API behavior + assert data == {"powerOfAttorneyBag": [], "attorneyBag": []} def test_record_attorney_roundtrip( - self, sample_record_attorney_data: Dict[str, Any] + self, sample_record_attorney_data: dict[str, Any] ) -> None: """ Tests the round-trip serialization for the RecordAttorney class. @@ -1292,7 +1257,7 @@ def test_assignor_to_dict(self) -> None: class TestAssignee: """Tests for the Assignee class.""" - def test_assignee_from_dict(self, sample_address_data: Dict[str, Any]) -> None: + def test_assignee_from_dict(self, sample_address_data: dict[str, Any]) -> None: data = { "assigneeNameText": "Test Company Inc.", "assigneeAddress": sample_address_data, @@ -1302,7 +1267,7 @@ def test_assignee_from_dict(self, sample_address_data: Dict[str, Any]) -> None: assert assignee.assignee_address is not None assert assignee.assignee_address.city_name == sample_address_data["cityName"] - def test_assignee_to_dict(self, sample_address_data: Dict[str, Any]) -> None: + def test_assignee_to_dict(self, sample_address_data: dict[str, Any]) -> None: address_obj = Address.from_dict(sample_address_data) assignee = Assignee( assignee_name_text="Another Co.", assignee_address=address_obj @@ -1320,7 +1285,7 @@ def test_assignee_to_dict_no_address(self) -> None: class TestAssignment: """Tests for the Assignment class.""" - def test_assignment_from_dict(self, sample_address_data: Dict[str, Any]) -> None: + def test_assignment_from_dict(self, sample_address_data: dict[str, Any]) -> None: data = { "reelNumber": 12345, "frameNumber": 67890, @@ -1367,7 +1332,7 @@ def test_assignment_from_dict(self, sample_address_data: Dict[str, Any]) -> None == sample_address_data["cityName"] ) - def test_assignment_to_dict(self, sample_address_data: Dict[str, Any]) -> None: + def test_assignment_to_dict(self, sample_address_data: dict[str, Any]) -> None: address_obj = Address.from_dict(sample_address_data) assignor_obj = Assignor(assignor_name="Signer", execution_date=date(2023, 1, 1)) assignee_obj = Assignee( @@ -1396,9 +1361,14 @@ def test_assignment_to_dict(self, sample_address_data: Dict[str, Any]) -> None: assert len(data["assignorBag"]) == 1 assert len(data["assigneeBag"]) == 1 assert data["correspondenceAddress"] is not None - assert data["correspondenceAddress"]["cityName"] == sample_address_data["cityName"] + assert ( + data["correspondenceAddress"]["cityName"] == sample_address_data["cityName"] + ) assert data["domesticRepresentative"] is not None - assert data["domesticRepresentative"]["cityName"] == sample_address_data["cityName"] + assert ( + data["domesticRepresentative"]["cityName"] + == sample_address_data["cityName"] + ) def test_assignment_to_dict_empty_bags(self) -> None: assignment = Assignment( @@ -1412,12 +1382,13 @@ def test_assignment_to_dict_empty_bags(self) -> None: assert data["reelNumber"] == 2002 assert data["assignorBag"] == [] assert data["assigneeBag"] == [] - assert data["correspondenceAddress"] is None - assert data["domesticRepresentative"] is None + # None values are filtered out, so these keys won't exist + assert "correspondenceAddress" not in data + assert "domesticRepresentative" not in data def test_assignment_roundtrip( self, - sample_assignment_data: Dict[str, Any], + sample_assignment_data: dict[str, Any], ) -> None: """ Tests the round-trip serialization for the Assignment class. @@ -1485,14 +1456,14 @@ def test_continuity_to_dict(self) -> None: # This is different from other to_dict methods that use serialize_date. # For this test, we compare against the raw asdict output after filtering. expected_camel_asdict = cont.to_dict() - assert cont.to_dict() == expected_camel_asdict + assert expected_data == expected_camel_asdict class TestParentContinuity: """Tests for the ParentContinuity class.""" def test_parent_continuity_from_dict( - self, sample_parent_continuity_data: Dict[str, Any] + self, sample_parent_continuity_data: dict[str, Any] ) -> None: pc = ParentContinuity.from_dict(sample_parent_continuity_data) assert pc.first_inventor_to_file_indicator is True @@ -1503,7 +1474,7 @@ def test_parent_continuity_from_dict( assert pc.application_number_text == "12345678" def test_parent_continuity_to_dict( - self, sample_parent_continuity_data: Dict[str, Any] + self, sample_parent_continuity_data: dict[str, Any] ) -> None: pc_instance = ParentContinuity.from_dict(sample_parent_continuity_data) data = pc_instance.to_dict() @@ -1519,7 +1490,7 @@ class TestChildContinuity: """Tests for the ChildContinuity class.""" def test_child_continuity_from_dict( - self, sample_child_continuity_data: Dict[str, Any] + self, sample_child_continuity_data: dict[str, Any] ) -> None: cc = ChildContinuity.from_dict(sample_child_continuity_data) assert cc.first_inventor_to_file_indicator is True @@ -1530,7 +1501,7 @@ def test_child_continuity_from_dict( assert cc.application_number_text == "87654321" def test_child_continuity_to_dict( - self, sample_child_continuity_data: Dict[str, Any] + self, sample_child_continuity_data: dict[str, Any] ) -> None: cc_instance = ChildContinuity.from_dict(sample_child_continuity_data) data = cc_instance.to_dict() @@ -1538,6 +1509,8 @@ def test_child_continuity_to_dict( expected_data["childApplicationFilingDate"] = serialize_date( parse_to_date(expected_data["childApplicationFilingDate"]) ) + # Remove None values from expected data to match to_dict() filtering behavior + expected_data = {k: v for k, v in expected_data.items() if v is not None} assert data == expected_data @@ -1558,6 +1531,7 @@ def test_pta_history_from_dict(self) -> None: assert pta_hist.event_date == date(2022, 1, 1) assert pta_hist.applicant_day_delay_quantity == 10.0 assert pta_hist.event_description_text == "Response to Office Action" + assert pta_hist.ip_office_day_delay_quantity == 5.0 def test_pta_history_to_dict(self) -> None: pta_hist = PatentTermAdjustmentHistoryData( @@ -1567,11 +1541,16 @@ def test_pta_history_to_dict(self) -> None: event_sequence_number=1.0, originating_event_sequence_number=0.0, pta_pte_code="A", + ip_office_day_delay_quantity=5.0, ) data = pta_hist.to_dict() assert data["eventDate"] == "2022-01-01" assert data["applicantDayDelayQuantity"] == 10.0 assert data["eventDescriptionText"] == "Response to Office Action" + assert data["eventSequenceNumber"] == 1.0 + assert data["originatingEventSequenceNumber"] == 0.0 + assert data["ptaPTECode"] == "A" + assert data["ipOfficeDayDelayQuantity"] == 5.0 class TestPatentTermAdjustmentData: @@ -1581,11 +1560,13 @@ def test_pta_data_from_dict(self) -> None: data = { "aDelayQuantity": 100.0, "adjustmentTotalQuantity": 150.0, + "ipOfficeAdjustmentDelayQuantity": 2.0, "patentTermAdjustmentHistoryDataBag": [{"eventDate": "2022-01-01"}], } pta_data = PatentTermAdjustmentData.from_dict(data) assert pta_data.a_delay_quantity == 100.0 assert pta_data.adjustment_total_quantity == 150.0 + assert pta_data.ip_office_adjustment_delay_quantity == 2.0 assert len(pta_data.patent_term_adjustment_history_data_bag) == 1 assert pta_data.patent_term_adjustment_history_data_bag[0].event_date == date( 2022, 1, 1 @@ -1616,7 +1597,7 @@ def test_pta_data_to_dict_empty_history_bag(self) -> None: assert "patentTermAdjustmentHistoryDataBag" not in data def test_patent_term_adjustment_data_roundtrip( - self, sample_patent_term_adjustment_data: Dict[str, Any] + self, sample_patent_term_adjustment_data: dict[str, Any] ) -> None: """ Tests the round-trip serialization for the PatentTermAdjustmentData class. @@ -1661,7 +1642,7 @@ class TestDocumentMetaData: """Tests for the DocumentMetaData class.""" def test_document_meta_data_from_dict( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: doc_meta = PrintedMetaData.from_dict(sample_document_meta_data_data) assert doc_meta.zip_file_name == sample_document_meta_data_data["zipFileName"] @@ -1670,7 +1651,7 @@ def test_document_meta_data_from_dict( ) def test_document_meta_data_to_dict( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: doc_meta = PrintedMetaData( zip_file_name=sample_document_meta_data_data["zipFileName"], @@ -1681,7 +1662,7 @@ def test_document_meta_data_to_dict( ) data = doc_meta.to_dict() assert data["zipFileName"] == sample_document_meta_data_data["zipFileName"] - assert data["fileCreateDateTime"] == "2023-01-01T12:00:00Z" + assert data["fileCreateDateTime"] == "2023-01-01T07:00:00" def test_document_meta_data_with_null_input(self) -> None: doc_meta = PrintedMetaData.from_dict({}) @@ -1778,11 +1759,11 @@ def test_application_meta_data_to_dict(self) -> None: } app_meta_aia = ApplicationMetaData(first_inventor_to_file_indicator=True) - data_aia = app_meta_aia.to_dict() + app_meta_aia.to_dict() def test_application_meta_data_roundtrip_object_comparison( self, - sample_application_meta_data: Dict[str, Any], + sample_application_meta_data: dict[str, Any], ) -> None: original_app_meta = ApplicationMetaData.from_dict( data=sample_application_meta_data @@ -1809,7 +1790,7 @@ class TestPatentFileWrapper: """Tests for the PatentFileWrapper class.""" def test_patent_file_wrapper_from_dict( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: data = { "applicationNumberText": "12345678", @@ -1834,7 +1815,7 @@ def test_patent_file_wrapper_from_dict( ) def test_patent_file_wrapper_to_dict( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: app_meta_obj = ApplicationMetaData(invention_title="Title") pgpub_obj = PrintedMetaData.from_dict(sample_document_meta_data_data) @@ -1854,10 +1835,10 @@ def test_patent_file_wrapper_to_dict( data["pgpubDocumentMetaData"]["zipFileName"] == sample_document_meta_data_data["zipFileName"] ) - assert data["lastIngestionDateTime"] == "2023-02-02T11:00:00Z" + assert data["lastIngestionDateTime"] == "2023-02-02T06:00:00" def test_patent_file_wrapper_with_grant_document_meta_data( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: data = { "applicationNumberText": "12345678", @@ -1883,7 +1864,7 @@ def test_empty_patent_file_wrapper_to_dict(self) -> None: def test_patent_file_wrapper_roundtrip( self, - patent_data_sample: Dict[str, Any], + patent_data_sample: dict[str, Any], ) -> None: """ Tests the round-trip serialization (from_dict -> to_dict -> from_dict) @@ -1933,7 +1914,7 @@ def test_patent_data_response_to_dict(self) -> None: ) def test_patent_data_response_to_dict_with_sample( - self, patent_data_sample: Dict[str, Any] + self, patent_data_sample: dict[str, Any] ) -> None: response = PatentDataResponse.from_dict(patent_data_sample) result = response.to_dict() @@ -1958,10 +1939,10 @@ def test_empty_patent_data_response_to_dict(self) -> None: response = PatentDataResponse(count=0, patent_file_wrapper_data_bag=[]) result = response.to_dict() assert result["count"] == 0 - assert result["patentFileWrapperDataBag"] == [] + assert "patentFileWrapperDataBag" not in result def test_patent_data_response_to_csv( - self, patent_data_sample: Dict[str, Any] + self, patent_data_sample: dict[str, Any] ) -> None: """Tests the to_csv method of PatentDataResponse.""" response = PatentDataResponse.from_dict(patent_data_sample) @@ -2047,7 +2028,7 @@ def test_patent_data_response_to_csv_missing_metadata(self) -> None: ) csv_string = response.to_csv() reader = csv.reader(io.StringIO(csv_string)) - header_row = next(reader) # Skip header + next(reader) # Skip header with pytest.raises(StopIteration): # Should skip the row with missing meta next(reader) @@ -2221,8 +2202,8 @@ class TestApplicationContinuityData: def test_from_wrapper_with_data( self, - sample_parent_continuity_data: Dict[str, Any], - sample_child_continuity_data: Dict[str, Any], + sample_parent_continuity_data: dict[str, Any], + sample_child_continuity_data: dict[str, Any], ) -> None: parent_cont = ParentContinuity.from_dict(sample_parent_continuity_data) child_cont = ChildContinuity.from_dict(sample_child_continuity_data) @@ -2244,8 +2225,8 @@ def test_from_wrapper_with_empty_data(self) -> None: def test_to_dict( self, - sample_parent_continuity_data: Dict[str, Any], - sample_child_continuity_data: Dict[str, Any], + sample_parent_continuity_data: dict[str, Any], + sample_child_continuity_data: dict[str, Any], ) -> None: parent = ParentContinuity.from_dict(sample_parent_continuity_data) child = ChildContinuity.from_dict(sample_child_continuity_data) @@ -2271,7 +2252,7 @@ class TestAssociatedDocumentsData: """Tests for the AssociatedDocumentsData helper class.""" def test_from_wrapper_with_data( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: pgpub_meta_data = sample_document_meta_data_data.copy() pgpub_meta_data["productIdentifier"] = "PGPUB" @@ -2289,7 +2270,7 @@ def test_from_wrapper_with_data( assert assoc_docs.grant_document_meta_data is grant_meta def test_from_wrapper_with_partial_data( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: pgpub_meta = PrintedMetaData.from_dict(sample_document_meta_data_data) wrapper = PatentFileWrapper( @@ -2307,7 +2288,7 @@ def test_from_wrapper_with_no_data(self) -> None: assert assoc_docs.pgpub_document_meta_data is None assert assoc_docs.grant_document_meta_data is None - def test_to_dict(self, sample_document_meta_data_data: Dict[str, Any]) -> None: + def test_to_dict(self, sample_document_meta_data_data: dict[str, Any]) -> None: pgpub_meta_dict = sample_document_meta_data_data.copy() pgpub_meta_dict["zipFileName"] = "pgpub.zip" grant_meta_dict = sample_document_meta_data_data.copy() @@ -2326,7 +2307,7 @@ def test_to_dict(self, sample_document_meta_data_data: Dict[str, Any]) -> None: assert data_dict["grantDocumentMetaData"]["zipFileName"] == "grant.zip" def test_to_dict_with_partial_data( - self, sample_document_meta_data_data: Dict[str, Any] + self, sample_document_meta_data_data: dict[str, Any] ) -> None: pgpub_meta = PrintedMetaData.from_dict(sample_document_meta_data_data) assoc_docs = PrintedPublication( diff --git a/tests/models/test_petition_decision_models.py b/tests/models/test_petition_decision_models.py index 83d8b4e..310ad1d 100644 --- a/tests/models/test_petition_decision_models.py +++ b/tests/models/test_petition_decision_models.py @@ -1,11 +1,10 @@ -""" -Tests for the petition_decisions models. +"""Tests for the petition_decisions models. This module contains comprehensive tests for all classes in pyUSPTO.models.petition_decisions. """ -from datetime import date, datetime, timezone -from typing import Any, Dict +from datetime import date, datetime +from typing import Any import pytest @@ -21,7 +20,7 @@ @pytest.fixture -def sample_download_option_dict() -> Dict[str, Any]: +def sample_download_option_dict() -> dict[str, Any]: """Provide a sample download option dictionary.""" return { "mimeTypeIdentifier": "PDF", @@ -31,7 +30,7 @@ def sample_download_option_dict() -> Dict[str, Any]: @pytest.fixture -def sample_document_dict() -> Dict[str, Any]: +def sample_document_dict() -> dict[str, Any]: """Provide a sample petition decision document dictionary.""" return { "applicationNumberText": "13815942", @@ -39,7 +38,7 @@ def sample_document_dict() -> Dict[str, Any]: "documentIdentifier": "M98QOH0NWFYTX17", "documentCode": "PETDEC", "documentCodeDescriptionText": "Petition Decision", - "documentDirectionCategory": "OUTGOING", + "directionCategory": "OUTGOING", "downloadOptionBag": [ { "mimeTypeIdentifier": "PDF", @@ -55,7 +54,7 @@ def sample_document_dict() -> Dict[str, Any]: @pytest.fixture -def sample_petition_decision_dict() -> Dict[str, Any]: +def sample_petition_decision_dict() -> dict[str, Any]: """Provide a sample petition decision dictionary.""" return { "actionTakenByCourtName": "None", @@ -87,8 +86,8 @@ def sample_petition_decision_dict() -> Dict[str, Any]: @pytest.fixture def sample_petition_response_dict( - sample_petition_decision_dict: Dict[str, Any], -) -> Dict[str, Any]: + sample_petition_decision_dict: dict[str, Any], +) -> dict[str, Any]: """Provide a sample petition decision response dictionary.""" return { "count": 1, @@ -99,8 +98,8 @@ def sample_petition_response_dict( @pytest.fixture def sample_download_response_dict( - sample_petition_decision_dict: Dict[str, Any], -) -> Dict[str, Any]: + sample_petition_decision_dict: dict[str, Any], +) -> dict[str, Any]: """Provide a sample download response dictionary.""" return {"petitionDecisionData": [sample_petition_decision_dict]} @@ -109,7 +108,7 @@ class TestDocumentDownloadOptionFromDict: """Tests for DocumentDownloadOption.from_dict method.""" def test_from_dict_complete( - self, sample_download_option_dict: Dict[str, Any] + self, sample_download_option_dict: dict[str, Any] ) -> None: """Test from_dict with complete data.""" option = DocumentDownloadOption.from_dict(sample_download_option_dict) @@ -140,7 +139,7 @@ class TestDocumentDownloadOptionToDict: """Tests for DocumentDownloadOption.to_dict method.""" def test_to_dict_complete( - self, sample_download_option_dict: Dict[str, Any] + self, sample_download_option_dict: dict[str, Any] ) -> None: """Test to_dict with complete data.""" option = DocumentDownloadOption.from_dict(sample_download_option_dict) @@ -164,14 +163,14 @@ def test_to_dict_filters_none(self) -> None: class TestPetitionDecisionDocumentFromDict: """Tests for PetitionDecisionDocument.from_dict method.""" - def test_from_dict_complete(self, sample_document_dict: Dict[str, Any]) -> None: + def test_from_dict_complete(self, sample_document_dict: dict[str, Any]) -> None: """Test from_dict with complete data.""" doc = PetitionDecisionDocument.from_dict(sample_document_dict) assert doc.application_number_text == "13815942" assert doc.document_identifier == "M98QOH0NWFYTX17" assert doc.document_code == "PETDEC" assert doc.document_code_description_text == "Petition Decision" - assert doc.document_direction_category == "OUTGOING" + assert doc.direction_category == "OUTGOING" assert len(doc.download_option_bag) == 2 assert doc.download_option_bag[0].mime_type_identifier == "PDF" assert doc.download_option_bag[1].mime_type_identifier == "XML" @@ -201,7 +200,7 @@ def test_from_dict_missing_download_options(self) -> None: class TestPetitionDecisionDocumentToDict: """Tests for PetitionDecisionDocument.to_dict method.""" - def test_to_dict_complete(self, sample_document_dict: Dict[str, Any]) -> None: + def test_to_dict_complete(self, sample_document_dict: dict[str, Any]) -> None: """Test to_dict with complete data.""" doc = PetitionDecisionDocument.from_dict(sample_document_dict) result = doc.to_dict() @@ -216,7 +215,7 @@ class TestPetitionDecisionFromDict: """Tests for PetitionDecision.from_dict method.""" def test_from_dict_complete( - self, sample_petition_decision_dict: Dict[str, Any] + self, sample_petition_decision_dict: dict[str, Any] ) -> None: """Test from_dict with complete data.""" decision = PetitionDecision.from_dict(sample_petition_decision_dict) @@ -285,7 +284,7 @@ def test_from_dict_with_patent_number(self) -> None: assert decision.patent_number == "11000000" def test_from_dict_with_documents( - self, sample_document_dict: Dict[str, Any] + self, sample_document_dict: dict[str, Any] ) -> None: """Test from_dict with document bag.""" data = { @@ -364,7 +363,7 @@ class TestPetitionDecisionToDict: """Tests for PetitionDecision.to_dict method.""" def test_to_dict_complete( - self, sample_petition_decision_dict: Dict[str, Any] + self, sample_petition_decision_dict: dict[str, Any] ) -> None: """Test to_dict with complete data.""" decision = PetitionDecision.from_dict(sample_petition_decision_dict) @@ -391,6 +390,7 @@ def test_to_dict_filters_none_and_empty_lists(self) -> None: """Test to_dict filters out None values and empty lists.""" decision = PetitionDecision( application_number_text="12345678", + petition_decision_record_identifier="test-id", patent_number=None, inventor_bag=[], ) @@ -404,7 +404,7 @@ class TestPetitionDecisionResponseFromDict: """Tests for PetitionDecisionResponse.from_dict method.""" def test_from_dict_complete( - self, sample_petition_response_dict: Dict[str, Any] + self, sample_petition_response_dict: dict[str, Any] ) -> None: """Test from_dict with complete data.""" response = PetitionDecisionResponse.from_dict(sample_petition_response_dict) @@ -418,7 +418,7 @@ def test_from_dict_complete( def test_from_dict_empty(self) -> None: """Test from_dict with empty data.""" response = PetitionDecisionResponse.from_dict({}) - assert response.count is None + assert response.count == 0 assert response.request_identifier is None assert len(response.petition_decision_data_bag) == 0 @@ -447,7 +447,7 @@ class TestPetitionDecisionResponseToDict: """Tests for PetitionDecisionResponse.to_dict method.""" def test_to_dict_complete( - self, sample_petition_response_dict: Dict[str, Any] + self, sample_petition_response_dict: dict[str, Any] ) -> None: """Test to_dict with complete data.""" response = PetitionDecisionResponse.from_dict(sample_petition_response_dict) @@ -462,7 +462,7 @@ class TestPetitionDecisionDownloadResponseFromDict: """Tests for PetitionDecisionDownloadResponse.from_dict method.""" def test_from_dict_complete( - self, sample_download_response_dict: Dict[str, Any] + self, sample_download_response_dict: dict[str, Any] ) -> None: """Test from_dict with complete data.""" response = PetitionDecisionDownloadResponse.from_dict( @@ -481,7 +481,7 @@ class TestPetitionDecisionDownloadResponseToDict: """Tests for PetitionDecisionDownloadResponse.to_dict method.""" def test_to_dict_complete( - self, sample_download_response_dict: Dict[str, Any] + self, sample_download_response_dict: dict[str, Any] ) -> None: """Test to_dict with complete data.""" response = PetitionDecisionDownloadResponse.from_dict( diff --git a/tests/models/test_ptab_models.py b/tests/models/test_ptab_models.py new file mode 100644 index 0000000..ce9cf05 --- /dev/null +++ b/tests/models/test_ptab_models.py @@ -0,0 +1,1812 @@ +""" +Tests for PTAB models. + +This module contains unit tests for the PTAB model classes with full coverage. +""" + +import importlib +from datetime import date, datetime, timezone +from typing import Any + +import pytest + +from pyUSPTO.models.ptab import ( + AdditionalPartyData, + AppealDocumentData, + # Appeal Decisions Models + AppealMetaData, + AppellantData, + DecisionData, + DerivationPetitionerData, + InterferenceDocumentData, + # Interference Decisions Models + InterferenceMetaData, + JuniorPartyData, + # Base and shared models + PartyData, + PatentOwnerData, + PTABAppealDecision, + PTABAppealResponse, + PTABInterferenceDecision, + PTABInterferenceResponse, + PTABTrialDocument, + PTABTrialDocumentResponse, + PTABTrialProceeding, + PTABTrialProceedingResponse, + RegularPetitionerData, + RequestorData, + RespondentData, + SeniorPartyData, + TrialDecisionData, + # Trial Documents/Decisions Models + TrialDocumentData, + # Trial Proceedings Models + TrialMetaData, +) + + +# Sample API response fixtures for round-trip testing +@pytest.fixture +def trial_proceeding_api_sample() -> dict[str, Any]: + """Sample trial proceeding API response for testing.""" + return { + "count": 1, + "requestIdentifier": "4649ea27-4192-4ea4-86f9-e033ca24c17a", + "patentTrialProceedingDataBag": [ + { + "lastModifiedDateTime": "2025-11-20T01:27:05", + "respondentData": { + "patentOwnerName": "ADAMS et al", + "patentNumber": "9780412", + "grantDate": "2017-10-03", + "technologyCenterNumber": "1700", + "groupArtUnitNumber": "1725", + "applicationNumberText": "15461849", + "inventorName": "Brian D. ADAMS et al", + }, + "patentOwnerData": { + "patentOwnerName": "ADAMS et al", + "patentNumber": "9780412", + "grantDate": "2017-10-03", + "technologyCenterNumber": "1700", + "groupArtUnitNumber": "1725", + "applicationNumberText": "15461849", + "inventorName": "Brian D. ADAMS et al", + }, + "trialMetaData": { + "accordedFilingDate": "2018-07-20", + "terminationDate": "2019-01-30", + "trialTypeCode": "DER", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/DER/2018/00018/DER2018-00018.zip", + "trialStatusCategory": "Terminated-Settled", + "trialLastModifiedDate": "2020-06-04", + "petitionFilingDate": "2018-07-20", + "trialLastModifiedDateTime": "2020-06-04T13:49:04", + }, + "trialNumber": "DER2018-00018", + "regularPetitionerData": {"counselName": "Todd Baker"}, + "derivationPetitionerData": { + "technologyCenterNumber": "1700", + "groupArtUnitNumber": "1722", + "applicationNumberText": "15513914", + "inventorName": "Brian D. ADAMS et al", + "counselName": "Todd Baker", + }, + } + ], + } + + +@pytest.fixture +def trial_decision_api_sample() -> dict[str, Any]: + """Sample trial decision API response for testing.""" + return { + "count": 1, + "requestIdentifier": "e303e566-a896-4b1e-9b22-1f8b1b1cc5cf", + "patentTrialDocumentDataBag": [ + { + "trialNumber": "IPR2025-01319", + "lastModifiedDateTime": "2025-11-21T03:56:35", + "trialDocumentCategory": "Decision", + "trialMetaData": { + "institutionDecisionDate": "2025-11-20", + "accordedFilingDate": "2025-07-17", + "petitionFilingDate": "2025-07-17", + "trialLastModifiedDateTime": "2025-11-20T15:34:55", + "trialLastModifiedDate": "2025-11-20", + "terminationDate": "2025-11-20", + "trialStatusCategory": "Discretionary Denial", + "latestDecisionDate": "2025-11-20", + "trialTypeCode": "IPR", + }, + "patentOwnerData": { + "applicationNumberText": "17980669", + "counselName": "Cohen, Alexiset al", + "grantDate": "2023-07-18", + "groupArtUnitNumber": "2884", + "inventorName": "Yong Qin Chen", + "realPartyInInterestName": "Beckman Coulter, Inc. et al.", + "patentNumber": "11703443", + "technologyCenterNumber": "2800", + }, + "regularPetitionerData": { + "counselName": "Knight, Dustinet al", + "realPartyInInterestName": "Cytek Biosciences, Inc.", + }, + "documentData": { + "documentTypeDescriptionText": "RESPONSE", + "documentFilingDate": "2025-11-20", + "documentIdentifier": "171242096", + "documentName": "20251120 Director Notice Regarding Institution Paper 13.pdf", + "documentNumber": 13, + "documentSizeQuantity": 85780, + "documentTitleText": "Director Discretionary Decision: Deny", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/IPR/2025/01319/171242096.pdf", + "filingPartyCategory": "BOARD", + "documentOCRText": "Trials@uspto.gov Paper 13", + }, + "decisionData": { + "statuteAndRuleBag": ["35 USC 314", "35 USC 324"], + "decisionIssueDate": "2025-11-20", + "decisionTypeCategory": "Decision", + "trialOutcomeCategory": "Institution Denied", + }, + } + ], + } + + +@pytest.fixture +def trial_document_api_sample() -> dict[str, Any]: + """Sample trial document API response for testing.""" + return { + "count": 2, + "requestIdentifier": "e02d1512-7e81-489f-9c98-4d0c6070864e", + "patentTrialDocumentDataBag": [ + { + "trialNumber": "DER2023-00012", + "lastModifiedDateTime": "2025-11-20T01:22:58", + "trialDocumentCategory": "Document", + "trialMetaData": { + "accordedFilingDate": "2023-05-11", + "petitionFilingDate": "2023-05-11", + "trialLastModifiedDateTime": "2025-07-24T12:21:16", + "trialLastModifiedDate": "2025-07-24", + "trialStatusCategory": "Pending", + "trialTypeCode": "DER", + }, + "patentOwnerData": { + "applicationNumberText": "17522731", + "counselName": "Ringenberg, Scotet al", + "groupArtUnitNumber": "3676", + "inventorName": "Nicholas Kleinschmit et al", + "realPartyInInterestName": "Holmberg, Aaron et al.", + "technologyCenterNumber": "3600", + }, + "regularPetitionerData": { + "counselName": "Brewer, Peteret al", + "realPartyInInterestName": "Rasmussen, Jon et al.", + }, + "respondentData": { + "applicationNumberText": "17522731", + "counselName": "Ringenberg, Scotet al", + "groupArtUnitNumber": "3676", + "inventorName": "Nicholas Kleinschmit et al", + "realPartyInInterestName": "Holmberg, Aaron et al.", + "technologyCenterNumber": "3600", + }, + "derivationPetitionerData": { + "applicationNumberText": "17846932", + "counselName": "Brewer, Peteret al", + "groupArtUnitNumber": "3674", + "inventorName": "Jon Randall Rasmussen et al", + "realPartyInInterestName": "Rasmussen, Jon et al.", + "patentOwnerName": "Rasmussen, JonRandall", + "technologyCenterNumber": "3600", + }, + "documentData": { + "documentTypeDescriptionText": "ORDER", + "documentFilingDate": "2025-07-24", + "documentIdentifier": "171138150", + "documentName": "Order Limited Remand Final DER2023-00012 Circ.pdf", + "documentNumber": 25, + "documentSizeQuantity": 94697, + "documentTitleText": "ORDER Conduct of Proceeding Limited Remand 37 C.F.R. § 42.5(a)", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/DER/2023/00012/171138150.pdf", + "filingPartyCategory": "BOARD", + }, + "decisionData": {"statuteAndRuleBag": []}, + }, + { + "trialNumber": "DER2022-00004", + "lastModifiedDateTime": "2025-11-20T01:22:56", + "trialDocumentCategory": "Decision", + "trialMetaData": { + "institutionDecisionDate": "2023-11-28", + "accordedFilingDate": "2022-03-17", + "petitionFilingDate": "2022-03-17", + "trialLastModifiedDateTime": "2023-12-29T00:00:02", + "trialLastModifiedDate": "2023-12-29", + "terminationDate": "2023-11-28", + "trialStatusCategory": "Institution Denied", + "latestDecisionDate": "2023-11-28", + "trialTypeCode": "DER", + }, + "patentOwnerData": { + "applicationNumberText": "17018233", + "counselName": "Miotke, Josephet al", + "grantDate": "2024-05-21", + "groupArtUnitNumber": "2144", + "inventorName": "Grant Vergottini", + "realPartyInInterestName": "Xcential Corporation", + "patentNumber": "11989794", + "technologyCenterNumber": "2100", + }, + "regularPetitionerData": { + "counselName": "Totten, Jeffreyet al", + "realPartyInInterestName": "Akin Gump Strauss Hauer & Feld LLP et al.", + }, + "respondentData": { + "applicationNumberText": "17018233", + "counselName": "Miotke, Josephet al", + "grantDate": "2024-05-21", + "groupArtUnitNumber": "2144", + "inventorName": "Grant Vergottini", + "realPartyInInterestName": "Xcential Corporation", + "patentNumber": "11989794", + "technologyCenterNumber": "2100", + }, + "derivationPetitionerData": { + "applicationNumberText": "17696389", + "counselName": "Totten, Jeffreyet al", + "groupArtUnitNumber": "2144", + "inventorName": "Louis AGNELLO", + "realPartyInInterestName": "Akin Gump Strauss Hauer & Feld LLP et al.", + "technologyCenterNumber": "2100", + }, + "documentData": { + "documentTypeDescriptionText": "DECISION", + "documentFilingDate": "2023-11-28", + "documentIdentifier": "170697659", + "documentName": "DER2022-00004 Institution Decision.pdf", + "documentNumber": 15, + "documentSizeQuantity": 382075, + "documentTitleText": "Institution Decision: Deny", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/DER/2022/00004/170697659.pdf", + "filingPartyCategory": "BOARD", + "documentOCRText": "DER2022-00004 Akin Gump Strauss Hauer & Feld LLP et al. v. Xcential Corporation\n\n\nTrials@uspto.gov Paper 15 \n571-272-7822 Date: November 28, 2023 \n \n\n \n\nUNITED STATES PATENT AND TRADEMARK OFFICE \n \n\nBEFORE THE PATENT TRIAL AND APPEAL BOARD \n \n\n \n \n\nAKIN GUMP STRAUSS HAUER & FELD LLP, \nPetitioner Application 17/696,389, \n\nPetitioner, \n \n\nv. \n \n\nXCENTIAL CORP., \nRespondent Application 17/018,233, \n\nRespondent. \n____________ \n\n \nDER2022-00004 \n______________ \n\n \nBefore JAMESON LEE, JUSTIN T. ARBES", + }, + "decisionData": { + "statuteAndRuleBag": [ + "37 CFR 42.405", + " 47 CFR 42.401", + " 40 CFR 42.401", + "37 CFR 42.408", + ], + "decisionIssueDate": "2023-11-28", + "decisionTypeCategory": "Decision", + "issueTypeBag": ["102", "103"], + "trialOutcomeCategory": "Institution Denied", + }, + }, + ], + } + + +@pytest.fixture +def appeal_decision_api_sample() -> dict[str, Any]: + """Sample appeal decision API response for testing.""" + return { + "count": 1, + "requestIdentifier": "7cf343b3-cad4-4813-9d46-993cf3c50283", + "patentAppealDataBag": [ + { + "appealNumber": "2015000194", + "lastModifiedDateTime": "2015-02-18T14:23:45Z", + "appealDocumentCategory": "Decision", + "appealMetaData": { + "appealFilingDate": "2014-10-02", + "appealLastModifiedDate": "2015-02-18", + "appealLastModifiedDateTime": "2015-02-18T10:07:59", + "applicationTypeCategory": "Utility", + "docketNoticeMailedDate": "2014-11-15", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/APPEAL/2015/000194.zip", + }, + "appellantData": { + "applicationNumberText": "12608694", + "counselName": "PAULEY ERICKSON & SWANSON", + "groupArtUnitNumber": "3992", + "inventorName": "John Smith", + "realPartyInInterestName": "Tech Company Inc.", + "patentOwnerName": "Tech Company Inc.", + "publicationDate": "2010-06-10", + "publicationNumber": "US20100145456", + "technologyCenterNumber": "3900", + }, + "requestorData": {"thirdPartyName": "Third Party Requestor LLC"}, + "documentData": { + "documentFilingDate": "2015-02-18", + "documentIdentifier": "appeal-doc-12345", + "documentName": "Decision on Appeal.pdf", + "documentSizeQuantity": 345678, + "documentOCRText": "Decision on Appeal text content...", + "documentTypeDescriptionText": "Decision", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/APPEAL/2015/000194/decision.pdf", + }, + "decisionData": { + "appealOutcomeCategory": "Affirmed", + "statuteAndRuleBag": ["35 U.S.C. § 103", "37 CFR 1.111"], + "decisionIssueDate": "2015-02-18", + "decisionTypeCategory": "Examiner Affirmed", + "issueTypeBag": ["Obviousness"], + }, + } + ], + } + + +@pytest.fixture +def interference_decision_api_sample() -> dict[str, Any]: + """Sample interference decision API response for testing.""" + return { + "count": 2, + "requestIdentifier": "66576984-9c20-4e41-b4da-e28d13fe18f1", + "patentInterferenceDataBag": [ + { + "interferenceNumber": "104807", + "lastModifiedDateTime": "2025-11-20T03:12:32", + "interferenceMetaData": { + "interferenceLastModifiedDateTime": "2006-12-22T00:00:00", + "interferenceLastModifiedDate": "2006-12-22", + "declarationDate": "2002-12-11", + "interferenceStyleName": "VINOGRADOV V. FLAMM", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/104807/104807.zip", + }, + "seniorPartyData": { + "applicationNumberText": "08739037", + "grantDate": "1999-10-12", + "groupArtUnitNumber": "1763", + "inventorName": "GEORGY VINOGRADOV et al", + "patentNumber": "5965034", + "patentOwnerName": "GEORGY VINOGRADOV et al", + "realPartyInInterestName": "GEORGY VINOGRADOV et al", + "technologyCenterNumber": "1700", + }, + "juniorPartyData": { + "applicationNumberText": "08748746", + "grantDate": "2005-02-22", + "groupArtUnitNumber": "1763", + "inventorName": "DANIEL L. FLAMM et al", + "patentNumber": "6858112", + "patentOwnerName": "DANIEL L. FLAMM et al", + "publicationDate": "2003-09-11", + "publicationNumber": "20030168427A1", + "realPartyInInterestName": "DANIEL L. FLAMM et al", + "technologyCenterNumber": "1700", + }, + "documentData": { + "documentIdentifier": "b8c473a3bcab88d5c33ef3231daf45a10f967103a89b8db9c791d1ee", + "documentName": "fd10480712-11-2002", + "documentSizeQuantity": 160468, + "documentOCRText": "The opinion in support of the decision being \nentered today is not binding precedent of the Board. \n\nPaper 20 \nFiled by: Trial Section Motions Panel \n\nBox Interference Filed: December 11, 2002 \nWashington, D.C. 20231 \nTel: 703-308-9797 \nFax: 703-305-0942 \n\nUNITED STATES PATENT AND TRADEMARK OFFICE \n\nBEFORE THE BOARD OF PATENT APPEALS \nAND INTERFERENCES \n\nMAILED \nDANIEL L. FLAMM \n\nJunior Party DEC 2002 \n(U.S. Application 08/748,746), \n\nPAT & TM OFFICE BOARD OF PATENT \nAND INTERFER,'N\"FALS \n\nGEORGY", + "documentTitleText": "DECISION-104807", + "interferenceOutcomeCategory": "Final Decision", + "decisionIssueDate": "2002-12-11", + "decisionTypeCategory": "Decision", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/104807/Intf608_10480708739037_1039582800000.pdf", + "documentFilingDate": "2002-12-11", + }, + }, + { + "interferenceNumber": "106130", + "lastModifiedDateTime": "2025-11-20T03:12:32", + "additionalPartyDataBag": [ + { + "applicationNumberText": "16159021", + "inventorName": "Lee M Kaplan et al", + "patentNumber": "", + "additionalPartyName": "LEE M. KAPLAN, ALICE P. LIOU, PETER J. TURNBAUGH, and JASON L. HARRIS", + }, + { + "applicationNumberText": "15698965", + "inventorName": "Lee M Kaplan et al", + "patentNumber": "10149870", + "additionalPartyName": "LEE M. KAPLAN, ALICE P. LIOU, PETER J. TURNBAUGH, and JASON L. HARRIS", + }, + { + "applicationNumberText": "16669143", + "inventorName": "Lee M Kaplan et al", + "patentNumber": "10729732", + "additionalPartyName": "LEE M. KAPLAN, ALICE P. LIOU, PETER J. TURNBAUGH, and JASON L. HARRIS", + }, + ], + "interferenceMetaData": { + "interferenceLastModifiedDateTime": "2025-11-13T00:00:00", + "interferenceLastModifiedDate": "2025-11-13", + "declarationDate": "2021-01-26", + "interferenceStyleName": "LEE M. KAPLAN, ALICE P. LIOU, PETER J. TURNBAUGH, and JASON L. HARRIS v. PATRICE CANI, AMANDINE EVERARD, CLARA BELZER, and WILLEM DE VOS", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/106130/106130.zip", + }, + "seniorPartyData": { + "applicationNumberText": "14443829", + "counselName": "ALSTON & BIRD and GEMINI LAW LLP ", + "groupArtUnitNumber": "1651", + "inventorName": "Patrice Cani et al", + "patentOwnerName": "CANI, PATRICE; EVERARD, Amandine; BELZER, Clara; DE VOS Willem", + "publicationDate": "2015-10-29", + "publicationNumber": "US20150306152A1", + "realPartyInInterestName": "UNIVERSITÉ CATHOLIQUE DE LOUVAIN; WAGENINGEN UNIVERSITEIT", + "technologyCenterNumber": "1600", + }, + "juniorPartyData": { + "applicationNumberText": "14862663", + "counselName": "ROTHWELL, FIGG, ERNST & MANBECK, P.C. and LATHROP GAGE LLP", + "grantDate": "2018-12-11", + "groupArtUnitNumber": "1651", + "inventorName": "Lee M. Kaplan et al", + "patentNumber": "10149867", + "publicationDate": "2016-04-28", + "publicationNumber": "US20160113971A1", + "realPartyInInterestName": "Ethicon Endo-Surgery, Inc.; The General Hospital Corporation D/B/A Massachusetts General Hospital; President and Fellows of Harvard College ", + "technologyCenterNumber": "1600", + }, + "documentData": { + "documentIdentifier": "229ba0b8d5f70d2e45cc36b79476f56f3faf51bd26c7ccc977208e7b", + "documentName": "106130_106130-jd-20250128.pdf", + "documentSizeQuantity": 97923, + "documentOCRText": "Microsoft Word - 106,130 Judgment (to be mailed)\n\n\n \nTrials@uspto.gov Filed: January 28, 2025 \nTel: 571-272-7822 \n \n\nUNITED STATES PATENT AND TRADEMARK OFFICE \n_______________ \n\n \nBEFORE THE PATENT TRIAL AND APPEAL BOARD \n\n \n________________ \n\n \nLEE M. KAPLAN, \n\nALICE P. LIOU, PETER J. TURNBAUGH, and JASON L. HARRIS, \n \n\nJunior Party \n(Patents 10,149,867; 10,149,870; and 10,729,732; \n\nand Application 16/159,021), \n \n\nv. \n \n\nPATRICE CANI, \nAMANDINE EVERARD, CLARA BELZER, and WILLEM", + "documentTitleText": "Judgment 37 C.F.R. § 41.127(a)", + "interferenceOutcomeCategory": "Judgment", + "statuteAndRuleBag": ["37 CFR 41.127(a)"], + "decisionIssueDate": "2025-01-28", + "decisionTypeCategory": "Decision", + "fileDownloadURI": "https://api.uspto.gov/api/v1/patent/ptab-files/INTF/106130/Intf508_10613014862663_1738040400000.pdf", + "documentFilingDate": "2025-01-28", + "issueTypeBag": ["112"], + }, + }, + ], + } + + +class TestSelfImport: + """Tests for Self type import compatibility across Python versions.""" + + def test_self_import_works(self) -> None: + """Test that Self type can be imported from ptab module.""" + # This test verifies the try/except import pattern works + # by importing the module (which happens at test module import time) + # and using a from_dict method that relies on Self type hints + + # Verify the import succeeded (module is already imported) + import pyUSPTO.models.ptab as ptab_module + + assert hasattr(ptab_module, "PartyData") + + def test_import_fallback_logic(self) -> None: + """ + Tests that the module falls back to typing_extensions.Self if typing.Self fails. + Uses builtins.__import__ patching to avoid corrupting the global typing module. + """ + import builtins + import sys + from unittest.mock import patch + + import typing_extensions + + module_name = "pyUSPTO.models.ptab" + + # 1. Ensure the module is unloaded so we can force a fresh import + if module_name in sys.modules: + del sys.modules[module_name] + + # 2. Define a side_effect that simulates ImportError ONLY when importing Self from typing + # This intercepts 'from typing import Self' + original_import = builtins.__import__ + + def mock_import(name, globals=None, locals=None, fromlist=(), level=0): + if name == "typing" and "Self" in fromlist: + raise ImportError("Simulated missing Self in typing") + return original_import(name, globals, locals, fromlist, level) + + # 3. Apply the patch and import + with patch("builtins.__import__", side_effect=mock_import): + ptab_module = importlib.import_module(module_name) + + # 4. Verify the fallback works (it should be the typing_extensions version) + assert ptab_module.Self is typing_extensions.Self + + # 5. Cleanup: Restore the module to its normal state for other tests + if module_name in sys.modules: + del sys.modules[module_name] + importlib.import_module(module_name) + + def test_self_type_in_from_dict_methods(self) -> None: + """Test that from_dict methods work correctly with Self return type.""" + # Create an instance using from_dict which uses Self as return type + data = {"counselName": "Test Counsel", "patentNumber": "US1234567"} + result = PartyData.from_dict(data) + + # Verify the return type is correct (should be PartyData instance) + assert isinstance(result, PartyData) + assert result.counsel_name == "Test Counsel" + assert result.patent_number == "US1234567" + + def test_self_type_returns_correct_class_instance(self) -> None: + """Test that from_dict returns an instance of the calling class.""" + # Test with different model classes to ensure Self works correctly + trial_data = {"trialNumber": "IPR2023-00001"} + trial_result = PTABTrialProceeding.from_dict(trial_data) + assert isinstance(trial_result, PTABTrialProceeding) + + appeal_data = {"appealNumber": "2023-001234"} + appeal_result = PTABAppealDecision.from_dict(appeal_data) + assert isinstance(appeal_result, PTABAppealDecision) + + interference_data = {"interferenceNumber": "106123"} + interference_result = PTABInterferenceDecision.from_dict(interference_data) + assert isinstance(interference_result, PTABInterferenceDecision) + + +class TestPartyData: + """Tests for PartyData base class.""" + + def test_party_data_from_dict_full(self) -> None: + """Test PartyData.from_dict() with all fields.""" + data = { + "applicationNumberText": "15/123456", + "counselName": "Test Counsel", + "grantDate": "2023-01-15", + "groupArtUnitNumber": "3600", + "inventorName": "John Inventor", + "realPartyInInterestName": "Real Party Inc", + "patentNumber": "US1234567", + "patentOwnerName": "Patent Owner LLC", + "technologyCenterNumber": "3600", + "publicationDate": "2022-12-01", + "publicationNumber": "US20220012345", + } + result = PartyData.from_dict(data) + assert result.application_number_text == "15/123456" + assert result.counsel_name == "Test Counsel" + assert result.grant_date == date(2023, 1, 15) + assert result.group_art_unit_number == "3600" + assert result.inventor_name == "John Inventor" + assert result.real_party_in_interest_name == "Real Party Inc" + assert result.patent_number == "US1234567" + assert result.patent_owner_name == "Patent Owner LLC" + assert result.technology_center_number == "3600" + assert result.publication_date == date(2022, 12, 1) + assert result.publication_number == "US20220012345" + + def test_party_data_from_dict_empty(self) -> None: + """Test PartyData.from_dict() with empty dict.""" + result = PartyData.from_dict({}) + assert result.application_number_text is None + assert result.counsel_name is None + assert result.grant_date is None + assert result.group_art_unit_number is None + assert result.inventor_name is None + assert result.real_party_in_interest_name is None + assert result.patent_number is None + assert result.patent_owner_name is None + assert result.technology_center_number is None + assert result.publication_date is None + assert result.publication_number is None + + def test_party_data_from_dict_ignores_include_raw_data(self) -> None: + """Test PartyData.from_dict() ignores include_raw_data parameter.""" + data = {"counselName": "Test"} + result = PartyData.from_dict(data, include_raw_data=True) + assert result.counsel_name == "Test" + + +class TestPTABTrialModels: + """Tests for PTAB trial proceeding models.""" + + def test_trial_metadata_from_dict_full(self) -> None: + """Test TrialMetaData.from_dict() with all fields.""" + data = { + "petitionFilingDate": "2023-01-15", + "accordedFilingDate": "2023-01-16", + "trialLastModifiedDateTime": "2023-06-01T10:30:00Z", + "trialLastModifiedDate": "2023-06-01", + "trialStatusCategory": "Instituted", + "trialTypeCode": "IPR", + "fileDownloadURI": "https://example.com/download.zip", + "terminationDate": "2024-01-15", + "latestDecisionDate": "2023-12-15", + "institutionDecisionDate": "2023-07-15", + } + result = TrialMetaData.from_dict(data) + assert result.petition_filing_date == date(2023, 1, 15) + assert result.accorded_filing_date == date(2023, 1, 16) + assert result.trial_last_modified_date_time == datetime( + 2023, 6, 1, 10, 30, 0, tzinfo=timezone.utc + ) + assert result.trial_last_modified_date == date(2023, 6, 1) + assert result.trial_status_category == "Instituted" + assert result.trial_type_code == "IPR" + assert result.file_download_uri == "https://example.com/download.zip" + assert result.termination_date == date(2024, 1, 15) + assert result.latest_decision_date == date(2023, 12, 15) + assert result.institution_decision_date == date(2023, 7, 15) + + def test_trial_metadata_from_dict_empty(self) -> None: + """Test TrialMetaData.from_dict() with empty dict.""" + result = TrialMetaData.from_dict({}) + assert result.petition_filing_date is None + assert result.accorded_filing_date is None + assert result.trial_last_modified_date_time is None + assert result.trial_last_modified_date is None + assert result.trial_status_category is None + assert result.trial_type_code is None + assert result.file_download_uri is None + assert result.termination_date is None + assert result.latest_decision_date is None + assert result.institution_decision_date is None + + def test_patent_owner_data_from_dict(self) -> None: + """Test PatentOwnerData.from_dict().""" + data = { + "patentOwnerName": "Owner Inc", + "patentNumber": "US1234567", + "counselName": "Owner Counsel", + } + result = PatentOwnerData.from_dict(data) + assert result.patent_owner_name == "Owner Inc" + assert result.patent_number == "US1234567" + assert result.counsel_name == "Owner Counsel" + + def test_regular_petitioner_data_from_dict(self) -> None: + """Test RegularPetitionerData.from_dict().""" + data = { + "counselName": "Test Counsel", + "realPartyInInterestName": "Real Party", + } + result = RegularPetitionerData.from_dict(data) + assert result.counsel_name == "Test Counsel" + assert result.real_party_in_interest_name == "Real Party" + + def test_regular_petitioner_data_from_dict_empty(self) -> None: + """Test RegularPetitionerData.from_dict() with empty dict.""" + result = RegularPetitionerData.from_dict({}) + assert result.counsel_name is None + assert result.real_party_in_interest_name is None + + def test_respondent_data_from_dict(self) -> None: + """Test RespondentData.from_dict().""" + data = { + "counselName": "Respondent Counsel", + "realPartyInInterestName": "Respondent Party", + "patentNumber": "US7654321", + } + result = RespondentData.from_dict(data) + assert result.counsel_name == "Respondent Counsel" + assert result.real_party_in_interest_name == "Respondent Party" + assert result.patent_number == "US7654321" + + def test_derivation_petitioner_data_from_dict(self) -> None: + """Test DerivationPetitionerData.from_dict().""" + data = { + "counselName": "Derivation Counsel", + "grantDate": "2023-01-15", + "groupArtUnitNumber": "3600", + "inventorName": "John Inventor", + "patentNumber": "US1234567", + "technologyCenterNumber": "3600", + "realPartyInInterestName": "Derivation Party", + "patentOwnerName": "Derivation Owner", + } + result = DerivationPetitionerData.from_dict(data) + assert result.counsel_name == "Derivation Counsel" + assert result.grant_date == date(2023, 1, 15) + assert result.patent_number == "US1234567" + assert result.patent_owner_name == "Derivation Owner" + + def test_trial_proceeding_from_dict_full( + self, trial_proceeding_api_sample: dict[str, Any] + ) -> None: + """Test PTABTrialProceeding.from_dict() with all nested objects using real API data.""" + data = trial_proceeding_api_sample["patentTrialProceedingDataBag"][0] + result = PTABTrialProceeding.from_dict(data) + + assert result.trial_number == "DER2018-00018" + assert result.trial_meta_data is not None + assert result.trial_meta_data.trial_status_category == "Terminated-Settled" + assert result.trial_meta_data.trial_type_code == "DER" + assert result.patent_owner_data is not None + assert result.patent_owner_data.patent_owner_name == "ADAMS et al" + assert result.patent_owner_data.patent_number == "9780412" + assert result.regular_petitioner_data is not None + assert result.regular_petitioner_data.counsel_name == "Todd Baker" + assert result.respondent_data is not None + assert result.respondent_data.patent_owner_name == "ADAMS et al" + assert result.derivation_petitioner_data is not None + assert result.derivation_petitioner_data.counsel_name == "Todd Baker" + assert result.raw_data is None + + def test_trial_proceeding_from_dict_with_raw_data(self) -> None: + """Test PTABTrialProceeding.from_dict() with include_raw_data=True.""" + data = { + "trialNumber": "IPR2023-00001", + # "trialRecordIdentifier": "uuid-1", + } + result = PTABTrialProceeding.from_dict(data, include_raw_data=True) + assert result.trial_number == "IPR2023-00001" + assert result.raw_data == data + + def test_trial_proceeding_from_dict_empty(self) -> None: + """Test PTABTrialProceeding.from_dict() with empty dict.""" + result = PTABTrialProceeding.from_dict({}) + assert result.trial_number is None + # assert result.trial_record_identifier is None + assert result.last_modified_date_time is None + assert result.trial_meta_data is None + assert result.patent_owner_data is None + assert result.regular_petitioner_data is None + assert result.respondent_data is None + assert result.derivation_petitioner_data is None + assert result.raw_data is None + + def test_trial_proceeding_response_from_dict_full( + self, trial_proceeding_api_sample: dict[str, Any] + ) -> None: + """Test PTABTrialProceedingResponse.from_dict() using real API data.""" + result = PTABTrialProceedingResponse.from_dict(trial_proceeding_api_sample) + assert result.count == 1 + assert result.request_identifier == "4649ea27-4192-4ea4-86f9-e033ca24c17a" + assert len(result.patent_trial_proceeding_data_bag) == 1 + assert ( + result.patent_trial_proceeding_data_bag[0].trial_number == "DER2018-00018" + ) + assert result.raw_data is None + + def test_trial_proceeding_response_from_dict_with_raw_data(self) -> None: + """Test PTABTrialProceedingResponse.from_dict() with include_raw_data=True.""" + data = { + "count": 1, + "requestIdentifier": "request-uuid-1", + "patentTrialProceedingDataBag": [ + {"trialNumber": "IPR2023-00001"}, + ], + } + result = PTABTrialProceedingResponse.from_dict(data, include_raw_data=True) + assert result.count == 1 + assert result.raw_data == data + assert len(result.patent_trial_proceeding_data_bag) == 1 + assert result.patent_trial_proceeding_data_bag[0].raw_data == { + "trialNumber": "IPR2023-00001" + } + + def test_trial_proceeding_response_from_dict_empty(self) -> None: + """Test PTABTrialProceedingResponse.from_dict() with empty list.""" + data = { + "count": 0, + "patentTrialProceedingDataBag": [], + } + result = PTABTrialProceedingResponse.from_dict(data) + assert result.count == 0 + assert len(result.patent_trial_proceeding_data_bag) == 0 + + +class TestPTABTrialDocumentModels: + """Tests for PTAB trial document models.""" + + def test_trial_document_data_from_dict_full(self) -> None: + """Test TrialDocumentData.from_dict() with all fields.""" + data = { + # "documentCategory": "Petition", + "documentFilingDate": "2023-01-15", + "documentIdentifier": "doc-uuid-1", + "documentName": "Petition.pdf", + "documentNumber": "1001", + "documentSizeQuantity": 123456, + "documentOCRText": "Full OCR text content here...", + "documentTitleText": "Petition for IPR", + "documentTypeDescriptionText": "Petition Document", + "downloadURI": "https://example.com/doc1.pdf", + "filingPartyCategory": "Petitioner", + # "mimeTypeIdentifier": "application/pdf", + # "documentStatus": "Public", + } + result = TrialDocumentData.from_dict(data) + # assert result.document_category == "Petition" + assert result.document_filing_date == date(2023, 1, 15) + assert result.document_identifier == "doc-uuid-1" + assert result.document_name == "Petition.pdf" + assert result.document_number == "1001" + assert result.document_size_quantity == 123456 + assert result.document_ocr_text == "Full OCR text content here..." + assert result.document_title_text == "Petition for IPR" + assert result.document_type_description_text == "Petition Document" + assert result.file_download_uri == "https://example.com/doc1.pdf" + assert result.filing_party_category == "Petitioner" + # assert result.mime_type_identifier == "application/pdf" + # assert result.document_status == "Public" + + def test_trial_document_data_from_dict_empty(self) -> None: + """Test TrialDocumentData.from_dict() with empty dict.""" + result = TrialDocumentData.from_dict({}) + # assert result.document_category is None + assert result.document_filing_date is None + assert result.document_identifier is None + assert result.document_name is None + assert result.document_number is None + assert result.document_size_quantity is None + assert result.document_ocr_text is None + assert result.document_title_text is None + assert result.document_type_description_text is None + assert result.file_download_uri is None + assert result.filing_party_category is None + # assert result.mime_type_identifier is None + # assert result.document_status is None + + def test_trial_decision_data_from_dict_full(self) -> None: + """Test TrialDecisionData.from_dict() with all fields.""" + data = { + "statuteAndRuleBag": ["35 U.S.C. § 103", "37 CFR 42.100"], + "decisionIssueDate": "2023-12-15", + "decisionTypeCategory": "Final Written Decision", + "issueTypeBag": ["Obviousness", "Claim Construction"], + "trialOutcomeCategory": "Denied", + } + result = TrialDecisionData.from_dict(data) + assert result.statute_and_rule_bag == ["35 U.S.C. § 103", "37 CFR 42.100"] + assert result.decision_issue_date == date(2023, 12, 15) + assert result.decision_type_category == "Final Written Decision" + assert result.issue_type_bag == ["Obviousness", "Claim Construction"] + assert result.trial_outcome_category == "Denied" + + def test_trial_decision_data_from_dict_empty(self) -> None: + """Test TrialDecisionData.from_dict() with empty dict.""" + result = TrialDecisionData.from_dict({}) + assert result.statute_and_rule_bag == [] + assert result.decision_issue_date is None + assert result.decision_type_category is None + assert result.issue_type_bag == [] + assert result.trial_outcome_category is None + + def test_trial_document_from_dict_full( + self, trial_document_api_sample: dict[str, Any] + ) -> None: + """Test PTABTrialDocument.from_dict() with all nested objects using real API data.""" + data = trial_document_api_sample["patentTrialDocumentDataBag"][0] + result = PTABTrialDocument.from_dict(data) + + assert result.trial_document_category == "Document" + assert result.trial_number == "DER2023-00012" + assert result.trial_meta_data is not None + assert result.trial_meta_data.trial_status_category == "Pending" + assert result.patent_owner_data is not None + assert result.patent_owner_data.patent_number is None + assert result.regular_petitioner_data is not None + assert ( + result.regular_petitioner_data.real_party_in_interest_name + == "Rasmussen, Jon et al." + ) + assert result.document_data is not None + assert result.document_data.document_number == 25 + assert result.decision_data is not None + assert result.raw_data is None + + def test_trial_document_from_dict_with_raw_data(self) -> None: + """Test PTABTrialDocument.from_dict() with include_raw_data=True.""" + data = { + "trialNumber": "IPR2023-00001", + "trialDocumentCategory": "Document", + } + result = PTABTrialDocument.from_dict(data, include_raw_data=True) + assert result.trial_number == "IPR2023-00001" + assert result.raw_data == data + + def test_trial_document_from_dict_empty(self) -> None: + """Test PTABTrialDocument.from_dict() with empty dict.""" + result = PTABTrialDocument.from_dict({}) + assert result.trial_document_category is None + assert result.last_modified_date_time is None + assert result.trial_number is None + assert result.trial_type_code is None + assert result.trial_meta_data is None + assert result.patent_owner_data is None + assert result.regular_petitioner_data is None + assert result.respondent_data is None + assert result.derivation_petitioner_data is None + assert result.document_data is None + assert result.decision_data is None + assert result.raw_data is None + + def test_trial_document_response_from_dict_full(self) -> None: + """Test PTABTrialDocumentResponse.from_dict() with multiple documents.""" + data = { + "count": 2, + "patentTrialDocumentDataBag": [ + { + "trialNumber": "IPR2023-00001", + "trialDocumentCategory": "Document", + }, + { + "trialNumber": "IPR2023-00002", + "trialDocumentCategory": "Decision", + }, + ], + } + result = PTABTrialDocumentResponse.from_dict(data) + assert result.count == 2 + assert len(result.patent_trial_document_data_bag) == 2 + assert result.patent_trial_document_data_bag[0].trial_number == "IPR2023-00001" + assert ( + result.patent_trial_document_data_bag[0].trial_document_category + == "Document" + ) + assert result.patent_trial_document_data_bag[1].trial_number == "IPR2023-00002" + assert ( + result.patent_trial_document_data_bag[1].trial_document_category + == "Decision" + ) + assert result.raw_data is None + + def test_trial_document_response_from_dict_with_raw_data(self) -> None: + """Test PTABTrialDocumentResponse.from_dict() with include_raw_data=True.""" + data = { + "count": 1, + "patentTrialDocumentDataBag": [ + {"trialNumber": "IPR2023-00001"}, + ], + } + result = PTABTrialDocumentResponse.from_dict(data, include_raw_data=True) + assert result.count == 1 + assert result.raw_data == data + assert len(result.patent_trial_document_data_bag) == 1 + assert result.patent_trial_document_data_bag[0].raw_data == { + "trialNumber": "IPR2023-00001" + } + + def test_trial_document_response_from_dict_empty(self) -> None: + """Test PTABTrialDocumentResponse.from_dict() with empty list.""" + result = PTABTrialDocumentResponse.from_dict({}) + assert result.count == 0 + assert len(result.patent_trial_document_data_bag) == 0 + assert result.raw_data is None + + +class TestPTABAppealModels: + """Tests for PTAB appeal decision models.""" + + def test_appeal_metadata_from_dict_full(self) -> None: + """Test AppealMetaData.from_dict() with all fields.""" + data = { + "appealFilingDate": "2023-01-15", + "appealLastModifiedDate": "2023-06-01", + "appealLastModifiedDateTime": "2023-06-01T12:00:01", + "applicationTypeCategory": "Utility", + "docketNoticeMailedDate": "2023-02-01", + "fileDownloadURI": "https://example.com/appeal.zip", + } + result = AppealMetaData.from_dict(data) + assert result.appeal_filing_date == date(2023, 1, 15) + assert result.appeal_last_modified_date == date(2023, 6, 1) + assert result.appeal_last_modified_date_time == datetime( + 2023, 6, 1, 16, 0, 1, tzinfo=timezone.utc + ) + assert result.application_type_category == "Utility" + assert result.docket_notice_mailed_date == date(2023, 2, 1) + assert result.file_download_uri == "https://example.com/appeal.zip" + + def test_appeal_metadata_from_dict_empty(self) -> None: + """Test AppealMetaData.from_dict() with empty dict.""" + result = AppealMetaData.from_dict({}) + assert result.appeal_filing_date is None + assert result.appeal_last_modified_date is None + assert result.application_type_category is None + assert result.docket_notice_mailed_date is None + assert result.file_download_uri is None + + def test_appellant_data_from_dict(self) -> None: + """Test AppellantData.from_dict().""" + data = { + "applicationNumberText": "15/123456", + "counselName": "Appellant Counsel", + "groupArtUnitNumber": "3600", + "inventorName": "Jane Inventor", + "realPartyInInterestName": "Appellant Party", + "patentOwnerName": "Appellant Owner", + "publicationDate": "2023-01-15", + "publicationNumber": "US20230012345", + "technologyCenterNumber": "3600", + } + result = AppellantData.from_dict(data) + assert result.application_number_text == "15/123456" + assert result.counsel_name == "Appellant Counsel" + assert result.inventor_name == "Jane Inventor" + assert result.technology_center_number == "3600" + assert result.publication_date == date(2023, 1, 15) + + def test_requestor_data_from_dict(self) -> None: + """Test RequestorData.from_dict().""" + data = {"thirdPartyName": "Third Party Inc"} + result = RequestorData.from_dict(data) + assert result.third_party_name == "Third Party Inc" + + def test_requestor_data_from_dict_empty(self) -> None: + """Test RequestorData.from_dict() with empty dict.""" + result = RequestorData.from_dict({}) + assert result.third_party_name is None + + def test_appeal_document_data_from_dict_full(self) -> None: + """Test AppealDocumentData.from_dict() with all fields.""" + data = { + "documentFilingDate": "2023-01-15", + "documentIdentifier": "doc-uuid-1", + "documentName": "Appeal Brief", + "documentSizeQuantity": 12345, + "documentOCRText": "Full OCR text content", + "documentTypeDescriptionText": "Brief", + "fileDownloadURI": "https://example.com/download", + } + result = AppealDocumentData.from_dict(data) + assert result.document_filing_date == date(2023, 1, 15) + assert result.document_identifier == "doc-uuid-1" + assert result.document_name == "Appeal Brief" + assert result.document_size_quantity == 12345 + assert result.document_ocr_text == "Full OCR text content" + assert result.document_type_description_text == "Brief" + assert result.file_download_uri == "https://example.com/download" + + def test_appeal_document_data_from_dict_with_alias_downloaduri(self) -> None: + """Test AppealDocumentData.from_dict() handles downloadURI alias.""" + data = { + "documentName": "Brief.pdf", + "downloadURI": "https://example.com/brief.pdf", + } + result = AppealDocumentData.from_dict(data) + assert result.file_download_uri == "https://example.com/brief.pdf" + + def test_appeal_document_data_from_dict_with_alias_document_type(self) -> None: + """Test AppealDocumentData.from_dict() handles documentTypeCategory alias.""" + data = { + "documentName": "Decision.pdf", + "documentTypeCategory": "Decision", + } + result = AppealDocumentData.from_dict(data) + assert result.document_type_description_text == "Decision" + + def test_appeal_document_data_from_dict_empty(self) -> None: + """Test AppealDocumentData.from_dict() with empty dict.""" + result = AppealDocumentData.from_dict({}) + assert result.document_filing_date is None + assert result.document_identifier is None + assert result.document_name is None + assert result.document_size_quantity is None + assert result.document_ocr_text is None + assert result.document_type_description_text is None + assert result.file_download_uri is None + + def test_decision_data_from_dict_full(self) -> None: + """Test DecisionData.from_dict() with all fields.""" + data = { + "appealOutcomeCategory": "Affirmed", + "statuteAndRuleBag": ["35 U.S.C. § 103", "37 CFR 1.111"], + "decisionIssueDate": "2023-12-15", + "decisionTypeCategory": "Examiner Affirmed", + "issueTypeBag": ["Obviousness", "Anticipation"], + } + result = DecisionData.from_dict(data) + assert result.appeal_outcome_category == "Affirmed" + assert result.statute_and_rule_bag == ["35 U.S.C. § 103", "37 CFR 1.111"] + assert result.decision_issue_date == date(2023, 12, 15) + assert result.decision_type_category == "Examiner Affirmed" + assert result.issue_type_bag == ["Obviousness", "Anticipation"] + + def test_decision_data_from_dict_empty(self) -> None: + """Test DecisionData.from_dict() with empty dict.""" + result = DecisionData.from_dict({}) + assert result.appeal_outcome_category is None + assert result.statute_and_rule_bag == [] + assert result.decision_issue_date is None + assert result.decision_type_category is None + assert result.issue_type_bag == [] + + def test_decision_data_to_dict_with_empty_lists(self) -> None: + """Test DecisionData.to_dict() filters out empty lists.""" + # Create DecisionData with empty lists + result = DecisionData.from_dict( + { + "appealOutcomeCategory": "Affirmed", + "decisionIssueDate": "2023-12-15", + "statuteAndRuleBag": [], + "issueTypeBag": [], + } + ) + + # Convert to dict - empty lists should be filtered out + result_dict = result.to_dict() + + # Verify non-empty fields are present + assert result_dict["appealOutcomeCategory"] == "Affirmed" + assert result_dict["decisionIssueDate"] == "2023-12-15" + + # Verify empty lists are NOT in the result + assert "statuteAndRuleBag" not in result_dict + assert "issueTypeBag" not in result_dict + + def test_appeal_decision_from_dict_full(self) -> None: + """Test PTABAppealDecision.from_dict() with all nested objects.""" + data = { + "appealNumber": "2023-001234", + "lastModifiedDateTime": "2023-06-15T10:30:00Z", + "appealDocumentCategory": "Decision", + "appealMetaData": { + "appealFilingDate": "2023-01-15", + "applicationTypeCategory": "Utility", + }, + "appellantData": { + "applicationNumberText": "15/123456", + "counselName": "Test Counsel", + "technologyCenterNumber": "3600", + }, + "requestorData": {"thirdPartyName": "Third Party Inc"}, + "documentData": { + "documentName": "Final Decision", + "documentIdentifier": "doc-123", + }, + "decisionData": { + "decisionTypeCategory": "Affirmed", + "decisionIssueDate": "2023-06-01", + }, + } + result = PTABAppealDecision.from_dict(data) + assert result.appeal_number == "2023-001234" + assert result.last_modified_date_time == datetime( + 2023, 6, 15, 10, 30, 0, tzinfo=timezone.utc + ) + assert result.appeal_document_category == "Decision" + assert result.appeal_meta_data is not None + assert result.appeal_meta_data.application_type_category == "Utility" + assert result.appellant_data is not None + assert result.appellant_data.counsel_name == "Test Counsel" + assert result.requestor_data is not None + assert result.requestor_data.third_party_name == "Third Party Inc" + assert result.document_data is not None + assert result.document_data.document_name == "Final Decision" + assert result.decision_data is not None + assert result.decision_data.decision_type_category == "Affirmed" + assert result.raw_data is None + + def test_appeal_decision_from_dict_with_typo_appelant(self) -> None: + """Test PTABAppealDecision.from_dict() handles 'appelantData' typo.""" + data = { + "appealNumber": "2023-001234", + "appelantData": { + "counselName": "Test Counsel", + }, + } + result = PTABAppealDecision.from_dict(data) + assert result.appellant_data is not None + assert result.appellant_data.counsel_name == "Test Counsel" + + def test_appeal_decision_from_dict_with_raw_data(self) -> None: + """Test PTABAppealDecision.from_dict() with include_raw_data=True.""" + data = { + "appealNumber": "2023-001234", + "appealDocumentCategory": "Decision", + } + result = PTABAppealDecision.from_dict(data, include_raw_data=True) + assert result.appeal_number == "2023-001234" + assert result.raw_data == data + + def test_appeal_decision_from_dict_empty(self) -> None: + """Test PTABAppealDecision.from_dict() with empty dict.""" + result = PTABAppealDecision.from_dict({}) + assert result.appeal_number is None + assert result.last_modified_date_time is None + assert result.appeal_document_category is None + assert result.appeal_meta_data is None + assert result.appellant_data is None + assert result.requestor_data is None + assert result.document_data is None + assert result.decision_data is None + assert result.raw_data is None + + def test_appeal_response_from_dict_full(self) -> None: + """Test PTABAppealResponse.from_dict() with multiple appeals.""" + data = { + "count": 2, + "requestIdentifier": "request-uuid-1", + "patentAppealDataBag": [ + { + "appealNumber": "2023-001234", + "appealDocumentCategory": "Decision", + }, + { + "appealNumber": "2023-005678", + "appealDocumentCategory": "Brief", + }, + ], + } + result = PTABAppealResponse.from_dict(data) + assert result.count == 2 + assert result.request_identifier == "request-uuid-1" + assert len(result.patent_appeal_data_bag) == 2 + assert result.patent_appeal_data_bag[0].appeal_number == "2023-001234" + assert result.patent_appeal_data_bag[0].appeal_document_category == "Decision" + assert result.patent_appeal_data_bag[1].appeal_number == "2023-005678" + assert result.patent_appeal_data_bag[1].appeal_document_category == "Brief" + assert result.raw_data is None + + def test_appeal_response_from_dict_with_raw_data(self) -> None: + """Test PTABAppealResponse.from_dict() with include_raw_data=True.""" + data = { + "count": 1, + "requestIdentifier": "request-uuid-1", + "patentAppealDataBag": [ + {"appealNumber": "2023-001234"}, + ], + } + result = PTABAppealResponse.from_dict(data, include_raw_data=True) + assert result.count == 1 + assert result.raw_data == data + assert len(result.patent_appeal_data_bag) == 1 + assert result.patent_appeal_data_bag[0].raw_data == { + "appealNumber": "2023-001234" + } + + def test_appeal_response_from_dict_empty(self) -> None: + """Test PTABAppealResponse.from_dict() with empty list.""" + result = PTABAppealResponse.from_dict({}) + assert result.count == 0 + assert result.request_identifier is None + assert len(result.patent_appeal_data_bag) == 0 + assert result.raw_data is None + + +class TestPTABInterferenceModels: + """Tests for PTAB interference decision models.""" + + def test_interference_metadata_from_dict_full(self) -> None: + """Test InterferenceMetaData.from_dict() with all fields.""" + data = { + "interferenceStyleName": "Senior v. Junior", + "interferenceLastModifiedDate": "2023-03-15", + "fileDownloadURI": "https://example.com/interference.zip", + } + result = InterferenceMetaData.from_dict(data) + assert result.interference_style_name == "Senior v. Junior" + assert result.interference_last_modified_date == date(2023, 3, 15) + assert result.file_download_uri == "https://example.com/interference.zip" + + def test_interference_metadata_from_dict_empty(self) -> None: + """Test InterferenceMetaData.from_dict() with empty dict.""" + result = InterferenceMetaData.from_dict({}) + assert result.interference_style_name is None + assert result.interference_last_modified_date is None + assert result.file_download_uri is None + + def test_senior_party_data_from_dict(self) -> None: + """Test SeniorPartyData.from_dict().""" + data = { + "applicationNumberText": "12/345678", + "counselName": "Senior Counsel", + "grantDate": "2023-01-15", + "groupArtUnitNumber": "1600", + "realPartyInInterestName": "Senior Party Inc", + "patentNumber": "US1234567", + "patentOwnerName": "Senior Owner", + "technologyCenterNumber": "1600", + } + result = SeniorPartyData.from_dict(data) + assert result.application_number_text == "12/345678" + assert result.counsel_name == "Senior Counsel" + assert result.grant_date == date(2023, 1, 15) + assert result.patent_owner_name == "Senior Owner" + assert result.patent_number == "US1234567" + + def test_junior_party_data_from_dict(self) -> None: + """Test JuniorPartyData.from_dict().""" + data = { + "publicationNumber": "US20230012345", + "counselName": "Junior Counsel", + "groupArtUnitNumber": "1600", + "inventorName": "Jane Inventor", + "patentOwnerName": "Junior Owner", + "publicationDate": "2023-02-20", + "realPartyInInterestName": "Junior Party LLC", + "technologyCenterNumber": "1600", + } + result = JuniorPartyData.from_dict(data) + assert result.publication_number == "US20230012345" + assert result.counsel_name == "Junior Counsel" + assert result.inventor_name == "Jane Inventor" + assert result.patent_owner_name == "Junior Owner" + assert result.publication_date == date(2023, 2, 20) + + def test_additional_party_data_from_dict(self) -> None: + """Test AdditionalPartyData.from_dict().""" + data = { + "applicationNumberText": "14/111222", + "inventorName": "John Inventor", + "additionalPartyName": "Additional Entity", + "patentNumber": "US1112223", + } + result = AdditionalPartyData.from_dict(data) + assert result.application_number_text == "14/111222" + assert result.inventor_name == "John Inventor" + assert result.additional_party_name == "Additional Entity" + assert result.patent_number == "US1112223" + + def test_additional_party_data_from_dict_empty(self) -> None: + """Test AdditionalPartyData.from_dict() with empty dict.""" + result = AdditionalPartyData.from_dict({}) + assert result.application_number_text is None + assert result.inventor_name is None + assert result.additional_party_name is None + assert result.patent_number is None + + def test_interference_document_data_from_dict_full(self) -> None: + """Test InterferenceDocumentData.from_dict() with all fields.""" + data = { + "documentIdentifier": "doc-uuid-1", + "documentName": "Final Decision.pdf", + "documentSizeQuantity": 234567, + "documentOCRText": "Full OCR content...", + "documentTitleText": "Final Decision on Priority", + "interferenceOutcomeCategory": "Priority to Senior Party", + "decisionIssueDate": "2023-03-15", + "decisionTypeCategory": "Final Decision", + "fileDownloadURI": "https://example.com/decision.pdf", + "statuteAndRuleBag": ["35 U.S.C. § 102", "37 CFR 41.125"], + "issueTypeBag": ["Priority", "Patentability"], + } + result = InterferenceDocumentData.from_dict(data) + assert result.document_identifier == "doc-uuid-1" + assert result.document_name == "Final Decision.pdf" + assert result.document_size_quantity == 234567 + assert result.document_ocr_text == "Full OCR content..." + assert result.document_title_text == "Final Decision on Priority" + assert result.interference_outcome_category == "Priority to Senior Party" + assert result.decision_issue_date == date(2023, 3, 15) + assert result.decision_type_category == "Final Decision" + assert result.file_download_uri == "https://example.com/decision.pdf" + assert result.statute_and_rule_bag == ["35 U.S.C. § 102", "37 CFR 41.125"] + assert result.issue_type_bag == ["Priority", "Patentability"] + + def test_interference_document_data_from_dict_with_alias_downloaduri(self) -> None: + """Test InterferenceDocumentData.from_dict() handles downloadURI alias.""" + data = { + "documentName": "Decision.pdf", + "downloadURI": "https://example.com/decision.pdf", + } + result = InterferenceDocumentData.from_dict(data) + assert result.file_download_uri == "https://example.com/decision.pdf" + + def test_interference_document_data_from_dict_empty(self) -> None: + """Test InterferenceDocumentData.from_dict() with empty dict.""" + result = InterferenceDocumentData.from_dict({}) + assert result.document_identifier is None + assert result.document_name is None + assert result.document_size_quantity is None + assert result.document_ocr_text is None + assert result.document_title_text is None + assert result.interference_outcome_category is None + assert result.decision_issue_date is None + assert result.decision_type_category is None + assert result.file_download_uri is None + assert result.statute_and_rule_bag == [] + assert result.issue_type_bag == [] + + def test_interference_decision_from_dict_full(self) -> None: + """Test PTABInterferenceDecision.from_dict() with all nested objects.""" + data = { + "interferenceNumber": "106123", + "lastModifiedDateTime": "2023-03-15T10:30:00Z", + "interferenceMetaData": { + "interferenceStyleName": "Senior v. Junior", + "interferenceLastModifiedDate": "2023-03-15", + }, + "seniorPartyData": { + "patentOwnerName": "Senior Inc", + "applicationNumberText": "12/345678", + "patentNumber": "US1234567", + }, + "juniorPartyData": { + "patentOwnerName": "Junior LLC", + "publicationNumber": "US20230012345", + }, + "additionalPartyDataBag": [ + { + "additionalPartyName": "Additional Party 1", + "applicationNumberText": "14/111222", + }, + { + "additionalPartyName": "Additional Party 2", + "applicationNumberText": "14/333444", + }, + ], + "documentData": { + "interferenceOutcomeCategory": "Priority to Senior Party", + "decisionTypeCategory": "Final Decision", + }, + } + result = PTABInterferenceDecision.from_dict(data) + assert result.interference_number == "106123" + assert result.last_modified_date_time == datetime( + 2023, 3, 15, 10, 30, 0, tzinfo=timezone.utc + ) + assert result.interference_meta_data is not None + assert ( + result.interference_meta_data.interference_style_name == "Senior v. Junior" + ) + assert result.senior_party_data is not None + assert result.senior_party_data.patent_owner_name == "Senior Inc" + assert result.junior_party_data is not None + assert result.junior_party_data.patent_owner_name == "Junior LLC" + assert len(result.additional_party_data_bag) == 2 + assert ( + result.additional_party_data_bag[0].additional_party_name + == "Additional Party 1" + ) + assert ( + result.additional_party_data_bag[1].additional_party_name + == "Additional Party 2" + ) + assert result.document_data is not None + assert ( + result.document_data.interference_outcome_category + == "Priority to Senior Party" + ) + assert result.raw_data is None + + def test_interference_decision_from_dict_with_alias_decision_document_data( + self, + ) -> None: + """Test PTABInterferenceDecision.from_dict() handles decisionDocumentData alias.""" + data = { + "interferenceNumber": "106123", + "decisionDocumentData": { + "decisionTypeCategory": "Final Decision", + }, + } + result = PTABInterferenceDecision.from_dict(data) + assert result.document_data is not None + assert result.document_data.decision_type_category == "Final Decision" + + def test_interference_decision_from_dict_with_raw_data(self) -> None: + """Test PTABInterferenceDecision.from_dict() with include_raw_data=True.""" + data = { + "interferenceNumber": "106123", + } + result = PTABInterferenceDecision.from_dict(data, include_raw_data=True) + assert result.interference_number == "106123" + assert result.raw_data == data + + def test_interference_decision_from_dict_empty(self) -> None: + """Test PTABInterferenceDecision.from_dict() with empty dict.""" + result = PTABInterferenceDecision.from_dict({}) + assert result.interference_number is None + assert result.last_modified_date_time is None + assert result.interference_meta_data is None + assert result.senior_party_data is None + assert result.junior_party_data is None + assert len(result.additional_party_data_bag) == 0 + assert result.document_data is None + assert result.raw_data is None + + def test_interference_response_from_dict_full(self) -> None: + """Test PTABInterferenceResponse.from_dict() with multiple interferences.""" + data = { + "count": 2, + "requestIdentifier": "request-uuid-1", + "patentInterferenceDataBag": [ + { + "interferenceNumber": "106123", + }, + { + "interferenceNumber": "106456", + }, + ], + } + result = PTABInterferenceResponse.from_dict(data) + assert result.count == 2 + assert result.request_identifier == "request-uuid-1" + assert len(result.patent_interference_data_bag) == 2 + assert result.patent_interference_data_bag[0].interference_number == "106123" + assert result.patent_interference_data_bag[1].interference_number == "106456" + assert result.raw_data is None + + def test_interference_response_from_dict_with_raw_data(self) -> None: + """Test PTABInterferenceResponse.from_dict() with include_raw_data=True.""" + data = { + "count": 1, + "requestIdentifier": "request-uuid-1", + "patentInterferenceDataBag": [ + {"interferenceNumber": "106123"}, + ], + } + result = PTABInterferenceResponse.from_dict(data, include_raw_data=True) + assert result.count == 1 + assert result.raw_data == data + assert len(result.patent_interference_data_bag) == 1 + assert result.patent_interference_data_bag[0].raw_data == { + "interferenceNumber": "106123" + } + + def test_interference_response_from_dict_empty(self) -> None: + """Test PTABInterferenceResponse.from_dict() with empty list.""" + result = PTABInterferenceResponse.from_dict({}) + assert result.count == 0 + assert result.request_identifier is None + assert len(result.patent_interference_data_bag) == 0 + assert result.raw_data is None + + +class TestToDictRoundTripping: + """Tests for to_dict() methods via round-trip serialization using real API response data.""" + + def test_trial_proceeding_response_round_trip( + self, trial_proceeding_api_sample: dict[str, Any] + ) -> None: + """Test PTABTrialProceedingResponse round-trip: from_dict → to_dict.""" + # Parse the API response + response = PTABTrialProceedingResponse.from_dict(trial_proceeding_api_sample) + + # Convert back to dict + result = response.to_dict() + + # Verify top-level fields + assert result["count"] == trial_proceeding_api_sample["count"] + assert ( + result["requestIdentifier"] + == trial_proceeding_api_sample["requestIdentifier"] + ) + assert len(result["patentTrialProceedingDataBag"]) == 1 + + # Verify nested proceeding data + proceeding = result["patentTrialProceedingDataBag"][0] + original_proceeding = trial_proceeding_api_sample[ + "patentTrialProceedingDataBag" + ][0] + + assert proceeding["trialNumber"] == original_proceeding["trialNumber"] + assert ( + proceeding["trialMetaData"]["trialTypeCode"] + == original_proceeding["trialMetaData"]["trialTypeCode"] + ) + assert ( + proceeding["patentOwnerData"]["patentNumber"] + == original_proceeding["patentOwnerData"]["patentNumber"] + ) + + def test_trial_decision_response_round_trip( + self, trial_decision_api_sample: dict[str, Any] + ) -> None: + """Test PTABTrialDocumentResponse round-trip with decision data.""" + # Parse the API response + response = PTABTrialDocumentResponse.from_dict(trial_decision_api_sample) + + # Convert back to dict + result = response.to_dict() + + # Verify top-level fields + assert result["count"] == trial_decision_api_sample["count"] + assert len(result["patentTrialDocumentDataBag"]) == 1 + + # Verify nested document and decision data + document = result["patentTrialDocumentDataBag"][0] + original_document = trial_decision_api_sample["patentTrialDocumentDataBag"][0] + + assert document["trialNumber"] == original_document["trialNumber"] + assert ( + document["trialDocumentCategory"] + == original_document["trialDocumentCategory"] + ) + assert ( + document["documentData"]["documentName"] + == original_document["documentData"]["documentName"] + ) + assert ( + document["decisionData"]["decisionTypeCategory"] + == original_document["decisionData"]["decisionTypeCategory"] + ) + + def test_trial_document_response_round_trip( + self, trial_document_api_sample: dict[str, Any] + ) -> None: + """Test PTABTrialDocumentResponse round-trip with document data.""" + # Parse the API response + response = PTABTrialDocumentResponse.from_dict(trial_document_api_sample) + + # Convert back to dict + result = response.to_dict() + + # Verify top-level fields + assert result["count"] == trial_document_api_sample["count"] + assert len(result["patentTrialDocumentDataBag"]) == 2 + + # Verify nested document data + document = result["patentTrialDocumentDataBag"][0] + original_document = trial_document_api_sample["patentTrialDocumentDataBag"][0] + + assert document["trialNumber"] == original_document["trialNumber"] + assert ( + document["documentData"]["documentNumber"] + == original_document["documentData"]["documentNumber"] + ) + assert ( + document["regularPetitionerData"]["counselName"] + == original_document["regularPetitionerData"]["counselName"] + ) + + def test_party_data_with_dates_round_trip( + self, trial_proceeding_api_sample: dict[str, Any] + ) -> None: + """Test that PartyData properly serializes dates in to_dict().""" + # Get patent owner data which has dates + party_data_dict = trial_proceeding_api_sample["patentTrialProceedingDataBag"][ + 0 + ]["patentOwnerData"] + party = PatentOwnerData.from_dict(party_data_dict) + result = party.to_dict() + + # Verify date serialization + assert result["grantDate"] == party_data_dict["grantDate"] + assert isinstance(result["grantDate"], str) + assert result["patentNumber"] == party_data_dict["patentNumber"] + + def test_to_dict_filters_none_values(self) -> None: + """Test that to_dict() filters out None values.""" + data = { + "applicationNumberText": "12/345678", + "patentNumber": "US1234567", + # Other fields omitted, will be None + } + party = PartyData.from_dict(data) + result = party.to_dict() + + # Should only contain non-None values + assert "applicationNumberText" in result + assert "patentNumber" in result + # None fields should be filtered out + assert "grantDate" not in result + + def test_appeal_decision_response_round_trip( + self, appeal_decision_api_sample: dict[str, Any] + ) -> None: + """Test PTABAppealResponse round-trip: from_dict → to_dict.""" + # Parse the API response + response = PTABAppealResponse.from_dict(appeal_decision_api_sample) + + # Convert back to dict + result = response.to_dict() + + # Verify top-level fields + assert result["count"] == appeal_decision_api_sample["count"] + assert ( + result["requestIdentifier"] + == appeal_decision_api_sample["requestIdentifier"] + ) + assert len(result["patentAppealDataBag"]) == 1 + + # Verify nested appeal data + appeal = result["patentAppealDataBag"][0] + original_appeal = appeal_decision_api_sample["patentAppealDataBag"][0] + + assert appeal["appealNumber"] == original_appeal["appealNumber"] + assert ( + appeal["appealDocumentCategory"] + == original_appeal["appealDocumentCategory"] + ) + + # Verify appealMetaData.to_dict() was called + assert ( + appeal["appealMetaData"]["appealFilingDate"] + == original_appeal["appealMetaData"]["appealFilingDate"] + ) + assert ( + appeal["appealMetaData"]["applicationTypeCategory"] + == original_appeal["appealMetaData"]["applicationTypeCategory"] + ) + + # Verify appellantData.to_dict() was called + assert ( + appeal["appellantData"]["applicationNumberText"] + == original_appeal["appellantData"]["applicationNumberText"] + ) + assert ( + appeal["appellantData"]["counselName"] + == original_appeal["appellantData"]["counselName"] + ) + + # Verify requestorData.to_dict() was called + assert ( + appeal["requestorData"]["thirdPartyName"] + == original_appeal["requestorData"]["thirdPartyName"] + ) + + # Verify documentData.to_dict() was called + assert ( + appeal["documentData"]["documentName"] + == original_appeal["documentData"]["documentName"] + ) + assert ( + appeal["documentData"]["documentFilingDate"] + == original_appeal["documentData"]["documentFilingDate"] + ) + + # Verify decisionData.to_dict() was called + assert ( + appeal["decisionData"]["appealOutcomeCategory"] + == original_appeal["decisionData"]["appealOutcomeCategory"] + ) + assert ( + appeal["decisionData"]["decisionTypeCategory"] + == original_appeal["decisionData"]["decisionTypeCategory"] + ) + + def test_interference_decision_response_round_trip( + self, interference_decision_api_sample: dict[str, Any] + ) -> None: + """Test PTABInterferenceResponse round-trip: from_dict → to_dict.""" + # Parse the API response + response = PTABInterferenceResponse.from_dict(interference_decision_api_sample) + + # Convert back to dict + result = response.to_dict() + + # Verify top-level fields + assert result["count"] == interference_decision_api_sample["count"] + assert ( + result["requestIdentifier"] + == interference_decision_api_sample["requestIdentifier"] + ) + assert len(result["patentInterferenceDataBag"]) == 2 + + # Verify nested interference data + interference = result["patentInterferenceDataBag"][0] + original_interference = interference_decision_api_sample[ + "patentInterferenceDataBag" + ][0] + + assert ( + interference["interferenceNumber"] + == original_interference["interferenceNumber"] + ) + + # Verify interferenceMetaData.to_dict() was called + assert ( + interference["interferenceMetaData"]["interferenceStyleName"] + == original_interference["interferenceMetaData"]["interferenceStyleName"] + ) + assert ( + interference["interferenceMetaData"]["declarationDate"] + == original_interference["interferenceMetaData"]["declarationDate"] + ) + + # Verify seniorPartyData.to_dict() was called + assert ( + interference["seniorPartyData"]["patentNumber"] + == original_interference["seniorPartyData"]["patentNumber"] + ) + assert ( + interference["seniorPartyData"]["patentOwnerName"] + == original_interference["seniorPartyData"]["patentOwnerName"] + ) + + # Verify juniorPartyData.to_dict() was called + assert ( + interference["juniorPartyData"]["patentNumber"] + == original_interference["juniorPartyData"]["patentNumber"] + ) + assert ( + interference["juniorPartyData"]["publicationNumber"] + == original_interference["juniorPartyData"]["publicationNumber"] + ) + + # Verify documentData.to_dict() was called + assert ( + interference["documentData"]["documentName"] + == original_interference["documentData"]["documentName"] + ) + assert ( + interference["documentData"]["interferenceOutcomeCategory"] + == original_interference["documentData"]["interferenceOutcomeCategory"] + ) diff --git a/tests/models/test_utils.py b/tests/models/test_utils.py index caa62d1..d318e9a 100644 --- a/tests/models/test_utils.py +++ b/tests/models/test_utils.py @@ -1,10 +1,9 @@ """Tests for models.utils""" -from datetime import date, datetime, timedelta, timezone, tzinfo import importlib -from typing import Optional -from unittest.mock import patch import warnings +from datetime import date, datetime, timedelta, timezone, tzinfo +from unittest.mock import patch from zoneinfo import ZoneInfo, ZoneInfoNotFoundError import pytest @@ -13,17 +12,14 @@ # Import utility functions from models.utils module from pyUSPTO.models.utils import ( - ASSUMED_NAIVE_TIMEZONE, ASSUMED_NAIVE_TIMEZONE_STR, - parse_to_date, parse_to_datetime_utc, parse_yn_to_bool, serialize_bool_to_yn, serialize_date, serialize_datetime_as_iso, - to_camel_case, + serialize_datetime_as_naive, ) - from pyUSPTO.warnings import ( USPTOBooleanParseWarning, USPTODateParseWarning, @@ -94,28 +90,50 @@ def test_serialize_date(self) -> None: def test_serialize_datetime_as_iso(self) -> None: """Test serialize_datetime_as_iso utility function.""" dt_utc = datetime(2023, 1, 1, 10, 0, 0, tzinfo=timezone.utc) - assert serialize_datetime_as_iso(dt_utc) == "2023-01-01T10:00:00Z" + assert serialize_datetime_as_iso(dt_utc) == "2023-01-01T05:00:00.000-0500" - dt_naive = datetime(2023, 1, 1, 10, 0, 0) - assert serialize_datetime_as_iso(dt_naive) == "2023-01-01T10:00:00Z" + dt_naive = datetime(2023, 1, 2, 17, 0, 0) + assert serialize_datetime_as_iso(dt_naive) == "2023-01-02T17:00:00.000-0500" minus_five = timezone(timedelta(hours=-5)) - dt_est = datetime(2023, 1, 1, 10, 0, 0, tzinfo=minus_five) - assert serialize_datetime_as_iso(dt_est) == "2023-01-01T15:00:00Z" + dt_est = datetime(2023, 1, 3, 23, 0, 0, tzinfo=minus_five) + assert serialize_datetime_as_iso(dt_est) == "2023-01-03T23:00:00.000-0500" assert serialize_datetime_as_iso(None) is None + def test_serialize_datetime_as_naive(self) -> None: + """Test serialize_datetime_as_naive utility function with both aware and naive datetimes.""" + # Test with timezone-aware datetime (hits if branch, line 163-164) + dt_utc = datetime(2023, 1, 1, 10, 0, 0, tzinfo=timezone.utc) + result_aware = serialize_datetime_as_naive(dt_utc) + assert isinstance(result_aware, str) + # Should convert to ASSUMED_NAIVE_TIMEZONE and serialize as ISO + assert result_aware.startswith("2023-01-01") + + # Test with naive datetime (hits else branch, line 165-166) + dt_naive = datetime(2023, 1, 1, 15, 30, 45) + result_naive = serialize_datetime_as_naive(dt_naive) + assert isinstance(result_naive, str) + assert result_naive == "2023-01-01T15:30:45" + + # Test with timezone-aware datetime with offset + minus_five = timezone(timedelta(hours=-5)) + dt_est = datetime(2023, 1, 1, 10, 0, 0, tzinfo=minus_five) + result_est = serialize_datetime_as_naive(dt_est) + assert isinstance(result_est, str) + assert result_est.startswith("2023-01-01") + def test_parse_to_datetime_utc_localization_failure_and_fallback(self) -> None: """Triggers the except block by making astimezone() raise, and tests fallback path.""" class FailingTZ(tzinfo): - def utcoffset(self, dt: Optional[datetime]) -> None: + def utcoffset(self, dt: datetime | None) -> None: raise Exception("boom") - def dst(self, dt: Optional[datetime]) -> Optional[timedelta]: + def dst(self, dt: datetime | None) -> timedelta | None: return None - def tzname(self, dt: Optional[datetime]) -> Optional[str]: + def tzname(self, dt: datetime | None) -> str | None: return None dt_str = "2023-01-01T10:00:00" @@ -130,13 +148,13 @@ def test_parse_to_datetime_utc_fallback_to_utc_replace(self) -> None: """Triggers fallback to dt_obj.replace(tzinfo=timezone.utc) without touching datetime.*""" class FailingButEqualToUTC(tzinfo): - def utcoffset(self, dt: Optional[datetime]) -> None: + def utcoffset(self, dt: datetime | None) -> None: raise Exception("boom") - def dst(self, dt: Optional[datetime]) -> Optional[timedelta]: + def dst(self, dt: datetime | None) -> timedelta | None: return None - def tzname(self, dt: Optional[datetime]) -> Optional[str]: + def tzname(self, dt: datetime | None) -> str | None: return None def __eq__(self, other: object) -> bool: diff --git a/tests/test_config.py b/tests/test_config.py index 91a0fe1..149d368 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,4 @@ """Tests for USPTOConfig""" -import os - -import pytest from pyUSPTO.config import USPTOConfig from pyUSPTO.http_config import HTTPConfig @@ -64,7 +61,7 @@ def test_config_custom_base_urls(self): api_key="test", bulk_data_base_url="https://bulk.example.com", patent_data_base_url="https://patent.example.com", - petition_decisions_base_url="https://petition.example.com" + petition_decisions_base_url="https://petition.example.com", ) assert config.bulk_data_base_url == "https://bulk.example.com" assert config.patent_data_base_url == "https://patent.example.com" @@ -75,7 +72,9 @@ def test_config_from_env_custom_urls(self, monkeypatch): monkeypatch.setenv("USPTO_API_KEY", "test_key") monkeypatch.setenv("USPTO_BULK_DATA_BASE_URL", "https://bulk.example.com") monkeypatch.setenv("USPTO_PATENT_DATA_BASE_URL", "https://patent.example.com") - monkeypatch.setenv("USPTO_PETITION_DECISIONS_BASE_URL", "https://petition.example.com") + monkeypatch.setenv( + "USPTO_PETITION_DECISIONS_BASE_URL", "https://petition.example.com" + ) config = USPTOConfig.from_env() assert config.bulk_data_base_url == "https://bulk.example.com" diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index ef4783b..fa51009 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -5,7 +5,6 @@ and functions defined in pyUSPTO.exceptions. """ -from typing import Optional, Type, Union from unittest.mock import MagicMock import pytest @@ -205,14 +204,14 @@ class TestGetAPIException: ], ) def test_returns_correct_exception_type( - self, status_code: Optional[int], expected_exception_type: USPTOApiError + self, status_code: int | None, expected_exception_type: USPTOApiError ) -> None: """Test that get_api_exception returns the correct type of exception.""" message_val: str = "Test operation" - api_short_error_val: Optional[str] = "API Short Error" + api_short_error_val: str | None = "API Short Error" # Ensure error_details_val matches the Optional[Union[str, dict]] type - error_details_val: Optional[Union[str, dict]] = "Some details here." - request_identifier_val: Optional[str] = "req-id-test" + error_details_val: str | dict | None = "Some details here." + request_identifier_val: str | None = "req-id-test" if status_code is None: # For non-HTTP errors, these fields might be None api_short_error_val = None diff --git a/tests/test_http_config.py b/tests/test_http_config.py index 325069b..8cf8177 100644 --- a/tests/test_http_config.py +++ b/tests/test_http_config.py @@ -1,7 +1,6 @@ """Tests for HTTPConfig""" -import os -import pytest +import os from pyUSPTO.http_config import HTTPConfig @@ -31,7 +30,7 @@ def test_custom_values(self): retry_status_codes=[500, 503], pool_connections=20, pool_maxsize=30, - custom_headers={"User-Agent": "TestApp/1.0"} + custom_headers={"User-Agent": "TestApp/1.0"}, ) assert config.timeout == 60.0 assert config.connect_timeout == 15.0 @@ -62,8 +61,14 @@ def test_from_env(self, monkeypatch): def test_from_env_with_defaults(self): """Test HTTPConfig.from_env() uses defaults when env vars not set""" # Clear any existing env vars - for key in ["USPTO_REQUEST_TIMEOUT", "USPTO_CONNECT_TIMEOUT", "USPTO_MAX_RETRIES", - "USPTO_BACKOFF_FACTOR", "USPTO_POOL_CONNECTIONS", "USPTO_POOL_MAXSIZE"]: + for key in [ + "USPTO_REQUEST_TIMEOUT", + "USPTO_CONNECT_TIMEOUT", + "USPTO_MAX_RETRIES", + "USPTO_BACKOFF_FACTOR", + "USPTO_POOL_CONNECTIONS", + "USPTO_POOL_MAXSIZE", + ]: os.environ.pop(key, None) config = HTTPConfig.from_env() @@ -93,10 +98,7 @@ def test_custom_headers_none_by_default(self): def test_custom_headers_can_be_set(self): """Test custom_headers can be set""" - headers = { - "User-Agent": "MyApp/2.0", - "X-Custom-Header": "custom-value" - } + headers = {"User-Agent": "MyApp/2.0", "X-Custom-Header": "custom-value"} config = HTTPConfig(custom_headers=headers) assert config.custom_headers == headers diff --git a/tests/test_init.py b/tests/test_init.py index 7c62fc7..c3b9499 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -4,10 +4,8 @@ This module contains tests for import paths, version handling, and import error scenarios. """ -import importlib import sys -from importlib.metadata import PackageNotFoundError, version -from typing import Any +from importlib.metadata import PackageNotFoundError from unittest.mock import patch import pyUSPTO diff --git a/tests/utils/test_http.py b/tests/utils/test_http.py index b97bf8a..c9afb1a 100644 --- a/tests/utils/test_http.py +++ b/tests/utils/test_http.py @@ -4,8 +4,6 @@ from unittest.mock import ANY, MagicMock, patch -import pytest - from pyUSPTO.utils.http import create_session diff --git a/tox.ini b/tox.ini index dfc82aa..6042ae2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ [tox] +# py315 commented out - requires C++ build tools for librt (mypy dependency) envlist = py310,py311,py312,py313,py314 isolated_build = True @@ -9,6 +10,7 @@ basepython = py312: {env:LOCALAPPDATA}\Python\pythoncore-3.12-64\python.exe py313: {env:LOCALAPPDATA}\Python\pythoncore-3.13-64\python.exe py314: {env:LOCALAPPDATA}\Python\pythoncore-3.14-64\python.exe + # py315: {env:LOCALAPPDATA}\Python\pythoncore-3.15-64\python.exe deps = -r requirements-dev.txt commands =