Class: Mindee::V1::Client

Inherits:
Object show all
Defined in:
lib/mindee/v1/client.rb,
sig/mindee/v1/client.rbs

Overview

Mindee API Client. See: https://developers.mindee.com/docs

Instance Method Summary collapse

Constructor Details

#initialize(api_key: '') ⇒ Client

Returns a new instance of Client.

Parameters:

  • api_key (String) (defaults to: '')
  • api_key: (String) (defaults to: '')


94
95
96
# File 'lib/mindee/v1/client.rb', line 94

def initialize(api_key: '')
  @api_key = api_key
end

Instance Method Details

#create_endpoint(endpoint_name: '', account_name: '', version: '') ⇒ Mindee::V1::HTTP::Endpoint

Creates a custom endpoint with the given values. Do not set for standard (off the shelf) endpoints.

Parameters:

  • endpoint_name (String) (defaults to: '') —

    For custom endpoints, the "API name" field in the "Settings" page of the API Builder. Do not set for standard (off the shelf) endpoints.

  • account_name (String) (defaults to: '') —

    For custom endpoints, your account or organization username on the API Builder. This is normally not required unless you have a custom endpoint which has the same name as a standard (off the shelf) endpoint.

  • version (String) (defaults to: '') —

    For custom endpoints, version of the product

  • endpoint_name: (String) (defaults to: '')
  • account_name: (String) (defaults to: '')
  • version: (String) (defaults to: '')

Returns:



405
406
407
408
409
410
411
412
# File 'lib/mindee/v1/client.rb', line 405

def create_endpoint(endpoint_name: '', account_name: '', version: '')
  initialize_endpoint(
    Mindee::V1::Product::Universal::Universal,
    endpoint_name: endpoint_name,
    account_name: ,
    version: version
  )
end

#enqueue(input_source, product_class, endpoint: nil, options: {}) ⇒ Mindee::V1::Parsing::Common::ApiResponse

Enqueue a document for async parsing

Parameters:

  • input_source (Mindee::Input::Source::LocalInputSource, Mindee::Input::Source::URLInputSource) —

    The source of the input document (local file or URL).

  • product_class (Mindee::Inference) —

    The class of the product.

  • options (Hash) (defaults to: {}) —

    A hash of options to configure the enqueue behavior. Possible keys:

    • :endpoint [V1::HTTP::Endpoint, nil] Endpoint of the API. Doesn't need to be set in the case of OTS APIs.
    • :all_words [bool] Whether to extract all the words on each page. This performs a full OCR operation on the server and will increase response time.
    • :full_text [bool] Whether to include the full OCR text response in compatible APIs. This performs a full OCR operation on the server and may increase response time.
    • :close_file [bool] Whether to close() the file after parsing it. Set to false if you need to access the file after this operation.
    • :page_options [Hash, nil] Page cutting/merge options:
      - `:page_indexes` [Array<Integer>] Zero-based list of page indexes.
      - `:operation` [Symbol] Operation to apply on the document, given the `page_indexes` specified:
        - `:KEEP_ONLY` - keep only the specified pages, and remove all others.
        - `:REMOVE` - remove the specified pages, and keep all others.
      - `:on_min_pages` [Integer] Apply the operation only if the document has at least this many pages.
      
    • :cropper [bool] Whether to include cropper results for each page. This performs a cropping operation on the server and will increase response time.
    • :rag [bool] Whether to enable Retrieval-Augmented Generation. Only works if a Workflow ID is provided.
    • :workflow_id [String, nil] ID of the workflow to use.
  • endpoint (Mindee::V1::HTTP::Endpoint) (defaults to: nil) —

    Endpoint of the API.

  • (Input::Source::LocalInputSource, Input::Source::URLInputSource)
  • (singleton(V1::Parsing::Common::Inference))
  • endpoint: (V1::HTTP::Endpoint, nil) (defaults to: nil)
  • options: (ParseOptions, Hash[String | Symbol, untyped]) (defaults to: {})

Returns:



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/mindee/v1/client.rb', line 195

def enqueue(input_source, product_class, endpoint: nil, options: {})
  opts = normalize_parse_options(options)
  endpoint ||= initialize_endpoint(product_class)
  logger.debug("Enqueueing document as '#{endpoint.url_root}'")

  prediction, raw_http = endpoint.predict_async(
    input_source,
    opts
  )
  Mindee::V1::Parsing::Common::ApiResponse.new(product_class, prediction, raw_http)
end

#enqueue_and_parse(input_source, product_class, endpoint, options, cancellation_token = nil) ⇒ Mindee::V1::Parsing::Common::ApiResponse

rubocop:disable Metrics/CyclomaticComplexity Enqueue a document for async parsing and automatically try to retrieve it

rubocop:disable-next Metrics/PerceivedComplexity

Parameters:

  • input_source (Mindee::Input::Source::LocalInputSource, Mindee::Input::Source::URLInputSource) —

    The source of the input document (local file or URL).

  • product_class (Mindee::Inference) —

    The class of the product.

  • options (Hash) —

    A hash of options to configure the parsing behavior. Possible keys:

    • :endpoint [V1::HTTP::Endpoint, nil] Endpoint of the API. Doesn't need to be set in the case of OTS APIs.
    • :all_words [bool] Whether to extract all the words on each page. This performs a full OCR operation on the server and will increase response time.
    • :full_text [bool] Whether to include the full OCR text response in compatible APIs. This performs a full OCR operation on the server and may increase response time.
    • :close_file [bool] Whether to close() the file after parsing it. Set to false if you need to access the file after this operation.
    • :page_options [Hash, nil] Page cutting/merge options:
      - `:page_indexes` [Array<Integer>] Zero-based list of page indexes.
      - `:operation` [Symbol] Operation to apply on the document, given the `page_indexes` specified:
        - `:KEEP_ONLY` - keep only the specified pages, and remove all others.
        - `:REMOVE` - remove the specified pages, and keep all others.
      - `:on_min_pages` [Integer] Apply the operation only if the document has at least this many pages.
      
    • :cropper [bool, nil] Whether to include cropper results for each page. This performs a cropping operation on the server and will increase response time.
    • :rag [bool] Whether to enable Retrieval-Augmented Generation. Only works if a Workflow ID is provided.
    • :workflow_id [String, nil] ID of the workflow to use.
    • :initial_delay_sec [Numeric] Initial delay before polling. Defaults to 2.
    • :delay_sec [Numeric] Delay between polling attempts. Defaults to 1.5.
    • :max_retries [Integer] Maximum number of retries. Defaults to 80.
  • endpoint (Mindee::V1::HTTP::Endpoint) —

    Endpoint of the API.

  • cancellation_token (Mindee::HTTP::CancellationToken, nil) (defaults to: nil) —

    Token for cancellation.

  • (Input::Source::URLInputSource, Input::Source::LocalInputSource)
  • (singleton(V1::Parsing::Common::Inference))
  • (V1::HTTP::Endpoint)
  • (ParseOptions)

Returns:

Raises:



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/mindee/v1/client.rb', line 254

def enqueue_and_parse(
  input_source,
  product_class,
  endpoint,
  options,
  cancellation_token = nil
)
  validate_async_params(options.initial_delay_sec, options.delay_sec, options.max_retries)
  enqueue_res = enqueue(input_source, product_class, endpoint: endpoint, options: options)
  job = enqueue_res.job or raise Error::MindeeAPIError, 'Expected job to be present'
  job_id = job.id

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

  sleep(options.initial_delay_sec)
  polling_attempts = 1
  logger.debug("Successfully enqueued document with job id: '#{job_id}'")
  queue_res = parse_queued(job_id, product_class, endpoint: endpoint)
  queue_res_job = queue_res.job or raise Error::MindeeAPIError, 'Expected job to be present'
  valid_statuses = [
    Mindee::V1::Parsing::Common::JobStatus::WAITING,
    Mindee::V1::Parsing::Common::JobStatus::PROCESSING,
  ]
  # @type var valid_statuses: Array[(:waiting | :processing | :completed | :failed)]
  while valid_statuses.include?(queue_res_job.status) && polling_attempts < options.max_retries
    logger.debug("Polling server for parsing result with job id: '#{job_id}'. Attempt #{polling_attempts}")
    raise Mindee::Error::MindeeError, 'Enqueueing of the document was canceled.' if cancellation_token&.canceled?

    sleep(options.delay_sec)
    queue_res = parse_queued(job_id, product_class, endpoint: endpoint)
    queue_res_job = queue_res.job or raise Error::MindeeAPIError, 'Expected job to be present'
    polling_attempts += 1
  end

  if queue_res_job.status != Mindee::V1::Parsing::Common::JobStatus::COMPLETED
    elapsed = options.initial_delay_sec + (polling_attempts * options.delay_sec.to_f)
    raise Error::MindeeAPIError,
          "Asynchronous parsing request timed out after #{elapsed} seconds (#{polling_attempts} tries)"
  end

  queue_res
end

#execute_workflow(input_source, workflow_id, options: {}) ⇒ Mindee::V1::Parsing::Common::WorkflowResponse

rubocop:enable Metrics/CyclomaticComplexity Sends a document to a workflow.

Accepts options either as a Hash or as a WorkflowOptions struct.

requiring authentication.

  • page_options [Hash, nil] Page cutting/merge options:
    • :page_indexes Zero-based list of page indexes.
      • :operation Operation to apply on the document, given the `page_indexes specified:
        • :KEEP_ONLY - keep only the specified pages, and remove all others.
        • :REMOVE - remove the specified pages, and keep all others.
    • :on_min_pages Apply the operation only if document has at least this many pages.

Parameters:

Returns:



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/mindee/v1/client.rb', line 319

def execute_workflow(input_source, workflow_id, options: {})
  opts = options.is_a?(WorkflowOptions) ? options : WorkflowOptions.new(params: options)
  if opts.respond_to?(:page_options) && input_source.is_a?(Input::Source::LocalInputSource)
    process_pdf_if_required(input_source, opts)
  end

  workflow_endpoint = V1::HTTP::WorkflowEndpoint.new(workflow_id, api_key: @api_key.to_s)
  logger.debug("Sending document to workflow '#{workflow_id}'")

  prediction, raw_http = workflow_endpoint.execute_workflow(
    input_source,
    opts
  )

  Mindee::V1::Parsing::Common::WorkflowResponse.new(V1::Product::Universal::Universal, prediction, raw_http)
end

#fix_account_name(account_name) ⇒ String

Parameters:

  • (String)

Returns:

  • (String)


461
462
463
464
465
466
467
468
# File 'lib/mindee/v1/client.rb', line 461

def ()
  if .nil? || .empty?
    logger.info("No account name provided, #{OTS_OWNER} will be used by default.")
    return OTS_OWNER
  end

  
end

#fix_endpoint_name(product_class, endpoint_name) ⇒ String

Parameters:

Returns:

  • (String)


457
458
459
# File 'lib/mindee/v1/client.rb', line 457

def fix_endpoint_name(product_class, endpoint_name)
  endpoint_name.nil? || endpoint_name.empty? ? product_class.endpoint_name.to_s : endpoint_name.to_s
end

#fix_version(product_class, version) ⇒ String

Parameters:

Returns:

  • (String)


470
471
472
473
474
475
476
477
478
# File 'lib/mindee/v1/client.rb', line 470

def fix_version(product_class, version)
  return version unless version.nil? || version.empty?

  if product_class.endpoint_version.nil? || product_class.endpoint_version.to_s.empty?
    logger.debug('No version provided for a custom build, will attempt to poll version 1 by default.')
    return '1'
  end
  product_class.endpoint_version || ''
end

#initialize_endpoint(product_class, endpoint_name: '', account_name: '', version: '') ⇒ Mindee::V1::HTTP::Endpoint

Creates an endpoint with the given values. Raises an error if the endpoint is invalid.

Parameters:

  • product_class (Mindee::V1::Parsing::Common::Inference) —

    class of the product

  • endpoint_name (String) (defaults to: '') —

    For custom endpoints, the "API name" field in the "Settings" page of the API Builder. Do not set for standard (off the shelf) endpoints.

  • account_name (String) (defaults to: '') —

    For custom endpoints, your account or organization username on the API Builder. This is normally not required unless you have a custom endpoint which has the same name as a standard (off the shelf) endpoint.

  • version (String) (defaults to: '') —

    For custom endpoints, version of the product.

  • (singleton(V1::Parsing::Common::Inference))
  • endpoint_name: (String) (defaults to: '')
  • account_name: (String) (defaults to: '')
  • version: (String) (defaults to: '')

Returns:



445
446
447
448
449
450
451
452
453
454
455
# File 'lib/mindee/v1/client.rb', line 445

def initialize_endpoint(product_class, endpoint_name: '', account_name: '', version: '')
  if (endpoint_name.nil? || endpoint_name.empty?) && product_class == Mindee::V1::Product::Universal::Universal
    raise Mindee::Error::MindeeConfigurationError, 'Missing argument endpoint_name when using custom class'
  end

  endpoint_name = fix_endpoint_name(product_class, endpoint_name)
   = ()
  version = fix_version(product_class, version)

  V1::HTTP::Endpoint.new(, endpoint_name, version, api_key: @api_key.to_s)
end

#load_prediction(product_class, local_response) ⇒ Mindee::V1::Parsing::Common::ApiResponse

Load a prediction.

Parameters:

Returns:



341
342
343
344
345
346
347
348
349
350
# File 'lib/mindee/v1/client.rb', line 341

def load_prediction(product_class, local_response)
  raise Error::MindeeAPIError, 'Expected LocalResponse to not be nil.' if local_response.nil?

  response_hash = local_response.as_hash || {}
  raise Error::MindeeAPIError, 'Expected LocalResponse#as_hash to return a hash.' if response_hash.nil?

  Mindee::V1::Parsing::Common::ApiResponse.new(product_class, response_hash, response_hash.to_json)
rescue KeyError, Error::MindeeAPIError
  raise Error::MindeeInputError, 'No prediction found in local response.'
end

#logger ⇒ Logging

Returns:



39
# File 'sig/mindee/v1/client.rbs', line 39

def logger: () -> Logging

#normalize_parse_options(options) ⇒ ParseOptions

If needed, converts the parsing options provided as a hash into a proper ParseOptions object.

Parameters:

Returns:



483
484
485
486
487
# File 'lib/mindee/v1/client.rb', line 483

def normalize_parse_options(options)
  return options if options.is_a?(ParseOptions)

  ParseOptions.new(params: options)
end

#parse(input_source, product_class, endpoint: nil, options: {}, enqueue: true) ⇒ Mindee::V1::Parsing::Common::ApiResponse

Enqueue a document for parsing and automatically try to retrieve it if needed.

Accepts options either as a Hash or as a ParseOptions struct.

Parameters:

  • input_source (Mindee::Input::Source::LocalInputSource, Mindee::Input::Source::URLInputSource)
  • product_class (Mindee::Inference) —

    The class of the product.

  • endpoint (Mindee::V1::HTTP::Endpoint, nil) (defaults to: nil) —

    Endpoint of the API.

  • options (Hash) (defaults to: {}) —

    A hash of options to configure the parsing behavior. Possible keys:

    • :all_words [bool] Whether to extract all the words on each page. This performs a full OCR operation on the server and will increase response time.
    • :full_text [bool] Whether to include the full OCR text response in compatible APIs. This performs a full OCR operation on the server and may increase response time.
    • :close_file [bool] Whether to close() the file after parsing it. Set to false if you need to access the file after this operation.
    • :page_options [Hash, nil] Page cutting/merge options:
      - `:page_indexes` [Array<Integer>] Zero-based list of page indexes.
      - `:operation` [Symbol] Operation to apply on the document, given the `page_indexes` specified:
        - `:KEEP_ONLY` - keep only the specified pages, and remove all others.
        - `:REMOVE` - remove the specified pages, and keep all others.
      - `:on_min_pages` [Integer] Apply the operation only if the document has at least this many pages.
      
    • :cropper [bool, nil] Whether to include cropper results for each page. This performs a cropping operation on the server and will increase response time.
    • :initial_delay_sec [Numeric] Initial delay before polling. Defaults to 2.
    • :delay_sec [Numeric] Delay between polling attempts. Defaults to 1.5.
    • :max_retries [Integer] Maximum number of retries. Defaults to 80.
  • enqueue (bool) (defaults to: true) —

    Whether to enqueue the file.

  • (Input::Source::LocalInputSource, Input::Source::URLInputSource)
  • (singleton(V1::Parsing::Common::Inference))
  • endpoint: (V1::HTTP::Endpoint, nil) (defaults to: nil)
  • options: (ParseOptions, Hash[String | Symbol, untyped]) (defaults to: {})

Returns:



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/mindee/v1/client.rb', line 125

def parse(input_source, product_class, endpoint: nil, options: {}, enqueue: true)
  opts = normalize_parse_options(options)
  process_pdf_if_required(input_source, opts) if input_source.is_a?(Input::Source::LocalInputSource)
  endpoint ||= initialize_endpoint(product_class)

  if enqueue && product_class.has_async
    enqueue_and_parse(input_source, product_class, endpoint, opts)
  else
    parse_sync(input_source, product_class, endpoint, opts)
  end
end

#parse_queued(job_id, product_class, endpoint: nil) ⇒ Mindee::V1::Parsing::Common::ApiResponse

Parses a queued document

Doesn't need to be set in the case of OTS APIs.

Parameters:

Returns:



215
216
217
218
219
220
# File 'lib/mindee/v1/client.rb', line 215

def parse_queued(job_id, product_class, endpoint: nil)
  endpoint = initialize_endpoint(product_class) if endpoint.nil?
  logger.debug("Fetching queued document as '#{endpoint.url_root}'")
  prediction, raw_http = endpoint.parse_async(job_id)
  Mindee::V1::Parsing::Common::ApiResponse.new(product_class, prediction, raw_http)
end

#parse_sync(input_source, product_class, endpoint, options) ⇒ Mindee::V1::Parsing::Common::ApiResponse

Call prediction API on a document and parse the results.

Parameters:

  • input_source (Mindee::Input::Source::LocalInputSource, Mindee::Input::Source::URLInputSource)
  • product_class (Mindee::Inference) —

    class of the product

  • endpoint (Mindee::V1::HTTP::Endpoint, nil) —

    Endpoint of the API.

  • options (Hash) —

    A hash of options to configure the parsing behavior. Possible keys:

    • :all_words [bool] Whether to extract all the words on each page. This performs a full OCR operation on the server and will increase response time.
    • :full_text [bool] Whether to include the full OCR text response in compatible APIs. This performs a full OCR operation on the server and may increase response time.
    • :close_file [bool] Whether to close() the file after parsing it. Set to false if you need to access the file after this operation.
    • :page_options [Hash, nil] Page cutting/merge options:
      - `:page_indexes` [Array<Integer>] Zero-based list of page indexes.
      - `:operation` [Symbol] Operation to apply on the document, given the `page_indexes` specified:
        - `:KEEP_ONLY` - keep only the specified pages, and remove all others.
        - `:REMOVE` - remove the specified pages, and keep all others.
      - `:on_min_pages` [Integer] Apply the operation only if the document has at least this many pages.
      
    • :cropper [bool, nil] Whether to include cropper results for each page. This performs a cropping operation on the server and will increase response time.
  • (Input::Source::LocalInputSource, Input::Source::URLInputSource)
  • (singleton(V1::Parsing::Common::Inference))
  • (V1::HTTP::Endpoint)
  • (ParseOptions)

Returns:



158
159
160
161
162
163
164
165
166
167
# File 'lib/mindee/v1/client.rb', line 158

def parse_sync(input_source, product_class, endpoint, options)
  logger.debug("Parsing document as '#{endpoint.url_root}'")

  prediction, raw_http = endpoint.predict(
    input_source,
    options
  )

  Mindee::V1::Parsing::Common::ApiResponse.new(product_class, prediction, raw_http)
end

#process_pdf_if_required(input_source, opts) ⇒ void

This method returns an undefined value.

Processes a PDF if parameters were provided.



492
493
494
495
496
497
498
# File 'lib/mindee/v1/client.rb', line 492

def process_pdf_if_required(input_source, opts)
  return unless input_source.is_a?(Mindee::Input::Source::LocalInputSource) &&
                opts.page_options.on_min_pages &&
                input_source.pdf?

  input_source.process_pdf(opts.page_options)
end

#source_from_b64string(base64_string, filename, repair_pdf: false) ⇒ Mindee::Input::Source::Base64InputSource

Load a document from a base64 encoded string.

Parameters:

  • base64_string (String) —

    Input to parse as base64 string

  • filename (String) —

    The name of the file (without the path)

  • repair_pdf (bool) (defaults to: false) —

    Attempts to fix broken pdf if true

  • (String)
  • (String)
  • repair_pdf: (Boolean) (defaults to: false)

Returns:



374
375
376
# File 'lib/mindee/v1/client.rb', line 374

def source_from_b64string(base64_string, filename, repair_pdf: false)
  Input::Source::Base64InputSource.new(base64_string, filename, repair_pdf: repair_pdf)
end

#source_from_bytes(input_bytes, filename, repair_pdf: false) ⇒ Mindee::Input::Source::BytesInputSource

Load a document from raw bytes.

Parameters:

  • input_bytes (String) —

    Encoding::BINARY byte input

  • filename (String) —

    The name of the file (without the path)

  • repair_pdf (bool) (defaults to: false) —

    Attempts to fix broken pdf if true

  • (String)
  • (String)
  • repair_pdf: (Boolean) (defaults to: false)

Returns:



365
366
367
# File 'lib/mindee/v1/client.rb', line 365

def source_from_bytes(input_bytes, filename, repair_pdf: false)
  Input::Source::BytesInputSource.new(input_bytes, filename, repair_pdf: repair_pdf)
end

#source_from_file(input_file, filename, repair_pdf: false) ⇒ Mindee::Input::Source::FileInputSource

Load a document from a normal Ruby File.

Parameters:

  • input_file (File) —

    Input file handle

  • filename (String) —

    The name of the file (without the path)

  • repair_pdf (bool) (defaults to: false) —

    Attempts to fix broken pdf if true

  • (File)
  • (String)
  • repair_pdf: (Boolean) (defaults to: false)

Returns:



383
384
385
# File 'lib/mindee/v1/client.rb', line 383

def source_from_file(input_file, filename, repair_pdf: false)
  Input::Source::FileInputSource.new(input_file, filename, repair_pdf: repair_pdf)
end

#source_from_path(input_path, repair_pdf: false) ⇒ Mindee::Input::Source::PathInputSource

Load a document from an absolute path, as a string.

Parameters:

  • input_path (String) —

    Path of file to open

  • repair_pdf (bool) (defaults to: false) —

    Attempts to fix broken pdf if true

  • (String)
  • repair_pdf: (Boolean) (defaults to: false)

Returns:



356
357
358
# File 'lib/mindee/v1/client.rb', line 356

def source_from_path(input_path, repair_pdf: false)
  Input::Source::PathInputSource.new(input_path, repair_pdf: repair_pdf)
end

#source_from_url(url) ⇒ Mindee::Input::Source::URLInputSource

Load a document from a secure remote source (HTTPS).

Parameters:

  • url (String) —

    URL of the file

  • (String)

Returns:



390
391
392
# File 'lib/mindee/v1/client.rb', line 390

def source_from_url(url)
  Input::Source::URLInputSource.new(url)
end

#validate_async_params(initial_delay_sec, delay_sec, max_retries) ⇒ void

This method returns an undefined value.

Validates the parameters for async auto-polling

Parameters:

  • initial_delay_sec (Numeric) —

    initial delay before polling

  • delay_sec (Numeric) —

    delay between polling attempts

  • max_retries (Integer, nil) —

    maximum amount of retries.

  • (Integer, Float)
  • (Integer, Float)
  • (Integer)

Raises:

  • (ArgumentError)


418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/mindee/v1/client.rb', line 418

def validate_async_params(initial_delay_sec, delay_sec, max_retries)
  min_delay_sec = 1
  min_initial_delay_sec = 1
  min_retries = 2

  if delay_sec < min_delay_sec
    raise ArgumentError,
          "Cannot set auto-poll delay to less than #{min_delay_sec} second(s)"
  end
  if initial_delay_sec < min_initial_delay_sec
    raise ArgumentError,
          "Cannot set initial parsing delay to less than #{min_initial_delay_sec} second(s)"
  end
  raise ArgumentError, "Cannot set auto-poll retries to less than #{min_retries}" if max_retries < min_retries
end