class PoisePython::Resources::PythonPackage::Provider

The default provider for the `python_package` resource.

@see Resource

Constants

PACKAGE_NAME_EXTRA

Regexp for extras.

PACKAGE_NAME_URL

Regexp for package URLs.

Public Instance Methods

check_package_versions(resource, version=new_resource.version) click to toggle source

Populate current and candidate versions for all needed packages.

@api private @param resource [PoisePython::Resources::PythonPackage::Resource]

Resource to load for.

@param version [String, Array<String>] Current version(s) of package(s). @return [void]

# File lib/poise_python/resources/python_package.rb, line 212
def check_package_versions(resource, version=new_resource.version)
  version_data = Hash.new {|hash, key| hash[key] = {current: nil, candidate: nil} }
  # Get the version for everything currently installed.
  list = pip_command('list', :list, [], environment: {'PIP_FORMAT' => 'json'}).stdout
  parse_pip_list(list).each do |name, current|
    # Merge current versions in to the data.
    version_data[name][:current] = current
  end
  # Check for newer candidates.
  outdated = pip_outdated(pip_requirements(resource.package_name, version, parse: true)).stdout
  Chef::JSONCompat.parse(outdated).each do |name, candidate|
    # Merge candidates in to the existing versions.
    version_data[name][:candidate] = candidate
  end
  # Populate the current resource and candidate versions. Youch this is
  # a gross mix of data flow.
  if(resource.package_name.is_a?(Array))
    @candidate_version = []
    versions = []
    [resource.package_name].flatten.each do |name|
      ver = version_data[parse_package_name(name)]
      versions << ver[:current]
      @candidate_version << ver[:candidate]
    end
    resource.version(versions)
  else
    ver = version_data[parse_package_name(resource.package_name)]
    resource.version(ver[:current])
    @candidate_version = ver[:candidate]
  end
end
install_package(name, version) click to toggle source

Install package(s) using pip.

@param name [String, Array<String>] Name(s) of package(s). @param version [String, Array<String>] Version(s) of package(s). @return [void]

# File lib/poise_python/resources/python_package.rb, line 249
def install_package(name, version)
  pip_install(name, version, upgrade: false)
end
load_current_resource() click to toggle source

Load current and candidate versions for all needed packages.

@api private @return [Chef::Resource]

# File lib/poise_python/resources/python_package.rb, line 197
def load_current_resource
  @current_resource = new_resource.class.new(new_resource.name, run_context)
  current_resource.package_name(new_resource.package_name)
  check_package_versions(current_resource)
  Chef::Log.debug("[#{new_resource}] Current version: #{current_resource.version}, candidate version: #{@candidate_version}")
  current_resource
end
remove_package(name, version) click to toggle source

Uninstall package(s) using pip.

@param name [String, Array<String>] Name(s) of package(s). @param version [String, Array<String>] Version(s) of package(s). @return [void]

# File lib/poise_python/resources/python_package.rb, line 267
def remove_package(name, version)
  pip_command('uninstall', :install, %w{--yes} + [name].flatten)
end
upgrade_package(name, version) click to toggle source

Upgrade package(s) using pip.

@param name [String, Array<String>] Name(s) of package(s). @param version [String, Array<String>] Version(s) of package(s). @return [void]

# File lib/poise_python/resources/python_package.rb, line 258
def upgrade_package(name, version)
  pip_install(name, version, upgrade: true)
end

Private Instance Methods

parse_package_name(raw_name) click to toggle source

Find the underlying name from a pip input sequence.

@param raw_name [String] Raw package name. @return [String]

# File lib/poise_python/resources/python_package.rb, line 391
def parse_package_name(raw_name)
  case raw_name
  when PACKAGE_NAME_URL, PACKAGE_NAME_EXTRA
    $1
  else
    raw_name
  end.downcase.gsub(/_/, '-')
end
parse_pip_list(text) click to toggle source

Parse the output from `pip list`. Returns a hash of package key to current version.

@param text [String] Output to parse. @return [Hash<String, String>]

# File lib/poise_python/resources/python_package.rb, line 361
def parse_pip_list(text)
  if text[0] == '['
    # Pip 9 or newer, so it understood $PIP_FORMAT=json.
    Chef::JSONCompat.parse(text).each_with_object({}) do |data, memo|
      memo[parse_package_name(data['name'])] = data['version']
    end
  else
    # Pip 8 or earlier, which doesn't support JSON output.
    text.split(/\r?\n/).each_with_object({}) do |line, memo|
      # Example of a line:
      # boto (2.25.0)
      if md = line.match(/^(\S+)\s+\(([^\s,]+).*\)$/i)
        memo[parse_package_name(md[1])] = md[2]
      else
        Chef::Log.debug("[#{new_resource}] Unparsable line in pip list: #{line}")
      end
    end
  end
end
pip_command(pip_command, options_type, pip_options=[], opts={}) click to toggle source

Run a pip command.

@param pip_command [String, nil] The pip subcommand to run (eg. install). @param options_type [Symbol] Either `:install` to `:list` to select

which extra options to use.

@param pip_options [Array<String>] Options for the pip command. @param opts [Hash] Mixlib::ShellOut options. @return [Mixlib::ShellOut]

# File lib/poise_python/resources/python_package.rb, line 308
def pip_command(pip_command, options_type, pip_options=[], opts={})
  runner = opts.delete(:pip_runner) || %w{-m pip.__main__}
  type_specific_options = new_resource.send(:"#{options_type}_options")
  full_cmd = if new_resource.options || type_specific_options
    if (new_resource.options && new_resource.options.is_a?(String)) || (type_specific_options && type_specific_options.is_a?(String))
      # We have to use a string for this case to be safe because the
      # options are a string and I don't want to try and parse that.
      global_options = new_resource.options.is_a?(Array) ? Shellwords.join(new_resource.options) : new_resource.options.to_s
      type_specific_options = type_specific_options.is_a?(Array) ? Shellwords.join(type_specific_options) : type_specific_options.to_s
      "#{runner.join(' ')} #{pip_command} #{global_options} #{type_specific_options} #{Shellwords.join(pip_options)}"
    else
      runner + (pip_command ? [pip_command] : []) + (new_resource.options || []) + (type_specific_options || []) + pip_options
    end
  else
    # No special options, use an array to skip the extra /bin/sh.
    runner + (pip_command ? [pip_command] : []) + pip_options
  end
  # Set user and group.
  opts[:user] = new_resource.user if new_resource.user
  opts[:group] = new_resource.group if new_resource.group

  python_shell_out!(full_cmd, opts)
end
pip_install(name, version, upgrade: false) click to toggle source

Run `pip install` to install a package(s).

@param name [String, Array<String>] Name(s) of package(s) to install. @param version [String, Array<String>] Version(s) of package(s) to

install.

@param upgrade [Boolean] Use upgrade mode? @return [Mixlib::ShellOut]

# File lib/poise_python/resources/python_package.rb, line 339
def pip_install(name, version, upgrade: false)
  cmd = pip_requirements(name, version)
  # Prepend --upgrade if needed.
  cmd = %w{--upgrade} + cmd if upgrade
  pip_command('install', :install, cmd)
end
pip_outdated(requirements) click to toggle source

Run my hacked version of `pip list –outdated` with a specific set of package requirements.

@see pip_requirements @param requirements [Array<String>] Pip-formatted package requirements. @return [Mixlib::ShellOut]

# File lib/poise_python/resources/python_package.rb, line 352
def pip_outdated(requirements)
  pip_command(nil, :list, requirements, input: PIP_HACK_SCRIPT, pip_runner: %w{-})
end
pip_requirements(name, version, parse: false) click to toggle source

Convert name(s) and version(s) to an array of pkg_resources.Requirement compatible strings. These are strings like “django” or “django==1.0”.

@param name [String, Array<String>] Name or names for the packages. @param version [String, Array<String>] Version or versions for the

packages.

@param parse [Boolean] Use parsed package names. @return [Array<String>]

# File lib/poise_python/resources/python_package.rb, line 281
def pip_requirements(name, version, parse: false)
  [name].flatten.zip([version].flatten).map do |n, v|
    n = parse_package_name(n) if parse
    v = v.to_s.strip
    if n =~ /:\/\//
      # Probably a URI.
      n
    elsif v.empty?
      # No version requirement, send through unmodified.
      n
    elsif v =~ /^\d/
      "#{n}==#{v}"
    else
      # If the first character isn't a digit, assume something fancy.
      n + v
    end
  end
end