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
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
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"""
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.
15import os
16import re
17import sys
18import platform
20from .compat import string_types
21from .util import in_venv, parse_marker
22from .version import LegacyVersion as LV
24__all__ = ['interpret']
26_VERSION_PATTERN = re.compile(r'((\d+(\.\d+)*\w*)|\'(\d+(\.\d+)*\w*)\'|\"(\d+(\.\d+)*\w*)\")')
27_VERSION_MARKERS = {'python_version', 'python_full_version'}
30def _is_version_marker(s):
31 return isinstance(s, string_types) and s in _VERSION_MARKERS
34def _is_literal(o):
35 if not isinstance(o, string_types) or not o:
36 return False
37 return o[0] in '\'"'
40def _get_versions(s):
41 return {LV(m.groups()[0]) for m in _VERSION_PATTERN.finditer(s)}
44class Evaluator(object):
45 """
46 This class is used to evaluate marker expressions.
47 """
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 }
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))
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
99_DIGITS = re.compile(r'\d+\.\d+')
102def default_context():
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
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 = ''
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
138DEFAULT_CONTEXT = default_context()
139del default_context
141evaluator = Evaluator()
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)
149def interpret(marker, execution_context=None):
150 """
151 Interpret a marker and return a result depending on environment.
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)