rubypython 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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