rubypython 0.3.2 → 0.5.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.
Files changed (51) hide show
  1. data/.autotest +3 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +13 -0
  4. data/.hgignore +14 -0
  5. data/.hgtags +7 -0
  6. data/.rspec +1 -1
  7. data/Contributors.rdoc +9 -0
  8. data/History.rdoc +148 -0
  9. data/{License.txt → License.rdoc} +7 -1
  10. data/Manifest.txt +15 -10
  11. data/PostInstall.txt +11 -4
  12. data/README.rdoc +272 -0
  13. data/Rakefile +107 -22
  14. data/autotest/discover.rb +1 -0
  15. data/lib/rubypython.rb +214 -120
  16. data/lib/rubypython/blankobject.rb +16 -14
  17. data/lib/rubypython/conversion.rb +242 -173
  18. data/lib/rubypython/legacy.rb +30 -31
  19. data/lib/rubypython/macros.rb +43 -34
  20. data/lib/rubypython/operators.rb +103 -101
  21. data/lib/rubypython/options.rb +41 -44
  22. data/lib/rubypython/pygenerator.rb +61 -0
  23. data/lib/rubypython/pymainclass.rb +46 -29
  24. data/lib/rubypython/pyobject.rb +193 -177
  25. data/lib/rubypython/python.rb +189 -176
  26. data/lib/rubypython/pythonerror.rb +54 -63
  27. data/lib/rubypython/pythonexec.rb +123 -0
  28. data/lib/rubypython/rubypyproxy.rb +213 -137
  29. data/lib/rubypython/type.rb +20 -0
  30. data/spec/basic_spec.rb +50 -0
  31. data/spec/callback_spec.rb +7 -17
  32. data/spec/conversion_spec.rb +7 -21
  33. data/spec/legacy_spec.rb +1 -16
  34. data/spec/pymainclass_spec.rb +6 -15
  35. data/spec/pyobject_spec.rb +39 -64
  36. data/spec/python_helpers/basics.py +20 -0
  37. data/spec/python_helpers/objects.py +24 -20
  38. data/spec/pythonerror_spec.rb +5 -17
  39. data/spec/refcnt_spec.rb +4 -10
  40. data/spec/rubypyclass_spec.rb +1 -11
  41. data/spec/rubypyproxy_spec.rb +45 -54
  42. data/spec/rubypython_spec.rb +45 -57
  43. data/spec/spec_helper.rb +49 -33
  44. metadata +87 -63
  45. data.tar.gz.sig +0 -0
  46. data/History.markdown +0 -97
  47. data/README.markdown +0 -105
  48. data/lib/rubypython/core_ext/string.rb +0 -7
  49. data/lib/rubypython/version.rb +0 -9
  50. data/spec/python_helpers/objects.pyc +0 -0
  51. metadata.gz.sig +0 -0
data/Rakefile CHANGED
@@ -1,22 +1,107 @@
1
- require 'rspec/core/rake_task'
2
- require 'yard'
3
-
4
- desc "Run all examples"
5
- RSpec::Core::RakeTask.new('spec') do |t|
6
- t.pattern = 'spec/**/*_spec.rb'
7
- t.rspec_opts = '-t ~@slow' unless ENV['filter'] == 'none'
8
- end
9
-
10
- desc "Run all examples with RCov"
11
- RSpec::Core::RakeTask.new('spec:rcov') do |t|
12
- t.pattern = 'spec/**/*.rb'
13
- t.rspec_opts = '--tag ~slow:true' unless ENV['filter'] == 'none'
14
- t.rcov = true
15
- t.rcov_opts = ['--exclude', 'spec']
16
- end
17
-
18
- YARD::Rake::YardocTask.new do |t|
19
- t.options = [ '--markup','markdown', '--title', 'RubyPython Documentation' ]
20
- end
21
-
22
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :doofus
7
+ Hoe.plugin :gemspec
8
+ Hoe.plugin :git
9
+ Hoe.plugin :hg
10
+
11
+ Hoe.spec 'rubypython' do |spec|
12
+ spec.rubyforge_name = spec.name
13
+
14
+ developer('Steeve Morin', 'swiuzzz+rubypython@gmail.com')
15
+ developer('Austin Ziegler', 'austin@rubyforge.org')
16
+ developer('Zach Raines', 'raineszm+rubypython@gmail.com')
17
+
18
+ spec.remote_rdoc_dir = 'rdoc'
19
+ spec.rsync_args << ' --exclude=statsvn/'
20
+
21
+ spec.history_file = 'History.rdoc'
22
+ spec.readme_file = 'README.rdoc'
23
+ spec.extra_rdoc_files = FileList["*.rdoc"].to_a
24
+
25
+ spec.extra_deps << ['ffi', '~> 1.0.7']
26
+ spec.extra_deps << ['blankslate', '>= 2.1.2.3']
27
+
28
+ spec.extra_dev_deps << ['rspec', '~> 2.0']
29
+ spec.extra_dev_deps << ['tilt', '~> 1.0']
30
+
31
+ spec.spec_extras[:requirements] = [ "Python, ~> 2.4" ]
32
+ end
33
+
34
+ namespace :website do
35
+ desc "Build the website files."
36
+ task :build => [ "website/index.html" ]
37
+
38
+ deps = FileList["website/**/*"].exclude { |f| File.directory? f }
39
+ deps.include(*%w(Rakefile))
40
+ deps.include(*FileList["*.rdoc"].to_a)
41
+ deps.exclude(*%w(website/index.html website/images/*))
42
+
43
+ file "website/index.html" => deps do |t|
44
+ require 'tilt'
45
+ require 'rubypython'
46
+
47
+ puts "Generating #{t.name}…"
48
+
49
+ # Let's modify the rdoc for presenation purposes.
50
+ body_rdoc = File.read("README.rdoc")
51
+
52
+ contrib = File.read("Contributors.rdoc")
53
+ body_rdoc.gsub!(/^:include: Contributors.rdoc/, contrib)
54
+
55
+ license = File.read("License.rdoc")
56
+ body_rdoc.sub!(/^:include: License.rdoc/, license)
57
+ toc_elements = body_rdoc.scan(/^(=+) (.*)$/)
58
+ toc_elements.map! { |e| [ e[0].count('='), e[1] ] }
59
+ body_rdoc.gsub!(/^(=.*)/) { "#{$1.downcase}" }
60
+ body = Tilt::RDocTemplate.new(nil) { body_rdoc }.render
61
+
62
+ title = nil
63
+ body.gsub!(%r{<h1>(.*)</h1>}) { title = $1; "" }
64
+
65
+ toc_elements = toc_elements.select { |e| e[0].between?(2, 3) }
66
+
67
+ last_level = 0
68
+ toc = ""
69
+
70
+ toc_elements.each do |element|
71
+ level, text = *element
72
+ ltext = text.downcase
73
+ id = text.downcase.gsub(/[^a-z]+/, '-')
74
+
75
+ body.gsub!(%r{<h#{level}>#{ltext}</h#{level}>}) {
76
+ %Q(<h#{level} id="#{id}">#{ltext}</h#{level}>)
77
+ }
78
+
79
+ if last_level != level
80
+ if level > last_level
81
+ toc << "<ol>"
82
+ else
83
+ toc << "</li></ol></li>"
84
+ end
85
+
86
+ last_level = level
87
+ end
88
+
89
+ toc << %Q(<li><a href="##{id}">#{text}</a>)
90
+ end
91
+ toc << "</li></ol>"
92
+
93
+ template = Tilt.new("website/index.rhtml", :trim => "<>%")
94
+ context = {
95
+ :title => title,
96
+ :toc => toc,
97
+ :body => body,
98
+ :download => "http://rubyforge.org/frs/?group_id=6737",
99
+ :version => RubyPython::VERSION,
100
+ :modified => Time.now
101
+ }
102
+ File.open(t.name, "w") { |f| f.write template.render(self, context) } end
103
+ end
104
+
105
+ task "docs" => "website:build"
106
+
107
+ # vim: syntax=ruby
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/lib/rubypython.rb CHANGED
@@ -1,176 +1,270 @@
1
- require 'rubypython/core_ext/string'
1
+ # RubyPython is a bridge between the Ruby and \Python interpreters. It
2
+ # embeds a \Python interpreter in the Ruby application's process using FFI
3
+ # and provides a means for wrapping, converting, and calling \Python objects
4
+ # and methods.
5
+ #
6
+ # == Usage
7
+ # The \Python interpreter must be started before the RubyPython bridge is
8
+ # functional. The user can either manually manage the running of the
9
+ # interpreter as shown below, or use the +RubyPython.run+ or
10
+ # +RubyPython.session+ methods to automatically start and stop the
11
+ # interpreter.
12
+ #
13
+ # RubyPython.start
14
+ # cPickle = RubyPython.import "cPickle"
15
+ # puts cPickle.dumps("RubyPython is awesome!").rubify
16
+ # RubyPython.stop
17
+ module RubyPython
18
+ VERSION = '0.5.0' #: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
29
+ end
30
+
31
+ require 'rubypython/blankobject'
2
32
  require 'rubypython/options'
3
33
  require 'rubypython/python'
4
34
  require 'rubypython/pythonerror'
5
35
  require 'rubypython/pyobject'
6
36
  require 'rubypython/rubypyproxy'
7
37
  require 'rubypython/pymainclass'
38
+ require 'rubypython/pygenerator'
8
39
 
9
-
10
- #This module provides the direct user interface for the RubyPython extension.
11
- #
12
- #RubyPython interfaces to the Python C API via the {Python} module using the
13
- #Ruby FFI gem. However, the end user should only worry about dealing with the
14
- #methods made avaiable via the RubyPython module.
15
- #
16
- #Usage
17
- #-----
18
- #It is important to remember that the Python Interpreter must be
19
- #started before the bridge is functional. This will start the embedded
20
- #interpreter. If this approach is used, the user should remember to call
21
- #RubyPython.stop when they are finished with Python.
22
- #@example
23
- # RubyPython.start
24
- # cPickle = RubyPython.import "cPickle"
25
- # puts cPickle.dumps("RubyPython is awesome!").rubify
26
- # RubyPython.stop
27
- #
28
- #Legacy Mode vs Normal Mode
29
- #---------------------------
30
- #By default RubyPython always returns a proxy class which refers method calls to
31
- #the wrapped Python object. If you instead would like RubyPython to aggressively
32
- #attempt conversion of return values, as it did in RubyPython 0.2.x, then you
33
- #should set {RubyPython.legacy_mode} to true. In this case RubyPython will
34
- #attempt to convert any return value from Python to a native Ruby type, and only
35
- #return a proxy if conversion is not possible. For further examples see
36
- #{RubyPython.legacy_mode}.
37
40
  module RubyPython
38
-
39
-
40
41
  class << self
41
-
42
- #Determines whether RubyPython is operating in Normal Mode or Legacy Mode.
43
- #If legacy_mode is true, RubyPython switches into a mode compatible with
44
- #versions < 0.3.0. All Python objects returned by method invocations are
45
- #automatically converted to natve Ruby Types if RubyPython knows how to do
46
- #this. Only if no such conversion is known are the objects wrapped in proxy
47
- #objects. Otherwise RubyPython automatically wraps all returned objects as
48
- #an instance of {RubyPyProxy} or one of its subclasses.
49
- #@return [Boolean]
50
- #@example Normal Mode
51
- # RubyPython.start
52
- # string = RubyPython.import 'string'
53
- # ascii_letters = string.ascii_letters # Here ascii_letters is a proxy object
54
- # puts ascii_letters.rubify # we use the rubify method to convert it to a
55
- # # native type
56
- # RubyPython.stop
57
- #
58
- #@example Legacy Mode
59
- # RubyPython.legacy_mode = true
60
- # RubyPython.start
61
- # string = RubyPython.import 'string'
62
- # ascii_letters = string.ascii_letters # Here ascii_letters is a native ruby string
63
- # puts ascii_letters # No explicit conversion is neccessary
64
- # RubyPython.stop
42
+ # Controls whether RubyPython is operating in <em>Normal Mode</em> or
43
+ # <em>Legacy Mode</em>.
44
+ #
45
+ # === Normal Mode
46
+ # 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.
51
+ #
52
+ # RubyPython.session do
53
+ # string = RubyPython.import 'string'
54
+ # ascii_letters = string.ascii_letters
55
+ # puts ascii_letters.isalpha # => True
56
+ # puts ascii_letters.rubify.isalpha # throws NoMethodError
57
+ # end
58
+ #
59
+ # === Legacy Mode
60
+ # If +legacy_mode+ is +true+, RubyPython automatically tries to convert
61
+ # returned objects to native Ruby object types. If there is no such
62
+ # 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.
65
+ #
66
+ # RubyPython.legacy_mode = true
67
+ # RubyPython.session do
68
+ # string = RubyPython.import 'string'
69
+ # ascii_letters = string.ascii_letters
70
+ # puts ascii_letters.isalpha # throws NoMethodError
71
+ # end
65
72
  attr_accessor :legacy_mode
66
73
 
67
- #Starts ups the Python interpreter. This method **must** be run
68
- #before using any Python code. The only alternatives are use of the
69
- #{session} and {run} methods. If the Python interpreter needs to
70
- #be loaded or reloaded, it will be done here.
71
- #@return [Boolean] returns true if the interpreter was started here
72
- # and false otherwise
73
- def start
74
- unless @loaded
75
- @loaded = true
74
+ # Starts the \Python interpreter. Either +RubyPython.start+,
75
+ # +RubyPython.session+, or +RubyPython.run+ must be run before using any
76
+ # \Python code. Returns +true+ if the interpreter was started; +false+
77
+ # otherwise.
78
+ #
79
+ # [options] Configures the interpreter prior to starting it. Principally
80
+ # used to provide an alternative \Python interpreter to start.
81
+ #
82
+ # With no options provided:
83
+ # RubyPython.start
84
+ # sys = RubyPython.import 'sys'
85
+ # p sys.version # => "2.6.6"
86
+ # RubyPython.stop
87
+ #
88
+ # With an alternative \Python executable:
89
+ # RubyPython.start(:python_exe => 'python2.7')
90
+ # sys = RubyPython.import 'sys'
91
+ # p sys.version # => "2.7.1"
92
+ # 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
+ def start(options = {})
99
+ RubyPython.configure(options)
100
+
101
+ unless @load_ffi
102
+ @load_ffi = true
103
+ @reload = false
76
104
  reload_library
77
105
  end
78
- if Python.Py_IsInitialized != 0
79
- return false
80
- end
106
+
107
+ return false if RubyPython::Python.Py_IsInitialized != 0
108
+
81
109
  if @reload
82
110
  reload_library
83
111
  @reload = false
84
112
  end
85
- Python.Py_Initialize
113
+
114
+ RubyPython::Python.Py_Initialize
86
115
  notify :start
87
116
  true
88
117
  end
89
118
 
90
- #Stops the Python interpreter if it is running. Returns true if the
91
- #intepreter is stopped by this invocation. All wrapped Python objects
92
- #should be considered invalid after invocation of this method.
93
- #@return [Boolean] returns true if the interpreter was stopped here
94
- # and false otherwise
119
+ # Stops the \Python interpreter if it is running. Returns +true+ if the
120
+ # intepreter is stopped. All wrapped \Python objects are invalid after
121
+ # invocation of this method. If you need the values within the \Python
122
+ # proxy objects, be sure to call +RubyPyProxy#rubify+ on them.
95
123
  def stop
96
- if Python.Py_IsInitialized !=0
124
+ if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
97
125
  Python.Py_Finalize
98
126
  notify :stop
99
- return true
127
+ true
128
+ else
129
+ false
100
130
  end
101
- false
102
131
  end
103
132
 
104
- #Import a Python module into the interpreter and return a proxy object
105
- #for it. This is the preferred way to gain access to Python object.
106
- #@param [String] mod_name the name of the module to import
107
- #@return [RubyPyModule] a proxy object wrapping the requested
108
- #module
133
+ # Import a \Python module into the interpreter and return a proxy object
134
+ # for it.
135
+ #
136
+ # This is the preferred way to gain access to \Python objects.
137
+ #
138
+ # [mod_name] The name of the module to import.
109
139
  def import(mod_name)
110
- pModule = Python.PyImport_ImportModule mod_name
111
- if(PythonError.error?)
112
- raise PythonError.handle_error
140
+ if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
141
+ pModule = Python.PyImport_ImportModule mod_name
142
+ raise PythonError.handle_error if PythonError.error?
143
+ pymod = PyObject.new pModule
144
+ RubyPyModule.new(pymod)
145
+ else
146
+ raise "Python has not been started."
113
147
  end
114
- pymod = PyObject.new pModule
115
- RubyPyModule.new(pymod)
116
148
  end
117
149
 
118
- #Execute the given block, starting the Python interperter before its execution
119
- #and stopping the interpreter after its execution. The last expression of the
120
- #block is returned; be careful that this is not a Python object as it will
121
- #become invalid when the interpreter is stopped.
122
- #@param [Block] block the code to be executed while the interpreter is running
123
- #@return the result of evaluating the given block
124
- def session
125
- start
126
- result = yield
150
+ # Starts the \Python interpreter (optionally with options) and +yields+
151
+ # to the provided block. When the block exits for any reason, the
152
+ # \Python interpreter is stopped automatically.
153
+ #
154
+ # The last executed expression of the block is returned. Be careful that
155
+ # the last expression of the block does not return a RubyPyProxy object,
156
+ # because the proxy object will be invalidated when the interpreter is
157
+ # stopped.
158
+ #
159
+ # [options] Configures the interpreter prior to starting it. Principally
160
+ # used to provide an alternative \Python interpreter to start.
161
+ #
162
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to change
163
+ # \Python interpreters in a single Ruby process execution, but it is
164
+ # *strongly* discouraged as this may lead to segmentation faults. This
165
+ # feature is highly experimental and may be disabled in the future.
166
+ #
167
+ # :call-seq:
168
+ # session(options = {}) { block to execute }
169
+ def session(options = {})
170
+ start(options)
171
+ yield
172
+ ensure
127
173
  stop
128
- result
129
174
  end
130
175
 
131
- #The same as {session} except that the block is executed within the scope
132
- #of the RubyPython module.
133
- #@return the result of evaluating the given block.
134
- def run(&block)
135
- start
136
- result = module_eval(&block)
176
+ # Starts the \Python interpreter (optionally with options) and executes
177
+ # the provided block in the RubyPython module scope. When the block
178
+ # exits for any reason, the \Python interpreter is stopped
179
+ # automatically.
180
+ #
181
+ # The last executed expression of the block is returned. Be careful that
182
+ # the last expression of the block does not return a RubyPyProxy object,
183
+ # because the proxy object will be invalidated when the interpreter is
184
+ # stopped.
185
+ #
186
+ # [options] Configures the interpreter prior to starting it. Principally
187
+ # used to provide an alternative \Python interpreter to start.
188
+ #
189
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to
190
+ # change \Python interpreters in a single Ruby process execution, but it
191
+ # is *strongly* discouraged as this may lead to segmentation faults.
192
+ # This feature is highly experimental and may be disabled in the future.
193
+ #
194
+ # :call-seq:
195
+ # run(options = {}) { block to execute in RubyPython context }
196
+ def run(options = {}, &block)
197
+ start(options)
198
+ module_eval(&block)
199
+ ensure
137
200
  stop
201
+ end
202
+
203
+ # Starts the \Python interpreter for a
204
+ # {virtualenv}[http://pypi.python.org/pypi/virtualenv] virtual
205
+ # environment. Returns +true+ if the interpreter was started.
206
+ #
207
+ # [virtualenv] The root path to the virtualenv-installed \Python
208
+ # interpreter.
209
+ #
210
+ # RubyPython.start_from_virtualenv('/path/to/virtualenv')
211
+ # sys = RubyPython.import 'sys'
212
+ # p sys.version # => "2.7.1"
213
+ # RubyPython.stop
214
+ #
215
+ # *NOTE*: In the current version of RubyPython, it _is_ possible to
216
+ # change \Python interpreters in a single Ruby process execution, but it
217
+ # is *strongly* discouraged as this may lead to segmentation faults.
218
+ # This feature is highly experimental and may be disabled in the future.
219
+ def start_from_virtualenv(virtualenv)
220
+ result = start(:python => File.join(virtualenv, "bin", "python"))
221
+ activate
138
222
  result
139
223
  end
140
224
 
141
- private
225
+ # Returns an object describing the currently active Python interpreter.
226
+ def python
227
+ RubyPython::Python::EXEC
228
+ end
229
+
230
+ # Used to activate the virtualenv.
231
+ def activate
232
+ imp = import("imp")
233
+ imp.load_source("activate_this",
234
+ File.join(File.dirname(RubyPython::Python::EXEC.python),
235
+ "activate_this.py"))
236
+ end
237
+ private :activate
142
238
 
143
239
  def add_observer(object)
144
240
  @observers ||= []
145
241
  @observers << object
146
242
  true
147
243
  end
244
+ private :add_observer
148
245
 
149
246
  def notify(status)
150
- if not @observers.nil?
151
- @observers.each do |o|
152
- o.update status
153
- end
247
+ @observers ||= []
248
+ @observers.each do |o|
249
+ next if nil === o
250
+ o.update status
154
251
  end
155
252
  end
253
+ private :notify
156
254
 
157
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
158
260
  remove_const :Python
159
- load 'rubypython/python.rb'
261
+ load RubyPython::PYTHON_RB
160
262
  true
161
263
  end
162
-
163
- end
164
-
165
- [
166
- PyMain,
167
- Operators,
168
- PyObject::AutoPyPointer
169
- ].each do |observer|
170
- add_observer observer
264
+ private :reload_library
171
265
  end
172
266
 
173
-
267
+ add_observer PyMain
268
+ add_observer Operators
269
+ add_observer PyObject::AutoPyPointer
174
270
  end
175
-
176
-