patent_odp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +139 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +392 -0
- data/ROADMAP.md +189 -0
- data/Rakefile +22 -0
- data/lib/patent_odp/application.rb +107 -0
- data/lib/patent_odp/client.rb +112 -0
- data/lib/patent_odp/configuration.rb +37 -0
- data/lib/patent_odp/errors.rb +27 -0
- data/lib/patent_odp/version.rb +5 -0
- data/lib/patent_odp.rb +34 -0
- data/sig/patent_odp.rbs +4 -0
- metadata +86 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e98c0359a5ab7545b88c5f13ea0773d6bd84956daaa4b4cf200d1fdbeb82fe07
|
|
4
|
+
data.tar.gz: 2d6c66fe82ed82623f0fb1a9fd44c8400a058b343e633fad1dff0f21e4d483b9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 38759c72bf38b3a9ea7152fcd4863210a35851ee469b817fcb6e88ba8345991d353b0ecb7a5d1fe529fba7094e2296ab0b5f62630c1f9cd9e9dcc68ed047801a
|
|
7
|
+
data.tar.gz: 07f2d0c94b876b006fb679b69d77efd503453f3aea06fd79c8239c69ccc8282a661dc726f87c55e741fdc536f625a179deb59c194be7e2398b09f8d640fa3f72
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Planned
|
|
11
|
+
- Search API support
|
|
12
|
+
- Document retrieval
|
|
13
|
+
- Transaction history
|
|
14
|
+
- Assignment data
|
|
15
|
+
- Pagination support
|
|
16
|
+
|
|
17
|
+
See [ROADMAP.md](ROADMAP.md) for detailed future plans.
|
|
18
|
+
|
|
19
|
+
## [0.1.0] - 2024-10-16
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
#### Core Features
|
|
24
|
+
- Application metadata retrieval by application number via `Client#application`
|
|
25
|
+
- `PatentODP::Client` class for HTTP communication with USPTO API
|
|
26
|
+
- `PatentODP::Application` class representing patent application data with clean Ruby API
|
|
27
|
+
- Global configuration via `PatentODP.configure`
|
|
28
|
+
- Per-client configuration override support
|
|
29
|
+
|
|
30
|
+
#### Application Data Access
|
|
31
|
+
- `Application#id` - Application number
|
|
32
|
+
- `Application#title` - Patent/application title
|
|
33
|
+
- `Application#patent_number` - Patent number if granted
|
|
34
|
+
- `Application#filing_date` - Filing date (Date object)
|
|
35
|
+
- `Application#status` - Application status
|
|
36
|
+
- `Application#status_date` - Status date (Date object)
|
|
37
|
+
- `Application#early_publication_number` - Publication number
|
|
38
|
+
- `Application#early_publication_date` - Publication date (Date object)
|
|
39
|
+
- `Application#inventors` - Array of inventor names
|
|
40
|
+
- `Application#applicants` - Array of applicant names
|
|
41
|
+
- `Application#patented?` - Boolean helper for patent status
|
|
42
|
+
- `Application#to_h` - Access raw API response data
|
|
43
|
+
- `Application#inspect` - Human-readable string representation
|
|
44
|
+
|
|
45
|
+
#### Configuration Options
|
|
46
|
+
- `api_key` - USPTO API key (required)
|
|
47
|
+
- `timeout` - HTTP request timeout in seconds (default: 30)
|
|
48
|
+
- `retry_enabled` - Enable/disable automatic retry logic (default: true)
|
|
49
|
+
- `base_url` - API base URL (read-only, set to USPTO endpoint)
|
|
50
|
+
|
|
51
|
+
#### Error Handling
|
|
52
|
+
- `PatentODP::Error` - Base error class
|
|
53
|
+
- `PatentODP::ConfigurationError` - Configuration validation errors
|
|
54
|
+
- `PatentODP::APIError` - Base class for API-related errors
|
|
55
|
+
- `PatentODP::ClientError` - 4xx HTTP errors
|
|
56
|
+
- `PatentODP::ServerError` - 5xx HTTP errors
|
|
57
|
+
- `PatentODP::UnauthorizedError` - 401 authentication errors
|
|
58
|
+
- `PatentODP::NotFoundError` - 404 not found errors
|
|
59
|
+
- `PatentODP::RateLimitError` - 429 rate limit errors
|
|
60
|
+
|
|
61
|
+
#### Security Features
|
|
62
|
+
- Input validation for application numbers to prevent path traversal attacks
|
|
63
|
+
- Character whitelist validation (alphanumeric, underscore, hyphen only)
|
|
64
|
+
- Type safety checks for all configuration values
|
|
65
|
+
- API key validation before requests
|
|
66
|
+
- Timeout value validation (must be positive number)
|
|
67
|
+
- Protection against non-string API keys
|
|
68
|
+
- Defensive nil checks throughout
|
|
69
|
+
|
|
70
|
+
#### Developer Experience
|
|
71
|
+
- Automatic date parsing (converts strings to Ruby `Date` objects)
|
|
72
|
+
- Snake_case method names following Ruby conventions
|
|
73
|
+
- Detailed YARD documentation
|
|
74
|
+
- Helpful error messages with configuration hints
|
|
75
|
+
- Clean, chainable API design
|
|
76
|
+
|
|
77
|
+
#### HTTP Client Features
|
|
78
|
+
- Built on Faraday for reliable HTTP communication
|
|
79
|
+
- Automatic retry logic for transient failures (429, 5xx errors)
|
|
80
|
+
- Configurable retry behavior (max: 3, with exponential backoff)
|
|
81
|
+
- Proper timeout handling (connect and read timeouts)
|
|
82
|
+
- HTTPS-only communication
|
|
83
|
+
- API key sent via `X-API-KEY` header
|
|
84
|
+
|
|
85
|
+
#### Testing
|
|
86
|
+
- 72 comprehensive test cases
|
|
87
|
+
- 98%+ code coverage with SimpleCov
|
|
88
|
+
- Unit tests for all components
|
|
89
|
+
- Integration tests with mocked API responses
|
|
90
|
+
- Security tests for input validation
|
|
91
|
+
- Error handling tests
|
|
92
|
+
- WebMock integration for reliable HTTP testing
|
|
93
|
+
- Fast test suite (< 0.05 seconds)
|
|
94
|
+
|
|
95
|
+
#### Documentation
|
|
96
|
+
- Comprehensive README with usage examples
|
|
97
|
+
- Rails integration examples
|
|
98
|
+
- Error handling guide
|
|
99
|
+
- Security best practices
|
|
100
|
+
- ROADMAP documenting planned features
|
|
101
|
+
- Inline code documentation with YARD
|
|
102
|
+
|
|
103
|
+
#### Dependencies
|
|
104
|
+
- `faraday` (~> 2.0) - HTTP client
|
|
105
|
+
- `faraday-retry` (~> 2.0) - Automatic retry middleware
|
|
106
|
+
- Development: `rspec`, `webmock`, `rubocop`, `simplecov`
|
|
107
|
+
|
|
108
|
+
### Technical Details
|
|
109
|
+
|
|
110
|
+
#### API Coverage
|
|
111
|
+
- `GET /api/v1/patent/applications/{application_number}` - Retrieve application metadata
|
|
112
|
+
|
|
113
|
+
#### Ruby Version
|
|
114
|
+
- Requires Ruby >= 3.2.0
|
|
115
|
+
- Tested on Ruby 3.4.x
|
|
116
|
+
|
|
117
|
+
#### Performance
|
|
118
|
+
- Default 30-second timeout
|
|
119
|
+
- Configurable connection and read timeouts
|
|
120
|
+
- Optional retry logic can be disabled for faster failures in tests
|
|
121
|
+
|
|
122
|
+
### Security
|
|
123
|
+
|
|
124
|
+
#### Vulnerability Fixes
|
|
125
|
+
- Fixed path traversal vulnerability in application number handling
|
|
126
|
+
- Added input validation regex to prevent injection attacks
|
|
127
|
+
- Added type checking to prevent NoMethodError on invalid inputs
|
|
128
|
+
- Added JSON parsing error handling
|
|
129
|
+
- Protected against malformed API responses
|
|
130
|
+
|
|
131
|
+
#### Best Practices
|
|
132
|
+
- HTTPS-only communication
|
|
133
|
+
- No secrets logged or exposed in errors
|
|
134
|
+
- API keys passed via headers (not URL)
|
|
135
|
+
- Comprehensive input validation
|
|
136
|
+
- Safe error messages that don't leak internal details
|
|
137
|
+
|
|
138
|
+
[unreleased]: https://github.com/zalepa/patent_odp/compare/v0.1.0...HEAD
|
|
139
|
+
[0.1.0]: https://github.com/zalepa/patent_odp/releases/tag/v0.1.0
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official email address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
[INSERT CONTACT METHOD].
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
+
ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
+
community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.1, available at
|
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by
|
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
+
|
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
+
|
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 George Zalepa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# PatentODP
|
|
2
|
+
|
|
3
|
+
A Ruby gem for interacting with the USPTO's Open Data Portal (ODP) API. This gem provides a clean, idiomatic Ruby interface to access patent file wrapper data including application metadata, documents, and more.
|
|
4
|
+
|
|
5
|
+
[]()
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
8
|
+
[](LICENSE.txt)
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Usage](#usage)
|
|
16
|
+
- [Basic Usage](#basic-usage)
|
|
17
|
+
- [Working with Applications](#working-with-applications)
|
|
18
|
+
- [Error Handling](#error-handling)
|
|
19
|
+
- [Advanced Configuration](#advanced-configuration)
|
|
20
|
+
- [Security](#security)
|
|
21
|
+
- [Development](#development)
|
|
22
|
+
- [Roadmap](#roadmap)
|
|
23
|
+
- [Contributing](#contributing)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
### Currently Implemented ✅
|
|
29
|
+
|
|
30
|
+
- **Application Metadata Retrieval** - Fetch detailed patent application information by application number
|
|
31
|
+
- **Clean Ruby API** - Idiomatic Ruby interface with snake_case methods and automatic date parsing
|
|
32
|
+
- **Comprehensive Error Handling** - Specific error classes for different failure modes
|
|
33
|
+
- **Input Validation** - Protection against path traversal and injection attacks
|
|
34
|
+
- **Automatic Retries** - Built-in retry logic for transient failures (configurable)
|
|
35
|
+
- **Type Safety** - Robust validation of all inputs
|
|
36
|
+
- **Well Tested** - 72+ test cases with 98%+ code coverage
|
|
37
|
+
|
|
38
|
+
### Planned Features 🚧
|
|
39
|
+
|
|
40
|
+
See [ROADMAP.md](ROADMAP.md) for upcoming features including:
|
|
41
|
+
- Search API support
|
|
42
|
+
- Document retrieval
|
|
43
|
+
- Transaction history
|
|
44
|
+
- Assignment data
|
|
45
|
+
- Pagination support
|
|
46
|
+
- Bulk operations
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Add this line to your application's Gemfile:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
gem 'patent_odp'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
And then execute:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bundle install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or install it yourself as:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
gem install patent_odp
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
### Getting an API Key
|
|
71
|
+
|
|
72
|
+
You'll need a USPTO API key to use this gem. Get one for free at:
|
|
73
|
+
[https://data.uspto.gov/apis/getting-started](https://data.uspto.gov/apis/getting-started)
|
|
74
|
+
|
|
75
|
+
### Basic Configuration
|
|
76
|
+
|
|
77
|
+
Configure the gem with your API key:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
require 'patent_odp'
|
|
81
|
+
|
|
82
|
+
PatentODP.configure do |config|
|
|
83
|
+
config.api_key = 'your_api_key_here'
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Using Environment Variables
|
|
88
|
+
|
|
89
|
+
For security, store your API key in an environment variable:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# In your .env or environment
|
|
93
|
+
# USPTO_API_KEY=your_api_key_here
|
|
94
|
+
|
|
95
|
+
PatentODP.configure do |config|
|
|
96
|
+
config.api_key = ENV['USPTO_API_KEY']
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
### Basic Usage
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
require 'patent_odp'
|
|
106
|
+
|
|
107
|
+
# Configure the gem
|
|
108
|
+
PatentODP.configure do |config|
|
|
109
|
+
config.api_key = ENV['USPTO_API_KEY']
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Create a client
|
|
113
|
+
client = PatentODP::Client.new
|
|
114
|
+
|
|
115
|
+
# Fetch application data
|
|
116
|
+
app = client.application("16123456")
|
|
117
|
+
|
|
118
|
+
# Access application data
|
|
119
|
+
puts app.title
|
|
120
|
+
# => "LEARNING ASSISTANCE DEVICE, METHOD OF OPERATING LEARNING ASSISTANCE DEVICE..."
|
|
121
|
+
|
|
122
|
+
puts app.patent_number
|
|
123
|
+
# => "10902286"
|
|
124
|
+
|
|
125
|
+
puts app.status
|
|
126
|
+
# => "Patented Case"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Working with Applications
|
|
130
|
+
|
|
131
|
+
The `Application` object provides clean, Ruby-friendly access to patent data:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
app = client.application("16123456")
|
|
135
|
+
|
|
136
|
+
# Basic Information
|
|
137
|
+
app.id # => "16123456"
|
|
138
|
+
app.title # => "Patent title"
|
|
139
|
+
app.patent_number # => "10902286"
|
|
140
|
+
|
|
141
|
+
# Dates (automatically parsed as Date objects)
|
|
142
|
+
app.filing_date # => #<Date: 2018-09-06>
|
|
143
|
+
app.status_date # => #<Date: 2021-01-06>
|
|
144
|
+
app.early_publication_date # => #<Date: 2019-03-28>
|
|
145
|
+
|
|
146
|
+
# Status Information
|
|
147
|
+
app.status # => "Patented Case"
|
|
148
|
+
app.patented? # => true (boolean helper)
|
|
149
|
+
|
|
150
|
+
# Publication Information
|
|
151
|
+
app.early_publication_number # => "US20190095759A1"
|
|
152
|
+
|
|
153
|
+
# People (returns Arrays)
|
|
154
|
+
app.inventors # => ["Shoji KANADA"]
|
|
155
|
+
app.applicants # => ["FUJIFILM Corporation"]
|
|
156
|
+
|
|
157
|
+
# Raw data access
|
|
158
|
+
app.to_h # => Full hash from API
|
|
159
|
+
app.inspect # => Human-readable representation
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Error Handling
|
|
163
|
+
|
|
164
|
+
The gem provides specific error classes for different failure modes:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
begin
|
|
168
|
+
app = client.application("invalid")
|
|
169
|
+
rescue PatentODP::NotFoundError
|
|
170
|
+
puts "Application not found"
|
|
171
|
+
rescue PatentODP::UnauthorizedError
|
|
172
|
+
puts "Invalid API key"
|
|
173
|
+
rescue PatentODP::RateLimitError
|
|
174
|
+
puts "Rate limit exceeded - try again later"
|
|
175
|
+
rescue PatentODP::ServerError => e
|
|
176
|
+
puts "Server error: #{e.message}"
|
|
177
|
+
rescue PatentODP::APIError => e
|
|
178
|
+
puts "API error: #{e.message}"
|
|
179
|
+
rescue ArgumentError => e
|
|
180
|
+
puts "Invalid input: #{e.message}"
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Error Types
|
|
185
|
+
|
|
186
|
+
- `PatentODP::ConfigurationError` - Invalid or missing configuration
|
|
187
|
+
- `PatentODP::UnauthorizedError` - Invalid API key (401)
|
|
188
|
+
- `PatentODP::NotFoundError` - Application not found (404)
|
|
189
|
+
- `PatentODP::RateLimitError` - Rate limit exceeded (429)
|
|
190
|
+
- `PatentODP::ServerError` - Server errors (5xx)
|
|
191
|
+
- `PatentODP::APIError` - Base class for all API errors
|
|
192
|
+
- `ArgumentError` - Invalid input (e.g., malformed application number)
|
|
193
|
+
|
|
194
|
+
### Advanced Configuration
|
|
195
|
+
|
|
196
|
+
#### Custom Timeouts
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
PatentODP.configure do |config|
|
|
200
|
+
config.api_key = ENV['USPTO_API_KEY']
|
|
201
|
+
config.timeout = 60 # seconds (default: 30)
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Disabling Retries
|
|
206
|
+
|
|
207
|
+
By default, the client retries failed requests (429, 5xx errors). You can disable this:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# Globally
|
|
211
|
+
PatentODP.configure do |config|
|
|
212
|
+
config.api_key = ENV['USPTO_API_KEY']
|
|
213
|
+
config.retry_enabled = false
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Per-client
|
|
217
|
+
client = PatentODP::Client.new(retry_enabled: false)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### Per-Client Configuration
|
|
221
|
+
|
|
222
|
+
Override global settings for specific clients:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
# Use global config for most things, but custom timeout for this client
|
|
226
|
+
client = PatentODP::Client.new(timeout: 120)
|
|
227
|
+
|
|
228
|
+
# Or completely custom client
|
|
229
|
+
client = PatentODP::Client.new(
|
|
230
|
+
api_key: 'different_key',
|
|
231
|
+
timeout: 90,
|
|
232
|
+
retry_enabled: false
|
|
233
|
+
)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Rails Integration
|
|
237
|
+
|
|
238
|
+
In a Rails application, configure in an initializer:
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
# config/initializers/patent_odp.rb
|
|
242
|
+
PatentODP.configure do |config|
|
|
243
|
+
config.api_key = Rails.application.credentials.dig(:uspto, :api_key)
|
|
244
|
+
config.timeout = 30
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Then use in your models or controllers:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
class Patent < ApplicationRecord
|
|
252
|
+
def fetch_metadata
|
|
253
|
+
client = PatentODP::Client.new
|
|
254
|
+
app = client.application(self.application_number)
|
|
255
|
+
|
|
256
|
+
update!(
|
|
257
|
+
title: app.title,
|
|
258
|
+
patent_number: app.patent_number,
|
|
259
|
+
filing_date: app.filing_date,
|
|
260
|
+
status: app.status
|
|
261
|
+
)
|
|
262
|
+
rescue PatentODP::NotFoundError
|
|
263
|
+
Rails.logger.warn("Application #{application_number} not found")
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Security
|
|
269
|
+
|
|
270
|
+
This gem implements several security best practices:
|
|
271
|
+
|
|
272
|
+
- ✅ **Input Validation** - All application numbers are validated to prevent path traversal and injection attacks
|
|
273
|
+
- ✅ **HTTPS Only** - All API requests use HTTPS
|
|
274
|
+
- ✅ **No Secret Logging** - API keys are never logged or exposed in error messages
|
|
275
|
+
- ✅ **Type Safety** - All inputs are validated for correct types
|
|
276
|
+
- ✅ **Error Sanitization** - Error messages don't expose internal implementation details
|
|
277
|
+
- ✅ **Dependency Security** - Uses well-maintained libraries (Faraday)
|
|
278
|
+
|
|
279
|
+
### Best Practices
|
|
280
|
+
|
|
281
|
+
1. **Never commit API keys** - Use environment variables or Rails credentials
|
|
282
|
+
2. **Validate application numbers** - Before passing user input to the API
|
|
283
|
+
3. **Handle rate limits** - Implement backoff strategies in production
|
|
284
|
+
4. **Monitor errors** - Track API errors in your application monitoring
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Run tests
|
|
292
|
+
rake spec
|
|
293
|
+
|
|
294
|
+
# Run linter
|
|
295
|
+
rake rubocop
|
|
296
|
+
|
|
297
|
+
# Auto-fix linting issues (safe corrections only)
|
|
298
|
+
rake rubocop:autocorrect
|
|
299
|
+
|
|
300
|
+
# Auto-fix all linting issues (including unsafe corrections)
|
|
301
|
+
rake rubocop:autocorrect_all
|
|
302
|
+
|
|
303
|
+
# Run both tests and linter (default)
|
|
304
|
+
rake
|
|
305
|
+
|
|
306
|
+
# View coverage report (automatically generated)
|
|
307
|
+
open coverage/index.html
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Running Tests
|
|
311
|
+
|
|
312
|
+
The test suite includes:
|
|
313
|
+
- Unit tests for all components
|
|
314
|
+
- Integration tests with mocked API responses
|
|
315
|
+
- Security tests for input validation
|
|
316
|
+
- Error handling tests
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# All tests
|
|
320
|
+
rake spec
|
|
321
|
+
|
|
322
|
+
# Specific file
|
|
323
|
+
rspec spec/patent_odp/client_spec.rb
|
|
324
|
+
|
|
325
|
+
# Specific test
|
|
326
|
+
rspec spec/patent_odp/client_spec.rb:30
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Interactive Console
|
|
330
|
+
|
|
331
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
bin/console
|
|
335
|
+
|
|
336
|
+
# In the console:
|
|
337
|
+
PatentODP.configure { |c| c.api_key = "your_key" }
|
|
338
|
+
client = PatentODP::Client.new
|
|
339
|
+
app = client.application("16123456")
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Roadmap
|
|
343
|
+
|
|
344
|
+
See [ROADMAP.md](ROADMAP.md) for planned features and enhancements.
|
|
345
|
+
|
|
346
|
+
## API Documentation
|
|
347
|
+
|
|
348
|
+
The USPTO Open Data Portal provides access to:
|
|
349
|
+
|
|
350
|
+
- **100+ data attributes** for patent applications
|
|
351
|
+
- **Daily data refreshes** from USPTO systems
|
|
352
|
+
- **Historical data** back to 2001
|
|
353
|
+
- **File wrapper documents** including office actions, responses, and more
|
|
354
|
+
|
|
355
|
+
For complete API documentation, visit:
|
|
356
|
+
- [USPTO Open Data Portal](https://data.uspto.gov/)
|
|
357
|
+
- [API Documentation](https://data.uspto.gov/apis/patent-file-wrapper/search)
|
|
358
|
+
- [Getting Started Guide](https://data.uspto.gov/apis/getting-started)
|
|
359
|
+
|
|
360
|
+
## Contributing
|
|
361
|
+
|
|
362
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zalepa/patent_odp. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/zalepa/patent_odp/blob/main/CODE_OF_CONDUCT.md).
|
|
363
|
+
|
|
364
|
+
### Development Guidelines
|
|
365
|
+
|
|
366
|
+
1. Fork the repository
|
|
367
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
368
|
+
3. Write tests for your changes
|
|
369
|
+
4. Ensure all tests pass (`rake spec`)
|
|
370
|
+
5. Ensure code style compliance (`rubocop`)
|
|
371
|
+
6. Commit your changes (`git commit -am 'Add amazing feature'`)
|
|
372
|
+
7. Push to the branch (`git push origin feature/amazing-feature`)
|
|
373
|
+
8. Create a Pull Request
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
378
|
+
|
|
379
|
+
## Code of Conduct
|
|
380
|
+
|
|
381
|
+
Everyone interacting in the PatentODP project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/zalepa/patent_odp/blob/main/CODE_OF_CONDUCT.md).
|
|
382
|
+
|
|
383
|
+
## Acknowledgments
|
|
384
|
+
|
|
385
|
+
- [USPTO Open Data Portal](https://data.uspto.gov/) for providing the API
|
|
386
|
+
- Built with [Faraday](https://github.com/lostisland/faraday) for HTTP requests
|
|
387
|
+
|
|
388
|
+
## Support
|
|
389
|
+
|
|
390
|
+
- 📚 [Documentation](https://github.com/zalepa/patent_odp)
|
|
391
|
+
- 🐛 [Issue Tracker](https://github.com/zalepa/patent_odp/issues)
|
|
392
|
+
- 💬 [Discussions](https://github.com/zalepa/patent_odp/discussions)
|
data/ROADMAP.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# PatentODP Roadmap
|
|
2
|
+
|
|
3
|
+
This document outlines the development roadmap for PatentODP, including completed features and planned enhancements.
|
|
4
|
+
|
|
5
|
+
## Version 0.1.0 (Current) ✅
|
|
6
|
+
|
|
7
|
+
**Status**: Released
|
|
8
|
+
**Focus**: Core application metadata retrieval with robust error handling and security
|
|
9
|
+
|
|
10
|
+
### Completed Features
|
|
11
|
+
|
|
12
|
+
- ✅ Application metadata retrieval by application number
|
|
13
|
+
- ✅ Clean, idiomatic Ruby API with snake_case methods
|
|
14
|
+
- ✅ Automatic date parsing (returns Ruby `Date` objects)
|
|
15
|
+
- ✅ Comprehensive error handling with specific error classes
|
|
16
|
+
- ✅ Input validation to prevent path traversal attacks
|
|
17
|
+
- ✅ Configurable automatic retry logic
|
|
18
|
+
- ✅ Type safety throughout the codebase
|
|
19
|
+
- ✅ Full test coverage (72+ tests)
|
|
20
|
+
- ✅ Security hardening and validation
|
|
21
|
+
|
|
22
|
+
### API Coverage
|
|
23
|
+
|
|
24
|
+
- ✅ `GET /api/v1/patent/applications/{application_number}` - Application metadata
|
|
25
|
+
|
|
26
|
+
## Version 0.2.0 (Planned) 🚧
|
|
27
|
+
|
|
28
|
+
**Status**: Planning
|
|
29
|
+
**Focus**: Search capabilities and pagination
|
|
30
|
+
|
|
31
|
+
### Planned Features
|
|
32
|
+
|
|
33
|
+
- 🚧 **Search API Support**
|
|
34
|
+
- Basic keyword search across patent applications
|
|
35
|
+
- Field-specific searches (title, inventor, assignee, etc.)
|
|
36
|
+
- Date range filtering
|
|
37
|
+
- Result pagination
|
|
38
|
+
- Search result object with metadata
|
|
39
|
+
|
|
40
|
+
- 🚧 **Pagination Support**
|
|
41
|
+
- Automatic pagination handling
|
|
42
|
+
- Configurable page sizes
|
|
43
|
+
- Iterator/Enumerator pattern for large result sets
|
|
44
|
+
|
|
45
|
+
- 🚧 **Enhanced Application Methods**
|
|
46
|
+
- Additional metadata fields (CPC classifications, etc.)
|
|
47
|
+
- Better handling of complex nested data structures
|
|
48
|
+
|
|
49
|
+
### API Coverage
|
|
50
|
+
|
|
51
|
+
- 🚧 `GET /api/v1/patent/applications/search` - Search applications
|
|
52
|
+
|
|
53
|
+
## Version 0.3.0 (Planned) 📋
|
|
54
|
+
|
|
55
|
+
**Status**: Planning
|
|
56
|
+
**Focus**: Document retrieval and file wrapper data
|
|
57
|
+
|
|
58
|
+
### Planned Features
|
|
59
|
+
|
|
60
|
+
- 📋 **Document Retrieval**
|
|
61
|
+
- Fetch list of documents for an application
|
|
62
|
+
- Download individual documents
|
|
63
|
+
- Document metadata (type, date, description)
|
|
64
|
+
- Binary/PDF download support
|
|
65
|
+
|
|
66
|
+
- 📋 **Transaction History**
|
|
67
|
+
- Retrieve transaction/event history for applications
|
|
68
|
+
- Filter by event type
|
|
69
|
+
- Timeline view of application lifecycle
|
|
70
|
+
|
|
71
|
+
- 📋 **Document Types**
|
|
72
|
+
- Office actions
|
|
73
|
+
- Applicant responses
|
|
74
|
+
- Notices
|
|
75
|
+
- Certificates
|
|
76
|
+
- Correspondence
|
|
77
|
+
|
|
78
|
+
### API Coverage
|
|
79
|
+
|
|
80
|
+
- 📋 `GET /api/v1/patent/applications/{application_number}/documents` - List documents
|
|
81
|
+
- 📋 `GET /api/v1/patent/applications/{application_number}/documents/{document_id}` - Download document
|
|
82
|
+
- 📋 `GET /api/v1/patent/applications/{application_number}/transactions` - Transaction history
|
|
83
|
+
|
|
84
|
+
## Version 0.4.0 (Planned) 📄
|
|
85
|
+
|
|
86
|
+
**Status**: Planning
|
|
87
|
+
**Focus**: Assignment data and ownership information
|
|
88
|
+
|
|
89
|
+
### Planned Features
|
|
90
|
+
|
|
91
|
+
- 📄 **Assignment Search**
|
|
92
|
+
- Search assignments by assignee, assignor, or patent number
|
|
93
|
+
- Assignment history for applications
|
|
94
|
+
- Ownership chain tracking
|
|
95
|
+
|
|
96
|
+
- 📄 **Assignment Details**
|
|
97
|
+
- Recording date and execution date
|
|
98
|
+
- Assignor and assignee information
|
|
99
|
+
- Conveyance type
|
|
100
|
+
- Reel and frame numbers
|
|
101
|
+
|
|
102
|
+
### API Coverage
|
|
103
|
+
|
|
104
|
+
- 📄 `GET /api/v1/patent/applications/{application_number}/assignments` - Assignment data
|
|
105
|
+
- 📄 Assignment search endpoints
|
|
106
|
+
|
|
107
|
+
## Version 1.0.0 (Future) 🎯
|
|
108
|
+
|
|
109
|
+
**Status**: Planning
|
|
110
|
+
**Focus**: Production readiness, performance, and advanced features
|
|
111
|
+
|
|
112
|
+
### Planned Features
|
|
113
|
+
|
|
114
|
+
- 🎯 **Bulk Operations**
|
|
115
|
+
- Batch application retrieval
|
|
116
|
+
- Parallel requests with connection pooling
|
|
117
|
+
- Rate limit aware batching
|
|
118
|
+
|
|
119
|
+
- 🎯 **Caching Layer**
|
|
120
|
+
- Optional Redis/Memcached integration
|
|
121
|
+
- Configurable TTL
|
|
122
|
+
- Cache invalidation strategies
|
|
123
|
+
|
|
124
|
+
- 🎯 **Advanced Search**
|
|
125
|
+
- OpenSearch query DSL support
|
|
126
|
+
- Complex boolean queries
|
|
127
|
+
- Faceted search
|
|
128
|
+
- Aggregations
|
|
129
|
+
|
|
130
|
+
- 🎯 **Webhook Support**
|
|
131
|
+
- Subscribe to application updates (if supported by API)
|
|
132
|
+
|
|
133
|
+
- 🎯 **Performance Optimizations**
|
|
134
|
+
- Connection pooling
|
|
135
|
+
- HTTP/2 support
|
|
136
|
+
- Streaming for large responses
|
|
137
|
+
- Async/concurrent request support
|
|
138
|
+
|
|
139
|
+
- 🎯 **Developer Tools**
|
|
140
|
+
- CLI tool for quick queries
|
|
141
|
+
- Debug mode with request/response logging
|
|
142
|
+
- API usage analytics
|
|
143
|
+
|
|
144
|
+
## Future Considerations 💭
|
|
145
|
+
|
|
146
|
+
Features under consideration for future versions:
|
|
147
|
+
|
|
148
|
+
- **Trademark Support** - If USPTO ODP expands to trademarks
|
|
149
|
+
- **Export Utilities** - CSV, JSON, XML export of search results
|
|
150
|
+
- **Data Analysis Tools** - Helper methods for patent portfolio analysis
|
|
151
|
+
- **GraphQL API** - Alternative query interface
|
|
152
|
+
- **ActiveRecord Integration** - Optional Rails/ActiveRecord helpers
|
|
153
|
+
- **Monitoring & Observability** - Built-in metrics and tracing
|
|
154
|
+
|
|
155
|
+
## Contributing
|
|
156
|
+
|
|
157
|
+
We welcome contributions! If you'd like to work on any of these features:
|
|
158
|
+
|
|
159
|
+
1. Check the [issue tracker](https://github.com/zalepa/patent_odp/issues) for related issues
|
|
160
|
+
2. Comment on the issue or create a new one to discuss your approach
|
|
161
|
+
3. Fork the repository and create a feature branch
|
|
162
|
+
4. Submit a pull request with tests and documentation
|
|
163
|
+
|
|
164
|
+
## Feedback
|
|
165
|
+
|
|
166
|
+
Have ideas for features not listed here? We'd love to hear from you!
|
|
167
|
+
|
|
168
|
+
- Open an issue: https://github.com/zalepa/patent_odp/issues
|
|
169
|
+
- Start a discussion: https://github.com/zalepa/patent_odp/discussions
|
|
170
|
+
|
|
171
|
+
## Release Schedule
|
|
172
|
+
|
|
173
|
+
We aim for:
|
|
174
|
+
- **Minor versions** (0.x.0) every 2-3 months with new features
|
|
175
|
+
- **Patch versions** (0.x.y) as needed for bug fixes and security updates
|
|
176
|
+
- **Major version** (1.0.0) when API is stable and production-ready
|
|
177
|
+
|
|
178
|
+
## Version History
|
|
179
|
+
|
|
180
|
+
### 0.1.0 - Initial Release
|
|
181
|
+
- Application metadata retrieval
|
|
182
|
+
- Error handling and validation
|
|
183
|
+
- Security hardening
|
|
184
|
+
- Test coverage
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
Last Updated: 2024-10-16
|
|
189
|
+
Current Version: 0.1.0
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
require "rubocop/rake_task"
|
|
9
|
+
|
|
10
|
+
RuboCop::RakeTask.new
|
|
11
|
+
|
|
12
|
+
# Auto-correct RuboCop offenses
|
|
13
|
+
RuboCop::RakeTask.new("rubocop:autocorrect") do |task|
|
|
14
|
+
task.options = ["--autocorrect"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Auto-correct all RuboCop offenses (including unsafe)
|
|
18
|
+
RuboCop::RakeTask.new("rubocop:autocorrect_all") do |task|
|
|
19
|
+
task.options = ["--autocorrect-all"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
task default: %i[spec rubocop]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module PatentODP
|
|
6
|
+
# Represents a patent application with metadata from the USPTO API
|
|
7
|
+
class Application
|
|
8
|
+
# @return [Hash] Raw application data from API
|
|
9
|
+
attr_reader :data
|
|
10
|
+
|
|
11
|
+
# @param data [Hash] Raw application data from API response
|
|
12
|
+
# @param application_number [String, nil] Application number from request
|
|
13
|
+
def initialize(data, application_number = nil)
|
|
14
|
+
@data = data
|
|
15
|
+
@metadata = data["applicationMetaData"] || {}
|
|
16
|
+
@application_number = application_number
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [String, nil] Application ID
|
|
20
|
+
def id
|
|
21
|
+
@application_number
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [String, nil] Patent number if granted
|
|
25
|
+
def patent_number
|
|
26
|
+
@metadata["patentNumber"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [String, nil] Patent/Application title
|
|
30
|
+
def title
|
|
31
|
+
@metadata["inventionTitle"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Date, nil] Filing date
|
|
35
|
+
def filing_date
|
|
36
|
+
parse_date(@metadata["filingDate"])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [String, nil] Application status
|
|
40
|
+
def status
|
|
41
|
+
@metadata["applicationStatusDescriptionText"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Date, nil] Status date
|
|
45
|
+
def status_date
|
|
46
|
+
parse_date(@metadata["applicationStatusDate"])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [String, nil] Early publication number
|
|
50
|
+
def early_publication_number
|
|
51
|
+
@metadata["earliestPublicationNumber"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Date, nil] Early publication date
|
|
55
|
+
def early_publication_date
|
|
56
|
+
parse_date(@metadata["earliestPublicationDate"])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Array<String>] List of applicant names
|
|
60
|
+
def applicants
|
|
61
|
+
extract_names(@metadata["applicantBag"], "applicantNameText")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Array<String>] List of inventor names
|
|
65
|
+
def inventors
|
|
66
|
+
extract_names(@metadata["inventorBag"], "inventorNameText")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Boolean] Whether the application has been granted
|
|
70
|
+
def patented?
|
|
71
|
+
status&.include?("Patented") || false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Hash] Raw data hash
|
|
75
|
+
def to_h
|
|
76
|
+
@data
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [String] Human-readable representation
|
|
80
|
+
def inspect
|
|
81
|
+
"#<PatentODP::Application id=#{id.inspect} title=#{title.inspect}>"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# Parse date string to Date object
|
|
87
|
+
# @param date_string [String, nil]
|
|
88
|
+
# @return [Date, nil]
|
|
89
|
+
def parse_date(date_string)
|
|
90
|
+
return nil if date_string.nil? || date_string.empty?
|
|
91
|
+
|
|
92
|
+
Date.parse(date_string)
|
|
93
|
+
rescue ArgumentError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Extract names from nested API structure
|
|
98
|
+
# @param items [Array, nil] Array of items with name fields
|
|
99
|
+
# @param name_field [String] Name of the field containing names
|
|
100
|
+
# @return [Array<String>]
|
|
101
|
+
def extract_names(items, name_field)
|
|
102
|
+
return [] if items.nil?
|
|
103
|
+
|
|
104
|
+
Array(items).map { |item| item[name_field] if item.is_a?(Hash) }.compact
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/retry"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module PatentODP
|
|
8
|
+
# HTTP client for interacting with the USPTO Open Data Portal API
|
|
9
|
+
class Client
|
|
10
|
+
BASE_URL = "https://api.uspto.gov/api/v1/patent/applications"
|
|
11
|
+
|
|
12
|
+
# @param api_key [String, nil] API key for authentication. If nil, uses global config.
|
|
13
|
+
# @param timeout [Integer, nil] Request timeout in seconds. If nil, uses global config.
|
|
14
|
+
# @param retry_enabled [Boolean, nil] Enable retry logic. If nil, uses global config.
|
|
15
|
+
def initialize(api_key: nil, timeout: nil, retry_enabled: nil)
|
|
16
|
+
@api_key = api_key || PatentODP.configuration.api_key!
|
|
17
|
+
@timeout = timeout || PatentODP.configuration.timeout
|
|
18
|
+
@retry_enabled = retry_enabled.nil? ? PatentODP.configuration.retry_enabled : retry_enabled
|
|
19
|
+
@connection = build_connection
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Fetch application metadata by application number
|
|
23
|
+
# @param application_number [String] The patent application number
|
|
24
|
+
# @return [Application] Application object with metadata
|
|
25
|
+
# @raise [NotFoundError] if application doesn't exist
|
|
26
|
+
# @raise [UnauthorizedError] if API key is invalid
|
|
27
|
+
# @raise [RateLimitError] if rate limit is exceeded
|
|
28
|
+
# @raise [ServerError] for server errors
|
|
29
|
+
# @raise [ArgumentError] if application_number is invalid
|
|
30
|
+
def application(application_number)
|
|
31
|
+
validate_application_number!(application_number)
|
|
32
|
+
|
|
33
|
+
response = @connection.get(application_number) do |req|
|
|
34
|
+
req.headers["X-API-KEY"] = @api_key
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
handle_response(response, application_number)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Validate application number to prevent path traversal and injection attacks
|
|
43
|
+
# @param application_number [String] The application number to validate
|
|
44
|
+
# @raise [ArgumentError] if application_number is invalid
|
|
45
|
+
def validate_application_number!(application_number)
|
|
46
|
+
raise ArgumentError, "Application number cannot be nil" if application_number.nil?
|
|
47
|
+
raise ArgumentError, "Application number must be a string" unless application_number.is_a?(String)
|
|
48
|
+
raise ArgumentError, "Application number cannot be empty" if application_number.strip.empty?
|
|
49
|
+
|
|
50
|
+
# Application numbers should only contain alphanumeric characters and basic punctuation
|
|
51
|
+
# This prevents path traversal attacks like "../../../etc/passwd"
|
|
52
|
+
return if application_number.match?(/\A[\w-]+\z/)
|
|
53
|
+
|
|
54
|
+
raise ArgumentError,
|
|
55
|
+
"Application number contains invalid characters. Only alphanumeric, underscore, and hyphen allowed."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_connection
|
|
59
|
+
Faraday.new(url: BASE_URL) do |conn|
|
|
60
|
+
if @retry_enabled
|
|
61
|
+
conn.request :retry, {
|
|
62
|
+
max: 3,
|
|
63
|
+
interval: 0.5,
|
|
64
|
+
backoff_factor: 2,
|
|
65
|
+
retry_statuses: [429, 500, 502, 503, 504]
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
conn.adapter Faraday.default_adapter
|
|
69
|
+
conn.options.timeout = @timeout
|
|
70
|
+
conn.options.open_timeout = 10
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def handle_response(response, application_number)
|
|
75
|
+
case response.status
|
|
76
|
+
when 200
|
|
77
|
+
parse_application_response(response, application_number)
|
|
78
|
+
when 401
|
|
79
|
+
raise UnauthorizedError, "Invalid API key"
|
|
80
|
+
when 404
|
|
81
|
+
raise NotFoundError, "Application not found"
|
|
82
|
+
when 429
|
|
83
|
+
raise RateLimitError, "API rate limit exceeded"
|
|
84
|
+
when 500..599
|
|
85
|
+
raise ServerError, "Server error: #{response.status}"
|
|
86
|
+
else
|
|
87
|
+
raise APIError, "Unexpected response: #{response.status}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def parse_application_response(response, application_number)
|
|
92
|
+
data = JSON.parse(response.body)
|
|
93
|
+
|
|
94
|
+
# Navigate the nested structure to get application data
|
|
95
|
+
wrapper_bag = data["patentFileWrapperDataBag"]
|
|
96
|
+
return Application.new({}, application_number) if wrapper_bag.nil? || wrapper_bag.empty?
|
|
97
|
+
|
|
98
|
+
# Get the first (and should be only) item in the bag
|
|
99
|
+
wrapper_data = wrapper_bag.first
|
|
100
|
+
return Application.new({}, application_number) unless wrapper_data
|
|
101
|
+
|
|
102
|
+
# Extract the application metadata
|
|
103
|
+
app_metadata = wrapper_data["applicationMetaData"]
|
|
104
|
+
return Application.new({}, application_number) unless app_metadata
|
|
105
|
+
|
|
106
|
+
# Pass the full wrapper data so Application can access events, etc if needed
|
|
107
|
+
Application.new(wrapper_data, application_number)
|
|
108
|
+
rescue JSON::ParserError => e
|
|
109
|
+
raise APIError, "Failed to parse API response: #{e.message}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PatentODP
|
|
4
|
+
# Configuration class for PatentODP gem
|
|
5
|
+
# Handles API key, base URL, and other configurable options
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :api_key, :retry_enabled
|
|
8
|
+
attr_reader :base_url, :timeout
|
|
9
|
+
|
|
10
|
+
def initialize(api_key: nil, timeout: nil, retry_enabled: nil)
|
|
11
|
+
@api_key = api_key
|
|
12
|
+
@base_url = "https://api.uspto.gov/api/v1/patent/applications"
|
|
13
|
+
self.timeout = timeout || 30
|
|
14
|
+
@retry_enabled = retry_enabled.nil? || retry_enabled
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Set timeout with validation
|
|
18
|
+
# @param value [Integer] Timeout in seconds
|
|
19
|
+
# @raise [ArgumentError] if timeout is invalid
|
|
20
|
+
def timeout=(value)
|
|
21
|
+
raise ArgumentError, "Timeout must be a positive number" if value && (!value.is_a?(Numeric) || value <= 0)
|
|
22
|
+
|
|
23
|
+
@timeout = value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the API key if set, raises an error otherwise
|
|
27
|
+
# @return [String] the API key
|
|
28
|
+
# @raise [ConfigurationError] if API key is not set or is blank
|
|
29
|
+
def api_key!
|
|
30
|
+
if @api_key.nil? || !@api_key.is_a?(String) || @api_key.strip.empty?
|
|
31
|
+
raise ConfigurationError,
|
|
32
|
+
"API key is required. Set it with PatentODP.configure { |c| c.api_key = 'your_key' }"
|
|
33
|
+
end
|
|
34
|
+
@api_key
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PatentODP
|
|
4
|
+
# Base error class for all PatentODP errors
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when configuration is invalid or missing
|
|
8
|
+
class ConfigurationError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when API request fails
|
|
11
|
+
class APIError < Error; end
|
|
12
|
+
|
|
13
|
+
# Raised when API returns 4xx error
|
|
14
|
+
class ClientError < APIError; end
|
|
15
|
+
|
|
16
|
+
# Raised when API returns 5xx error
|
|
17
|
+
class ServerError < APIError; end
|
|
18
|
+
|
|
19
|
+
# Raised when API returns 401 Unauthorized
|
|
20
|
+
class UnauthorizedError < ClientError; end
|
|
21
|
+
|
|
22
|
+
# Raised when API returns 404 Not Found
|
|
23
|
+
class NotFoundError < ClientError; end
|
|
24
|
+
|
|
25
|
+
# Raised when API returns 429 Too Many Requests
|
|
26
|
+
class RateLimitError < ClientError; end
|
|
27
|
+
end
|
data/lib/patent_odp.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "patent_odp/version"
|
|
4
|
+
require_relative "patent_odp/errors"
|
|
5
|
+
require_relative "patent_odp/configuration"
|
|
6
|
+
require_relative "patent_odp/application"
|
|
7
|
+
require_relative "patent_odp/client"
|
|
8
|
+
|
|
9
|
+
module PatentODP
|
|
10
|
+
class << self
|
|
11
|
+
# Returns the global configuration object
|
|
12
|
+
# @return [Configuration] the configuration instance
|
|
13
|
+
def configuration
|
|
14
|
+
@configuration ||= Configuration.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Yields the configuration object for setup
|
|
18
|
+
# @yield [Configuration] the configuration instance
|
|
19
|
+
# @example
|
|
20
|
+
# PatentODP.configure do |config|
|
|
21
|
+
# config.api_key = "your_api_key"
|
|
22
|
+
# end
|
|
23
|
+
def configure
|
|
24
|
+
yield(configuration)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Resets the configuration to a fresh instance
|
|
28
|
+
# Primarily used for testing
|
|
29
|
+
# @return [Configuration] the new configuration instance
|
|
30
|
+
def reset_configuration!
|
|
31
|
+
@configuration = Configuration.new
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/sig/patent_odp.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: patent_odp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- George Zalepa
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-retry
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
description: A Ruby gem for interacting with the USPTO's Open Data Portal API, providing
|
|
41
|
+
access to patent file wrapper data.
|
|
42
|
+
email:
|
|
43
|
+
- george.zalepa@gmail.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- CHANGELOG.md
|
|
49
|
+
- CODE_OF_CONDUCT.md
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- README.md
|
|
52
|
+
- ROADMAP.md
|
|
53
|
+
- Rakefile
|
|
54
|
+
- lib/patent_odp.rb
|
|
55
|
+
- lib/patent_odp/application.rb
|
|
56
|
+
- lib/patent_odp/client.rb
|
|
57
|
+
- lib/patent_odp/configuration.rb
|
|
58
|
+
- lib/patent_odp/errors.rb
|
|
59
|
+
- lib/patent_odp/version.rb
|
|
60
|
+
- sig/patent_odp.rbs
|
|
61
|
+
homepage: https://github.com/zalepa/patent_odp
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
homepage_uri: https://github.com/zalepa/patent_odp
|
|
66
|
+
source_code_uri: https://github.com/zalepa/patent_odp
|
|
67
|
+
changelog_uri: https://github.com/zalepa/patent_odp/blob/main/CHANGELOG.md
|
|
68
|
+
rubygems_mfa_required: 'true'
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.2.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 3.6.9
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: Ruby wrapper for the USPTO Open Data Portal (ODP) API
|
|
86
|
+
test_files: []
|