A comprehensive Ruby client for the Rospatent patent search API with advanced features including intelligent caching, input validation, structured logging, and robust error handling.
🇷🇺 Документация на русском языке доступна ниже
- 🔍 Complete API Coverage - Search, retrieve patents, media files, and datasets
- 🛡️ Robust Error Handling - Comprehensive error types with detailed context
- ⚡ Intelligent Caching - In-memory caching with TTL and LRU eviction
- ✅ Input Validation - Automatic parameter validation with configurable limits and helpful error messages
- 📊 Structured Logging - JSON/text logging with request/response tracking
- 🚀 Batch Operations - Process multiple patents concurrently
- ⚙️ Environment-Aware - Different configurations for dev/staging/production
- 🧪 Comprehensive Testing - Extensive unit and integration test coverage with robust error handling validation
- 📚 Excellent Documentation - Detailed examples and API documentation
Add this line to your application's Gemfile:
gem 'rospatent'And then execute:
$ bundle installOr install it yourself as:
$ gem install rospatent# Minimal configuration
Rospatent.configure do |config|
config.token = "your_jwt_token"
end
# Create a client and search
client = Rospatent.client
results = client.search(q: "ракета", limit: 10)
puts "Found #{results.total} results"
results.hits.each do |hit|
puts "Patent: #{hit['id']} - #{hit.dig('biblio', 'ru', 'title')}"
endRospatent.configure do |config|
# Required
config.token = "your_jwt_token"
# API settings
config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
config.timeout = 30
config.retry_count = 3
# Environment (development, staging, production)
config.environment = "production"
endRospatent.configure do |config|
config.token = "your_jwt_token"
# Caching (enabled by default)
config.cache_enabled = true
config.cache_ttl = 300 # 5 minutes
config.cache_max_size = 1000 # Maximum cached items
# Logging
config.log_level = :info # :debug, :info, :warn, :error
config.log_requests = true # Log API requests
config.log_responses = true # Log API responses
# Connection settings
config.connection_pool_size = 5
config.connection_keep_alive = true
# Token management
config.token_expires_at = Time.now + 3600
config.token_refresh_callback = -> { refresh_token! }
endCustomize validation thresholds for different parameters to suit your specific needs:
Rospatent.configure do |config|
config.token = "your_jwt_token"
# Customize validation limits
config.validation_limits = {
# Query parameters
query_max_length: 5000, # Default: 2000
natural_query_max_length: 3000, # Default: 2000
# Pagination limits
limit_max_value: 200, # Default: 100
offset_max_value: 50_000, # Default: 10,000
# Array and string limits
array_max_size: 20, # Default: 10
string_max_length: 2000, # Default: 1000
# Highlighting limits
pre_tag_max_length: 100, # Default: 50
post_tag_max_length: 100, # Default: 50
pre_tag_max_size: 20, # Default: 10
post_tag_max_size: 20, # Default: 10
# Classification search limits
classification_query_max_length: 2000, # Default: 1000
classification_code_max_length: 100, # Default: 50
# Similar search limits
similar_text_min_words: 30, # Default: 50
similar_text_max_length: 20_000, # Default: 10,000
similar_count_max_value: 2000, # Default: 1000
# Batch operation limits
batch_size_max_value: 100, # Default: 50
batch_ids_max_size: 2000 # Default: 1000
}
endBenefits of configurable validation limits:
- Flexibility: Adjust limits based on your application's requirements
- Performance: Fine-tune validation for optimal performance
- API Evolution: Easily adapt to changes in Rospatent API specifications
- Environment-specific: Different limits for development, staging, and production
The gem automatically adjusts settings based on environment with sensible defaults:
# Optimized for development
Rospatent.configure do |config|
config.environment = "development"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :debug
config.log_requests = true
config.log_responses = true
config.cache_ttl = 60 # Short cache for development
config.timeout = 10 # Fast timeouts for quick feedback
end# Optimized for staging
Rospatent.configure do |config|
config.environment = "staging"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :info
config.cache_ttl = 300 # Longer cache for performance
config.timeout = 45 # Longer timeouts for reliability
config.retry_count = 3 # More retries for resilience
end# Optimized for production
Rospatent.configure do |config|
config.environment = "production"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :warn
config.cache_ttl = 600 # Longer cache for performance
config.timeout = 60 # Longer timeouts for reliability
config.retry_count = 5 # More retries for resilience
end- Rails credentials:
Rails.application.credentials.rospatent_token - Primary environment variable:
ROSPATENT_TOKEN - Legacy environment variable:
ROSPATENT_API_TOKEN
# Recommended approach
export ROSPATENT_TOKEN="your_jwt_token"
# Legacy support (still works)
export ROSPATENT_API_TOKEN="your_jwt_token"# Environment variable takes precedence over Rails defaults
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
endROSPATENT_LOG_LEVEL=debug in production will override Rails-specific logic and cause DEBUG logs to appear in production!
# Core configuration
ROSPATENT_TOKEN="your_jwt_token" # API authentication token
ROSPATENT_ENV="production" # Override Rails.env if needed
ROSPATENT_API_URL="custom_url" # Override default API URL
# Logging configuration
ROSPATENT_LOG_LEVEL="warn" # debug, info, warn, error
ROSPATENT_LOG_REQUESTS="false" # Log API requests
ROSPATENT_LOG_RESPONSES="false" # Log API responses
# Cache configuration
ROSPATENT_CACHE_ENABLED="true" # Enable/disable caching
ROSPATENT_CACHE_TTL="300" # Cache TTL in seconds
ROSPATENT_CACHE_MAX_SIZE="1000" # Maximum cache items
# Connection configuration
ROSPATENT_TIMEOUT="30" # Request timeout in seconds
ROSPATENT_RETRY_COUNT="3" # Number of retries
ROSPATENT_POOL_SIZE="5" # Connection pool size
ROSPATENT_KEEP_ALIVE="true" # Keep-alive connections
# Environment-specific overrides
ROSPATENT_DEV_API_URL="dev_url" # Development API URL
ROSPATENT_STAGING_API_URL="staging_url" # Staging API URL-
Use Rails credentials for tokens:
rails credentials:edit # Add: rospatent_token: your_jwt_token -
Set environment-specific variables:
# config/environments/production.rb ENV["ROSPATENT_LOG_LEVEL"] ||= "warn" ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
-
Avoid setting DEBUG level in production:
# ❌ DON'T DO THIS in production export ROSPATENT_LOG_LEVEL=debug # ✅ DO THIS instead export ROSPATENT_LOG_LEVEL=warn
# Validate current configuration
errors = Rospatent.validate_configuration
if errors.any?
puts "Configuration errors:"
errors.each { |error| puts " - #{error}" }
else
puts "Configuration is valid ✓"
endclient = Rospatent.client
# Simple text search
results = client.search(q: "ракета")
# Natural language search
results = client.search(qn: "rocket engine design")
# Advanced search with all options
results = client.search(
q: "ракета AND двигатель",
limit: 50,
offset: 100,
datasets: ["ru_since_1994"],
filter: {
"classification.ipc_group": { "values": ["F02K9"] },
"application.filing_date": { "range": { "gte": "20200101" } }
},
sort: "publication_date:desc", # same as 'sort: :pub_date'; see Search#validate_sort_parameter for other sort options
group_by: "family:dwpi", # Patent family grouping: "family:docdb" or "family:dwpi"
include_facets: true, # Boolean: true/false (automatically converted to 1/0 for API)
pre_tag: "<mark>", # Both pre_tag and post_tag must be provided together
post_tag: "</mark>", # Can be strings or arrays for multi-color highlighting
highlight: { # Advanced highlight configuration (independent of pre_tag/post_tag)
"profiles" => [
{ "q" => "космическая", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_"
]
}
)
# Simple highlighting with tags (both pre_tag and post_tag required)
results = client.search(
q: "ракета",
pre_tag: "<mark>",
post_tag: "</mark>"
)
# Multi-color highlighting with arrays
results = client.search(
q: "космическая ракета",
pre_tag: ["<b>", "<i>"], # Round-robin highlighting
post_tag: ["</b>", "</i>"] # with different tags
)
# Advanced highlighting with profiles (independent of pre_tag/post_tag)
results = client.search(
q: "ракета",
highlight: {
"profiles" => [
{ "q" => "космическая", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_" # References main search query highlighting
]
}
)
# Patent family grouping (groups patents from the same invention)
results = client.search(
q: "rocket",
group_by: "family:docdb", # DOCDB simple patent families
datasets: ["dwpi"],
limit: 10
)
results = client.search(
q: "rocket",
group_by: "family:dwpi", # DWPI simple patent families
datasets: ["dwpi"],
limit: 10
)
# Process results
puts "Found #{results.total} total results (#{results.available} available)"
puts "Showing #{results.count} results"
results.hits.each do |hit|
puts "ID: #{hit['id']}"
puts "Title: #{hit.dig('biblio', 'ru', 'title')}"
puts "Date: #{hit.dig('common', 'publication_date')}"
puts "IPC: #{hit.dig('common', 'classification', 'ipc')&.map {|c| c['fullname']}&.join('; ')}"
puts "---"
endThe filter parameter supports complex filtering with automatic validation and format conversion:
# Classification filters
results = client.search(
q: "artificial intelligence",
filter: {
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"classification.cpc_group": { "values": ["G06N3/", "G06N20/"] }
}
)
# Author and patent holder filters
results = client.search(
q: "invention",
filter: {
"authors": { "values": ["Иванов И.И.", "Петров П.П."] },
"patent_holders": { "values": ["ООО Компания"] },
"country": { "values": ["RU", "US"] },
"kind": { "values": ["A1", "U1"] }
}
)
# Document ID filters
results = client.search(
q: "device",
filter: {
"ids": { "values": ["RU134694U1_20131120", "RU2358138C1_20090610"] }
}
)# Automatic date format conversion
results = client.search(
q: "innovation",
filter: {
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-06-15" } }
}
)
# Direct API format (YYYYMMDD)
results = client.search(
q: "technology",
filter: {
"date_published": { "range": { "gte": "20200101", "lt": "20240101" } }
}
)
# Using Date objects (automatically converted)
results = client.search(
q: "patent",
filter: {
"application.filing_date": {
"range": {
"gte": Date.new(2020, 1, 1),
"lte": Date.new(2023, 12, 31)
}
}
}
)Supported date operators: gt, gte, lt, lte
Date format conversion:
"2020-01-01"→"20200101"Date.new(2020, 1, 1)→"20200101""20200101"→"20200101"(no change)
# Comprehensive filter example
results = client.search(
q: "машинное обучение",
filter: {
# List filters
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"country": { "values": ["RU", "US", "CN"] },
"kind": { "values": ["A1", "A2"] },
"authors": { "values": ["Иванов И.И."] },
# Date range filters
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-01-01" } }
},
limit: 50
)Supported Filter Fields:
List filters (require {"values": [...]} format):
authors- Patent authorspatent_holders- Patent holders/assigneescountry- Country codeskind- Document typesids- Specific document IDsclassification.ipc*- IPC classification codesclassification.cpc*- CPC classification codes
Date filters (require {"range": {"operator": "YYYYMMDD"}} format):
date_published- Publication dateapplication.filing_date- Application filing date
Filter Validation:
- ✅ Automatic field name validation
- ✅ Structure validation (list vs range format)
- ✅ Date format conversion and validation
- ✅ Operator validation for ranges
- ✅ Helpful error messages for invalid filters
# These will raise ValidationError with specific messages:
client.search(
q: "test",
filter: { "invalid_field": { "values": ["test"] } }
)
# Error: "Invalid filter field: invalid_field"
client.search(
q: "test",
filter: { "authors": ["direct", "array"] } # Missing {"values": [...]} wrapper
)
# Error: "Filter 'authors' requires format: {\"values\": [...]}"
client.search(
q: "test",
filter: { "date_published": { "range": { "invalid_op": "20200101" } } }
)
# Error: "Invalid range operator: invalid_op. Supported: gt, gte, lt, lte"# Get patent by document ID
patent_doc = client.patent("RU134694U1_20131120")
# Get patent by components
patent_doc = client.patent_by_components(
"RU", # country_code
"134694", # number
"U1", # doc_type
Date.new(2013, 11, 20) # date (String or Date object)
)
# Access patent data
title = patent_doc.dig('biblio', 'ru', 'title')
abstract = patent_doc.dig('abstract', 'ru')
inventors = patent_doc.dig('biblio', 'ru', 'inventor')Extract clean text or structured content from patents:
# Parse abstract
abstract_text = client.parse_abstract(patent_doc)
abstract_html = client.parse_abstract(patent_doc, format: :html)
abstract_en = client.parse_abstract(patent_doc, language: "en")
# Parse description
description_text = client.parse_description(patent_doc)
description_html = client.parse_description(patent_doc, format: :html)
# Get structured sections
sections = client.parse_description(patent_doc, format: :sections)
sections.each do |section|
puts "Section #{section[:number]}: #{section[:content]}"
end# Find similar patents by ID
similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
# Find similar patents by text description
similar = client.similar_patents_by_text(
"Ракетный двигатель с улучшенной тягой ...", # 50 words in request minimum
count: 25
)
# Process similar patents
similar["data"]&.each do |patent|
puts "Similar: #{patent['id']} (score: #{patent['similarity']} (#{patent['similarity_norm']}))"
endSearch within patent classification systems (IPC and CPC) and get detailed information about classification codes:
# Search for classification codes related to rockets in IPC
ipc_results = client.classification_search("ipc", query: "ракета", lang: "ru")
puts "Found #{ipc_results.size} IPC codes"
ipc_results&.each do |result|
puts "#{result['Code']}: #{result['Description']}"
end
# Search for rocket-related codes in CPC using English
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
# Get detailed information about a specific classification code
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
puts "Code: #{code}"
puts "Description: #{info&.first['Description']}"
puts "Hierarchy: #{info&.map{|level| level['Code']}&.join(' → ')}"
# Get CPC code information in English
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")Supported Classification Systems:
"ipc"- International Patent Classification (МПК)"cpc"- Cooperative Patent Classification (СПК)
Supported Languages:
"ru"- Russian"en"- English
datasets = client.datasets_tree
datasets.each do |category|
puts "Category: #{category['name_en']}"
category.children.each do |dataset|
puts " #{dataset['id']}: #{dataset['name_en']}"
end
end# ✅ Recommended: Download patent PDF with auto-generated filename
# Automatically uses the formatted publication number (e.g., "0000134694.pdf")
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694" # pub_number (filename auto-generated)
)
client.save_binary_file(pdf_data, "patent.pdf")
# ✅ Alternative: Download with explicit filename
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694", # pub_number
"document.pdf" # explicit filename
)
client.save_binary_file(pdf_data, "patent_explicit.pdf")
# ✅ Simplified method using patent ID (auto-generated filename)
pdf_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National" # filename auto-generated as "0000134694.pdf"
)
client.save_binary_file(pdf_data, "patent_by_id.pdf")
# ✅ Or with explicit filename for specific files
image_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National",
"image.png" # explicit filename for non-PDF files
)
client.save_binary_file(image_data, "patent_image.png")
# ✅ Safe file saving options:
File.binwrite("patent.pdf", pdf_data) # Manual binary write
# ❌ Avoid: File.write can cause encoding errors with binary data
# File.write("patent.pdf", pdf_data) # This may fail!Process multiple patents efficiently with concurrent requests:
document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
# Process patents in batches
client.batch_patents(document_ids, batch_size: 5) do |patent_doc|
if patent_doc[:error]
puts "Error for #{patent_doc[:document_id]}: #{patent_doc[:error]}"
else
puts "Retrieved patent: #{patent_doc['id']}"
# Process patent document
end
end
# Or collect all results
patents = []
client.batch_patents(document_ids) { |doc| patents << doc }Automatic intelligent caching improves performance:
# Caching is automatic and transparent
patent1 = client.patent("RU134694U1_20131120") # API call
patent2 = client.patent("RU134694U1_20131120") # Cached result
# Check cache statistics
stats = client.statistics
puts "Cache hit rate: #{stats[:cache_stats][:hit_rate_percent]}%"
puts "Total requests: #{stats[:requests_made]}"
puts "Average response time: #{stats[:average_request_time]}s"
# Use shared cache across clients
shared_cache = Rospatent.shared_cache
client1 = Rospatent.client(cache: shared_cache)
client2 = Rospatent.client(cache: shared_cache)
# Manual cache management
shared_cache.clear # Clear all cached data
expired_count = shared_cache.cleanup_expired # Remove expired entries
cache_stats = shared_cache.statistics # Get detailed cache statisticsConfigure detailed logging for monitoring and debugging:
# Create custom logger
logger = Rospatent::Logger.new(
output: Rails.logger, # Or any IO object
level: :info,
formatter: :json # :json or :text
)
client = Rospatent.client(logger: logger)
# Logs include:
# - API requests/responses with timing
# - Cache operations (hits/misses)
# - Error details with context
# - Performance metrics
# Access shared logger
shared_logger = Rospatent.shared_logger(level: :debug)Notes:
- When using
Rails.logger, formatting is controlled by Rails configuration,formatterparameter ignored - When using IO objects,
formatterparameter controls output format
Comprehensive error handling with specific error types and improved error message extraction:
begin
patent = client.patent("INVALID_ID")
rescue Rospatent::Errors::ValidationError => e
puts "Invalid input: #{e.message}"
puts "Field errors: #{e.errors}" if e.errors.any?
rescue Rospatent::Errors::NotFoundError => e
puts "Patent not found: #{e.message}"
rescue Rospatent::Errors::RateLimitError => e
puts "Rate limited. Retry after: #{e.retry_after} seconds"
rescue Rospatent::Errors::AuthenticationError => e
puts "Authentication failed: #{e.message}"
rescue Rospatent::Errors::ApiError => e
puts "API error (#{e.status_code}): #{e.message}"
puts "Request ID: #{e.request_id}" if e.request_id
retry if e.retryable?
rescue Rospatent::Errors::ConnectionError => e
puts "Connection error: #{e.message}"
puts "Original error: #{e.original_error}"
end
# Enhanced error message extraction
# The client automatically extracts error messages from various API response formats:
# - {"result": "Error message"} (Rospatent API format)
# - {"error": "Error message"} (Standard format)
# - {"message": "Error message"} (Alternative format)
# - {"details": "Validation details"} (Validation errors)All inputs are automatically validated with helpful error messages:
# These will raise ValidationError with specific messages:
client.search(limit: 0) # "Limit must be at least 1"
client.patent("") # "Document_id cannot be empty"
client.similar_patents_by_text("", count: -1) # Multiple validation errors
# Validation includes:
# - Parameter types and formats
# - Patent ID format validation
# - Date format validation
# - Enum value validation
# - Required field validationTrack performance and usage statistics:
# Client-specific statistics
stats = client.statistics
puts "Requests made: #{stats[:requests_made]}"
puts "Total duration: #{stats[:total_duration_seconds]}s"
puts "Average request time: #{stats[:average_request_time]}s"
puts "Cache hit rate: #{stats[:cache_stats][:hit_rate_percent]}%"
# Global statistics
global_stats = Rospatent.statistics
puts "Environment: #{global_stats[:configuration][:environment]}"
puts "Cache enabled: #{global_stats[:configuration][:cache_enabled]}"
puts "API URL: #{global_stats[:configuration][:api_url]}"$ rails generate rospatent:installThis creates config/initializers/rospatent.rb:
Rospatent.configure do |config|
# Token priority: Rails credentials > ROSPATENT_TOKEN > ROSPATENT_API_TOKEN
config.token = Rails.application.credentials.rospatent_token ||
ENV["ROSPATENT_TOKEN"] ||
ENV["ROSPATENT_API_TOKEN"]
# Environment configuration respects ROSPATENT_ENV
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
# CRITICAL: Environment variables take priority over Rails defaults
# This prevents DEBUG logs appearing in production if ROSPATENT_LOG_LEVEL=debug is set
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
end
config.cache_enabled = Rails.env.production?
end# In config/initializers/rospatent.rb
Rospatent.configure do |config|
config.token = Rails.application.credentials.rospatent_token
end
# Create client with Rails logger
logger = Rospatent::Logger.new(
output: Rails.logger,
level: Rails.env.production? ? :warn : :debug,
formatter: :text
)
# Use in controllers/services
class PatentService
def initialize
@client = Rospatent.client(logger: logger)
end
def search_patents(query)
@client.search(q: query, limit: 20)
rescue Rospatent::Errors::ApiError => e
Rails.logger.error "Patent search failed: #{e.message}"
raise
end
end# Run all tests
$ bundle exec rake test
# Run specific test file
$ bundle exec ruby -Itest test/unit/client_test.rb
# Run integration tests (requires API token)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=your_token bundle exec rake test_integration
# Run with coverage
$ bundle exec rake coverageFor testing, reset and configure in each test's setup method:
# test/test_helper.rb - Base setup for unit tests
module Minitest
class Test
def setup
Rospatent.reset # Clean state between tests
Rospatent.configure do |config|
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
config.environment = "development"
config.cache_enabled = false # Disable cache for predictable tests
config.log_level = :error # Reduce test noise
end
end
end
end
# For integration tests - stable config, no reset needed
class IntegrationTest < Minitest::Test
def setup
skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
@token = ENV.fetch("ROSPATENT_TEST_TOKEN", nil)
skip "ROSPATENT_TEST_TOKEN not set" unless @token
# No reset needed - integration tests use consistent configuration
Rospatent.configure do |config|
config.token = @token
config.environment = "development"
config.cache_enabled = true
config.log_level = :debug
end
end
end# test/test_helper.rb
module Minitest
class Test
def assert_valid_patent_id(patent_id, message = nil)
message ||= "Expected #{patent_id} to be a valid patent ID (format: XX12345Y1_YYYYMMDD)"
assert patent_id.match?(/^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/), message
end
end
end
# Usage in tests
def test_patent_id_validation
assert_valid_patent_id("RU134694U1_20131120")
assert_valid_patent_id("RU134694A_20131120")
endThe library uses Faraday as the HTTP client with redirect support for all endpoints:
- All endpoints (
/search,/docs/{id},/similar_search,/datasets/tree, etc.) - ✅ Working perfectly with Faraday - Redirect handling: Configured with
faraday-follow_redirectsmiddleware to handle server redirects automatically
- Similar Patents by Text: Occasionally returns
503 Service Unavailable(a server-side issue, not a client implementation issue)
- Similar Patents: According to the documentation, the array of hits is named
hits, but the real implementation uses the namedata - Available Datasets: The
namekey in the real implementation has the localization suffix —name_ru,name_en
All core functionality works perfectly and is production-ready with a unified HTTP approach.
Rospatent::Errors::Error (base)
├── MissingTokenError
├── ApiError
│ ├── AuthenticationError (401)
│ ├── NotFoundError (404)
│ ├── RateLimitError (429)
│ └── ServiceUnavailableError (503)
├── ConnectionError
│ └── TimeoutError
├── InvalidRequestError
└── ValidationError
# Missing or invalid token
Rospatent::Errors::MissingTokenError
Rospatent::Errors::AuthenticationError
# Invalid input parameters
Rospatent::Errors::ValidationError
# Resource not found
Rospatent::Errors::NotFoundError
# Rate limiting
Rospatent::Errors::RateLimitError # Check retry_after
# Network issues
Rospatent::Errors::ConnectionError
Rospatent::Errors::TimeoutError
# Server problems
Rospatent::Errors::ServiceUnavailableErrorUseful development and maintenance tasks:
# Validate configuration
$ bundle exec rake validate
# Cache management
$ bundle exec rake cache:stats
$ bundle exec rake cache:clear
# Generate documentation
$ bundle exec rake doc
# Run integration tests
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<your_jwt_token>' bundle exec rake test_integration
# Setup development environment
$ bundle exec rake setup
# Pre-release checks
$ bundle exec rake release_check- Use Caching: Enable caching for repeated requests
- Batch Operations: Use
batch_patentsfor multiple documents - Appropriate Limits: Don't request more data than needed
- Connection Reuse: Use the same client instance when possible
- Environment Configuration: Use production settings in production
# Good: Reuse client instance
client = Rospatent.client
patents = patent_ids.map { |id| client.patent(id) }
# Better: Use batch operations
patents = []
client.batch_patents(patent_ids) { |doc| patents << doc }
# Best: Use caching with shared instance
shared_client = Rospatent.client(cache: Rospatent.shared_cache)Authentication Errors:
# Check token validity
errors = Rospatent.validate_configuration
puts errors if errors.any?Network Timeouts:
# Increase timeout for slow connections
Rospatent.configure do |config|
config.timeout = 120
config.retry_count = 5
endMemory Usage:
# Limit cache size for memory-constrained environments
Rospatent.configure do |config|
config.cache_max_size = 100
config.cache_ttl = 300
endDebug API Calls:
# Enable detailed logging
Rospatent.configure do |config|
config.log_level = :debug
config.log_requests = true
config.log_responses = true
endAfter checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests.
$ git clone https://hub.mos.ru/ad/rospatent.git
$ cd rospatent
$ bundle install
$ bundle exec rake setup# Unit tests
$ bundle exec rake test
# Integration tests (requires API token)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=your_token bundle exec rake test_integration
# Code style
$ bundle exec rubocop
# All checks
$ bundle exec rake ci$ bin/consoleBug reports and pull requests are welcome on MosHub at https://hub.mos.ru/ad/rospatent.
- Write Tests: Ensure all new features have corresponding tests
- Follow Style: Run
rubocopand fix any style issues - Document Changes: Update README and CHANGELOG
- Validate Configuration: Run
rake validatebefore submitting
# Pre-release checks
$ bundle exec rake release_check
# Update version and release
$ bundle exec rake releaseRospatent — это комплексный Ruby-клиент для взаимодействия с API поиска патентов Роспатента. Библиотека предоставляет удобный интерфейс для поиска, получения и анализа патентной информации с автоматическим кешированием, валидацией запросов и подробным логированием.
- 🔍 Полное покрытие API - поиск, получение патентов, медиафайлы и датасеты
- 🛡️ Надежная обработка ошибок - комплексные типы ошибок с детальным контекстом
- ⚡ Интеллектуальное кеширование - кеширование в памяти с TTL и LRU исключением
- ✅ Валидация входных данных - автоматическая валидация параметров с настраиваемыми лимитами и полезными сообщениями
- 📊 Структурированное логирование - JSON/текстовое логирование с отслеживанием запросов/ответов
- 🚀 Пакетные операции - параллельная обработка множества патентов
- ⚙️ Адаптивные окружения - различные конфигурации для development/staging/production
- 🧪 Комплексное тестирование - Обширное покрытие модульными и интеграционными тестами с валидацией обработки ошибок
- 📚 Отличная документация - подробные примеры и документация API
Добавьте в ваш Gemfile:
gem 'rospatent'Затем выполните:
$ bundle installИли установите напрямую:
$ gem install rospatent# Минимальная конфигурация
Rospatent.configure do |config|
config.token = "ваш_jwt_токен"
end
# Создание клиента и поиск
client = Rospatent.client
results = client.search(q: "ракета", limit: 10)
puts "Найдено #{results.total} результатов"
results.hits.each do |hit|
puts "Патент: #{hit['id']} - #{hit.dig('biblio', 'ru', 'title')}"
endRospatent.configure do |config|
# Обязательно
config.token = "ваш_jwt_токен"
# Настройки API
config.api_url = "https://searchplatform.rospatent.gov.ru/patsearch/v0.2"
config.timeout = 30
config.retry_count = 3
# Окружение (development, staging, production)
config.environment = "production"
endRospatent.configure do |config|
config.token = "ваш_jwt_токен"
# Кеширование (включено по умолчанию)
config.cache_enabled = true
config.cache_ttl = 300 # 5 минут
config.cache_max_size = 1000 # Максимум элементов кеша
# Логирование
config.log_level = :info # :debug, :info, :warn, :error
config.log_requests = true # Логировать API запросы
config.log_responses = true # Логировать API ответы
# Настройки соединения
config.connection_pool_size = 5
config.connection_keep_alive = true
# Управление токенами
config.token_expires_at = Time.now + 3600
config.token_refresh_callback = -> { refresh_token! }
endНастройте пороговые значения валидации для различных параметров в соответствии с вашими потребностями:
Rospatent.configure do |config|
config.token = "ваш_jwt_токен"
# Настройка лимитов валидации
config.validation_limits = {
# Параметры запросов
query_max_length: 5000, # По умолчанию: 2000
natural_query_max_length: 3000, # По умолчанию: 2000
# Лимиты пагинации
limit_max_value: 200, # По умолчанию: 100
offset_max_value: 50_000, # По умолчанию: 10,000
# Лимиты массивов и строк
array_max_size: 20, # По умолчанию: 10
string_max_length: 2000, # По умолчанию: 1000
# Лимиты подсветки
pre_tag_max_length: 100, # По умолчанию: 50
post_tag_max_length: 100, # По умолчанию: 50
pre_tag_max_size: 20, # По умолчанию: 10
post_tag_max_size: 20, # По умолчанию: 10
# Лимиты поиска по классификации
classification_query_max_length: 2000, # По умолчанию: 1000
classification_code_max_length: 100, # По умолчанию: 50
# Лимиты поиска похожих патентов
similar_text_min_words: 30, # По умолчанию: 50
similar_text_max_length: 20_000, # По умолчанию: 10,000
similar_count_max_value: 2000, # По умолчанию: 1000
# Лимиты пакетных операций
batch_size_max_value: 100, # По умолчанию: 50
batch_ids_max_size: 2000 # По умолчанию: 1000
}
endПреимущества настраиваемых лимитов валидации:
- Гибкость: Настройка лимитов в соответствии с требованиями вашего приложения
- Производительность: Точная настройка валидации для оптимальной производительности
- Эволюция API: Легкая адаптация к изменениям в спецификациях API Роспатента
- Специфичность окружения: Различные лимиты для разработки, staging и продакшна
Gem автоматически настраивается под окружение с разумными значениями по умолчанию:
# Оптимизировано для разработки
Rospatent.configure do |config|
config.environment = "development"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :debug
config.log_requests = true
config.log_responses = true
config.cache_ttl = 60 # Короткий кеш для разработки
config.timeout = 10 # Быстрые таймауты для быстрой обратной связи
end# Оптимизировано для staging
Rospatent.configure do |config|
config.environment = "staging"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :info
config.cache_ttl = 300 # Более длительный кеш для производительности
config.timeout = 45 # Более длительные таймауты для надежности
config.retry_count = 3 # Больше повторов для устойчивости
end# Оптимизировано для продакшна
Rospatent.configure do |config|
config.environment = "production"
config.token = ENV['ROSPATENT_TOKEN']
config.log_level = :warn
config.cache_ttl = 600 # Более длительный кеш для производительности
config.timeout = 60 # Более длительные таймауты для надежности
config.retry_count = 5 # Больше повторов для устойчивости
end- Rails credentials:
Rails.application.credentials.rospatent_token - Основная переменная окружения:
ROSPATENT_TOKEN - Устаревшая переменная окружения:
ROSPATENT_API_TOKEN
# Рекомендуемый подход
export ROSPATENT_TOKEN="your_jwt_token"
# Поддержка устаревшего формата (все еще работает)
export ROSPATENT_API_TOKEN="your_jwt_token"# Переменная окружения имеет приоритет над настройками Rails по умолчанию
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
endROSPATENT_LOG_LEVEL=debug в продакшне переопределит логику Rails и приведёт к появлению DEBUG логов в продакшне!
# Основная конфигурация
ROSPATENT_TOKEN="your_jwt_token" # Токен аутентификации API
ROSPATENT_ENV="production" # Переопределить Rails.env при необходимости
ROSPATENT_API_URL="custom_url" # Переопределить URL API по умолчанию
# Конфигурация логирования
ROSPATENT_LOG_LEVEL="warn" # debug, info, warn, error
ROSPATENT_LOG_REQUESTS="false" # Логировать API запросы
ROSPATENT_LOG_RESPONSES="false" # Логировать API ответы
# Конфигурация кеша
ROSPATENT_CACHE_ENABLED="true" # Включить/отключить кеширование
ROSPATENT_CACHE_TTL="300" # TTL кеша в секундах
ROSPATENT_CACHE_MAX_SIZE="1000" # Максимальное количество элементов кеша
# Конфигурация соединения
ROSPATENT_TIMEOUT="30" # Таймаут запроса в секундах
ROSPATENT_RETRY_COUNT="3" # Количество повторов
ROSPATENT_POOL_SIZE="5" # Размер пула соединений
ROSPATENT_KEEP_ALIVE="true" # Keep-alive соединения
# Переопределения для конкретных окружений
ROSPATENT_DEV_API_URL="dev_url" # URL API для разработки
ROSPATENT_STAGING_API_URL="staging_url" # URL API для staging-
Используйте Rails credentials для токенов:
rails credentials:edit # Добавьте: rospatent_token: your_jwt_token -
Установите переменные для конкретных окружений:
# config/environments/production.rb ENV["ROSPATENT_LOG_LEVEL"] ||= "warn" ENV["ROSPATENT_CACHE_ENABLED"] ||= "true"
-
Избегайте установки DEBUG уровня в продакшне:
# ❌ НЕ ДЕЛАЙТЕ ТАК в продакшне export ROSPATENT_LOG_LEVEL=debug # ✅ ДЕЛАЙТЕ ТАК export ROSPATENT_LOG_LEVEL=warn
# Валидация текущей конфигурации
errors = Rospatent.validate_configuration
if errors.any?
puts "Ошибки конфигурации:"
errors.each { |error| puts " - #{error}" }
else
puts "Конфигурация действительна ✓"
end# Простой поиск
results = client.search(q: "солнечная батарея")
# Поиск на естественном языке
results = client.search(qn: "конструкция ракетного двигателя")
# Расширенный поиск с всеми опциями
results = client.search(
q: "искусственный интеллект AND нейронная сеть",
limit: 50,
offset: 100,
datasets: ["ru_since_1994"],
filter: {
"classification.ipc_group": { "values": ["G06N"] },
"application.filing_date": { "range": { "gte": "20200101" } }
},
sort: "publication_date:desc", # то же самое, что 'sort: :pub_date'; см. варианты параметров сортировки в Search#validate_sort_parameter
group_by: "family:dwpi", # Группировка по семействам: "family:docdb" или "family:dwpi"
include_facets: true, # Boolean: true/false (автоматически конвертируется в 1/0 для API)
pre_tag: "<mark>", # Оба тега должны быть указаны вместе
post_tag: "</mark>", # Могут быть строками или массивами
highlight: { # Продвинутая настройка подсветки (независимо от тегов)
"profiles" => [
{ "q" => "нейронная сеть", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_"
]
}
)
# Простая подсветка с тегами (оба тега обязательны)
results = client.search(
q: "ракета",
pre_tag: "<mark>",
post_tag: "</mark>"
)
# Многоцветная подсветка с массивами
results = client.search(
q: "космическая ракета",
pre_tag: ["<b>", "<i>"], # Циклическая подсветка
post_tag: ["</b>", "</i>"] # разными тегами
)
# Продвинутая подсветка с использованием профилей (независимо от pre_tag/post_tag)
results = client.search(
q: "ракета",
highlight: {
"profiles" => [
{ "q" => "космическая", "pre_tag" => "<b>", "post_tag" => "</b>" },
"_searchquery_" # Ссылка на параметры подсветки основного поискового запроса
]
}
)
# Группировка по семействам патентов (группирует патенты одного изобретения)
results = client.search(
q: "ракета",
group_by: "family:docdb", # Простые семейства патентов DOCDB
datasets: ["dwpi"],
limit: 10
)
results = client.search(
q: "ракета",
group_by: "family:dwpi", # Простые семейства патентов DWPI
datasets: ["dwpi"],
limit: 10
)
# Обработка результатов
puts "Найдено: #{results.total} патентов (доступно #{results.available})"
puts "Показано: #{results.count}"
results.hits.each do |hit|
puts "ID: #{hit['id']}"
puts "Название: #{hit.dig('biblio', 'ru', 'title')}"
puts "Дата: #{hit.dig('common', 'publication_date')}"
puts "МПК: #{hit.dig('common', 'classification', 'ipc')&.map {|c| c['fullname']}&.join('; ')}"
puts "---"
endПараметр filter поддерживает сложную фильтрацию с автоматической валидацией и преобразованием форматов:
# Фильтры по классификации
results = client.search(
q: "искусственный интеллект",
filter: {
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"classification.cpc_group": { "values": ["G06N3/", "G06N20/"] }
}
)
# Фильтры по авторам и патентообладателям
results = client.search(
q: "изобретение",
filter: {
"authors": { "values": ["Иванов И.И.", "Петров П.П."] },
"patent_holders": { "values": ["ООО Компания"] },
"country": { "values": ["RU", "US"] },
"kind": { "values": ["A1", "U1"] }
}
)
# Фильтры по ID документов
results = client.search(
q: "устройство",
filter: {
"ids": { "values": ["RU134694U1_20131120", "RU2358138C1_20090610"] }
}
)# Автоматическое преобразование формата дат
results = client.search(
q: "инновация",
filter: {
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-06-15" } }
}
)
# Прямой формат API (YYYYMMDD)
results = client.search(
q: "технология",
filter: {
"date_published": { "range": { "gte": "20200101", "lt": "20240101" } }
}
)
# Использование объектов Date (автоматически конвертируются)
results = client.search(
q: "патент",
filter: {
"application.filing_date": {
"range": {
"gte": Date.new(2020, 1, 1),
"lte": Date.new(2023, 12, 31)
}
}
}
)Поддерживаемые операторы дат: gt, gte, lt, lte
Преобразование формата дат:
"2020-01-01"→"20200101"Date.new(2020, 1, 1)→"20200101""20200101"→"20200101"(без изменений)
# Комплексный пример фильтра
results = client.search(
q: "машинное обучение",
filter: {
# Списочные фильтры
"classification.ipc_group": { "values": ["G06N", "G06F"] },
"country": { "values": ["RU", "US", "CN"] },
"kind": { "values": ["A1", "A2"] },
"authors": { "values": ["Иванов И.И."] },
# Диапазонные фильтры по датам
"date_published": { "range": { "gte": "2020-01-01", "lte": "2023-12-31" } },
"application.filing_date": { "range": { "gte": "2019-01-01" } }
},
limit: 50
)Поддерживаемые поля фильтров:
Списочные фильтры (требуют формат {"values": [...]})::
authors- Авторы патентовpatent_holders- Патентообладатели/правопреемникиcountry- Коды странkind- Типы документовids- Конкретные ID документовclassification.ipc*- Коды классификации IPCclassification.cpc*- Коды классификации CPC
Фильтры по датам (требуют формат {"range": {"operator": "YYYYMMDD"}})::
date_published- Дата публикацииapplication.filing_date- Дата подачи заявки
Валидация фильтров:
- ✅ Автоматическая валидация названий полей
- ✅ Валидация структуры (списочный vs диапазонный формат)
- ✅ Преобразование и валидация формата дат
- ✅ Валидация операторов для диапазонов
- ✅ Полезные сообщения об ошибках для неверных фильтров
# Эти примеры вызовут ValidationError с конкретными сообщениями:
client.search(
q: "тест",
filter: { "invalid_field": { "values": ["тест"] } }
)
# Ошибка: "Invalid filter field: invalid_field"
client.search(
q: "тест",
filter: { "authors": ["прямой", "массив"] } # Отсутствует обертка {"values": [...]}
)
# Ошибка: "Filter 'authors' requires format: {\"values\": [...]}"
client.search(
q: "тест",
filter: { "date_published": { "range": { "invalid_op": "20200101" } } }
)
# Ошибка: "Invalid range operator: invalid_op. Supported: gt, gte, lt, lte"# По идентификатору документа
patent = client.patent("RU134694U1_20131120")
# По компонентам идентификатора
patent_doc = client.patent_by_components(
"RU", # country_code
"134694", # number
"U1", # doc_type
Date.new(2013, 11, 20) # date (String или объект Date)
)
# Доступ к данным патента
title = patent_doc.dig('biblio', 'ru', 'title')
abstract = patent_doc.dig('abstract', 'ru')
inventors = patent_doc.dig('biblio', 'ru', 'inventor')Получение чистого текста или структурированного содержимого:
# Парсинг аннотации
abstract_text = client.parse_abstract(patent_doc)
abstract_html = client.parse_abstract(patent_doc, format: :html)
abstract_ru = client.parse_abstract(patent_doc, language: "ru")
# Парсинг описания
description_text = client.parse_description(patent_doc)
description_html = client.parse_description(patent_doc, format: :html)
# Парсинг описания с разбивкой на секции
sections = client.parse_description(patent_doc, format: :sections)
sections.each do |section|
puts "Секция #{section[:number]}: #{section[:content]}"
end# Поиск похожих патентов по ID
similar = client.similar_patents_by_id("RU134694U1_20131120", count: 50)
# Поиск похожих патентов по описанию текста
similar = client.similar_patents_by_text(
"Ракетный двигатель с улучшенной тягой ...", # минимум 50 слов в запросе
count: 25
)
# Обработка похожих патентов
similar["data"]&.each do |patent|
puts "Похожий: #{patent['id']} (оценка: #{patent['similarity']} (#{patent['similarity_norm']}))"
endПоиск в системах патентной классификации (IPC и CPC) и получение подробной информации о классификационных кодах:
# Поиск классификационных кодов, связанных с ракетами в IPC
ipc_results = client.classification_search("ipc", query: "ракета", lang: "ru")
puts "Найдено #{ipc_results.size} кодов IPC"
ipc_results&.each do |result|
puts "#{result['Code']}: #{result['Description']}"
end
# Поиск кодов, связанных с ракетами в CPC на английском
cpc_results = client.classification_search("cpc", query: "rocket", lang: "en")
# Получение подробной информации о конкретном классификационном коде
code, info = client.classification_code("ipc", code: "F02K9/00", lang: "ru")&.first
puts "Код: #{code}"
puts "Описание: #{info&.first['Description']}"
puts "Иерархия: #{info&.map{|level| level['Code']}&.join(' → ')}"
# Получение информации о коде CPC на английском
cpc_info = client.classification_code("cpc", code: "B63H11/00", lang: "en")Поддерживаемые системы классификации:
"ipc"- Международная патентная классификация (МПК)"cpc"- Совместная патентная классификация (СПК)
Поддерживаемые языки:
"ru"- Русский"en"- Английский
datasets = client.datasets_tree
datasets.each do |category|
puts "Категория: #{category['name_ru']}"
category.children.each do |dataset|
puts " #{dataset['id']}: #{dataset['name_ru']}"
end
end# ✅ Рекомендуется: Скачивание PDF патента с автоматически генерируемым именем файла
# Автоматически использует отформатированный номер публикации (например, "0000134694.pdf")
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694" # pub_number (имя файла генерируется автоматически)
)
client.save_binary_file(pdf_data, "patent.pdf")
# ✅ Альтернатива: Скачивание с явным указанием имени файла
pdf_data = client.patent_media(
"National", # collection_id
"RU", # country_code
"U1", # doc_type
"2013/11/20", # pub_date
"134694", # pub_number
"document.pdf" # явное имя файла
)
client.save_binary_file(pdf_data, "patent_explicit.pdf")
# ✅ Упрощенный метод с использованием ID патента (автогенерируемое имя)
pdf_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National" # имя файла автоматически генерируется как "0000134694.pdf"
)
client.save_binary_file(pdf_data, "patent_by_id.pdf")
# ✅ Или с явным именем файла для конкретных файлов
image_data = client.patent_media_by_id(
"RU134694U1_20131120",
"National",
"image.png" # явное имя файла для файлов не-PDF
)
client.save_binary_file(image_data, "patent_image.png")
# ✅ Варианты безопасного сохранения файлов:
File.binwrite("patent.pdf", pdf_data) # Ручная бинарная запись
# ❌ Избегайте: File.write может вызвать ошибки кодировки с бинарными данными
# File.write("patent.pdf", pdf_data) # Это может не сработать!Эффективная обработка множества патентов с параллельными запросами:
document_ids = ["RU134694U1_20131120", "RU2358138C1_20090610", "RU2756123C1_20210927"]
# Обработка патентов пакетами
client.batch_patents(document_ids, batch_size: 5) do |patent_doc|
if patent_doc[:error]
puts "Ошибка для #{patent_doc[:document_id]}: #{patent_doc[:error]}"
else
puts "Получен патент: #{patent_doc['id']}"
# Обработка документа патента
end
end
# Или сбор всех результатов
patents = []
client.batch_patents(document_ids) { |doc| patents << doc }Автоматическое интеллектуальное кеширование улучшает производительность:
# Кеширование автоматическое и прозрачное
patent1 = client.patent("RU134694U1_20131120") # API вызов
patent2 = client.patent("RU134694U1_20131120") # Кешированный результат
# Проверка статистики кеша
stats = client.statistics
puts "Процент попаданий в кеш: #{stats[:cache_stats][:hit_rate_percent]}%"
puts "Всего запросов: #{stats[:requests_made]}"
puts "Среднее время ответа: #{stats[:average_request_time]}с"
# Использование общего кеша между клиентами
shared_cache = Rospatent.shared_cache
client1 = Rospatent.client(cache: shared_cache)
client2 = Rospatent.client(cache: shared_cache)
# Ручное управление кешем
shared_cache.clear # Очистить все кешированные данные
expired_count = shared_cache.cleanup_expired # Удалить истекшие записи
cache_stats = shared_cache.statistics # Получить детальную статистику кешаНастройка детального логирования для мониторинга и отладки:
# Создание собственного логгера
logger = Rospatent::Logger.new(
output: Rails.logger, # Или любой объект IO
level: :info,
formatter: :json # :json или :text
)
client = Rospatent.client(logger: logger)
# Логи включают:
# - API запросы/ответы с временными метками
# - Операции кеша (попадания/промахи)
# - Детали ошибок с контекстом
# - Метрики производительности
# Доступ к общему логгеру
shared_logger = Rospatent.shared_logger(level: :debug)Комментарии:
- При использовании
Rails.logger, форматирование контролируется конфигурацией Rails, параметрformatterигнорируется - При использовании IO объекта, формат определяется параметром
formatter
Комплексная обработка ошибок с конкретными типами ошибок и улучшенным извлечением сообщений об ошибках:
begin
patent = client.patent("INVALID_ID")
rescue Rospatent::Errors::ValidationError => e
puts "Неверный ввод: #{e.message}"
puts "Ошибки полей: #{e.errors}" if e.errors.any?
rescue Rospatent::Errors::NotFoundError => e
puts "Патент не найден: #{e.message}"
rescue Rospatent::Errors::RateLimitError => e
puts "Ограничение скорости. Повторить через: #{e.retry_after} секунд"
rescue Rospatent::Errors::AuthenticationError => e
puts "Ошибка аутентификации: #{e.message}"
rescue Rospatent::Errors::ApiError => e
puts "Ошибка API (#{e.status_code}): #{e.message}"
puts "ID запроса: #{e.request_id}" if e.request_id
retry if e.retryable?
rescue Rospatent::Errors::ConnectionError => e
puts "Ошибка соединения: #{e.message}"
puts "Исходная ошибка: #{e.original_error}"
end
# Улучшенное извлечение сообщений об ошибках
# Клиент автоматически извлекает сообщения об ошибках из различных форматов ответов API:
# - {"result": "Сообщение об ошибке"} (формат API Роспатента)
# - {"error": "Сообщение об ошибке"} (стандартный формат)
# - {"message": "Сообщение об ошибке"} (альтернативный формат)
# - {"details": "Детали валидации"} (ошибки валидации)Все входные данные автоматически валидируются с полезными сообщениями об ошибках:
# Эти примеры вызовут ValidationError с конкретными сообщениями:
client.search(limit: 0) # "Limit must be at least 1"
client.patent("") # "Document_id cannot be empty"
client.similar_patents_by_text("", count: -1) # Множественные ошибки валидации
# Валидация включает:
# - Типы и форматы параметров
# - Валидация формата ID патента
# - Валидация формата даты
# - Валидация значений перечислений
# - Валидация обязательных полейОтслеживание производительности и статистики использования:
# Статистика конкретного клиента
stats = client.statistics
puts "Выполнено запросов: #{stats[:requests_made]}"
puts "Общая продолжительность: #{stats[:total_duration_seconds]}с"
puts "Среднее время запроса: #{stats[:average_request_time]}с"
puts "Процент попаданий в кеш: #{stats[:cache_stats][:hit_rate_percent]}%"
# Глобальная статистика
global_stats = Rospatent.statistics
puts "Окружение: #{global_stats[:configuration][:environment]}"
puts "Кеш включен: #{global_stats[:configuration][:cache_enabled]}"
puts "URL API: #{global_stats[:configuration][:api_url]}"$ rails generate rospatent:installЭто создает config/initializers/rospatent.rb:
Rospatent.configure do |config|
# Приоритет токена: Rails credentials > ROSPATENT_TOKEN > ROSPATENT_API_TOKEN
config.token = Rails.application.credentials.rospatent_token ||
ENV["ROSPATENT_TOKEN"] ||
ENV["ROSPATENT_API_TOKEN"]
# Конфигурация окружения учитывает ROSPATENT_ENV
config.environment = ENV.fetch("ROSPATENT_ENV", Rails.env)
# КРИТИЧНО: Переменные окружения имеют приоритет над настройками Rails
# Это предотвращает появление DEBUG логов в продакшне при ROSPATENT_LOG_LEVEL=debug
config.log_level = if ENV.key?("ROSPATENT_LOG_LEVEL")
ENV["ROSPATENT_LOG_LEVEL"].to_sym
else
Rails.env.production? ? :warn : :debug
end
config.cache_enabled = Rails.env.production?
end# В config/initializers/rospatent.rb
Rospatent.configure do |config|
config.token = Rails.application.credentials.rospatent_token
end
# Создание клиента с логгером Rails
logger = Rospatent::Logger.new(
output: Rails.logger,
level: Rails.env.production? ? :warn : :debug,
formatter: :text
)
# Использование в контроллерах/сервисах
class PatentService
def initialize
@client = Rospatent.client(logger: logger)
end
def search_patents(query)
@client.search(q: query, limit: 20)
rescue Rospatent::Errors::ApiError => e
Rails.logger.error "Поиск патентов не удался: #{e.message}"
raise
end
end# Запуск всех тестов
$ bundle exec rake test
# Запуск конкретного тестового файла
$ bundle exec ruby -Itest test/unit/client_test.rb
# Запуск интеграционных тестов (требуется API токен)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=ваш_токен bundle exec rake test_integration
# Запуск с покрытием
$ bundle exec rake coverageДля тестирования сбрасывайте и настраивайте в методе setup каждого теста:
# test/test_helper.rb - Базовая настройка для модульных тестов
module Minitest
class Test
def setup
Rospatent.reset # Чистое состояние между тестами
Rospatent.configure do |config|
config.token = ENV.fetch("ROSPATENT_TEST_TOKEN", "test_token")
config.environment = "development"
config.cache_enabled = false # Отключить кеш для предсказуемых тестов
config.log_level = :error # Уменьшить шум тестов
end
end
end
end
# Для интеграционных тестов - стабильная конфигурация, сброс не нужен
class IntegrationTest < Minitest::Test
def setup
skip unless ENV["ROSPATENT_INTEGRATION_TESTS"]
@token = ENV.fetch("ROSPATENT_TEST_TOKEN", nil)
skip "ROSPATENT_TEST_TOKEN not set" unless @token
# Сброс не нужен - интеграционные тесты используют согласованную конфигурацию
Rospatent.configure do |config|
config.token = @token
config.environment = "development"
config.cache_enabled = true
config.log_level = :debug
end
end
end# test/test_helper.rb
module Minitest
class Test
def assert_valid_patent_id(patent_id, message = nil)
message ||= "Ожидается #{patent_id} как действительный ID патента (формат: XX12345Y1_YYYYMMDD)"
assert patent_id.match?(/^[A-Z]{2}[A-Z0-9]+[A-Z]\d*_\d{8}$/), message
end
end
end
# Использование в тестах
def test_patent_id_validation
assert_valid_patent_id("RU134694U1_20131120")
assert_valid_patent_id("RU134694A_20131120")
endБиблиотека использует Faraday в качестве HTTP-клиента с поддержкой редиректов для всех endpoints:
- Все endpoints (
/search,/docs/{id},/similar_search,/datasets/tree, и т.д.) - ✅ Работают идеально с Faraday - Обработка редиректов: Настроена с middleware
faraday-follow_redirectsдля автоматической обработки серверных редиректов
- Поиск похожих патентов по тексту: Иногда возвращает
503 Service Unavailable(проблема сервера, не клиентской реализации)
- Поиск похожих патентов: Массив совпадений в документации назван
hits, фактическая реализация используетdata - Перечень датасетов: Ключ
nameв фактической реализации содержит признак локализации —name_ru,name_en
Вся основная функциональность реализована и готова для продакшена.
Rospatent::Errors::Error (базовая)
├── MissingTokenError
├── ApiError
│ ├── AuthenticationError (401)
│ ├── NotFoundError (404)
│ ├── RateLimitError (429)
│ └── ServiceUnavailableError (503)
├── ConnectionError
│ └── TimeoutError
├── InvalidRequestError
└── ValidationError
# Отсутствующий или недействительный токен
Rospatent::Errors::MissingTokenError
Rospatent::Errors::AuthenticationError
# Недействительные входные параметры
Rospatent::Errors::ValidationError
# Ресурс не найден
Rospatent::Errors::NotFoundError
# Ограничение скорости
Rospatent::Errors::RateLimitError # Проверьте retry_after
# Проблемы с сетью
Rospatent::Errors::ConnectionError
Rospatent::Errors::TimeoutError
# Проблемы сервера
Rospatent::Errors::ServiceUnavailableErrorПолезные задачи для разработки и обслуживания:
# Валидация конфигурации
$ bundle exec rake validate
# Управление кешем
$ bundle exec rake cache:stats
$ bundle exec rake cache:clear
# Генерация документации
$ bundle exec rake doc
# Запуск интеграционных тестов
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN='<ваш_jwt_токен>' bundle exec rake test_integration
# Настройка среды разработки
$ bundle exec rake setup
# Проверки перед релизом
$ bundle exec rake release_check- Используйте кеширование: Включите кеширование для повторяющихся запросов
- Пакетные операции: Используйте
batch_patentsдля множества документов - Подходящие лимиты: Не запрашивайте больше данных, чем необходимо
- Переиспользование соединений: Используйте один экземпляр клиента когда возможно
- Конфигурация окружения: Используйте продакшн настройки в продакшне
# Хорошо: Переиспользование экземпляра клиента
client = Rospatent.client
patents = patent_ids.map { |id| client.patent(id) }
# Лучше: Использование пакетных операций
patents = []
client.batch_patents(patent_ids) { |doc| patents << doc }
# Отлично: Использование кеширования с общим экземпляром
shared_client = Rospatent.client(cache: Rospatent.shared_cache)Ошибки аутентификации:
# Проверка валидности токена
errors = Rospatent.validate_configuration
puts errors if errors.any?Таймауты сети:
# Увеличение таймаута для медленных соединений
Rospatent.configure do |config|
config.timeout = 120
config.retry_count = 5
endИспользование памяти:
# Ограничение размера кеша для окружений с ограниченной памятью
Rospatent.configure do |config|
config.cache_max_size = 100
config.cache_ttl = 300
endОтладка API вызовов:
# Включение подробного логирования
Rospatent.configure do |config|
config.log_level = :debug
config.log_requests = true
config.log_responses = true
endПосле клонирования репозитория выполните bin/setup для установки зависимостей. Затем запустите rake test для выполнения тестов.
$ git clone https://hub.mos.ru/ad/rospatent.git
$ cd rospatent
$ bundle install
$ bundle exec rake setup# Модульные тесты
$ bundle exec rake test
# Интеграционные тесты (требуется API токен)
$ ROSPATENT_INTEGRATION_TESTS=true ROSPATENT_TEST_TOKEN=ваш_токен bundle exec rake test_integration
# Стиль кода
$ bundle exec rubocop
# Все проверки
$ bundle exec rake ci$ bin/consoleОтчеты об ошибках и pull request приветствуются на MosHub по адресу https://hub.mos.ru/ad/rospatent.
- Пишите тесты: Убедитесь, что все новые функции имеют соответствующие тесты
- Следуйте стилю: Выполните
rubocopи исправьте любые проблемы стиля - Документируйте изменения: Обновите README и CHANGELOG
- Валидируйте конфигурацию: Запустите
rake validateперед отправкой
# Проверки перед релизом
$ bundle exec rake release_check
# Обновление версии и релиз
$ bundle exec rake releaseSee CHANGELOG.md for detailed version history.
The gem is available as open source under the terms of the MIT License.
For detailed API documentation, see the generated documentation or run:
$ bundle exec rake doc
$ open doc/index.htmlKey Classes:
Rospatent::Client- Main API clientRospatent::Configuration- Configuration managementRospatent::Cache- Caching systemRospatent::Logger- Structured loggingRospatent::SearchResult- Search result wrapperRospatent::PatentParser- Patent content parsing
Classification Features:
- Classification system search (IPC/CPC)
- Detailed classification code information
- Multi-language support (Russian/English)
- Automatic caching of classification data
Patent Features:
- Patent search by text
- Patent details retrieval
- Patent classification retrieval
- Patent content parsing
- Patent media retrieval
- Patent similarity search by text
- Patent similarity search by ID
Supported Ruby Versions: Ruby 3.3.0+