rubyx-py 0.2.0 → 0.2.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 +4 -4
- data/README.md +61 -6
- data/ext/rubyx/src/lib.rs +1 -0
- data/ext/rubyx/src/python_api.rs +1027 -0
- data/ext/rubyx/src/rubyx_object.rs +1075 -8
- data/ext/rubyx/src/rubyx_stream.rs +168 -0
- data/ext/rubyx/src/stream.rs +148 -2
- data/lib/rubyx/railtie.rb +2 -1
- data/lib/rubyx/tasks/rubyx.rake +127 -0
- data/lib/rubyx/version.rb +2 -2
- metadata +2 -1
|
@@ -73,6 +73,7 @@ mod tests {
|
|
|
73
73
|
use crate::stream::SendableValue;
|
|
74
74
|
use crate::test_helpers::{skip_if_no_python, with_ruby_python};
|
|
75
75
|
use crossbeam_channel::{bounded, unbounded};
|
|
76
|
+
use magnus::encoding::EncodingCapable;
|
|
76
77
|
use magnus::value::ReprValue;
|
|
77
78
|
use magnus::TryConvert;
|
|
78
79
|
use serial_test::serial;
|
|
@@ -947,4 +948,171 @@ mod tests {
|
|
|
947
948
|
assert_eq!(v, 99);
|
|
948
949
|
});
|
|
949
950
|
}
|
|
951
|
+
|
|
952
|
+
// ========== SendableValue::Bytes → Ruby String (ASCII-8BIT) ==========
|
|
953
|
+
|
|
954
|
+
#[test]
|
|
955
|
+
#[serial]
|
|
956
|
+
fn test_sendable_bytes_converts_to_ruby_string() {
|
|
957
|
+
with_ruby_python(|ruby, _api| {
|
|
958
|
+
let val: magnus::Value = SendableValue::Bytes(b"hello".to_vec())
|
|
959
|
+
.try_into()
|
|
960
|
+
.expect("Bytes conversion should succeed");
|
|
961
|
+
|
|
962
|
+
// Must be a Ruby String, not an Array of integers
|
|
963
|
+
let rstr = magnus::RString::try_convert(val).expect("should be a String");
|
|
964
|
+
let content = unsafe { rstr.as_slice() };
|
|
965
|
+
assert_eq!(content, b"hello");
|
|
966
|
+
|
|
967
|
+
// Verify encoding is ASCII-8BIT
|
|
968
|
+
let enc = rstr.enc_get();
|
|
969
|
+
let ascii_8bit = ruby
|
|
970
|
+
.find_encindex("ASCII-8BIT")
|
|
971
|
+
.expect("ASCII-8BIT must exist");
|
|
972
|
+
assert!(
|
|
973
|
+
enc == ascii_8bit,
|
|
974
|
+
"Bytes should produce ASCII-8BIT encoded String"
|
|
975
|
+
);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
#[test]
|
|
980
|
+
#[serial]
|
|
981
|
+
fn test_sendable_bytes_empty() {
|
|
982
|
+
with_ruby_python(|ruby, _api| {
|
|
983
|
+
let val: magnus::Value = SendableValue::Bytes(Vec::new())
|
|
984
|
+
.try_into()
|
|
985
|
+
.expect("empty Bytes conversion should succeed");
|
|
986
|
+
|
|
987
|
+
let rstr = magnus::RString::try_convert(val).expect("should be a String");
|
|
988
|
+
let content = unsafe { rstr.as_slice() };
|
|
989
|
+
assert!(content.is_empty());
|
|
990
|
+
|
|
991
|
+
let enc = rstr.enc_get();
|
|
992
|
+
let ascii_8bit = ruby
|
|
993
|
+
.find_encindex("ASCII-8BIT")
|
|
994
|
+
.expect("ASCII-8BIT must exist");
|
|
995
|
+
assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
#[test]
|
|
1000
|
+
#[serial]
|
|
1001
|
+
fn test_sendable_bytes_with_null_bytes() {
|
|
1002
|
+
with_ruby_python(|ruby, _api| {
|
|
1003
|
+
let data = vec![0x00, 0x01, 0xff, 0x00, 0xfe];
|
|
1004
|
+
let val: magnus::Value = SendableValue::Bytes(data.clone())
|
|
1005
|
+
.try_into()
|
|
1006
|
+
.expect("Bytes with NULs should convert");
|
|
1007
|
+
|
|
1008
|
+
let rstr = magnus::RString::try_convert(val).expect("should be a String");
|
|
1009
|
+
let content = unsafe { rstr.as_slice() };
|
|
1010
|
+
assert_eq!(content, data.as_slice());
|
|
1011
|
+
|
|
1012
|
+
let enc = rstr.enc_get();
|
|
1013
|
+
let ascii_8bit = ruby
|
|
1014
|
+
.find_encindex("ASCII-8BIT")
|
|
1015
|
+
.expect("ASCII-8BIT must exist");
|
|
1016
|
+
assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
#[test]
|
|
1021
|
+
#[serial]
|
|
1022
|
+
fn test_sendable_bytes_all_256_values() {
|
|
1023
|
+
with_ruby_python(|ruby, _api| {
|
|
1024
|
+
let data: Vec<u8> = (0..=255).collect();
|
|
1025
|
+
let val: magnus::Value = SendableValue::Bytes(data.clone())
|
|
1026
|
+
.try_into()
|
|
1027
|
+
.expect("all byte values should convert");
|
|
1028
|
+
|
|
1029
|
+
let rstr = magnus::RString::try_convert(val).expect("should be a String");
|
|
1030
|
+
let content = unsafe { rstr.as_slice() };
|
|
1031
|
+
assert_eq!(content, data.as_slice());
|
|
1032
|
+
|
|
1033
|
+
let enc = rstr.enc_get();
|
|
1034
|
+
let ascii_8bit = ruby
|
|
1035
|
+
.find_encindex("ASCII-8BIT")
|
|
1036
|
+
.expect("ASCII-8BIT must exist");
|
|
1037
|
+
assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
#[test]
|
|
1042
|
+
#[serial]
|
|
1043
|
+
fn test_sendable_bytes_is_not_array() {
|
|
1044
|
+
with_ruby_python(|_ruby, _api| {
|
|
1045
|
+
let val: magnus::Value = SendableValue::Bytes(b"test".to_vec())
|
|
1046
|
+
.try_into()
|
|
1047
|
+
.expect("Bytes conversion should succeed");
|
|
1048
|
+
|
|
1049
|
+
// Must NOT be an Array (Vec<u8>.into_value would produce Array<Integer>)
|
|
1050
|
+
assert!(
|
|
1051
|
+
magnus::RArray::try_convert(val).is_err(),
|
|
1052
|
+
"Bytes must not convert to Ruby Array"
|
|
1053
|
+
);
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
#[test]
|
|
1058
|
+
#[serial]
|
|
1059
|
+
fn test_sendable_bytes_in_stream_delivery() {
|
|
1060
|
+
with_ruby_python(|ruby, _api| {
|
|
1061
|
+
let (tx, rx) = unbounded();
|
|
1062
|
+
let (cancel_tx, _cancel_rx) = bounded(1);
|
|
1063
|
+
|
|
1064
|
+
thread::spawn(move || {
|
|
1065
|
+
tx.send(Some(SendableValue::Integer(1))).ok();
|
|
1066
|
+
tx.send(Some(SendableValue::Bytes(b"\xde\xad".to_vec())))
|
|
1067
|
+
.ok();
|
|
1068
|
+
tx.send(Some(SendableValue::Str("after".to_string()))).ok();
|
|
1069
|
+
tx.send(None).ok();
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
let mut stream = crate::stream::AsyncStream::from_channel(rx, cancel_tx);
|
|
1073
|
+
|
|
1074
|
+
// First: integer
|
|
1075
|
+
let val = stream.next().unwrap().unwrap();
|
|
1076
|
+
assert_eq!(i64::try_convert(val).unwrap(), 1);
|
|
1077
|
+
|
|
1078
|
+
// Second: bytes → ASCII-8BIT String
|
|
1079
|
+
let val = stream.next().unwrap().unwrap();
|
|
1080
|
+
let rstr = magnus::RString::try_convert(val).expect("should be String");
|
|
1081
|
+
let content = unsafe { rstr.as_slice() };
|
|
1082
|
+
assert_eq!(content, b"\xde\xad");
|
|
1083
|
+
let enc = rstr.enc_get();
|
|
1084
|
+
let ascii_8bit = ruby
|
|
1085
|
+
.find_encindex("ASCII-8BIT")
|
|
1086
|
+
.expect("ASCII-8BIT must exist");
|
|
1087
|
+
assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
|
|
1088
|
+
|
|
1089
|
+
// Third: regular string
|
|
1090
|
+
let val = stream.next().unwrap().unwrap();
|
|
1091
|
+
assert_eq!(String::try_convert(val).unwrap(), "after");
|
|
1092
|
+
|
|
1093
|
+
assert!(stream.next().is_none());
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
#[test]
|
|
1098
|
+
#[serial]
|
|
1099
|
+
fn test_sendable_bytes_large_payload() {
|
|
1100
|
+
with_ruby_python(|ruby, _api| {
|
|
1101
|
+
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
|
|
1102
|
+
let val: magnus::Value = SendableValue::Bytes(data.clone())
|
|
1103
|
+
.try_into()
|
|
1104
|
+
.expect("large Bytes should convert");
|
|
1105
|
+
|
|
1106
|
+
let rstr = magnus::RString::try_convert(val).expect("should be a String");
|
|
1107
|
+
let content = unsafe { rstr.as_slice() };
|
|
1108
|
+
assert_eq!(content.len(), 10_000);
|
|
1109
|
+
assert_eq!(content, data.as_slice());
|
|
1110
|
+
|
|
1111
|
+
let enc = rstr.enc_get();
|
|
1112
|
+
let ascii_8bit = ruby
|
|
1113
|
+
.find_encindex("ASCII-8BIT")
|
|
1114
|
+
.expect("ASCII-8BIT must exist");
|
|
1115
|
+
assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
950
1118
|
}
|
data/ext/rubyx/src/stream.rs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
use crate::api;
|
|
2
1
|
use crate::python_ffi::PyObject;
|
|
3
2
|
use crate::ruby_helpers::runtime_error;
|
|
4
3
|
use crate::rubyx_object::{python_to_sendable, RubyxObject};
|
|
4
|
+
use crate::{api, ruby_helpers};
|
|
5
5
|
use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
|
|
6
|
+
use magnus::encoding::EncodingCapable;
|
|
6
7
|
use magnus::value::ReprValue;
|
|
7
8
|
use magnus::{IntoValue, Value};
|
|
8
9
|
use std::thread;
|
|
@@ -21,6 +22,8 @@ pub(crate) enum SendableValue {
|
|
|
21
22
|
Float(f64),
|
|
22
23
|
Str(String),
|
|
23
24
|
Bool(bool),
|
|
25
|
+
Bytes(Vec<u8>),
|
|
26
|
+
Set(Vec<SendableValue>),
|
|
24
27
|
List(Vec<SendableValue>),
|
|
25
28
|
Dict(Vec<(SendableValue, SendableValue)>),
|
|
26
29
|
PyObjectRef(usize),
|
|
@@ -38,7 +41,15 @@ impl TryInto<magnus::Value> for SendableValue {
|
|
|
38
41
|
SendableValue::Float(f) => f.into_value_with(&ruby),
|
|
39
42
|
SendableValue::Str(s) => s.as_str().into_value_with(&ruby),
|
|
40
43
|
SendableValue::Bool(b) => b.into_value_with(&ruby),
|
|
41
|
-
SendableValue::
|
|
44
|
+
SendableValue::Bytes(b) => {
|
|
45
|
+
let s = ruby.str_from_slice(&b);
|
|
46
|
+
s.enc_associate(ruby.find_encoding("ASCII-8BIT").ok_or(magnus::Error::new(
|
|
47
|
+
ruby_helpers::no_method_error(),
|
|
48
|
+
"No ASCII-8BIT",
|
|
49
|
+
))?)?;
|
|
50
|
+
s.as_value()
|
|
51
|
+
}
|
|
52
|
+
SendableValue::List(l) | SendableValue::Set(l) => {
|
|
42
53
|
let ruby_array = ruby.ary_new_capa(l.len());
|
|
43
54
|
for item in l {
|
|
44
55
|
let val: Value = item.try_into()?;
|
|
@@ -786,4 +797,139 @@ mod tests {
|
|
|
786
797
|
api.decref(os);
|
|
787
798
|
});
|
|
788
799
|
}
|
|
800
|
+
|
|
801
|
+
// ========== Set: SendableValue::Set → Ruby Array ==========
|
|
802
|
+
|
|
803
|
+
#[test]
|
|
804
|
+
#[serial]
|
|
805
|
+
fn test_set_converts_to_ruby_array() {
|
|
806
|
+
with_ruby_python(|_ruby, _api| {
|
|
807
|
+
let (tx, rx) = unbounded();
|
|
808
|
+
let (cancel_tx, _cancel_rx) = bounded(1);
|
|
809
|
+
|
|
810
|
+
thread::spawn(move || {
|
|
811
|
+
tx.send(Some(SendableValue::Set(vec![
|
|
812
|
+
SendableValue::Integer(1),
|
|
813
|
+
SendableValue::Integer(2),
|
|
814
|
+
SendableValue::Integer(3),
|
|
815
|
+
])))
|
|
816
|
+
.ok();
|
|
817
|
+
tx.send(None).ok();
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
let mut stream = AsyncStream::from_channel(rx, cancel_tx);
|
|
821
|
+
let val = stream.next().unwrap().unwrap();
|
|
822
|
+
|
|
823
|
+
let arr = magnus::RArray::try_convert(val).unwrap();
|
|
824
|
+
assert_eq!(arr.len(), 3);
|
|
825
|
+
|
|
826
|
+
let mut items: Vec<i64> = (0..arr.len())
|
|
827
|
+
.map(|i| i64::try_convert(arr.entry::<Value>(i as isize).unwrap()).unwrap())
|
|
828
|
+
.collect();
|
|
829
|
+
items.sort();
|
|
830
|
+
assert_eq!(items, vec![1, 2, 3]);
|
|
831
|
+
|
|
832
|
+
assert!(stream.next().is_none());
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#[test]
|
|
837
|
+
#[serial]
|
|
838
|
+
fn test_empty_set_converts_to_empty_ruby_array() {
|
|
839
|
+
with_ruby_python(|_ruby, _api| {
|
|
840
|
+
let (tx, rx) = unbounded();
|
|
841
|
+
let (cancel_tx, _cancel_rx) = bounded(1);
|
|
842
|
+
|
|
843
|
+
thread::spawn(move || {
|
|
844
|
+
tx.send(Some(SendableValue::Set(vec![]))).ok();
|
|
845
|
+
tx.send(None).ok();
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
let mut stream = AsyncStream::from_channel(rx, cancel_tx);
|
|
849
|
+
let val = stream.next().unwrap().unwrap();
|
|
850
|
+
|
|
851
|
+
let arr = magnus::RArray::try_convert(val).unwrap();
|
|
852
|
+
assert_eq!(arr.len(), 0);
|
|
853
|
+
|
|
854
|
+
assert!(stream.next().is_none());
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
#[test]
|
|
859
|
+
#[serial]
|
|
860
|
+
fn test_set_with_mixed_types_converts_to_ruby_array() {
|
|
861
|
+
with_ruby_python(|_ruby, _api| {
|
|
862
|
+
let (tx, rx) = unbounded();
|
|
863
|
+
let (cancel_tx, _cancel_rx) = bounded(1);
|
|
864
|
+
|
|
865
|
+
thread::spawn(move || {
|
|
866
|
+
tx.send(Some(SendableValue::Set(vec![
|
|
867
|
+
SendableValue::Integer(42),
|
|
868
|
+
SendableValue::Str("hello".to_string()),
|
|
869
|
+
SendableValue::Float(3.14),
|
|
870
|
+
SendableValue::Bool(true),
|
|
871
|
+
])))
|
|
872
|
+
.ok();
|
|
873
|
+
tx.send(None).ok();
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
let mut stream = AsyncStream::from_channel(rx, cancel_tx);
|
|
877
|
+
let val = stream.next().unwrap().unwrap();
|
|
878
|
+
|
|
879
|
+
let arr = magnus::RArray::try_convert(val).unwrap();
|
|
880
|
+
assert_eq!(arr.len(), 4);
|
|
881
|
+
|
|
882
|
+
assert_eq!(
|
|
883
|
+
i64::try_convert(arr.entry::<Value>(0).unwrap()).unwrap(),
|
|
884
|
+
42
|
|
885
|
+
);
|
|
886
|
+
assert_eq!(
|
|
887
|
+
String::try_convert(arr.entry::<Value>(1).unwrap()).unwrap(),
|
|
888
|
+
"hello"
|
|
889
|
+
);
|
|
890
|
+
assert!(
|
|
891
|
+
(f64::try_convert(arr.entry::<Value>(2).unwrap()).unwrap() - 3.14).abs() < 1e-9
|
|
892
|
+
);
|
|
893
|
+
assert!(bool::try_convert(arr.entry::<Value>(3).unwrap()).unwrap());
|
|
894
|
+
|
|
895
|
+
assert!(stream.next().is_none());
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
#[test]
|
|
900
|
+
#[serial]
|
|
901
|
+
fn test_set_in_stream_with_other_types() {
|
|
902
|
+
with_ruby_python(|_ruby, _api| {
|
|
903
|
+
let (tx, rx) = unbounded();
|
|
904
|
+
let (cancel_tx, _cancel_rx) = bounded(1);
|
|
905
|
+
|
|
906
|
+
thread::spawn(move || {
|
|
907
|
+
tx.send(Some(SendableValue::Integer(1))).ok();
|
|
908
|
+
tx.send(Some(SendableValue::Set(vec![
|
|
909
|
+
SendableValue::Integer(10),
|
|
910
|
+
SendableValue::Integer(20),
|
|
911
|
+
])))
|
|
912
|
+
.ok();
|
|
913
|
+
tx.send(Some(SendableValue::Str("after".to_string()))).ok();
|
|
914
|
+
tx.send(None).ok();
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
let mut stream = AsyncStream::from_channel(rx, cancel_tx);
|
|
918
|
+
|
|
919
|
+
// First: integer
|
|
920
|
+
let val = stream.next().unwrap().unwrap();
|
|
921
|
+
assert_eq!(i64::try_convert(val).unwrap(), 1);
|
|
922
|
+
|
|
923
|
+
// Second: set → array
|
|
924
|
+
let val = stream.next().unwrap().unwrap();
|
|
925
|
+
let arr = magnus::RArray::try_convert(val).unwrap();
|
|
926
|
+
assert_eq!(arr.len(), 2);
|
|
927
|
+
|
|
928
|
+
// Third: string
|
|
929
|
+
let val = stream.next().unwrap().unwrap();
|
|
930
|
+
assert_eq!(String::try_convert(val).unwrap(), "after");
|
|
931
|
+
|
|
932
|
+
assert!(stream.next().is_none());
|
|
933
|
+
});
|
|
934
|
+
}
|
|
789
935
|
}
|
data/lib/rubyx/railtie.rb
CHANGED
|
@@ -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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubyx-py
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Naiker
|
|
@@ -96,6 +96,7 @@ files:
|
|
|
96
96
|
- lib/rubyx/error.rb
|
|
97
97
|
- lib/rubyx/rails.rb
|
|
98
98
|
- lib/rubyx/railtie.rb
|
|
99
|
+
- lib/rubyx/tasks/rubyx.rake
|
|
99
100
|
- lib/rubyx/uv.rb
|
|
100
101
|
- lib/rubyx/version.rb
|
|
101
102
|
homepage: https://github.com/yinho999/rubyx
|