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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

81 statements  

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

2# 

3# Copyright (C) 2012-2023 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 LegacyVersion as LV 

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 

29 

30def _is_version_marker(s): 

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

32 

33 

34def _is_literal(o): 

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

36 return False 

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

38 

39 

40def _get_versions(s): 

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

42 

43 

44class Evaluator(object): 

45 """ 

46 This class is used to evaluate marker expressions. 

47 """ 

48 

49 operations = { 

50 '==': lambda x, y: x == y, 

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

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

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

54 '<': lambda x, y: x < y, 

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

56 '>': lambda x, y: x > y, 

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

58 'and': lambda x, y: x and y, 

59 'or': lambda x, y: x or y, 

60 'in': lambda x, y: x in y, 

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

62 } 

63 

64 def evaluate(self, expr, context): 

65 """ 

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

67 function in the specified context. 

68 """ 

69 if isinstance(expr, string_types): 

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

71 result = expr[1:-1] 

72 else: 

73 if expr not in context: 

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

75 result = context[expr] 

76 else: 

77 assert isinstance(expr, dict) 

78 op = expr['op'] 

79 if op not in self.operations: 

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

81 elhs = expr['lhs'] 

82 erhs = expr['rhs'] 

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

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

85 

86 lhs = self.evaluate(elhs, context) 

87 rhs = self.evaluate(erhs, context) 

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

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

90 lhs = LV(lhs) 

91 rhs = LV(rhs) 

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

93 lhs = LV(lhs) 

94 rhs = _get_versions(rhs) 

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

96 return result 

97 

98 

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

100 

101 

102def default_context(): 

103 

104 def format_full_version(info): 

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

106 kind = info.releaselevel 

107 if kind != 'final': 

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

109 return version 

110 

111 if hasattr(sys, 'implementation'): 

112 implementation_version = format_full_version(sys.implementation.version) 

113 implementation_name = sys.implementation.name 

114 else: 

115 implementation_version = '0' 

116 implementation_name = '' 

117 

118 ppv = platform.python_version() 

119 m = _DIGITS.match(ppv) 

120 pv = m.group(0) 

121 result = { 

122 'implementation_name': implementation_name, 

123 'implementation_version': implementation_version, 

124 'os_name': os.name, 

125 'platform_machine': platform.machine(), 

126 'platform_python_implementation': platform.python_implementation(), 

127 'platform_release': platform.release(), 

128 'platform_system': platform.system(), 

129 'platform_version': platform.version(), 

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

131 'python_full_version': ppv, 

132 'python_version': pv, 

133 'sys_platform': sys.platform, 

134 } 

135 return result 

136 

137 

138DEFAULT_CONTEXT = default_context() 

139del default_context 

140 

141evaluator = Evaluator() 

142 

143def interpret_parsed(expr, execution_context=None): 

144 context = dict(DEFAULT_CONTEXT) 

145 if execution_context: 

146 context.update(execution_context) 

147 return evaluator.evaluate(expr, context) 

148 

149def interpret(marker, execution_context=None): 

150 """ 

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

152 

153 :param marker: The marker to interpret. 

154 :type marker: str 

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

156 :type execution_context: mapping 

157 """ 

158 try: 

159 expr, rest = parse_marker(marker) 

160 except Exception as e: 

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

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

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

164 return interpret_parsed(expr, execution_context)