Skip to content

Commit

Permalink
Fix: encountering a non-existing __init__.py file case (#215)
Browse files Browse the repository at this point in the history
* Fix: encountering non-existing __init__.py file case

* Add: tests

* Add: docs
  • Loading branch information
hadialqattan authored Aug 4, 2023
1 parent dc1be92 commit 1afd275
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 3 deletions.
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Fixed

- [Running Pycln in a virtual env against a folder (that has no `__init__.py` file) contains sub-packages causes ReadPermissionError by @hadialqattan](https://github.com/hadialqattan/pycln/pull/215)

## [2.2.0] - 2023-07-31

### Changed
Expand Down
6 changes: 6 additions & 0 deletions pycln/utils/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class WritePermissionError(BaseOSError):
"""Raises when the file does not have write permission."""


class InitFileDoesNotExistError(BaseOSError):

"""Raises when an `__init__.py` file path encountered of a non-existing
path."""


class UnexpandableImportStar(Exception):

"""Raises when the import `*` statement unexpandable."""
Expand Down
14 changes: 13 additions & 1 deletion pycln/utils/iou.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
from pathlib import Path
from typing import List, Tuple

from ._exceptions import ReadPermissionError, UnparsableFile, WritePermissionError
from ._exceptions import (
InitFileDoesNotExistError,
ReadPermissionError,
UnparsableFile,
WritePermissionError,
)

# Constants.
STDIN_FILE = Path("STDIN")
STDIN_NOTATION = Path("-")
FORM_FEED_CHAR = "\x0c"
CRLF = "\r\n"
LF = "\n"
__INIT__ = "__init__.py"

# Types
FileContent = str
Expand Down Expand Up @@ -61,7 +67,13 @@ def safe_read(
and the source does not have write permission.
:raises UnparsableFile: If both a BOM and a cookie are present, but disagree.
or some rare characters presented.
:raises InitFileDoesNotExistError: when `path` is a path to a non-existing
`__init__.py` file.
"""
# Check for a non-existing `__init__.py` file case.
if str(path).endswith(__INIT__) and not path.exists():
raise InitFileDoesNotExistError(2, "`__init__.py` file does not exist", path)

# Check these permissions before openinig the file.
for permission in permissions:
if not os.access(path, permission):
Expand Down
3 changes: 3 additions & 0 deletions pycln/utils/refactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .. import ISWIN
from . import iou, pathu, regexu, scan
from ._exceptions import (
InitFileDoesNotExistError,
ReadPermissionError,
UnexpandableImportStar,
UnparsableFile,
Expand Down Expand Up @@ -175,6 +176,8 @@ def session(self, path: Path) -> None:
UnparsableFile,
) as err:
self.reporter.failure(str(err))
except InitFileDoesNotExistError:
pass
finally:
self._reset()

Expand Down
30 changes: 28 additions & 2 deletions tests/test_iou.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""pycln/utils/iou.py tests."""
# pylint: disable=R0201,W0613
import os
from pathlib import Path
from typing import List
from unittest import mock

Expand All @@ -10,6 +11,7 @@
from pycln import ISWIN
from pycln.utils import iou
from pycln.utils._exceptions import (
InitFileDoesNotExistError,
ReadPermissionError,
UnparsableFile,
WritePermissionError,
Expand Down Expand Up @@ -76,9 +78,10 @@ def test_read_stdin(
raise sysu.Pass()

@pytest.mark.parametrize(
"content, expec_code, expec_newline, expec_err, chmod",
"file_path, content, expec_code, expec_newline, expec_err, chmod",
[
pytest.param(
None, # None means using the generated temp file path.
"print('Hello')",
"print('Hello')",
iou.LF,
Expand All @@ -87,6 +90,16 @@ def test_read_stdin(
id="bast case",
),
pytest.param(
"DoesNotExistInit/__init__.py",
None,
None,
None,
InitFileDoesNotExistError,
0o0644,
id="Init file does not exist",
),
pytest.param(
None,
"code...",
None,
None,
Expand All @@ -98,6 +111,7 @@ def test_read_stdin(
),
),
pytest.param(
None,
"code...",
None,
None,
Expand All @@ -109,6 +123,7 @@ def test_read_stdin(
),
),
pytest.param(
None,
#: Make conflict between BOM and encoding Cookie.
#: For more information: https://bit.ly/32o3eVl
"\ufeff\n# -*- coding: utf-32 -*-\nbad encoding",
Expand All @@ -119,6 +134,7 @@ def test_read_stdin(
id="bad encoding",
),
pytest.param(
None,
"try: pass\x0c;\nfinally: pass",
None,
None,
Expand All @@ -127,6 +143,7 @@ def test_read_stdin(
id="form feed char",
),
pytest.param(
None,
"print('Hello')\r\n",
"print('Hello')\n\n",
iou.CRLF,
Expand All @@ -135,6 +152,7 @@ def test_read_stdin(
id="detect CRLF",
),
pytest.param(
None,
"print('Hello')\n",
"print('Hello')\n",
iou.LF,
Expand All @@ -148,9 +166,17 @@ def test_read_stdin(
],
)
def test_safe_read(
self, content: str, expec_code: str, expec_newline: str, expec_err, chmod: int
self,
file_path: str,
content: str,
expec_code: str,
expec_newline: str,
expec_err,
chmod: int,
):
with pytest.raises(expec_err):
if file_path:
iou.safe_read(Path(file_path))
if expec_newline:
content = content.replace(os.linesep, expec_newline)
with sysu.reopenable_temp_file(content) as tmp_path:
Expand Down
8 changes: 8 additions & 0 deletions tests/test_refactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from pycln.utils import config, iou, refactor, report
from pycln.utils._exceptions import (
InitFileDoesNotExistError,
ReadPermissionError,
UnexpandableImportStar,
UnparsableFile,
Expand Down Expand Up @@ -302,6 +303,13 @@ def test_remove_useless_passes(self, source_lines, expec_lines):
UnparsableFile(Path(""), SyntaxError("")),
id="UnparsableFile[code-session]",
),
pytest.param(
Path("DoesNotExistInitFile/__init__.py"),
None,
InitFileDoesNotExistError(2, "", Path("")),
None,
id="InitFileDoesNotExistError[file]",
),
],
)
@mock.patch(MOCK % "Refactor._output")
Expand Down

0 comments on commit 1afd275

Please sign in to comment.