{"record":{"id":"dc66e9eef876de0f","repo":"puppetlabs/puppet","slug":"version-is-not-a-valid-python-package-version","errorCode":null,"errorMessage":"#{version} is not a valid python package version. Please refer to https://www.python.org/dev/peps/pep-0440/.","messagePattern":"#(.+?) is not a valid python package version\\. Please refer to https://www\\.python\\.org/dev/peps/pep-0440/\\.","errorType":"validation","errorClass":"ValidationFailure","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/package/version/pip.rb","lineNumber":39,"sourceCode":"          (?:\n            [-_\\\\.]?\n            (?<post_l>post|rev|r)\n            [-_\\\\.]?\n            (?<post_n2>[0-9]+)?\n          )\n        )?\n        (?<dev>                                             # dev release\n          [-_\\\\.]?\n          (?<dev_l>dev)\n          [-_\\\\.]?\n          (?<dev_n>[0-9]+)?\n        )?\n      )\n      (?:\\\\+(?<local>[a-z0-9]+(?:[-_\\\\.][a-z0-9]+)*))?      # local version\n    \"\n\n    def self.parse(version)\n      raise ValidationFailure, version.to_s unless version.is_a? String\n\n      matched = version.match(Regexp.new(\"^\\\\s*\" + VERSION_PATTERN + \"\\\\s*$\", Regexp::EXTENDED | Regexp::MULTILINE | Regexp::IGNORECASE))\n      raise ValidationFailure, version unless matched\n\n      new(matched)\n    end\n\n    def self.compare(version_a, version_b)\n      version_a = parse(version_a) unless version_a.is_a?(self)\n      version_b = parse(version_b) unless version_b.is_a?(self)\n\n      version_a <=> version_b\n    end\n\n    def to_s\n      parts = []\n\n      parts.push(\"#{@epoch_data}!\")           if @epoch_data && @epoch_data != 0","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/package/version/pip.rb#L21-L57","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Convert to String first: Pip.parse(version.to_s) when the source type is not guaranteed.","Type-check the input at the boundary: only call parse for String values.","Rescue Puppet::Util::Package::Version::Pip::ValidationFailure around third-party input."],"exampleFix":"// before\nv = Puppet::Util::Package::Version::Pip.parse(cfg[:version]) # cfg[:version] = 2.8 (Float)\n\n// after\nraw = cfg[:version]\nraw = raw.to_s if raw.is_a?(Numeric) || raw.is_a?(Symbol)\nv = Puppet::Util::Package::Version::Pip.parse(raw) if raw.is_a?(String)","handlingStrategy":"type-guard","validationCode":"def pip_parseable?(value)\n  value.is_a?(String) && !value.strip.empty?\nend","typeGuard":"def pip_version_input?(v)\n  v.is_a?(String) || v.is_a?(Numeric) || v.is_a?(Symbol) # caller must to_s the latter two\nend","tryCatchPattern":"begin\n  Puppet::Util::Package::Version::Pip.parse(value.to_s)\nrescue Puppet::Util::Package::Version::Pip::ValidationFailure\n  nil\nend","preventionTips":["Coerce numerics/symbols from JSON/YAML with to_s before pip version handling.","Guard nil at the source: check the config key exists before using its value as a version.","Keep one normalization choke-point for version input instead of parsing at multiple call sites."],"tags":["pip","python","pep-440","package-version","type-error","validation"],"backgroundTag":"version-string-validation","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}