Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/semantic_version/base.py : 32%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# Copyright (c) The python-semanticversion project
3# This code is distributed under the two-clause BSD License.
5import functools
6import re
7import warnings
10def _has_leading_zero(value):
11 return (value
12 and value[0] == '0'
13 and value.isdigit()
14 and value != '0')
17class MaxIdentifier(object):
18 __slots__ = []
20 def __repr__(self):
21 return 'MaxIdentifier()'
23 def __eq__(self, other):
24 return isinstance(other, self.__class__)
27@functools.total_ordering
28class NumericIdentifier(object):
29 __slots__ = ['value']
31 def __init__(self, value):
32 self.value = int(value)
34 def __repr__(self):
35 return 'NumericIdentifier(%r)' % self.value
37 def __eq__(self, other):
38 if isinstance(other, NumericIdentifier):
39 return self.value == other.value
40 return NotImplemented
42 def __lt__(self, other):
43 if isinstance(other, MaxIdentifier):
44 return True
45 elif isinstance(other, AlphaIdentifier):
46 return True
47 elif isinstance(other, NumericIdentifier):
48 return self.value < other.value
49 else:
50 return NotImplemented
53@functools.total_ordering
54class AlphaIdentifier(object):
55 __slots__ = ['value']
57 def __init__(self, value):
58 self.value = value.encode('ascii')
60 def __repr__(self):
61 return 'AlphaIdentifier(%r)' % self.value
63 def __eq__(self, other):
64 if isinstance(other, AlphaIdentifier):
65 return self.value == other.value
66 return NotImplemented
68 def __lt__(self, other):
69 if isinstance(other, MaxIdentifier):
70 return True
71 elif isinstance(other, NumericIdentifier):
72 return False
73 elif isinstance(other, AlphaIdentifier):
74 return self.value < other.value
75 else:
76 return NotImplemented
79class Version(object):
81 version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$')
82 partial_version_re = re.compile(r'^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$')
84 def __init__(
85 self,
86 version_string=None,
87 major=None,
88 minor=None,
89 patch=None,
90 prerelease=None,
91 build=None,
92 partial=False):
93 if partial:
94 warnings.warn(
95 "Partial versions will be removed in 3.0; use SimpleSpec('1.x.x') instead.",
96 DeprecationWarning,
97 stacklevel=2,
98 )
99 has_text = version_string is not None
100 has_parts = not (major is minor is patch is prerelease is build is None)
101 if not has_text ^ has_parts:
102 raise ValueError("Call either Version('1.2.3') or Version(major=1, ...).")
104 if has_text:
105 major, minor, patch, prerelease, build = self.parse(version_string, partial)
106 else:
107 # Convenience: allow to omit prerelease/build.
108 prerelease = tuple(prerelease or ())
109 if not partial:
110 build = tuple(build or ())
111 self._validate_kwargs(major, minor, patch, prerelease, build, partial)
113 self.major = major
114 self.minor = minor
115 self.patch = patch
116 self.prerelease = prerelease
117 self.build = build
119 self.partial = partial
121 @classmethod
122 def _coerce(cls, value, allow_none=False):
123 if value is None and allow_none:
124 return value
125 return int(value)
127 def next_major(self):
128 if self.prerelease and self.minor == self.patch == 0:
129 return Version(
130 major=self.major,
131 minor=0,
132 patch=0,
133 partial=self.partial,
134 )
135 else:
136 return Version(
137 major=self.major + 1,
138 minor=0,
139 patch=0,
140 partial=self.partial,
141 )
143 def next_minor(self):
144 if self.prerelease and self.patch == 0:
145 return Version(
146 major=self.major,
147 minor=self.minor,
148 patch=0,
149 partial=self.partial,
150 )
151 else:
152 return Version(
153 major=self.major,
154 minor=self.minor + 1,
155 patch=0,
156 partial=self.partial,
157 )
159 def next_patch(self):
160 if self.prerelease:
161 return Version(
162 major=self.major,
163 minor=self.minor,
164 patch=self.patch,
165 partial=self.partial,
166 )
167 else:
168 return Version(
169 major=self.major,
170 minor=self.minor,
171 patch=self.patch + 1,
172 partial=self.partial,
173 )
175 def truncate(self, level='patch'):
176 """Return a new Version object, truncated up to the selected level."""
177 if level == 'build':
178 return self
179 elif level == 'prerelease':
180 return Version(
181 major=self.major,
182 minor=self.minor,
183 patch=self.patch,
184 prerelease=self.prerelease,
185 partial=self.partial,
186 )
187 elif level == 'patch':
188 return Version(
189 major=self.major,
190 minor=self.minor,
191 patch=self.patch,
192 partial=self.partial,
193 )
194 elif level == 'minor':
195 return Version(
196 major=self.major,
197 minor=self.minor,
198 patch=None if self.partial else 0,
199 partial=self.partial,
200 )
201 elif level == 'major':
202 return Version(
203 major=self.major,
204 minor=None if self.partial else 0,
205 patch=None if self.partial else 0,
206 partial=self.partial,
207 )
208 else:
209 raise ValueError("Invalid truncation level `%s`." % level)
211 @classmethod
212 def coerce(cls, version_string, partial=False):
213 """Coerce an arbitrary version string into a semver-compatible one.
215 The rule is:
216 - If not enough components, fill minor/patch with zeroes; unless
217 partial=True
218 - If more than 3 dot-separated components, extra components are "build"
219 data. If some "build" data already appeared, append it to the
220 extra components
222 Examples:
223 >>> Version.coerce('0.1')
224 Version(0, 1, 0)
225 >>> Version.coerce('0.1.2.3')
226 Version(0, 1, 2, (), ('3',))
227 >>> Version.coerce('0.1.2.3+4')
228 Version(0, 1, 2, (), ('3', '4'))
229 >>> Version.coerce('0.1+2-3+4_5')
230 Version(0, 1, 0, (), ('2-3', '4-5'))
231 """
232 base_re = re.compile(r'^\d+(?:\.\d+(?:\.\d+)?)?')
234 match = base_re.match(version_string)
235 if not match:
236 raise ValueError(
237 "Version string lacks a numerical component: %r"
238 % version_string
239 )
241 version = version_string[:match.end()]
242 if not partial:
243 # We need a not-partial version.
244 while version.count('.') < 2:
245 version += '.0'
247 # Strip leading zeros in components
248 # Version is of the form nn, nn.pp or nn.pp.qq
249 version = '.'.join(
250 # If the part was '0', we end up with an empty string.
251 part.lstrip('0') or '0'
252 for part in version.split('.')
253 )
255 if match.end() == len(version_string):
256 return Version(version, partial=partial)
258 rest = version_string[match.end():]
260 # Cleanup the 'rest'
261 rest = re.sub(r'[^a-zA-Z0-9+.-]', '-', rest)
263 if rest[0] == '+':
264 # A 'build' component
265 prerelease = ''
266 build = rest[1:]
267 elif rest[0] == '.':
268 # An extra version component, probably 'build'
269 prerelease = ''
270 build = rest[1:]
271 elif rest[0] == '-':
272 rest = rest[1:]
273 if '+' in rest:
274 prerelease, build = rest.split('+', 1)
275 else:
276 prerelease, build = rest, ''
277 elif '+' in rest:
278 prerelease, build = rest.split('+', 1)
279 else:
280 prerelease, build = rest, ''
282 build = build.replace('+', '.')
284 if prerelease:
285 version = '%s-%s' % (version, prerelease)
286 if build:
287 version = '%s+%s' % (version, build)
289 return cls(version, partial=partial)
291 @classmethod
292 def parse(cls, version_string, partial=False, coerce=False):
293 """Parse a version string into a Version() object.
295 Args:
296 version_string (str), the version string to parse
297 partial (bool), whether to accept incomplete input
298 coerce (bool), whether to try to map the passed in string into a
299 valid Version.
300 """
301 if not version_string:
302 raise ValueError('Invalid empty version string: %r' % version_string)
304 if partial:
305 version_re = cls.partial_version_re
306 else:
307 version_re = cls.version_re
309 match = version_re.match(version_string)
310 if not match:
311 raise ValueError('Invalid version string: %r' % version_string)
313 major, minor, patch, prerelease, build = match.groups()
315 if _has_leading_zero(major):
316 raise ValueError("Invalid leading zero in major: %r" % version_string)
317 if _has_leading_zero(minor):
318 raise ValueError("Invalid leading zero in minor: %r" % version_string)
319 if _has_leading_zero(patch):
320 raise ValueError("Invalid leading zero in patch: %r" % version_string)
322 major = int(major)
323 minor = cls._coerce(minor, partial)
324 patch = cls._coerce(patch, partial)
326 if prerelease is None:
327 if partial and (build is None):
328 # No build info, strip here
329 return (major, minor, patch, None, None)
330 else:
331 prerelease = ()
332 elif prerelease == '':
333 prerelease = ()
334 else:
335 prerelease = tuple(prerelease.split('.'))
336 cls._validate_identifiers(prerelease, allow_leading_zeroes=False)
338 if build is None:
339 if partial:
340 build = None
341 else:
342 build = ()
343 elif build == '':
344 build = ()
345 else:
346 build = tuple(build.split('.'))
347 cls._validate_identifiers(build, allow_leading_zeroes=True)
349 return (major, minor, patch, prerelease, build)
351 @classmethod
352 def _validate_identifiers(cls, identifiers, allow_leading_zeroes=False):
353 for item in identifiers:
354 if not item:
355 raise ValueError(
356 "Invalid empty identifier %r in %r"
357 % (item, '.'.join(identifiers))
358 )
360 if item[0] == '0' and item.isdigit() and item != '0' and not allow_leading_zeroes:
361 raise ValueError("Invalid leading zero in identifier %r" % item)
363 @classmethod
364 def _validate_kwargs(cls, major, minor, patch, prerelease, build, partial):
365 if (
366 major != int(major)
367 or minor != cls._coerce(minor, partial)
368 or patch != cls._coerce(patch, partial)
369 or prerelease is None and not partial
370 or build is None and not partial
371 ):
372 raise ValueError(
373 "Invalid kwargs to Version(major=%r, minor=%r, patch=%r, "
374 "prerelease=%r, build=%r, partial=%r" % (
375 major, minor, patch, prerelease, build, partial
376 ))
377 if prerelease is not None:
378 cls._validate_identifiers(prerelease, allow_leading_zeroes=False)
379 if build is not None:
380 cls._validate_identifiers(build, allow_leading_zeroes=True)
382 def __iter__(self):
383 return iter((self.major, self.minor, self.patch, self.prerelease, self.build))
385 def __str__(self):
386 version = '%d' % self.major
387 if self.minor is not None:
388 version = '%s.%d' % (version, self.minor)
389 if self.patch is not None:
390 version = '%s.%d' % (version, self.patch)
392 if self.prerelease or (self.partial and self.prerelease == () and self.build is None):
393 version = '%s-%s' % (version, '.'.join(self.prerelease))
394 if self.build or (self.partial and self.build == ()):
395 version = '%s+%s' % (version, '.'.join(self.build))
396 return version
398 def __repr__(self):
399 return '%s(%r%s)' % (
400 self.__class__.__name__,
401 str(self),
402 ', partial=True' if self.partial else '',
403 )
405 def __hash__(self):
406 # We don't include 'partial', since this is strictly equivalent to having
407 # at least a field being `None`.
408 return hash((self.major, self.minor, self.patch, self.prerelease, self.build))
410 @property
411 def precedence_key(self):
412 if self.prerelease:
413 prerelease_key = tuple(
414 NumericIdentifier(part) if re.match(r'^[0-9]+$', part) else AlphaIdentifier(part)
415 for part in self.prerelease
416 )
417 else:
418 prerelease_key = (
419 MaxIdentifier(),
420 )
422 return (
423 self.major,
424 self.minor,
425 self.patch,
426 prerelease_key,
427 )
429 def __cmp__(self, other):
430 if not isinstance(other, self.__class__):
431 return NotImplemented
432 if self < other:
433 return -1
434 elif self > other:
435 return 1
436 elif self == other:
437 return 0
438 else:
439 return NotImplemented
441 def __eq__(self, other):
442 if not isinstance(other, self.__class__):
443 return NotImplemented
444 return (
445 self.major == other.major
446 and self.minor == other.minor
447 and self.patch == other.patch
448 and (self.prerelease or ()) == (other.prerelease or ())
449 and (self.build or ()) == (other.build or ())
450 )
452 def __ne__(self, other):
453 if not isinstance(other, self.__class__):
454 return NotImplemented
455 return tuple(self) != tuple(other)
457 def __lt__(self, other):
458 if not isinstance(other, self.__class__):
459 return NotImplemented
460 return self.precedence_key < other.precedence_key
462 def __le__(self, other):
463 if not isinstance(other, self.__class__):
464 return NotImplemented
465 return self.precedence_key <= other.precedence_key
467 def __gt__(self, other):
468 if not isinstance(other, self.__class__):
469 return NotImplemented
470 return self.precedence_key > other.precedence_key
472 def __ge__(self, other):
473 if not isinstance(other, self.__class__):
474 return NotImplemented
475 return self.precedence_key >= other.precedence_key
478class SpecItem(object):
479 """A requirement specification."""
481 KIND_ANY = '*'
482 KIND_LT = '<'
483 KIND_LTE = '<='
484 KIND_EQUAL = '=='
485 KIND_SHORTEQ = '='
486 KIND_EMPTY = ''
487 KIND_GTE = '>='
488 KIND_GT = '>'
489 KIND_NEQ = '!='
490 KIND_CARET = '^'
491 KIND_TILDE = '~'
492 KIND_COMPATIBLE = '~='
494 # Map a kind alias to its full version
495 KIND_ALIASES = {
496 KIND_SHORTEQ: KIND_EQUAL,
497 KIND_EMPTY: KIND_EQUAL,
498 }
500 re_spec = re.compile(r'^(<|<=||=|==|>=|>|!=|\^|~|~=)(\d.*)$')
502 def __init__(self, requirement_string, _warn=True):
503 if _warn:
504 warnings.warn(
505 "The `SpecItem` class will be removed in 3.0.",
506 DeprecationWarning,
507 stacklevel=2,
508 )
509 kind, spec = self.parse(requirement_string)
510 self.kind = kind
511 self.spec = spec
512 self._clause = Spec(requirement_string).clause
514 @classmethod
515 def parse(cls, requirement_string):
516 if not requirement_string:
517 raise ValueError("Invalid empty requirement specification: %r" % requirement_string)
519 # Special case: the 'any' version spec.
520 if requirement_string == '*':
521 return (cls.KIND_ANY, '')
523 match = cls.re_spec.match(requirement_string)
524 if not match:
525 raise ValueError("Invalid requirement specification: %r" % requirement_string)
527 kind, version = match.groups()
528 if kind in cls.KIND_ALIASES:
529 kind = cls.KIND_ALIASES[kind]
531 spec = Version(version, partial=True)
532 if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ):
533 raise ValueError(
534 "Invalid requirement specification %r: build numbers have no ordering."
535 % requirement_string
536 )
537 return (kind, spec)
539 @classmethod
540 def from_matcher(cls, matcher):
541 if matcher == Always():
542 return cls('*', _warn=False)
543 elif matcher == Never():
544 return cls('<0.0.0-', _warn=False)
545 elif isinstance(matcher, Range):
546 return cls('%s%s' % (matcher.operator, matcher.target), _warn=False)
548 def match(self, version):
549 return self._clause.match(version)
551 def __str__(self):
552 return '%s%s' % (self.kind, self.spec)
554 def __repr__(self):
555 return '<SpecItem: %s %r>' % (self.kind, self.spec)
557 def __eq__(self, other):
558 if not isinstance(other, SpecItem):
559 return NotImplemented
560 return self.kind == other.kind and self.spec == other.spec
562 def __hash__(self):
563 return hash((self.kind, self.spec))
566def compare(v1, v2):
567 return Version(v1).__cmp__(Version(v2))
570def match(spec, version):
571 return Spec(spec).match(Version(version))
574def validate(version_string):
575 """Validates a version string againt the SemVer specification."""
576 try:
577 Version.parse(version_string)
578 return True
579 except ValueError:
580 return False
583DEFAULT_SYNTAX = 'simple'
586class BaseSpec(object):
587 """A specification of compatible versions.
589 Usage:
590 >>> Spec('>=1.0.0', syntax='npm')
592 A version matches a specification if it matches any
593 of the clauses of that specification.
595 Internally, a Spec is AnyOf(
596 AllOf(Matcher, Matcher, Matcher),
597 AllOf(...),
598 )
599 """
600 SYNTAXES = {}
602 @classmethod
603 def register_syntax(cls, subclass):
604 syntax = subclass.SYNTAX
605 if syntax is None:
606 raise ValueError("A Spec needs its SYNTAX field to be set.")
607 elif syntax in cls.SYNTAXES:
608 raise ValueError(
609 "Duplicate syntax for %s: %r, %r"
610 % (syntax, cls.SYNTAXES[syntax], subclass)
611 )
612 cls.SYNTAXES[syntax] = subclass
613 return subclass
615 def __init__(self, expression):
616 super(BaseSpec, self).__init__()
617 self.expression = expression
618 self.clause = self._parse_to_clause(expression)
620 @classmethod
621 def parse(cls, expression, syntax=DEFAULT_SYNTAX):
622 """Convert a syntax-specific expression into a BaseSpec instance."""
623 return cls.SYNTAXES[syntax](expression)
625 @classmethod
626 def _parse_to_clause(cls, expression):
627 """Converts an expression to a clause."""
628 raise NotImplementedError()
630 def filter(self, versions):
631 """Filter an iterable of versions satisfying the Spec."""
632 for version in versions:
633 if self.match(version):
634 yield version
636 def match(self, version):
637 """Check whether a Version satisfies the Spec."""
638 return self.clause.match(version)
640 def select(self, versions):
641 """Select the best compatible version among an iterable of options."""
642 options = list(self.filter(versions))
643 if options:
644 return max(options)
645 return None
647 def __contains__(self, version):
648 """Whether `version in self`."""
649 if isinstance(version, Version):
650 return self.match(version)
651 return False
653 def __eq__(self, other):
654 if not isinstance(other, self.__class__):
655 return NotImplemented
657 return self.clause == other.clause
659 def __hash__(self):
660 return hash(self.clause)
662 def __str__(self):
663 return self.expression
665 def __repr__(self):
666 return '<%s: %r>' % (self.__class__.__name__, self.expression)
669class Clause(object):
670 __slots__ = []
672 def match(self, version):
673 raise NotImplementedError()
675 def __and__(self, other):
676 raise NotImplementedError()
678 def __or__(self, other):
679 raise NotImplementedError()
681 def __eq__(self, other):
682 raise NotImplementedError()
684 def prettyprint(self, indent='\t'):
685 """Pretty-print the clause.
686 """
687 return '\n'.join(self._pretty()).replace('\t', indent)
689 def _pretty(self):
690 """Actual pretty-printing logic.
692 Yields:
693 A list of string. Indentation is performed with \t.
694 """
695 yield repr(self)
697 def __ne__(self, other):
698 return not self == other
700 def simplify(self):
701 return self
704class AnyOf(Clause):
705 __slots__ = ['clauses']
707 def __init__(self, *clauses):
708 super(AnyOf, self).__init__()
709 self.clauses = frozenset(clauses)
711 def match(self, version):
712 return any(c.match(version) for c in self.clauses)
714 def simplify(self):
715 subclauses = set()
716 for clause in self.clauses:
717 simplified = clause.simplify()
718 if isinstance(simplified, AnyOf):
719 subclauses |= simplified.clauses
720 elif simplified == Never():
721 continue
722 else:
723 subclauses.add(simplified)
724 if len(subclauses) == 1:
725 return subclauses.pop()
726 return AnyOf(*subclauses)
728 def __hash__(self):
729 return hash((AnyOf, self.clauses))
731 def __iter__(self):
732 return iter(self.clauses)
734 def __eq__(self, other):
735 return isinstance(other, self.__class__) and self.clauses == other.clauses
737 def __and__(self, other):
738 if isinstance(other, AllOf):
739 return other & self
740 elif isinstance(other, Matcher) or isinstance(other, AnyOf):
741 return AllOf(self, other)
742 else:
743 return NotImplemented
745 def __or__(self, other):
746 if isinstance(other, AnyOf):
747 clauses = list(self.clauses | other.clauses)
748 elif isinstance(other, Matcher) or isinstance(other, AllOf):
749 clauses = list(self.clauses | set([other]))
750 else:
751 return NotImplemented
752 return AnyOf(*clauses)
754 def __repr__(self):
755 return 'AnyOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses))
757 def _pretty(self):
758 yield 'AnyOF('
759 for clause in self.clauses:
760 lines = list(clause._pretty())
761 for line in lines[:-1]:
762 yield '\t' + line
763 yield '\t' + lines[-1] + ','
764 yield ')'
767class AllOf(Clause):
768 __slots__ = ['clauses']
770 def __init__(self, *clauses):
771 super(AllOf, self).__init__()
772 self.clauses = frozenset(clauses)
774 def match(self, version):
775 return all(clause.match(version) for clause in self.clauses)
777 def simplify(self):
778 subclauses = set()
779 for clause in self.clauses:
780 simplified = clause.simplify()
781 if isinstance(simplified, AllOf):
782 subclauses |= simplified.clauses
783 elif simplified == Always():
784 continue
785 else:
786 subclauses.add(simplified)
787 if len(subclauses) == 1:
788 return subclauses.pop()
789 return AllOf(*subclauses)
791 def __hash__(self):
792 return hash((AllOf, self.clauses))
794 def __iter__(self):
795 return iter(self.clauses)
797 def __eq__(self, other):
798 return isinstance(other, self.__class__) and self.clauses == other.clauses
800 def __and__(self, other):
801 if isinstance(other, Matcher) or isinstance(other, AnyOf):
802 clauses = list(self.clauses | set([other]))
803 elif isinstance(other, AllOf):
804 clauses = list(self.clauses | other.clauses)
805 else:
806 return NotImplemented
807 return AllOf(*clauses)
809 def __or__(self, other):
810 if isinstance(other, AnyOf):
811 return other | self
812 elif isinstance(other, Matcher):
813 return AnyOf(self, AllOf(other))
814 elif isinstance(other, AllOf):
815 return AnyOf(self, other)
816 else:
817 return NotImplemented
819 def __repr__(self):
820 return 'AllOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses))
822 def _pretty(self):
823 yield 'AllOF('
824 for clause in self.clauses:
825 lines = list(clause._pretty())
826 for line in lines[:-1]:
827 yield '\t' + line
828 yield '\t' + lines[-1] + ','
829 yield ')'
832class Matcher(Clause):
833 __slots__ = []
835 def __and__(self, other):
836 if isinstance(other, AllOf):
837 return other & self
838 elif isinstance(other, Matcher) or isinstance(other, AnyOf):
839 return AllOf(self, other)
840 else:
841 return NotImplemented
843 def __or__(self, other):
844 if isinstance(other, AnyOf):
845 return other | self
846 elif isinstance(other, Matcher) or isinstance(other, AllOf):
847 return AnyOf(self, other)
848 else:
849 return NotImplemented
852class Never(Matcher):
853 __slots__ = []
855 def match(self, version):
856 return False
858 def __hash__(self):
859 return hash((Never,))
861 def __eq__(self, other):
862 return isinstance(other, self.__class__)
864 def __and__(self, other):
865 return self
867 def __or__(self, other):
868 return other
870 def __repr__(self):
871 return 'Never()'
874class Always(Matcher):
875 __slots__ = []
877 def match(self, version):
878 return True
880 def __hash__(self):
881 return hash((Always,))
883 def __eq__(self, other):
884 return isinstance(other, self.__class__)
886 def __and__(self, other):
887 return other
889 def __or__(self, other):
890 return self
892 def __repr__(self):
893 return 'Always()'
896class Range(Matcher):
897 OP_EQ = '=='
898 OP_GT = '>'
899 OP_GTE = '>='
900 OP_LT = '<'
901 OP_LTE = '<='
902 OP_NEQ = '!='
904 # <1.2.3 matches 1.2.3-a1
905 PRERELEASE_ALWAYS = 'always'
906 # <1.2.3 does not match 1.2.3-a1
907 PRERELEASE_NATURAL = 'natural'
908 # 1.2.3-a1 is only considered if target == 1.2.3-xxx
909 PRERELEASE_SAMEPATCH = 'same-patch'
911 # 1.2.3 matches 1.2.3+*
912 BUILD_IMPLICIT = 'implicit'
913 # 1.2.3 matches only 1.2.3, not 1.2.3+4
914 BUILD_STRICT = 'strict'
916 __slots__ = ['operator', 'target', 'prerelease_policy', 'build_policy']
918 def __init__(self, operator, target, prerelease_policy=PRERELEASE_NATURAL, build_policy=BUILD_IMPLICIT):
919 super(Range, self).__init__()
920 if target.build and operator not in (self.OP_EQ, self.OP_NEQ):
921 raise ValueError(
922 "Invalid range %s%s: build numbers have no ordering."
923 % (operator, target))
924 self.operator = operator
925 self.target = target
926 self.prerelease_policy = prerelease_policy
927 self.build_policy = self.BUILD_STRICT if target.build else build_policy
929 def match(self, version):
930 if self.build_policy != self.BUILD_STRICT:
931 version = version.truncate('prerelease')
933 if version.prerelease:
934 same_patch = self.target.truncate() == version.truncate()
936 if self.prerelease_policy == self.PRERELEASE_SAMEPATCH and not same_patch:
937 return False
939 if self.operator == self.OP_EQ:
940 if self.build_policy == self.BUILD_STRICT:
941 return (
942 self.target.truncate('prerelease') == version.truncate('prerelease')
943 and version.build == self.target.build
944 )
945 return version == self.target
946 elif self.operator == self.OP_GT:
947 return version > self.target
948 elif self.operator == self.OP_GTE:
949 return version >= self.target
950 elif self.operator == self.OP_LT:
951 if (
952 version.prerelease
953 and self.prerelease_policy == self.PRERELEASE_NATURAL
954 and version.truncate() == self.target.truncate()
955 and not self.target.prerelease
956 ):
957 return False
958 return version < self.target
959 elif self.operator == self.OP_LTE:
960 return version <= self.target
961 else:
962 assert self.operator == self.OP_NEQ
963 if self.build_policy == self.BUILD_STRICT:
964 return not (
965 self.target.truncate('prerelease') == version.truncate('prerelease')
966 and version.build == self.target.build
967 )
969 if (
970 version.prerelease
971 and self.prerelease_policy == self.PRERELEASE_NATURAL
972 and version.truncate() == self.target.truncate()
973 and not self.target.prerelease
974 ):
975 return False
976 return version != self.target
978 def __hash__(self):
979 return hash((Range, self.operator, self.target, self.prerelease_policy))
981 def __eq__(self, other):
982 return (
983 isinstance(other, self.__class__)
984 and self.operator == other.operator
985 and self.target == other.target
986 and self.prerelease_policy == other.prerelease_policy
987 )
989 def __str__(self):
990 return '%s%s' % (self.operator, self.target)
992 def __repr__(self):
993 policy_part = (
994 '' if self.prerelease_policy == self.PRERELEASE_NATURAL
995 else ', prerelease_policy=%r' % self.prerelease_policy
996 ) + (
997 '' if self.build_policy == self.BUILD_IMPLICIT
998 else ', build_policy=%r' % self.build_policy
999 )
1000 return 'Range(%r, %r%s)' % (
1001 self.operator,
1002 self.target,
1003 policy_part,
1004 )
1007@BaseSpec.register_syntax
1008class SimpleSpec(BaseSpec):
1010 SYNTAX = 'simple'
1012 @classmethod
1013 def _parse_to_clause(cls, expression):
1014 return cls.Parser.parse(expression)
1016 class Parser:
1017 NUMBER = r'\*|0|[1-9][0-9]*'
1018 NAIVE_SPEC = re.compile(r"""^
1019 (?P<op><|<=||=|==|>=|>|!=|\^|~|~=)
1020 (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)?
1021 (?:-(?P<prerel>[a-z0-9A-Z.-]*))?
1022 (?:\+(?P<build>[a-z0-9A-Z.-]*))?
1023 $
1024 """.format(nb=NUMBER),
1025 re.VERBOSE,
1026 )
1028 @classmethod
1029 def parse(cls, expression):
1030 blocks = expression.split(',')
1031 clause = Always()
1032 for block in blocks:
1033 if not cls.NAIVE_SPEC.match(block):
1034 raise ValueError("Invalid simple block %r" % block)
1035 clause &= cls.parse_block(block)
1037 return clause
1039 PREFIX_CARET = '^'
1040 PREFIX_TILDE = '~'
1041 PREFIX_COMPATIBLE = '~='
1042 PREFIX_EQ = '=='
1043 PREFIX_NEQ = '!='
1044 PREFIX_GT = '>'
1045 PREFIX_GTE = '>='
1046 PREFIX_LT = '<'
1047 PREFIX_LTE = '<='
1049 PREFIX_ALIASES = {
1050 '=': PREFIX_EQ,
1051 '': PREFIX_EQ,
1052 }
1054 EMPTY_VALUES = ['*', 'x', 'X', None]
1056 @classmethod
1057 def parse_block(cls, expr):
1058 if not cls.NAIVE_SPEC.match(expr):
1059 raise ValueError("Invalid simple spec component: %r" % expr)
1060 prefix, major_t, minor_t, patch_t, prerel, build = cls.NAIVE_SPEC.match(expr).groups()
1061 prefix = cls.PREFIX_ALIASES.get(prefix, prefix)
1063 major = None if major_t in cls.EMPTY_VALUES else int(major_t)
1064 minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t)
1065 patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t)
1067 if major is None: # '*'
1068 target = Version(major=0, minor=0, patch=0)
1069 if prefix not in (cls.PREFIX_EQ, cls.PREFIX_GTE):
1070 raise ValueError("Invalid simple spec: %r" % expr)
1071 elif minor is None:
1072 target = Version(major=major, minor=0, patch=0)
1073 elif patch is None:
1074 target = Version(major=major, minor=minor, patch=0)
1075 else:
1076 target = Version(
1077 major=major,
1078 minor=minor,
1079 patch=patch,
1080 prerelease=prerel.split('.') if prerel else (),
1081 build=build.split('.') if build else (),
1082 )
1084 if (major is None or minor is None or patch is None) and (prerel or build):
1085 raise ValueError("Invalid simple spec: %r" % expr)
1087 if build is not None and prefix not in (cls.PREFIX_EQ, cls.PREFIX_NEQ):
1088 raise ValueError("Invalid simple spec: %r" % expr)
1090 if prefix == cls.PREFIX_CARET:
1091 # Accept anything with the same most-significant digit
1092 if target.major:
1093 high = target.next_major()
1094 elif target.minor:
1095 high = target.next_minor()
1096 else:
1097 high = target.next_patch()
1098 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high)
1100 elif prefix == cls.PREFIX_TILDE:
1101 assert major is not None
1102 # Accept any higher patch in the same minor
1103 # Might go higher if the initial version was a partial
1104 if minor is None:
1105 high = target.next_major()
1106 else:
1107 high = target.next_minor()
1108 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high)
1110 elif prefix == cls.PREFIX_COMPATIBLE:
1111 assert major is not None
1112 # ~1 is 1.0.0..2.0.0; ~=2.2 is 2.2.0..3.0.0; ~=1.4.5 is 1.4.5..1.5.0
1113 if minor is None or patch is None:
1114 # We got a partial version
1115 high = target.next_major()
1116 else:
1117 high = target.next_minor()
1118 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high)
1120 elif prefix == cls.PREFIX_EQ:
1121 if major is None:
1122 return Range(Range.OP_GTE, target)
1123 elif minor is None:
1124 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_major())
1125 elif patch is None:
1126 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_minor())
1127 elif build == '':
1128 return Range(Range.OP_EQ, target, build_policy=Range.BUILD_STRICT)
1129 else:
1130 return Range(Range.OP_EQ, target)
1132 elif prefix == cls.PREFIX_NEQ:
1133 assert major is not None
1134 if minor is None:
1135 # !=1.x => <1.0.0 || >=2.0.0
1136 return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_major())
1137 elif patch is None:
1138 # !=1.2.x => <1.2.0 || >=1.3.0
1139 return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_minor())
1140 elif prerel == '':
1141 # !=1.2.3-
1142 return Range(Range.OP_NEQ, target, prerelease_policy=Range.PRERELEASE_ALWAYS)
1143 elif build == '':
1144 # !=1.2.3+ or !=1.2.3-a2+
1145 return Range(Range.OP_NEQ, target, build_policy=Range.BUILD_STRICT)
1146 else:
1147 return Range(Range.OP_NEQ, target)
1149 elif prefix == cls.PREFIX_GT:
1150 assert major is not None
1151 if minor is None:
1152 # >1.x => >=2.0
1153 return Range(Range.OP_GTE, target.next_major())
1154 elif patch is None:
1155 return Range(Range.OP_GTE, target.next_minor())
1156 else:
1157 return Range(Range.OP_GT, target)
1159 elif prefix == cls.PREFIX_GTE:
1160 return Range(Range.OP_GTE, target)
1162 elif prefix == cls.PREFIX_LT:
1163 assert major is not None
1164 if prerel == '':
1165 # <1.2.3-
1166 return Range(Range.OP_LT, target, prerelease_policy=Range.PRERELEASE_ALWAYS)
1167 return Range(Range.OP_LT, target)
1169 else:
1170 assert prefix == cls.PREFIX_LTE
1171 assert major is not None
1172 if minor is None:
1173 # <=1.x => <2.0
1174 return Range(Range.OP_LT, target.next_major())
1175 elif patch is None:
1176 return Range(Range.OP_LT, target.next_minor())
1177 else:
1178 return Range(Range.OP_LTE, target)
1181class LegacySpec(SimpleSpec):
1182 def __init__(self, *expressions):
1183 warnings.warn(
1184 "The Spec() class will be removed in 3.1; use SimpleSpec() instead.",
1185 PendingDeprecationWarning,
1186 stacklevel=2,
1187 )
1189 if len(expressions) > 1:
1190 warnings.warn(
1191 "Passing 2+ arguments to SimpleSpec will be removed in 3.0; concatenate them with ',' instead.",
1192 DeprecationWarning,
1193 stacklevel=2,
1194 )
1195 expression = ','.join(expressions)
1196 super(LegacySpec, self).__init__(expression)
1198 @property
1199 def specs(self):
1200 return list(self)
1202 def __iter__(self):
1203 warnings.warn(
1204 "Iterating over the components of a SimpleSpec object will be removed in 3.0.",
1205 DeprecationWarning,
1206 stacklevel=2,
1207 )
1208 try:
1209 clauses = list(self.clause)
1210 except TypeError: # Not an iterable
1211 clauses = [self.clause]
1212 for clause in clauses:
1213 yield SpecItem.from_matcher(clause)
1216Spec = LegacySpec
1219@BaseSpec.register_syntax
1220class NpmSpec(BaseSpec):
1221 SYNTAX = 'npm'
1223 @classmethod
1224 def _parse_to_clause(cls, expression):
1225 return cls.Parser.parse(expression)
1227 class Parser:
1228 JOINER = '||'
1229 HYPHEN = ' - '
1231 NUMBER = r'x|X|\*|0|[1-9][0-9]*'
1232 PART = r'[a-zA-Z0-9.-]*'
1233 NPM_SPEC_BLOCK = re.compile(r"""
1234 ^(?:v)? # Strip optional initial v
1235 (?P<op><|<=|>=|>|=|\^|~|) # Operator, can be empty
1236 (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)?
1237 (?:-(?P<prerel>{part}))? # Optional re-release
1238 (?:\+(?P<build>{part}))? # Optional build
1239 $""".format(nb=NUMBER, part=PART),
1240 re.VERBOSE,
1241 )
1243 @classmethod
1244 def range(cls, operator, target):
1245 return Range(operator, target, prerelease_policy=Range.PRERELEASE_SAMEPATCH)
1247 @classmethod
1248 def parse(cls, expression):
1249 result = Never()
1250 groups = expression.split(cls.JOINER)
1251 for group in groups:
1252 group = group.strip()
1253 if not group:
1254 group = '>=0.0.0'
1256 subclauses = []
1257 if cls.HYPHEN in group:
1258 low, high = group.split(cls.HYPHEN, 2)
1259 subclauses = cls.parse_simple('>=' + low) + cls.parse_simple('<=' + high)
1261 else:
1262 blocks = group.split(' ')
1263 for block in blocks:
1264 if not cls.NPM_SPEC_BLOCK.match(block):
1265 raise ValueError("Invalid NPM block in %r: %r" % (expression, block))
1267 subclauses.extend(cls.parse_simple(block))
1269 prerelease_clauses = []
1270 non_prerel_clauses = []
1271 for clause in subclauses:
1272 if clause.target.prerelease:
1273 if clause.operator in (Range.OP_GT, Range.OP_GTE):
1274 prerelease_clauses.append(Range(
1275 operator=Range.OP_LT,
1276 target=Version(
1277 major=clause.target.major,
1278 minor=clause.target.minor,
1279 patch=clause.target.patch + 1,
1280 ),
1281 prerelease_policy=Range.PRERELEASE_ALWAYS,
1282 ))
1283 elif clause.operator in (Range.OP_LT, Range.OP_LTE):
1284 prerelease_clauses.append(Range(
1285 operator=Range.OP_GTE,
1286 target=Version(
1287 major=clause.target.major,
1288 minor=clause.target.minor,
1289 patch=0,
1290 prerelease=(),
1291 ),
1292 prerelease_policy=Range.PRERELEASE_ALWAYS,
1293 ))
1294 prerelease_clauses.append(clause)
1295 non_prerel_clauses.append(cls.range(
1296 operator=clause.operator,
1297 target=clause.target.truncate(),
1298 ))
1299 else:
1300 non_prerel_clauses.append(clause)
1301 if prerelease_clauses:
1302 result |= AllOf(*prerelease_clauses)
1303 result |= AllOf(*non_prerel_clauses)
1305 return result
1307 PREFIX_CARET = '^'
1308 PREFIX_TILDE = '~'
1309 PREFIX_EQ = '='
1310 PREFIX_GT = '>'
1311 PREFIX_GTE = '>='
1312 PREFIX_LT = '<'
1313 PREFIX_LTE = '<='
1315 PREFIX_ALIASES = {
1316 '': PREFIX_EQ,
1317 }
1319 PREFIX_TO_OPERATOR = {
1320 PREFIX_EQ: Range.OP_EQ,
1321 PREFIX_LT: Range.OP_LT,
1322 PREFIX_LTE: Range.OP_LTE,
1323 PREFIX_GTE: Range.OP_GTE,
1324 PREFIX_GT: Range.OP_GT,
1325 }
1327 EMPTY_VALUES = ['*', 'x', 'X', None]
1329 @classmethod
1330 def parse_simple(cls, simple):
1331 match = cls.NPM_SPEC_BLOCK.match(simple)
1333 prefix, major_t, minor_t, patch_t, prerel, build = match.groups()
1335 prefix = cls.PREFIX_ALIASES.get(prefix, prefix)
1336 major = None if major_t in cls.EMPTY_VALUES else int(major_t)
1337 minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t)
1338 patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t)
1340 if build is not None and prefix not in [cls.PREFIX_EQ]:
1341 # Ignore the 'build' part when not comparing to a specific part.
1342 build = None
1344 if major is None: # '*', 'x', 'X'
1345 target = Version(major=0, minor=0, patch=0)
1346 if prefix not in [cls.PREFIX_EQ, cls.PREFIX_GTE]:
1347 raise ValueError("Invalid expression %r" % simple)
1348 prefix = cls.PREFIX_GTE
1349 elif minor is None:
1350 target = Version(major=major, minor=0, patch=0)
1351 elif patch is None:
1352 target = Version(major=major, minor=minor, patch=0)
1353 else:
1354 target = Version(
1355 major=major,
1356 minor=minor,
1357 patch=patch,
1358 prerelease=prerel.split('.') if prerel else (),
1359 build=build.split('.') if build else (),
1360 )
1362 if (major is None or minor is None or patch is None) and (prerel or build):
1363 raise ValueError("Invalid NPM spec: %r" % simple)
1365 if prefix == cls.PREFIX_CARET:
1366 if target.major: # ^1.2.4 => >=1.2.4 <2.0.0 ; ^1.x => >=1.0.0 <2.0.0
1367 high = target.truncate().next_major()
1368 elif target.minor: # ^0.1.2 => >=0.1.2 <0.2.0
1369 high = target.truncate().next_minor()
1370 elif minor is None: # ^0.x => >=0.0.0 <1.0.0
1371 high = target.truncate().next_major()
1372 elif patch is None: # ^0.2.x => >=0.2.0 <0.3.0
1373 high = target.truncate().next_minor()
1374 else: # ^0.0.1 => >=0.0.1 <0.0.2
1375 high = target.truncate().next_patch()
1376 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)]
1378 elif prefix == cls.PREFIX_TILDE:
1379 assert major is not None
1380 if minor is None: # ~1.x => >=1.0.0 <2.0.0
1381 high = target.next_major()
1382 else: # ~1.2.x => >=1.2.0 <1.3.0; ~1.2.3 => >=1.2.3 <1.3.0
1383 high = target.next_minor()
1384 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)]
1386 elif prefix == cls.PREFIX_EQ:
1387 if major is None:
1388 return [cls.range(Range.OP_GTE, target)]
1389 elif minor is None:
1390 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_major())]
1391 elif patch is None:
1392 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_minor())]
1393 else:
1394 return [cls.range(Range.OP_EQ, target)]
1396 elif prefix == cls.PREFIX_GT:
1397 assert major is not None
1398 if minor is None: # >1.x
1399 return [cls.range(Range.OP_GTE, target.next_major())]
1400 elif patch is None: # >1.2.x => >=1.3.0
1401 return [cls.range(Range.OP_GTE, target.next_minor())]
1402 else:
1403 return [cls.range(Range.OP_GT, target)]
1405 elif prefix == cls.PREFIX_GTE:
1406 return [cls.range(Range.OP_GTE, target)]
1408 elif prefix == cls.PREFIX_LT:
1409 assert major is not None
1410 return [cls.range(Range.OP_LT, target)]
1412 else:
1413 assert prefix == cls.PREFIX_LTE
1414 assert major is not None
1415 if minor is None: # <=1.x => <2.0.0
1416 return [cls.range(Range.OP_LT, target.next_major())]
1417 elif patch is None: # <=1.2.x => <1.3.0
1418 return [cls.range(Range.OP_LT, target.next_minor())]
1419 else:
1420 return [cls.range(Range.OP_LTE, target)]