Created
July 6, 2014 01:58
-
-
Save kenkeiter/22282d9a78a0e8a23251 to your computer and use it in GitHub Desktop.
A quick Ruby wrapper for manipulating Python's virtual environments.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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