attr-gather 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07ef681505341f374e4374d1baf4b5a8630a34b5dc87dc4fcaec159b2a7efeb2
4
- data.tar.gz: 680bc4891c2a140b6af5e780d97dba80e7ceb5aa750f8a0fe88dffcf7327c9f5
3
+ metadata.gz: ee9b30fd44bd8931964b53b904815224fa6a5a592ac1f22d19605a982a326d42
4
+ data.tar.gz: 7954a71a61dc89266358909ce189312b3b3164e4092d62ae4fae973712efca9d
5
5
  SHA512:
6
- metadata.gz: 4a634990d091aff5b530c06341e2cb2fdbad34cbec9f4b4dee073245822f45633fab79cbb8c4b76ee95dcda62d56a5b1ec856ac4b7500d04f9478a548664e070
7
- data.tar.gz: 6c1dddb4c8478d28ced0677c2b1b4b86343404f645721dde7af77ed9cdc44e880f1ece2fd3a271b26ed85b2325b7b8d4c5ce6cffaa5e1e9b4c2b589c0430dfb7
6
+ metadata.gz: 040b490aba781f8f851b617a365d2aea061f9d2afb189bcdf4e0e086effebc2135109a648b0f116d4be7000cbe4d3e10e369fadcb38d9e2910114c36957cce68
7
+ data.tar.gz: 59a93d0d6e16f9b2433822c4faf7ef4cda25f3e9e37da8a5fae816e313e1d6e0b86906a1d652b3c64125eae888607c9117ade38fc17ff11b84c361add21f4c62
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- attr-gather (1.1.3)
4
+ attr-gather (1.2.0)
5
5
  dry-container (~> 0.7)
6
6
 
7
7
  GEM
@@ -19,22 +19,20 @@ module Attr
19
19
  @filter = opts.delete(:filter) || NOOP_FILTER
20
20
  end
21
21
 
22
+ def with(**opts)
23
+ self.class.new(filter: @filter, **opts)
24
+ end
25
+
22
26
  def call(_original_input, _results_array)
23
27
  raise NotImplementedError
24
28
  end
25
29
 
26
30
  private
27
31
 
28
- def wrap_result(result)
29
- Concurrent::Promise.fulfill(result)
30
- end
31
-
32
32
  def unwrap_result(res)
33
- unvalidated = res.result.value!
34
-
35
- return unvalidated if filter.nil?
33
+ return res if filter.nil?
36
34
 
37
- filter.call(unvalidated).value
35
+ filter.call(res).value
38
36
  end
39
37
  end
40
38
  end
@@ -12,12 +12,10 @@ module Attr
12
12
  # Initialize a new DeepMerge aggregator
13
13
  #
14
14
  # @param reverse [Boolean] deep merge results in reverse order
15
- # @param merge_input [Boolean] include input in aggregation result
16
15
  #
17
16
  # @api private
18
- def initialize(reverse: false, merge_input: true, **)
17
+ def initialize(reverse: false, **)
19
18
  @reverse = reverse
20
- @merge_input = merge_input
21
19
  super
22
20
  end
23
21
 
@@ -25,27 +23,21 @@ module Attr
25
23
  execution_results = execution_results.reverse_each if reverse?
26
24
  initial = unwrap_initial_input(input)
27
25
 
28
- result = execution_results.reduce(initial) do |memo, res|
26
+ execution_results.reduce(initial) do |memo, res|
29
27
  deep_merge(memo, unwrap_result(res))
30
28
  end
31
-
32
- wrap_result(result)
33
29
  end
34
30
 
35
31
  private
36
32
 
37
33
  def unwrap_initial_input(input)
38
- merge_input? ? filter.call(input.dup).value : {}
34
+ input
39
35
  end
40
36
 
41
37
  def reverse?
42
38
  @reverse
43
39
  end
44
40
 
45
- def merge_input?
46
- @merge_input
47
- end
48
-
49
41
  def deep_merge(hash, other)
50
42
  Hash[hash].merge(other) do |_, orig, new|
51
43
  if orig.respond_to?(:to_hash) && new.respond_to?(:to_hash)
@@ -12,39 +12,26 @@ module Attr
12
12
  # Initialize a new DeepMerge aggregator
13
13
  #
14
14
  # @param reverse [Boolean] merge results in reverse order
15
- # @param merge_input [Boolean] include input in aggregation result
16
15
  #
17
16
  # @api private
18
- def initialize(reverse: false, merge_input: true, **)
17
+ def initialize(reverse: false, **)
19
18
  @reverse = reverse
20
- @merge_input = merge_input
21
19
  super
22
20
  end
23
21
 
24
22
  def call(input, execution_results)
25
23
  execution_results = execution_results.reverse_each if reverse?
26
- initial = unwrap_initial_input(input)
27
24
 
28
- result = execution_results.reduce(initial) do |memo, res|
25
+ execution_results.reduce(input) do |memo, res|
29
26
  memo.merge(unwrap_result(res))
30
27
  end
31
-
32
- wrap_result(result)
33
28
  end
34
29
 
35
30
  private
36
31
 
37
- def unwrap_initial_input(input)
38
- merge_input? ? filter.call(input.dup).value : {}
39
- end
40
-
41
32
  def reverse?
42
33
  @reverse
43
34
  end
44
-
45
- def merge_input?
46
- @merge_input
47
- end
48
35
  end
49
36
  end
50
37
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Attr
4
4
  module Gather
5
- VERSION = '1.1.3'
5
+ VERSION = '1.2.0'
6
6
  end
7
7
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'attr/gather/workflow/task_executor'
4
- require 'attr/gather/workflow/async_task_executor'
5
-
6
3
  module Attr
7
4
  module Gather
8
5
  module Workflow
@@ -21,42 +18,39 @@ module Attr
21
18
  #
22
19
  # @param input [Hash]
23
20
  #
24
- # @return [Concurrent::Promise]
21
+ # @return [Concurrent::Promise<Hash>]
25
22
  #
26
23
  # @note For more information, check out {https://dry-rb.org/gems/dry-monads/1.0/result}
27
24
  #
28
25
  # @api public
29
26
  def call(input)
30
- final_results = []
27
+ task_promises = {}
31
28
 
32
- each_task_batch.reduce(input.dup) do |aggregated_input, batch|
33
- executor_results = execute_batch(aggregated_input, batch)
34
- final_results << executor_results
35
- aggregator.call(aggregated_input, executor_results).value!
29
+ final_results = self.class.tasks.to_a.map do |task|
30
+ task_promises[task] = execute_task(input, task, task_promises)
36
31
  end
37
32
 
38
- aggregator.call(input.dup, final_results.flatten(1))
33
+ Concurrent::Promise.zip(*final_results).then do |results|
34
+ aggregator.call(input, results)
35
+ end
39
36
  end
40
37
 
41
38
  private
42
39
 
43
- # Enumator for task batches
44
- #
45
- # @return [Enumerator]
46
- #
47
- # @api private
48
- def each_task_batch
49
- self.class.tasks.each_batch
50
- end
51
-
52
40
  # Executes a batch of tasks
53
41
  #
54
42
  # @return [Array<TaskExecutionResult>]
55
43
  #
56
44
  # @api private
57
- def execute_batch(aggregated_input, batch)
58
- executor = AsyncTaskExecutor.new(batch, container: container)
59
- executor.call(aggregated_input)
45
+ def execute_task(initial_input, task, task_promises)
46
+ task_proc = container.resolve(task.name)
47
+ dep_promises = task.depends_on.map { |t| task_promises[t] }
48
+ input_promise = Concurrent::Promise.zip(*dep_promises)
49
+
50
+ input_promise.then do |results|
51
+ dep_input = aggregator.call(initial_input, results)
52
+ task_proc.call(dep_input)
53
+ end
60
54
  end
61
55
 
62
56
  # @api private
@@ -36,9 +36,9 @@ module Attr
36
36
  #
37
37
  # @api public
38
38
  def task(task_name, opts = EMPTY_HASH)
39
- task = Task.new(name: task_name, **opts)
40
- yield task
41
- tasks << task
39
+ conf = OpenStruct.new
40
+ yield conf
41
+ tasks << Hash[name: task_name, **opts, **conf.to_h]
42
42
  self
43
43
  end
44
44
 
@@ -1,19 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry-equalizer'
4
+
3
5
  module Attr
4
6
  module Gather
5
7
  module Workflow
6
8
  # @api private
7
9
  class Task
8
- attr_accessor :depends_on, :name
10
+ send :include, Dry::Equalizer(:name, :depends_on)
11
+
12
+ attr_accessor :name, :depends_on
9
13
 
14
+ # Initialize a new DeepMerge aggregator
15
+ #
16
+ # @param name [String] name of the task
17
+ # @param depends_on [Array<Task>] tasks needed before running this task
18
+ #
19
+ # @api private
10
20
  def initialize(name:, depends_on: [])
11
21
  @name = name
12
22
  @depends_on = depends_on
13
23
  end
14
24
 
25
+ # Check if this task depends on a given task
26
+ #
27
+ # @param other_task [Task] task to check
15
28
  def depends_on?(other_task)
16
- depends_on.include?(other_task.name)
29
+ depends_on.include?(other_task)
17
30
  end
18
31
 
19
32
  def fullfilled_given_remaining_tasks?(task_list)
@@ -20,7 +20,9 @@ module Attr
20
20
  tasks.each { |t| self << t }
21
21
  end
22
22
 
23
- def <<(task)
23
+ def <<(hash)
24
+ name, depends_on = hash.values_at :name, :depends_on
25
+ task = build_task(name, depends_on)
24
26
  validate_for_insert!(task)
25
27
 
26
28
  registered_tasks.each do |t|
@@ -68,6 +70,16 @@ module Attr
68
70
 
69
71
  private
70
72
 
73
+ def build_task(name, depends_on)
74
+ deps = depends_on.map do |dep_name|
75
+ registered_tasks.find do |task|
76
+ task.name == dep_name
77
+ end
78
+ end
79
+
80
+ Task.new(name: name, depends_on: deps)
81
+ end
82
+
71
83
  def tsort_each_child(node, &blk)
72
84
  to_h[node].each(&blk)
73
85
  end
@@ -99,7 +111,7 @@ module Attr
99
111
  end
100
112
 
101
113
  def depended_on_tasks_exist?(task)
102
- task.depends_on.all? { |t| registered_tasks.map(&:name).include?(t) }
114
+ task.depends_on.all? { |t| registered_tasks.include?(t) }
103
115
  end
104
116
  end
105
117
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attr-gather
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Ker-Seymer
@@ -115,14 +115,11 @@ files:
115
115
  - lib/attr/gather/filters/result.rb
116
116
  - lib/attr/gather/version.rb
117
117
  - lib/attr/gather/workflow.rb
118
- - lib/attr/gather/workflow/async_task_executor.rb
119
118
  - lib/attr/gather/workflow/callable.rb
120
119
  - lib/attr/gather/workflow/dot_serializer.rb
121
120
  - lib/attr/gather/workflow/dsl.rb
122
121
  - lib/attr/gather/workflow/graphable.rb
123
122
  - lib/attr/gather/workflow/task.rb
124
- - lib/attr/gather/workflow/task_execution_result.rb
125
- - lib/attr/gather/workflow/task_executor.rb
126
123
  - lib/attr/gather/workflow/task_graph.rb
127
124
  homepage: https://github.com/ianks/attr-gather
128
125
  licenses:
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'attr/gather/workflow/task_executor'
4
-
5
- module Attr
6
- module Gather
7
- module Workflow
8
- # @api private
9
- class AsyncTaskExecutor < TaskExecutor
10
- def initialize(batch, container:)
11
- super(batch, container: container)
12
- @executor = :io
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Attr
4
- module Gather
5
- module Workflow
6
- # A wrapper containing information and results of a task execution
7
- #
8
- # @!attribute [r] started_at
9
- # @return [Time] time which the execution occured
10
- #
11
- # @!attribute [r] task
12
- # @return [Attr::Gather::Workflow::Task] task that was run
13
- #
14
- # @!attribute [r] result
15
- # @return [Concurrent::Promise] the result promise of the the task
16
- #
17
- # @api public
18
- class TaskExecutionResult
19
- include Concerns::Identifiable
20
-
21
- attr_reader :task, :result, :started_at, :uuid
22
-
23
- def initialize(task, result, started_at: Time.now, uuid: SecureRandom.uuid) # rubocop:disable Metrics/LineLength
24
- @task = task
25
- @result = result
26
- @started_at = started_at
27
- @uuid = uuid
28
- end
29
-
30
- # @!attribute [r] state
31
- # @return [:unscheduled, :pending, :processing, :rejected, :fulfilled]
32
- def state
33
- result.state
34
- end
35
-
36
- # Extracts the result, this is an unsafe operation that blocks the
37
- # operation, and returns either the value or an exception.
38
- #
39
- # @note For more information, check out {https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Concern/Obligation.html#value!-instance_method}
40
- def value!
41
- result.value!
42
- end
43
-
44
- # Chain a new block result to be executed after resolution
45
- #
46
- # @return [TaskExecutionResult] the new task execution result
47
- # @yield The block operation to be performed asynchronously.
48
- def then(*args, &block)
49
- new_result = result.then(*args, &block)
50
- self.class.new(task, new_result, started_at: @started_at, uuid: @uuid)
51
- end
52
-
53
- # Catch an async exception when a failure occurs
54
- #
55
- # @return [TaskExecutionResult] the new task execution result
56
- # @yield The block operation to be performed asynchronously.
57
- def catch(*args, &block)
58
- new_result = result.catch(*args, &block)
59
- self.class.new(task, new_result, started_at: @started_at, uuid: @uuid)
60
- end
61
-
62
- # Executes a block after the result is fulfilled
63
- # Represents the TaskExecutionResult as a hash
64
- #
65
- # @return [Hash]
66
- def as_json
67
- value = result.value
68
-
69
- { started_at: started_at,
70
- task: task.as_json,
71
- state: state,
72
- value: value }
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'concurrent'
4
- require 'attr/gather/workflow/task_execution_result'
5
-
6
- module Attr
7
- module Gather
8
- module Workflow
9
- # @api private
10
- class TaskExecutor
11
- attr_reader :batch, :container, :executor
12
-
13
- def initialize(batch, container:)
14
- @batch = batch
15
- @container = container
16
- @executor = :immediate
17
- end
18
-
19
- def call(input)
20
- batch.map do |task|
21
- task_proc = container.resolve(task.name)
22
- result = Concurrent::Promise.execute(executor: executor) do
23
- task_proc.call(input)
24
- end
25
- TaskExecutionResult.new(task, result)
26
- end
27
- end
28
- end
29
- end
30
- end
31
- end