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
8 changes: 5 additions & 3 deletions dill/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ def findsource(object):
except AttributeError: pass
if isclass(object):
name = object.__name__
pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
# __name__ is not required to be an identifier, so escape it to keep
# it a literal rather than letting it act as part of the pattern
pat = re.compile(r'^(\s*)class\s*' + re.escape(name) + r'\b')
# make some effort to find the best matching class definition:
# use the one with the least indentation, which is the one
# that's most probably not inside a function definition.
Expand Down Expand Up @@ -853,7 +855,7 @@ def _closuredimport(func, alias='', builtin=False):
else: # we have to "hack" a bit... and maybe be lucky
encl = outermost(func)
# pattern: 'func = enclosing(fobj'
pat = r'.*[\w\s]=\s*'+getname(encl)+r'\('+getname(fobj)
pat = r'.*[\w\s]=\s*'+re.escape(getname(encl))+r'\('+re.escape(getname(fobj))
mod = getname(getmodule(encl))
#HACK: get file containing 'outer' function; is func there?
lines,_ = findsource(encl)
Expand All @@ -880,7 +882,7 @@ def _closuredimport(func, alias='', builtin=False):
lines,_ = findsource(name)
# pattern: 'func = enclosing('
candidate = [line for line in lines if getname(name) in line and \
re.match(r'.*[\w\s]=\s*'+getname(name)+r'\(', line)]
re.match(r'.*[\w\s]=\s*'+re.escape(getname(name))+r'\(', line)]
if not len(candidate): raise TypeError('import could not be found')
candidate = candidate[-1]
name = candidate.split('=',1)[0].split()[-1].strip()
Expand Down
27 changes: 27 additions & 0 deletions dill/tests/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,32 @@ def test_safe():
except SyntaxError:
pass

class Baz:
pass

class Qux: # sits after Baz, so a pattern-like name resolves here instead
pass

def test_name_not_a_pattern():
# __name__ is assignable and need not be an identifier, so it has to be
# matched literally rather than as part of the class-definition pattern
name = Baz.__name__
try:
Baz.__name__ = r'\w+' # otherwise matches the definition of Qux
try:
source = getsource(Baz)
assert False, source
except IOError:
pass
Baz.__name__ = 'Baz((' # otherwise fails to compile
try:
getsource(Baz)
assert False
except IOError:
pass
finally:
Baz.__name__ = name

if __name__ == '__main__':
test_getsource()
test_itself()
Expand All @@ -184,3 +210,4 @@ def test_safe():
test_numpy()
test_foo()
test_safe()
test_name_not_a_pattern()