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, )