ecoportal-api 0.10.5 → 0.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31b8d7b7f90a351a2335c73140458010669a42bf19538f0ec8a65df565881374
4
- data.tar.gz: 064e126df498599971d9c3ea999cba7b85eb303d944ea34738f17490c60c435b
3
+ metadata.gz: f7d949e504087bf41e30ba17c20425eea599e744aae98456ba943f4433dd819e
4
+ data.tar.gz: c382957de25cce7270caf6fa2615e731fc0aea94f67f43854ef339e97299461d
5
5
  SHA512:
6
- metadata.gz: b9020a08208cdc1da174e9addb03bb81e5d484fccc461f45e739edaf32a5a998e1b8508f8fdf3663282733352de1e025f2c4486ebda68109d153c4c1c8326d6b
7
- data.tar.gz: 2d4fa049b1cb31edcd28d783a50589922ce04560a2cab5e6a409abc7c3b21ffc807aad6a09a592013eb800b4e3c35e2ecc926d0f7c1e12a061df820e0dc1c37b
6
+ metadata.gz: e0a7e3401365dff83f9b40aa6e11a812540748075dbce2592feac30c656096baa331817ba70b010cce951b5ca22f0abc12832192a144deff41ddc5f1cd1fc8cc
7
+ data.tar.gz: 6ac12ae619ce68ea87819b020b2745f9de443abc90b3985f68123291e419d18e4286a6a3ba41debbe2586856fea1eab46aac572e835bc38e4a14e9bbd1813b7a
data/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.10.6] - 2024-10-xx
5
+ ## [0.10.8] - 2024-11-xx
6
6
 
7
7
  ### Added
8
8
 
@@ -10,6 +10,38 @@ All notable changes to this project will be documented in this file.
10
10
 
11
11
  ### Fixed
12
12
 
13
+ ## [0.10.7] - 2024-11-21
14
+
15
+ ### Changed
16
+
17
+ - Remove unused code
18
+ - `Ecoportal::API::Common::Client.new` the `version` argument doesn't default to `v1` but to `nil`.
19
+
20
+ ### Fixed
21
+
22
+ - A couple of errors
23
+ - Remove missing api key error log when no client `version` is specified.
24
+
25
+ ## [0.10.6] - 2024-10-28
26
+
27
+ ### Added
28
+
29
+ - `Ecoportal::API::V1::Job::Status`
30
+ - **added** methods `started?`, `progressing?` and `progress_increase`
31
+ - `Ecoportal::API::Errors::StartTimeOut` specificity on timeout error.
32
+
33
+ ### Changed
34
+
35
+ - Refactored `Ecoportal::API::V1::Awaiter` into:
36
+ - `Awaiter::Timer`: account for last waited, and manage time outs
37
+ - `Awaiter::StatusFrequency`: next awaiting time
38
+
39
+ ### Fixed
40
+
41
+ - `Portal::API::V1::Awaiter`
42
+ - `throughput` based on progress during last wait
43
+ - this helps to better detect non progressing jobs
44
+
13
45
  ## [0.10.5] - 2024-10-16
14
46
 
15
47
  ### Fixed
@@ -61,10 +61,10 @@ module Ecoportal
61
61
  }.tap do |options|
62
62
  # next unless false
63
63
 
64
- options.merge!({
65
- log_level: Logger::DEBUG,
66
- log_path: File.join(__dir__, "elastic_apm.log")
67
- })
64
+ # options.merge!({
65
+ # log_level: ::Logger::DEBUG,
66
+ # log_path: File.join('log', "elastic_apm.log")
67
+ # })
68
68
  end
69
69
  end
70
70
 
@@ -28,7 +28,7 @@ module Ecoportal
28
28
  msg = "Received non json body in response "
29
29
  msg << "(#{response.src_body.class}):\n "
30
30
  msg << response.src_body
31
- puts
31
+ puts msg
32
32
  end
33
33
  end
34
34
  end
@@ -47,7 +47,7 @@ module Ecoportal
47
47
  log_unexpected_server_error(response)
48
48
 
49
49
  msg = "Got server error (#{response.status}): #{response.body}\n"
50
- msg << "Going to retry (##{i} of #{attempts})"
50
+ msg << "Going to retry (##{i + 1} of #{attempts})"
51
51
  log(:debug) { msg }
52
52
 
53
53
  sleep(delay) if i < attempts
@@ -39,7 +39,7 @@ module Ecoportal
39
39
  # @param logger [Logger] an object with `Logger` interface to generate logs.
40
40
  # @param deep_logging [Boolean] whether or not batch responses should be logged
41
41
  # @return [Client] an object that holds the configuration of the api connection.
42
- def initialize(api_key:, version: "v1", host: DEFAULT_HOST, logger: nil, deep_logging: false)
42
+ def initialize(api_key:, version: nil, host: DEFAULT_HOST, logger: nil, deep_logging: false)
43
43
  @version = version
44
44
  @api_key = api_key
45
45
  @logger = logger
@@ -59,7 +59,9 @@ module Ecoportal
59
59
  end
60
60
 
61
61
  return unless @api_key.nil? || @api_key.match(/\A\W*\z/)
62
+ return unless version
62
63
 
64
+ byebug
63
65
  log(:error) { "Api-key missing!" }
64
66
  end
65
67
 
@@ -2,7 +2,6 @@ module Ecoportal
2
2
  module API
3
3
  module Common
4
4
  module DocHelpers
5
-
6
5
  def get_body(doc)
7
6
  if doc.respond_to?(:as_update)
8
7
  doc.as_update
@@ -3,6 +3,9 @@ module Ecoportal
3
3
  module Errors
4
4
  class TimeOut < Errors::Base
5
5
  end
6
+
7
+ class StartTimeOut < TimeOut
8
+ end
6
9
  end
7
10
  end
8
11
  end
@@ -0,0 +1,49 @@
1
+ module Ecoportal
2
+ module API
3
+ class V1
4
+ class Job
5
+ class Awaiter
6
+ module StatusFrequency
7
+ include Common::Client::TimeOut
8
+
9
+ DELAY_STATUS_CHECK = 4
10
+ MIN_STATUS_CHECK = 2
11
+
12
+ private
13
+
14
+ def status_checked?
15
+ @status_checked || false
16
+ end
17
+
18
+ def status_check_in(pending, timeout_in:)
19
+ # first check to be faster
20
+ unless status_checked?
21
+ @status_checked = true unless pending.zero? # <= increases job status requests!
22
+ return min_delay_status_check
23
+ end
24
+
25
+ return default_delay_status_check if around_min_throughput?
26
+
27
+ eta = eta_for(pending, approach: :optimistic)
28
+ check_in_max = [eta, timeout_in].min * 0.90
29
+
30
+ check_in_best = check_in_max / 2.0
31
+ default_5 = default_delay_status_check * 5
32
+
33
+ top_check = [default_5, check_in_best].min.ceil
34
+ [top_check, min_delay_status_check].max
35
+ end
36
+
37
+ def default_delay_status_check
38
+ self.class::DELAY_STATUS_CHECK
39
+ end
40
+
41
+ def min_delay_status_check
42
+ self.class::MIN_STATUS_CHECK
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,158 @@
1
+ module Ecoportal
2
+ module API
3
+ class V1
4
+ class Job
5
+ class Awaiter
6
+ TIMER_ARGS = %i[
7
+ total timeout
8
+ start last ldelay
9
+ status lstatus
10
+ ].freeze
11
+
12
+ Timer = Struct.new(*TIMER_ARGS) do
13
+ self::MAX_START_DELAY = 60
14
+
15
+ attr_reader :timestamp
16
+
17
+ def initialize(...)
18
+ @timestamp = Time.now
19
+ super
20
+ yield(self) if block_given?
21
+ end
22
+
23
+ alias_method :original_start, :start
24
+ # `start` time only counts from the moment that
25
+ # it already started to progress
26
+ def start
27
+ return timestamp unless lstatus&.started?
28
+
29
+ original_start || timestamp
30
+ end
31
+
32
+ alias_method :original_last, :last
33
+ def last
34
+ original_last || start
35
+ end
36
+
37
+ def new(**kargs, &block)
38
+ self.class.new(**new_kargs.merge(kargs), &block)
39
+ end
40
+
41
+ def job_id
42
+ status.id
43
+ end
44
+
45
+ def complete?
46
+ status.complete?(total)
47
+ end
48
+
49
+ def started?
50
+ status.started?
51
+ end
52
+
53
+ def pending
54
+ status.pending(total)
55
+ end
56
+
57
+ def progress
58
+ status.progress
59
+ end
60
+
61
+ def increased
62
+ status.progress_increase(lstatus)
63
+ end
64
+
65
+ def waited
66
+ timestamp - start
67
+ end
68
+
69
+ def lwaited
70
+ timestamp - last
71
+ end
72
+
73
+ def net_waited
74
+ last_delay = ldelay || 0
75
+ waited - (last_delay / 2)
76
+ end
77
+
78
+ def time_left
79
+ (timeout - waited).round(2)
80
+ end
81
+
82
+ def time_left_to_start
83
+ return max_start_delay if started?
84
+
85
+ (max_start_delay - waited).round(2)
86
+ end
87
+
88
+ def timeout_in
89
+ return time_left if started?
90
+
91
+ time_left_to_start
92
+ end
93
+
94
+ # timeout library is evil. So we make poor-man timeout.
95
+ # https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
96
+ def timeout?
97
+ !time_left.positive?
98
+ end
99
+
100
+ def start_timeout?
101
+ return false if status.started?
102
+
103
+ !time_left_to_start.positive?
104
+ end
105
+
106
+ def on_timeout!(&block)
107
+ return start_timeout!(&block) if start_timeout?
108
+ return timeout!(&block) if timeout?
109
+
110
+ false
111
+ end
112
+
113
+ def timeout!
114
+ return false unless timeout?
115
+
116
+ yield(self)
117
+
118
+ msg = "Job '#{job_id}' not complete (size: #{total}).\n"
119
+ msg << " Timed out after #{timeout} seconds.\n"
120
+ msg << " Current status: #{status}"
121
+
122
+ raise API::Errors::TimeOut, msg
123
+ end
124
+
125
+ def start_timeout!
126
+ return false unless start_timeout?
127
+
128
+ yield(self)
129
+
130
+ msg = "Job '#{job_id}' not started (size: #{total}).\n"
131
+ msg << " Start timed out after #{max_start_delay} seconds.\n"
132
+ msg << " Current status: #{status}"
133
+
134
+ raise API::Errors::StartTimeOut, msg
135
+ end
136
+
137
+ protected
138
+
139
+ def new_kargs
140
+ %i[total timeout start].each_with_object({}) do |key, kargs|
141
+ kargs[key] = send(key)
142
+ end.tap do |kargs|
143
+ kargs[:start] = timestamp
144
+ kargs[:lstatus] = status
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def max_start_delay
151
+ self.class::MAX_START_DELAY
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -1,64 +1,77 @@
1
+ require_relative 'awaiter/timer'
2
+ require_relative 'awaiter/status_frequency'
3
+
1
4
  module Ecoportal
2
5
  module API
3
6
  class V1
4
7
  class Job
5
8
  class Awaiter
6
9
  include Common::Client::TimeOut
7
-
8
- DELAY_STATUS_CHECK = 4
9
- MIN_STATUS_CHECK = 2
10
+ include StatusFrequency
10
11
 
11
12
  TIMEOUT_APPROACH = :min # :conservative # adaptative timeout
12
13
  TIMEOUT_FALLBACK = :min
13
14
 
14
- attr_reader :job, :job_id, :total
15
- attr_accessor :timeout_approach
15
+ attr_reader :job_api, :job_id, :total
16
16
 
17
- def initialize(job, job_id:, total:)
18
- @job = job
17
+ def initialize(job_api, job_id:, total:)
18
+ @job_api = job_api
19
19
  @job_id = job_id
20
20
  @total = total
21
- @checked = false
22
- self.timeout_approach = self.class::TIMEOUT_APPROACH
21
+ end
22
+
23
+ attr_writer :timeout_approach
24
+
25
+ def timeout_approach
26
+ @timeout_approach ||= self.class::TIMEOUT_APPROACH
23
27
  end
24
28
 
25
29
  # Allows to preserve the learned throughput
26
30
  def new(**kargs)
27
- self.class.new(job, **kargs).tap do |out|
28
- out.throughput = throughput
31
+ self.class.new(job_api, **kargs).tap do |out|
32
+ out.throughput = throughput
33
+ out.timeout_approach = timeout_approach
29
34
  end
30
35
  end
31
36
 
32
37
  def await_completion! # rubocop:disable Metrics/AbcSize
33
- max_timeout = timeout_for(total, approach: timeout_approach)
38
+ timeout = timeout_for(total, approach: timeout_approach)
39
+
40
+ first = 1
41
+ timer = Timer.new(
42
+ total: total,
43
+ timeout: timeout,
44
+ ldelay: first
45
+ )
34
46
 
35
- # timeout library is evil. So we make poor-man timeout.
36
- # https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
37
- before = Time.now
38
47
  delay_status_check = nil
39
48
 
40
49
  loop do
41
- status = job.status(job_id)
42
- done = status.progress
43
- waited = Time.now - before
50
+ sleep(first.tap {first = nil}) if first
44
51
 
45
- adapted = waited
46
- adapted = waited - (delay_status_check / 2) if delay_status_check
47
- ratio = throughput!(adapted, count: done)
52
+ timer = timer.new(
53
+ status: job_api.status(job_id),
54
+ ldelay: delay_status_check
55
+ )
48
56
 
49
- break status if status.complete?(total)
57
+ # ratio = throughput!(timer.net_waited, count: timer.progress)
58
+ ratio = throughput!(timer.lwaited, count: timer.increased)
50
59
 
51
- pending = status.pending(total)
52
- left = max_timeout - waited
60
+ break timer.status if timer.complete?
53
61
 
54
- timeout!(status, timeout: max_timeout) unless left.positive?
62
+ timer.on_timeout! do
63
+ @timeout_approach = self.class::TIMEOUT_FALLBACK
64
+ end
55
65
 
56
- delay_status_check = status_check_in(pending, timeout_in: left)
66
+ delay_status_check = status_check_in(
67
+ timer.pending,
68
+ timeout_in: timer.timeout_in
69
+ )
57
70
 
58
71
  msg = " ... Awaiting #{delay_status_check} s. -- "
59
- msg << " TimeOut: #{left.round(2)} s. "
72
+ msg << " TimeOut: #{timer.time_left} s. "
60
73
  msg << "(job '#{job_id}') "
61
- msg << "Done: #{done} (est. #{ratio} rec/s) "
74
+ msg << "Done: #{timer.progress} (est. #{ratio.round(2)} rec/s) "
62
75
  msg << " \r"
63
76
 
64
77
  print msg
@@ -67,48 +80,6 @@ module Ecoportal
67
80
  sleep(delay_status_check)
68
81
  end
69
82
  end
70
-
71
- private
72
-
73
- def timeout!(status, timeout:)
74
- self.timeout_approach = self.class::TIMEOUT_FALLBACK
75
-
76
- msg = "Job '#{job_id}' not complete (size: #{total}).\n"
77
- msg << " Timed out after #{timeout} seconds.\n"
78
- msg << " Current status: #{status}"
79
-
80
- raise API::Errors::TimeOut, msg
81
- end
82
-
83
- def checked?
84
- @checked
85
- end
86
-
87
- def status_check_in(pending, timeout_in:)
88
- unless checked?
89
- @checked = true
90
- return min_delay_status_check
91
- end
92
-
93
- return default_delay_status_check if around_min_throughput?
94
-
95
- eta = eta_for(pending, approach: :optimistic)
96
- check_in_max = [eta, timeout_in].min * 0.90
97
-
98
- check_in_best = check_in_max / 2.0
99
- default_5 = default_delay_status_check * 5
100
-
101
- top_check = [default_5, check_in_best].min.ceil
102
- [top_check, min_delay_status_check].max
103
- end
104
-
105
- def default_delay_status_check
106
- self.class::DELAY_STATUS_CHECK
107
- end
108
-
109
- def min_delay_status_check
110
- self.class::MIN_STATUS_CHECK
111
- end
112
83
  end
113
84
  end
114
85
  end
@@ -9,7 +9,15 @@ module Ecoportal
9
9
  @id = id
10
10
  @complete = complete
11
11
  @errored = errored
12
- @progress = progress
12
+ @progress = progress || 0
13
+ end
14
+
15
+ def started?
16
+ progress.positive?
17
+ end
18
+
19
+ def progressing?(prev)
20
+ progress_increase(prev).positive?
13
21
  end
14
22
 
15
23
  def complete?(total = nil)
@@ -25,6 +33,12 @@ module Ecoportal
25
33
  total - progress
26
34
  end
27
35
 
36
+ def progress_increase(prev = 0)
37
+ prev = prev.progress if prev.is_a?(self.class)
38
+ prev ||= 0
39
+ progress - prev
40
+ end
41
+
28
42
  def errored?
29
43
  @errored
30
44
  end
@@ -1,5 +1,5 @@
1
1
  module Ecoportal
2
2
  module API
3
- VERSION = '0.10.5'.freeze
3
+ VERSION = '0.10.7'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecoportal-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.5
4
+ version: 0.10.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tapio Saarinen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-15 00:00:00.000000000 Z
11
+ date: 2024-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -258,6 +258,8 @@ files:
258
258
  - lib/ecoportal/api/v1.rb
259
259
  - lib/ecoportal/api/v1/job.rb
260
260
  - lib/ecoportal/api/v1/job/awaiter.rb
261
+ - lib/ecoportal/api/v1/job/awaiter/status_frequency.rb
262
+ - lib/ecoportal/api/v1/job/awaiter/timer.rb
261
263
  - lib/ecoportal/api/v1/job/status.rb
262
264
  - lib/ecoportal/api/v1/people.rb
263
265
  - lib/ecoportal/api/v1/person.rb
@@ -287,7 +289,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
287
289
  - !ruby/object:Gem::Version
288
290
  version: '0'
289
291
  requirements: []
290
- rubygems_version: 3.5.18
292
+ rubygems_version: 3.5.23
291
293
  signing_key:
292
294
  specification_version: 4
293
295
  summary: A collection of helpers for interacting with the ecoPortal MS's various APIs