ruby_pymill 0.2.1 → 0.3.1

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: a7066e476986ad19ddcf36c5d4f2facb7882e3efed7972399c1384dccd3e006b
4
- data.tar.gz: 26375858e837e22ab68305e38fd55b8ea61942c0fa2a1c418209298ab99d8346
3
+ metadata.gz: c2419632f9e7eb3cbb8cf35cd28fa922da156e5487de259cf16ea2eec1471088
4
+ data.tar.gz: c43e0a5ef36b8be315d41a157589e1d4f5298a2dfa1bc52f9a4f1e56dc107afd
5
5
  SHA512:
6
- metadata.gz: 76bda36211334b1b286d05345c3497e106654149b84b0ff43b31928a4ebe464d5212a1a80463fe0ba180800beb4e5540dd6bd1d4f4ddafb628ce0c1603046f37
7
- data.tar.gz: a8ae641e6cdbab208733a053453436f6fb70c0693964b567a3d79ded51590963df98b550e4ef8e3aa9004fe9faae1637fb16cfa901784489f16e65c2b2e03ecc
6
+ metadata.gz: 63f499bd9dce06e1728c26fed3f39ad1be2c00e9dd97007580008a4b8a493d8107f6d04849e310528a0ffec653cf5c6a4451fc518bf16232babb04a0f22e26d5
7
+ data.tar.gz: d564e1b1b6d23596a6b1708b7d39ffe72058799d1f4912cd55ad006bbab96f444c61969abe3b18b686ae55646df0e811b8edb0080eee3cf920995173fc14b312
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # RubyPyMill
2
2
  Running Notebooks the Ruby Way — RubyPyMill and the Art of PoC Automation
3
3
 
4
+ RubyPyMill bridges Ruby orchestration with Python's Jupyter notebook execution ecosystem.
5
+
6
+ **RubyPyMill v0.3.0 introduces the experimental Ruby API.**
7
+
4
8
  ## Background and Purpose
5
9
  RubyPyMill is a lightweight runner and automation starter that allows Ruby
6
10
  to control Papermill (a Python Notebook runner).
@@ -77,6 +81,19 @@ bundle exec ruby bin/ruby_pymill exec <input.ipynb> \
77
81
  [--dry-run]
78
82
  ```
79
83
 
84
+ ### Windows (PowerShell)
85
+
86
+ PowerShell does not support `\` for line continuation.
87
+ Use backticks (`) or run the command in a single line.
88
+
89
+ ```powershell
90
+ bundle exec ruby bin/ruby_pymill exec examples/notebooks/lang_radar.ipynb `
91
+ --output examples/outputs/lang_radar_out.ipynb `
92
+ --params examples/params/lang_radar.json `
93
+ --kernel rpymill `
94
+ --cell_tags "parameters,setup,graph_view,graph_output"
95
+ ```
96
+
80
97
  ## Processing Overview
81
98
  1. Load the notebook as JSON.
82
99
  2. Filter cells by specified tags (the `parameters` cell is always preserved).
@@ -100,6 +117,7 @@ This example demonstrates:
100
117
  For a detailed explanation in Japanese, see `README.jp.md`.
101
118
 
102
119
  ## Programmatic Usage (Experimental)
120
+
103
121
  RubyPyMill is primarily designed as a CLI tool.
104
122
 
105
123
  Internally, it exposes a Ruby execution API (`RubyPyMill::API`),
@@ -111,6 +129,22 @@ or future web APIs.
111
129
  The CLI is considered the stable interface.
112
130
  The Ruby API is experimental and may change.
113
131
 
132
+ ```ruby
133
+ require "ruby_pymill"
134
+
135
+ result = RubyPyMill::API.run(
136
+ input: "examples/notebooks/lang_radar.ipynb",
137
+ output: "examples/outputs/lang_radar_out.ipynb",
138
+ params: "examples/params/lang_radar.json",
139
+ kernel: "rpymill",
140
+ cell_tags: "parameters,setup,graph_output"
141
+ )
142
+
143
+ puts result.output
144
+ ```
145
+
146
+ ---
147
+
114
148
  ## Changelog
115
149
 
116
150
  See [CHANGELOG.md](https://github.com/inoue-0852/RubyPyMill-OSS/blob/main/CHANGELOG.md) for release history.
@@ -1,59 +1,89 @@
1
1
  # lib/ruby_pymill/api.rb
2
- require "open3"
2
+ require "fileutils"
3
3
 
4
4
  module RubyPyMill
5
+ class Error < StandardError; end
6
+ class ExecutionError < Error; end
7
+
8
+ Result = Struct.new(
9
+ :success,
10
+ :output,
11
+ :filtered_input,
12
+ :command,
13
+ :stdout,
14
+ :stderr,
15
+ keyword_init: true
16
+ ) do
17
+ def success?
18
+ success
19
+ end
20
+ end
21
+
5
22
  module API
6
- # Ruby から notebook を実行する公式API
23
+ # Ruby から notebook を実行する公開API
7
24
  #
8
25
  # 例:
9
- # RubyPyMill::API.run(
10
- # notebook: "demo/notebooks/xxx.ipynb",
11
- # output: "demo/outputs/out.ipynb",
12
- # kernel: "rpymill",
13
- # cell_tags:"setup,preprocess,report",
14
- # params: "demo/params/kodama.json",
15
- # log: "demo/logs/run_xxx.log"
26
+ # result = RubyPyMill::API.run(
27
+ # input: "examples/notebooks/lang_radar.ipynb",
28
+ # output: "examples/outputs/lang_radar_out.ipynb",
29
+ # kernel: "rpymill",
30
+ # cell_tags: "parameters,setup,graph_output",
31
+ # params: "examples/params/lang_radar.json",
32
+ # log: "examples/logs/lang_radar.log"
33
+ # )
34
+ #
35
+ # puts result.output
36
+ #
37
+ # 後方互換: input: の旧名称 notebook: も引き続き使用可能
38
+ # result = RubyPyMill::API.run(
39
+ # notebook: "examples/notebooks/lang_radar.ipynb",
40
+ # ...
16
41
  # )
17
42
  #
18
43
  def self.run(
19
- notebook:,
44
+ input: nil,
45
+ notebook: nil,
20
46
  output:,
21
47
  kernel: "rpymill",
22
48
  cell_tags: nil,
23
49
  params: nil,
24
- log: nil
50
+ log: nil,
51
+ dry_run: false,
52
+ logger: $stdout,
53
+ cwd: nil
25
54
  )
26
- cmd = [
27
- "ruby_pymill", "exec",
28
- notebook,
29
- "--output", output,
30
- "--kernel", kernel,
31
- ]
32
- cmd += ["--cell-tag", cell_tags] if cell_tags && !cell_tags.empty?
33
- cmd += ["--params", params] if params && !params.empty?
34
-
35
- stdout_all = +""
36
- status = nil
37
-
38
- Open3.popen2e(*cmd) do |_stdin, stdout_err, wait_thr|
39
- stdout_err.each do |line|
40
- print line # コンソールにも流す
41
- stdout_all << line # ログにも残す
42
- end
43
- status = wait_thr.value
44
- end
55
+ # notebook: は input: の旧名称(後方互換)
56
+ # Add `notebook:` as a backward-compatible alias for `input:` in API.run
57
+ resolved_input = input || notebook
58
+ raise ArgumentError, "input: (or notebook:) is required" if resolved_input.nil?
59
+
60
+ runner = Runner.new(
61
+ kernel: kernel,
62
+ cwd: cwd,
63
+ logger: logger,
64
+ cell_tags: cell_tags
65
+ )
66
+
67
+ result = runner.run(
68
+ input_ipynb: resolved_input,
69
+ output_ipynb: output,
70
+ params_json: params,
71
+ kernel: kernel,
72
+ dry_run: dry_run,
73
+ cell_tags: cell_tags
74
+ )
45
75
 
46
76
  if log
47
77
  log_dir = File.dirname(log)
48
- Dir.mkdir(log_dir) unless Dir.exist?(log_dir)
49
- File.write(log, stdout_all)
78
+ FileUtils.mkdir_p(log_dir)
79
+ File.write(log, [result.stdout, result.stderr].reject(&:empty?).join)
50
80
  end
51
81
 
52
- unless status&.success?
53
- raise "ruby_pymill failed (status=#{status.exitstatus})"
82
+ unless result.success?
83
+ raise ExecutionError, "ruby_pymill failed"
54
84
  end
55
85
 
56
- stdout_all
86
+ result
57
87
  end
58
88
  end
59
- end
89
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module RubyPyMill
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.1"
4
4
  end
data/lib/ruby_pymill.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "ruby_pymill/version"
4
- require_relative "ruby_pymill/api"
5
4
  require "json"
6
5
  require "open3"
7
6
  require "tmpdir"
@@ -12,14 +11,14 @@ module RubyPyMill
12
11
  @kernel = kernel
13
12
  @cwd = cwd
14
13
  @logger = logger
15
- @cell_tags = normalize_tags(cell_tags) # ← 文字列/配列どちらでもOKに
14
+ @cell_tags = normalize_tags(cell_tags)
16
15
  end
17
16
 
18
17
  # params_json: path to json file or JSON string
19
18
  # cell_tags : initialize の指定を上書き可能(カンマ/空白区切り文字列 or 配列)
20
19
  def run(input_ipynb:, output_ipynb:, params_json: nil, kernel: nil, dry_run: false, cell_tags: nil)
21
20
  k = kernel || @kernel
22
- tags = normalize_tags(cell_tags.nil? ? @cell_tags : cell_tags) # ← 正規化して複数タグ対応
21
+ tags = normalize_tags(cell_tags.nil? ? @cell_tags : cell_tags)
23
22
 
24
23
  # 1) タグ指定があればノートを事前フィルタ
25
24
  filtered_input = tags.empty? ? input_ipynb : filter_by_tags(input_ipynb, tags)
@@ -38,14 +37,32 @@ module RubyPyMill
38
37
  end
39
38
  end
40
39
 
41
- cmd = args.join(" ")
42
- @logger.puts "[RubyPyMill] #{dry_run ? 'DRY' : 'RUN'}: #{cmd}"
43
- return true if dry_run
40
+ cmd_str = args.join(" ")
41
+ @logger.puts "[RubyPyMill] #{dry_run ? 'DRY' : 'RUN'}: #{cmd_str}"
44
42
 
45
- stdout_str, stderr_str, status = Open3.capture3(cmd, chdir: @cwd || Dir.pwd)
43
+ if dry_run
44
+ return Result.new(
45
+ success: true,
46
+ output: output_ipynb,
47
+ filtered_input: filtered_input,
48
+ command: cmd_str,
49
+ stdout: "",
50
+ stderr: ""
51
+ )
52
+ end
53
+
54
+ stdout_str, stderr_str, status = Open3.capture3(*args, chdir: @cwd || Dir.pwd)
46
55
  @logger.puts stdout_str unless stdout_str.empty?
47
56
  @logger.puts stderr_str unless stderr_str.empty?
48
- status.success?
57
+
58
+ Result.new(
59
+ success: status.success?,
60
+ output: output_ipynb,
61
+ filtered_input: filtered_input,
62
+ command: cmd_str,
63
+ stdout: stdout_str,
64
+ stderr: stderr_str
65
+ )
49
66
  end
50
67
 
51
68
  private
@@ -71,7 +88,9 @@ module RubyPyMill
71
88
 
72
89
  # 万一ゼロ件なら parameters 系のみ確保
73
90
  if kept.empty?
74
- kept = cells.select { |c| (Array(c.dig("metadata", "tags")) & %w[parameters injected-parameters]).any? }
91
+ kept = cells.select do |c|
92
+ (Array(c.dig("metadata", "tags")) & %w[parameters injected-parameters]).any?
93
+ end
75
94
  end
76
95
 
77
96
  filtered = data.dup
@@ -84,3 +103,5 @@ module RubyPyMill
84
103
  end
85
104
  end
86
105
  end
106
+
107
+ require_relative "ruby_pymill/api"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_pymill
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroshi Inoue
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-12 00:00:00.000000000 Z
11
+ date: 2026-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json