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 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
@@ -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
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]()
6
+ [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen)]()
7
+ [![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.2.0-blue)]()
8
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PatentODP
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module PatentOdp
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []