Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 45 additions & 26 deletions lib/mindee/v2/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,7 @@ def initialize(api_key: '')
@mindee_api = Mindee::V2::HTTP::MindeeApiV2.new(api_key: api_key)
end

# Retrieves a result from a given queue or URL to the result.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @param resource [String] ID of the inference or URL to the result.
# @return [Mindee::V2::Parsing::BaseResponse]
def get_result(product, resource)
@mindee_api.req_get_result(product, resource)
end

# Retrieves an inference from a given queue or URL to the job.
# @param job_id [String] ID of the job.
# @return [Mindee::V2::Parsing::JobResponse]
def get_job(job_id)
@mindee_api.req_get_job(job_id)
end

# Enqueue a document for async parsing.
# Send a file to the asynchronous processing queue for inference.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @param input_source [Mindee::Input::Source::LocalInputSource, Mindee::Input::Source::URLInputSource]
# The source of the input document (local file or URL).
Expand All @@ -51,16 +36,48 @@ def enqueue(
@mindee_api.req_post_product_enqueue(input_source, normalized_params)
end

# Enqueues to an asynchronous endpoint and automatically polls for a response.
#
# Get the status of an inference that was previously enqueued.
# Can be used for polling.
# @param url [String] HTTPS URL to the job.
# @return [Mindee::V2::Parsing::JobResponse]
def get_job_by_url(url)
@mindee_api.req_get_job_by_url(url)
end

# Get the result of an inference that was previously enqueued.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @param url [String] HTTPS URL to the result.
# @return [Mindee::V2::Parsing::BaseResponse]
def get_result_by_url(product, url)
@mindee_api.req_get_result_by_url(product, url)
end

# Get the result of an inference that was previously enqueued.
# Prefer using `get_result_by_url` using the result URL returned by the Job.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @param resource [String] ID of the inference or URL to the result.
# @return [Mindee::V2::Parsing::BaseResponse]
def get_result(product, resource)
@mindee_api.req_get_result_by_id(product, resource)
end

# Get the status of an inference that was previously enqueued.
# Prefer using `get_job_by_url` with the polling URL returned by enqueue.
# @param job_id [String] ID of the job.
# @return [Mindee::V2::Parsing::JobResponse]
def get_job(job_id)
@mindee_api.req_get_job_by_id(job_id)
end

# Send a file to the inference queue and return the results when complete.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @param input_source [Mindee::Input::Source::LocalInputSource, Mindee::Input::Source::URLInputSource]
# The source of the input document (local file or URL).
# @param params [Hash, ClientOptions::BaseProductParameters] Parameters for the inference.
# @param polling_options [Hash, PollingOptions, nil] Parameters for polling.
# @param cancellation_token [Mindee::HTTP::CancellationToken, nil] Token for cancellation.
# @return [Parsing::BaseResponse]
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
def enqueue_and_get_result(
product,
input_source,
Expand All @@ -84,29 +101,31 @@ def enqueue_and_get_result(

sleep(normalized_params.polling_options.initial_delay_sec)
retry_counter = 1
poll_results = get_job(job_id)
polling_url = enqueue_response.job.polling_url

job_response = get_job_by_url(polling_url)
Comment thread
ianardee marked this conversation as resolved.
Resolved
Comment thread
ianardee marked this conversation as resolved.
Resolved

while retry_counter < normalized_params.polling_options.max_retries
if poll_results.job.status == 'Failed'
if job_response.job.status == 'Failed'
break
elsif !poll_results.job.result_url.nil?
return get_result(product, poll_results.job.result_url)
elsif !job_response.job.result_url.nil?
return get_result_by_url(product, job_response.job.result_url)
end

logger.debug(
"Successfully enqueued inference with job id: #{job_id}.\n" \
"Attempt n°#{retry_counter}/#{normalized_params.polling_options.max_retries}.\n" \
"Job status: #{poll_results.job.status}."
"Job status: #{job_response.job.status}."
)

raise Mindee::Error::MindeeError, 'Enqueueing of the document was canceled.' if cancellation_token&.canceled?

sleep(normalized_params.polling_options.delay_sec)
poll_results = get_job(job_id)
job_response = get_job_by_url(polling_url)
retry_counter += 1
end

error = poll_results.job.error
error = job_response.job.error
unless error.nil?
err_to_raise = Mindee::Error::MindeeHTTPErrorV2.new(error)
# NOTE: purposefully decoupled from the line above, otherwise rubocop thinks `error` is a `message` param.
Expand Down
83 changes: 34 additions & 49 deletions lib/mindee/v2/http/mindee_api_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,44 @@ def req_post_product_enqueue(input_source, params)
end

# Retrieves a result from a given queue.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @param resource [String] ID of the inference or URL to the result.
# @param product [Class<Mindee::V2::Product::BaseProduct>]
# @param inference_id [String] ID of the inference or URL to the result.
# @return [Mindee::V2::Parsing::BaseResponse]
def req_get_result(product, resource)
return req_get_result_url(product.response_type, resource) if uri?(resource)

@settings.check_api_key
response = result_req_get(
resource,
product
)
product.response_type.new(process_response(response))
def req_get_result_by_id(product, inference_id)
req_get_result_by_url(product, "#{@settings.base_url}/v2/products/#{product.slug}/results/#{inference_id}")
end

# Retrieves a queued job.
#
# @param job_id [String] ID of the job or URL to the job.
# @return [Mindee::V2::Parsing::JobResponse]
def req_get_job(job_id)
def req_get_job_by_id(job_id)
req_get_job_by_url("#{@settings.base_url}/v2/jobs/#{job_id}")
end

# Retrieves a queued job from its polling URL.
#
# @param url [String] HTTPS URL to the job.
# @return [Mindee::V2::Parsing::JobResponse]
def req_get_job_by_url(url)
validate_https_url!(url)
@settings.check_api_key
response = poll("#{@settings.base_url}/v2/jobs/#{job_id}")
response = poll(url)
Mindee::V2::Parsing::JobResponse.new(process_response(response))
end

# Retrieves a result from its URL.
#
# @param product [Class<Mindee::V2::Product::BaseProduct>]
# @param url [String] HTTPS URL to the result.
# @return [Mindee::V2::Parsing::BaseResponse]
def req_get_result_by_url(product, url)
validate_https_url!(url)
@settings.check_api_key
response = poll(url)
product.response_type.new(process_response(response))
end

# Retrieves a list of resources with the given criteria.
# @param params [ClientOptions::BaseSearchParameters] Search parameters.
# @return [Mindee::V2::Parsing::Search::BaseSearchResponse] A search response containing the matching
Expand Down Expand Up @@ -85,35 +99,15 @@ def req_get_search(params)

private

# @param resource [String] Resource to check.
# @return [Boolean]
def uri?(resource)
uri = URI.parse(resource)
throw Mindee::Error::MindeeError, 'HTTP is not supported.' if uri.scheme == 'http'
uri.scheme == 'https'
rescue URI::BadURIError, URI::InvalidURIError
false
end

# Retrieves a queued job.
#
# @param url [String]
# @return [Mindee::V2::Parsing::JobResponse]
def req_get_job_url(url)
@settings.check_api_key
response = poll(url)
Mindee::V2::Parsing::JobResponse.new(process_response(response))
end

# Retrieves a queued job.
# Validates that a URL uses HTTPS and includes a host.
#
# @param result_class [Class<Mindee::V2::Parsing::BaseResponse>]
# @param url [String]
# @return [Mindee::V2::Parsing::BaseResponse]
def req_get_result_url(result_class, url)
@settings.check_api_key
response = poll(url)
result_class.new(process_response(response))
# @return [void]
def validate_https_url!(url)
uri = URI.parse(url)
raise Mindee::Error::MindeeError, "Invalid URL: #{url}" unless uri.scheme == 'https' && !uri.host.to_s.empty?
rescue URI::BadURIError, URI::InvalidURIError
raise Mindee::Error::MindeeError, "Invalid URL: #{url}"
Comment thread
ianardee marked this conversation as resolved.
Resolved
end

# Converts an HTTP response to a parsed response object.
Expand Down Expand Up @@ -156,15 +150,6 @@ def poll(url)
raise Mindee::Error::MindeeError, 'Could not resolve server response.'
end

# Polls the API for the result of an inference.
#
# @param queue_id [String] ID of the queue.
# @param product [Class<Mindee::V2::Product::BaseProduct>] The return class.
# @return [Net::HTTPResponse]
def result_req_get(queue_id, product)
poll("#{@settings.base_url}/v2/products/#{product.slug}/results/#{queue_id}")
end

# Handle parameters for the enqueue form
# @param form_data [Array] Array of form fields
# @param params [V2::Product::Extraction::Params::ExtractionParameters] Inference options.
Expand Down
4 changes: 4 additions & 0 deletions sig/mindee/v2/client.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ module Mindee

def get_job: (String job_id) -> V2::Parsing::JobResponse

def get_result_by_url: [T] (HTTP::_ProductClass[T] product, String url) -> T

def get_job_by_url: (String url) -> V2::Parsing::JobResponse

def enqueue: [T] (HTTP::_ProductClass[T] product, Input::Source::LocalInputSource | Input::Source::URLInputSource, Hash[String | Symbol, untyped] | ClientOptions::BaseProductParameters params) -> V2::Parsing::JobResponse

def enqueue_and_get_result: [T] (HTTP::_ProductClass[T] product, Input::Source::LocalInputSource | Input::Source::URLInputSource, Hash[String | Symbol, untyped] | ClientOptions::BaseProductParameters params, ?Hash[String | Symbol, untyped] | Input::PollingOptions | nil) -> T
Expand Down
16 changes: 7 additions & 9 deletions sig/mindee/v2/http/mindee_api_v2.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@ module Mindee

def initialize: (?api_key: String?) -> void

def req_get_result: [T] (_ProductClass[T] product, String resource) -> T
def req_get_result_by_id: [T] (_ProductClass[T] product, String inference_id) -> T

def req_post_product_enqueue: (Input::Source::LocalInputSource | Input::Source::URLInputSource, ClientOptions::BaseProductParameters) -> V2::Parsing::JobResponse

def req_get_job: (String) -> V2::Parsing::JobResponse
def req_get_job_by_id: (String job_id) -> V2::Parsing::JobResponse

def req_get_job_by_url: (String url) -> V2::Parsing::JobResponse

def req_get_result_by_url: [T] (_ProductClass[T] product, String url) -> T

def process_response: (Net::HTTPResponse?) -> Hash[String | Symbol, untyped]

def poll: (String) -> Net::HTTPResponse

def result_req_get: [T] (String, _ProductClass[T] product) -> Net::HTTPResponse

def enqueue: (Input::Source::LocalInputSource | Input::Source::URLInputSource, ClientOptions::BaseProductParameters) -> Net::HTTPResponse?

def req_get_search: [T] (_SearchParameters[T] params) -> T
Expand All @@ -47,11 +49,7 @@ module Mindee

def enqueue_form_options: (Array[Array[untyped]], V2::Product::Extraction::Params::ExtractionParameters) -> Array[Array[untyped]]

def req_get_job_url: (String) -> V2::Parsing::JobResponse

def req_get_result_url: [T] (_ResponseFactory[T] result_class, String url) -> T

def uri?: (String) -> bool
def validate_https_url!: (String) -> void
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions spec/v2/client_v2_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def stub_next_request_with(method, hash:, status_code: 0)
resp.job.completed_at.strftime('%Y-%m-%dT%H:%M:%S.%6N')
).to eq('2026-04-20T18:32:02.734312')
end

it 'poll builds a real Net::HTTP::Get request without a Transfer-Encoding header' do
job_json = File.read(File.join(V2_DATA_DIR, 'job', 'ok_processing.json'))
captured_request = nil
Expand Down Expand Up @@ -161,7 +162,7 @@ def stub_next_request_with(method, hash:, status_code: 0)
before do
allow(client).to receive(:sleep)
allow(client).to receive(:enqueue).and_return(mock_enqueue_res)
allow(client).to receive(:get_job).and_return(mock_processing_res)
allow(client).to receive(:get_job_by_url).and_return(mock_processing_res)
end

it 'raises MindeeError when token is cancelled before first poll' do
Expand All @@ -178,7 +179,7 @@ def stub_next_request_with(method, hash:, status_code: 0)
it 'raises MindeeError when token is cancelled during poll loop' do
token = Mindee::HTTP::CancellationToken.new
call_count = 0
allow(client).to receive(:get_job) do
allow(client).to receive(:get_job_by_url) do
call_count += 1
token.cancel if call_count == 1
mock_processing_res
Expand Down
Loading