puppetlabs/puppet · error · ValidationFailure

#{version} is not a valid python package version. Please ref

Error message

#{version} is not a valid python package version. Please refer to https://www.python.org/dev/peps/pep-0440/.

What it means

First guard in Puppet::Util::Package::Version::Pip.parse (pip.rb:39): raises ValidationFailure (interpolating version.to_s) when the argument is not a String. The class only accepts raw PEP 440 version strings; it does not coerce nil, Symbol, Float, or Integer input.

Source

Thrown at lib/puppet/util/package/version/pip.rb:39

          (?:
            [-_\\.]?
            (?<post_l>post|rev|r)
            [-_\\.]?
            (?<post_n2>[0-9]+)?
          )
        )?
        (?<dev>                                             # dev release
          [-_\\.]?
          (?<dev_l>dev)
          [-_\\.]?
          (?<dev_n>[0-9]+)?
        )?
      )
      (?:\\+(?<local>[a-z0-9]+(?:[-_\\.][a-z0-9]+)*))?      # local version
    "

    def self.parse(version)
      raise ValidationFailure, version.to_s unless version.is_a? String

      matched = version.match(Regexp.new("^\\s*" + VERSION_PATTERN + "\\s*$", Regexp::EXTENDED | Regexp::MULTILINE | Regexp::IGNORECASE))
      raise ValidationFailure, version unless matched

      new(matched)
    end

    def self.compare(version_a, version_b)
      version_a = parse(version_a) unless version_a.is_a?(self)
      version_b = parse(version_b) unless version_b.is_a?(self)

      version_a <=> version_b
    end

    def to_s
      parts = []

      parts.push("#{@epoch_data}!")           if @epoch_data && @epoch_data != 0

View on GitHub (pinned to e227c27540)

Solutions

  1. Convert to String first: Pip.parse(version.to_s) when the source type is not guaranteed.
  2. Type-check the input at the boundary: only call parse for String values.
  3. Rescue Puppet::Util::Package::Version::Pip::ValidationFailure around third-party input.

Example fix

// before
v = Puppet::Util::Package::Version::Pip.parse(cfg[:version]) # cfg[:version] = 2.8 (Float)

// after
raw = cfg[:version]
raw = raw.to_s if raw.is_a?(Numeric) || raw.is_a?(Symbol)
v = Puppet::Util::Package::Version::Pip.parse(raw) if raw.is_a?(String)
Defensive patterns

Strategy: type-guard

Validate before calling

def pip_parseable?(value)
  value.is_a?(String) && !value.strip.empty?
end

Type guard

def pip_version_input?(v)
  v.is_a?(String) || v.is_a?(Numeric) || v.is_a?(Symbol) # caller must to_s the latter two
end

Try / catch

begin
  Puppet::Util::Package::Version::Pip.parse(value.to_s)
rescue Puppet::Util::Package::Version::Pip::ValidationFailure
  nil
end

Prevention

When it happens

Trigger: Pip.parse(nil), Pip.parse(:'1.0.0'), Pip.parse(1.0) (a Float that should have been '1.0'), Pip.parse(3) (e.g. Python major version kept as Integer in config data).

Common situations: Version data loaded from JSON/YAML/Hiera where '2.0' became 2.0; nil defaults leaking when a config key is missing; code shared between gem and pip handling that passes through typed values unchanged.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/dc66e9eef876de0f. Report an issue: GitHub.