Skip to content

Commit 37518ab

Browse files
author
Daniel Sanz
committed
chore: introduce FileHandler
1 parent 396ed97 commit 37518ab

File tree

10 files changed

+122
-72
lines changed

10 files changed

+122
-72
lines changed

src/twyn/core/exceptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ class AllowlistPackageAlreadyExistsError(AllowlistError):
1313

1414
class AllowlistPackageDoesNotExistError(AllowlistError):
1515
message = "Package '{}' is not present in the allowlist. Skipping."
16+

src/twyn/dependency_parser/abstract_parser.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,29 @@
11
import logging
2-
import os
32
from abc import ABC, abstractmethod
4-
from pathlib import Path
53

6-
from twyn.base.exceptions import TwynError
7-
from twyn.dependency_parser.exceptions import (
8-
PathIsNotFileError,
9-
PathNotFoundError,
10-
)
4+
from twyn.file_handler.file_handler import FileHandlerPathlib
115

126
logger = logging.getLogger()
137

148

159
class AbstractParser(ABC):
16-
def __init__(self, file_path: str = "") -> None:
17-
self.file_path = Path(os.path.abspath(os.path.join(os.getcwd(), file_path)))
10+
"""
11+
Abstract class for file parsers.
1812
19-
def __str__(self):
13+
Provides basic methods to deal with the dependecies file.
14+
"""
15+
16+
def __init__(self, file_path: str) -> None:
17+
self.file_handler = FileHandlerPathlib(file_path=file_path)
18+
19+
def __str__(self) -> str:
2020
return self.__class__.__name__
2121

2222
def _read(self) -> str:
23-
content = self.file_path.read_text()
24-
logger.debug("Successfully read content from local dependencies file")
25-
26-
return content
23+
return self.file_handler.read()
2724

2825
def file_exists(self) -> bool:
29-
try:
30-
self.raise_for_valid_file()
31-
except TwynError:
32-
return False
33-
return True
34-
35-
def raise_for_valid_file(self) -> None:
36-
if not self.file_path.exists():
37-
raise PathNotFoundError
38-
39-
if not self.file_path.is_file():
40-
raise PathIsNotFileError
26+
return self.file_handler.file_exists()
4127

4228
@abstractmethod
4329
def parse(self) -> set[str]:

src/twyn/dependency_parser/dependency_selector.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,5 @@ def get_dependency_parser(self) -> AbstractParser:
5656

5757
file_parser = dependency_file_parser()
5858
logger.debug(f"Assigned {file_parser} parser for local dependencies file.")
59-
file_parser.raise_for_valid_file()
6059

6160
return file_parser
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
11
from twyn.base.exceptions import TwynError
22

33

4-
class PathIsNotFileError(TwynError):
5-
message = "Specified dependencies path is not a file"
6-
7-
8-
class PathNotFoundError(TwynError, FileNotFoundError):
9-
message = "Specified dependencies file path does not exist"
10-
11-
124
class NoMatchingParserError(TwynError):
135
message = "Could not assign a dependency file parser. Please specify it with --dependency-file"
146

157

168
class MultipleParsersError(TwynError):
17-
message = (
18-
"Can't auto detect dependencies file to parse. More than one format was found."
19-
)
9+
message = "Can't auto detect dependencies file to parse. More than one format was found."
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from twyn.base.exceptions import TwynError
2+
3+
4+
class PathIsNotFileError(TwynError):
5+
message = "Specified dependencies path is not a file"
6+
7+
8+
class PathNotFoundError(TwynError, FileNotFoundError):
9+
message = "Specified dependencies file path does not exist"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import logging
2+
import os
3+
from pathlib import Path
4+
from typing import Protocol
5+
6+
from twyn.base.exceptions import TwynError
7+
from twyn.file_handler.exceptions import PathIsNotFileError, PathNotFoundError
8+
9+
logger = logging.getLogger()
10+
11+
12+
class BaseFileHandler(Protocol):
13+
def __init__(self, file_path: str) -> None: ...
14+
def read(self) -> str: ...
15+
def file_exists(self) -> bool: ...
16+
17+
18+
class FileHandlerPathlib(BaseFileHandler):
19+
def __init__(self, file_path: str) -> None:
20+
self.file_path = Path(os.path.abspath(os.path.join(os.getcwd(), file_path)))
21+
22+
def read(self) -> str:
23+
self._raise_for_file_exists()
24+
25+
content = self.file_path.read_text()
26+
logger.debug("Successfully read content from local dependencies file")
27+
28+
return content
29+
30+
def file_exists(self) -> bool:
31+
try:
32+
self._raise_for_file_exists()
33+
except TwynError:
34+
return False
35+
return True
36+
37+
def _raise_for_file_exists(self) -> None:
38+
if not self.file_path.exists():
39+
raise PathNotFoundError
40+
41+
if not self.file_path.is_file():
42+
raise PathIsNotFileError

tests/conftest.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import os
2+
from typing import Generator
23

34
import pytest
45

56

67
@pytest.fixture
7-
def requirements_txt_file(tmp_path):
8+
def requirements_txt_file(tmp_path) -> Generator[str, None, None]:
89
requirements_txt_file = tmp_path / "requirements.txt"
910
requirements_txt_file.write_text(
1011
"""
1112
South==1.0.1 --hash=sha256:abcdefghijklmno
1213
pycrypto>=2.6
1314
"""
1415
)
15-
yield requirements_txt_file
16+
yield str(requirements_txt_file)
1617
os.remove(requirements_txt_file)
1718

1819

1920
@pytest.fixture
20-
def poetry_lock_file_lt_1_5(tmp_path):
21+
def poetry_lock_file_lt_1_5(tmp_path) -> Generator[str, None, None]:
2122
"""Poetry lock version < 1.5."""
2223
poetry_lock_file = tmp_path / "poetry.lock"
2324
poetry_lock_file.write_text(
@@ -61,12 +62,12 @@ def poetry_lock_file_lt_1_5(tmp_path):
6162
mccabe = []
6263
"""
6364
)
64-
yield poetry_lock_file
65+
yield str(poetry_lock_file)
6566
os.remove(poetry_lock_file)
6667

6768

6869
@pytest.fixture
69-
def poetry_lock_file_ge_1_5(tmp_path):
70+
def poetry_lock_file_ge_1_5(tmp_path) -> Generator[str, None, None]:
7071
"""Poetry lock version >= 1.5."""
7172
poetry_lock_file = tmp_path / "poetry.lock"
7273
poetry_lock_file.write_text(
@@ -107,12 +108,12 @@ def poetry_lock_file_ge_1_5(tmp_path):
107108
mccabe = []
108109
"""
109110
)
110-
yield poetry_lock_file
111+
yield str(poetry_lock_file)
111112
os.remove(poetry_lock_file)
112113

113114

114115
@pytest.fixture
115-
def pyproject_toml_file(tmp_path):
116+
def pyproject_toml_file(tmp_path) -> Generator[str, None, None]:
116117
pyproject_toml = tmp_path / "pyproject.toml"
117118
pyproject_toml.write_text(
118119
"""
@@ -136,5 +137,5 @@ def pyproject_toml_file(tmp_path):
136137
137138
"""
138139
)
139-
yield pyproject_toml
140+
yield str(pyproject_toml)
140141
os.remove(pyproject_toml)

tests/dependency_parser/test_dependency_parser.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from unittest.mock import patch
22

33
import pytest
4-
from twyn.base.exceptions import TwynError
54
from twyn.dependency_parser import PoetryLockParser, RequirementsTxtParser
65
from twyn.dependency_parser.abstract_parser import AbstractParser
7-
from twyn.dependency_parser.exceptions import PathIsNotFileError, PathNotFoundError
6+
from twyn.file_handler.exceptions import PathIsNotFileError, PathNotFoundError
87

98

109
class TestAbstractParser:
@@ -15,34 +14,26 @@ def parse(self) -> set[str]:
1514
self._read()
1615
return set()
1716

18-
@patch("twyn.dependency_parser.abstract_parser.AbstractParser.raise_for_valid_file")
19-
def test_file_exists_success(self, _mock_raise_for_valid_file):
17+
@patch("pathlib.Path.exists")
18+
@patch("pathlib.Path.is_file")
19+
def test_file_exists(self, _mock_exists, _mock_is_file):
20+
_mock_exists.return_value = True
21+
_mock_is_file.return_value = True
2022
parser = self.TemporaryParser("fake_path.txt")
2123
assert parser.file_exists() is True
2224

23-
@patch("twyn.dependency_parser.abstract_parser.AbstractParser.raise_for_valid_file")
24-
def test_file_exists_fail(self, mock_raise_for_valid_file):
25-
def raise_twyn_error():
26-
raise TwynError
27-
28-
mock_raise_for_valid_file.side_effect = raise_twyn_error
29-
parser = self.TemporaryParser("fake_path.txt")
30-
assert parser.file_exists() is False
31-
3225
@patch("pathlib.Path.exists")
3326
@patch("pathlib.Path.is_file")
3427
@pytest.mark.parametrize(
3528
"file_exists, is_file, exception",
3629
[[False, False, PathNotFoundError], [True, False, PathIsNotFileError]],
3730
)
38-
def test_raise_for_valid_file(
39-
self, mock_is_file, mock_exists, file_exists, is_file, exception
40-
):
31+
def test_raise_for_valid_file(self, mock_is_file, mock_exists, file_exists, is_file, exception):
4132
mock_exists.return_value = file_exists
4233
mock_is_file.return_value = is_file
4334

44-
with pytest.raises(exception):
45-
self.TemporaryParser("fake_path").raise_for_valid_file()
35+
parser = self.TemporaryParser("fake_path.txt")
36+
assert parser.file_exists() is False
4637

4738

4839
class TestRequirementsTxtParser:

tests/dependency_parser/test_dependency_selector.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212
class TestDependencySelector:
1313
@patch("twyn.dependency_parser.poetry_lock.PoetryLockParser.file_exists")
1414
@patch("twyn.dependency_parser.requirements_txt.RequirementsTxtParser.file_exists")
15-
@patch("twyn.dependency_parser.abstract_parser.AbstractParser.raise_for_valid_file")
16-
@patch(
17-
"twyn.dependency_parser.dependency_selector.DependencySelector._raise_for_selected_parsers"
18-
)
15+
@patch("twyn.dependency_parser.dependency_selector.DependencySelector._raise_for_selected_parsers")
1916
@pytest.mark.parametrize(
2017
"file_name, requirements_exists, poetry_exists, parser_obj",
2118
[
@@ -35,7 +32,6 @@ class TestDependencySelector:
3532
def test_get_dependency_parser(
3633
self,
3734
_raise_for_selected_parsers,
38-
_raise_for_valid_file,
3935
req_exists,
4036
poet_exists,
4137
file_name,
@@ -53,9 +49,7 @@ def test_get_dependency_parser(
5349
[(True, MultipleParsersError), (False, NoMatchingParserError)],
5450
)
5551
@patch("twyn.dependency_parser.abstract_parser.AbstractParser.file_exists")
56-
def test_auto_detect_dependency_file_parser_exceptions(
57-
self, file_exists, exists, exception
58-
):
52+
def test_auto_detect_dependency_file_parser_exceptions(self, file_exists, exists, exception):
5953
file_exists.return_value = exists
6054
with pytest.raises(exception):
6155
DependencySelector().get_dependency_parser()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from unittest.mock import patch
2+
3+
import pytest
4+
from twyn.file_handler.exceptions import PathIsNotFileError, PathNotFoundError
5+
from twyn.file_handler.file_handler import FileHandlerPathlib
6+
7+
8+
class TestFileHandlerPathlib:
9+
def test_file_exists(self, pyproject_toml_file: str):
10+
parser = FileHandlerPathlib(pyproject_toml_file)
11+
assert parser.file_exists() is True
12+
13+
def test_read_file_success(self, pyproject_toml_file: str):
14+
parser = FileHandlerPathlib(pyproject_toml_file)
15+
read = parser.read()
16+
assert len(read) > 1
17+
assert isinstance(read, str)
18+
19+
def test_read_file_does_not_exist(
20+
self,
21+
):
22+
parser = FileHandlerPathlib("")
23+
with pytest.raises(PathIsNotFileError):
24+
parser.read()
25+
26+
@patch("pathlib.Path.exists")
27+
@patch("pathlib.Path.is_file")
28+
@pytest.mark.parametrize(
29+
"file_exists, is_file, exception",
30+
[[False, False, PathNotFoundError], [True, False, PathIsNotFileError]],
31+
)
32+
def test_raise_for_valid_file(self, mock_is_file, mock_exists, file_exists, is_file, exception):
33+
mock_exists.return_value = file_exists
34+
mock_is_file.return_value = is_file
35+
36+
parser = FileHandlerPathlib("fake.txt")
37+
assert parser.file_exists() is False

0 commit comments

Comments
 (0)