diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml new file mode 100644 index 0000000..b829da2 --- /dev/null +++ b/.github/workflows/publish-to-test-pypi.yml @@ -0,0 +1,80 @@ +name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI + +on: push + +jobs: + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + # only publish to PyPI on tag pushes + if: startsWith(github.ref, 'refs/tags/') + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/pyUSPTO + permissions: + id-token: write + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/pyUSPTO + + permissions: + id-token: write + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + attestations: true + skip-existing: true + verbose: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a88e98..d85b7e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,11 +50,16 @@ git checkout -b feature/your-feature-name ```bash # Run the test suite -pytest +python -m pytest tests/ --cov=pyUSPTO --cov-report=term --cov-report=term-missing -vv # Run linting and type checking -flake8 -mypy pyUSPTO +flake8 src/pyUSPTO --count --select=E9,F63,F7,F82,D100,D101,D102,D103 --show-source --statistics +mypy ./src/ + +# Correct Formatting +black . + + ``` ### Submit a Pull Request @@ -109,6 +114,57 @@ Types include: 4. PRs require approval from at least one maintainer 5. Once approved, a maintainer will merge your PR +## Testing + +The library includes unit and integration tests using pytest. + +### Running Tests + +1. **Run all tests (excluding integration tests)**: + + ```bash + python -m pytest pyUSPTO/tests/ + ``` + +2. **Run tests with verbose output**: + + ```bash + python -m pytest pyUSPTO/tests/ -v + ``` + +3. **Run specific test files**: + + ```bash + python -m pytest pyUSPTO/tests/test_base_client.py + python -m pytest pyUSPTO/tests/test_bulk_data.py + python -m pytest pyUSPTO/tests/test_patent_data.py + ``` + +4. **Run specific test classes or methods**: + + ```bash + python -m pytest pyUSPTO/tests/test_bulk_data.py::TestBulkDataClient + python -m pytest pyUSPTO/tests/test_bulk_data.py::TestBulkDataClient::test_download_file + ``` + +5. **Run integration tests** (these are skipped by default): + + ```bash + # On Windows + set ENABLE_INTEGRATION_TESTS=true + python -m pytest pyUSPTO/tests/test_integration.py -v + + # On Unix/Linux/macOS + ENABLE_INTEGRATION_TESTS=true python -m pytest pyUSPTO/tests/test_integration.py -v + ``` + +6. **Run tests with coverage report**: + ```bash + python -m pytest pyUSPTO/tests/ --cov=pyUSPTO + ``` + +The tests are designed to use mocking to avoid making real API calls, making them fast and reliable. The integration tests are optional and will make real API calls to the USPTO API if enabled. + ## Versioning The project uses setuptools-scm for version management: diff --git a/README.md b/README.md index b54f936..edd9692 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,17 @@ # 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/) A Python client library for interacting with the USPTO APIs. This package provides clients for interacting with both the USPTO Bulk Data API and the USPTO Patent Data API. It features comprehensive type hints and docstrings for improved developer experience. -## Installation +## Quick Start + +### Installation + +**Requirements**: Python ≥3.10 ```bash pip install pyUSPTO @@ -18,100 +25,18 @@ cd pyUSPTO pip install -e . ``` -### Development Setup - -For development, you can install the required dependencies using the provided requirements files: - -```bash -# Install core dependencies -pip install -r requirements.txt - -# Install development dependencies (includes testing, linting, etc.) -pip install -r requirements-dev.txt -``` - -## Features - -- Access to both USPTO Bulk Data API and Patent Data API -- Search for products and patents using various filters -- Download files and documents from the APIs -- Type-safe data models using Python dataclasses -- Well-organized package structure for better maintainability - -## Package Structure - -The library is organized into the following modules: - -``` -pyUSPTO/ -├── __init__.py # Package initialization and top-level exports -├── config.py # Configuration management -├── exceptions.py # All exceptions in one place -├── clients/ # Client implementations -│ ├── __init__.py -│ ├── bulk_data.py # Bulk data client -│ └── patent_data.py # Patent data client -├── models/ # Data models -│ ├── __init__.py -│ ├── base.py # Common model utilities -│ ├── bulk_data.py # Bulk data models -│ └── patent_data.py # Patent data models -└── utils/ # Utilities - ├── __init__.py - └── http.py # HTTP-related utilities -``` - -This structure provides better separation of concerns, improved maintainability, and a clearer dependency graph. - -## Development Status - -The development of this library is at version 0.1.6-dev. The following major features have been implemented: - -### Documentation Setup (Complete) - -The project documentation is set up using Sphinx with the following features: - -- ✅ Read the Docs theme -- ✅ Auto-documentation from docstrings using autodoc and napoleon -- ✅ Advanced features like intersphinx, typehints, and copybutton -- ✅ Full documentation structure with installation, quickstart, and API reference - -The documentation is structured as follows: - -- `index.rst` - Main landing page -- `installation.rst` - Installation instructions -- `quickstart.rst` - Quick start guide -- `api/` - API reference (auto-generated) -- `examples/` - Code examples with explanations -- `development/` - Contributing guidelines - -### Package Configuration (Complete) - -The project uses modern Python packaging with: - -- ✅ Comprehensive pyproject.toml configuration -- ✅ Automatic versioning via setuptools-scm -- ✅ Tool configurations for testing, linting, and type checking - -### Future Improvements - -- Add more code examples -- Set up documentation hosting on Read the Docs -- Expand test coverage - -## Quick Start ### Configuration Options There are multiple ways to configure the USPTO API clients: ```python -from pyUSPTO import BulkDataClient, PatentDataClient +from pyUSPTO import PatentDataClient from pyUSPTO.config import USPTOConfig import os # Method 1: Direct API key initialization -client1 = BulkDataClient(api_key="your_api_key_here") +client1 = PatentDataClient(api_key="your_api_key_here") # Method 2: Using USPTOConfig with explicit parameters config = USPTOConfig( @@ -119,139 +44,33 @@ config = USPTOConfig( bulk_data_base_url="https://api.uspto.gov/api/v1/datasets", patent_data_base_url="https://api.uspto.gov/api/v1/patent" ) -client2 = BulkDataClient(config=config) +client2 = PatentDataClient(config=config) # Method 3: Using environment variables (recommended for production) os.environ["USPTO_API_KEY"] = "your_api_key_here" config_from_env = USPTOConfig.from_env() -client3 = BulkDataClient(config=config_from_env) -``` - -### Securely Handling API Keys - -When working with the USPTO APIs, it's important to handle your API keys securely: - -1. **Never hardcode API keys in your source code** - - - Hardcoded keys can accidentally be committed to version control - - This creates security risks if your repository is public or compromised - -2. **Environment Variables (Recommended)** - - - Store API keys in environment variables - - Access them at runtime using `os.environ` - - For development, use a `.env` file with a package like `python-dotenv` - - For production, configure environment variables in your deployment platform - - ```python - # Example using python-dotenv (pip install python-dotenv) - from dotenv import load_dotenv - import os - - # Load variables from .env file - load_dotenv() - - # Access the API key - api_key = os.environ.get("USPTO_API_KEY") - client = BulkDataClient(api_key=api_key) - ``` - -3. **Secret Management Services** - - - For production applications, consider using a secret management service - - Options include AWS Secrets Manager, Google Secret Manager, HashiCorp Vault, etc. - -4. **Configuration Files** - - If you must use configuration files, ensure they are: - - Added to your `.gitignore` file - - Have restricted file permissions - - Located outside your application's source directory - -Remember that the most secure approach is to use environment variables or a dedicated secrets management solution, especially in production environments. - -### Bulk Data API - -```python -from pyUSPTO import BulkDataClient - -# Initialize the client (see Configuration Options for more methods) -client = BulkDataClient(api_key="your_api_key_here") - -# Get all available products -response = client.get_products() -print(f"Found {response.count} products") - -# Get a specific product by ID -product = client.get_product_by_id("EXAMPLE_PRODUCT_123") - -# Download a file -if product.product_file_bag.file_data_bag: - file_data = product.product_file_bag.file_data_bag[0] - downloaded_path = client.download_file(file_data, "./downloads") - print(f"Downloaded file to: {downloaded_path}") +client3 = PatentDataClient(config=config_from_env) ``` ### Patent Data API ```python -from pyUSPTO import PatentDataClient - -# Initialize the client (see Configuration Options for more methods) -client = PatentDataClient(api_key="your_api_key_here") - -# Search for patents by inventor name -inventor_search = client.search_patents(inventor_name="Smith") -print(f"Found {inventor_search.count} patents with 'Smith' as inventor") - -# Get a specific patent by application number -patent = client.get_patent_by_application_number("12345678") -print(f"Retrieved patent application: {patent.application_number_text}") - -# Get a specific patent by patent number -patent_search = client.search_patents(patent_number="10000000", limit=1) -if patent_search.count > 0: - patent = patent_search.patent_file_wrapper_data_bag[0] - print(f"Retrieved patent: US{patent.application_meta_data.patent_number}") +# Search for applications by inventor name +inventor_search = client1.search_applications(inventor_name_q="Smith") +print(f"Found {inventor_search.count} applications with 'Smith' as inventor") +# > Found 104926 applications with 'Smith' as inventor. ``` -## Type Hints and Documentation - -This library is fully typed with Python type hints, making it compatible with static type checkers like mypy and providing excellent IDE support with autocompletion and inline documentation. Every function, method, class, and module has comprehensive docstrings that explain: +## Features -- Purpose and functionality -- Parameters with types and descriptions -- Return values with types and descriptions -- Examples where appropriate +- Access to both USPTO Bulk Data API and Patent Data API +- Search for products and patents using various filters +- Download files and documents from the APIs -Example of type hints in action: - -```python -def search_patents( - self, - query: Optional[str] = None, - inventor_name: Optional[str] = None, - filing_date_from: Optional[str] = None, - filing_date_to: Optional[str] = None, - limit: Optional[int] = 25, -) -> PatentDataResponse: - """ - Search for patents with various filters. - - Args: - query: Search text in all fields - inventor_name: Filter by inventor name - filing_date_from: Filter by filing date from (YYYY-MM-DD) - filing_date_to: Filter by filing date to (YYYY-MM-DD) - limit: Number of results to return (default 25) - - Returns: - PatentDataResponse object containing matching patents - """ -``` ## Data Models -The library uses Python dataclasses to represent API responses. All data models include comprehensive type hints and docstrings for improved IDE support and documentation: +The library uses Python dataclasses to represent API responses. All data models include proper type annotations for attributes and methods, making them fully compatible with static type checkers. ### Bulk Data API @@ -260,29 +79,6 @@ The library uses Python dataclasses to represent API responses. All data models - `ProductFileBag`: Container for file data elements - `FileData`: Information about an individual file -Example of a data model with type hints: - -```python -@dataclass -class FileData: - """Represents a file in the bulk data API.""" - - file_name: str - file_size: int - file_data_from_date: str - 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 - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "FileData": - """Create a FileData object from a dictionary.""" - # Implementation... -``` - ### Patent Data API - `PatentDataResponse`: Top-level response from the API @@ -295,59 +91,14 @@ class FileData: - `PatentTermAdjustmentData`: Patent term adjustment information - And many more specialized classes for different aspects of patent data -All data models include proper type annotations for attributes and methods, making them fully compatible with static type checkers. - -## Testing - -The library includes unit and integration tests using pytest. - -### Running Tests - -1. **Run all tests (excluding integration tests)**: - - ```bash - python -m pytest pyUSPTO/tests/ - ``` - -2. **Run tests with verbose output**: - - ```bash - python -m pytest pyUSPTO/tests/ -v - ``` - -3. **Run specific test files**: - - ```bash - python -m pytest pyUSPTO/tests/test_base_client.py - python -m pytest pyUSPTO/tests/test_bulk_data.py - python -m pytest pyUSPTO/tests/test_patent_data.py - ``` - -4. **Run specific test classes or methods**: - - ```bash - python -m pytest pyUSPTO/tests/test_bulk_data.py::TestBulkDataClient - python -m pytest pyUSPTO/tests/test_bulk_data.py::TestBulkDataClient::test_download_file - ``` - -5. **Run integration tests** (these are skipped by default): +## License - ```bash - # On Windows - set ENABLE_INTEGRATION_TESTS=true - python -m pytest pyUSPTO/tests/test_integration.py -v +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - # On Unix/Linux/macOS - ENABLE_INTEGRATION_TESTS=true python -m pytest pyUSPTO/tests/test_integration.py -v - ``` +## Contributing -6. **Run tests with coverage report**: - ```bash - python -m pytest pyUSPTO/tests/ --cov=pyUSPTO - ``` +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute to this project. -The tests are designed to use mocking to avoid making real API calls, making them fast and reliable. The integration tests are optional and will make real API calls to the USPTO API if enabled. +--- -## License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +**For badges**: You'll need to add these to the top of your README after the title: diff --git a/dist/pyuspto-0.1.6.dev67+g4af4e34-py3-none-any.whl b/dist/pyuspto-0.1.6.dev67+g4af4e34-py3-none-any.whl new file mode 100644 index 0000000..9385696 Binary files /dev/null and b/dist/pyuspto-0.1.6.dev67+g4af4e34-py3-none-any.whl differ diff --git a/dist/pyuspto-0.1.6.dev67+g4af4e34.tar.gz b/dist/pyuspto-0.1.6.dev67+g4af4e34.tar.gz new file mode 100644 index 0000000..d11dd7c Binary files /dev/null and b/dist/pyuspto-0.1.6.dev67+g4af4e34.tar.gz differ diff --git a/examples/ifw_example.py b/examples/ifw_example.py index af11a62..f0dcbf5 100644 --- a/examples/ifw_example.py +++ b/examples/ifw_example.py @@ -36,7 +36,7 @@ print("\nGet IFW Based on Publication Number ->") -pub_no_ifw = client.get_IFW(publication_number="20150157873") +pub_no_ifw = client.get_IFW(publication_number="*20150157873*") 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") @@ -50,7 +50,7 @@ print("\nGet IFW Based on PCT Pub Number ->") -pct_pub_no_ifw = client.get_IFW(PCT_pub_number="2009064413") +pct_pub_no_ifw = client.get_IFW(PCT_pub_number="*2009064413*") 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") @@ -59,18 +59,18 @@ 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(app_no_ifw.grant_document_meta_data) 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(f"-Downloaded document to: {file_path}") print("Now 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) download_path = "./download-example" file_path = client.download_archive( printed_metadata=grant_archive, destination_path=download_path, overwrite=True ) - print(f"Downloaded document to: {file_path}") + print(f"-Downloaded document to: {file_path}") diff --git a/pyproject.toml b/pyproject.toml index 8678af2..24f6ca5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=64", "wheel", "setuptools_scm>=8"] +requires = ["setuptools>=80", "wheel", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] @@ -11,6 +11,7 @@ authors = [ readme = "README.md" requires-python = ">=3.10" license = "MIT" +license-files = ["LICENSE*"] classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", diff --git a/src/pyUSPTO/clients/patent_data.py b/src/pyUSPTO/clients/patent_data.py index 06fef04..9f3a505 100644 --- a/src/pyUSPTO/clients/patent_data.py +++ b/src/pyUSPTO/clients/patent_data.py @@ -164,11 +164,11 @@ def search_applications( ) if earliestPublicationNumber_q: q_parts.append( - f"applicationMetaData.earliestPublicationNumber:*{earliestPublicationNumber_q}*" + f"applicationMetaData.earliestPublicationNumber:{earliestPublicationNumber_q}" ) if pctPublicationNumber_q: q_parts.append( - f"applicationMetaData.pctPublicationNumber:*{pctPublicationNumber_q}*" + f"applicationMetaData.pctPublicationNumber:{pctPublicationNumber_q}" ) if filing_date_from_q and filing_date_to_q: q_parts.append( diff --git a/tests/clients/test_patent_data_clients.py b/tests/clients/test_patent_data_clients.py index 39587ff..0907057 100644 --- a/tests/clients/test_patent_data_clients.py +++ b/tests/clients/test_patent_data_clients.py @@ -383,12 +383,12 @@ def test_search_applications_post( "applicationMetaData.cpcClassificationBag:H04L", ), ( - {"earliestPublicationNumber_q": "12345678"}, + {"earliestPublicationNumber_q": "*12345678*"}, "applicationMetaData.earliestPublicationNumber:*12345678*", ), ( {"pctPublicationNumber_q": "PCTUS202501234567"}, - "applicationMetaData.pctPublicationNumber:*PCTUS202501234567*", + "applicationMetaData.pctPublicationNumber:PCTUS202501234567", ), ( {"filing_date_from_q": "2021-01-01"}, @@ -1086,7 +1086,7 @@ def test_get_ifw_by_publication_number( method="GET", endpoint="api/v1/patent/applications/search", params={ - "q": f"applicationMetaData.earliestPublicationNumber:*{pub_num}*", + "q": f"applicationMetaData.earliestPublicationNumber:{pub_num}", "limit": 1, "offset": 0, }, @@ -1135,7 +1135,7 @@ def test_get_ifw_by_pct_pub_number( method="GET", endpoint="api/v1/patent/applications/search", params={ - "q": f"applicationMetaData.pctPublicationNumber:*{pct_pub}*", + "q": f"applicationMetaData.pctPublicationNumber:{pct_pub}", "limit": 1, "offset": 0, },