rubypython 0.5.3 → 0.6.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.
Binary file
@@ -1,3 +1,35 @@
1
+ === 0.6 / 2011-04-17
2
+ * Major Enhancements:
3
+ * Previous versions of RubyPython theoretically allowed the loading of
4
+ different Python interpreters during a single Ruby session. Because of the
5
+ likelihood of crashes, this functionality has been removed. Now, if you
6
+ attempt to start RubyPython more than once while specifying different
7
+ Python interpreters, RubyPython will print a warning and continue working
8
+ with the already loaded interpreter.
9
+ * The Python interpreter DLL will only be loaded once. It is configured with
10
+ a self-removing method, Interpreter#infect! instead of require/load reload
11
+ hacks.
12
+ * Removed RubyPython.configure; since the interpreter can only be configured
13
+ once, independent configuration no longer makes sense.
14
+ * Minor Enhancements:
15
+ * RubyPython interpreter initialization is done with a Mutex synchronization
16
+ to ensure that only one Python interpreter DLL is loaded.
17
+ * Added RubyPython::Tuple, a simple subclass of ::Array that will correctly
18
+ be converted to a Python tuple object such that isinstance(x, tuple)
19
+ returns True.
20
+ * Renamed RubyPython::PythonExec to RubyPython::Interpreter. Added some
21
+ helper methods to assist with comparison. Construction is with an options
22
+ hash.
23
+ * The #update methods on Python interpreter observers are now private. This
24
+ is an implementation detail, not a public interface. (The methods have also
25
+ been renamed to #python_interpreter_update.)
26
+ * Deprecation:
27
+ * RubyPython's legacy mode (automatic unboxing of Python proxy objects where
28
+ possible) has been deprecated and will be removed in the next non-bugfix
29
+ release after this version. Introduced a new private method
30
+ (RubyPython.legacy_mode?) to check if legacy mode is turned on so that the
31
+ deprecation warning is not printed in all uses of RubyPython.
32
+
1
33
  === 0.5.3 / 2011-10-22
2
34
  * Bug Fixes:
3
35
  * Improved 64-bit Python detection on Unixes with Linux-like semantics (e.g.,
@@ -1,7 +1,4 @@
1
1
  .autotest
2
- .gitignore
3
- .hgignore
4
- .hgtags
5
2
  .rspec
6
3
  Contributors.rdoc
7
4
  History.rdoc
@@ -14,17 +11,17 @@ autotest/discover.rb
14
11
  lib/rubypython.rb
15
12
  lib/rubypython/blankobject.rb
16
13
  lib/rubypython/conversion.rb
14
+ lib/rubypython/interpreter.rb
17
15
  lib/rubypython/legacy.rb
18
16
  lib/rubypython/macros.rb
19
17
  lib/rubypython/operators.rb
20
- lib/rubypython/options.rb
21
18
  lib/rubypython/pygenerator.rb
22
19
  lib/rubypython/pymainclass.rb
23
20
  lib/rubypython/pyobject.rb
24
21
  lib/rubypython/python.rb
25
22
  lib/rubypython/pythonerror.rb
26
- lib/rubypython/pythonexec.rb
27
23
  lib/rubypython/rubypyproxy.rb
24
+ lib/rubypython/tuple.rb
28
25
  lib/rubypython/type.rb
29
26
  spec/basic_spec.rb
30
27
  spec/callback_spec.rb
@@ -15,39 +15,34 @@
15
15
  # puts cPickle.dumps("RubyPython is awesome!").rubify
16
16
  # RubyPython.stop
17
17
  module RubyPython
18
- VERSION = '0.5.3' #:nodoc:
19
-
20
- # Do not load the FFI interface by default. Wait until the user asks for
21
- # it.
22
- @load_ffi = false
23
-
24
- # Indicates whether the \Python DLL has been loaded. For internal use
25
- # only.
26
- def self.load_ffi? #:nodoc:
27
- @load_ffi
28
- end
18
+ VERSION = '0.6.0'
29
19
  end
30
20
 
31
21
  require 'rubypython/blankobject'
32
- require 'rubypython/options'
22
+ require 'rubypython/interpreter'
33
23
  require 'rubypython/python'
34
24
  require 'rubypython/pythonerror'
35
25
  require 'rubypython/pyobject'
36
26
  require 'rubypython/rubypyproxy'
37
27
  require 'rubypython/pymainclass'
38
28
  require 'rubypython/pygenerator'
29
+ require 'rubypython/tuple'
30
+ require 'thread'
39
31
 
40
32
  module RubyPython
41
33
  class << self
42
- # Controls whether RubyPython is operating in <em>Normal Mode</em> or
43
- # <em>Legacy Mode</em>.
34
+ ##
35
+ # :attr_accessor:
36
+ # Controls whether RubyPython is operating in <em>Proxy Mode</em> or
37
+ # <em>Legacy Mode</em>. This behavioural difference is deprecated as of
38
+ # RubyPython 0.6 and will be removed in a subsequent release.
44
39
  #
45
- # === Normal Mode
40
+ # === Proxy Mode
46
41
  # By default, +legacy_mode+ is +false+, meaning that any object returned
47
- # from a \Python function call will be wrapped in an instance of
48
- # +RubyPyProxy+ or one of its subclasses. This allows \Python method
49
- # calls to be forwarded to the \Python object, even if it would otherwise
50
- # be a native Ruby object.
42
+ # from a \Python function call will be wrapped in a Ruby-Python proxy
43
+ # (an instance of +RubyPyProxy+ or one of its subclasses). This allows
44
+ # \Python method calls to be forwarded to the \Python object, even if it
45
+ # would otherwise be a native Ruby object.
51
46
  #
52
47
  # RubyPython.session do
53
48
  # string = RubyPython.import 'string'
@@ -60,8 +55,8 @@ module RubyPython
60
55
  # If +legacy_mode+ is +true+, RubyPython automatically tries to convert
61
56
  # returned objects to native Ruby object types. If there is no such
62
57
  # conversion, the object remains wrapped in +RubyPyProxy+. This
63
- # behaviour is the same as RubyPython 0.2 and earlier. This mode is not
64
- # recommended and may be phased out for RubyPython 1.0.
58
+ # behaviour is the same as RubyPython 0.2 and earlier. This mode is
59
+ # deprecated as of RubyPython 0.6 and will be removed.
65
60
  #
66
61
  # RubyPython.legacy_mode = true
67
62
  # RubyPython.session do
@@ -69,10 +64,32 @@ module RubyPython
69
64
  # ascii_letters = string.ascii_letters
70
65
  # puts ascii_letters.isalpha # throws NoMethodError
71
66
  # end
72
- attr_accessor :legacy_mode
67
+ def legacy_mode=(value)
68
+ warn_legacy_mode_deprecation unless defined? @legacy_mode
69
+ @legacy_mode = value
70
+ end
71
+
72
+ def legacy_mode
73
+ unless defined? @legacy_mode
74
+ warn_legacy_mode_deprecation
75
+ @legacy_mode = nil
76
+ end
77
+ @legacy_mode
78
+ end
73
79
 
74
- # Starts the \Python interpreter. Either +RubyPython.start+,
75
- # +RubyPython.session+, or +RubyPython.run+ must be run before using any
80
+ def legacy_mode?
81
+ @legacy_mode = nil unless defined? @legacy_mode
82
+ @legacy_mode
83
+ end
84
+ private :legacy_mode?
85
+
86
+ def warn_legacy_mode_deprecation
87
+ warn "RubyPython's Legacy Mode is deprecated and will be removed after version #{VERSION}."
88
+ end
89
+ private :warn_legacy_mode_deprecation
90
+
91
+ ## Starts the \Python interpreter. One of +RubyPython.start+,
92
+ # RubyPython.session+, or +RubyPython.run+ must be run before using any
76
93
  # \Python code. Returns +true+ if the interpreter was started; +false+
77
94
  # otherwise.
78
95
  #
@@ -90,27 +107,30 @@ module RubyPython
90
107
  # sys = RubyPython.import 'sys'
91
108
  # p sys.version # => "2.7.1"
92
109
  # RubyPython.stop
93
- #
94
- # *NOTE*: In the current version of RubyPython, it _is_ possible to
95
- # change \Python interpreters in a single Ruby process execution, but it
96
- # is *strongly* discouraged as this may lead to segmentation faults.
97
- # This feature is highly experimental and may be disabled in the future.
98
110
  def start(options = {})
99
- RubyPython.configure(options)
111
+ Mutex.new.synchronize do
112
+ # Has the Runtime interpreter been defined?
113
+ if self.const_defined?(:Runtime)
114
+ # If this constant is defined, then yes it is. Since it is, let's
115
+ # see if we should print a warning to the user.
116
+ unless Runtime == options
117
+ warn "The Python interpreter has already been loaded from #{Runtime.python} and cannot be changed in this process. Continuing with the current runtime."
118
+ end
119
+ else
120
+ interp = RubyPython::Interpreter.new(options)
121
+ if interp.valid?
122
+ self.const_set(:Runtime, interp)
123
+ else
124
+ raise RubyPython::InvalidInterpreter, "An invalid interpreter was specified."
125
+ end
126
+ end
100
127
 
101
- unless @load_ffi
102
- @load_ffi = true
103
- @reload = false
104
- reload_library
128
+ unless defined? RubyPython::Python.ffi_libraries
129
+ Runtime.__send__(:infect!, RubyPython::Python)
130
+ end
105
131
  end
106
132
 
107
133
  return false if RubyPython::Python.Py_IsInitialized != 0
108
-
109
- if @reload
110
- reload_library
111
- @reload = false
112
- end
113
-
114
134
  RubyPython::Python.Py_Initialize
115
135
  notify :start
116
136
  true
@@ -195,7 +215,7 @@ module RubyPython
195
215
  # run(options = {}) { block to execute in RubyPython context }
196
216
  def run(options = {}, &block)
197
217
  start(options)
198
- module_eval(&block)
218
+ self.module_eval(&block)
199
219
  ensure
200
220
  stop
201
221
  end
@@ -218,23 +238,28 @@ module RubyPython
218
238
  # This feature is highly experimental and may be disabled in the future.
219
239
  def start_from_virtualenv(virtualenv)
220
240
  result = start(:python_exe => File.join(virtualenv, "bin", "python"))
221
- activate
241
+ activate_virtualenv
222
242
  result
223
243
  end
224
244
 
225
- # Returns an object describing the currently active Python interpreter.
245
+ # Returns an object describing the active Python interpreter. Returns
246
+ # +nil+ if there is no active interpreter.
226
247
  def python
227
- RubyPython::Python::EXEC
248
+ if self.const_defined? :Runtime
249
+ self::Runtime
250
+ else
251
+ nil
252
+ end
228
253
  end
229
254
 
230
255
  # Used to activate the virtualenv.
231
- def activate
256
+ def activate_virtualenv
232
257
  imp = import("imp")
233
258
  imp.load_source("activate_this",
234
- File.join(File.dirname(RubyPython::Python::EXEC.python),
259
+ File.join(File.dirname(RubyPython::Runtime.python),
235
260
  "activate_this.py"))
236
261
  end
237
- private :activate
262
+ private :activate_virtualenv
238
263
 
239
264
  def add_observer(object)
240
265
  @observers ||= []
@@ -247,21 +272,10 @@ module RubyPython
247
272
  @observers ||= []
248
273
  @observers.each do |o|
249
274
  next if nil === o
250
- o.update status
275
+ o.__send__ :python_interpreter_update, status
251
276
  end
252
277
  end
253
278
  private :notify
254
-
255
- def reload_library
256
- # Invalidate the current Python instance, if defined.
257
- if defined? RubyPython::Python::EXEC and RubyPython::Python::EXEC
258
- RubyPython::Python::EXEC.instance_eval { invalidate! }
259
- end
260
- remove_const :Python
261
- load RubyPython::PYTHON_RB
262
- true
263
- end
264
- private :reload_library
265
279
  end
266
280
 
267
281
  add_observer PyMain
@@ -30,8 +30,8 @@ module RubyPython::Conversion
30
30
  pList
31
31
  end
32
32
 
33
- # Convert a Ruby Array to \Python Tuple. Returns an FFI::Pointer to a
34
- # PyTupleObject.
33
+ # Convert a Ruby Array (including the subclass RubyPython::Tuple) to
34
+ # \Python \tuple. Returns an FFI::Pointer to a PyTupleObject.
35
35
  def self.rtopArrayToTuple(rArray)
36
36
  pList = rtopArrayToList(rArray)
37
37
  pTuple = RubyPython::Python.PySequence_Tuple(pList)
@@ -126,6 +126,8 @@ module RubyPython::Conversion
126
126
  case rObj
127
127
  when String
128
128
  rtopString rObj
129
+ when RubyPython::Tuple
130
+ rtopArrayToTuple rObj
129
131
  when Array
130
132
  # If this object is going to be used as a hash key we should make it a
131
133
  # tuple instead of a list
@@ -221,13 +223,13 @@ module RubyPython::Conversion
221
223
  RubyPython::Python.PyFloat_AsDouble(pNum)
222
224
  end
223
225
 
224
- # Convert an FFI::Pointer to a \Python Tuple (PyTupleObject) to a Ruby
225
- # Array.
226
+ # Convert an FFI::Pointer to a \Python Tuple (PyTupleObject) to an
227
+ # instance of RubyPython::Tuple, a subclass of the Ruby Array class.
226
228
  def self.ptorTuple(pTuple)
227
229
  pList = RubyPython::Python.PySequence_List pTuple
228
230
  rArray = ptorList pList
229
231
  RubyPython::Python.Py_DecRef pList
230
- rArray
232
+ RubyPython::Tuple.tuple(rArray)
231
233
  end
232
234
 
233
235
  # Convert an FFI::Pointer to a \Python Dictionary (PyDictObject) to a Ruby
@@ -0,0 +1,250 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ class RubyPython::InvalidInterpreter < Exception
4
+ end
5
+
6
+ ##
7
+ # An instance of this class represents information about a particular
8
+ # \Python interpreter.
9
+ #
10
+ # This class represents the current Python interpreter.
11
+ # A class that represents a \Python executable.
12
+ #
13
+ # End users may get the instance that represents the current running \Python
14
+ # interpreter (from +RubyPython.python+), but should not directly
15
+ # instantiate this class.
16
+ class RubyPython::Interpreter
17
+
18
+ ##
19
+ # Compare the current Interpreter to the provided Interpreter or
20
+ # configuration hash. A configuration hash will be converted to an
21
+ # Interpreter object before being compared.
22
+ # :python_exe basename is used. If comparing against another Interpreter
23
+ # object, the Interpreter basename and version are used.
24
+ def ==(other)
25
+ other = self.class.new(other) if other.kind_of? Hash
26
+ return false unless other.kind_of? self.class
27
+ (self.version == other.version) && (self.version_name == other.version_name)
28
+ end
29
+
30
+ ##
31
+ # Create a new instance of an Interpreter instance describing a particular
32
+ # \Python executable and shared library.
33
+ #
34
+ # Expects a hash that matches the configuration options provided to
35
+ # RubyPython.start; currently only one value is recognized in that hash:
36
+ #
37
+ # * <tt>:python_exe</tt>: Specifies the name of the python executable to
38
+ # run.
39
+ def initialize(options = {})
40
+ @python_exe = options[:python_exe]
41
+ # Windows: 'C:\\Python27\python.exe'
42
+ # Mac OS X: '/usr/bin/
43
+ rc, @python = runpy "import sys; print sys.executable"
44
+ if rc.exitstatus.nonzero?
45
+ raise RubyPython::InvalidInterpreter, "An invalid interpreter was specified."
46
+ end
47
+ rc, @version = runpy "import sys; print '%d.%d' % sys.version_info[:2]"
48
+ rc, @sys_prefix = runpy "import sys; print sys.prefix"
49
+
50
+ if FFI::Platform.windows?
51
+ flat_version = @version.tr('.', '')
52
+ basename = File.basename(@python, '.exe')
53
+
54
+ if basename =~ /(?:#{@version}|#{flat_version})$/
55
+ @version_name = basename
56
+ else
57
+ @version_name = "#{basename}#{flat_version}"
58
+ end
59
+ else
60
+ basename = File.basename(@python)
61
+
62
+ if basename =~ /#{@version}/
63
+ @version_name = basename
64
+ else
65
+ @version_name = "#{basename}#{@version}"
66
+ end
67
+ end
68
+
69
+ @library = find_python_lib
70
+ end
71
+
72
+ def find_python_lib
73
+ # By default, the library name will be something like
74
+ # libpython2.6.so, but that won't always work.
75
+ @libbase = "#{FFI::Platform::LIBPREFIX}#{@version_name}"
76
+ @libext = FFI::Platform::LIBSUFFIX
77
+ @libname = "#{@libbase}.#{@libext}"
78
+
79
+ # We may need to look in multiple locations for Python, so let's
80
+ # build this as an array.
81
+ @locations = [ File.join(@sys_prefix, "lib", @libname) ]
82
+
83
+ if FFI::Platform.mac?
84
+ # On the Mac, let's add a special case that has even a different
85
+ # @libname. This may not be fully useful on future versions of OS
86
+ # X, but it should work on 10.5 and 10.6. Even if it doesn't, the
87
+ # next step will (/usr/lib/libpython<version>.dylib is a symlink
88
+ # to the correct location).
89
+ @locations << File.join(@sys_prefix, "Python")
90
+ # Let's also look in the location that was originally set in this
91
+ # library:
92
+ File.join(@sys_prefix, "lib", "#{@realname}", "config", @libname)
93
+ end
94
+
95
+ if FFI::Platform.unix?
96
+ # On Unixes, let's look in some standard alternative places, too.
97
+ # Just in case. Some Unixes don't include a .so symlink when they
98
+ # should, so let's look for the base case of .so.1, too.
99
+ [ @libname, "#{@libname}.1" ].each do |name|
100
+ @locations << File.join("/opt/local/lib", name)
101
+ @locations << File.join("/opt/lib", name)
102
+ @locations << File.join("/usr/local/lib", name)
103
+ @locations << File.join("/usr/lib", name)
104
+ @locations << File.join("/opt/local/lib64", name)
105
+ @locations << File.join("/opt/lib64", name)
106
+ @locations << File.join("/usr/local/lib64", name)
107
+ @locations << File.join("/usr/lib64", name)
108
+ end
109
+ end
110
+
111
+ if FFI::Platform.windows?
112
+ # On Windows, the appropriate DLL is usually be found in
113
+ # %SYSTEMROOT%\system or %SYSTEMROOT%\system32; as a fallback we'll
114
+ # use C:\Windows\system{,32} as well as the install directory and the
115
+ # install directory + libs.
116
+ system_root = File.expand_path(ENV['SYSTEMROOT']).gsub(/\\/, '/')
117
+ @locations << File.join(system_root, 'system', @libname)
118
+ @locations << File.join(system_root, 'system32', @libname)
119
+ @locations << File.join("C:/WINDOWS", "System", @libname)
120
+ @locations << File.join("C:/WINDOWS", "System32", @libname)
121
+ @locations << File.join(sys_prefix, @libname)
122
+ @locations << File.join(sys_prefix, 'libs', @libname)
123
+ @locations << File.join(system_root, "SysWOW64", @libname)
124
+ @locations << File.join("C:/WINDOWS", "SysWOW64", @libname)
125
+ end
126
+
127
+ # Let's add alternative extensions; again, just in case.
128
+ @locations.dup.each do |location|
129
+ path = File.dirname(location)
130
+ base = File.basename(location, ".#{@libext}")
131
+ @locations << File.join(path, "#{base}.so") # Standard Unix
132
+ @locations << File.join(path, "#{base}.dylib") # Mac OS X
133
+ @locations << File.join(path, "#{base}.dll") # Windows
134
+ end
135
+
136
+ # Remove redundant locations
137
+ @locations.uniq!
138
+
139
+ library = nil
140
+
141
+ @locations.each do |location|
142
+ if File.exists? location
143
+ library = location
144
+ break
145
+ end
146
+ end
147
+
148
+ library
149
+ end
150
+ private :find_python_lib
151
+
152
+ def valid?
153
+ if @python.nil? or @python.empty?
154
+ false
155
+ elsif @library.nil? or @library.empty?
156
+ false
157
+ else
158
+ true
159
+ end
160
+ end
161
+
162
+ ##
163
+ # The name of the \Python executable that is used. This is the value of
164
+ # 'sys.executable' for the \Python interpreter provided in
165
+ # <tt>:python_exe</tt> or 'python' if it is not provided.
166
+ #
167
+ # On Mac OS X Lion (10.7), this value is '/usr/bin/python' for 'python'.
168
+ attr_reader :python
169
+ ##
170
+ # The version of the \Python interpreter. This is a decimalized version of
171
+ # 'sys.version_info[:2]' (such that \Python 2.7.1 is reported as '2.7').
172
+ attr_reader :version
173
+ ##
174
+ # The system prefix for the \Python interpreter. This is the value of
175
+ # 'sys.prefix'.
176
+ attr_reader :sys_prefix
177
+ ##
178
+ # The basename of the \Python interpreter with a version number. This is
179
+ # mostly an intermediate value used to find the shared \Python library,
180
+ # but /usr/bin/python is often a link to /usr/bin/python2.7 so it may be
181
+ # of value. Note that this does *not* include the full path to the
182
+ # interpreter.
183
+ attr_reader :version_name
184
+
185
+ # The \Python library.
186
+ attr_reader :library
187
+
188
+ # Run a Python command-line command.
189
+ def runpy(command)
190
+ i = @python || @python_exe || 'python'
191
+ if FFI::Platform.windows?
192
+ o = %x(#{i} -c "#{command}" 2> NUL:)
193
+ else
194
+ o = %x(#{i} -c "#{command}" 2> /dev/null)
195
+ end
196
+
197
+ [ $?, o.chomp ]
198
+ end
199
+ private :runpy
200
+
201
+ def inspect(debug = false)
202
+ if debug
203
+ debug_s
204
+ elsif @python
205
+ "#<#{self.class}: #{python} v#{version} #{sys_prefix} #{version_name}>"
206
+ else
207
+ "#<#{self.class}: invalid interpreter>"
208
+ end
209
+ end
210
+
211
+ def debug_s(format = nil)
212
+ system = ""
213
+ system << "windows " if FFI::Platform.windows?
214
+ system << "mac " if FFI::Platform.mac?
215
+ system << "unix " if FFI::Platform.unix?
216
+ system << "unknown " if system.empty?
217
+
218
+ case format
219
+ when :report
220
+ s = <<-EOS
221
+ python_exe: #{@python_exe}
222
+ python: #{@python}
223
+ version: #{@version}
224
+ sys_prefix: #{@sys_prefix}
225
+ version_name: #{@version_name}
226
+ platform: #{system.chomp}
227
+ library: #{@library.inspect}
228
+ libbase: #{@libbase}
229
+ libext: #{@libext}
230
+ libname: #{@libname}
231
+ locations: #{@locations.inspect}
232
+ EOS
233
+ else
234
+ s = "#<#{self.class}: "
235
+ s << "python_exe=#{@python_exe.inspect} "
236
+ s << "python=#{@python.inspect} "
237
+ s << "version=#{@version.inspect} "
238
+ s << "sys_prefix=#{@sys_prefix.inspect} "
239
+ s << "version_name=#{@version_name.inspect} "
240
+ s << system
241
+ s << "library=#{@library.inspect} "
242
+ s << "libbase=#{@libbase.inspect} "
243
+ s << "libext=#{@libext.inspect} "
244
+ s << "libname=#{@libname.inspect} "
245
+ s << "locations=#{@locations.inspect}"
246
+ end
247
+
248
+ s
249
+ end
250
+ end