Skip to content

Instantly share code, notes, and snippets.

@kenkeiter
Created July 6, 2014 01:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenkeiter/22282d9a78a0e8a23251 to your computer and use it in GitHub Desktop.
Save kenkeiter/22282d9a78a0e8a23251 to your computer and use it in GitHub Desktop.
A quick Ruby wrapper for manipulating Python's virtual environments.
require 'open3'
class VirtualEnvError < Exception; end
class PythonVirtualEnv
def self.exists?(path)
File.directory?(path) and File.exists?(File.join(path, 'bin/activate'))
end
def initialize(path)
@env_path = path
@activate_path = File.join(@env_path, 'bin/activate')
self.create! unless self.exists?
end
def exists?
File.directory?(@env_path) and File.exists?(@activate_path)
end
def create!
status_code, _, _ = self.cmd("virtualenv #{@env_path}")
if status_code != 0
raise VirtualEnvError, "Failed to create virtualenv: #{output}"
end
end
def cmd(cmd)
# HACK: fix leaking env vars to the pip subprocess's git subprocess
env = {'GIT_DIR'=>nil, 'GIT_WORK_TREE'=>nil, 'GIT_INDEX_FILE'=>nil}
# run the command
Open3.popen3(env, cmd) do |stdin, stdout, stderr, wait_thr|
# read the stream contents
output = stdout.read
errors = stderr.read
# wait for the process to exit
pid = wait_thr.pid # pid of the started process.
exit_status = wait_thr.value # Process::Status object returned.
# close each stream
stdin.close
stdout.close
stderr.close
# return the response
return exit_status, output, errors
end
end
def exec(cmd)
status, output, err = self.cmd("source #{@activate_path} && #{cmd}")
return status, output, err
end
def install_dependencies(path)
pip_path = File.join(@env_path, 'bin', 'pip')
exit_code, output, err = self.cmd("#{pip_path} install -r #{path}")
if exit_code != 0
raise VirtualEnvError, "Failed to install pip dependencies: #{exit_code} #{output} #{err}"
end
return exit_code, output, err
end
def path
return @env_path
end
def binary(name)
bin_path = File.join(@env_path, 'bin', name)
unless File.exists?(bin_path)
raise VirtualEnvError, "No such binary in environment: #{name}"
end
return bin_path
end
def packages
pip_path = File.join(@env_path, 'bin', 'pip')
exit_code, output, err = self.cmd("#{pip_path} list")
if exit_code != 0
raise VirtualEnvError, "Could not list packages: #{err}"
end
# extract matches (ugly hack)
matches = output.to_enum(:scan, /([a-zA-Z0-9\.\-]+)\s+\(([a-zA-Z0-9\.\-]+)/).map { Regexp.last_match }
# build a hash of package names to versions
packages = {}
matches.each do |match|
packages[match[1]] = match[2]
end
return packages
end
end
### USAGE ###
env = PythonVirtualEnv.new('/path/to/virtualenv') # will be created if non-existent
env.install_dependencies('requirements.txt')
env.packages # => {"requests" => "2.1.0", ...}
env.exec("binary_name arg0 arg1 ...") # => [return_code, stdout, stderr]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment