Skip to content

Commit

Permalink
Also store reason of deprecation, if any, in the cache
Browse files Browse the repository at this point in the history
This is done through some mangling of the deprecated name.

Based on the fact that a function name cannot contain ':', we use it as a
delimiter between function name and reason.

So a deprecated name without ':' is just a name, and a deprecated name with at
least a ':' is a name followed by the reason why it's deprecated.

Note that this storage does *not* introduce a new format version ;-)

Fix #41
  • Loading branch information
serge-sans-paille committed Nov 4, 2020
1 parent 00b480b commit bea2138
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 11 deletions.
31 changes: 22 additions & 9 deletions memestra/memestra.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
def make_deprecated(node, reason=None):
return (node, reason)

def store_deprecated(name, reason):
return name if reason is None else '{}:{}'.format(name, reason)

def load_deprecated(entry):
if ':' in entry:
return entry.split(':', maxsplit=1)
else:
return entry, None

class SilentDefUseChains(beniget.DefUseChains):

def unbound_identifier(self, name, node):
Expand Down Expand Up @@ -70,14 +79,15 @@ def load_deprecated_from_module(self, module_name):
if module_key in self.cache:
data = self.cache[module_key]
if data['version'] == Format.version:
return set(data['deprecated'])
return dict(load_deprecated(entry)
for entry in data['deprecated'])
elif data['generator'] == 'manual':
warnings.warn(
("skipping module {} because it has an obsolete, "
"manually generated, cache file: {}")
.format(module_name,
module_key.module_hash))
return []
return {}

with open(module_path) as fd:
try:
Expand All @@ -98,15 +108,17 @@ def load_deprecated_from_module(self, module_name):
self.recursive,
parent=self)
resolver.visit(module)
deprecated_imports = [make_deprecated(d, reason) for _, _, d, reason in
deprecated_imports = [make_deprecated(d, reason)
for _, _, d, reason in
resolver.get_deprecated_users(duc, anc)]
else:
deprecated_imports = []
deprecated = self.collect_deprecated(module, duc, anc)
deprecated.update(deprecated_imports)
dl = {d[0].name for d in deprecated if d is not None}
dl = {d[0].name: d[1] for d in deprecated if d is not None}
data = {'generator': 'memestra',
'deprecated': sorted(dl)}
'deprecated': [store_deprecated(d, dl[d]) for d in
sorted(dl)]}
self.cache[module_key] = data
return dl

Expand Down Expand Up @@ -134,7 +146,8 @@ def visit_Import(self, node):
parent = self.ancestors.parents(user.node)[-1]
if isinstance(parent, ast.Attribute):
if parent.attr in deprecated:
self.deprecated.add(make_deprecated(parent))
reason = deprecated[parent.attr]
self.deprecated.add(make_deprecated(parent, reason))

# FIXME: handle relative imports
def visit_ImportFrom(self, node):
Expand All @@ -144,12 +157,12 @@ def visit_ImportFrom(self, node):

aliases = [alias.name for alias in node.names]

for deprec in deprecated:
for deprec, reason in deprecated.items():
try:
index = aliases.index(deprec)
alias = node.names[index]
for user in self.def_use_chains.chains[alias].users():
self.deprecated.add(make_deprecated(user.node))
self.deprecated.add(make_deprecated(user.node, reason))
except ValueError:
continue

Expand Down Expand Up @@ -234,7 +247,7 @@ def extract_decorator_from_parents(self, parents, deprecated):
for keyword in parent.keywords:
if self.reason_keyword == keyword.arg:
reason = keyword.value.value
deprecated.add(make_deprecated(parent_p, reason=reason))
deprecated.add(make_deprecated(parent_p, reason))
return

def prettyname(node):
Expand Down
3 changes: 3 additions & 0 deletions tests/misc/some_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
def foo(): pass

def bar(): pass

@deprecated("because it's too old")
def foobar(): pass
7 changes: 5 additions & 2 deletions tests/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def checkDeprecatedUses(self, code, expected_output):

def test_import_from(self):
code = '''
from some_module import foo, bar
from some_module import foo, bar, foobar
foobar()
def foobar():
foo()
Expand All @@ -27,7 +28,9 @@ def foobar():

self.checkDeprecatedUses(
code,
[('foo', '<>', 5, 4, None), ('foo', '<>', 8, 0, None)])
[('foo', '<>', 6, 4, None),
('foo', '<>', 9, 0, None),
('foobar', '<>', 3, 0, "because it's too old")])

def test_import_from_as(self):
code = '''
Expand Down

0 comments on commit bea2138

Please sign in to comment.