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
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(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 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
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(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
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 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
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
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
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
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