ask-graph 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99d66a3b6b63d97a13187b67bbefd25c291d5ab80a63146e61639a0b7949689d
4
- data.tar.gz: ab66b1d0586ee8570e71f2612938d6d87a9e3c5a3d44b93b046ef95343a5951a
3
+ metadata.gz: 8035b4d2c2a93881c2733cb41e33696fe6e7403b2233a709c89ac75e1035323a
4
+ data.tar.gz: 04c96e95ec9b0afa00133f100627250ae26dc87d9208be62b22246f80548eaa7
5
5
  SHA512:
6
- metadata.gz: 8cd5ca7104c5ea6f643e9f9333ec8a7578b53f0fcda6c0f8a5a4273ac9cbb856025024e192a9f89c4a30fc3c8072b6b2b46283ccdf3805db05faade627fef81c
7
- data.tar.gz: f8f4e2494be0350b17b86f568c1de1428a1731b73f9599132f6fe408a70fed15649c803847cf01a36ba009b819f2bcfaf3f37df6d8db2a8dd36dface2c8ae01b
6
+ metadata.gz: 3a365f235bb392a4be69981dd816bc6f961ea68a1fe740e094fe07a4b1a5f7f39839261ba75784b5028a6a82b7add2351e9f10c97695a7a9edeb00b512cf4d67
7
+ data.tar.gz: fabd27780966a9877e4a17b014388fcad7c3c57fdfbe9df8bc433ba15d05960fe3542f09c6f9501e388d147197a5d84c85964b59646f3b245a44cdb03ba13a6d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,46 @@
1
+ ## [0.7.0] — 2026-07-31
2
+
3
+ ### Added
4
+
5
+ - **`workflow_timeout`** — total runtime cap for the entire workflow. The
6
+ whole run aborts with `Ask::Graph::WorkflowTimeout` if it exceeds the
7
+ limit, regardless of individual step timeouts.
8
+
9
+ ```ruby
10
+ class MyWorkflow < Ask::Graph
11
+ workflow_timeout 60 # whole workflow must finish within 60s
12
+ step FetchData, timeout: 30
13
+ step ProcessData, timeout: 30
14
+ end
15
+ ```
16
+
17
+ - **`Ask::Graph.default_workflow_timeout`** — global default total cap for
18
+ all graphs that don't set their own.
19
+
20
+ ### Changed
21
+
22
+ - **`timeout` renamed to `step_timeout`** — the class-level default timeout
23
+ per step is now `step_timeout`. The inline step option `step X, timeout: 10`
24
+ is unchanged. No alias is kept — the gem is pre-1.0.
25
+
26
+ ```ruby
27
+ # Before
28
+ class MyWorkflow < Ask::Graph
29
+ timeout 30
30
+ end
31
+ Ask::Graph.timeout 30
32
+
33
+ # After
34
+ class MyWorkflow < Ask::Graph
35
+ step_timeout 30
36
+ end
37
+ Ask::Graph.default_step_timeout 30
38
+ ```
39
+
40
+ ### Tested
41
+
42
+ - 95 tests, 129 assertions, 0 failures
43
+
1
44
  ## [0.6.1] — 2026-07-27
2
45
 
3
46
  ### Fixed
@@ -11,6 +11,9 @@ module Ask
11
11
  # Error raised when a step times out.
12
12
  class StepTimeout < StandardError; end
13
13
 
14
+ # Error raised when the entire workflow exceeds its total runtime cap.
15
+ class WorkflowTimeout < StandardError; end
16
+
14
17
  # Error raised when a step pauses for human approval.
15
18
  class Paused < StandardError; end
16
19
 
@@ -24,39 +27,30 @@ module Ask
24
27
  # @param storage [#set, #get, #delete] key-value store for checkpoint data
25
28
  # @param hooks [Hash] lifecycle hook method names
26
29
  # @param graph_instance [Object] the graph instance (for hooks)
27
- # @param default_timeout [Integer, nil] fallback timeout for steps with no explicit timeout
30
+ # @param step_timeout [Integer, nil] fallback timeout for steps with no explicit timeout
31
+ # @param workflow_timeout [Integer, nil] total runtime cap for the whole run
28
32
  def initialize(declarations, storage: nil,
29
33
  hooks: { before_step: [], after_step: [], on_failure: [] },
30
34
  graph_instance: nil,
31
- default_timeout: nil)
35
+ step_timeout: nil,
36
+ workflow_timeout: nil)
32
37
  @declarations = declarations
33
38
  @store = storage
34
39
  @hooks = hooks
35
40
  @graph = graph_instance
36
- @default_timeout = default_timeout
41
+ @step_timeout = step_timeout
42
+ @workflow_timeout = workflow_timeout
37
43
  @completed_steps = []
38
44
  end
39
45
 
40
46
  def run(context)
41
- resume_index = load_checkpoint(context)
42
-
43
- @declarations.each_with_index do |decl, idx|
44
- next if resume_index && idx <= resume_index
45
-
46
- unless condition_met?(decl, context)
47
- record_completion(idx, :skipped)
48
- next
49
- end
50
-
51
- run_hooks(:before_step, decl, context)
52
- execute_with_retry(decl, context)
53
- run_hooks(:after_step, decl, context)
54
-
55
- record_completion(idx, :completed)
56
- save_checkpoint(context, idx)
47
+ if @workflow_timeout
48
+ ::Timeout.timeout(@workflow_timeout) { run_steps(context) }
49
+ else
50
+ run_steps(context)
57
51
  end
58
-
59
- context
52
+ rescue ::Timeout::Error
53
+ raise WorkflowTimeout, "workflow timed out after #{@workflow_timeout}s"
60
54
  end
61
55
 
62
56
  def resume(context, input:)
@@ -77,6 +71,28 @@ module Ask
77
71
 
78
72
  private
79
73
 
74
+ def run_steps(context)
75
+ resume_index = load_checkpoint(context)
76
+
77
+ @declarations.each_with_index do |decl, idx|
78
+ next if resume_index && idx <= resume_index
79
+
80
+ unless condition_met?(decl, context)
81
+ record_completion(idx, :skipped)
82
+ next
83
+ end
84
+
85
+ run_hooks(:before_step, decl, context)
86
+ execute_with_retry(decl, context)
87
+ run_hooks(:after_step, decl, context)
88
+
89
+ record_completion(idx, :completed)
90
+ save_checkpoint(context, idx)
91
+ end
92
+
93
+ context
94
+ end
95
+
80
96
  def execute_with_retry(decl, context)
81
97
  retries = decl[:retry] || 0
82
98
 
@@ -93,7 +109,7 @@ module Ask
93
109
  end
94
110
 
95
111
  def run_with_timeout(decl, context, timeout_value)
96
- timeout_value ||= @default_timeout
112
+ timeout_value ||= @step_timeout
97
113
  if timeout_value
98
114
  ::Timeout.timeout(timeout_value) { execute_step(decl, context) }
99
115
  else
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  class Graph
5
- VERSION = "0.6.1"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
data/lib/ask/graph.rb CHANGED
@@ -73,36 +73,68 @@ module Ask
73
73
  lifecycle_hooks.transform_values(&:dup))
74
74
  end
75
75
 
76
- # --- Timeout ---
76
+ # --- Timeouts ---
77
77
 
78
- # Set or get the default timeout (in seconds) for steps in this graph.
79
- # Steps without an explicit +timeout:+ option will inherit this value.
78
+ # Set or get the default timeout (in seconds) for each step in this graph.
79
+ # Steps without an explicit +timeout:+ option inherit this value.
80
80
  # Setting on {Ask::Graph} itself applies to all graphs that don't set
81
81
  # their own default — a global default.
82
82
  #
83
83
  # @example Global default for all graphs
84
- # Ask::Graph.timeout 30
84
+ # Ask::Graph.default_step_timeout 30
85
85
  #
86
86
  # @example Per-graph override
87
87
  # class MyGraph < Ask::Graph
88
- # timeout 15
88
+ # step_timeout 15
89
89
  # step FastOp # uses 15s
90
90
  # step SlowOp, timeout: 60 # overrides
91
91
  # end
92
92
  #
93
93
  # @param seconds [Integer, nil] timeout in seconds, or nil to clear
94
- # @return [Integer, nil] the current default timeout for this graph
95
- def timeout(seconds = :not_set)
94
+ # @return [Integer, nil] the current default step timeout for this graph
95
+ def step_timeout(seconds = :not_set)
96
96
  if seconds == :not_set
97
- if instance_variable_defined?(:@timeout)
98
- @timeout
99
- elsif superclass.respond_to?(:timeout)
100
- superclass.timeout
97
+ if instance_variable_defined?(:@step_timeout)
98
+ @step_timeout
99
+ elsif superclass.respond_to?(:step_timeout)
100
+ superclass.step_timeout
101
101
  end
102
102
  else
103
- @timeout = seconds
103
+ @step_timeout = seconds
104
104
  end
105
105
  end
106
+ alias default_step_timeout step_timeout
107
+
108
+ # Set or get the total runtime cap (in seconds) for the entire graph.
109
+ # The whole workflow aborts with {Ask::Graph::WorkflowTimeout} if it
110
+ # exceeds this limit, regardless of individual step timeouts.
111
+ # Setting on {Ask::Graph} itself applies to all graphs that don't set
112
+ # their own default — a global default.
113
+ #
114
+ # @example Global default for all graphs
115
+ # Ask::Graph.default_workflow_timeout 60
116
+ #
117
+ # @example Per-graph override
118
+ # class MyGraph < Ask::Graph
119
+ # workflow_timeout 30
120
+ # step FetchData, timeout: 10
121
+ # step ProcessData, timeout: 10
122
+ # end
123
+ #
124
+ # @param seconds [Integer, nil] timeout in seconds, or nil to clear
125
+ # @return [Integer, nil] the current workflow timeout for this graph
126
+ def workflow_timeout(seconds = :not_set)
127
+ if seconds == :not_set
128
+ if instance_variable_defined?(:@workflow_timeout)
129
+ @workflow_timeout
130
+ elsif superclass.respond_to?(:workflow_timeout)
131
+ superclass.workflow_timeout
132
+ end
133
+ else
134
+ @workflow_timeout = seconds
135
+ end
136
+ end
137
+ alias default_workflow_timeout workflow_timeout
106
138
 
107
139
  # --- Storage ---
108
140
 
@@ -251,7 +283,8 @@ module Ask
251
283
  storage: store,
252
284
  hooks: hooks,
253
285
  graph_instance: self,
254
- default_timeout: self.class.timeout)
286
+ step_timeout: self.class.step_timeout,
287
+ workflow_timeout: self.class.workflow_timeout)
255
288
  @context = nil
256
289
  end
257
290
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto