attr-gather 1.1.3 → 1.2.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: 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