comparison env/lib/python3.7/site-packages/distlib/version.py @ 0:26e78fe6e8c4 draft

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