Skip to content

Commit

Permalink
Destroy MarkerEvaluation for being pointless, fix two silly mistakes …
Browse files Browse the repository at this point in the history
…in tests and update packaging changes.
  • Loading branch information
s-t-e-v-e-n-k committed Jan 7, 2016
1 parent 7d63ebf commit 3bd5118
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 175 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ update-vendored:
pip install -r pkg_resources/_vendor/vendored.txt -t pkg_resources/_vendor/
sed -i -e 's/ \(pyparsing\)/ pkg_resources._vendor.\1/' \
pkg_resources/_vendor/packaging/*.py
sed -i -e 's/ \(six\)/ pkg_resources._vendor.\1/' \
pkg_resources/_vendor/packaging/*.py
rm -rf pkg_resources/_vendor/*.{egg,dist}-info
181 changes: 21 additions & 160 deletions pkg_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1407,170 +1407,31 @@ def to_filename(name):
return name.replace('-','_')


class MarkerEvaluation(object):
values = {
'os_name': lambda: os.name,
'sys_platform': lambda: sys.platform,
'python_full_version': platform.python_version,
'python_version': lambda: platform.python_version()[:3],
'platform_version': platform.version,
'platform_machine': platform.machine,
'platform_python_implementation': platform.python_implementation,
'python_implementation': platform.python_implementation,
}

@classmethod
def is_invalid_marker(cls, text):
"""
Validate text as a PEP 508 environment marker; return an exception
if invalid or False otherwise.
"""
try:
cls.evaluate_marker(text)
except packaging.markers.InvalidMarker as e:
e.filename = None
e.lineno = None
return e
return False

@staticmethod
def normalize_exception(exc):
"""
Given a SyntaxError from a marker evaluation, normalize the error
message:
- Remove indications of filename and line number.
- Replace platform-specific error messages with standard error
messages.
"""
subs = {
'unexpected EOF while parsing': 'invalid syntax',
'parenthesis is never closed': 'invalid syntax',
}
exc.filename = None
exc.lineno = None
exc.msg = subs.get(exc.msg, exc.msg)
return exc

@classmethod
def and_test(cls, nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
items = [
cls.interpret(nodelist[i])
for i in range(1, len(nodelist), 2)
]
return functools.reduce(operator.and_, items)

@classmethod
def test(cls, nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
items = [
cls.interpret(nodelist[i])
for i in range(1, len(nodelist), 2)
]
return functools.reduce(operator.or_, items)

@classmethod
def atom(cls, nodelist):
t = nodelist[1][0]
if t == token.LPAR:
if nodelist[2][0] == token.RPAR:
raise SyntaxError("Empty parentheses")
return cls.interpret(nodelist[2])
msg = "Language feature not supported in environment markers"
raise SyntaxError(msg)

@classmethod
def comparison(cls, nodelist):
if len(nodelist) > 4:
msg = "Chained comparison not allowed in environment markers"
raise SyntaxError(msg)
comp = nodelist[2][1]
cop = comp[1]
if comp[0] == token.NAME:
if len(nodelist[2]) == 3:
if cop == 'not':
cop = 'not in'
else:
cop = 'is not'
try:
cop = cls.get_op(cop)
except KeyError:
msg = repr(cop) + " operator not allowed in environment markers"
raise SyntaxError(msg)
return cop(cls.evaluate(nodelist[1]), cls.evaluate(nodelist[3]))

@classmethod
def get_op(cls, op):
ops = {
symbol.test: cls.test,
symbol.and_test: cls.and_test,
symbol.atom: cls.atom,
symbol.comparison: cls.comparison,
'not in': lambda x, y: x not in y,
'in': lambda x, y: x in y,
'==': operator.eq,
'!=': operator.ne,
'<': operator.lt,
'>': operator.gt,
'<=': operator.le,
'>=': operator.ge,
}
if hasattr(symbol, 'or_test'):
ops[symbol.or_test] = cls.test
return ops[op]

@classmethod
def evaluate_marker(cls, text, extra=None):
"""
Evaluate a PEP 508 environment marker on CPython 2.4+.
Return a boolean indicating the marker result in this environment.
Raise InvalidMarker if marker is invalid.
This implementation uses the 'pyparsing' module.
"""
marker = packaging.markers.Marker(text)
return marker.evaluate()
def invalid_marker(text):
"""
Validate text as a PEP 508 environment marker; return an exception
if invalid or False otherwise.
"""
try:
evaluate_marker(text)
except packaging.markers.InvalidMarker as e:
e.filename = None
e.lineno = None
return e
return False

@classmethod
def interpret(cls, nodelist):
while len(nodelist)==2: nodelist = nodelist[1]
try:
op = cls.get_op(nodelist[0])
except KeyError:
raise SyntaxError("Comparison or logical expression expected")
return op(nodelist)

@classmethod
def evaluate(cls, nodelist):
while len(nodelist)==2: nodelist = nodelist[1]
kind = nodelist[0]
name = nodelist[1]
if kind==token.NAME:
try:
op = cls.values[name]
except KeyError:
raise SyntaxError("Unknown name %r" % name)
return op()
if kind==token.STRING:
s = nodelist[1]
if not cls._safe_string(s):
raise SyntaxError(
"Only plain strings allowed in environment markers")
return s[1:-1]
msg = "Language feature not supported in environment markers"
raise SyntaxError(msg)
def evaluate_marker(text, extra=None):
"""
Evaluate a PEP 508 environment marker.
Return a boolean indicating the marker result in this environment.
Raise InvalidMarker if marker is invalid.
@staticmethod
def _safe_string(cand):
return (
cand[:1] in "'\"" and
not cand.startswith('"""') and
not cand.startswith("'''") and
'\\' not in cand
)
This implementation uses the 'pyparsing' module.
"""
marker = packaging.markers.Marker(text)
return marker.evaluate()

invalid_marker = MarkerEvaluation.is_invalid_marker
evaluate_marker = MarkerEvaluation.evaluate_marker

class NullProvider:
"""Try to implement resources and metadata for arbitrary PEP 302 loaders"""
Expand Down
4 changes: 2 additions & 2 deletions pkg_resources/_vendor/packaging/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Value(Node):

VARIABLE = (
L("implementation_version") |
L("python_implementation") |
L("platform_python_implementation") |
L("implementation_name") |
L("python_full_version") |
L("platform_release") |
Expand Down Expand Up @@ -209,7 +209,7 @@ def default_environment():
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"python_implementation": platform.python_implementation(),
"platform_python_implementation": platform.python_implementation(),
"python_version": platform.python_version()[:3],
"sys_platform": sys.platform,
}
Expand Down
13 changes: 10 additions & 3 deletions pkg_resources/_vendor/packaging/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pkg_resources._vendor.pyparsing import stringStart, stringEnd, originalTextFor, ParseException
from pkg_resources._vendor.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine
from pkg_resources._vendor.pyparsing import Literal as L # noqa
from six.moves.urllib import parse as urlparse
from pkg_resources._vendor.six.moves.urllib import parse as urlparse

from .markers import MARKER_EXPR, Marker
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
Expand Down Expand Up @@ -73,6 +73,12 @@ class InvalidRequirement(ValueError):


class Requirement(object):
"""Parse a requirement.
Parse a given requirement string into its parts, such as name, specifier,
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
string.
"""

# TODO: Can we test whether something is contained within a requirement?
# If so how do we do that? Do we need to test against the _name_ of
Expand All @@ -82,9 +88,10 @@ class Requirement(object):
def __init__(self, requirement_string):
try:
req = REQUIREMENT.parseString(requirement_string)
except ParseException:
except ParseException as e:
raise InvalidRequirement(
"Invalid requirement: {0!r}".format(requirement_string))
"Invalid requirement, parse error at \"{0!r}\"".format(
requirement_string[e.loc:e.loc + 8]))

self.name = req.name
if req.url:
Expand Down
6 changes: 4 additions & 2 deletions pkg_resources/_vendor/packaging/specifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ class LegacySpecifier(_IndividualSpecifier):
"""
)

_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.X | re.I)
_regex = re.compile(
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)

_operators = {
"==": "equal",
Expand Down Expand Up @@ -366,7 +367,8 @@ class Specifier(_IndividualSpecifier):
"""
)

_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.X | re.I)
_regex = re.compile(
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)

_operators = {
"~=": "compatible",
Expand Down
1 change: 1 addition & 0 deletions pkg_resources/_vendor/vendored.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
packaging==15.3
pyparsing==2.0.6
six==1.10.0
2 changes: 1 addition & 1 deletion pkg_resources/api_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ Environment Markers
>>> print(im("os.open=='y'"))
Invalid marker: "os.open=='y'"

>>> em("'linux' in sys_platform")
>>> em("sys_platform=='win32'") == (sys.platform=='win32')
True

>>> em("python_version >= '2.6'")
Expand Down
12 changes: 5 additions & 7 deletions pkg_resources/tests/test_markers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
try:
import unittest.mock as mock
import unitest.mock as mock
except ImportError:
import mock
import mock

from pkg_resources import evaluate_marker


@mock.patch.dict('pkg_resources.MarkerEvaluation.values',
python_full_version=mock.Mock(return_value='2.7.10'))
def test_ordering():
assert evaluate_marker("python_full_version > '2.7.3'") is True
@mock.patch('platform.python_version', return_value='2.7.10')
def test_ordering(python_version_mock):
assert evaluate_marker("python_full_version > '2.7.3'") is True

0 comments on commit 3bd5118

Please sign in to comment.