Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,16 @@ def validate(self, data: str, **kwargs: Any) -> str:
e = self._error

try:
if self._pattern.search(data):
# In non-MULTILINE mode, ``$`` also matches just before a single
# trailing newline, so e.g. ``^a$`` spuriously matches ``"a\n"``.
# When the data ends with a newline, re-check with an extra newline
# appended so that quirk cannot fire; if the match vanishes it was a
# phantom and validation must fail. ``newline`` mirrors the data type
# so byte patterns keep working.
newline = "\n" if isinstance(data, str) else b"\n"
if self._pattern.search(data) and not (
data.endswith(newline) and not self._pattern.search(data + newline)
):
return data
else:
error_message = (
Expand Down
17 changes: 17 additions & 0 deletions test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,23 @@ def test_regex():
with SE:
Regex(r"^[a-z]+$").validate("letters + spaces") == "letters + spaces"

# A trailing newline must not sneak past an end-anchored pattern: in
# non-MULTILINE mode Python's ``$`` also matches just before a single
# trailing newline, so ``^a$`` used to spuriously accept ``"a\n"``.
assert Regex(r"^a$").validate("a") == "a"
with SE:
Regex(r"^a$").validate("a\n")
with SE:
Regex(r"^a$").validate("a\n\n")
with SE:
Regex(r"^a$").validate("\na")
with SE:
Regex(r"^[a-z]+$").validate("letters\n")
# Unanchored patterns keep their substring-matching behaviour, so a value
# with a trailing newline is still accepted.
assert Regex(r"foo").validate("foo\n") == "foo\n"
assert Regex(r"^foo").validate("foo\n") == "foo\n"

# Validate dict key
assert Schema({Regex(r"^foo"): str}).validate({"fookey": "value"}) == {
"fookey": "value"
Expand Down