I have a regular expression defined in a YAML configuration file.
To make things easier, I'll use a dictionary here instead:
rule_1 = {
'kind': 'regex',
'method': 'match',
'args': None,
'kwargs': {
'pattern': "[a-z_]+",
'flags': re.X,
'string': 's_test.log',
}
}
I want to be able to parse that rule in a function.
If we assume such values don't change, then I could do something like this.
Importing modules:
import re
from operator import methodcaller
from functools import partial
My first function below is able to adapt to changes in the regex method used:
def rule_parser_re_1(*, kind, method, args=None, kwargs=None):
if args is None: args = []
if kwargs is None: kwargs = {}
mc = methodcaller(method, **kwargs)
return mc(re)
It works as expected:
>>> rule_parser_re_1(**rule_1)
<re.Match object; span=(0, 6), match='s_test'>
Now, let's say I don't have the string to parse available at the time the configuration dictionary is defined.
e.g. Let's say it's a specific line in a file which is accessible at runtime only.
myfile = """
first line
second line
third line
"""
io_myfile = io.StringIO(myfile)
content = io_myfile.readlines()
My second rule, where "line_number" (i.e. an int) replaces "string" (i.e. a str).
rule_2 = {
'kind': 'regex',
'method': 'match',
'args': None,
'kwargs': {
'pattern': "[a-z_]+",
'flags': re.X,
'line_number': 2,
}
}
My understanding is that I should be able to solve this by defining a partial rule_parser_re function.
Such function should behave like the original one called with pattern and flags, but without string.
I've come up with the below function:
def rule_parser_re_2(*, kind, method, args=None, kwargs=None):
if args is None: args = []
if kwargs is None: kwargs = {}
if kind == 'regex' and method == 'match':
pa = partial(re.match, pattern=kwargs['pattern'], flags=kwargs['flags'])
return pa
Which also seems to work properly:
>>> r2 = rule_parser_re_2(**rule_2)
>>> r2(string=content[2])
<re.Match object; span=(0, 6), match='second'>
Although, I see two maintainability problems with the above implementation:
- I'm using that
ifstatement which forces me to amend the function for everyremethod I want to support; - I need to explicitly specify the arguments, instead of just unpacking "**kwargs"
My aims/doubts:
- Is there any way to make the above function more dynamic and maintainable?
- Are
functools.partial()andoperator.methodcaller()the right tools for the job? - If so, can they be combined together?
Thanks!
lines=None. And passcontentsin the second case. And inside the function check ifkwargscontainsline_number, if so, fromkwargs,popline_numberand addstringkey withlines[<popped value>]content) to a new kwarg namedlines. Then, I useline_numberI get from dictionaryrule_2to get the proper item fromcontent. Eventually, I modifyrule_2- or a copy of it - by replacing its keyline_numberwithstringand valuecontent[<int>]. At this point, I can use the same approach used forrule_1. Is that correct?