lib/pycall/pyobject_wrapper.rb in pycall-0.1.0.alpha.20170711 vs lib/pycall/pyobject_wrapper.rb in pycall-1.0.0

- old
+ new

@@ -1,213 +1,212 @@ -module PyCall - HASH_SALT = "PyCall::PyObject".hash +require 'pycall/wrapper_object_cache' - Py_LT = 0 - Py_LE = 1 - Py_EQ = 2 - Py_NE = 3 - Py_GT = 4 - Py_GE = 5 - - RICH_COMPARISON_OPCODES = { - :< => Py_LT, - :<= => Py_LE, - :== => Py_EQ, - :!= => Py_NE, - :> => Py_GT, - :>= => Py_GE - }.freeze - +module PyCall module PyObjectWrapper - module ClassMethods - private + attr_reader :__pyptr__ - def wrap_class(pyclass) - pyclass__pyobj__ = pyclass.__pyobj__ - define_singleton_method(:__pyobj__) { pyclass__pyobj__ } + def self.extend_object(obj) + pyptr = obj.instance_variable_get(:@__pyptr__) + unless pyptr.kind_of? PyPtr + raise TypeError, "@__pyptr__ should have PyCall::PyPtr object" + end + super + end - PyCall.dir(__pyobj__).each do |name| - obj = PyCall.getattr(__pyobj__, name) - next unless obj.kind_of?(PyCall::PyObject) || obj.kind_of?(PyCall::PyObjectWrapper) - next unless PyCall.callable?(obj) + OPERATOR_METHOD_NAMES = { + :+ => :__add__, + :- => :__sub__, + :* => :__mul__, + :/ => :__truediv__, + :% => :__mod__, + :** => :__pow__, + :<< => :__lshift__, + :>> => :__rshift__, + :& => :__and__, + :^ => :__xor__, + :| => :__or__ + }.freeze - define_method(name) do |*args, **kwargs| - PyCall.getattr(__pyobj__, name).(*args, **kwargs) - end + def method_missing(name, *args) + name_str = name.to_s if name.kind_of?(Symbol) + name_str.chop! if name_str.end_with?('=') + case name + when *OPERATOR_METHOD_NAMES.keys + op_name = OPERATOR_METHOD_NAMES[name] + if LibPython::Helpers.hasattr?(__pyptr__, op_name) + LibPython::Helpers.define_wrapper_method(self, op_name) + singleton_class.__send__(:alias_method, name, op_name) + return self.__send__(name, *args) end - - class << self - def method_missing(name, *args, **kwargs) - return super unless PyCall.hasattr?(__pyobj__, name) - PyCall.getattr(__pyobj__, name) - end + else + if LibPython::Helpers.hasattr?(__pyptr__, name_str) + LibPython::Helpers.define_wrapper_method(self, name) + return self.__send__(name, *args) end - - PyCall::Conversions.python_type_mapping(__pyobj__, self) end + super end - def self.included(mod) - mod.extend ClassMethods + def respond_to_missing?(name, include_private) + return true if LibPython::Helpers.hasattr?(__pyptr__, name) + super end - def initialize(pyobj) - pyobj = LibPython::PyObjectStruct.new(pyobj) if pyobj.kind_of? FFI::Pointer - pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct - @__pyobj__ = pyobj + def kind_of?(cls) + case cls + when PyTypeObjectWrapper + __pyptr__.kind_of?(cls.__pyptr__) + else + super + end end - attr_reader :__pyobj__ - - def eql?(other) - rich_compare(other, :==) + [:==, :!=, :<, :<=, :>, :>=].each do |op| + class_eval("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1) + begin; + def #{op}(other) + case other + when PyObjectWrapper + LibPython::Helpers.compare(:#{op}, __pyptr__, other.__pyptr__) + else + other = Conversion.from_ruby(other) + LibPython::Helpers.compare(:#{op}, __pyptr__, other) + end + end + end; end - def hash - hash_value = LibPython.PyObject_Hash(__pyobj__) - return super if hash_value == -1 - hash_value + def [](*key) + LibPython::Helpers.getitem(__pyptr__, key) end - def type - LibPython.PyObject_Type(__pyobj__).to_ruby + def []=(*key, value) + LibPython::Helpers.setitem(__pyptr__, key, value) end - def null? - __pyobj__.null? + def call(*args) + LibPython::Helpers.call_object(__pyptr__, *args) end - def to_ptr - __pyobj__.to_ptr - end + class SwappedOperationAdapter + def initialize(obj) + @obj = obj + end - def py_none? - to_ptr == PyCall.None.to_ptr - end + attr_reader :obj - def kind_of?(klass) - case klass - when PyObjectWrapper - __pyobj__.kind_of? klass.__pyobj__ - when LibPython::PyObjectStruct - __pyobj__.kind_of? klass - else - super + def +(other) + other.__radd__(self.obj) end - end - def rich_compare(other, op) - opcode = RICH_COMPARISON_OPCODES[op] - raise ArgumentError, "Unknown comparison op: #{op}" unless opcode + def -(other) + other.__rsub__(self.obj) + end - other = Conversions.from_ruby(other) - return other.null? if __pyobj__.null? - return false if other.null? + def *(other) + other.__rmul__(self.obj) + end - value = LibPython.PyObject_RichCompare(__pyobj__, other, opcode) - raise "Unable to compare: #{self} #{op} #{other}" if value.null? - value.to_ruby - end + def /(other) + other.__rtruediv__(self.obj) + end - RICH_COMPARISON_OPCODES.keys.each do |op| - define_method(op) {|other| rich_compare(other, op) } - end + def %(other) + other.__rmod__(self.obj) + end - def [](*indices) - if indices.length == 1 - indices = indices[0] - else - indices = PyCall.tuple(*indices) + def **(other) + other.__rpow__(self.obj) end - PyCall.getitem(self, indices) - end - def []=(*indices_and_value) - value = indices_and_value.pop - indices = indices_and_value - if indices.length == 1 - indices = indices[0] - else - indices = PyCall.tuple(*indices) + def <<(other) + other.__rlshift__(self.obj) end - PyCall.setitem(self, indices, value) + + def >>(other) + other.__rrshift__(self.obj) + end + + def &(other) + other.__rand__(self.obj) + end + + def ^(other) + other.__rxor__(self.obj) + end + + def |(other) + other.__ror__(self.obj) + end end - def +(other) - other = Conversions.from_ruby(other) - value = LibPython.PyNumber_Add(__pyobj__, other) - return value.to_ruby unless value.null? - raise PyError.fetch + def coerce(other) + [SwappedOperationAdapter.new(other), self] end - def -(other) - other = Conversions.from_ruby(other) - value = LibPython.PyNumber_Subtract(__pyobj__, other) - return value.to_ruby unless value.null? - raise PyError.fetch + def dup + super.tap do |duped| + copied = PyCall.import_module('copy').copy(__pyptr__) + copied = copied.__pyptr__ if copied.kind_of? PyObjectWrapper + duped.instance_variable_set(:@__pyptr__, copied) + end end - def *(other) - other = Conversions.from_ruby(other) - value = LibPython.PyNumber_Multiply(__pyobj__, other) - return value.to_ruby unless value.null? - raise PyError.fetch + def inspect + PyCall.builtins.repr(__pyptr__) end - def /(other) - other = Conversions.from_ruby(other) - value = LibPython.PyNumber_TrueDivide(__pyobj__, other) - return value.to_ruby unless value.null? - raise PyError.fetch + def to_s + LibPython::Helpers.str(__pyptr__) end - def **(other) - other = Conversions.from_ruby(other) - value = LibPython.PyNumber_Power(__pyobj__, other, PyCall.None) - return value.to_ruby unless value.null? - raise PyError.fetch + def to_i + LibPython::Helpers.call_object(PyCall::builtins.int.__pyptr__, __pyptr__) end - def coerce(other) - [PyObject.new(Conversions.from_ruby(other)), self] + def to_f + LibPython::Helpers.call_object(PyCall::builtins.float.__pyptr__, __pyptr__) end + end - def call(*args, **kwargs) - args = PyCall::Tuple[*args] - kwargs = kwargs.empty? ? PyObject.null : PyCall::Dict.new(kwargs) - res = LibPython.PyObject_Call(__pyobj__, args.__pyobj__, kwargs.__pyobj__) - return res.to_ruby if LibPython.PyErr_Occurred().null? - raise PyError.fetch + module_function + + class WrapperModuleCache < WrapperObjectCache + def initialize + super(LibPython::API::PyModule_Type) end - def method_missing(name, *args, **kwargs) - name_s = name.to_s - if name_s.end_with? '=' - name = name_s[0..-2] - if PyCall.hasattr?(__pyobj__, name.to_s) - PyCall.setattr(__pyobj__, name, args.first) - else - raise NameError, "object has no attribute `#{name}'" - end - elsif PyCall.hasattr?(__pyobj__, name.to_s) - PyCall.getattr(__pyobj__, name) - else - super + def check_wrapper_object(wrapper_object) + unless wrapper_object.kind_of?(Module) && wrapper_object.kind_of?(PyObjectWrapper) + raise TypeError, "unexpected type #{wrapper_object.class} (expected Module extended by PyObjectWrapper)" end end - def to_s - s = LibPython.PyObject_Repr(__pyobj__) - if s.null? - LibPython.PyErr_Clear() - s = LibPython.PyObject_Str(__pyobj__) - if s.null? - LibPython.PyErr_Clear() - return super - end + def self.instance + @instance ||= self.new + end + end + + private_constant :WrapperModuleCache + + def wrap_module(pymodptr) + check_ismodule(pymodptr) + WrapperModuleCache.instance.lookup(pymodptr) do + Module.new do |mod| + mod.instance_variable_set(:@__pyptr__, pymodptr) + mod.extend PyObjectWrapper end - s.to_ruby end + end - alias inspect to_s + def check_isclass(pyptr) + pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper + return if pyptr.kind_of? LibPython::API::PyType_Type + return defined?(LibPython::API::PyClass_Type) && pyptr.kind_of?(LibPython::API::PyClass_Type) + raise TypeError, "PyType object is required" + end + + def check_ismodule(pyptr) + return if pyptr.kind_of? LibPython::API::PyModule_Type + raise TypeError, "PyModule object is required" end end