Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/distlib/markers.py: 81%

78 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:51 +0000

1# -*- coding: utf-8 -*- 

2# 

3# Copyright (C) 2012-2017 Vinay Sajip. 

4# Licensed to the Python Software Foundation under a contributor agreement. 

5# See LICENSE.txt and CONTRIBUTORS.txt. 

6# 

7""" 

8Parser for the environment markers micro-language defined in PEP 508. 

9""" 

10 

11# Note: In PEP 345, the micro-language was Python compatible, so the ast 

12# module could be used to parse it. However, PEP 508 introduced operators such 

13# as ~= and === which aren't in Python, necessitating a different approach. 

14 

15import os 

16import re 

17import sys 

18import platform 

19 

20from .compat import string_types 

21from .util import in_venv, parse_marker 

22from .version import NormalizedVersion as NV 

23 

24__all__ = ['interpret'] 

25 

26_VERSION_PATTERN = re.compile(r'((\d+(\.\d+)*\w*)|\'(\d+(\.\d+)*\w*)\'|\"(\d+(\.\d+)*\w*)\")') 

27_VERSION_MARKERS = {'python_version', 'python_full_version'} 

28 

29def _is_version_marker(s): 

30 return isinstance(s, string_types) and s in _VERSION_MARKERS 

31 

32def _is_literal(o): 

33 if not isinstance(o, string_types) or not o: 

34 return False 

35 return o[0] in '\'"' 

36 

37def _get_versions(s): 

38 return {NV(m.groups()[0]) for m in _VERSION_PATTERN.finditer(s)} 

39 

40class Evaluator(object): 

41 """ 

42 This class is used to evaluate marker expressions. 

43 """ 

44 

45 operations = { 

46 '==': lambda x, y: x == y, 

47 '===': lambda x, y: x == y, 

48 '~=': lambda x, y: x == y or x > y, 

49 '!=': lambda x, y: x != y, 

50 '<': lambda x, y: x < y, 

51 '<=': lambda x, y: x == y or x < y, 

52 '>': lambda x, y: x > y, 

53 '>=': lambda x, y: x == y or x > y, 

54 'and': lambda x, y: x and y, 

55 'or': lambda x, y: x or y, 

56 'in': lambda x, y: x in y, 

57 'not in': lambda x, y: x not in y, 

58 } 

59 

60 def evaluate(self, expr, context): 

61 """ 

62 Evaluate a marker expression returned by the :func:`parse_requirement` 

63 function in the specified context. 

64 """ 

65 if isinstance(expr, string_types): 

66 if expr[0] in '\'"': 

67 result = expr[1:-1] 

68 else: 

69 if expr not in context: 

70 raise SyntaxError('unknown variable: %s' % expr) 

71 result = context[expr] 

72 else: 

73 assert isinstance(expr, dict) 

74 op = expr['op'] 

75 if op not in self.operations: 

76 raise NotImplementedError('op not implemented: %s' % op) 

77 elhs = expr['lhs'] 

78 erhs = expr['rhs'] 

79 if _is_literal(expr['lhs']) and _is_literal(expr['rhs']): 

80 raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs)) 

81 

82 lhs = self.evaluate(elhs, context) 

83 rhs = self.evaluate(erhs, context) 

84 if ((_is_version_marker(elhs) or _is_version_marker(erhs)) and 

85 op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')): 

86 lhs = NV(lhs) 

87 rhs = NV(rhs) 

88 elif _is_version_marker(elhs) and op in ('in', 'not in'): 

89 lhs = NV(lhs) 

90 rhs = _get_versions(rhs) 

91 result = self.operations[op](lhs, rhs) 

92 return result 

93 

94_DIGITS = re.compile(r'\d+\.\d+') 

95 

96def default_context(): 

97 def format_full_version(info): 

98 version = '%s.%s.%s' % (info.major, info.minor, info.micro) 

99 kind = info.releaselevel 

100 if kind != 'final': 

101 version += kind[0] + str(info.serial) 

102 return version 

103 

104 if hasattr(sys, 'implementation'): 

105 implementation_version = format_full_version(sys.implementation.version) 

106 implementation_name = sys.implementation.name 

107 else: 

108 implementation_version = '0' 

109 implementation_name = '' 

110 

111 ppv = platform.python_version() 

112 m = _DIGITS.match(ppv) 

113 pv = m.group(0) 

114 result = { 

115 'implementation_name': implementation_name, 

116 'implementation_version': implementation_version, 

117 'os_name': os.name, 

118 'platform_machine': platform.machine(), 

119 'platform_python_implementation': platform.python_implementation(), 

120 'platform_release': platform.release(), 

121 'platform_system': platform.system(), 

122 'platform_version': platform.version(), 

123 'platform_in_venv': str(in_venv()), 

124 'python_full_version': ppv, 

125 'python_version': pv, 

126 'sys_platform': sys.platform, 

127 } 

128 return result 

129 

130DEFAULT_CONTEXT = default_context() 

131del default_context 

132 

133evaluator = Evaluator() 

134 

135def interpret(marker, execution_context=None): 

136 """ 

137 Interpret a marker and return a result depending on environment. 

138 

139 :param marker: The marker to interpret. 

140 :type marker: str 

141 :param execution_context: The context used for name lookup. 

142 :type execution_context: mapping 

143 """ 

144 try: 

145 expr, rest = parse_marker(marker) 

146 except Exception as e: 

147 raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e)) 

148 if rest and rest[0] != '#': 

149 raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest)) 

150 context = dict(DEFAULT_CONTEXT) 

151 if execution_context: 

152 context.update(execution_context) 

153 return evaluator.evaluate(expr, context)