m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

751 lines
23 KiB

6 months ago
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2012-2023 The Python Software Foundation.
  4. # See LICENSE.txt and CONTRIBUTORS.txt.
  5. #
  6. """
  7. Implementation of a flexible versioning scheme providing support for PEP-440,
  8. setuptools-compatible and semantic versioning.
  9. """
  10. import logging
  11. import re
  12. from .compat import string_types
  13. from .util import parse_requirement
  14. __all__ = ['NormalizedVersion', 'NormalizedMatcher',
  15. 'LegacyVersion', 'LegacyMatcher',
  16. 'SemanticVersion', 'SemanticMatcher',
  17. 'UnsupportedVersionError', 'get_scheme']
  18. logger = logging.getLogger(__name__)
  19. class UnsupportedVersionError(ValueError):
  20. """This is an unsupported version."""
  21. pass
  22. class Version(object):
  23. def __init__(self, s):
  24. self._string = s = s.strip()
  25. self._parts = parts = self.parse(s)
  26. assert isinstance(parts, tuple)
  27. assert len(parts) > 0
  28. def parse(self, s):
  29. raise NotImplementedError('please implement in a subclass')
  30. def _check_compatible(self, other):
  31. if type(self) != type(other):
  32. raise TypeError('cannot compare %r and %r' % (self, other))
  33. def __eq__(self, other):
  34. self._check_compatible(other)
  35. return self._parts == other._parts
  36. def __ne__(self, other):
  37. return not self.__eq__(other)
  38. def __lt__(self, other):
  39. self._check_compatible(other)
  40. return self._parts < other._parts
  41. def __gt__(self, other):
  42. return not (self.__lt__(other) or self.__eq__(other))
  43. def __le__(self, other):
  44. return self.__lt__(other) or self.__eq__(other)
  45. def __ge__(self, other):
  46. return self.__gt__(other) or self.__eq__(other)
  47. # See http://docs.python.org/reference/datamodel#object.__hash__
  48. def __hash__(self):
  49. return hash(self._parts)
  50. def __repr__(self):
  51. return "%s('%s')" % (self.__class__.__name__, self._string)
  52. def __str__(self):
  53. return self._string
  54. @property
  55. def is_prerelease(self):
  56. raise NotImplementedError('Please implement in subclasses.')
  57. class Matcher(object):
  58. version_class = None
  59. # value is either a callable or the name of a method
  60. _operators = {
  61. '<': lambda v, c, p: v < c,
  62. '>': lambda v, c, p: v > c,
  63. '<=': lambda v, c, p: v == c or v < c,
  64. '>=': lambda v, c, p: v == c or v > c,
  65. '==': lambda v, c, p: v == c,
  66. '===': lambda v, c, p: v == c,
  67. # by default, compatible => >=.
  68. '~=': lambda v, c, p: v == c or v > c,
  69. '!=': lambda v, c, p: v != c,
  70. }
  71. # this is a method only to support alternative implementations
  72. # via overriding
  73. def parse_requirement(self, s):
  74. return parse_requirement(s)
  75. def __init__(self, s):
  76. if self.version_class is None:
  77. raise ValueError('Please specify a version class')
  78. self._string = s = s.strip()
  79. r = self.parse_requirement(s)
  80. if not r:
  81. raise ValueError('Not valid: %r' % s)
  82. self.name = r.name
  83. self.key = self.name.lower() # for case-insensitive comparisons
  84. clist = []
  85. if r.constraints:
  86. # import pdb; pdb.set_trace()
  87. for op, s in r.constraints:
  88. if s.endswith('.*'):
  89. if op not in ('==', '!='):
  90. raise ValueError('\'.*\' not allowed for '
  91. '%r constraints' % op)
  92. # Could be a partial version (e.g. for '2.*') which
  93. # won't parse as a version, so keep it as a string
  94. vn, prefix = s[:-2], True
  95. # Just to check that vn is a valid version
  96. self.version_class(vn)
  97. else:
  98. # Should parse as a version, so we can create an
  99. # instance for the comparison
  100. vn, prefix = self.version_class(s), False
  101. clist.append((op, vn, prefix))
  102. self._parts = tuple(clist)
  103. def match(self, version):
  104. """
  105. Check if the provided version matches the constraints.
  106. :param version: The version to match against this instance.
  107. :type version: String or :class:`Version` instance.
  108. """
  109. if isinstance(version, string_types):
  110. version = self.version_class(version)
  111. for operator, constraint, prefix in self._parts:
  112. f = self._operators.get(operator)
  113. if isinstance(f, string_types):
  114. f = getattr(self, f)
  115. if not f:
  116. msg = ('%r not implemented '
  117. 'for %s' % (operator, self.__class__.__name__))
  118. raise NotImplementedError(msg)
  119. if not f(version, constraint, prefix):
  120. return False
  121. return True
  122. @property
  123. def exact_version(self):
  124. result = None
  125. if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='):
  126. result = self._parts[0][1]
  127. return result
  128. def _check_compatible(self, other):
  129. if type(self) != type(other) or self.name != other.name:
  130. raise TypeError('cannot compare %s and %s' % (self, other))
  131. def __eq__(self, other):
  132. self._check_compatible(other)
  133. return self.key == other.key and self._parts == other._parts
  134. def __ne__(self, other):
  135. return not self.__eq__(other)
  136. # See http://docs.python.org/reference/datamodel#object.__hash__
  137. def __hash__(self):
  138. return hash(self.key) + hash(self._parts)
  139. def __repr__(self):
  140. return "%s(%r)" % (self.__class__.__name__, self._string)
  141. def __str__(self):
  142. return self._string
  143. PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|alpha|b|beta|c|rc|pre|preview)(\d+)?)?'
  144. r'(\.(post|r|rev)(\d+)?)?([._-]?(dev)(\d+)?)?'
  145. r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$', re.I)
  146. def _pep_440_key(s):
  147. s = s.strip()
  148. m = PEP440_VERSION_RE.match(s)
  149. if not m:
  150. raise UnsupportedVersionError('Not a valid version: %s' % s)
  151. groups = m.groups()
  152. nums = tuple(int(v) for v in groups[1].split('.'))
  153. while len(nums) > 1 and nums[-1] == 0:
  154. nums = nums[:-1]
  155. if not groups[0]:
  156. epoch = 0
  157. else:
  158. epoch = int(groups[0][:-1])
  159. pre = groups[4:6]
  160. post = groups[7:9]
  161. dev = groups[10:12]
  162. local = groups[13]
  163. if pre == (None, None):
  164. pre = ()
  165. else:
  166. if pre[1] is None:
  167. pre = pre[0], 0
  168. else:
  169. pre = pre[0], int(pre[1])
  170. if post == (None, None):
  171. post = ()
  172. else:
  173. if post[1] is None:
  174. post = post[0], 0
  175. else:
  176. post = post[0], int(post[1])
  177. if dev == (None, None):
  178. dev = ()
  179. else:
  180. if dev[1] is None:
  181. dev = dev[0], 0
  182. else:
  183. dev = dev[0], int(dev[1])
  184. if local is None:
  185. local = ()
  186. else:
  187. parts = []
  188. for part in local.split('.'):
  189. # to ensure that numeric compares as > lexicographic, avoid
  190. # comparing them directly, but encode a tuple which ensures
  191. # correct sorting
  192. if part.isdigit():
  193. part = (1, int(part))
  194. else:
  195. part = (0, part)
  196. parts.append(part)
  197. local = tuple(parts)
  198. if not pre:
  199. # either before pre-release, or final release and after
  200. if not post and dev:
  201. # before pre-release
  202. pre = ('a', -1) # to sort before a0
  203. else:
  204. pre = ('z',) # to sort after all pre-releases
  205. # now look at the state of post and dev.
  206. if not post:
  207. post = ('_',) # sort before 'a'
  208. if not dev:
  209. dev = ('final',)
  210. return epoch, nums, pre, post, dev, local
  211. _normalized_key = _pep_440_key
  212. class NormalizedVersion(Version):
  213. """A rational version.
  214. Good:
  215. 1.2 # equivalent to "1.2.0"
  216. 1.2.0
  217. 1.2a1
  218. 1.2.3a2
  219. 1.2.3b1
  220. 1.2.3c1
  221. 1.2.3.4
  222. TODO: fill this out
  223. Bad:
  224. 1 # minimum two numbers
  225. 1.2a # release level must have a release serial
  226. 1.2.3b
  227. """
  228. def parse(self, s):
  229. result = _normalized_key(s)
  230. # _normalized_key loses trailing zeroes in the release
  231. # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0
  232. # However, PEP 440 prefix matching needs it: for example,
  233. # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0).
  234. m = PEP440_VERSION_RE.match(s) # must succeed
  235. groups = m.groups()
  236. self._release_clause = tuple(int(v) for v in groups[1].split('.'))
  237. return result
  238. PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev'])
  239. @property
  240. def is_prerelease(self):
  241. return any(t[0] in self.PREREL_TAGS for t in self._parts if t)
  242. def _match_prefix(x, y):
  243. x = str(x)
  244. y = str(y)
  245. if x == y:
  246. return True
  247. if not x.startswith(y):
  248. return False
  249. n = len(y)
  250. return x[n] == '.'
  251. class NormalizedMatcher(Matcher):
  252. version_class = NormalizedVersion
  253. # value is either a callable or the name of a method
  254. _operators = {
  255. '~=': '_match_compatible',
  256. '<': '_match_lt',
  257. '>': '_match_gt',
  258. '<=': '_match_le',
  259. '>=': '_match_ge',
  260. '==': '_match_eq',
  261. '===': '_match_arbitrary',
  262. '!=': '_match_ne',
  263. }
  264. def _adjust_local(self, version, constraint, prefix):
  265. if prefix:
  266. strip_local = '+' not in constraint and version._parts[-1]
  267. else:
  268. # both constraint and version are
  269. # NormalizedVersion instances.
  270. # If constraint does not have a local component,
  271. # ensure the version doesn't, either.
  272. strip_local = not constraint._parts[-1] and version._parts[-1]
  273. if strip_local:
  274. s = version._string.split('+', 1)[0]
  275. version = self.version_class(s)
  276. return version, constraint
  277. def _match_lt(self, version, constraint, prefix):
  278. version, constraint = self._adjust_local(version, constraint, prefix)
  279. if version >= constraint:
  280. return False
  281. release_clause = constraint._release_clause
  282. pfx = '.'.join([str(i) for i in release_clause])
  283. return not _match_prefix(version, pfx)
  284. def _match_gt(self, version, constraint, prefix):
  285. version, constraint = self._adjust_local(version, constraint, prefix)
  286. if version <= constraint:
  287. return False
  288. release_clause = constraint._release_clause
  289. pfx = '.'.join([str(i) for i in release_clause])
  290. return not _match_prefix(version, pfx)
  291. def _match_le(self, version, constraint, prefix):
  292. version, constraint = self._adjust_local(version, constraint, prefix)
  293. return version <= constraint
  294. def _match_ge(self, version, constraint, prefix):
  295. version, constraint = self._adjust_local(version, constraint, prefix)
  296. return version >= constraint
  297. def _match_eq(self, version, constraint, prefix):
  298. version, constraint = self._adjust_local(version, constraint, prefix)
  299. if not prefix:
  300. result = (version == constraint)
  301. else:
  302. result = _match_prefix(version, constraint)
  303. return result
  304. def _match_arbitrary(self, version, constraint, prefix):
  305. return str(version) == str(constraint)
  306. def _match_ne(self, version, constraint, prefix):
  307. version, constraint = self._adjust_local(version, constraint, prefix)
  308. if not prefix:
  309. result = (version != constraint)
  310. else:
  311. result = not _match_prefix(version, constraint)
  312. return result
  313. def _match_compatible(self, version, constraint, prefix):
  314. version, constraint = self._adjust_local(version, constraint, prefix)
  315. if version == constraint:
  316. return True
  317. if version < constraint:
  318. return False
  319. # if not prefix:
  320. # return True
  321. release_clause = constraint._release_clause
  322. if len(release_clause) > 1:
  323. release_clause = release_clause[:-1]
  324. pfx = '.'.join([str(i) for i in release_clause])
  325. return _match_prefix(version, pfx)
  326. _REPLACEMENTS = (
  327. (re.compile('[.+-]$'), ''), # remove trailing puncts
  328. (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start
  329. (re.compile('^[.-]'), ''), # remove leading puncts
  330. (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses
  331. (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion)
  332. (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion)
  333. (re.compile('[.]{2,}'), '.'), # multiple runs of '.'
  334. (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha
  335. (re.compile(r'\b(pre-alpha|prealpha)\b'),
  336. 'pre.alpha'), # standardise
  337. (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses
  338. )
  339. _SUFFIX_REPLACEMENTS = (
  340. (re.compile('^[:~._+-]+'), ''), # remove leading puncts
  341. (re.compile('[,*")([\\]]'), ''), # remove unwanted chars
  342. (re.compile('[~:+_ -]'), '.'), # replace illegal chars
  343. (re.compile('[.]{2,}'), '.'), # multiple runs of '.'
  344. (re.compile(r'\.$'), ''), # trailing '.'
  345. )
  346. _NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)')
  347. def _suggest_semantic_version(s):
  348. """
  349. Try to suggest a semantic form for a version for which
  350. _suggest_normalized_version couldn't come up with anything.
  351. """
  352. result = s.strip().lower()
  353. for pat, repl in _REPLACEMENTS:
  354. result = pat.sub(repl, result)
  355. if not result:
  356. result = '0.0.0'
  357. # Now look for numeric prefix, and separate it out from
  358. # the rest.
  359. # import pdb; pdb.set_trace()
  360. m = _NUMERIC_PREFIX.match(result)
  361. if not m:
  362. prefix = '0.0.0'
  363. suffix = result
  364. else:
  365. prefix = m.groups()[0].split('.')
  366. prefix = [int(i) for i in prefix]
  367. while len(prefix) < 3:
  368. prefix.append(0)
  369. if len(prefix) == 3:
  370. suffix = result[m.end():]
  371. else:
  372. suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():]
  373. prefix = prefix[:3]
  374. prefix = '.'.join([str(i) for i in prefix])
  375. suffix = suffix.strip()
  376. if suffix:
  377. # import pdb; pdb.set_trace()
  378. # massage the suffix.
  379. for pat, repl in _SUFFIX_REPLACEMENTS:
  380. suffix = pat.sub(repl, suffix)
  381. if not suffix:
  382. result = prefix
  383. else:
  384. sep = '-' if 'dev' in suffix else '+'
  385. result = prefix + sep + suffix
  386. if not is_semver(result):
  387. result = None
  388. return result
  389. def _suggest_normalized_version(s):
  390. """Suggest a normalized version close to the given version string.
  391. If you have a version string that isn't rational (i.e. NormalizedVersion
  392. doesn't like it) then you might be able to get an equivalent (or close)
  393. rational version from this function.
  394. This does a number of simple normalizations to the given string, based
  395. on observation of versions currently in use on PyPI. Given a dump of
  396. those version during PyCon 2009, 4287 of them:
  397. - 2312 (53.93%) match NormalizedVersion without change
  398. with the automatic suggestion
  399. - 3474 (81.04%) match when using this suggestion method
  400. @param s {str} An irrational version string.
  401. @returns A rational version string, or None, if couldn't determine one.
  402. """
  403. try:
  404. _normalized_key(s)
  405. return s # already rational
  406. except UnsupportedVersionError:
  407. pass
  408. rs = s.lower()
  409. # part of this could use maketrans
  410. for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'),
  411. ('beta', 'b'), ('rc', 'c'), ('-final', ''),
  412. ('-pre', 'c'),
  413. ('-release', ''), ('.release', ''), ('-stable', ''),
  414. ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''),
  415. ('final', '')):
  416. rs = rs.replace(orig, repl)
  417. # if something ends with dev or pre, we add a 0
  418. rs = re.sub(r"pre$", r"pre0", rs)
  419. rs = re.sub(r"dev$", r"dev0", rs)
  420. # if we have something like "b-2" or "a.2" at the end of the
  421. # version, that is probably beta, alpha, etc
  422. # let's remove the dash or dot
  423. rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs)
  424. # 1.0-dev-r371 -> 1.0.dev371
  425. # 0.1-dev-r79 -> 0.1.dev79
  426. rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs)
  427. # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1
  428. rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs)
  429. # Clean: v0.3, v1.0
  430. if rs.startswith('v'):
  431. rs = rs[1:]
  432. # Clean leading '0's on numbers.
  433. # TODO: unintended side-effect on, e.g., "2003.05.09"
  434. # PyPI stats: 77 (~2%) better
  435. rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
  436. # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers
  437. # zero.
  438. # PyPI stats: 245 (7.56%) better
  439. rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs)
  440. # the 'dev-rNNN' tag is a dev tag
  441. rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs)
  442. # clean the - when used as a pre delimiter
  443. rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs)
  444. # a terminal "dev" or "devel" can be changed into ".dev0"
  445. rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs)
  446. # a terminal "dev" can be changed into ".dev0"
  447. rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs)
  448. # a terminal "final" or "stable" can be removed
  449. rs = re.sub(r"(final|stable)$", "", rs)
  450. # The 'r' and the '-' tags are post release tags
  451. # 0.4a1.r10 -> 0.4a1.post10
  452. # 0.9.33-17222 -> 0.9.33.post17222
  453. # 0.9.33-r17222 -> 0.9.33.post17222
  454. rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs)
  455. # Clean 'r' instead of 'dev' usage:
  456. # 0.9.33+r17222 -> 0.9.33.dev17222
  457. # 1.0dev123 -> 1.0.dev123
  458. # 1.0.git123 -> 1.0.dev123
  459. # 1.0.bzr123 -> 1.0.dev123
  460. # 0.1a0dev.123 -> 0.1a0.dev123
  461. # PyPI stats: ~150 (~4%) better
  462. rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs)
  463. # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage:
  464. # 0.2.pre1 -> 0.2c1
  465. # 0.2-c1 -> 0.2c1
  466. # 1.0preview123 -> 1.0c123
  467. # PyPI stats: ~21 (0.62%) better
  468. rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
  469. # Tcl/Tk uses "px" for their post release markers
  470. rs = re.sub(r"p(\d+)$", r".post\1", rs)
  471. try:
  472. _normalized_key(rs)
  473. except UnsupportedVersionError:
  474. rs = None
  475. return rs
  476. #
  477. # Legacy version processing (distribute-compatible)
  478. #
  479. _VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I)
  480. _VERSION_REPLACE = {
  481. 'pre': 'c',
  482. 'preview': 'c',
  483. '-': 'final-',
  484. 'rc': 'c',
  485. 'dev': '@',
  486. '': None,
  487. '.': None,
  488. }
  489. def _legacy_key(s):
  490. def get_parts(s):
  491. result = []
  492. for p in _VERSION_PART.split(s.lower()):
  493. p = _VERSION_REPLACE.get(p, p)
  494. if p:
  495. if '0' <= p[:1] <= '9':
  496. p = p.zfill(8)
  497. else:
  498. p = '*' + p
  499. result.append(p)
  500. result.append('*final')
  501. return result
  502. result = []
  503. for p in get_parts(s):
  504. if p.startswith('*'):
  505. if p < '*final':
  506. while result and result[-1] == '*final-':
  507. result.pop()
  508. while result and result[-1] == '00000000':
  509. result.pop()
  510. result.append(p)
  511. return tuple(result)
  512. class LegacyVersion(Version):
  513. def parse(self, s):
  514. return _legacy_key(s)
  515. @property
  516. def is_prerelease(self):
  517. result = False
  518. for x in self._parts:
  519. if (isinstance(x, string_types) and x.startswith('*') and
  520. x < '*final'):
  521. result = True
  522. break
  523. return result
  524. class LegacyMatcher(Matcher):
  525. version_class = LegacyVersion
  526. _operators = dict(Matcher._operators)
  527. _operators['~='] = '_match_compatible'
  528. numeric_re = re.compile(r'^(\d+(\.\d+)*)')
  529. def _match_compatible(self, version, constraint, prefix):
  530. if version < constraint:
  531. return False
  532. m = self.numeric_re.match(str(constraint))
  533. if not m:
  534. logger.warning('Cannot compute compatible match for version %s '
  535. ' and constraint %s', version, constraint)
  536. return True
  537. s = m.groups()[0]
  538. if '.' in s:
  539. s = s.rsplit('.', 1)[0]
  540. return _match_prefix(version, s)
  541. #
  542. # Semantic versioning
  543. #
  544. _SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)'
  545. r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?'
  546. r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I)
  547. def is_semver(s):
  548. return _SEMVER_RE.match(s)
  549. def _semantic_key(s):
  550. def make_tuple(s, absent):
  551. if s is None:
  552. result = (absent,)
  553. else:
  554. parts = s[1:].split('.')
  555. # We can't compare ints and strings on Python 3, so fudge it
  556. # by zero-filling numeric values so simulate a numeric comparison
  557. result = tuple([p.zfill(8) if p.isdigit() else p for p in parts])
  558. return result
  559. m = is_semver(s)
  560. if not m:
  561. raise UnsupportedVersionError(s)
  562. groups = m.groups()
  563. major, minor, patch = [int(i) for i in groups[:3]]
  564. # choose the '|' and '*' so that versions sort correctly
  565. pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*')
  566. return (major, minor, patch), pre, build
  567. class SemanticVersion(Version):
  568. def parse(self, s):
  569. return _semantic_key(s)
  570. @property
  571. def is_prerelease(self):
  572. return self._parts[1][0] != '|'
  573. class SemanticMatcher(Matcher):
  574. version_class = SemanticVersion
  575. class VersionScheme(object):
  576. def __init__(self, key, matcher, suggester=None):
  577. self.key = key
  578. self.matcher = matcher
  579. self.suggester = suggester
  580. def is_valid_version(self, s):
  581. try:
  582. self.matcher.version_class(s)
  583. result = True
  584. except UnsupportedVersionError:
  585. result = False
  586. return result
  587. def is_valid_matcher(self, s):
  588. try:
  589. self.matcher(s)
  590. result = True
  591. except UnsupportedVersionError:
  592. result = False
  593. return result
  594. def is_valid_constraint_list(self, s):
  595. """
  596. Used for processing some metadata fields
  597. """
  598. # See issue #140. Be tolerant of a single trailing comma.
  599. if s.endswith(','):
  600. s = s[:-1]
  601. return self.is_valid_matcher('dummy_name (%s)' % s)
  602. def suggest(self, s):
  603. if self.suggester is None:
  604. result = None
  605. else:
  606. result = self.suggester(s)
  607. return result
  608. _SCHEMES = {
  609. 'normalized': VersionScheme(_normalized_key, NormalizedMatcher,
  610. _suggest_normalized_version),
  611. 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s),
  612. 'semantic': VersionScheme(_semantic_key, SemanticMatcher,
  613. _suggest_semantic_version),
  614. }
  615. _SCHEMES['default'] = _SCHEMES['normalized']
  616. def get_scheme(name):
  617. if name not in _SCHEMES:
  618. raise ValueError('unknown scheme name: %r' % name)
  619. return _SCHEMES[name]