module RubyPy

Ruby wrapper for Python modules (based on the PyCall gem).

Public Class Methods

import(pym) click to toggle source

Import a python.module as a RubyPy::Python::Module using the PyCall gem. Methods called on the Ruby module will be sent to the Python module.

# File lib/ruby.py.rb, line 11
  def self.import(pym)
    # very simple filter to mitigate the risk of code injection attacks
    raise ArgumentError unless pym =~ /\A[-_.A-Za-z0-9]+\Z/

    # convert a python.module.name to a [RubyPy::]Python::Module::Name
    mod = pym.split('.').map do |segment|
      segment.split('_').map(&:capitalize).join
    end

    # create the [RubyPy::]Python::Module::Name hierarchy
    create_module_hierarchy = \
      mod.size.times.map { |i| "module #{mod[0..i].join('::')};end" }.join(';')
    instance_eval(create_module_hierarchy, __FILE__, __LINE__)

    # import the python module into an anonymous module and delegate to it
    instance_eval("#{<<-"DEFMOD"}\n#{<<-"ENDMOD"}", __FILE__, __LINE__)
    DEFMOD
      module #{mod.join('::')}
        unless @_pycall
          def self._pycall
            @_pycall ||= Module.new do
              extend ::PyCall::Import
              pyimport #{pym.inspect}, as: 'import'
            end
          end

          def self.method_missing(method_name, *arguments, &block)
            if respond_to_missing?(method_name)
              _pycall.import.send(method_name, *arguments, &block)
            else
              super
            end
          end
          def self.respond_to_missing?(method_name, include_private = false)
            _pycall.import.respond_to?(method_name, include_private)
          end
        end

        self
      end
    ENDMOD
  end