Skip to content

Commit

Permalink
Support implicit imports from sub-packages. (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
hadialqattan authored Oct 14, 2020
1 parent fe0dbeb commit e49c133
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Added

- [Support implicit imports from sub-packages by @hadialqattan](https://github.com/hadialqattan/pycln/pull/36)
- [Support semi string type hint by @hadialqattan](https://github.com/hadialqattan/pycln/pull/35)
- [Support casting case by @hadialqattan](https://github.com/hadialqattan/pycln/pull/34)

Expand Down
12 changes: 12 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,18 @@ All bellow imports are considered as used:
import y
```

### Implicit Imports From Sub-Packages

> Pycln can deal with implicit imports from sub-packages.

For example:

```python
import os.path # marked as used.
print(os.getpid())
```

### Import With Importlib

> Not supported yet, on the roadmap:
Expand Down
19 changes: 18 additions & 1 deletion pycln/utils/refactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ def _get_used_names(
"""
used_names: Set[str] = set()
for alias in node.names:
if self._should_remove(node, alias, is_star):
if self._should_remove(
node, alias, is_star
) and not self._is_partially_used(alias, is_star):
if not is_star:
self.reporter.removed_import(self._path, node, alias)
continue
Expand Down Expand Up @@ -308,6 +310,21 @@ def _expand_import_star(
self.reporter.ignored_import(self._path, node, is_star=True)
return node, None

def _is_partially_used(self, alias: ast.alias, is_star: bool) -> bool:
"""Determine if the alias name partially used or not.
:param alias: an `ast.alias` node.
:param is_star: is it a '*' import.
:returns: whather the alias name partially used or not.
"""
if not alias.asname and "." in alias.name:
names = alias.name.split(".")[1:]
for name in reversed(names):
alias.name = alias.name.rstrip("." + name)
if self._has_used(alias.name, is_star):
return True
return False

def _should_remove(
self, node: Union[Import, ImportFrom], alias: ast.alias, is_star: bool
) -> bool:
Expand Down
18 changes: 18 additions & 0 deletions tests/test_refactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,24 @@ def test_expand_import_star(
enode, is_star = self.session_maker._expand_import_star(node)
assert (enode, is_star) == (node, expec_is_star)

@pytest.mark.parametrize(
"_has_used_return, name, asname, expec_val",
[
pytest.param(True, "os.path.join", None, True, id="used"),
pytest.param(False, "os.path.join", None, False, id="unused"),
pytest.param(None, "os.path.join", "asname", False, id="as alias"),
pytest.param(None, "os", None, False, id="single name"),
],
)
@mock.patch(MOCK % "Refactor._has_used")
def test_is_partially_used(
self, _has_used, _has_used_return, name, asname, expec_val
):
_has_used.return_value = _has_used_return
alias = ast.alias(name=name, asname=asname)
val = self.session_maker._is_partially_used(alias, False)
assert val == expec_val

@pytest.mark.parametrize(
"_has_used_return, _has_side_effects_return, all_, name, expec_val",
[
Expand Down

0 comments on commit e49c133

Please sign in to comment.