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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/attr/gather/aggregators/base.rb +6 -8
- data/lib/attr/gather/aggregators/deep_merge.rb +3 -11
- data/lib/attr/gather/aggregators/shallow_merge.rb +2 -15
- data/lib/attr/gather/version.rb +1 -1
- data/lib/attr/gather/workflow/callable.rb +16 -22
- data/lib/attr/gather/workflow/dsl.rb +3 -3
- data/lib/attr/gather/workflow/task.rb +15 -2
- data/lib/attr/gather/workflow/task_graph.rb +14 -2
- metadata +1 -4
- data/lib/attr/gather/workflow/async_task_executor.rb +0 -17
- data/lib/attr/gather/workflow/task_execution_result.rb +0 -77
- data/lib/attr/gather/workflow/task_executor.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee9b30fd44bd8931964b53b904815224fa6a5a592ac1f22d19605a982a326d42
|
4
|
+
data.tar.gz: 7954a71a61dc89266358909ce189312b3b3164e4092d62ae4fae973712efca9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 040b490aba781f8f851b617a365d2aea061f9d2afb189bcdf4e0e086effebc2135109a648b0f116d4be7000cbe4d3e10e369fadcb38d9e2910114c36957cce68
|
7
|
+
data.tar.gz: 59a93d0d6e16f9b2433822c4faf7ef4cda25f3e9e37da8a5fae816e313e1d6e0b86906a1d652b3c64125eae888607c9117ade38fc17ff11b84c361add21f4c62
|
data/Gemfile.lock
CHANGED
@@ -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
|
-
|
34
|
-
|
35
|
-
return unvalidated if filter.nil?
|
33
|
+
return res if filter.nil?
|
36
34
|
|
37
|
-
filter.call(
|
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,
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
data/lib/attr/gather/version.rb
CHANGED
@@ -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
|
-
|
27
|
+
task_promises = {}
|
31
28
|
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
58
|
-
|
59
|
-
|
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
|
-
|
40
|
-
yield
|
41
|
-
tasks <<
|
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
|
-
|
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
|
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 <<(
|
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.
|
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.
|
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
|