Compiled regular expressions with auto-escaped interpolations using Python 3.14's t-strings.
This only supports Python 3.14 (which is not yet released) because it relies on t-strings.
Have you ever tried to use user input or variables in a regular expression and run into escaping issues?
For example, if you want to match a file extension that's stored in a variable:
>>> import re
>>> extension = ".txt"
>>> pattern = re.compile(rf"^.*{extension}$")
>>> text = "filetxt"
>>> if pattern.search(text):
... print(f"{text} matched")
... else:
... print(f"{text} did not match")
...
filetxt matchedSpecial regular expression characters like ., *, +, and ? need to be properly escaped when used in regular expressions.
We can use the re.escape function to manually escape each replacement field:
>>> import re
>>> extension = ".txt"
>>> pattern = re.compile(rf"^.*{re.escape(extension)}$")
>>> text = "filetxt"
>>> if pattern.search(text):
... print(f"{text} matched")
... else:
... print(f"{text} did not match")
...
filetxt did not matchThis is tedious, especially with multiple f-string replacement fields.
The regex_template.compile function automatically escapes interpolated variables when using t-strings, while leaving the main pattern unescaped:
>>> import regex_template as ret
>>> extension = ".txt"
>>> pattern = ret.compile(rt"^.*{extension}$")
>>> text = "filetxt"
>>> if pattern.search(text):
... print(f"{text} matched")
... else:
... print(f"{text} did not match")
...
filetxt did not matchReplacement fields ({...}) are automatically escaped.
Note that regex_template.compile only accepts t-strings.
If you need to ensure specific replacement fields that are not escaped, use the :safe format specifier:
>>> import regex_template as ret
>>> part = "[^/]+"
>>> pattern = ret.compile(rt"/home/({part:safe})/Documents")
>>> text = "/home/trey/Documents"
>>> if match := pattern.search(text):
... print(f"Matched Documents for user {match[1]}")
...
Matched Documents for user treyAll standard Python format specifiers work normally and are applied before escaping:
>>> import regex_template as ret
>>> tracks = [(1, "Gloria"), (2, "Redondo Beach")]
>>> filename = "01 Gloria.mp3"
>>> for n, name in tracks:
... pattern = ret.compile(rt"{n:02d}\ {name}\.mp3")
... if pattern.fullmatch(filename):
... print(f"Track {n} found!")
...
Track 1 found!By default, regex_template.compile enables verbose mode (re.VERBOSE) to encourage the use of more readable regular expressions:
import regex_template as ret
username = "trey"
hostname = "farnsworth"
# SSH log entry pattern
pattern = ret.compile(rt"""
^
(\w{{3}} \s+ \d{{1,2}}) \s+ # Month and day ("Jan 1")
(\d{{2}} : \d{{2}} : \d{{2}}) \s+ # Time ("14:23:45")
{hostname} \s+ # Server hostname (auto-escaped)
sshd \[\d+\] : \s+ # sshd process
Accepted \s+ \w+ \s+ # Authentication method
for \s+ {username} \s+ # Username (auto-escaped)
from \s+ ([\d.]+) \s+ # IP address
port \s+ \d+ # Port number
""")
with open("sshd.log") as log_file:
for line in log_file:
if match := pattern.search(line):
print("Login from IP {match[1]}")You can set verbose=False to disable this:
pattern = ret.compile(
rt"^(\w+ \d+ \d+:\d+:\d+) {hostname} .* for {username} from ([\d.]+)",
verbose=False,
)You can install regex-template with pip (you'll need to be on Python 3.14):
pip install regex-templateOr if you have uv installed and you'd like to play with it right now (Python 3.14 will be auto-installed):
uvx --with regex-template pythonYou can then import regex_template like this:
import regex_template as retThis project uses hatch.
To run the tests:
hatch testTo see code coverage:
hatch test --cover
hatch run cov-html
open htmlcov/index.htmlregex-template is distributed under the terms of the MIT license.