Skip to content

Commit

Permalink
Add PyCall.iterable
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkn committed Jul 12, 2021
1 parent 6ebb716 commit bef15b8
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/pycall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module PyCall
require 'pycall/pyobject_wrapper'
require 'pycall/pytypeobject_wrapper'
require 'pycall/pymodule_wrapper'
require 'pycall/iterable_wrapper'
require 'pycall/init'

module_function
Expand Down Expand Up @@ -73,6 +74,10 @@ def import_module(name)
LibPython::Helpers.import_module(name)
end

def iterable(obj)
IterableWrapper.new(obj)
end

def len(obj)
case obj
when PyObjectWrapper
Expand Down
32 changes: 32 additions & 0 deletions lib/pycall/iterable_wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module PyCall
class IterableWrapper
include Enumerable

def initialize(obj)
@obj = check_iterable(obj)
end

private def check_iterable(obj)
unless PyCall.hasattr?(obj, :__iter__)
raise ArgumentError, "%p object is not iterable" % obj
end
obj
end

def each
return enum_for(__method__) unless block_given?
iter = @obj.__iter__()
while true
begin
yield iter.__next__()
rescue PyCall::PyError => err
if err.type == PyCall.builtins.StopIteration
break
else
raise err
end
end
end
end
end
end
6 changes: 6 additions & 0 deletions test/helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
require "pycall"
require "pathname"
require "test-unit"

test_dir = Pathname.new(__dir__)
python_dir = test_dir + "python"

PyCall.sys.path.append(python_dir.to_s)
Empty file added test/python/pycall/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions test/python/pycall/simple_iterable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class IntGenerator(object):
def __init__(self, start, stop):
self.start = start
self.stop = stop
self.current = None

def __iter__(self):
self.current = self.start
return self

def __next__(self):
if self.current == self.stop:
raise StopIteration()
value = self.current
self.current += 1
return value

if __name__ == "__main__":
print(list(IntGenerator(105, 115)))
14 changes: 14 additions & 0 deletions test/test-pycall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,18 @@ class PyCallTest < Test::Unit::TestCase
def test_VERSION
assert_not_nil(PyCall::VERSION)
end

def test_iterable
simple_iterable = PyCall.import_module("pycall.simple_iterable")
int_gen = simple_iterable.IntGenerator.new(10, 25)
iterable = PyCall.iterable(int_gen)
assert_equal({
enumerable_p: true,
each_to_a: (10 .. 24).to_a,
},
{
enumerable_p: iterable.is_a?(Enumerable),
each_to_a: iterable.each.to_a
})
end
end

0 comments on commit bef15b8

Please sign in to comment.