rubyx-py 0.2.0-arm64-darwin → 0.2.1-arm64-darwin

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: 86d5cf5c52d52664994c76fa7cae53a0403e5f34bc2d772ebe702832b2e3acd7
4
- data.tar.gz: d7e7993819adc02bee503790f210746917c6db81b4304d65f12ad84fa1d98140
3
+ metadata.gz: a3ae1306c44028e00fc740e97698a652a04f9f632ee60f11fe7e342869686d3d
4
+ data.tar.gz: b5b878d20cc076841a5adf1b20fb0ab9202243c466cf0a29a361d7e23ed452c4
5
5
  SHA512:
6
- metadata.gz: ddf9c0e08e80cff34c621b72c9c25e3135d4d1e3324457aec776a23b1cc5722068ac5a52abd417e9636fdbcb0dc30a91b2824906bcb5c03ebc2b536b6e0d3032
7
- data.tar.gz: 39380e4aad86765816954a47e89af7aba8387dc4d94269901e3edb68c93e69a8c6280e7b15b15ea02452d14415bc165335c2a85a24e01aee20618ae7862930e0
6
+ metadata.gz: a7149f3120299ddaf8242ef0f1b1ff07f8657c3274b750513a7fc9661ab940fd5c5a545d1bdf1c43acb475d0f56b1a63b1cc9bf2650d8503d4916197e449d752
7
+ data.tar.gz: 766c75dd79bcea478746ea6b9b982c9d7e5f245b7b768381e38dfb6c09c7058c03a0e05a0013fe218321b107fddf86bbf21f6eee5809597cc14e8bc10fb6460c
data/README.md CHANGED
@@ -6,14 +6,16 @@
6
6
 
7
7
  **Call Python from Ruby. No microservices, no REST APIs, no serialization overhead.**
8
8
 
9
- Powered by Rust for safety and performance. Built for Rails.
9
+ Powered by Rust for safety and performance. Built for Rails. Inspired by [Pythonx](https://github.com/livebook-dev/pythonx)
10
10
 
11
11
  [![Gem Version](https://badge.fury.io/rb/rubyx-py.svg)](https://badge.fury.io/rb/rubyx-py)
12
12
  [![CI](https://github.com/yinho999/rubyx/actions/workflows/ci.yml/badge.svg)](https://github.com/yinho999/rubyx/actions/workflows/ci.yml)
13
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
  [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.0-red.svg)](https://www.ruby-lang.org)
15
15
  [![Rust](https://img.shields.io/badge/Rust-powered-orange.svg)](https://www.rust-lang.org)
16
-
16
+ > *"Rubyx showed that the proxy-object pattern works beautifully for cross-language bridges in Rust, and its magnus + rb-sys architecture is exactly what [Boax](https://intertwingly.net/blog/2026/03/25/Calling-JavaScript-from-Ruby.html) uses."*
17
+ >
18
+ > — [Sam Ruby](https://en.wikipedia.org/wiki/Sam_Ruby), author of *Agile Web Development with Rails*
17
19
  </div>
18
20
 
19
21
  ---
@@ -93,7 +95,7 @@ dependencies = []
93
95
 
94
96
  ```python
95
97
  # app/python/example.py
96
- def hello(name="World"):
98
+ def hello(name):
97
99
  return f"Hello, {name}! From Python."
98
100
  ```
99
101
 
@@ -102,7 +104,7 @@ def hello(name="World"):
102
104
  class GreetingsController < ApplicationController
103
105
  def index
104
106
  example = Rubyx.import('example')
105
- render json: { message: example.hello(params[:name]).to_ruby }
107
+ render json: { message: example.hello(params[:name] || 'World').to_ruby }
106
108
  end
107
109
  end
108
110
  ```
@@ -159,7 +161,7 @@ class TasksController < ApplicationController
159
161
  tasks = Rubyx.import('tasks')
160
162
 
161
163
  # Non-blocking — returns a Future immediately
162
- future = Rubyx.async_await(tasks.delayed_greet(params[:name], seconds: 2))
164
+ future = Rubyx.async_await(tasks.delayed_greet(params[:name] || 'World', seconds: 2))
163
165
  do_other_work()
164
166
  render json: { message: future.await.to_ruby }
165
167
  end
@@ -331,7 +333,29 @@ Rubyx.eval("f'Hello, {name}!'", name: "World").to_ruby # => "Hello, World!"
331
333
  Rubyx.eval("max(items)", items: [3, 1, 4, 1, 5]).to_ruby # => 5
332
334
  ```
333
335
 
334
- Supports: Integer, Float, String, Symbol, Bool, nil, Array, Hash, and RubyxObject.
336
+ Supports: Integer, Float, String, Symbol, Bool, nil, Array, Hash, binary String (ASCII-8BIT), and RubyxObject.
337
+
338
+ ## Bytes / Binary Data
339
+
340
+ Python `bytes` and `bytearray` convert to Ruby `String` with `ASCII-8BIT` encoding. Ruby binary strings (`.b`) convert to Python `bytes`:
341
+
342
+ ```ruby
343
+ # Python bytes → Ruby
344
+ Rubyx.eval("b'hello'").to_ruby # => "hello" (ASCII-8BIT)
345
+ Rubyx.eval("bytearray(b'hello')").to_ruby # => "hello" (ASCII-8BIT)
346
+
347
+ # Ruby → Python
348
+ ctx = Rubyx.context
349
+ ctx.eval("type(data).__name__", data: "hello".b) # => "bytes"
350
+ ctx.eval("type(data).__name__", data: "hello") # => "str"
351
+
352
+ # Roundtrip binary data
353
+ ctx.eval("data", data: "\xff\x00\xfe".b).to_ruby # => "\xFF\x00\xFE" (ASCII-8BIT)
354
+
355
+ # Works with Python stdlib
356
+ ctx.eval("import base64")
357
+ ctx.eval("base64.b64encode(raw)", raw: "Hello".b).to_ruby # => "SGVsbG8=" (ASCII-8BIT)
358
+ ```
335
359
 
336
360
  ## Python Objects
337
361
 
@@ -470,6 +494,37 @@ svc.Analyzer([1, 2, 3]).summary.to_ruby # => {"count" => 3, "sum" => 6}
470
494
  | `.callable?` | Check if callable |
471
495
  | `.py_type` | Python type name |
472
496
 
497
+ ## Type Conversion
498
+
499
+ | Python | Ruby | Notes |
500
+ |-----------------------|------------------------------|------------------------|
501
+ | `int` | `Integer` | |
502
+ | `float` | `Float` | |
503
+ | `str` | `String` (UTF-8) | |
504
+ | `bytes` | `String` (ASCII-8BIT) | binary data |
505
+ | `bytearray` | `String` (ASCII-8BIT) | binary data |
506
+ | `bool` | `true` / `false` | |
507
+ | `None` | `nil` | |
508
+ | `list` / `tuple` | `Array` | |
509
+ | `dict` | `Hash` | |
510
+ | `set` / `frozenset` | `Array` | |
511
+ | everything else | `RubyxObject` | proxy to Python object |
512
+
513
+ **Ruby → Python** (via globals/kwargs):
514
+
515
+ | Ruby | Python |
516
+ |--------------------------------|-------------|
517
+ | `Integer` | `int` |
518
+ | `Float` | `float` |
519
+ | `String` (UTF-8) | `str` |
520
+ | `String` (ASCII-8BIT / `.b`) | `bytes` |
521
+ | `true` / `false` | `bool` |
522
+ | `nil` | `None` |
523
+ | `Array` | `list` |
524
+ | `Hash` | `dict` |
525
+ | `Symbol` | `str` |
526
+ | `RubyxObject` | original |
527
+
473
528
  ## Requirements
474
529
 
475
530
  - Ruby >= 3.0
Binary file
Binary file
Binary file
Binary file
data/lib/rubyx/railtie.rb CHANGED
@@ -14,7 +14,8 @@ module Rubyx
14
14
 
15
15
  # Register rake tasks
16
16
  rake_tasks do
17
- load 'rubyx/tasks/rubyx.rake'
17
+ task_file = File.expand_path('tasks/rubyx.rake', __dir__)
18
+ load task_file if File.exist?(task_file)
18
19
  end
19
20
  end
20
21
  end
@@ -0,0 +1,127 @@
1
+ namespace :rubyx do
2
+ desc 'Initialize Python environment (downloads uv and Python if needed)'
3
+ task init: :environment do
4
+ Rubyx::Rails.init!
5
+ puts '[Rubyx] Python environment initialized successfully.'
6
+ end
7
+
8
+ desc 'Check Python environment health'
9
+ task check: :environment do
10
+ puts 'Checking Python environment...'
11
+ puts
12
+
13
+ # Check uv
14
+ system_uv = `which uv 2>/dev/null`.strip
15
+ uv_available = !system_uv.empty? && File.exist?(system_uv)
16
+ puts "uv available: #{uv_available ? "Yes (#{system_uv})" : 'No (will auto-download)'}"
17
+
18
+ begin
19
+ Rubyx::Rails.ensure_initialized!
20
+ puts 'Python initialized: Yes'
21
+ rescue => e
22
+ puts "Python initialized: No (#{e.message})"
23
+ puts
24
+ puts 'Run `rake rubyx:init` to initialize.'
25
+ exit 1
26
+ end
27
+
28
+ begin
29
+ Rubyx.eval('1 + 1')
30
+ puts 'Basic eval: OK'
31
+ rescue => e
32
+ puts "Basic eval: FAILED (#{e.message})"
33
+ exit 1
34
+ end
35
+
36
+ begin
37
+ gen = Rubyx.eval("import sys\niter([sys.version.split()[0]])")
38
+ version = Rubyx.stream(gen).first
39
+ puts "Import sys: OK (Python #{version})"
40
+ rescue => e
41
+ puts "Import sys: FAILED (#{e.message})"
42
+ exit 1
43
+ end
44
+
45
+ puts
46
+ puts 'All checks passed!'
47
+ end
48
+
49
+ desc 'Show Rubyx configuration and status'
50
+ task status: :environment do
51
+ config = Rubyx::Rails.configuration
52
+
53
+ puts 'Rubyx Status'
54
+ puts '=' * 40
55
+
56
+ puts "Initialized: #{Rubyx::Rails.initialized?}"
57
+ puts
58
+
59
+ puts 'Configuration:'
60
+ puts " pyproject_path: #{config.pyproject_path || '(not set)'}"
61
+ puts " pyproject_content: #{config.pyproject_content ? '(inline, %d bytes)' % config.pyproject_content.length : '(not set)'}"
62
+ puts " auto_init: #{config.auto_init}"
63
+ puts " force_reinit: #{config.force_reinit}"
64
+ puts " uv_version: #{config.uv_version}"
65
+ puts " debug: #{config.debug}"
66
+ puts " python_paths: #{config.python_paths.inspect}"
67
+ puts " uv_path: #{config.uv_path || '(auto-download)'}"
68
+ puts " uv_args: #{config.uv_args.inspect}"
69
+ puts
70
+
71
+ if config.pyproject_path
72
+ exists = File.exist?(config.pyproject_path.to_s)
73
+ puts "pyproject.toml exists: #{exists}"
74
+ end
75
+
76
+ if config.pyproject_path
77
+ venv_dir = File.join(File.dirname(config.pyproject_path.to_s), '.venv')
78
+ puts ".venv exists: #{Dir.exist?(venv_dir)}"
79
+ end
80
+
81
+ system_uv = `which uv 2>/dev/null`.strip
82
+ puts "System uv: #{!system_uv.empty? ? system_uv : '(not found)'}"
83
+ end
84
+
85
+ desc 'List installed Python packages'
86
+ task packages: :environment do
87
+ Rubyx::Rails.ensure_initialized!
88
+
89
+ gen = Rubyx.eval(<<~PY)
90
+ import pkg_resources
91
+ packages = sorted([f"{d.project_name}=={d.version}" for d in pkg_resources.working_set])
92
+ iter(packages)
93
+ PY
94
+
95
+ puts 'Installed Python packages:'
96
+ Rubyx.stream(gen).each { |pkg| puts " #{pkg}" }
97
+ rescue => e
98
+ begin
99
+ gen = Rubyx.eval(<<~PY)
100
+ from importlib.metadata import distributions
101
+ packages = sorted([f"{d.metadata['Name']}=={d.metadata['Version']}" for d in distributions()])
102
+ iter(packages)
103
+ PY
104
+
105
+ puts 'Installed Python packages:'
106
+ Rubyx.stream(gen).each { |pkg| puts " #{pkg}" }
107
+ rescue => e2
108
+ puts "Could not list packages: #{e2.message}"
109
+ end
110
+ end
111
+
112
+ desc 'Clear the Rubyx cache (re-download uv + Python on next init)'
113
+ task clear_cache: :environment do
114
+ cache_dir = File.join(
115
+ ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache')),
116
+ 'rubyx'
117
+ )
118
+
119
+ if Dir.exist?(cache_dir)
120
+ require 'fileutils'
121
+ FileUtils.rm_rf(cache_dir)
122
+ puts "[Rubyx] Cache cleared: #{cache_dir}"
123
+ else
124
+ puts '[Rubyx] No cache directory found.'
125
+ end
126
+ end
127
+ end
data/lib/rubyx/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Rubyx
3
- VERSION = "0.2.0".freeze
4
- end
3
+ VERSION = "0.2.1".freeze
4
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyx-py
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: arm64-darwin
6
6
  authors:
7
7
  - Naiker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-26 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -63,6 +63,7 @@ files:
63
63
  - lib/rubyx/error.rb
64
64
  - lib/rubyx/rails.rb
65
65
  - lib/rubyx/railtie.rb
66
+ - lib/rubyx/tasks/rubyx.rake
66
67
  - lib/rubyx/uv.rb
67
68
  - lib/rubyx/version.rb
68
69
  homepage: https://github.com/yinho999/rubyx